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