Overview
  • Namespace
  • Class

Namespaces

  • Klarna
    • XMLRPC
      • Exception

Interfaces

  • Klarna\XMLRPC\Exception\KlarnaException
   1 <?php
   2 /**
   3  * Copyright 2016 Klarna AB.
   4  *
   5  * Licensed under the Apache License, Version 2.0 (the "License");
   6  * you may not use this file except in compliance with the License.
   7  * You may obtain a copy of the License at
   8  *
   9  *     http://www.apache.org/licenses/LICENSE-2.0
  10  *
  11  * Unless required by applicable law or agreed to in writing, software
  12  * distributed under the License is distributed on an "AS IS" BASIS,
  13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14  * See the License for the specific language governing permissions and
  15  * limitations under the License.
  16  */
  17 namespace Klarna\XMLRPC;
  18 
  19 use PhpXmlRpc;
  20 
  21 /**
  22  * This API provides a way to integrate with Klarna's services over the
  23  * XMLRPC protocol.
  24  *
  25  * All strings inputted need to be encoded with ISO-8859-1.<br>
  26  * In addition you need to decode HTML entities, if they exist.<br>
  27  */
  28 class Klarna
  29 {
  30     /**
  31      * Klarna PHP API version identifier.
  32      *
  33      * @var string
  34      */
  35     protected $version = 'php:api:5.0.0';
  36 
  37     /**
  38      * Klarna protocol identifier.
  39      *
  40      * @var string
  41      */
  42     protected $proto = '4.1';
  43 
  44     /**
  45      * The encoding used in Klarna.
  46      *
  47      * @var string
  48      */
  49     protected $encoding = 'ISO-8859-1';
  50 
  51     /**
  52      * Constants used with LIVE mode for the communications with Klarna.
  53      *
  54      * @var int
  55      */
  56     const LIVE = 0;
  57 
  58     /**
  59      * URL/Address to the live Klarna Online server.
  60      *
  61      * @var string
  62      */
  63     private static $liveAddr = 'payment.klarna.com';
  64 
  65     /**
  66      * Constants used with BETA mode for the communications with Klarna.
  67      *
  68      * @var int
  69      */
  70     const BETA = 1;
  71 
  72     /**
  73      * URL/Address to the beta test Klarna Online server.
  74      *
  75      * @var string
  76      */
  77     private static $betaAddr = 'payment.testdrive.klarna.com';
  78 
  79     /**
  80      * An object of PhpXmlRpc\Client, used to communicate with Klarna.
  81      *
  82      * @link https://packagist.org/packages/phpxmlrpc/phpxmlrpc
  83      *
  84      * @var PhpXmlRpc\Client
  85      */
  86     protected $xmlrpc;
  87 
  88     /**
  89      * Which server the Klarna API is using, LIVE or BETA (TESTING).
  90      *
  91      * @see Klarna::LIVE
  92      * @see Klarna::BETA
  93      *
  94      * @var int
  95      */
  96     protected $mode;
  97 
  98     /**
  99      * Associative array holding url information.
 100      *
 101      * @var array
 102      */
 103     private $url;
 104 
 105     /**
 106      * The estore's identifier received from Klarna.
 107      *
 108      * @var int
 109      */
 110     private $eid;
 111 
 112     /**
 113      * The estore's shared secret received from Klarna.
 114      *
 115      * <b>Note</b>:<br>
 116      * DO NOT SHARE THIS WITH ANYONE!
 117      *
 118      * @var string
 119      */
 120     private $secret;
 121 
 122     /**
 123      * Country constant.
 124      *
 125      * @see Country
 126      *
 127      * @var int
 128      */
 129     private $country;
 130 
 131     /**
 132      * Currency constant.
 133      *
 134      * @see Currency
 135      *
 136      * @var int
 137      */
 138     private $currency;
 139 
 140     /**
 141      * Language constant.
 142      *
 143      * @see Language
 144      *
 145      * @var int
 146      */
 147     private $language;
 148 
 149     /**
 150      * An array of articles for the current order.
 151      *
 152      * @var array
 153      */
 154     protected $goodsList;
 155 
 156     /**
 157      * An array of article numbers and quantity.
 158      *
 159      * @var array
 160      */
 161     protected $artNos;
 162 
 163     /**
 164      * An Address object containing the billing address.
 165      *
 166      * @var Address
 167      */
 168     protected $billing;
 169 
 170     /**
 171      * An Address object containing the shipping address.
 172      *
 173      * @var Address
 174      */
 175     protected $shipping;
 176 
 177     /**
 178      * External order numbers from other systems.
 179      *
 180      * @var string
 181      */
 182     protected $orderid = array('', '');
 183 
 184     /**
 185      * Reference (person) parameter.
 186      *
 187      * @var string
 188      */
 189     protected $reference = '';
 190 
 191     /**
 192      * Reference code parameter.
 193      *
 194      * @var string
 195      */
 196     protected $reference_code = '';
 197 
 198     /**
 199      * An array of named extra info.
 200      *
 201      * @var array
 202      */
 203     protected $extraInfo = array();
 204 
 205     /**
 206      * An array of named bank info.
 207      *
 208      * @var array
 209      */
 210     protected $bankInfo = array();
 211 
 212     /**
 213      * An array of named income expense info.
 214      *
 215      * @var array
 216      */
 217     protected $incomeInfo = array();
 218 
 219     /**
 220      * An array of named shipment info.
 221      *
 222      * @var array
 223      */
 224     protected $shipInfo = array();
 225 
 226     /**
 227      * An array of named travel info.
 228      *
 229      * @ignore Do not show this in PHPDoc.
 230      *
 231      * @var array
 232      */
 233     protected $travelInfo = array();
 234 
 235     /**
 236      * An array of named activate info.
 237      *
 238      * @ignore
 239      *
 240      * @var array
 241      */
 242     protected $activateInfo = array();
 243 
 244     /**
 245      * An array of named session id's.<br>
 246      * E.g. "dev_id_1" => ...<br>.
 247      *
 248      * @var array
 249      */
 250     protected $sid = array();
 251 
 252     /**
 253      * A comment sent in the XMLRPC communications.
 254      * This is resetted using clear().
 255      *
 256      * @var string
 257      */
 258     protected $comment = '';
 259 
 260     /**
 261      * Flag to indicate if the API should output verbose
 262      * debugging information.
 263      *
 264      * @var bool
 265      */
 266     public static $debug = false;
 267 
 268     /**
 269      * Turns on the internal XMLRPC debugging.
 270      *
 271      * @var bool
 272      */
 273     public static $xmlrpcDebug = false;
 274 
 275     /**
 276      * If this is set to true, XMLRPC invocation is disabled.
 277      *
 278      * @var bool
 279      */
 280     public static $disableXMLRPC = false;
 281 
 282     /**
 283      * If the estore is using a proxy which populates the clients IP to
 284      * x_forwarded_for
 285      * then and only then should this be set to true.
 286      *
 287      * <b>Note</b>:<br>
 288      * USE WITH CARE!
 289      *
 290      * @var bool
 291      */
 292     public static $x_forwarded_for = false;
 293 
 294     /**
 295      * Array of HTML entities, used to create numeric htmlentities.
 296      *
 297      * @ignore Do not show this in PHPDoc.
 298      *
 299      * @var array
 300      */
 301     protected static $htmlentities = false;
 302 
 303     /**
 304      * PClass list.
 305      *
 306      * @ignore Do not show this in PHPDoc.
 307      *
 308      * @var PClass[]
 309      */
 310     protected $pclasses;
 311 
 312     /**
 313      * \ArrayAccess instance.
 314      *
 315      * @ignore Do not show this in PHPDoc.
 316      *
 317      * @var \ArrayAccess
 318      */
 319     protected $config;
 320 
 321     /**
 322      * Client IP.
 323      *
 324      * @var string
 325      */
 326     protected $clientIP;
 327 
 328     /**
 329      * Empty constructor, because sometimes it's needed.
 330      */
 331     public function __construct()
 332     {
 333     }
 334 
 335     /**
 336      * Checks if the config has fields described in argument.<br>
 337      * Missing field(s) is in the exception message.
 338      *
 339      * To check that the config has eid and secret:<br>
 340      * <code>
 341      * try {
 342      *     $this->hasFields('eid', 'secret');
 343      * }
 344      * catch(\Exception $e) {
 345      *     echo "Missing fields: " . $e->getMessage();
 346      * }
 347      * </code>
 348      *
 349      * @throws \RuntimeException
 350      */
 351     protected function hasFields()
 352     {
 353         $missingFields = array();
 354         $args = func_get_args();
 355         foreach ($args as $field) {
 356             if (!isset($this->config[$field])) {
 357                 $missingFields[] = $field;
 358             }
 359         }
 360         if (count($missingFields) > 0) {
 361             throw new \RuntimeException(
 362                 'Config missing fields: '.implode(', ', $missingFields)
 363             );
 364         }
 365     }
 366 
 367     /**
 368      * Initializes the Klarna object accordingly to the set config object.
 369      *
 370      * @throws \RuntimeException For invalid configuration
 371      */
 372     protected function init()
 373     {
 374         $this->hasFields('eid', 'secret', 'mode');
 375 
 376         if (!is_int($this->config['eid'])) {
 377             $this->config['eid'] = intval($this->config['eid']);
 378         }
 379 
 380         if ($this->config['eid'] <= 0) {
 381             throw new \RuntimeException('Config field eid is not valid!');
 382         }
 383 
 384         if (!is_string($this->config['secret'])) {
 385             $this->config['secret'] = strval($this->config['secret']);
 386         }
 387 
 388         if (strlen($this->config['secret']) == 0) {
 389             throw new \RuntimeException('Config field secret is not valid!');
 390         }
 391 
 392         //Set the shop id and secret.
 393         $this->eid = $this->config['eid'];
 394         $this->secret = $this->config['secret'];
 395 
 396         //Set the country specific attributes.
 397         try {
 398             $this->hasFields('country', 'language', 'currency');
 399 
 400             //If hasFields doesn't throw exception we can set them all.
 401             $this->setCountry($this->config['country']);
 402             $this->setLanguage($this->config['language']);
 403             $this->setCurrency($this->config['currency']);
 404         } catch (\Exception $e) {
 405             //fields missing for country, language or currency
 406             $this->country = $this->language = $this->currency = null;
 407         }
 408 
 409         //Set addr and port according to mode.
 410         $this->mode = (int) $this->config['mode'];
 411 
 412         $this->url = array();
 413 
 414         // If a custom url has been added to the config, use that as xmlrpc
 415         // recipient.
 416         if (isset($this->config['url'])) {
 417             $this->url = parse_url($this->config['url']);
 418             if ($this->url === false) {
 419                 $message = "Configuration value 'url' could not be parsed. ".
 420                     "(Was: '{$this->config['url']}')";
 421                 self::printDebug(__METHOD__, $message);
 422                 throw new \RuntimeException($message);
 423             }
 424         } else {
 425             $this->url['scheme'] = 'https';
 426             $this->url['port'] = 443;
 427 
 428             if ($this->mode === self::LIVE) {
 429                 $this->url['host'] = self::$liveAddr;
 430             } else {
 431                 $this->url['host'] = self::$betaAddr;
 432             }
 433         }
 434 
 435         try {
 436             $this->hasFields('xmlrpcDebug');
 437             self::$xmlrpcDebug = $this->config['xmlrpcDebug'];
 438         } catch (\Exception $e) {
 439             //No 'xmlrpcDebug' field ignore it...
 440         }
 441 
 442         try {
 443             $this->hasFields('debug');
 444             self::$debug = $this->config['debug'];
 445         } catch (\Exception $e) {
 446             //No 'debug' field ignore it...
 447         }
 448 
 449         // Default path to '/' if not set.
 450         if (!array_key_exists('path', $this->url)) {
 451             $this->url['path'] = '/';
 452         }
 453 
 454         $this->xmlrpc = new PhpXmlRpc\Client(
 455             $this->url['path'],
 456             $this->url['host'],
 457             $this->url['port'],
 458             $this->url['scheme']
 459         );
 460 
 461         $this->xmlrpc->setSSLVerifyHost(2);
 462 
 463         $this->xmlrpc->request_charset_encoding = $this->encoding;
 464     }
 465 
 466     /**
 467      * Method of ease for setting common config fields.
 468      *
 469      * <b>Note</b>:<br>
 470      * This disables the config file storage.<br>
 471      *
 472      * @param int    $eid      Merchant ID/EID
 473      * @param string $secret   Secret key/Shared key
 474      * @param int    $country  {@link Country}
 475      * @param int    $language {@link Language}
 476      * @param int    $currency {@link Currency}
 477      * @param int    $mode     {@link Klarna::LIVE} or {@link Klarna::BETA}
 478      *
 479      * @see Klarna::setConfig()
 480      * @see Config
 481      *
 482      * @throws Exception\KlarnaException
 483      */
 484     public function config(
 485         $eid,
 486         $secret,
 487         $country,
 488         $language,
 489         $currency,
 490         $mode = self::LIVE
 491     ) {
 492         try {
 493             $this->config = new Config(null);
 494 
 495             $this->config['eid'] = $eid;
 496             $this->config['secret'] = $secret;
 497             $this->config['country'] = $country;
 498             $this->config['language'] = $language;
 499             $this->config['currency'] = $currency;
 500             $this->config['mode'] = $mode;
 501 
 502             $this->init();
 503         } catch (\Exception $e) {
 504             $this->config = null;
 505             throw new Exception\KlarnaException(
 506                 $e->getMessage(),
 507                 $e->getCode()
 508             );
 509         }
 510     }
 511 
 512     /**
 513      * Sets and initializes this Klarna object using the supplied config object.
 514      *
 515      * @param Config $config Config object.
 516      *
 517      * @see Config
 518      *
 519      * @throws Exception\KlarnaException
 520      */
 521     public function setConfig(&$config)
 522     {
 523         $this->checkConfig($config);
 524 
 525         $this->config = $config;
 526         $this->init();
 527     }
 528 
 529     /**
 530      * Get the complete locale (country, language, currency) to use for the
 531      * values passed, or the configured value if passing null.
 532      *
 533      * @param mixed $country  country  constant or code
 534      * @param mixed $language language constant or code
 535      * @param mixed $currency currency constant or code
 536      *
 537      * @throws Exception\KlarnaException
 538      *
 539      * @return array
 540      */
 541     public function getLocale(
 542         $country = null,
 543         $language = null,
 544         $currency = null
 545     ) {
 546         $locale = array(
 547             'country' => null,
 548             'language' => null,
 549             'currency' => null,
 550         );
 551 
 552         if ($country === null) {
 553             // Use the configured country / language / currency
 554             $locale['country'] = $this->country;
 555             if ($this->language !== null) {
 556                 $locale['language'] = $this->language;
 557             }
 558 
 559             if ($this->currency !== null) {
 560                 $locale['currency'] = $this->currency;
 561             }
 562         } else {
 563             // Use the given country / language / currency
 564             if (!is_numeric($country)) {
 565                 $country = Country::fromCode($country);
 566             }
 567             $locale['country'] = intval($country);
 568 
 569             if ($language !== null) {
 570                 if (!is_numeric($language)) {
 571                     $language = Language::fromCode($language);
 572                 }
 573                 $locale['language'] = intval($language);
 574             }
 575 
 576             if ($currency !== null) {
 577                 if (!is_numeric($currency)) {
 578                     $currency = Currency::fromCode($currency);
 579                 }
 580                 $locale['currency'] = intval($currency);
 581             }
 582         }
 583 
 584         // Complete partial structure with defaults
 585         if ($locale['currency'] === null) {
 586             $locale['currency'] = $this->getCurrencyForCountry(
 587                 $locale['country']
 588             );
 589         }
 590 
 591         if ($locale['language'] === null) {
 592             $locale['language'] = $this->getLanguageForCountry(
 593                 $locale['country']
 594             );
 595         }
 596 
 597         $this->checkCountry($locale['country']);
 598         $this->checkCurrency($locale['currency']);
 599         $this->checkLanguage($locale['language']);
 600 
 601         return $locale;
 602     }
 603 
 604     /**
 605      * Sets the country used.
 606      *
 607      * <b>Note</b>:<br>
 608      * If you input 'dk', 'fi', 'de', 'nl', 'no' or 'se', <br>
 609      * then currency and language will be set to mirror that country.<br>
 610      *
 611      * @param string|int $country {@link Country}
 612      *
 613      * @see Country
 614      *
 615      * @throws Exception\KlarnaException
 616      */
 617     public function setCountry($country)
 618     {
 619         if (!is_numeric($country)
 620             && (strlen($country) == 2 || strlen($country) == 3)
 621         ) {
 622             $country = Country::fromCode($country);
 623         }
 624         $this->checkCountry($country);
 625         $this->country = $country;
 626     }
 627 
 628     /**
 629      * Returns the country code for the set country constant.
 630      *
 631      * @param int $country {@link Country Country} constant.
 632      *
 633      * @return string Two letter code, e.g. "se", "no", etc.
 634      */
 635     public function getCountryCode($country = null)
 636     {
 637         if ($country === null) {
 638             $country = $this->country;
 639         }
 640 
 641         $code = Country::getCode($country);
 642 
 643         return (string) $code;
 644     }
 645 
 646     /**
 647      * Returns the {@link Country country} constant from the country code.
 648      *
 649      * @param string $code Two letter code, e.g. "se", "no", etc.
 650      *
 651      * @throws \RuntimeException
 652      *
 653      * @return int {@link Country Country} constant.
 654      */
 655     public static function getCountryForCode($code)
 656     {
 657         $country = Country::fromCode($code);
 658         if ($country === null) {
 659             throw new \RuntimeException("Unknown country code: {$code}");
 660         }
 661 
 662         return $country;
 663     }
 664 
 665     /**
 666      * Returns the country constant.
 667      *
 668      * @return int {@link Country}
 669      */
 670     public function getCountry()
 671     {
 672         return $this->country;
 673     }
 674 
 675     /**
 676      * Sets the language used.
 677      *
 678      * <b>Note</b>:<br>
 679      * You can use the two letter language code instead of the constant.<br>
 680      * E.g. 'da' instead of using {@link Language::DA}.<br>
 681      *
 682      * @param string|int $language {@link Language}
 683      *
 684      * @see Language
 685      *
 686      * @throws Exception\KlarnaException
 687      */
 688     public function setLanguage($language)
 689     {
 690         if (!is_numeric($language) && strlen($language) == 2) {
 691             $this->setLanguage(self::getLanguageForCode($language));
 692         } else {
 693             $this->checkLanguage($language);
 694             $this->language = $language;
 695         }
 696     }
 697 
 698     /**
 699      * Returns the language code for the set language constant.
 700      *
 701      * @param int $language {@link Language Language} constant.
 702      *
 703      * @return string Two letter code, e.g. "da", "de", etc.
 704      */
 705     public function getLanguageCode($language = null)
 706     {
 707         if ($language === null) {
 708             $language = $this->language;
 709         }
 710         $code = Language::getCode($language);
 711 
 712         return (string) $code;
 713     }
 714 
 715     /**
 716      * Returns the {@link Language language} constant from the language code.
 717      *
 718      * @param string $code Two letter code, e.g. "da", "de", etc.
 719      *
 720      * @throws \RuntimeException
 721      *
 722      * @return int {@link Language Language} constant.
 723      */
 724     public static function getLanguageForCode($code)
 725     {
 726         $language = Language::fromCode($code);
 727 
 728         if ($language === null) {
 729             throw new \RuntimeException("Unknown language code: {$code}");
 730         }
 731 
 732         return $language;
 733     }
 734 
 735     /**
 736      * Returns the language constant.
 737      *
 738      * @return int {@link Language}
 739      */
 740     public function getLanguage()
 741     {
 742         return $this->language;
 743     }
 744 
 745     /**
 746      * Sets the currency used.
 747      *
 748      * <b>Note</b>:<br>
 749      * You can use the three letter shortening of the currency.<br>
 750      * E.g. "dkk", "eur", "nok" or "sek" instead of the constant.<br>
 751      *
 752      * @param string|int $currency {@link Currency}
 753      *
 754      * @see Currency
 755      *
 756      * @throws Exception\KlarnaException
 757      */
 758     public function setCurrency($currency)
 759     {
 760         if (!is_numeric($currency) && strlen($currency) == 3) {
 761             $this->setCurrency(self::getCurrencyForCode($currency));
 762         } else {
 763             $this->checkCurrency($currency);
 764             $this->currency = $currency;
 765         }
 766     }
 767 
 768     /**
 769      * Returns the {@link Currency currency} constant from the currency
 770      * code.
 771      *
 772      * @param string $code Two letter code, e.g. "dkk", "eur", etc.
 773      *
 774      * @throws \RuntimeException
 775      *
 776      * @return int {@link Currency Currency} constant.
 777      */
 778     public static function getCurrencyForCode($code)
 779     {
 780         $currency = Currency::fromCode($code);
 781         if ($currency === null) {
 782             throw new \RuntimeException("Unknown currency code: {$code}");
 783         }
 784 
 785         return $currency;
 786     }
 787 
 788     /**
 789      * Returns the the currency code for the set currency constant.
 790      *
 791      * @param int $currency {@link Currency Currency} constant.
 792      *
 793      * @return string Three letter currency code.
 794      */
 795     public function getCurrencyCode($currency = null)
 796     {
 797         if ($currency === null) {
 798             $currency = $this->currency;
 799         }
 800 
 801         $code = Currency::getCode($currency);
 802 
 803         return (string) $code;
 804     }
 805 
 806     /**
 807      * Returns the set currency constant.
 808      *
 809      * @return int {@link Currency}
 810      */
 811     public function getCurrency()
 812     {
 813         return $this->currency;
 814     }
 815 
 816     /**
 817      * Returns the {@link Language language} constant for the specified
 818      * or set country.
 819      *
 820      * @param int $country {@link Country Country} constant.
 821      *
 822      * @deprecated Do not use.
 823      *
 824      * @return int|false if no match otherwise Language constant.
 825      */
 826     public function getLanguageForCountry($country = null)
 827     {
 828         if ($country === null) {
 829             $country = $this->country;
 830         }
 831         // Since getLanguage defaults to EN, check so we actually have a match
 832         $language = Country::getLanguage($country);
 833         if (Country::checkLanguage($country, $language)) {
 834             return $language;
 835         }
 836 
 837         return false;
 838     }
 839 
 840     /**
 841      * Returns the {@link Currency currency} constant for the specified
 842      * or set country.
 843      *
 844      * @param int $country {@link Country country} constant.
 845      *
 846      * @deprecated Do not use.
 847      *
 848      * @return int|false {@link Currency currency} constant.
 849      */
 850     public function getCurrencyForCountry($country = null)
 851     {
 852         if ($country === null) {
 853             $country = $this->country;
 854         }
 855 
 856         return Country::getCurrency($country);
 857     }
 858 
 859     /**
 860      * Sets the session id's for various device identification,
 861      * behaviour identification software.
 862      *
 863      * <b>Available named session id's</b>:<br>
 864      * string - dev_id_1<br>
 865      * string - dev_id_2<br>
 866      * string - dev_id_3<br>
 867      * string - beh_id_1<br>
 868      * string - beh_id_2<br>
 869      * string - beh_id_3<br>
 870      *
 871      * @param string $name Session ID identifier, e.g. 'dev_id_1'.
 872      * @param string $sid  Session ID.
 873      *
 874      * @throws Exception\KlarnaException
 875      */
 876     public function setSessionID($name, $sid)
 877     {
 878         $this->checkArgument($name, 'name');
 879         $this->checkArgument($sid, 'sid');
 880 
 881         $this->sid[$name] = $sid;
 882     }
 883 
 884     /**
 885      * Sets the shipment information for the upcoming transaction.<br>.
 886      *
 887      * Using this method is optional.
 888      *
 889      * <b>Available named values are</b>:<br>
 890      * int    - delay_adjust<br>
 891      * string - shipping_company<br>
 892      * string - shipping_product<br>
 893      * string - tracking_no<br>
 894      * array  - warehouse_addr<br>
 895      *
 896      * "warehouse_addr" is sent using {@link Address::toArray()}.
 897      *
 898      * Make sure you send in the values as the right data type.<br>
 899      * Use strval, intval or similar methods to ensure the right type is sent.
 900      *
 901      * @param string $name  key
 902      * @param mixed  $value value
 903      *
 904      * @throws Exception\KlarnaException
 905      */
 906     public function setShipmentInfo($name, $value)
 907     {
 908         $this->checkArgument($name, 'name');
 909 
 910         $this->shipInfo[$name] = $value;
 911     }
 912 
 913     /**
 914      * Sets the Activation information for the upcoming transaction.<br>.
 915      *
 916      * Using this method is optional.
 917      *
 918      * <b>Available named values are</b>:<br>
 919      * int    - flags<br>
 920      * int    - bclass<br>
 921      * string - orderid1<br>
 922      * string - orderid2<br>
 923      * string - ocr<br>
 924      * string - reference<br>
 925      * string - reference_code<br>
 926      * string - cust_no<br>
 927      *
 928      * Make sure you send in the values as the right data type.<br>
 929      * Use strval, intval or similar methods to ensure the right type is sent.
 930      *
 931      * @param string $name  key
 932      * @param mixed  $value value
 933      *
 934      * @see setShipmentInfo
 935      */
 936     public function setActivateInfo($name, $value)
 937     {
 938         $this->activateInfo[$name] = $value;
 939     }
 940 
 941     /**
 942      * Sets the extra information for the upcoming transaction.<br>.
 943      *
 944      * Using this method is optional.
 945      *
 946      * <b>Available named values are</b>:<br>
 947      * string - cust_no<br>
 948      * string - ready_date<br>
 949      * string - rand_string<br>
 950      * int    - bclass<br>
 951      * string - pin<br>
 952      *
 953      * Make sure you send in the values as the right data type.<br>
 954      * Use strval, intval or similar methods to ensure the right type is sent.
 955      *
 956      * @param string $name  key
 957      * @param mixed  $value value
 958      *
 959      * @throws Exception\KlarnaException
 960      */
 961     public function setExtraInfo($name, $value)
 962     {
 963         $this->checkArgument($name, 'name');
 964 
 965         $this->extraInfo[$name] = $value;
 966     }
 967 
 968     /**
 969      * Sets the income expense information for the upcoming transaction.<br>.
 970      *
 971      * Using this method is optional.
 972      *
 973      * Make sure you send in the values as the right data type.<br>
 974      * Use strval, intval or similar methods to ensure the right type is sent.
 975      *
 976      * @param string $name  key
 977      * @param mixed  $value value
 978      *
 979      * @throws Exception\KlarnaException
 980      */
 981     public function setIncomeInfo($name, $value)
 982     {
 983         $this->checkArgument($name, 'name');
 984 
 985         $this->incomeInfo[$name] = $value;
 986     }
 987 
 988     /**
 989      * Sets the bank information for the upcoming transaction.<br>.
 990      *
 991      * Using this method is optional.
 992      *
 993      * Make sure you send in the values as the right data type.<br>
 994      * Use strval, intval or similar methods to ensure the right type is sent.
 995      *
 996      * @param string $name  key
 997      * @param mixed  $value value
 998      *
 999      * @throws Exception\KlarnaException
1000      */
1001     public function setBankInfo($name, $value)
1002     {
1003         $this->checkArgument($name, 'name');
1004 
1005         $this->bankInfo[$name] = $value;
1006     }
1007 
1008     /**
1009      * Sets the travel information for the upcoming transaction.<br>.
1010      *
1011      * Using this method is optional.
1012      *
1013      * Make sure you send in the values as the right data type.<br>
1014      * Use strval, intval or similar methods to ensure the right type is sent.
1015      *
1016      * @param string $name  key
1017      * @param mixed  $value value
1018      *
1019      * @throws Exception\KlarnaException
1020      */
1021     public function setTravelInfo($name, $value)
1022     {
1023         $this->checkArgument($name, 'name');
1024 
1025         $this->travelInfo[$name] = $value;
1026     }
1027 
1028     /**
1029      * Set client IP.
1030      *
1031      * @param string $clientIP Client IP address
1032      */
1033     public function setClientIP($clientIP)
1034     {
1035         $this->clientIP = $clientIP;
1036     }
1037 
1038     /**
1039      * Returns the clients IP address.
1040      *
1041      * @return string
1042      */
1043     public function getClientIP()
1044     {
1045         if (isset($this->clientIP)) {
1046             return $this->clientIP;
1047         }
1048 
1049         $tmp_ip = '';
1050         $x_fwd = null;
1051 
1052         //Proxy handling.
1053         if (array_key_exists('REMOTE_ADDR', $_SERVER)) {
1054             $tmp_ip = $_SERVER['REMOTE_ADDR'];
1055         }
1056 
1057         if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
1058             $x_fwd = $_SERVER['HTTP_X_FORWARDED_FOR'];
1059         }
1060 
1061         if (self::$x_forwarded_for && ($x_fwd !== null)) {
1062             $forwarded = explode(',', $x_fwd);
1063 
1064             return trim($forwarded[0]);
1065         }
1066 
1067         return $tmp_ip;
1068     }
1069 
1070     /**
1071      * Sets the specified address for the current order.
1072      *
1073      * <b>Address type can be</b>:<br>
1074      * {@link Flags::IS_SHIPPING}<br>
1075      * {@link Flags::IS_BILLING}<br>
1076      *
1077      * @param int     $type Address type.
1078      * @param Address $addr Specified address.
1079      *
1080      * @throws Exception\KlarnaException
1081      */
1082     public function setAddress($type, Address $addr)
1083     {
1084         if ($addr->isCompany === null) {
1085             $addr->isCompany = false;
1086         }
1087 
1088         if ($type === Flags::IS_SHIPPING) {
1089             $this->shipping = $addr;
1090             self::printDebug('shipping address array', $this->shipping);
1091 
1092             return;
1093         }
1094 
1095         if ($type === Flags::IS_BILLING) {
1096             $this->billing = $addr;
1097             self::printDebug('billing address array', $this->billing);
1098 
1099             return;
1100         }
1101         throw new \RuntimeException("Unknown address type: {$type}");
1102     }
1103 
1104     /**
1105      * Sets order id's from other systems for the upcoming transaction.<br>.
1106      *
1107      * @param string $orderid1 order id 1
1108      * @param string $orderid2 order id 2
1109      *
1110      * @see Klarna::setExtraInfo()
1111      *
1112      * @throws Exception\KlarnaException
1113      */
1114     public function setEstoreInfo($orderid1 = '', $orderid2 = '')
1115     {
1116         if (!is_string($orderid1)) {
1117             $orderid1 = strval($orderid1);
1118         }
1119 
1120         if (!is_string($orderid2)) {
1121             $orderid2 = strval($orderid2);
1122         }
1123 
1124         $this->orderid[0] = $orderid1;
1125         $this->orderid[1] = $orderid2;
1126     }
1127 
1128     /**
1129      * Sets the reference (person) and reference code, for the upcoming
1130      * transaction.
1131      *
1132      * If this is omitted, it can grab first name, last name from the address
1133      * and use that as a reference person.
1134      *
1135      * @param string $ref  Reference person / message to customer on invoice.
1136      * @param string $code Reference code / message to customer on invoice.
1137      */
1138     public function setReference($ref, $code)
1139     {
1140         $this->checkRef($ref, $code);
1141         $this->reference = $ref;
1142         $this->reference_code = $code;
1143     }
1144 
1145     /**
1146      * Returns the reference (person).
1147      *
1148      * @return string
1149      */
1150     public function getReference()
1151     {
1152         return $this->reference;
1153     }
1154 
1155     /**
1156      * Returns an associative array used to send the address to Klarna.
1157      * TODO: Kill it all.
1158      *
1159      * @param Address $addr Address object to assemble.
1160      *
1161      * @return array The address for the specified method.
1162      */
1163     protected function assembleAddr(Address $addr)
1164     {
1165         return $addr->toArray();
1166     }
1167 
1168     /**
1169      * Sets the comment field, which can be shown in the invoice.
1170      *
1171      * @param string $data comment to set
1172      */
1173     public function setComment($data)
1174     {
1175         $this->comment = $data;
1176     }
1177 
1178     /**
1179      * Returns the PNO/SSN encoding constant for currently set country.
1180      *
1181      * <b>Note</b>:<br>
1182      * Country, language and currency needs to match!
1183      *
1184      * @throws Exception\KlarnaException
1185      *
1186      * @return int {@link Encoding} constant.
1187      */
1188     public function getPNOEncoding()
1189     {
1190         $this->checkLocale();
1191 
1192         $country = Country::getCode($this->country);
1193 
1194         return Encoding::get($country);
1195     }
1196 
1197     /**
1198      * Purpose: The get_addresses function is used to retrieve a customer's
1199      * address(es). Using this, the customer is not required to enter any
1200      * information, only confirm the one presented to him/her.<br>.
1201      *
1202      * The get_addresses function can also be used for companies.<br>
1203      * If the customer enters a company number, it will return all the
1204      * addresses where the company is registered at.<br>
1205      *
1206      * The get_addresses function is ONLY allowed to be used for Swedish
1207      * persons with the following conditions:
1208      * <ul>
1209      *     <li>
1210      *          It can be only used if invoice or part payment is
1211      *          the default payment method
1212      *     </li>
1213      *     <li>
1214      *          It has to disappear if the customer chooses another
1215      *          payment method
1216      *     </li>
1217      *     <li>
1218      *          The button is not allowed to be called "get address", but
1219      *          "continue" or<br>
1220      *          it can be picked up automatically when all the numbers have
1221      *          been typed.
1222      *     </li>
1223      * </ul>
1224      *
1225      * <b>Type can be one of these</b>:<br>
1226      * {@link Flags::GA_ALL},<br>
1227      * {@link Flags::GA_LAST},<br>
1228      * {@link Flags::GA_GIVEN}.<br>
1229      *
1230      * @example docs/examples/getAddresses.php How to get a customers address.
1231      *
1232      * @param string $pno      Social security number, personal number, ...
1233      * @param int    $encoding {@link Encoding PNO Encoding} constant.
1234      * @param int    $type     Specifies returned information.
1235      *
1236      * @throws Exception\KlarnaException
1237      *
1238      * @return array An array of {@link Address} objects.
1239      */
1240     public function getAddresses(
1241         $pno,
1242         $encoding = null,
1243         $type = Flags::GA_GIVEN
1244     ) {
1245         if ($this->country !== Country::SE) {
1246             throw new \RuntimeException(
1247                 'This method is only available for customers from: Sweden'
1248             );
1249         }
1250 
1251         //Get the PNO/SSN encoding constant.
1252         if ($encoding === null) {
1253             $encoding = $this->getPNOEncoding();
1254         }
1255 
1256         $this->checkPNO($pno, $encoding);
1257 
1258         $digestSecret = self::digest(
1259             self::colon(
1260                 $this->eid,
1261                 $pno,
1262                 $this->secret
1263             )
1264         );
1265 
1266         $paramList = array(
1267             $pno,
1268             $this->eid,
1269             $digestSecret,
1270             $encoding,
1271             $type,
1272             $this->getClientIP(),
1273         );
1274 
1275         self::printDebug('get_addresses array', $paramList);
1276 
1277         $result = $this->xmlrpcCall('get_addresses', $paramList);
1278 
1279         self::printDebug('get_addresses result array', $result);
1280 
1281         $addrs = array();
1282         foreach ($result as $tmpAddr) {
1283             try {
1284                 $addr = new Address();
1285                 if ($type === Flags::GA_GIVEN) {
1286                     $addr->isCompany = (count($tmpAddr) == 5) ? true : false;
1287                     if ($addr->isCompany) {
1288                         $addr->setCompanyName($tmpAddr[0]);
1289                         $addr->setStreet($tmpAddr[1]);
1290                         $addr->setZipCode($tmpAddr[2]);
1291                         $addr->setCity($tmpAddr[3]);
1292                         $addr->setCountry($tmpAddr[4]);
1293                     } else {
1294                         $addr->setFirstName($tmpAddr[0]);
1295                         $addr->setLastName($tmpAddr[1]);
1296                         $addr->setStreet($tmpAddr[2]);
1297                         $addr->setZipCode($tmpAddr[3]);
1298                         $addr->setCity($tmpAddr[4]);
1299                         $addr->setCountry($tmpAddr[5]);
1300                     }
1301                 } elseif ($type === Flags::GA_LAST) {
1302                     // Here we cannot decide if it is a company or not?
1303                     // Assume private person.
1304                     $addr->setLastName($tmpAddr[0]);
1305                     $addr->setStreet($tmpAddr[1]);
1306                     $addr->setZipCode($tmpAddr[2]);
1307                     $addr->setCity($tmpAddr[3]);
1308                     $addr->setCountry($tmpAddr[4]);
1309                 } elseif ($type === Flags::GA_ALL) {
1310                     if (strlen($tmpAddr[0]) > 0) {
1311                         $addr->setFirstName($tmpAddr[0]);
1312                         $addr->setLastName($tmpAddr[1]);
1313                     } else {
1314                         $addr->isCompany = true;
1315                         $addr->setCompanyName($tmpAddr[1]);
1316                     }
1317                     $addr->setStreet($tmpAddr[2]);
1318                     $addr->setZipCode($tmpAddr[3]);
1319                     $addr->setCity($tmpAddr[4]);
1320                     $addr->setCountry($tmpAddr[5]);
1321                 } else {
1322                     continue;
1323                 }
1324                 $addrs[] = $addr;
1325             } catch (\Exception $e) {
1326                 //Silently fail
1327             }
1328         }
1329 
1330         return $addrs;
1331     }
1332 
1333     /**
1334      * Adds an article to the current goods list for the current order.
1335      *
1336      * <b>Note</b>:<br>
1337      * It is recommended that you use {@link Flags::INC_VAT}.<br>
1338      *
1339      * <b>Flags can be</b>:<br>
1340      * {@link Flags::INC_VAT}<br>
1341      * {@link Flags::IS_SHIPMENT}<br>
1342      * {@link Flags::IS_HANDLING}<br>
1343      * {@link Flags::PRINT_1000}<br>
1344      * {@link Flags::PRINT_100}<br>
1345      * {@link Flags::PRINT_10}<br>
1346      * {@link Flags::NO_FLAG}<br>
1347      *
1348      * Some flags can be added to each other for multiple options.
1349      *
1350      * @param int    $qty      Quantity.
1351      * @param string $artNo    Article number.
1352      * @param string $title    Article title.
1353      * @param int    $price    Article price.
1354      * @param float  $vat      VAT in percent, e.g. 25% is inputted as 25.
1355      * @param float  $discount Possible discount on article.
1356      * @param int    $flags    Options which specify the article
1357      *                         ({@link Flags::IS_HANDLING}) and it's price
1358      *                         ({@link Flags::INC_VAT})
1359      *
1360      * @see Klarna::reserveAmount()
1361      *
1362      * @throws Exception\KlarnaException
1363      */
1364     public function addArticle(
1365         $qty,
1366         $artNo,
1367         $title,
1368         $price,
1369         $vat,
1370         $discount = 0,
1371         $flags = Flags::INC_VAT
1372     ) {
1373         $this->checkQty($qty);
1374 
1375         // Either artno or title has to be set
1376         if ((($artNo === null) || ($artNo == ''))
1377             && (($title === null) || ($title == ''))
1378         ) {
1379             throw new \InvalidArgumentException('Either Title and ArtNo needs to be set');
1380         }
1381 
1382         $this->checkPrice($price);
1383         $this->checkVAT($vat);
1384         $this->checkDiscount($discount);
1385         $this->checkInt($flags, 'flags');
1386 
1387         //Create goodsList array if not set.
1388         if (!$this->goodsList || !is_array($this->goodsList)) {
1389             $this->goodsList = array();
1390         }
1391 
1392         //Populate a temp array with the article details.
1393         $tmpArr = array(
1394             'artno' => $artNo,
1395             'title' => $title,
1396             'price' => $price,
1397             'vat' => $vat,
1398             'discount' => $discount,
1399             'flags' => $flags,
1400         );
1401 
1402         //Add the temp array and quantity field to the internal goods list.
1403         $this->goodsList[] = array(
1404                 'goods' => $tmpArr,
1405                 'qty' => $qty,
1406         );
1407 
1408         if (count($this->goodsList) > 0) {
1409             self::printDebug(
1410                 'article added',
1411                 $this->goodsList[count($this->goodsList) - 1]
1412             );
1413         }
1414     }
1415 
1416     /**
1417      * Summarizes the prices of the held goods list.
1418      *
1419      * @return int total amount
1420      */
1421     public function summarizeGoodsList()
1422     {
1423         $amount = 0;
1424         if (!is_array($this->goodsList)) {
1425             return $amount;
1426         }
1427         foreach ($this->goodsList as $goods) {
1428             $price = $goods['goods']['price'];
1429 
1430             // Add VAT if price is Excluding VAT
1431             if (($goods['goods']['flags'] & Flags::INC_VAT) === 0) {
1432                 $vat = $goods['goods']['vat'] / 100.0;
1433                 $price *= (1.0 + $vat);
1434             }
1435 
1436             // Reduce discounts
1437             if ($goods['goods']['discount'] > 0) {
1438                 $discount = $goods['goods']['discount'] / 100.0;
1439                 $price *= (1.0 - $discount);
1440             }
1441 
1442             $amount += $price * (int) $goods['qty'];
1443         }
1444 
1445         return $amount;
1446     }
1447 
1448     /**
1449      * Reserves a purchase amount for a specific customer. <br>
1450      * The reservation is valid, by default, for 7 days.<br>.
1451      *
1452      * <b>This method returns an array with</b>:<br>
1453      * A reservation number (rno)<br>
1454      * Order status flag<br>
1455      *
1456      * <b>Order status can be</b>:<br>
1457      * {@link Flags::ACCEPTED}<br>
1458      * {@link Flags::PENDING}<br>
1459      * {@link Flags::DENIED}<br>
1460      *
1461      * <b>Please note</b>:<br>
1462      * Activation must be done with activate_reservation, i.e. you cannot
1463      * activate through Klarna Online.
1464      *
1465      * Gender is only required for Germany and Netherlands.<br>
1466      *
1467      * <b>Flags can be set to</b>:<br>
1468      * {@link Flags::NO_FLAG}<br>
1469      * {@link Flags::TEST_MODE}<br>
1470      * {@link Flags::RSRV_SENSITIVE_ORDER}<br>
1471      * {@link Flags::RSRV_PHONE_TRANSACTION}<br>
1472      * {@link Flags::RSRV_SEND_PHONE_PIN}<br>
1473      *
1474      * Some flags can be added to each other for multiple options.
1475      *
1476      * <b>Note</b>:<br>
1477      * Normal shipment type is assumed unless otherwise specified, you can do
1478      * this by calling:<br>
1479      * {@link Klarna::setShipmentInfo() setShipmentInfo('delay_adjust', ...)}
1480      * with either: {@link Flags::NORMAL_SHIPMENT NORMAL_SHIPMENT} or
1481      * {@link Flags::EXPRESS_SHIPMENT EXPRESS_SHIPMENT}<br>
1482      *
1483      * @example docs/examples/reserveAmount.php How to create a reservation.
1484      *
1485      * @param string $pno      Personal number, SSN, date of birth, etc.
1486      * @param int    $gender   {@link Flags::FEMALE} or
1487      *                         {@link Flags::MALE}, null for unspecified.
1488      * @param int    $amount   Amount to be reserved, including VAT.
1489      * @param int    $flags    Options which affect the behaviour.
1490      * @param int    $pclass   {@link PClass::getId() PClass ID}.
1491      * @param int    $encoding {@link Encoding PNO Encoding} constant.
1492      * @param bool   $clear    Whether customer info should be cleared after
1493      *                         this call.
1494      *
1495      * @throws \InvalidArgumentException
1496      * @throws Exception\KlarnaException
1497      *
1498      * @return array An array with reservation number and order
1499      *               status. [string, int]
1500      */
1501     public function reserveAmount(
1502         $pno,
1503         $gender,
1504         $amount,
1505         $flags = 0,
1506         $pclass = PClass::INVOICE,
1507         $encoding = null,
1508         $clear = true
1509     ) {
1510         $this->checkLocale();
1511 
1512         //Get the PNO/SSN encoding constant.
1513         if ($encoding === null) {
1514             $encoding = $this->getPNOEncoding();
1515         }
1516 
1517         $this->checkPNO($pno, $encoding);
1518 
1519         if ($gender === 'm') {
1520             $gender = Flags::MALE;
1521         } elseif ($gender === 'f') {
1522             $gender = Flags::FEMALE;
1523         }
1524         if ($gender !== null && strlen($gender) > 0) {
1525             $this->checkInt($gender, 'gender');
1526         }
1527 
1528         $this->checkInt($flags, 'flags');
1529         $this->checkInt($pclass, 'pclass');
1530 
1531         //Check so required information is set.
1532         $this->checkGoodslist();
1533 
1534         //Calculate automatically the amount from goodsList.
1535         if ($amount === -1) {
1536             $amount = (int) round($this->summarizeGoodsList());
1537         } else {
1538             $this->checkAmount($amount);
1539         }
1540 
1541         if ($amount < 0) {
1542             throw new \InvalidArgumentException(
1543                 "Amount must be greater than zero, but was: {$amount}"
1544             );
1545         }
1546 
1547         //No addresses used for phone transactions
1548         if ($flags & Flags::RSRV_PHONE_TRANSACTION) {
1549             $billing = $shipping = '';
1550         } else {
1551             $billing = $this->assembleAddr($this->billing);
1552             $shipping = $this->assembleAddr($this->shipping);
1553 
1554             if (strlen($shipping['country']) > 0
1555                 && ($shipping['country'] !== $this->country)
1556             ) {
1557                 throw new \RuntimeException(
1558                     'Shipping address country must match the country set!'
1559                 );
1560             }
1561         }
1562 
1563         //Assume normal shipment unless otherwise specified.
1564         if (!isset($this->shipInfo['delay_adjust'])) {
1565             $this->setShipmentInfo('delay_adjust', Flags::NORMAL_SHIPMENT);
1566         }
1567 
1568         $digestSecret = self::digest(
1569             "{$this->eid}:{$pno}:{$amount}:{$this->secret}"
1570         );
1571 
1572         $paramList = array(
1573             $pno,
1574             $gender,
1575             $amount,
1576             $this->reference,
1577             $this->reference_code,
1578             $this->orderid[0],
1579             $this->orderid[1],
1580             $shipping,
1581             $billing,
1582             $this->getClientIP(),
1583             $flags,
1584             $this->currency,
1585             $this->country,
1586             $this->language,
1587             $this->eid,
1588             $digestSecret,
1589             $encoding,
1590             $pclass,
1591             $this->goodsList,
1592             $this->comment,
1593             $this->shipInfo,
1594             $this->travelInfo,
1595             $this->incomeInfo,
1596             $this->bankInfo,
1597             $this->sid,
1598             $this->extraInfo,
1599         );
1600 
1601         self::printDebug('reserve_amount', $paramList);
1602 
1603         $result = $this->xmlrpcCall('reserve_amount', $paramList);
1604 
1605         if ($clear === true) {
1606             //Make sure any stored values that need to be unique between
1607             //purchases are cleared.
1608             $this->clear();
1609         }
1610 
1611         self::printDebug('reserve_amount result', $result);
1612 
1613         return $result;
1614     }
1615 
1616     /**
1617      * Extends the reservations expiration date.
1618      *
1619      * @example docs/examples/extendExpiryDate.php How to extend a reservations expiry date.
1620      *
1621      * @param string $rno Reservation number.
1622      *
1623      * @throws Exception\KlarnaException
1624      *
1625      * @return DateTime The new expiration date.
1626      */
1627     public function extendExpiryDate($rno)
1628     {
1629         $this->checkRNO($rno);
1630 
1631         $digestSecret = self::digest(
1632             self::colon($this->eid, $rno, $this->secret)
1633         );
1634 
1635         $paramList = array(
1636             $rno,
1637             $this->eid,
1638             $digestSecret,
1639         );
1640 
1641         self::printDebug('extend_expiry_date', $paramList);
1642 
1643         $result = $this->xmlrpcCall('extend_expiry_date', $paramList);
1644 
1645         // Default to server location as API does not include time zone info
1646         $timeZone = new \DateTimeZone('Europe/Stockholm');
1647 
1648         // $result = '20150525T103631';
1649         $date = \DateTime::createFromFormat('Ymd\THis', $result, $timeZone);
1650         if ($date === false) {
1651             throw new Exception\KlarnaException(
1652                 "Could not parse result '{$result}' into date format 'Ymd\\This'"
1653             );
1654         }
1655 
1656         return $date;
1657     }
1658 
1659     /**
1660      * Extends the due date on the specified invoice.
1661      *
1662      * @param string $invNo   Invoice number.
1663      * @param int    $numDays Amount of days to extend the invoice with.
1664      * @param bool   $dryRun  Enabling dry run will only calculate cost.
1665      *
1666      * @throws KlarnaException
1667      *
1668      * @return array An array with cost as a float, and the new expiry date
1669      *               in the format YYYY-MM-DD.
1670      *               ['cost' => float, 'new_date' => string]
1671      */
1672     public function extendInvoiceDueDate($invNo, $numDays, $dryRun = false)
1673     {
1674         $this->checkInvNo($invNo);
1675 
1676         $this->checkInt($numDays, 'numDays');
1677 
1678         $digestSecret = self::digest(
1679             self::colon($this->eid, $invNo, $this->secret)
1680         );
1681 
1682         $paramList = array(
1683             $this->eid,
1684             $invNo,
1685             $digestSecret,
1686             array(
1687                 'days' => $numDays,
1688                 'calculate_only' => $dryRun === true
1689             )
1690         );
1691 
1692         self::printDebug('extend_invoice_due_date', $paramList);
1693 
1694         $result = $this->xmlrpcCall('extend_invoice_due_date', $paramList);
1695 
1696         $result['cost'] = floatval($result['cost'] / 100);
1697 
1698         return $result;
1699     }
1700 
1701     /**
1702      * Cancels a reservation.
1703      *
1704      * @example docs/examples/cancelReservation.php How to cancel a reservation.
1705      *
1706      * @param string $rno Reservation number.
1707      *
1708      * @throws Exception\KlarnaException
1709      *
1710      * @return bool True, if the cancellation was successful.
1711      */
1712     public function cancelReservation($rno)
1713     {
1714         $this->checkRNO($rno);
1715 
1716         $digestSecret = self::digest(
1717             self::colon($this->eid, $rno, $this->secret)
1718         );
1719         $paramList = array(
1720             $rno,
1721             $this->eid,
1722             $digestSecret,
1723         );
1724 
1725         self::printDebug('cancel_reservation', $paramList);
1726 
1727         $result = $this->xmlrpcCall('cancel_reservation', $paramList);
1728 
1729         return $result == 'ok';
1730     }
1731 
1732     /**
1733      * Update the reservation matching the given reservation number.
1734      *
1735      * @example docs/examples/update.php How to update a reservation.
1736      *
1737      * @param string $rno   Reservation number
1738      * @param bool   $clear clear set data after updating. Defaulted to true.
1739      *
1740      * @throws Exception\KlarnaException if no RNO is given, or if an error is received
1741      *                                   from Klarna Online.
1742      *
1743      * @return true if the update was successful
1744      */
1745     public function update($rno, $clear = true)
1746     {
1747         $rno = strval($rno);
1748 
1749         // All info that is sent in is part of the digest secret, in this order:
1750         // [
1751         //      proto_vsn, client_vsn, eid, rno, careof, street, zip, city,
1752         //      country, fname, lname, careof, street, zip, city, country,
1753         //      fname, lname, artno, qty, orderid1, orderid2
1754         // ].
1755         // The address part appears twice, that is one per address that
1756         // changes. If no value is sent in for an optional field, there
1757         // is no entry for this field in the digest secret. Shared secret
1758         // is added at the end of the digest secret.
1759         $digestArray = array(
1760             str_replace('.', ':', $this->proto),
1761             $this->version,
1762             $this->eid,
1763             $rno,
1764         );
1765         $digestArray = array_merge(
1766             $digestArray,
1767             $this->addressDigestPart($this->shipping)
1768         );
1769         $digestArray = array_merge(
1770             $digestArray,
1771             $this->addressDigestPart($this->billing)
1772         );
1773         if (is_array($this->goodsList) && $this->goodsList !== array()) {
1774             foreach ($this->goodsList as $goods) {
1775                 if (strlen($goods['goods']['artno']) > 0) {
1776                     $digestArray[] = $goods['goods']['artno'];
1777                 } else {
1778                     $digestArray[] = $goods['goods']['title'];
1779                 }
1780                 $digestArray[] = $goods['qty'];
1781             }
1782         }
1783         foreach ($this->orderid as $orderid) {
1784             $digestArray[] = $orderid;
1785         }
1786         $digestArray[] = $this->secret;
1787 
1788         $digestSecret = $this->digest(
1789             call_user_func_array(
1790                 array('self', 'colon'),
1791                 $digestArray
1792             )
1793         );
1794 
1795         $shipping = array();
1796         $billing = array();
1797         if ($this->shipping !== null && $this->shipping instanceof Address) {
1798             $shipping = $this->shipping->toArray();
1799         }
1800         if ($this->billing !== null && $this->billing instanceof Address) {
1801             $billing = $this->billing->toArray();
1802         }
1803         $paramList = array(
1804             $this->eid,
1805             $digestSecret,
1806             $rno,
1807             array(
1808                 'goods_list' => $this->goodsList,
1809                 'dlv_addr' => $shipping,
1810                 'bill_addr' => $billing,
1811                 'orderid1' => $this->orderid[0],
1812                 'orderid2' => $this->orderid[1],
1813             ),
1814         );
1815 
1816         self::printDebug('update array', $paramList);
1817 
1818         $result = $this->xmlrpcCall('update', $paramList);
1819 
1820         self::printDebug('update result', $result);
1821 
1822         return $result === 'ok';
1823     }
1824 
1825     /**
1826      * Help function to sort the address for update digest.
1827      *
1828      * @param Address|null $address Address object or null
1829      *
1830      * @return array
1831      */
1832     private function addressDigestPart(Address $address = null)
1833     {
1834         if ($address === null) {
1835             return array();
1836         }
1837 
1838         $keyOrder = array(
1839             'careof', 'street', 'zip', 'city', 'country', 'fname', 'lname',
1840         );
1841 
1842         $holder = $address->toArray();
1843         $digest = array();
1844 
1845         foreach ($keyOrder as $key) {
1846             if ($holder[$key] != '') {
1847                 $digest[] = $holder[$key];
1848             }
1849         }
1850 
1851         return $digest;
1852     }
1853 
1854     /**
1855      * Activate the reservation matching the given reservation number.
1856      * Optional information should be set in ActivateInfo.
1857      *
1858      * To perform a partial activation, use the addArtNo function to specify
1859      * which items in the reservation to include in the activation.
1860      *
1861      * @example docs/examples/activate.php How to activate a reservation.
1862      *
1863      * @param string $rno   Reservation number
1864      * @param string $ocr   optional OCR number to attach to the reservation when
1865      *                      activating. Overrides OCR specified in activateInfo.
1866      * @param string $flags optional flags to affect behaviour. If specified it
1867      *                      will overwrite any flag set in activateInfo.
1868      * @param bool   $clear clear set data after activating. Defaulted to true.
1869      *
1870      * @throws Exception\KlarnaException when the RNO is not specified, or if an error
1871      *                                   is received from Klarna Online.
1872      *
1873      * @return A string array with risk status and reservation number.
1874      */
1875     public function activate(
1876         $rno,
1877         $ocr = null,
1878         $flags = null,
1879         $clear = true
1880     ) {
1881         $this->checkRNO($rno);
1882 
1883         // Overwrite any OCR set on activateInfo if supplied here since this
1884         // method call is more specific.
1885         if ($ocr !== null) {
1886             $this->setActivateInfo('ocr', $ocr);
1887         }
1888 
1889         // If flags is specified set the flag supplied here to activateInfo.
1890         if ($flags !== null) {
1891             $this->setActivateInfo('flags', $flags);
1892         }
1893 
1894         //Assume normal shipment unless otherwise specified.
1895         if (!array_key_exists('delay_adjust', $this->shipInfo)) {
1896             $this->setShipmentInfo('delay_adjust', Flags::NORMAL_SHIPMENT);
1897         }
1898 
1899         // Append shipment info to activateInfo
1900         $this->activateInfo['shipment_info'] = $this->shipInfo;
1901 
1902         // Unlike other calls, if NO_FLAG is specified it should not be sent in
1903         // at all.
1904         if (array_key_exists('flags', $this->activateInfo)
1905             && $this->activateInfo['flags'] === Flags::NO_FLAG
1906         ) {
1907             unset($this->activateInfo['flags']);
1908         }
1909 
1910         // Build digest. Any field in activateInfo that is set is included in
1911         // the digest.
1912         $digestArray = array(
1913             str_replace('.', ':', $this->proto),
1914             $this->version,
1915             $this->eid,
1916             $rno,
1917         );
1918 
1919         $optionalDigestKeys = array(
1920             'bclass',
1921             'cust_no',
1922             'flags',
1923             'ocr',
1924             'orderid1',
1925             'orderid2',
1926             'reference',
1927             'reference_code',
1928         );
1929 
1930         foreach ($optionalDigestKeys as $key) {
1931             if (array_key_exists($key, $this->activateInfo)) {
1932                 $digestArray[] = $this->activateInfo[$key];
1933             }
1934         }
1935 
1936         if (array_key_exists('delay_adjust', $this->activateInfo['shipment_info'])) {
1937             $digestArray[] = $this->activateInfo['shipment_info']['delay_adjust'];
1938         }
1939 
1940         // If there are any artnos added with addArtNo, add them to the digest
1941         // and to the activateInfo
1942         if (is_array($this->artNos)) {
1943             foreach ($this->artNos as $artNo) {
1944                 $digestArray[] = $artNo['artno'];
1945                 $digestArray[] = $artNo['qty'];
1946             }
1947             $this->setActivateInfo('artnos', $this->artNos);
1948         }
1949 
1950         $digestArray[] = $this->secret;
1951         $digestSecret = self::digest(
1952             call_user_func_array(
1953                 array('self', 'colon'),
1954                 $digestArray
1955             )
1956         );
1957 
1958         // Create the parameter list.
1959         $paramList = array(
1960             $this->eid,
1961             $digestSecret,
1962             $rno,
1963             $this->activateInfo,
1964         );
1965 
1966         self::printDebug('activate array', $paramList);
1967 
1968         $result = $this->xmlrpcCall('activate', $paramList);
1969 
1970         self::printDebug('activate result', $result);
1971 
1972         // Clear the state if specified.
1973         if ($clear) {
1974             $this->clear();
1975         }
1976 
1977         return $result;
1978     }
1979 
1980     /**
1981      * Splits a reservation due to for example outstanding articles.
1982      *
1983      * <b>For flags usage see</b>:<br>
1984      * {@link Klarna::reserveAmount()}<br>
1985      *
1986      * @example docs/examples/splitReservation.php How to split a reservation.
1987      *
1988      * @param string $rno    Reservation number.
1989      * @param int    $amount The amount to be subtracted from the reservation.
1990      * @param int    $flags  Options which affect the behaviour.
1991      *
1992      * @throws \InvalidArgumentException
1993      * @throws Exception\KlarnaException
1994      *
1995      * @return string A new reservation number.
1996      */
1997     public function splitReservation(
1998         $rno,
1999         $amount,
2000         $flags = Flags::NO_FLAG
2001     ) {
2002         //Check so required information is set.
2003         $this->checkRNO($rno);
2004         $this->checkAmount($amount);
2005 
2006         if ($amount <= 0) {
2007             throw new \InvalidArgumentException("Amount cannot be negative: {$amount}");
2008         }
2009 
2010         $digestSecret = self::digest(
2011             self::colon($this->eid, $rno, $amount, $this->secret)
2012         );
2013         $paramList = array(
2014             $rno,
2015             $amount,
2016             $this->orderid[0],
2017             $this->orderid[1],
2018             $flags,
2019             $this->eid,
2020             $digestSecret,
2021         );
2022 
2023         self::printDebug('split_reservation array', $paramList);
2024 
2025         $result = $this->xmlrpcCall('split_reservation', $paramList);
2026 
2027         self::printDebug('split_reservation result', $result);
2028 
2029         return $result;
2030     }
2031 
2032     /**
2033      * Reserves a specified number of OCR numbers.<br>
2034      * For the specified country or the {@link Klarna::setCountry() set country}.<br>.
2035      *
2036      * @example docs/examples/reserveOCR.php How to reserve OCRs.
2037      *
2038      * @param int $no      The number of OCR numbers to reserve.
2039      * @param int $country {@link Country} constant.
2040      *
2041      * @throws \InvalidArgumentException
2042      * @throws Exception\KlarnaException
2043      *
2044      * @return array An array of OCR numbers.
2045      */
2046     public function reserveOCR($no, $country = null)
2047     {
2048         $this->checkNo($no);
2049         if ($country === null) {
2050             if (!$this->country) {
2051                 throw new \InvalidArgumentException('You must set country first!');
2052             }
2053             $country = $this->country;
2054         } else {
2055             $this->checkCountry($country);
2056         }
2057 
2058         $digestSecret = self::digest(
2059             self::colon($this->eid, $no, $this->secret)
2060         );
2061         $paramList = array(
2062             $no,
2063             $this->eid,
2064             $digestSecret,
2065             $country,
2066         );
2067 
2068         self::printDebug('reserve_ocr_nums array', $paramList);
2069 
2070         return $this->xmlrpcCall('reserve_ocr_nums', $paramList);
2071     }
2072 
2073     /**
2074      * Checks if the specified SSN/PNO has an part payment account with Klarna.
2075      *
2076      * @example docs/examples/hasAccount.php How to check for a part payment account.
2077      *
2078      * @param string $pno      Social security number, Personal number, ...
2079      * @param int    $encoding {@link Encoding PNO Encoding} constant.
2080      *
2081      * @throws Exception\KlarnaException
2082      *
2083      * @return bool True, if customer has an account.
2084      */
2085     public function hasAccount($pno, $encoding = null)
2086     {
2087         //Get the PNO/SSN encoding constant.
2088         if ($encoding === null) {
2089             $encoding = $this->getPNOEncoding();
2090         }
2091 
2092         $this->checkPNO($pno, $encoding);
2093 
2094         $digest = self::digest(
2095             self::colon($this->eid, $pno, $this->secret)
2096         );
2097 
2098         $paramList = array(
2099             $this->eid,
2100             $pno,
2101             $digest,
2102             $encoding,
2103         );
2104 
2105         self::printDebug('has_account', $paramList);
2106 
2107         $result = $this->xmlrpcCall('has_account', $paramList);
2108 
2109         return $result === 'true';
2110     }
2111 
2112     /**
2113      * Adds an article number and quantity to be used in
2114      * {@link Klarna::creditPart()}.
2115      *
2116      * @param int    $qty   Quantity of specified article.
2117      * @param string $artNo Article number.
2118      *
2119      * @throws Exception\KlarnaException
2120      */
2121     public function addArtNo($qty, $artNo)
2122     {
2123         $this->checkQty($qty);
2124         $this->checkArtNo($artNo);
2125 
2126         if (!is_array($this->artNos)) {
2127             $this->artNos = array();
2128         }
2129 
2130         $this->artNos[] = array('artno' => $artNo, 'qty' => $qty);
2131     }
2132 
2133     /**
2134      * Sends an activated invoice to the customer via e-mail. <br>
2135      * The email is sent in plain text format and contains a link to a
2136      * PDF-invoice.<br>.
2137      *
2138      * <b>Please note!</b><br>
2139      * Regular postal service is used if the customer has not entered his/her
2140      * e-mail address when making the purchase (charges may apply).<br>
2141      *
2142      * @example docs/examples/emailInvoice.php How to email an invoice.
2143      *
2144      * @param string $invNo Invoice number.
2145      *
2146      * @throws Exception\KlarnaException
2147      *
2148      * @return string Invoice number.
2149      */
2150     public function emailInvoice($invNo)
2151     {
2152         $this->checkInvNo($invNo);
2153 
2154         $digestSecret = self::digest(
2155             self::colon($this->eid, $invNo, $this->secret)
2156         );
2157         $paramList = array(
2158             $this->eid,
2159             $invNo,
2160             $digestSecret,
2161         );
2162 
2163         self::printDebug('email_invoice array', $paramList);
2164 
2165         return $this->xmlrpcCall('email_invoice', $paramList);
2166     }
2167 
2168     /**
2169      * Requests a postal send-out of an activated invoice to a customer by
2170      * Klarna (charges may apply).
2171      *
2172      * @example docs/examples/sendInvoice.php How to send an invoice.
2173      *
2174      * @param string $invNo Invoice number.
2175      *
2176      * @throws Exception\KlarnaException
2177      *
2178      * @return string Invoice number.
2179      */
2180     public function sendInvoice($invNo)
2181     {
2182         $this->checkInvNo($invNo);
2183 
2184         $digestSecret = self::digest(
2185             self::colon($this->eid, $invNo, $this->secret)
2186         );
2187         $paramList = array(
2188             $this->eid,
2189             $invNo,
2190             $digestSecret,
2191         );
2192 
2193         self::printDebug('send_invoice array', $paramList);
2194 
2195         return $this->xmlrpcCall('send_invoice', $paramList);
2196     }
2197 
2198     /**
2199      * Gives discounts on invoices.<br>
2200      * If you are using standard integration and the purchase is not yet
2201      * activated (you have not yet delivered the goods), <br>
2202      * just change the article list in our online interface Klarna Online.<br>.
2203      *
2204      * <b>Flags can be</b>:<br>
2205      * {@link Flags::INC_VAT}<br>
2206      * {@link Flags::NO_FLAG}, <b>NOT RECOMMENDED!</b><br>
2207      *
2208      * @param string $invNo       Invoice number.
2209      * @param int    $amount      The amount given as a discount.
2210      * @param float  $vat         VAT in percent, e.g. 22.2 for 22.2%.
2211      * @param int    $flags       If amount is
2212      *                            {@link Flags::INC_VAT including} or
2213      *                            {@link Flags::NO_FLAG excluding} VAT.
2214      * @param string $description Optional custom text to present as discount
2215      *                            in the invoice.
2216      *
2217      * @example docs/examples/returnAmount.php How to perform a return.
2218      *
2219      * @throws Exception\KlarnaException
2220      *
2221      * @return string Invoice number.
2222      */
2223     public function returnAmount(
2224         $invNo,
2225         $amount,
2226         $vat,
2227         $flags = Flags::INC_VAT,
2228         $description = ''
2229     ) {
2230         $this->checkInvNo($invNo);
2231         $this->checkAmount($amount);
2232         $this->checkVAT($vat);
2233         $this->checkInt($flags, 'flags');
2234 
2235         if ($description == null) {
2236             $description = '';
2237         }
2238 
2239         $digestSecret = self::digest(
2240             self::colon($this->eid, $invNo, $this->secret)
2241         );
2242         $paramList = array(
2243             $this->eid,
2244             $invNo,
2245             $amount,
2246             $vat,
2247             $digestSecret,
2248             $flags,
2249             $description,
2250         );
2251 
2252         self::printDebug('return_amount', $paramList);
2253 
2254         return $this->xmlrpcCall('return_amount', $paramList);
2255     }
2256 
2257     /**
2258      * Performs a complete refund on an invoice, part payment and mobile
2259      * purchase.
2260      *
2261      * @example docs/examples/creditInvoice.php How to credit an invoice.
2262      *
2263      * @param string $invNo  Invoice number.
2264      * @param string $credNo Credit number.
2265      *
2266      * @throws Exception\KlarnaException
2267      *
2268      * @return string Invoice number.
2269      */
2270     public function creditInvoice($invNo, $credNo = '')
2271     {
2272         $this->checkInvNo($invNo);
2273         $this->checkCredNo($credNo);
2274 
2275         $digestSecret = self::digest(
2276             self::colon($this->eid, $invNo, $this->secret)
2277         );
2278         $paramList = array(
2279             $this->eid,
2280             $invNo,
2281             $credNo,
2282             $digestSecret,
2283         );
2284 
2285         self::printDebug('credit_invoice', $paramList);
2286 
2287         return $this->xmlrpcCall('credit_invoice', $paramList);
2288     }
2289 
2290     /**
2291      * Performs a partial refund on an invoice, part payment or mobile purchase.
2292      *
2293      * <b>Note</b>:<br>
2294      * You need to call {@link Klarna::addArtNo()} first.<br>
2295      *
2296      * @example docs/examples/creditPart.php How to partially credit an invoice.
2297      *
2298      * @param string $invNo  Invoice number.
2299      * @param string $credNo Credit number.
2300      *
2301      * @see Klarna::addArtNo()
2302      *
2303      * @throws Exception\KlarnaException
2304      *
2305      * @return string Invoice number.
2306      */
2307     public function creditPart($invNo, $credNo = '')
2308     {
2309         $this->checkInvNo($invNo);
2310         $this->checkCredNo($credNo);
2311 
2312         if ($this->goodsList === null || empty($this->goodsList)) {
2313             $this->checkArtNos($this->artNos);
2314         }
2315 
2316         //function activate_part_digest
2317         $string = $this->eid.':'.$invNo.':';
2318 
2319         if ($this->artNos !== null && !empty($this->artNos)) {
2320             foreach ($this->artNos as $artNo) {
2321                 $string .= $artNo['artno'].':'.$artNo['qty'].':';
2322             }
2323         }
2324 
2325         $digestSecret = self::digest($string.$this->secret);
2326         //end activate_part_digest
2327 
2328         $paramList = array(
2329             $this->eid,
2330             $invNo,
2331             $this->artNos,
2332             $credNo,
2333             $digestSecret,
2334         );
2335 
2336         if ($this->goodsList !== null && !empty($this->goodsList)) {
2337             $paramList[] = 0;
2338             $paramList[] = $this->goodsList;
2339         }
2340 
2341         $this->artNos = array();
2342 
2343         self::printDebug('credit_part', $paramList);
2344 
2345         return $this->xmlrpcCall('credit_part', $paramList);
2346     }
2347 
2348     /**
2349      * Returns the current order status for a specific reservation or invoice.
2350      * Use this when {@link Klarna::reserveAmount()} returns a {@link Flags::PENDING}
2351      * status.
2352      *
2353      * <b>Order status can be</b>:<br>
2354      * {@link Flags::ACCEPTED}<br>
2355      * {@link Flags::PENDING}<br>
2356      * {@link Flags::DENIED}<br>
2357      *
2358      * @example docs/examples/checkOrderStatus.php How to check a order status.
2359      *
2360      * @param string $id   Reservation number or invoice number.
2361      * @param int    $type 0 if $id is an invoice or reservation, 1 for order id
2362      *
2363      * @throws \InvalidArgumentException
2364      * @throws Exception\KlarnaException
2365      *
2366      * @return string The order status.
2367      */
2368     public function checkOrderStatus($id, $type = 0)
2369     {
2370         $this->checkArgument($id, 'id');
2371 
2372         $this->checkInt($type, 'type');
2373         if ($type !== 0 && $type !== 1) {
2374             throw new \InvalidArgumentException('Expected type to be 0 or 1');
2375         }
2376 
2377         $digestSecret = self::digest(
2378             self::colon($this->eid, $id, $this->secret)
2379         );
2380         $paramList = array(
2381             $this->eid,
2382             $digestSecret,
2383             $id,
2384             $type,
2385         );
2386 
2387         self::printDebug('check_order_status', $paramList);
2388 
2389         return $this->xmlrpcCall('check_order_status', $paramList);
2390     }
2391 
2392     /**
2393      * Get the PClasses from Klarna Online.<br>
2394      * You are only allowed to call this once, or once per update of PClasses
2395      * in KO.<br>.
2396      *
2397      * <b>Note</b>:<br>
2398      * You should store these in a DB of choice for later use.
2399      *
2400      * @example docs/examples/getPClasses.php How to get your estore PClasses.
2401      *
2402      * @param string|int $country  {@link Country Country} constant,
2403      *                             or two letter code.
2404      * @param mixed      $language {@link Language Language} constant,
2405      *                             or two letter code.
2406      * @param mixed      $currency {@link Currency Currency} constant,
2407      *                             or three letter code.
2408      *
2409      * @throws Exception\KlarnaException
2410      *
2411      * @return PClass[] A list of pclasses.
2412      */
2413     public function getPClasses($country = null, $language = null, $currency = null)
2414     {
2415         extract(
2416             $this->getLocale($country, $language, $currency),
2417             EXTR_OVERWRITE
2418         );
2419 
2420         $this->checkConfig();
2421 
2422         $digestSecret = self::digest(
2423             $this->eid.':'.$currency.':'.$this->secret
2424         );
2425 
2426         $paramList = array(
2427             $this->eid,
2428             $currency,
2429             $digestSecret,
2430             $country,
2431             $language,
2432         );
2433 
2434         self::printDebug('get_pclasses array', $paramList);
2435 
2436         $result = $this->xmlrpcCall('get_pclasses', $paramList);
2437 
2438         self::printDebug('get_pclasses result', $result);
2439 
2440         $pclasses = array();
2441 
2442         foreach ($result as $data) {
2443             $pclass = new PClass();
2444             $pclass->setEid($this->eid);
2445             $pclass->setId($data[0]);
2446             $pclass->setDescription($data[1]);
2447             $pclass->setMonths($data[2]);
2448             $pclass->setStartFee($data[3] / 100);
2449             $pclass->setInvoiceFee($data[4] / 100);
2450             $pclass->setInterestRate($data[5] / 100);
2451             $pclass->setMinAmount($data[6] / 100);
2452             $pclass->setCountry($data[7]);
2453             $pclass->setType($data[8]);
2454             $pclass->setExpire(strtotime($data[9]));
2455 
2456             $pclasses[] = $pclass;
2457         }
2458 
2459         return $pclasses;
2460     }
2461 
2462     /**
2463      * Returns the cheapest, per month, PClass related to the specified sum.
2464      *
2465      * <b>Note</b>: This choose the cheapest PClass for the current country.<br>
2466      * {@link Klarna::setCountry()}
2467      *
2468      * <b>Flags can be</b>:<br>
2469      * {@link Flags::CHECKOUT_PAGE}<br>
2470      * {@link Flags::PRODUCT_PAGE}<br>
2471      *
2472      * @param float    $sum      The product cost, or total sum of the cart.
2473      * @param int      $flags    Which type of page the info will be displayed on.
2474      * @param PClass[] $pclasses The list of pclasses to search in.
2475      *
2476      * @throws \InvalidArgumentException
2477      * @throws Exception\KlarnaException
2478      *
2479      * @return PClass or false if none was found.
2480      */
2481     public function getCheapestPClass($sum, $flags, $pclasses)
2482     {
2483         if (!is_numeric($sum)) {
2484             throw new \InvalidArgumentException("Sum has to be numeric: {$sum}");
2485         }
2486 
2487         if (!is_numeric($flags)
2488             || !in_array(
2489                 $flags,
2490                 array(
2491                     Flags::CHECKOUT_PAGE, Flags::PRODUCT_PAGE, )
2492             )
2493         ) {
2494             throw new \InvalidArgumentException(
2495                 'Expected $flags to be '.Flags::CHECKOUT_PAGE.' or '.Flags::PRODUCT_PAGE
2496             );
2497         }
2498 
2499         $lowest_pp = $lowest = false;
2500 
2501         foreach ($pclasses as $pclass) {
2502             $lowest_payment = Calc::getLowestPaymentForAccount(
2503                 $pclass->getCountry()
2504             );
2505             if ($pclass->getType() < 2 && $sum >= $pclass->getMinAmount()) {
2506                 $minpay = Calc::calcMonthlyCost(
2507                     $sum,
2508                     $pclass,
2509                     $flags
2510                 );
2511 
2512                 if ($minpay < $lowest_pp || $lowest_pp === false) {
2513                     if ($pclass->getType() == PClass::ACCOUNT
2514                         || $minpay >= $lowest_payment
2515                     ) {
2516                         $lowest_pp = $minpay;
2517                         $lowest = $pclass;
2518                     }
2519                 }
2520             }
2521         }
2522 
2523         return $lowest;
2524     }
2525 
2526     /**
2527      * Creates a XMLRPC call with specified XMLRPC method and parameters from array.
2528      *
2529      * @param string $method XMLRPC method.
2530      * @param array  $array  XMLRPC parameters.
2531      *
2532      * @throws \InvalidArgumentException
2533      * @throws Exception\KlarnaException
2534      *
2535      * @return mixed
2536      */
2537     protected function xmlrpcCall($method, $array)
2538     {
2539         $this->checkConfig();
2540 
2541         if (!isset($method) || !is_string($method)) {
2542             throw new \InvalidArgumentException('method has to be a string');
2543         }
2544         if ($array === null || count($array) === 0) {
2545             throw new \InvalidArgumentException('Parameter list cannot empty or null!');
2546         }
2547         if (self::$disableXMLRPC) {
2548             return true;
2549         }
2550 
2551         $encoder = new PhpXmlRpc\Encoder();
2552 
2553         try {
2554             //Create the XMLRPC message.
2555             $msg = new PhpXmlRpc\Request($method);
2556             $params = array_merge(
2557                 array(
2558                     $this->proto,
2559                     $this->version,
2560                 ),
2561                 $array
2562             );
2563 
2564             foreach ($params as $p) {
2565                 if (!$msg->addParam(
2566                     $encoder->encode($p, array('extension_api'))
2567                 )
2568                 ) {
2569                     throw new \RuntimeException(
2570                         'Failed to add parameters to XMLRPC message.',
2571                         50068
2572                     );
2573                 }
2574             }
2575 
2576             if (self::$xmlrpcDebug) {
2577                 $this->xmlrpc->setDebug(2);
2578             }
2579 
2580             $internalEncoding = PhpXmlRpc\PhpXmlRpc::$xmlrpc_internalencoding;
2581             PhpXmlRpc\PhpXmlRpc::$xmlrpc_internalencoding = $this->encoding;
2582 
2583             //Send the message.
2584             $xmlrpcresp = $this->xmlrpc->send(
2585                 $msg,
2586                 isset($this->config['timeout']) ? intval($this->config['timeout']) : 10
2587             );
2588 
2589             $status = $xmlrpcresp->faultCode();
2590 
2591             PhpXmlRpc\PhpXmlRpc::$xmlrpc_internalencoding = $internalEncoding;
2592 
2593             if ($status !== 0) {
2594                 throw new Exception\KlarnaException($xmlrpcresp->faultString(), $status);
2595             }
2596 
2597             return $encoder->decode($xmlrpcresp->value());
2598         } catch (Exception\KlarnaException $e) {
2599             //Otherwise it is caught below, and re-thrown.
2600             throw $e;
2601         } catch (\Exception $e) {
2602             throw new Exception\KlarnaException($e->getMessage(), $e->getCode());
2603         }
2604     }
2605 
2606     /**
2607      * Create a new CurlTransport.
2608      *
2609      * @return CurlTransport New CurlTransport instance
2610      */
2611     public function createTransport()
2612     {
2613         return new CurlTransport(
2614             new CurlHandle(),
2615             isset($this->config['timeout']) ? intval($this->config['timeout']) : 10
2616         );
2617     }
2618 
2619     /**
2620      * Perform a checkout service request.
2621      *
2622      * @example docs/examples/checkoutService.php How to use the checkout service.
2623      *
2624      * @param int|float $price    The total price for the checkout including VAT.
2625      * @param string    $currency ISO 4217 Currency Code
2626      * @param string    $locale   Specify what locale is used by the checkout.
2627      *                            ISO 639 language and ISO 3166-1 country separated
2628      *                            by underscore. Example: sv_SE
2629      * @param string    $country  (Optional) Specify what ISO 3166-1 country to use
2630      *                            for fetching payment methods. If not specified
2631      *                            the locale country will be used.
2632      *
2633      * @throws \RuntimeException If the curl extension is not loaded
2634      *
2635      * @return CheckoutServiceResponse Response with payment methods
2636      */
2637     public function checkoutService($price, $currency, $locale, $country = null)
2638     {
2639         $this->checkAmount($price);
2640 
2641         $params = array(
2642             'merchant_id' => $this->config['eid'],
2643             'total_price' => $price,
2644             'currency' => strtoupper($currency),
2645             'locale' => strtolower($locale),
2646         );
2647 
2648         if ($country !== null) {
2649             $params['country'] = $country;
2650         }
2651 
2652         return $this->createTransport()->send(
2653             new CheckoutServiceRequest($this->config, $params)
2654         );
2655     }
2656 
2657     /**
2658      * Removes all relevant order/customer data from the internal structure.
2659      */
2660     public function clear()
2661     {
2662         $this->goodsList = null;
2663         $this->comment = '';
2664 
2665         $this->billing = null;
2666         $this->shipping = null;
2667 
2668         $this->shipInfo = array();
2669         $this->extraInfo = array();
2670         $this->bankInfo = array();
2671         $this->incomeInfo = array();
2672         $this->activateInfo = array();
2673 
2674         $this->reference = '';
2675         $this->reference_code = '';
2676 
2677         $this->orderid[0] = '';
2678         $this->orderid[1] = '';
2679 
2680         $this->artNos = array();
2681     }
2682 
2683     /**
2684      * Implodes parameters with delimiter ':'.
2685      * Null and "" values are ignored by the colon function to
2686      * ensure there is not several colons in succession.
2687      *
2688      * @return string Colon separated string.
2689      */
2690     public static function colon()
2691     {
2692         $args = func_get_args();
2693 
2694         return implode(
2695             ':',
2696             array_filter(
2697                 $args,
2698                 array('self', 'filterDigest')
2699             )
2700         );
2701     }
2702 
2703     /**
2704      * Implodes parameters with delimiter '|'.
2705      *
2706      * @return string Pipe separated string.
2707      */
2708     public static function pipe()
2709     {
2710         $args = func_get_args();
2711 
2712         return implode('|', $args);
2713     }
2714 
2715     /**
2716      * Check if the value has a string length larger than 0.
2717      *
2718      * @param mixed $value The value to check.
2719      *
2720      * @return bool True if string length is larger than 0
2721      */
2722     public static function filterDigest($value)
2723     {
2724         return strlen(strval($value)) > 0;
2725     }
2726 
2727     /**
2728      * Creates a digest hash from the inputted string,
2729      * and the specified or the preferred hash algorithm.
2730      *
2731      * @param string $data Data to be hashed.
2732      * @param string $hash hash algoritm to use
2733      *
2734      * @throws Exception\KlarnaException
2735      *
2736      * @return string Base64 encoded hash.
2737      */
2738     public static function digest($data, $hash = null)
2739     {
2740         if ($hash === null) {
2741             $preferred = array(
2742                 'sha512',
2743                 'sha384',
2744                 'sha256',
2745                 'sha224',
2746                 'md5',
2747             );
2748 
2749             $hashes = array_intersect($preferred, hash_algos());
2750 
2751             if (count($hashes) == 0) {
2752                 throw new Exception\KlarnaException(
2753                     'No available hash algorithm supported!'
2754                 );
2755             }
2756             $hash = array_shift($hashes);
2757         }
2758         self::printDebug('digest() using hash', $hash);
2759 
2760         return base64_encode(pack('H*', hash($hash, $data)));
2761     }
2762 
2763     /**
2764      * Prints debug information if debug is set to true.
2765      * $msg is used as header/footer in the output.
2766      *
2767      * If FirePHP is available it will be used instead of
2768      * dumping the debug info into the document.
2769      *
2770      * It uses print_r and encapsulates it in HTML/XML comments.
2771      * (<!-- -->)
2772      *
2773      * @param string $msg   Debug identifier, e.g. "my array".
2774      * @param mixed  $mixed Object, type, etc, to be debugged.
2775      */
2776     public static function printDebug($msg, $mixed)
2777     {
2778         if (self::$debug) {
2779             if (class_exists('FB', false)) {
2780                 FB::send($mixed, $msg);
2781             } else {
2782                 echo "\n<!-- {$msg}: \n";
2783                 print_r($mixed);
2784                 echo "\n end {$msg} -->\n";
2785             }
2786         }
2787     }
2788 
2789     /**
2790      * Checks/fixes so the invNo input is valid.
2791      *
2792      * @param string $invNo Invoice number.
2793      *
2794      * @throws \InvalidArgumentException
2795      */
2796     private function checkInvNo(&$invNo)
2797     {
2798         if (!isset($invNo)) {
2799             throw new \InvalidArgumentException('Invoice number must be set');
2800         }
2801         if (!is_string($invNo)) {
2802             $invNo = strval($invNo);
2803         }
2804         if (strlen($invNo) == 0) {
2805             throw new \InvalidArgumentException("Invoice number must be a string: {$invNo}");
2806         }
2807     }
2808 
2809     /**
2810      * Checks/fixes so the quantity input is valid.
2811      *
2812      * @param int $qty Quantity.
2813      *
2814      * @throws \InvalidArgumentException
2815      */
2816     private function checkQty(&$qty)
2817     {
2818         if (!isset($qty)) {
2819             throw new \InvalidArgumentException('Quantity must be set');
2820         }
2821         if (is_numeric($qty) && !is_int($qty)) {
2822             $qty = intval($qty);
2823         }
2824         if (!is_int($qty)) {
2825             throw new \InvalidArgumentException('Expected Quantity to be an integer');
2826         }
2827     }
2828 
2829     /**
2830      * Checks/fixes so the artNo input is valid.
2831      *
2832      * @param int|string $artNo Article number.
2833      *
2834      * @throws \InvalidArgumentException
2835      */
2836     private function checkArtNo(&$artNo)
2837     {
2838         if (is_numeric($artNo) && !is_string($artNo)) {
2839             //Convert artNo to string if integer.
2840             $artNo = strval($artNo);
2841         }
2842         if (!isset($artNo) || strlen($artNo) == 0 || (!is_string($artNo))) {
2843             throw new \InvalidArgumentException("artNo must be set and a string: {$artNo}");
2844         }
2845     }
2846 
2847     /**
2848      * Checks/fixes so the credNo input is valid.
2849      *
2850      * @param string $credNo Credit number.
2851      *
2852      * @throws \InvalidArgumentException
2853      */
2854     private function checkCredNo(&$credNo)
2855     {
2856         if (!isset($credNo)) {
2857             throw new \InvalidArgumentException('credNo is not set');
2858         }
2859 
2860         if ($credNo === false || $credNo === null) {
2861             $credNo = '';
2862         }
2863         if (!is_string($credNo)) {
2864             $credNo = strval($credNo);
2865             if (!is_string($credNo)) {
2866                 throw new \InvalidArgumentException('Credit number is not a string');
2867             }
2868         }
2869     }
2870 
2871     /**
2872      * Checks so that artNos is an array and is not empty.
2873      *
2874      * @param array $artNos Array from {@link Klarna::addArtNo()}.
2875      *
2876      * @throws \InvalidArgumentException
2877      */
2878     private function checkArtNos(&$artNos)
2879     {
2880         if (!is_array($artNos)) {
2881             throw new \InvalidArgumentException('artNos is not an array');
2882         }
2883         if (empty($artNos)) {
2884             throw new \InvalidArgumentException('ArtNo array cannot empty!');
2885         }
2886     }
2887 
2888     /**
2889      * Checks/fixes so the integer input is valid.
2890      *
2891      * @param int    $int   {@link Flags flags} constant.
2892      * @param string $field Name of the field.
2893      *
2894      * @throws \InvalidArgumentException
2895      */
2896     private function checkInt(&$int, $field)
2897     {
2898         if (!isset($int)) {
2899             throw new \InvalidArgumentException("Expected field to be set: {$field}");
2900         }
2901         if (is_numeric($int) && !is_int($int)) {
2902             $int = intval($int);
2903         }
2904         if (!is_numeric($int) || !is_int($int)) {
2905             throw new \InvalidArgumentException(
2906                 "Expected field to be an integer: {$field}"
2907             );
2908         }
2909     }
2910 
2911     /**
2912      * Checks/fixes so the VAT input is valid.
2913      *
2914      * @param float $vat VAT.
2915      *
2916      * @throws \InvalidArgumentException
2917      */
2918     private function checkVAT(&$vat)
2919     {
2920         if (!isset($vat)) {
2921             throw new \InvalidArgumentException('VAT must be set');
2922         }
2923         if (is_numeric($vat) && (!is_int($vat) || !is_float($vat))) {
2924             $vat = floatval($vat);
2925         }
2926         if (!is_numeric($vat) || (!is_int($vat) && !is_float($vat))) {
2927             throw new \InvalidArgumentException(
2928                 "VAT must be an integer or float: {$vat}"
2929             );
2930         }
2931     }
2932 
2933     /**
2934      * Checks/fixes so the amount input is valid.
2935      *
2936      * @param int $amount Amount.
2937      *
2938      * @throws \InvalidArgumentException
2939      */
2940     private function checkAmount(&$amount)
2941     {
2942         if (!isset($amount)) {
2943             throw new \InvalidArgumentException('Amount must be set');
2944         }
2945         if (is_numeric($amount)) {
2946             $this->fixValue($amount);
2947         }
2948         if (is_numeric($amount) && !is_int($amount)) {
2949             $amount = intval($amount);
2950         }
2951         if (!is_numeric($amount) || !is_int($amount)) {
2952             throw new \InvalidArgumentException("amount must be an integer: {$amount}");
2953         }
2954     }
2955 
2956     /**
2957      * Checks/fixes so the price input is valid.
2958      *
2959      * @param int $price Price.
2960      *
2961      * @throws \InvalidArgumentException
2962      */
2963     private function checkPrice(&$price)
2964     {
2965         if (!isset($price)) {
2966             throw new \InvalidArgumentException('Price must be set');
2967         }
2968         if (is_numeric($price)) {
2969             $this->fixValue($price);
2970         }
2971         if (is_numeric($price) && !is_int($price)) {
2972             $price = intval($price);
2973         }
2974         if (!is_numeric($price) || !is_int($price)) {
2975             throw new \InvalidArgumentException("Price must be an integer: {$price}");
2976         }
2977     }
2978 
2979     /**
2980      * Multiplies value with 100 and rounds it.
2981      * This fixes value/price/amount inputs so that KO can handle them.
2982      *
2983      * @param float $value value
2984      */
2985     private function fixValue(&$value)
2986     {
2987         $value = round($value * 100);
2988     }
2989 
2990     /**
2991      * Checks/fixes so the discount input is valid.
2992      *
2993      * @param float $discount Discount amount.
2994      *
2995      * @throws \InvalidArgumentException
2996      */
2997     private function checkDiscount(&$discount)
2998     {
2999         if (!isset($discount)) {
3000             throw new \InvalidArgumentException('Discount must be set');
3001         }
3002         if (is_numeric($discount)
3003             && (!is_int($discount) || !is_float($discount))
3004         ) {
3005             $discount = floatval($discount);
3006         }
3007 
3008         if (!is_numeric($discount)
3009             || (!is_int($discount) && !is_float($discount))
3010         ) {
3011             throw new \InvalidArgumentException(
3012                 "Discount must be an integer or float: {$discount}"
3013             );
3014         }
3015     }
3016 
3017     /**
3018      * Checks/fixes to the PNO/SSN input is valid.
3019      *
3020      * @param string $pno Personal number, social security  number, ...
3021      * @param int    $enc {@link Encoding PNO Encoding} constant.
3022      *
3023      * @throws \InvalidArgumentException
3024      * @throws \RuntimeException         If pno is unsupported
3025      */
3026     private function checkPNO(&$pno, $enc)
3027     {
3028         if (!$pno) {
3029             throw new \InvalidArgumentException('PNO/SSN must be set');
3030         }
3031 
3032         if (!Encoding::checkPNO($pno)) {
3033             throw new \RuntimeException('PNO/SSN is not valid!');
3034         }
3035     }
3036 
3037     /**
3038      * Checks/fixes to the country input is valid.
3039      *
3040      * @param int $country {@link Country Country} constant.
3041      *
3042      * @throws \InvalidArgumentException
3043      */
3044     private function checkCountry(&$country)
3045     {
3046         if (!isset($country)) {
3047             throw new \InvalidArgumentException('Country must be set');
3048         }
3049         if (is_numeric($country) && !is_int($country)) {
3050             $country = intval($country);
3051         }
3052         if (!is_numeric($country) || !is_int($country)) {
3053             throw new \InvalidArgumentException(
3054                 "Country must be an integer: {$country}"
3055             );
3056         }
3057     }
3058 
3059     /**
3060      * Checks/fixes to the language input is valid.
3061      *
3062      * @param int $language {@link Language Language} constant.
3063      *
3064      * @throws \InvalidArgumentException
3065      */
3066     private function checkLanguage(&$language)
3067     {
3068         if (!isset($language)) {
3069             throw new \InvalidArgumentException('Language must be set');
3070         }
3071         if (is_numeric($language) && !is_int($language)) {
3072             $language = intval($language);
3073         }
3074         if (!is_numeric($language) || !is_int($language)) {
3075             throw new \InvalidArgumentException('Language must be a integer');
3076         }
3077     }
3078 
3079     /**
3080      * Checks/fixes to the currency input is valid.
3081      *
3082      * @param int $currency {@link Currency Currency} constant.
3083      *
3084      * @throws \InvalidArgumentException
3085      */
3086     private function checkCurrency(&$currency)
3087     {
3088         if (!isset($currency)) {
3089             throw new \InvalidArgumentException('Currency must be set');
3090         }
3091         if (is_numeric($currency) && !is_int($currency)) {
3092             $currency = intval($currency);
3093         }
3094         if (!is_numeric($currency) || !is_int($currency)) {
3095             throw new \InvalidArgumentException('Currency must be a integer');
3096         }
3097     }
3098 
3099     /**
3100      * Checks/fixes so no/number is a valid input.
3101      *
3102      * @param int $no Number.
3103      *
3104      * @throws \InvalidArgumentException
3105      */
3106     private function checkNo(&$no)
3107     {
3108         if (!isset($no)) {
3109             throw new \InvalidArgumentException('number must be set');
3110         }
3111         if (is_numeric($no) && !is_int($no)) {
3112             $no = intval($no);
3113         }
3114         if (!is_numeric($no) || !is_int($no) || $no <= 0) {
3115             throw new \InvalidArgumentException(
3116                 'number must be an integer and greater than 0'
3117             );
3118         }
3119     }
3120 
3121     /**
3122      * Checks/fixes so reservation number is a valid input.
3123      *
3124      * @param string $rno Reservation number.
3125      *
3126      * @throws \InvalidArgumentException
3127      */
3128     private function checkRNO(&$rno)
3129     {
3130         if (!is_string($rno)) {
3131             $rno = strval($rno);
3132         }
3133         if (strlen($rno) == 0) {
3134             throw new \InvalidArgumentException('RNO must be set');
3135         }
3136     }
3137 
3138     /**
3139      * Checks/fixes so that reference/refCode are valid.
3140      *
3141      * @param string $reference Reference string.
3142      * @param string $refCode   Reference code.
3143      *
3144      * @throws \InvalidArgumentException
3145      */
3146     private function checkRef(&$reference, &$refCode)
3147     {
3148         if (!is_string($reference)) {
3149             $reference = strval($reference);
3150             if (!is_string($reference)) {
3151                 throw new \InvalidArgumentException('Reference must be a string');
3152             }
3153         }
3154 
3155         if (!is_string($refCode)) {
3156             $refCode = strval($refCode);
3157             if (!is_string($refCode)) {
3158                 throw new \InvalidArgumentException('Reference code must be a string');
3159             }
3160         }
3161     }
3162 
3163     /**
3164      * Check so required argument is supplied.
3165      *
3166      * @param string $argument argument to check
3167      * @param string $name     name of argument
3168      *
3169      * @throws \InvalidArgumentException
3170      */
3171     private function checkArgument($argument, $name)
3172     {
3173         if (!is_string($argument)) {
3174             $argument = strval($argument);
3175         }
3176 
3177         if (strlen($argument) == 0) {
3178             throw new \InvalidArgumentException("{$name} must be set: {$argument}");
3179         }
3180     }
3181 
3182     /**
3183      * Check so Locale settings (country, currency, language) are set.
3184      *
3185      * @throws \RuntimeException If locale configurations are missing
3186      */
3187     private function checkLocale()
3188     {
3189         if (!is_int($this->country)
3190             || !is_int($this->language)
3191             || !is_int($this->currency)
3192         ) {
3193             throw new \RuntimeException('You must set country, language and currency!');
3194         }
3195     }
3196 
3197     /**
3198      * Checks whether a goods list is set.
3199      *
3200      * @throws \RuntimeException
3201      */
3202     private function checkGoodslist()
3203     {
3204         if (!is_array($this->goodsList) || empty($this->goodsList)) {
3205             throw new \RuntimeException('No articles in goods list');
3206         }
3207     }
3208 
3209     /**
3210      * Ensure the configuration is of the correct type.
3211      *
3212      * @param array|ArrayAccess|null $config an optional config to validate
3213      *
3214      * @throws \RuntimeException
3215      */
3216     private function checkConfig($config = null)
3217     {
3218         if ($config === null) {
3219             $config = $this->config;
3220         }
3221         if (!($config instanceof \ArrayAccess)
3222             && !is_array($config)
3223         ) {
3224             throw new \RuntimeException('Klarna instance not fully configured');
3225         }
3226     }
3227 }
3228 
API documentation generated by ApiGen