PluginProbe
Pay with Vipps and MobilePay for WooCommerce / 5.4.2
Pay with Vipps and MobilePay for WooCommerce v5.4.2
6.2.1 6.2.0 6.1.10 6.1.9 6.1.8 6.1.7 6.1.6 6.1.5 6.1.4 6.1.3 6.1.2 6.1.1 6.1.0 6.0.5 6.0.4 6.0.3 6.0.2 6.0.1 6.0.0 5.4.3 5.4.2 5.4.1 5.4.0 5.3.4 trunk All 183 releases
woo-vipps / payment / VippsApi.class.php

VippsApi.class.php in Pay with Vipps and MobilePay for WooCommerce 5.4.2, at payment/VippsApi.class.php

1,589 lines 75.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /*
3 This is the VippsApi class, a delegate in WC_Payment_Gateway that handles the actual communication with Vipps.
4 The parameters are fetched from the containing class. IOK 2018-05-11
5
6
7 This file is part of the plugin Pay with Vipps and MobilePay for WooCommerce
8 Copyright (c) 2019 WP-Hosting AS
9
10 MIT License
11
12 Copyright (c) 2019 WP-Hosting AS
13
14 Permission is hereby granted, free of charge, to any person obtaining a copy
15 of this software and associated documentation files (the "Software"), to deal
16 in the Software without restriction, including without limitation the rights
17 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18 copies of the Software, and to permit persons to whom the Software is
19 furnished to do so, subject to the following conditions:
20
21 The above copyright notice and this permission notice shall be included in all
22 copies or substantial portions of the Software.
23
24 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30 SOFTWARE.
31
32
33 */
34 if ( ! defined( 'ABSPATH' ) ) {
35 exit; // Exit if accessed directly
36 }
37 require_once(dirname(__FILE__) . "/VippsAPIException.class.php");
38
39 class VippsApi {
40 public $gateway;
41
42 public function __construct($gateway) {
43 $this->gateway = $gateway;
44 }
45
46 // These abstraction gets the correct client id and so forth based on whether or not test mode is on
47 public function get_merchant_serial() {
48 return $this->gateway->get_merchant_serial();
49 }
50 public function get_clientid($msn="") {
51 return $this->gateway->get_clientid($msn);
52 }
53 public function get_secret($msn="") {
54 return $this->gateway->get_secret($msn);
55 }
56 public function get_key($msn="") {
57 return $this->gateway->get_key($msn);
58 }
59 // Orderprefix is the same for all MSN (currently)
60 public function get_orderprefix() {
61 return $this->gateway->get_orderprefix();
62 }
63
64 public function get_option($optionname) {
65 return $this->gateway->get_option($optionname);
66 }
67 public function log($what,$type='info') {
68 return $this->gateway->log($what,$type);
69 }
70
71 // All methods get these headers, adding meta info, access token etc
72 public function get_headers($msn="") {
73 if (!$msn) $msn =$this->get_merchant_serial();
74 $date = gmdate('c');
75 $ip = $_SERVER['SERVER_ADDR'] ?? $_SERVER['LOCAL_ADDR'] ?? '127.0.0.1' ;
76 $at = $this->get_access_token($msn);
77 $subkey = $this->get_key($msn);
78
79 if (!$msn || !$at || !$subkey) {
80 return null;
81 }
82
83 $headers = array();
84 $headers['Authorization'] = 'Bearer ' . $at;
85 $headers['X-TimeStamp'] = $date;
86 $headers['X-Source-Address'] = $ip;
87 $headers['Ocp-Apim-Subscription-Key'] = $subkey;
88 $headers['Merchant-Serial-Number'] = $msn;
89
90 $headers['Vipps-System-Name'] = 'woocommerce';
91 $headers['Vipps-System-Version'] = get_bloginfo( 'version' ) . "/" . WC_VERSION;
92 $headers['Vipps-System-Plugin-Name'] = 'woo-vipps';
93 $headers['Vipps-System-Plugin-Version'] = WOO_VIPPS_VERSION;
94 return $headers;
95 }
96
97 // List all webhooks registered for the given MSN IOK 2023-12-19
98 // will return false on error and empty when no webhooks. Use the "raw" version to get
99 // the errors. 2025-01-31
100 public function get_webhooks($msn="") {
101 try {
102 return $this->get_webhooks_raw($msn);
103 return $res;
104 } catch (Exception $e) {
105 $this->log(sprintf(__("Could not get webhooks for merchant serial number %1\$s: ", 'woo-vipps'), $msn) . $e->getMessage(), 'error');
106 return false;
107 }
108 }
109
110 // version of get webhooks that re-throws errors so you can detect configuration problems easier
111 // IOK 2025-01-31
112 public function get_webhooks_raw($msn="") {
113 $command = "webhooks/v1/webhooks";
114 if (!$msn) $msn = $this->get_merchant_serial();
115 $headers = $this->get_headers($msn);
116 $args = [];
117 $res = $this->http_call($msn,$command,$args,'GET',$headers,'json');
118 return $res;
119 }
120
121
122 // Try to register a webhook for the site and the MSN passed
123 public function register_webhook($msn, $callback, $events=null) {
124 $command = "webhooks/v1/webhooks";
125 if (!$msn) $msn = $this->get_merchant_serial();
126 $headers = $this->get_headers($msn);
127 // We want the authorized event, and all the "no longer relevant" events. IOK 2023-12-19
128 if (!$events) {
129 $events = ['epayments.payment.authorized.v1', 'epayments.payment.aborted.v1', 'epayments.payment.expired.v1', 'epayments.payment.terminated.v1'];
130 }
131 $args = ['url'=>$callback, 'events'=>$events];
132 try {
133 $res = $this->http_call($msn,$command,$args,'POST',$headers,'json');
134 return $res;
135 } catch (Exception $e) {
136 $this->log(sprintf(__("Could not register webhooks for merchant serial number %1\$s callback %2\$s: ", 'woo-vipps'), $msn, $callback) . $e->getMessage(), 'error');
137 return false;
138 }
139 }
140 // Delete a webhook with the given id
141 public function delete_webhook($msn, $id) {
142 $command = "webhooks/v1/webhooks/" . $id;
143 if (!$msn) $msn = $this->get_merchant_serial();
144 $headers = $this->get_headers($msn);
145 $args = [];
146 try {
147 $res = $this->http_call($msn,$command,$args,'DELETE',$headers,'json');
148 return $res;
149 } catch (Exception $e) {
150 $this->log(sprintf(__("Could not delete webhook for merchant serial number %1\$s id %2\$s: ", 'woo-vipps'), $msn, $id) . $e->getMessage(), 'error');
151 return false;
152 }
153 }
154
155 // Get an App access token if neccesary. Returns this or throws an error. IOK 2018-04-18
156 // IOK 2023-12-19 changed this so the system could use several MSNs in the future.
157 public function get_access_token($msn="",$force=0) {
158 $msn = $msn ?? $this->get_merchant_serial();
159 $transientname = '_vipps_app_token' . '_' . sanitize_title($msn);
160 // First, get a stored token if it exists
161 $stored = get_transient($transientname);
162 if (!$force && $stored && $stored['expires_on'] > time()) {
163 return $stored['access_token'];
164 }
165 // Otherwise, get it from vipps - this might throw errors
166 $fresh = $this->get_access_token_from_vipps($msn);
167 if (!$fresh) return null;
168
169 $at = $fresh['access_token'];
170 $expire = $fresh['expires_in']/2;
171 set_transient($transientname,$fresh,$expire);
172 return $at;
173 }
174
175 // Fetch an access token if possible from the Vipps Api IOK 2018-04-18
176 private function get_access_token_from_vipps($msn="") {
177 $clientid=$this->get_clientid($msn);
178 $secret=$this->get_secret($msn);
179 $subkey = $this->get_key($msn);
180
181 $command = 'accessToken/get';
182 try {
183 $args = array('client_id'=>$clientid,'client_secret'=>$secret,'Ocp-Apim-Subscription-Key'=>$subkey);
184 $result = $this->http_call($msn,$command,array(),'POST',$args,'url');
185 return $result;
186 } catch (TemporaryVippsAPIException $e) {
187 $this->log(__("Could not get Vipps access token",'woo-vipps') .' '. $e->getMessage(), 'error');
188 throw $e;
189 } catch (Exception $e) {
190 $this->log(__("Could not get Vipps access token",'woo-vipps') .' '. $e->getMessage(). "\n" . $e->getMessage(), 'error');
191 throw new VippsAPIConfigurationException($e->getMessage());
192 }
193 }
194
195 # Order Management API functions
196 // 200, 400, 401, 404, 409, 409 invalid params also
197 public function add_image ($image, $is_bytes=false){
198 $command = 'order-management/v1/images/';
199 $bytes = $image;
200
201
202 if (!$is_bytes){
203 if ($image && is_readable($image)) {
204 $bytes = file_get_contents($image);
205 if (!$bytes) {
206 $this->log(__("Could not read image file: ",'woo-vipps') .' '. $image, 'error');
207 return false;
208 }
209
210 // Check image dimensions
211 $imageinfo = getimagesize($image);
212 if ($imageinfo && $imageinfo[1] < 167) {
213 $this->log(__("Image height too small - minimum height requirement is 167px", 'woo-vipps'), 'error');
214 return false;
215 }
216 }
217 }
218
219 $msn = $this->get_merchant_serial();
220 $headers = $this->get_headers($msn);
221
222 // imageid = ^[0-9A-Za-z-_\.] - 128 chars
223 $imageid = hash('sha512',$bytes); // Yields 128 hex chars
224 $base64 = base64_encode($bytes);
225 $args = ['imageId'=>$imageid,'src'=>$base64,'type'=>'base64'];
226
227 try {
228 $res = $this->http_call($msn,$command,$args,'POST',$headers,'json');
229 return $res['imageId'];
230 } catch (Exception $e) {
231 if ($this->is_duplicate_error($e)) return $imageid;
232
233 if ($this->is_image_size_error($e)) {
234 $this->log(__("Image rejected by Vipps - minimum height requirement is 167px", 'woo-vipps'), 'error');
235 return false;
236 }
237
238 $this->log(__("Could not send image to Vipps: ", 'woo-vipps') . $e->getMessage(), 'error');
239 return false;
240 }
241 }
242
243 private function is_duplicate_error($e) {
244 if (!is_a($e, 'VippsApiException')) return false;
245 return ($e->responsecode == 409) ||
246 ($e->responsecode == 400 && preg_match("!duplicate!i", $e->getMessage()));
247 }
248
249 private function is_image_size_error($e) {
250 if (!is_a($e, 'VippsApiException')) return false;
251 return $e->responsecode == 400 &&
252 (strpos($e->getMessage(), 'height') !== false ||
253 strpos($e->getMessage(), 'size') !== false);
254 }
255
256 // Used by the add_receipt API call as well as epayment_initiate_payment. The latter will use this
257 // if we have the shipping information - that is, we are using the normal woo checkout flow. IOK 2023-12-13
258 public function get_receipt_data($order) {
259 $receiptdata = [];
260 try {
261 $orderlines = [];
262 $bottomline = ['tipAmount'=>0, 'giftCardAmount'=>0, 'terminalId'=>'woocommerce'];
263 $bottomline['currency'] = $order->get_currency();
264
265 // 'receipt.bottomLine.giftCardAmount' is deprecated in epayment: https://developer.vippsmobilepay.com/api/epayment/#tag/CreatePayments
266 // and same for 'bottomLine.giftCardAmount' in order management api: https://developer.vippsmobilepay.com/api/order-management/#tag/Orderlines/operation/post-receipt-v2
267 // LP 2025-07-10
268 $giftcardamount = apply_filters('woo_vipps_order_giftcard_amount', 0, $order);
269 $tipamount = apply_filters('woo_vipps_order_tip_amount', 0, $order);
270 $bottomline['tipAmount'] = round($tipamount*100);
271 $bottomline['giftCardAmount'] = round($giftcardamount*100);
272
273 // 'receipt.bottomLine.terminalId' is deprecated in epayment: https://developer.vippsmobilepay.com/api/epayment/#tag/CreatePayments/operation/createPayment
274 // and same for 'bottomLine.terminalId' in order management api: https://developer.vippsmobilepay.com/api/order-management/#tag/Orderlines/operation/post-receipt-v2
275 // ----> use 'posId' instead.
276 // LP 2025-07-10
277 $bottomline['terminalId'] = apply_filters('woo_vipps_order_terminalid', 'woocommerce', $order);
278 $bottomline['receiptNumber'] = strval($order->get_id());
279
280 foreach ($order->get_items() as $key => $order_item) {
281 $orderline = [];
282 $prodid = $order_item->get_product_id(); // sku can be tricky
283 $totalNoTax = $order_item->get_total() ?: "0";
284 $tax = $order_item->get_total_tax() ?: "0";
285 $total = $tax+$totalNoTax;
286 $subtotalNoTax = $order_item->get_subtotal() ?: "0";
287 $subtotalTax = $order_item->get_subtotal_tax() ?: "0";
288 $subtotal = $subtotalNoTax + $subtotalTax;
289 $quantity = $order_item->get_quantity();
290 $unitprice = $subtotal/$quantity;
291 // Must do this to avoid rounding errors, since we get floats instead of money here :(
292 $discount = round(100*$subtotal) - round(100*$total);
293 if ($discount < 0) $discount = 0;
294 $product = wc_get_product($prodid);
295 $url = home_url("/");
296 if ($product) {
297 $url = get_permalink($prodid);
298 }
299 $taxpercentageraw = 0;
300 if ($subtotalNoTax > 0) {
301 $taxpercentageraw = (($subtotal - $subtotalNoTax) / $subtotalNoTax)*100;
302 }
303 $taxrate = abs(round(100*$taxpercentageraw));
304 $taxpercentage = abs(round($taxpercentageraw));
305 $unitInfo = [];
306 $orderline['name'] = strip_tags($order_item->get_name());
307 $orderline['id'] = strval($prodid);
308 $orderline['totalAmount'] = round($total*100);
309 $orderline['totalAmountExcludingTax'] = round($totalNoTax*100);
310 $orderline['totalTaxAmount'] = round($tax*100);
311
312 $orderline['taxRate'] = $taxrate;
313 $unitinfo['unitPrice'] = round($unitprice*100);
314 $unitinfo['quantity'] = strval($quantity);
315 $unitinfo['quantityUnit'] = 'PCS';
316 $orderline['unitInfo'] = $unitinfo;
317 $orderline['discount'] = $discount;
318 $orderline['productUrl'] = $url;
319 $orderline['isShipping'] = false;
320 $orderlines[] = $orderline;
321 }
322
323 foreach($order->get_items('fee') as $key=>$order_item) {
324 $orderline = [];
325 $totalNoTax = $order_item->get_total() ?: "0";
326 $tax = $order_item->get_total_tax() ?: "0";
327 $total = $tax+$totalNoTax;
328 $quantity = 1;
329 $taxpercentageraw = 0;
330 if ($totalNoTax > 0) {
331 $taxpercentageraw = (($total - $totalNoTax) / $totalNoTax)*100;
332 }
333 $taxrate = abs(round(100*$taxpercentageraw));
334 $taxpercentage = abs(round($taxpercentageraw));
335 $unitInfo = [];
336 $orderline['name'] = strip_tags($order_item->get_name());
337 $orderline['id'] = substr(sanitize_title($orderline['name']), 0, 254);
338 $orderline['totalAmount'] = round($total*100);
339 $orderline['totalAmountExcludingTax'] = round($totalNoTax*100);
340 $orderline['totalTaxAmount'] = round($tax*100);
341 $orderline['discount'] = 0;
342
343 $orderline['taxRate'] = $taxrate;
344 $orderlines[] = $orderline;
345 }
346
347
348 // Handle shipping
349 foreach( $order->get_items( 'shipping' ) as $item_id => $order_item ){
350 $shippingline = [];
351 $orderline['name'] = strip_tags($order_item->get_name());
352 $orderline['id'] = strval($order_item->get_method_id());
353 if (method_exists($order_item, 'get_instance_id')) {
354 $orderline['id'] .= ":" . strval($order_item->get_instance_id());
355 }
356
357 $totalNoTax = $order_item->get_total() ?: "0";
358 $tax = $order_item->get_total_tax() ?: "0";
359 $total = $tax+$totalNoTax;
360 $subtotalNoTax =$totalNoTax;
361 $subtotalTax = $tax;
362 $subtotal = $subtotalNoTax + $subtotalTax;
363
364 $taxpercentageraw = 0;
365 if ($subtotalNoTax > 0) {
366 $taxpercentageraw = (($subtotal - $subtotalNoTax) / $subtotalNoTax)*100;
367 }
368 $taxpercentage = abs(round($taxpercentageraw));
369 $taxrate= abs(round($taxpercentageraw * 100));
370
371 $orderline['totalAmount'] = round($total*100);
372 $orderline['totalAmountExcludingTax'] = round($totalNoTax*100);
373 $orderline['totalTaxAmount'] = round($tax*100);
374 $orderline['taxRate'] = $taxrate;
375
376 $unitinfo = [];
377
378 $unitinfo['unitPrice'] = round($total*100);
379 $unitinfo['quantity'] = strval(1);
380 $unitinfo['quantityUnit'] = 'PCS';
381 $orderline['unitInfo'] = $unitinfo;
382 $discount = 0;
383 $orderline['discount'] = $discount;
384 $orderline['isShipping'] = true;
385 $orderlines[] = $orderline;
386 }
387
388 $receiptdata['orderLines'] = $orderlines;
389 $receiptdata['bottomLine'] = $bottomline;
390
391 } catch (Exception $e) {
392 $this->log(sprintf(__('Cannot create receipt for order %1$d: %2$s', 'woo-vipps'), $order->get_id(), $e->getMessage()), 'error');
393 }
394 return $receiptdata;
395 }
396
397 public function add_receipt ($order) {
398 if ($order->get_meta('_vipps_receipt_sent')) {
399 return true;
400 }
401 $vippsid = $order->get_meta('_vipps_orderid');
402 if (!$vippsid) {
403 $this->log(sprintf(__("Cannot add receipt for order %1\$d: No vipps id present", 'woo-vipps'), $order->get_id()), 'error');
404 return false;
405 }
406 // Currently ecom or recurring - we are only doing ecom for now IOK 2022-06-20
407 // please note that 'ecom' applies to both ecom and epayment. IOK 2023-12-13
408 $paymenttype = apply_filters('woo_vipps_receipt_type', 'ecom', $order);
409 $command = 'order-management/v2/' . $paymenttype . '/receipts/' . $vippsid;
410 $msn = $this->get_merchant_serial();
411 $headers = $this->get_headers($msn);
412
413 $receiptdata = $this->get_receipt_data($order);
414 if (empty($receiptdata)) {
415 $this->log(__("Could not send receipt to Vipps: ", 'woo-vipps') . $order->getId(), 'error');
416 return false;
417 }
418 try {
419 $res = $this->http_call($msn,$command,$receiptdata,'POST',$headers,'json');
420 $order->update_meta_data('_vipps_receipt_sent', true);
421 $order->save();
422 $this->log(sprintf(__("Receipt for order %1\$d sent to Vipps ", 'woo-vipps'), $order->get_id()), 'info');
423 return true;
424 } catch (Exception $e) {
425 $this->log(__("Could not send receipt to Vipps: ", 'woo-vipps') . $e->getMessage(), 'error');
426 return false;
427 }
428
429
430 }
431
432 // Note that paymenttype 'ecom' applies to both ecom and epayment. IOK 2023-12-13
433 public function add_category($order, $link, $imageid, $categorytype="GENERAL", $paymenttype="ecom") {
434 $vippsid = $order->get_meta('_vipps_orderid');
435 if (!$vippsid) {
436 $this->log(sprintf(__("Cannot add category for order %1\$d: No vipps id present", 'woo-vipps'), $order->get_id()), 'error');
437 return false;
438 }
439
440 $date = gmdate('c');
441 $ip = $_SERVER['SERVER_ADDR'] ?? $_SERVER['LOCAL_ADDR'] ?? '127.0.0.1' ;
442 $at = $this->get_access_token();
443 $subkey = $this->get_key();
444 $msn = $this->get_merchant_serial();
445 $headers = $this->get_headers($msn);
446
447 // Currently ecom or recurring - we are only doing ecom for now IOK 2022-06-20
448 // Note that 'ecom' applies to both ecom and epayment. IOK 2023-12-13
449 $paymenttype = apply_filters('woo_vipps_receipt_type', 'ecom', $order);
450 $command = "order-management/v2/$paymenttype/categories/$vippsid";
451
452 $args = ['category'=>$categorytype, 'orderDetailsUrl' => $link ];
453 if ($imageid) {
454 $args['imageId'] = $imageid;
455 }
456 try {
457 $res = $this->http_call($msn,$command,$args,'PUT',$headers,'json');
458 return true;
459 } catch (Exception $e) {
460 $this->log(sprintf(__("Could not add category %1\$s to Vipps: ", 'woo-vipps'), $categorytype) . $e->getMessage(), 'error');
461 return false;
462 }
463 }
464
465 // Note that paymenttype 'ecom' applies to both ecom and epayment. IOK 2023-12-13
466 public function get_receipt($order, $paymenttype = "ecom") {
467 $vippsid = $order->get_meta('_vipps_orderid');
468 if (!$vippsid) {
469 $this->log(sprintf(__("Cannot add category for order %1\$d: No vipps id present", 'woo-vipps'), $order->get_id()), 'error');
470 return false;
471 }
472 $msn = $this->get_merchant_serial();
473 $headers = $this->get_headers($msn);
474
475 // Currently ecom or recurring - we are only doing ecom for now IOK 2022-06-20
476 // Note that 'ecom' applies to both ecom and epayment. IOK 2023-12-13
477 $paymenttype = apply_filters('woo_vipps_receipt_type', 'ecom', $order);
478 $command = "order-management/v2/$paymenttype/$vippsid";
479 try {
480 $res = $this->http_call($msn,$command,[],'GET',$headers);
481 return $res;
482 } catch (Exception $e) {
483 $this->log(sprintf(__("Could not get receipt data for order %1\$s from Vipps: ", 'woo-vipps'), $order->get_id()) . $e->getMessage(), 'error');
484 return false;
485 }
486
487 }
488 # End Order Management API functions
489
490 // Initiate payment via the epayment API; Express Checkout will still use Ecomm/v2 IOK 2023-12-13
491 // Not any more: Express Checkout is started if the logistics parameters are set. IOK 2025-06-19
492 public function epayment_initiate_payment($phone,$order,$returnurl,$authtoken,$idempotency_key=null) {
493 $command = 'epayment/v1/payments';
494 $msn = $this->get_merchant_serial();
495 $subkey = $this->get_key($msn);
496 $prefix = $this->get_orderprefix();
497 $static_shipping = $order->get_meta('_vipps_static_shipping');
498 $needs_shipping = $order->get_meta('_vipps_needs_shipping');
499
500 if (!$subkey) {
501 throw new VippsAPIConfigurationException(__('The Vipps gateway is not correctly configured.','woo-vipps'));
502 $this->log(__('The Vipps gateway is not correctly configured.','woo-vipps'),'error');
503 }
504 if (!$msn) {
505 throw new VippsAPIConfigurationException(__('The Vipps gateway is not correctly configured.','woo-vipps'));
506 $this->log(__('The Vipps gateway is not correctly configured.','woo-vipps'),'error');
507 }
508
509 if (!$idempotency_key) $idempotency_key = $order->get_order_key();
510
511 // Make sure to clean up old metas, since we now support restarting order payment with Vipps retry sesssions,
512 // so these may be set and cause conflicts. LP 2026-03-11
513 if ($order->get_meta('_vipps_shipping_set')) {
514 $order->delete_meta_data('_vipps_checkout_session');
515 $order->delete_meta_data('_vipps_express_checkout'); // note: This is also set for Checkout. LP 2026-03-12
516 $order->delete_meta_data('_vipps_init_timestamp');
517 $order->delete_meta_data('_vipps_callback_timestamp');
518 $order->delete_meta_data('_vipps_capture_timestamp');
519 $order->delete_meta_data('_vipps_refund_timestamp');
520 $order->delete_meta_data('_vipps_cancel_timestamp');
521 }
522
523 $headers = $this->get_headers($msn);
524
525 $express = $order->get_meta('_vipps_express_checkout');
526
527 // Make it easier to find express checkout orders in Vipps' backend IOK 2025-10-29
528 if ($express) {
529 $headers['Vipps-System-Plugin-Name'] = 'woo-vipps-express';
530 }
531
532 // We will use this to retrieve the orders in the callback, since the prefix can change in the admin interface. IOK 2018-05-03
533 // This is really for the new epayment api only, but we do this to ensure we use the same logic. For short prefixes and order numbers.
534 // Pad orderid with 0 to the left so the entire vipps-orderid/reference is at least 8 chars long. IOK 2022-04-06
535 $orderid = $order->get_id();
536 $woovippsid = $prefix . $orderid;
537 $len = strlen($woovippsid);
538 if ($len < 8) { # max is 50 so that would probably not be an issue
539 $padwith = 8 - strlen($prefix);
540 $paddedid = str_pad("".$orderid, $padwith, "0", STR_PAD_LEFT);
541 $woovippsid = $prefix . $paddedid;
542 }
543 $vippsorderid = apply_filters('woo_vipps_orderid', $woovippsid, $prefix, $order);
544
545 // Retry sessions: add retry index to reference and idempotency key. Note: reusing idempotency key even
546 // with a different reference will result in fail. We also dont want to regenerate the order key (which is used as idempotency key by default)
547 // since that may have unwanted effects (e.g. links in session/emails may stop working). LP 2026-03-13
548 // https://developer.vippsmobilepay.com/docs/knowledge-base/orderid/#handling-multiple-payment-attempts-for-the-same-order
549 $retrycount = intval($order->get_meta('_vipps_retry_count'));
550 if ($retrycount) {
551 $vippsorderid .= "-$retrycount";
552 $idempotency_key .= "-$retrycount";
553 }
554 $headers['Idempotency-Key'] = $idempotency_key;
555
556 $order->update_meta_data('_vipps_api', 'epayment');
557 $order->update_meta_data('_vipps_prefix',$prefix);
558 $order->update_meta_data('_vipps_orderid', $vippsorderid);
559 $order->set_transaction_id($vippsorderid); // The Vipps order id is probably the clossest we are getting to a transaction ID IOK 2019-03-04
560 $order->delete_meta_data('_vipps_static_shipping'); // Don't need this any more
561 $order->save();
562
563 $callback = $this->gateway->payment_callback_url($authtoken, $orderid);
564 $fallback = $returnurl;
565
566
567 $data = [];
568 $data['reference'] = $vippsorderid;
569 $data['paymentMethod'] = ['type' => 'WALLET']; // This is the Vipps MobilePay app. CARD is credit card, must then use userFlow WEB_REDIRECT
570 $data['amount'] = ['currency' => $order->get_currency(), 'value' => round(wc_format_decimal($order->get_total(),'') * 100)];
571 $data['returnUrl'] = $fallback;
572
573 $data['customer'] = [];
574
575 // Allow filters to use CUSTOMER_PRESENT if using in store situation with the user physically present IOK 2023-12-13
576 $data['customer']['customerInteraction'] = apply_filters('woo_vipps_customerInteraction', 'CUSTOMER_NOT_PRESENT', $orderid);
577 if ($phone) {
578 $phonenr = Vipps::normalizePhoneNumber($phone, $order->get_billing_country());
579 if ($phonenr) {
580 $data['customer']['phoneNumber'] = $phonenr;
581 }
582 $data['customer'] = apply_filters('woo_vipps_payment_customer_data',$data['customer'],$orderid);
583 }
584
585 // Store the original orderid as metadata, so we can retrieve it if neccessary IOK 2023-12-21
586 $metadata = [];
587 $metadata['orderid'] = $orderid;
588 $metadata = apply_filters('woo_vipps_payment_metadata', $metadata, $orderid);
589 $data['metadata'] = $metadata;
590
591 // We will not normally ask for user info, but if this is an express checkout order, we do want name, email, phone and maybe address.
592 // A filter will allow advanced users to add scope for their own usage.
593 // When scope has been added, it is possible to get a 'sub' value from the payment details, which for several weeks
594 // can be used to retrieve user information from the user info API. IOK 2023-03-10
595 // IOK 2023-03-10 'scope' determines for what data we ask the customer.
596 // possible values, name, address, email, phoneNumber, birthDate, nin and accountNumbers (last ones are of course restricted)
597 // we need name, email and maybe address for the new express. LP 2025-05-26
598 $scope = array();
599 if ($express) {
600 // The old "explicit shipping" option which is now the only option - if set to "yes", always ask for address
601 $explicit_option = ($this->gateway->get_option('useExplicitCheckoutFlow') == "yes");
602 // Merchant may always need the address, so if so chosen, ask for it
603 $always_address = ($this->gateway->get_option('expresscheckout_always_address') == "yes");
604 $ask_for_address = apply_filters('woo_vipps_express_checkout_ask_for_address', ($needs_shipping || $always_address || $explicit_option), $order);
605
606 // Otherwise we are going for name, email, phone.
607 if ($ask_for_address) {
608 $scope = ["name", "email", "phoneNumber", "address"];
609 } else {
610 $scope = ["name", "email", "phoneNumber"];
611 }
612 $scope = apply_filters('woo_vipps_express_checkout_scope', $scope, $order);
613 }
614
615 $scope = apply_filters('woo_vipps_payment_scope', $scope, $orderid);
616 if (!empty($scope)) {
617 $data['profile'] = [];
618 $data['profile']['scope'] = join(" ", $scope);
619 }
620
621 // minimumUserAge: Integer [0..100] or null. LP 2025-05-26
622 $minage = apply_filters('woo_vipps_payment_minimum_user_age', null);
623 $minageint = intval($minage);
624 // intval defaults to 0 when it fails to convert to int, so make sure this case will default to null, by is_numeric. LP 2025-05-26
625 if (is_numeric($minage) && $minageint >= 0 && $minageint <= 100) {
626 $minage = $minageint;
627 } else {
628 $minage = null;
629 }
630 $data['minimumUserAge'] = $minage;
631
632 // WEB_REDIRECT is the normal flow; requires a returnUrl. PUSH_MESSAGE requires a valid customer (phone number)
633 // NATIVE_REDIRECT is for automatic app switch between a native app and the Vipps MobilePay app.
634 // QR returns a QR code that can be scanned to complete the payment. IOK 2023-12-13
635 $data['userFlow'] = apply_filters('woo_vipps_payment_user_flow', 'WEB_REDIRECT', $orderid);
636
637 // Some control over the QR
638 if ($data['userFlow'] == 'QR') {
639 // Formats are IMAGE/SVG+XML, TEXT/TARGETURL, IMAGE/PNG
640 $data['qrFormat'] = ['format' => apply_filters('woo_vipps_payment_qr_format', 'IMAGE/SVG+XML', $orderid),
641 'size' => apply_filters('woo_vipps_payment_qr_size', 1024, $orderid)];
642 }
643
644
645 $shop_identification = apply_filters('woo_vipps_transaction_text_shop_id', home_url());
646 $transactionText = __('Confirm your order from','woo-vipps') . ' ' . $shop_identification;
647 $data['paymentDescription'] = apply_filters('woo_vipps_transaction_text', $transactionText, $order);
648 // The limit for the transaction text is 100. Ensure we don't go over. Thanks to Marco1970 on wp.org for reporting this. IOK 2019-10-17
649 $length = strlen($data['paymentDescription']);
650 if ($length>99) {
651 $this->log(__('The transaction text is too long! We are using a shorter transaction text to allow the transaction text to go through, but please check the \'woo_vipps_transaction_text_shop_id\' filter so that you can use a shorter name for your store', 'woo-vipps'));
652 $data['paymentDescription'] = substr($data['paymentDescription'],0,90); // Add some slack if this happens. IOK 2019-10-17
653 }
654
655 // Epayment can send the receipt already in the initiate call, so lets do it. IOK 2023-12-23
656 $receiptdata = $this->get_receipt_data($order);
657 if (!empty($receiptdata)) {
658 $data['receipt'] = $receiptdata;
659 $order->update_meta_data('_vipps_receipt_sent', true);
660 $order->save();
661 }
662
663 // The 'shipping' setting is to be set if and only if we are using Express Checkout *and* the order does in fact
664 // need shipping. IOK 2025-06-09
665 if ($express && $needs_shipping) {
666 if ($static_shipping) {
667 $data['shipping']['fixedOptions'] = $static_shipping;
668 } else { // dynamic shipping options for express. LP 2025-05-26
669 $shippingcallback = $this->gateway->shipping_details_callback_url($authtoken, $orderid);
670 $shippingoptions = ['callbackUrl' => $shippingcallback, 'callbackAuthorizationToken' => $authtoken];
671 $data['shipping']['dynamicOptions'] = $shippingoptions;
672 }
673 }
674
675 if ($data['receipt'] ?? false) {
676 // Please note: If expiresAt is added, a receipt must also be added.
677 // expiresAt -- control expiry of payment., must be more than 10 minutes, less than 28 days.
678 // format is RFC 3339 so yyyy-mm-ddTH:i:sZ gmt.
679 // These are for payments that can wait for fullfillment for quite a while, not very well suited for normal Woo stores where stock is
680 // an issue. IOK 2023-12-13
681 $expiresAt = apply_filters('woo_vipps_payment_expires_at', false, $orderid);
682 if ($expiresAt !== false) {
683 if (is_string($expiresAt)) {
684 $expiresAt = gmdate('Y-m-d\TH:i:s\Z', strtotime($expiresAt));
685 } elseif (is_int($expiresAt) && $expiresAt > time()) {
686 $expiresAt = gmdate('Y-m-d\TH:i:s\Z', $expiresAt);
687 } else {
688 $expiresAt = false;
689 }
690 }
691 if ($expiresAt) $data['expiresAt'] = $expiresAt;
692 }
693
694 // Arbitrary metadata that will be retrieved in payment responses. Please note, key length is <= 100, value <= 500, max elements is 5
695 $metadata_raw = [];
696 $metadata_filtered = apply_filters('woo_vipps_payment_metadata', $metadata_raw, $orderid);
697 $metadata = [];
698 $i = 0;
699 foreach($metadata_filtered as $key => $value) {
700 if (strlen($key)>100 || strlen($value) > 500) {
701 $this->log(sprintf(__('Could not add key %1$s to payment metadata of order %2$s - key or value is too long (100, 500 respectively)', 'woo-vipps'), $key, $orderid));
702 continue;
703 }
704 $i++;
705 if ($i > 5) {
706 $this->log(sprintf(__('Could not add all keys to the payment metadata of order %1$s - only 5 items are allowed', 'woo-vipps'), $orderid));
707 break;
708 }
709 $metadata[$key] = $value;
710 }
711 if (!empty($metadata)) {
712 $data['metadata'] = $metadata;
713 }
714
715 $this->log("Initiating Vipps MobilePay epayment session for $vippsorderid", 'debug');
716 $data = apply_filters('woo_vipps_epayment_initiate_payment_data', $data);
717
718 // Now for QR, this value will be an URL to the QR code, or the target URL. If the flow is PUSH_MESSAGE, nothing will be returned.
719 $res = $this->http_call($msn,$command,$data,'POST',$headers,'json');
720
721 // Backwards compatibility: Previous API returned this as an URL. We also get a 'reference' back, the Vipps Order Id
722 $res['url'] = $res['redirectUrl'] ?? false;
723 return $res;
724 }
725
726
727 // This is Vipps Checkout IOK 2021-06-19
728 // Updated for V3 2023-01-09
729 public function initiate_checkout($customerinfo,$order,$returnurl,$authtoken,$idempotency_key=null) {
730 $command = 'checkout/v3/session';
731 $static_shipping = $order->get_meta('_vipps_static_shipping');
732 $needs_shipping = $order->get_meta('_vipps_needs_shipping');
733
734 $msn = $this->get_merchant_serial();
735 $subkey = $this->get_key($msn);
736 $clientid = $this->get_clientid($msn);
737 $secret = $this->get_secret($msn);
738 $prefix = $this->get_orderprefix();
739 // Don't go on with the order, but don't tell the customer too much. IOK 2018-04-24
740 if (!$subkey) {
741 throw new VippsAPIConfigurationException(__('The Vipps gateway is not correctly configured.','woo-vipps'));
742 $this->log(__('The Vipps gateway is not correctly configured.','woo-vipps'),'error');
743 }
744 if (!$msn) {
745 throw new VippsAPIConfigurationException(__('The Vipps gateway is not correctly configured.','woo-vipps'));
746 $this->log(__('The Vipps gateway is not correctly configured.','woo-vipps'),'error');
747 }
748
749 if (!$idempotency_key) $idempotency_key = $order->get_order_key();
750
751 // We will use this to retrieve the orders in the callback, since the prefix can change in the admin interface. IOK 2018-05-03
752 // Pad orderid with 0 to the left so the entire vipps-orderid/reference is at least 8 chars long. IOk 2022-04-06
753 $orderid = $order->get_id();
754 $woovippsid = $prefix . $orderid;
755 $len = strlen($woovippsid);
756 if ($len < 8) { # max is 50 so that would probably not be an issue
757 $padwith = 8 - strlen($prefix);
758 $paddedid = str_pad("".$orderid, $padwith, "0", STR_PAD_LEFT);
759 $woovippsid = $prefix . $paddedid;
760 }
761 $vippsorderid = apply_filters('woo_vipps_orderid', $woovippsid, $prefix, $order);
762
763
764 $order->update_meta_data('_vipps_prefix',$prefix);
765 $order->update_meta_data('_vipps_orderid', $vippsorderid);
766 $order->set_transaction_id($vippsorderid); // The Vipps order id is probably the clossest we are getting to a transaction ID IOK 2019-03-04
767 # $order->delete_meta_data('_vipps_static_shipping'); // Don't need this any more
768 $order->save();
769
770 $headers = $this->get_headers($msn);
771 // Required for Checkout
772 $headers['client_id'] = $clientid;
773 $headers['client_secret'] = $secret;
774
775 $headers['Idempotency-Key'] = $idempotency_key;
776
777 // Object to send.
778 $data = array();
779 $data['reference'] = $vippsorderid;
780
781 // The string returned is a prefix ending with callback=, for v3 we need to send a complete URL
782 // so we just add the callback type here.
783 $callback = $this->gateway->payment_callback_url($authtoken,$orderid) . "checkout";
784 $fallback = $returnurl;
785
786 $transaction = array();
787 $currency = $order->get_currency();
788 // Ignore refOrderId - for child-transactions
789 $transaction['reference'] = $vippsorderid;
790 $transaction['amount'] = array('value' => round(wc_format_decimal($order->get_total(),'') * 100), 'currency' => $currency);
791 $shop_identification = apply_filters('woo_vipps_transaction_text_shop_id', home_url());
792 $transactionText = __('Confirm your order from','woo-vipps') . ' ' . $shop_identification;
793 $transaction['paymentDescription'] = apply_filters('woo_vipps_transaction_text', $transactionText, $order);
794
795 // The limit for the transaction text is 100. Ensure we don't go over. Thanks to Marco1970 on wp.org for reporting this. IOK 2019-10-17
796 $length = strlen($transaction['paymentDescription']);
797 if ($length>99) {
798 $this->log(__('The transaction text is too long! We are using a shorter transaction text to allow the transaction text to go through, but please check the \'woo_vipps_transaction_text_shop_id\' filter so that you can use a shorter name for your store', 'woo-vipps'));
799 $transaction['paymentDescription'] = substr($transaction['paymentDescription'],0,90); // Add some slack if this happens. IOK 2019-10-17
800 }
801
802
803 ## Vipps Checkout Shipping
804 $shippingcallback = $this->gateway->shipping_details_callback_url($authtoken, $orderid);
805 $shippingcallback .= "/v3/checkout/" . $vippsorderid . "/shippingDetails"; # because this is how eCom v2 does it.
806 $gw = $this->gateway;
807 if ($needs_shipping) {
808 $logistics = array();
809 if ($static_shipping) {
810 $logistics['fixedOptions'] = $static_shipping["shippingDetails"];
811 unset($logistics['dynamicOptionsCallback']);
812 } else {
813 $logistics['dynamicOptionsCallback'] = $shippingcallback;
814 }
815
816 // Add integration data if present
817 $integrations = array();
818 if ($gw->get_option('vcs_porterbuddy') == 'yes') {
819 $porterbuddy = array();
820 $porterbuddy['publicToken'] = $gw->get_option('vcs_porterbuddy_publicToken');
821 $porterbuddy['apiKey'] = $gw->get_option('vcs_porterbuddy_apiKey');
822 $origin = array();
823 $origin['name'] = get_bloginfo('name');
824 $origin['phoneNumber'] = $gw->get_option('vcs_porterbuddy_phoneNumber');
825 $origin['email'] = get_option('admin_email');
826 $address = array();
827 $address['streetAddress'] = join(", ", [WC()->countries->get_base_address(), WC()->countries->get_base_address_2()]);
828 $address['postalCode'] = WC()->countries->get_base_postcode();
829 $address['city'] = WC()->countries->get_base_city();
830 $address['country'] = WC()->countries->get_base_country();
831 $origin['address'] = $address;
832 $porterbuddy['origin'] = apply_filters('woo_vipps_porterbuddy_origin', $origin);
833 $integrations['porterbuddy'] = $porterbuddy;
834 }
835
836 if ($gw->get_option('vcs_helthjem') == 'yes') {
837 $helthjem = array();
838 $helthjem['username'] = $gw->get_option('vcs_helthjem_username');
839 $helthjem['password'] = $gw->get_option('vcs_helthjem_password');
840 $helthjem['shopId'] = $gw->get_option('vcs_helthjem_shopId');
841 $integrations['helthjem'] = $helthjem;
842 }
843 if (!empty($integrations)) {
844 // 'logistics.integrations' deprecated in Checkout: https://developer.vippsmobilepay.com/api/checkout/#tag/Session/paths/~1checkout~1v3~1session/post. LP 2025-07-10
845 $logistics['integrations'] = $integrations;
846 }
847 $data['logistics'] = $logistics;
848 }
849
850 // IOK 2025-03-26 currenlty only legal value
851 $data['type'] = "PAYMENT";
852
853 if (!empty($customerinfo)) {
854 $data['prefillCustomer'] = $customerinfo;
855 }
856
857 # This have to exist, but we'll not check it now.
858 if (! function_exists("wc_terms_and_conditions_page_id")) {
859 $msg = sprintf(__('You need a newer version of WooCommerce to use %1$s!', 'woo-vipps'), Vipps::CheckoutName());
860 $this->log($msg, 'error');;
861 throw new Exception($msg);
862 }
863 $termsAndConditionsUrl = get_permalink(wc_terms_and_conditions_page_id());
864 $data['merchantInfo'] = array('callbackAuthorizationToken'=>$authtoken, 'callbackUrl'=>$callback, 'returnUrl'=>$fallback);
865 if (!empty($termsAndConditionsUrl)) {
866 $data['merchantInfo']['termsAndConditionsUrl'] = $termsAndConditionsUrl;
867 } else {
868 $this->log(sprintf(__('Your site does not have a Terms and Conditions page defined - starting %1$s anyway, but this should be defined', 'woo-vipps'), Vipps::CheckoutName()));
869 }
870
871 // From v3: Certain data moved to a 'configuration' field
872 $configuration = [];
873 $configuration['elements'] = "Full";
874 $configuration['customerInteraction'] = apply_filters('woo_vipps_checkout_customerInteraction', 'CUSTOMER_NOT_PRESENT', $orderid);
875 $configuration['userFlow'] = "WEB_REDIRECT"; // Change to NATIVE_REDIRECT for apps in below filter
876 // Require consent of email and openid sub - really for login
877 $configuration['requireUserInfo'] = apply_filters('woo_vipps_checkout_requireUserInfo', $gw->get_option('requireUserInfo_checkout') == 'yes' , $orderid);
878
879
880 // IOK 2023-12-22 and we can add an order summary, so do so by default
881 $summarize = apply_filters('woo_vipps_checkout_show_order_summary', true, $order);
882 if ($summarize) {
883 $ordersummary = $this->get_receipt_data($order);
884 // This is different in the receipt api, the epayment api and in checkout.
885 $ordersummary['orderBottomLine'] = $ordersummary['bottomLine'];
886 unset($ordersummary['bottomLine']);
887
888 // Don't finalize the receipt number - we just want to show this rn.
889 unset($ordersummary['orderBottomLine']['receiptNumber']);
890 if (!empty($ordersummary)) {
891 $transaction['orderSummary'] = $ordersummary;
892 $configuration['showOrderSummary'] = true;
893
894 // Currently, for checkout, *this counts as a receipt*, even though it lacks shipping.
895 // (As of 2025-03-20, not a bug, but it may be one when modifying the order )
896 $order->update_meta_data('_vipps_receipt_sent', true);
897 $order->save();
898
899 }
900 }
901
902 // ISO-3166 Alpha 2 country list
903 $countries = array_keys((new WC_Countries())->get_allowed_countries());
904 $allowed_countries = apply_filters('woo_vipps_checkout_countries', $countries, $orderid);
905 if ($allowed_countries) {
906 $configuration['countries'] = ['supported' => $allowed_countries ];
907 } else {
908
909 }
910
911 // External payment methods IOK 2024-05-13
912 // Should return a map from other_method => ['gw'=>'gateway key or any or empty string]
913 $other_payment_methods = apply_filters('woo_vipps_checkout_external_payment_methods', VippsCheckout::instance()->external_payment_methods(), $order);
914 if (!empty($other_payment_methods)) {
915 $others = [];
916 foreach ($other_payment_methods as $methodkey => $methoddata) {
917 $chooseanother = ['action'=>'vipps_gw', 'o'=>$orderid];
918 $chooseanother['cb'] = wp_create_nonce('vipps_gw');
919 $chooseanother['gw'] = ($methoddata['gw'] ?? "");
920 $others[] = ['paymentMethod' => $methodkey, 'redirectUrl'=> add_query_arg($chooseanother,admin_url("admin-post.php")) ];
921 }
922 if (!empty($others)) {
923 $configuration['externalPaymentMethods'] = $others;
924 }
925 }
926
927 // Custom consent checkbox, for integration with Mailchimp etc .
928 $customconsenttext = apply_filters('woo_vipps_checkout_consent_query', "");
929 $customconsentrequired = apply_filters('woo_vipps_checkout_consent_required', false);
930 if ($customconsenttext) {
931 $customconsent = [];
932 $customconsent['text'] = $customconsenttext;
933 $customconsent['required'] = $customconsentrequired;
934 $configuration['customConsent'] = $customconsent;
935 }
936
937 if (!$needs_shipping) {
938 $nocontacts = $this->gateway->get_option('noContactFields') == 'yes';
939 $noaddress = $this->gateway->get_option('noAddressFields') == 'yes';
940 if ($noaddress) {
941 $configuration['elements'] = "PaymentAndContactInfo";
942 }
943 // AddressFields cannot be enabled while ContactFields is disabled
944 if ($noaddress && $nocontacts) {
945 $configuration['elements'] = "PaymentOnly";
946 }
947 }
948 $data['configuration'] = $configuration;
949 $data['transaction'] = $transaction;
950
951 $data = apply_filters('woo_vipps_initiate_checkout_data', $data);
952
953 $this->log("Initiating Checkout session for $vippsorderid", 'debug');
954 $res = $this->http_call($msn,$command,$data,'POST',$headers,'json');
955 return $res;
956 }
957
958 // If an order materially changes, we need to call this to change the sum total and order description at Vipps. IOK 2025-04-11
959 public function checkout_modify_session($order, $updated_shipping=null) {
960 $command = 'checkout/v3/session';
961 $msn = $this->get_merchant_serial();
962 $subkey = $this->get_key($msn);
963 $clientid = $this->get_clientid($msn);
964 $secret = $this->get_secret($msn);
965
966 $orderid = $order->get_id();
967 $vippsorderid = $order->get_meta('_vipps_orderid');
968 $reference = $vippsorderid;
969
970 $headers = $this->get_headers($msn);
971 // Required for Checkout
972 $headers['client_id'] = $clientid;
973 $headers['client_secret'] = $secret;
974
975 // Transaction: amount, description, orderSummary for modify.
976 $transaction = array();
977 $currency = $order->get_currency();
978
979 $total = round(wc_format_decimal($order->get_total(),'') * 100);
980 if ($total < 100) $total = 100; // Vipps requires all orders to be at least this large IOK 2025-05-14
981
982 $transaction['amount'] = array('value' => $total, 'currency' => $currency);
983 $shop_identification = apply_filters('woo_vipps_transaction_text_shop_id', home_url());
984 $transactionText = __('Confirm your order from','woo-vipps') . ' ' . $shop_identification;
985 $transaction['paymentDescription'] = apply_filters('woo_vipps_transaction_text', $transactionText, $order);
986 $summarize = apply_filters('woo_vipps_checkout_show_order_summary', true, $order);
987 if ($summarize) {
988 $ordersummary = $this->get_receipt_data($order);
989 // This is different in the receipt api, the epayment api and in checkout.
990 $ordersummary['orderBottomLine'] = $ordersummary['bottomLine'];
991 unset($ordersummary['bottomLine']);
992
993 // Don't finalize the receipt number - we just want to show this rn.
994 unset($ordersummary['orderBottomLine']['receiptNumber']);
995 if (!empty($ordersummary)) {
996 $transaction['orderSummary'] = $ordersummary;
997 }
998 }
999 $data = ['transaction'=>$transaction];
1000 // Probably mostly because free shipping has been added or removed using coupons. IOK 2025-09-12
1001 if ($updated_shipping) {
1002 $data['logisticOptions'] = $updated_shipping;
1003 }
1004 $res = $this->http_call($msn,$command . "/" . urlencode($reference),$data,'PATCH',$headers,'json');
1005 return $res;
1006 }
1007 public function checkout_expire_session($order) {
1008 $msn = $this->get_merchant_serial();
1009 $clientid = $this->get_clientid($msn);
1010 $secret = $this->get_secret($msn);
1011 $vippsorderid = $order->get_meta('_vipps_orderid');
1012 $reference = $vippsorderid;
1013 $command = "checkout/v3/session/$reference/expire";
1014
1015 $headers = $this->get_headers($msn);
1016 // Required for Checkout
1017 $headers['client_id'] = $clientid;
1018 $headers['client_secret'] = $secret;
1019
1020 $res = $this->http_call($msn, $command, [], 'POST', $headers, 'json');
1021 return $res;
1022 }
1023
1024 // Returns same data as session poll; we've changed it so 404s and so returns as words
1025 public function checkout_get_session_info($order) {
1026 $command = 'checkout/v3/session';
1027 $vippsid = $order->get_meta('_vipps_orderid');
1028 $command .= "/" . $vippsid;
1029
1030 $msn = $this->get_merchant_serial();
1031 $headers = $this->get_headers($msn);
1032 $clientid = $this->get_clientid($msn);
1033 $secret = $this->get_secret($msn);
1034
1035 $headers = $this->get_headers($msn);
1036 // Required for checkout
1037 $headers['client_id'] = $clientid;
1038 $headers['client_secret'] = $secret;
1039 $data = [];
1040
1041 $res = "ERROR";
1042 try {
1043 $res = $this->http_call($msn,$command,$data,'GET',$headers,'json');
1044 if (($res['sessionState'] ?? "") == 'SessionExpired') {
1045 return 'EXPIRED';
1046 }
1047 } catch (VippsAPIException $e) {
1048 if ($e->responsecode == 404) {
1049 return 'EXPIRED';
1050 } else {
1051 $this->log(sprintf(__("Error polling status - error message %1\$s", 'woo-vipps'), $e->getMessage()));
1052 // We can't do much more than this so just return ERROR
1053 return 'ERROR';
1054 }
1055 } catch (Exception $e) {
1056 $this->log(sprintf(__("Error polling status - error message %1\$s", 'woo-vipps'), $e->getMessage()));
1057 // We can't dom uch more than this so just return ERROR
1058 return 'ERROR';
1059 }
1060 return $res;
1061 }
1062
1063 // Capture a payment made. Amount is in cents and required. IOK 2018-05-07
1064 public function capture_payment($order,$amount,$requestid=1) {
1065 $orderid = $order->get_meta('_vipps_orderid');
1066
1067 $command = 'Ecomm/v2/payments/'.$orderid.'/capture';
1068 $msn = $this->get_merchant_serial();
1069 $subkey = $this->get_key($msn);
1070 if (!$subkey) {
1071 throw new VippsAPIConfigurationException(__('The Vipps gateway is not correctly configured.','woo-vipps'));
1072 $this->log(__('The Vipps gateway is not correctly configured.','woo-vipps'),'error');
1073 }
1074 if (!$msn) {
1075 throw new VippsAPIConfigurationException(__('The Vipps gateway is not correctly configured.','woo-vipps'));
1076 $this->log(__('The Vipps gateway is not correctly configured.','woo-vipps'),'error');
1077 }
1078 $headers = $this->get_headers($msn);
1079 $headers['X-Request-Id'] = $requestid;
1080
1081 $transaction = array();
1082 // Ignore refOrderId - for child-transactions
1083 $transaction['amount'] = round($amount);
1084
1085 $shop_identification = apply_filters('woo_vipps_transaction_text_shop_id', home_url());
1086
1087 $transaction['transactionText'] = __('Order capture for order','woo-vipps') . ' ' . $orderid . ' ' . $shop_identification;
1088
1089 // The limit for the transaction text is 100. Ensure we don't go over. Thanks to Marco1970 on wp.org for reporting this. IOK 2019-10-17
1090 $length = strlen($transaction['transactionText']);
1091 if ($length>99) {
1092 $this->log(__('The transaction text is too long! We are using a shorter transaction text to allow the transaction text to go through, but please check the \'woo_vipps_transaction_text_shop_id\' filter so that you can use a shorter name for your store', 'woo-vipps'));
1093 $transaction['transactionText'] = __('Order capture for order','woo-vipps') . ' ' . $orderid;
1094 $transaction['transactionText'] = substr($transaction['transactionText'],0,90); // Add some slack if this happens. IOK 2019-10-17
1095 }
1096
1097
1098
1099 $data = array();
1100 $data['merchantInfo'] = array('merchantSerialNumber' => $msn);
1101 $data['transaction'] = $transaction;
1102
1103 $res = $this->http_call($msn,$command,$data,'POST',$headers,'json');
1104 return $res;
1105 }
1106
1107 // Cancel a reserved but not captured payment IOK 2018-05-07
1108 public function cancel_payment($order,$requestid=1) {
1109 $orderid = $order->get_meta('_vipps_orderid');
1110
1111 $command = 'Ecomm/v2/payments/'.$orderid.'/cancel';
1112 $msn = $this->get_merchant_serial();
1113 $subkey = $this->get_key($msn);
1114 if (!$subkey) {
1115 throw new VippsAPIConfigurationException(__('The Vipps gateway is not correctly configured.','woo-vipps'));
1116 $this->log(__('The Vipps gateway is not correctly configured.','woo-vipps'),'error');
1117 }
1118 if (!$msn) {
1119 throw new VippsAPIConfigurationException(__('The Vipps gateway is not correctly configured.','woo-vipps'));
1120 $this->log(__('The Vipps gateway is not correctly configured.','woo-vipps'),'error');
1121 }
1122 $headers = $this->get_headers($msn);
1123 $headers['X-Request-Id'] = $requestid;
1124
1125 $transaction = array();
1126 $transaction['transactionText'] = __('Order cancel for order','woo-vipps') . ' ' . $orderid . ' ';
1127
1128 $data = array();
1129 $data['merchantInfo'] = array('merchantSerialNumber' => $msn);
1130 $data['transaction'] = $transaction;
1131
1132 $res = $this->http_call($msn,$command,$data,'PUT',$headers,'json');
1133 return $res;
1134 }
1135
1136 // Refund a captured payment. IOK 2018-05-08
1137 public function refund_payment($order,$requestid=1,$amount=0,$cents=false) {
1138 $orderid = $order->get_meta('_vipps_orderid');
1139 $amount = $amount ? $amount : wc_format_decimal($order->get_total(),'');
1140
1141 $command = 'Ecomm/v2/payments/'.$orderid.'/refund';
1142 $msn = $this->get_merchant_serial();
1143 $subkey = $this->get_key($msn);
1144 if (!$subkey) {
1145 throw new VippsAPIConfigurationException(__('The Vipps gateway is not correctly configured.','woo-vipps'));
1146 $this->log(__('The Vipps gateway is not correctly configured.','woo-vipps'),'error');
1147 }
1148 if (!$msn) {
1149 throw new VippsAPIConfigurationException(__('The Vipps gateway is not correctly configured.','woo-vipps'));
1150 $this->log(__('The Vipps gateway is not correctly configured.','woo-vipps'),'error');
1151 }
1152 $headers = $this->get_headers($msn);
1153 $headers['X-Request-Id'] = $requestid;
1154
1155 // Ignore refOrderId - for child-transactions
1156 $transaction = array();
1157 // If we have passed the value as 'øre' we don't need to calculate any more.
1158 if ($cents) {
1159 $transaction['amount'] = $amount;
1160 } else {
1161 $transaction['amount'] = round($amount * 100);
1162 }
1163 $transaction['transactionText'] = __('Refund for order','woo-vipps') . ' ' . $orderid;
1164
1165
1166 $data = array();
1167 $data['merchantInfo'] = array('merchantSerialNumber' => $msn);
1168 $data['transaction'] = $transaction;
1169
1170 $res = $this->http_call($msn,$command,$data,'POST',$headers,'json');
1171 return $res;
1172 }
1173
1174 // Used to retrieve shipping and user details for express checkout orders where relevant and the callback isn't coming.
1175 public function payment_details ($order) {
1176 $requestid=0;
1177 $orderid = $order->get_meta('_vipps_orderid');
1178 $command = 'Ecomm/v2/payments/'.$orderid.'/details';
1179 $msn = $this->get_merchant_serial();
1180 $subkey = $this->get_key($msn);
1181 if (!$subkey) {
1182 throw new VippsAPIConfigurationException(__('The Vipps gateway is not correctly configured.','woo-vipps'));
1183 $this->log(__('The Vipps gateway is not correctly configured.','woo-vipps'),'error');
1184 }
1185 if (!$msn) {
1186 throw new VippsAPIConfigurationException(__('The Vipps gateway is not correctly configured.','woo-vipps'));
1187 $this->log(__('The Vipps gateway is not correctly configured.','woo-vipps'),'error');
1188 }
1189 $headers = $this->get_headers($msn);
1190 $headers['X-Request-Id'] = $requestid;
1191
1192 $data = array();
1193
1194 $res = $this->http_call($msn,$command,$data,'GET',$headers,'json');
1195 return $res;
1196 }
1197
1198 // Support for then new epayment API, which is also used by Checkout
1199 // Cancel a reserved but not captured payment IOK 2018-05-07
1200 // Currently must cancel the entire amount, but partial cancel will be possible.
1201 public function epayment_cancel_payment($order,$requestid=1) {
1202 $orderid = $order->get_meta('_vipps_orderid');
1203 $command = 'epayment/v1/payments/'.$orderid.'/cancel';
1204 $msn = $this->get_merchant_serial();
1205 $subkey = $this->get_key($msn);
1206 if (!$subkey) {
1207 throw new VippsAPIConfigurationException(__('The Vipps gateway is not correctly configured.','woo-vipps'));
1208 $this->log(__('The Vipps gateway is not correctly configured.','woo-vipps'),'error');
1209 }
1210 if (!$msn) {
1211 throw new VippsAPIConfigurationException(__('The Vipps gateway is not correctly configured.','woo-vipps'));
1212 $this->log(__('The Vipps gateway is not correctly configured.','woo-vipps'),'error');
1213 }
1214
1215 $headers = $this->get_headers($msn);
1216 $headers['Idempotency-Key'] = $requestid;
1217
1218 // The only current allowed argument is "cancelTransactionOnly" which will, if true, only cancel
1219 // non-authorized transactions. We don't need that, but we have to send *something* or we get type errors. IOK 2024-11-25
1220 $data = array('cancelTransactionOnly' => false);
1221
1222 $res = $this->http_call($msn,$command,$data,'POST',$headers,'json');
1223 return $res;
1224 }
1225
1226 // Support for then new epayment API, which is also used by Checkout
1227 // Capture (a part of) reserved but not captured payment IOK 2018-05-07
1228 public function epayment_capture_payment($order, $amount, $requestid=1) {
1229 $orderid = $order->get_meta('_vipps_orderid');
1230 $command = 'epayment/v1/payments/'.$orderid.'/capture';
1231 $msn = $this->get_merchant_serial();
1232 $subkey = $this->get_key($msn);
1233 if (!$subkey) {
1234 throw new VippsAPIConfigurationException(__('The Vipps gateway is not correctly configured.','woo-vipps'));
1235 $this->log(__('The Vipps gateway is not correctly configured.','woo-vipps'),'error');
1236 }
1237 if (!$msn) {
1238 throw new VippsAPIConfigurationException(__('The Vipps gateway is not correctly configured.','woo-vipps'));
1239 $this->log(__('The Vipps gateway is not correctly configured.','woo-vipps'),'error');
1240 }
1241
1242 $clientid = $this->get_clientid();
1243 $secret = $this->get_secret();
1244 $headers = $this->get_headers($msn);
1245 $headers['Idempotency-Key'] = $requestid;
1246
1247 $modificationAmount = round($amount);
1248 $modificationCurrency = $order->get_currency();
1249
1250 $data = array();
1251 $data['modificationAmount'] = array('value'=>$modificationAmount, 'currency'=>$modificationCurrency);
1252
1253 $res = $this->http_call($msn,$command,$data,'POST',$headers,'json');
1254 return $res;
1255 }
1256
1257 // Support for then new epayment API, which is also used by Checkout
1258 // Refund (a part of) captured payment IOK 2018-05-07
1259 public function epayment_refund_payment($order, $requestid, $amount, $cents) {
1260 $orderid = $order->get_meta('_vipps_orderid');
1261 $command = 'epayment/v1/payments/'.$orderid.'/refund';
1262
1263 # null amount means the entire thing
1264 $amount = $amount ? $amount : wc_format_decimal($order->get_total(),'');
1265
1266 $msn = $this->get_merchant_serial();
1267 $subkey = $this->get_key($msn);
1268 if (!$subkey) {
1269 throw new VippsAPIConfigurationException(__('The Vipps gateway is not correctly configured.','woo-vipps'));
1270 $this->log(__('The Vipps gateway is not correctly configured.','woo-vipps'),'error');
1271 }
1272 if (!$msn) {
1273 throw new VippsAPIConfigurationException(__('The Vipps gateway is not correctly configured.','woo-vipps'));
1274 $this->log(__('The Vipps gateway is not correctly configured.','woo-vipps'),'error');
1275 }
1276
1277 $headers = $this->get_headers($msn);
1278 $headers['Idempotency-Key'] = $requestid;
1279
1280 // If we have passed the value as 'øre' we don't need to calculate any more, but woo is weird so we might need to
1281 $modificationAmount = round($amount);
1282 if ($cents) {
1283 $modificationAmount = round($amount);
1284 } else {
1285 $modificationAmount = round($amount * 100);
1286 }
1287 $modificationCurrency = $order->get_currency();
1288
1289 $data = array();
1290 $data['modificationAmount'] = array('value'=>$modificationAmount, 'currency'=>$modificationCurrency);
1291
1292 $res = $this->http_call($msn,$command,$data,'POST',$headers,'json');
1293 return $res;
1294 }
1295
1296 // For the new epayment API, also used by checkout, return payment details (but not the payment log). Equivalent to the old get-status + metainfo.
1297 // Takes either an order object or the Vipps orderid as argument.
1298 public function epayment_get_payment ($order, $msn='') {
1299 if (is_a($order, 'WC_Order')) {
1300 $orderid = $order->get_meta('_vipps_orderid');
1301 } else {
1302 $orderid = $order;
1303 }
1304 $command = 'epayment/v1/payments/'.$orderid;
1305
1306 if (!$msn) {
1307 $msn = $this->get_merchant_serial();
1308 }
1309
1310 $subkey = $this->get_key($msn);
1311 if (!$subkey) {
1312 throw new VippsAPIConfigurationException(__('The Vipps gateway is not correctly configured.','woo-vipps'));
1313 $this->log(__('The Vipps gateway is not correctly configured.','woo-vipps'),'error');
1314 }
1315 if (!$msn) {
1316 throw new VippsAPIConfigurationException(__('The Vipps gateway is not correctly configured.','woo-vipps'));
1317 $this->log(__('The Vipps gateway is not correctly configured.','woo-vipps'),'error');
1318 }
1319 $headers = $this->get_headers($msn);
1320
1321 $data = array();
1322
1323 $res = $this->http_call($msn,$command,$data,'GET',$headers);
1324 return $res;
1325 }
1326
1327 // For the new epayment API, also used by checkout, return payment log (as for old payment_details. Will be used for debugging.
1328 // epayment api.
1329 public function epayment_get_payment_log ($order) {
1330 $orderid = $order->get_meta('_vipps_orderid');
1331 $command = 'epayment/v1/payments/'.$orderid . "/events";
1332
1333 $msn = $this->get_merchant_serial();
1334 $subkey = $this->get_key($msn);
1335 if (!$subkey) {
1336 throw new VippsAPIConfigurationException(__('The Vipps gateway is not correctly configured.','woo-vipps'));
1337 $this->log(__('The Vipps gateway is not correctly configured.','woo-vipps'),'error');
1338 }
1339 if (!$msn) {
1340 throw new VippsAPIConfigurationException(__('The Vipps gateway is not correctly configured.','woo-vipps'));
1341 $this->log(__('The Vipps gateway is not correctly configured.','woo-vipps'),'error');
1342 }
1343 $headers = $this->get_headers($msn);
1344
1345 $data = array();
1346
1347 $res = $this->http_call($msn,$command,$data,'GET',$headers);
1348 return $res;
1349 }
1350
1351 // Implement part of the userInfo api, just to be able to get user data from Express Orders that aren't express
1352 // orders (because they didn't need shipping). In the future, will probably be used more + for integration with Login With Vipps
1353 public function get_userinfo($sub) {
1354 $command = "vipps-userinfo-api/userinfo" . "/" . $sub;
1355 $msn = $this->get_merchant_serial();
1356 $subkey = $this->get_key($msn);
1357 if (!$subkey) {
1358 throw new VippsAPIConfigurationException(__('The Vipps gateway is not correctly configured.','woo-vipps'));
1359 $this->log(__('The Vipps gateway is not correctly configured.','woo-vipps'),'error');
1360 }
1361 if (!$msn) {
1362 throw new VippsAPIConfigurationException(__('The Vipps gateway is not correctly configured.','woo-vipps'));
1363 $this->log(__('The Vipps gateway is not correctly configured.','woo-vipps'),'error');
1364 }
1365 $headers = $this->get_headers($msn);
1366
1367 $data = array();
1368
1369 $res = $this->http_call($msn,$command,$data,'GET',$headers);
1370 return $res;
1371 }
1372
1373
1374 // the QR api 2022-04-13. PUT is update (on id), DELETE is deletion (of id), GET is get the .. thing. Arguments would be Accept for image/png or image/svg+xml
1375 public function get_merchant_redirect_qr_entry ($id,$accept="text/targetUrl") {
1376 return $this->call_qr_merchant_redirect("GET", $id, null, $accept);
1377 }
1378 public function get_all_merchant_redirect_qr () {
1379 return $this->call_qr_merchant_redirect("GET", "", null, "text/targetUrl");
1380 }
1381 public function create_merchant_redirect_qr ($id,$url){
1382 $action = "POST";
1383 return $this->call_qr_merchant_redirect($action, $id, $url);
1384 }
1385 public function update_merchant_redirect_qr ($id, $url) {
1386 $action = "PUT";
1387 return $this->call_qr_merchant_redirect($action, $id, $url);
1388 }
1389 public function delete_merchant_redirect_qr ($id) {
1390 $action = "DELETE";
1391 return $this->call_qr_merchant_redirect($action, $id, $url);
1392 }
1393 private function call_qr_merchant_redirect($action, $id, $url=null, $accept='image/svg+xml') {
1394 $command = 'qr/v1/merchant-redirect/';
1395 if ($action != "POST") $command .= $id;
1396 $msn = $this->get_merchant_serial();
1397 $subkey = $this->get_key($msn);
1398 if (!$subkey) {
1399 throw new VippsAPIConfigurationException(__('The Vipps gateway is not correctly configured.','woo-vipps'));
1400 $this->log(__('The Vipps gateway is not correctly configured.','woo-vipps'),'error');
1401 }
1402 if (!$msn) {
1403 throw new VippsAPIConfigurationException(__('The Vipps gateway is not correctly configured.','woo-vipps'));
1404 $this->log(__('The Vipps gateway is not correctly configured.','woo-vipps'),'error');
1405 }
1406 $headers = $this->get_headers($msn);
1407 $headers['Accept'] = $accept;
1408
1409 $data = array();
1410 if ($id) $data['id'] = $id;
1411 if ($url) $data['redirectUrl'] = $url;
1412
1413 $res = $this->http_call($msn,$command,$data,$action,$headers, 'json');
1414
1415 return $res;
1416 }
1417
1418 // This isn't really neccessary since we can do this using just the fetch apis, but we'll do it anyway.
1419 // The URLs here are valid for just one hour, so this should be called right after an update.
1420 public function get_merchant_redirect_qr ($url, $accept = "image/svg+xml") {
1421 $msn= $this->get_merchant_serial();
1422 $subkey = $this->get_key($msn);
1423 if (!$subkey) {
1424 throw new VippsAPIConfigurationException(__('The Vipps gateway is not correctly configured.','woo-vipps'));
1425 $this->log(__('The Vipps gateway is not correctly configured.','woo-vipps'),'error');
1426 }
1427 if (!$msn) {
1428 throw new VippsAPIConfigurationException(__('The Vipps gateway is not correctly configured.','woo-vipps'));
1429 $this->log(__('The Vipps gateway is not correctly configured.','woo-vipps'),'error');
1430 }
1431 $headers = $this->get_headers($msn);
1432 $headers['Accept'] = $accept;
1433
1434 $res = $this->http_call($msn, $url,[],'GET',$headers);
1435 return $res;
1436 }
1437
1438 // Conveniently call Vipps IOK 2018-04-18
1439 private function http_call($msn,$command,$data,$verb='GET',$headers=null,$encoding='url'){
1440 $url = "";
1441 if (preg_match("/^http/i", $command)) {
1442 $url = $command;
1443 } else {
1444 $server=$this->gateway->apiurl($msn);
1445 $url = $server . "/" . $command;
1446 }
1447
1448 if (!$headers) $headers=array();
1449 $date = gmdate('c');
1450 $data_encoded = '';
1451 if ($encoding == 'url' || $verb == 'GET') {
1452 $data_encoded = http_build_query($data);
1453 } else {
1454 $data_encoded = json_encode($data, JSON_THROW_ON_ERROR);
1455 }
1456 $data_len = strlen ($data_encoded);
1457 $http_response_header = null;
1458
1459 $headers['Connection'] = 'close';
1460 if ($verb=='POST' || $verb == 'PATCH' || $verb == 'PUT') {
1461 $headers['Content-length'] = $data_len;
1462 if ($encoding == 'url') {
1463 $headers['Content-type'] = 'application/x-www-form-urlencoded';
1464 } else {
1465 $headers['Content-type'] = 'application/json';
1466 }
1467 }
1468 $args = array();
1469 $args['method'] = $verb;
1470 $args['headers'] = $headers;
1471 if ($verb == 'POST' || $verb == 'PATCH' || $verb == 'PUT') {
1472 $args['body'] = $data_encoded;
1473 }
1474 if ($verb == 'GET' && $data_encoded) {
1475 $url .= "?$data_encoded";
1476 }
1477
1478 $return = wp_remote_request($url,$args);
1479 $headers = array();
1480 $content=NULL;
1481 $response=0;
1482
1483 if (is_wp_error($return)) {
1484 $headers['status'] = "500 " . $return->get_error_message();
1485 $response = 500;
1486 } else {
1487 $response = wp_remote_retrieve_response_code($return);
1488 $message = wp_remote_retrieve_response_message($return);
1489
1490
1491 $headers = wp_remote_retrieve_headers($return);
1492 $headers['status'] = "$response $message";
1493 $contenttext = wp_remote_retrieve_body($return);
1494
1495 if ($contenttext) {
1496 $content = @json_decode($contenttext,true);
1497 // Assume we always get json, except for when we don't. IOK 2022-04-22.
1498 if (!$content && !empty($contenttext) && !preg_match("!json!i", $headers['content-type'])){
1499 $content = array('message' => $contenttext);
1500 }
1501 }
1502 }
1503
1504 // Parse the result, converting it to exceptions if neccessary. IOK 2018-05-11
1505 return $this->handle_http_response($msn, $response,$headers,$content);
1506 }
1507
1508 // Read the response from Vipps - if any - and convert errors (null results, results over 299)
1509 // to Exceptions IOK 2018-05-11
1510 private function handle_http_response ($msn, $response, $headers, $content) {
1511 // This would be an error in the URL or something - or a network outage IOK 2018-04-24
1512 // we will assume it is temporary (ie, no response).
1513 if (!$response) {
1514 $msg = __('No response from Vipps', 'woo-vipps');
1515 throw new TemporaryVippsAPIException($msg);
1516 }
1517
1518 // Good result!
1519 if ($response < 300) {
1520 return $content;
1521 }
1522
1523 // Now errorhandling. Default to use just the error header IOK 2018-05-11
1524 $msg = "MSN $msn " . $headers['status'] . " ";
1525
1526 // Sometimes we get one type of error, sometimes another, depending on which layer explodes. IOK 2018-04-24
1527 if ($content) {
1528 // can't happen, but be sure
1529 if (is_string($content)) {
1530 $msg .= " " . $content;
1531 // From initiate payment, at least some times. IOK 2018-06-18
1532 } elseif (isset($content['message'])) {
1533 $msg .= " " . $content['message'];
1534 // From the receipt api
1535 } elseif (isset($content['detail'])) {
1536 $msg .= (isset($content['title'])) ? (" " . $content['title']) : "";
1537 $msg .= ": " . $content['detail'];
1538 if (isset($content['extraDetails'])) {
1539 $msg .= "Extra details: " . print_r($content['extraDetails'], true);
1540 }
1541 } elseif (isset($content['errors'])) {
1542 $msg .= print_r($content['errors'], true);
1543 } elseif (isset($content['error'])) {
1544 // This seems to be only for the Access Token, which is a separate application IOK 2018-05-11
1545 $msg .= $content['error'];
1546 } elseif (isset($content['ResponseInfo'])) {
1547 // This seems to be an error in the API layer. The error is in this elements' ResponseMessage
1548 $msg .= $response . ' ' . $content['ResponseInfo']['ResponseMessage'];
1549 } elseif (isset($content['errorInfo'])) {
1550 $msg .= $response . ' ' . $content['errorInfo']['errorMessage'];
1551 } elseif (isset($content['type'])) {
1552 // The epayment API, version 1
1553 $msg .= $content['title'];
1554 if (isset($content['detail'])) $msg .= " - " . $content['detail'];
1555 if (isset($content['extraDetails'])) $msg .= " - " . print_r($content['extraDetails'], true);
1556 } else {
1557 // Otherwise, we get a simple array of objects with error messages. Grab them all.
1558 $msg .= '';
1559 if (is_array($content)) {
1560 foreach($content as $entry) {
1561 if (is_string($entry)) {
1562 // This started happening august 2023.
1563 $msg .= $entry . "\n";
1564 } elseif (is_array($entry)) {
1565 $msg .= $response . ' ' . @$entry['errorMessage'] . "\n";
1566 } else {
1567 $msg = $response . " " . print_r($content, true);
1568 }
1569 }
1570 } else {
1571 // At this point, we have no idea what we have got, so just stringify it IOK 2021-11-04
1572 $msg .= print_r($msg, true);
1573 }
1574 }
1575 }
1576
1577 // 502's are Bad Gateway which means that Vipps is busy. IOK 2018-05-11
1578 if (intval($response) == 502) {
1579 $exception = new TemporaryVippsAPIException($msg);
1580 } else {
1581 $exception = new VippsApiException($msg);
1582 }
1583
1584 $exception->responsecode = intval($response);
1585 throw $exception;
1586 }
1587
1588 }
1589