PluginProbe
Pay with Vipps and MobilePay for WooCommerce / 6.0.0
Pay with Vipps and MobilePay for WooCommerce v6.0.0
6.2.3 6.2.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 All 185 releases
woo-vipps / recurring / includes / wc-vipps-recurring-api.php

wc-vipps-recurring-api.php in Pay with Vipps and MobilePay for WooCommerce 6.0.0, at recurring/includes/wc-vipps-recurring-api.php

663 lines 20.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 defined( 'ABSPATH' ) || exit;
4
5 require_once( __DIR__ . '/wc-vipps-recurring-exceptions.php' );
6
7 /**
8 * Class WC_Vipps_Recurring_Api
9 */
10 class WC_Vipps_Recurring_Api {
11 public WC_Gateway_Vipps_Recurring $gateway;
12
13 /**
14 * Amount of days to retry a payment for when creating a charge
15 */
16 public int $retry_days = WC_VIPPS_RECURRING_RETRY_DAYS;
17
18
19 public function __construct( WC_Gateway_Vipps_Recurring $gateway ) {
20 $this->gateway = $gateway;
21 }
22
23 /**
24 * @return mixed|string|null
25 * @throws WC_Vipps_Recurring_Config_Exception
26 * @throws WC_Vipps_Recurring_Temporary_Exception
27 */
28 public function get_access_token( bool $force_fresh = false ) {
29 $stored = get_transient( '_vipps_recurring_token' );
30
31 if ( ! $force_fresh && $stored && $stored['expires_on'] > time() ) {
32 return $stored['access_token'];
33 }
34
35 $token = $this->get_access_token_from_vipps();
36
37 if ( ! $token ) {
38 return null;
39 }
40
41 set_transient( '_vipps_recurring_token', $token, $token['expires_in'] / 2 );
42
43 return $token['access_token'];
44 }
45
46 /**
47 * @throws WC_Vipps_Recurring_Config_Exception
48 * @throws WC_Vipps_Recurring_Temporary_Exception
49 */
50 private function get_access_token_from_vipps(): array {
51 try {
52 return $this->http_call( 'accessToken/get', 'POST' );
53 } catch ( WC_Vipps_Recurring_Temporary_Exception $e ) {
54 WC_Vipps_Recurring_Logger::log( 'Could not get Vipps/MobilePay access token ' . $e->getMessage() );
55
56 throw $e;
57 } catch ( Exception $e ) {
58 WC_Vipps_Recurring_Logger::log( 'Could not get Vipps/MobilePay access token ' . $e->getMessage() );
59
60 throw new WC_Vipps_Recurring_Config_Exception( $e->getMessage() );
61 }
62 }
63
64 public function generate_idempotency_key(): string {
65 return wp_generate_password( 24, false );
66 }
67
68 /**
69 * @throws WC_Vipps_Recurring_Config_Exception
70 * @throws WC_Vipps_Recurring_Exception
71 * @throws WC_Vipps_Recurring_Temporary_Exception
72 */
73 public function create_agreement( WC_Vipps_Agreement $agreement, string $idempotency_key ): array {
74 $token = $this->get_access_token();
75
76 $headers = [
77 'Authorization' => 'Bearer ' . $token,
78 'Idempotency-Key' => $idempotency_key,
79 ];
80
81 $data = apply_filters( 'wc_vipps_recurring_create_agreement_data', $agreement->to_array( true ) );
82
83 return $this->http_call( 'recurring/v3/agreements', 'POST', $data, $headers );
84 }
85
86 /**
87 * @throws WC_Vipps_Recurring_Config_Exception
88 * @throws WC_Vipps_Recurring_Exception
89 * @throws WC_Vipps_Recurring_Temporary_Exception
90 */
91 public function get_agreement( string $agreement_id ): WC_Vipps_Agreement {
92 $token = $this->get_access_token();
93
94 $headers = [
95 'Authorization' => 'Bearer ' . $token,
96 ];
97
98 return new WC_Vipps_Agreement( $this->http_call( 'recurring/v3/agreements/' . $agreement_id, 'GET', [], $headers ) );
99 }
100
101 /**
102 * @throws WC_Vipps_Recurring_Config_Exception
103 * @throws WC_Vipps_Recurring_Exception
104 * @throws WC_Vipps_Recurring_Temporary_Exception
105 */
106 public function update_agreement( string $agreement_id, WC_Vipps_Agreement $agreement, string $idempotency_key ): void {
107 $token = $this->get_access_token();
108
109 $headers = [
110 'Authorization' => 'Bearer ' . $token,
111 'Idempotency-Key' => $idempotency_key,
112 ];
113
114 $data = apply_filters( 'wc_vipps_recurring_update_agreement_data', $agreement->to_array() );
115
116 $this->http_call( 'recurring/v3/agreements/' . $agreement_id, 'PATCH', $data, $headers );
117 }
118
119 /**
120 * @throws Exception
121 */
122 private function get_price_from_agreement( WC_Vipps_Agreement $agreement ): ?int {
123 $amount = $agreement->pricing->amount;
124
125 if ( $agreement->campaign ) {
126 $now = new DateTime();
127 $end = $agreement->campaign->end;
128
129 if ( $end > $now ) {
130 $amount = $agreement->campaign->price;
131 }
132 }
133
134 return $amount;
135 }
136
137 /**
138 * @throws WC_Vipps_Recurring_Config_Exception
139 * @throws WC_Vipps_Recurring_Exception
140 * @throws WC_Vipps_Recurring_Temporary_Exception
141 */
142 public function cancel_agreement( string $agreement_id, string $idempotency_key ): void {
143 $agreement = $this->get_agreement( $agreement_id );
144 if ( $agreement->status !== WC_Vipps_Agreement::STATUS_ACTIVE ) {
145 return;
146 }
147
148 $token = $this->get_access_token();
149
150 $headers = [
151 'Authorization' => 'Bearer ' . $token,
152 'Idempotency-Key' => $idempotency_key,
153 ];
154
155 $data = apply_filters( 'wc_vipps_recurring_cancel_agreement_data', [
156 'status' => WC_Vipps_Agreement::STATUS_STOPPED,
157 ] );
158
159 $this->http_call( 'recurring/v3/agreements/' . $agreement_id, 'PATCH', $data, $headers );
160 }
161
162 /**
163 * @return mixed|string|null
164 * @throws WC_Vipps_Recurring_Config_Exception
165 * @throws WC_Vipps_Recurring_Exception
166 * @throws WC_Vipps_Recurring_Temporary_Exception
167 */
168 public function capture_reserved_charge( WC_Vipps_Agreement $agreement, WC_Vipps_Charge $charge, string $idempotency_key ) {
169 $token = $this->get_access_token();
170
171 $headers = [
172 'Authorization' => 'Bearer ' . $token,
173 'Idempotency-Key' => $idempotency_key,
174 ];
175
176 return $this->http_call( 'recurring/v3/agreements/' . $agreement->id . '/charges/' . $charge->id . '/capture', 'POST', $charge->to_array(), $headers );
177 }
178
179 /**
180 * @throws WC_Vipps_Recurring_Config_Exception
181 * @throws WC_Vipps_Recurring_Exception
182 * @throws WC_Vipps_Recurring_Temporary_Exception
183 * @throws Exception
184 */
185 public function create_charge( WC_Vipps_Agreement $agreement, string $idempotency_key, string $order_id, string $vipps_order_id, ?int $amount = null ): array {
186 $token = $this->get_access_token();
187
188 $headers = [
189 'Authorization' => 'Bearer ' . $token,
190 'Idempotency-Key' => $idempotency_key,
191 ];
192
193 if ( $amount === null ) {
194 $amount = $this->get_price_from_agreement( $agreement );
195 }
196
197 // minimum of 1 days
198 $due_at = date( 'Y-m-d', time() + 3600 * 24 );
199
200 $charge = ( new WC_Vipps_Charge() )->set_order_id( $vipps_order_id )
201 ->set_external_id( $order_id )
202 ->set_amount( $amount )
203 ->set_description( $agreement->product_name ?? get_bloginfo() )
204 ->set_transaction_type( WC_Vipps_Charge::TRANSACTION_TYPE_DIRECT_CAPTURE )
205 ->set_due( $due_at )
206 ->set_retry_days( $this->retry_days );
207
208 $data = apply_filters( 'wc_vipps_recurring_create_charge_data', $charge->to_array(), $agreement->id, $order_id );
209
210 return $this->http_call( 'recurring/v3/agreements/' . $agreement->id . '/charges', 'POST', $data, $headers );
211 }
212
213 /**
214 * @throws WC_Vipps_Recurring_Config_Exception
215 * @throws WC_Vipps_Recurring_Exception
216 * @throws WC_Vipps_Recurring_Temporary_Exception
217 */
218 public function cancel_charge( string $agreement_id, string $charge_id, string $idempotency_key ): void {
219 $token = $this->get_access_token();
220
221 $headers = [
222 'Authorization' => 'Bearer ' . $token,
223 'Idempotency-Key' => $idempotency_key,
224 ];
225
226 $this->http_call( 'recurring/v3/agreements/' . $agreement_id . '/charges/' . $charge_id, 'DELETE', [], $headers );
227 }
228
229 /**
230 * @throws WC_Vipps_Recurring_Config_Exception
231 * @throws WC_Vipps_Recurring_Exception
232 * @throws WC_Vipps_Recurring_Temporary_Exception
233 */
234 public function get_charge( string $agreement_id, string $charge_id ): WC_Vipps_Charge {
235 $token = $this->get_access_token();
236
237 $headers = [
238 'Authorization' => 'Bearer ' . $token,
239 ];
240
241 return new WC_Vipps_Charge( $this->http_call( 'recurring/v3/agreements/' . $agreement_id . '/charges/' . $charge_id, 'GET', [], $headers ) );
242 }
243
244 /**
245 * @return mixed|string|null
246 * @throws WC_Vipps_Recurring_Config_Exception
247 * @throws WC_Vipps_Recurring_Exception
248 * @throws WC_Vipps_Recurring_Temporary_Exception
249 */
250 public function refund_charge( string $agreement_id, string $charge_id, ?int $amount = null, ?string $reason = null ) {
251 $token = $this->get_access_token();
252
253 $headers = [
254 'Authorization' => 'Bearer ' . $token,
255 'Idempotency-Key' => $this->generate_idempotency_key(),
256 ];
257
258 if ( $reason !== null && strlen( $reason ) > 99 ) {
259 $reason = mb_substr( $reason, 0, 90 );
260 }
261
262 $data = [
263 'description' => $reason ?: 'Refund',
264 ];
265
266 if ( $amount !== null ) {
267 $data = array_merge( $data, [
268 'amount' => $amount,
269 ] );
270 }
271
272 $data = apply_filters( 'wc_vipps_recurring_refund_charge_data', $data );
273
274 return $this->http_call( 'recurring/v3/agreements/' . $agreement_id . '/charges/' . $charge_id . '/refund', 'POST', $data, $headers );
275 }
276
277 /**
278 * @return WC_Vipps_Charge[]
279 * @throws WC_Vipps_Recurring_Config_Exception
280 * @throws WC_Vipps_Recurring_Exception
281 * @throws WC_Vipps_Recurring_Temporary_Exception
282 */
283 public function get_charges_for( string $agreement_id ): array {
284 $token = $this->get_access_token();
285
286 $headers = [
287 'Authorization' => 'Bearer ' . $token,
288 ];
289
290 return array_map( static function ( $charge ) {
291 return new WC_Vipps_Charge( $charge );
292 }, $this->http_call( 'recurring/v3/agreements/' . $agreement_id . '/charges', 'GET', [], $headers ) );
293 }
294
295 /**
296 * @throws WC_Vipps_Recurring_Config_Exception
297 * @throws WC_Vipps_Recurring_Exception
298 * @throws WC_Vipps_Recurring_Temporary_Exception
299 */
300 public function get_userinfo( WC_Vipps_Agreement $agreement ): array {
301 $token = $this->get_access_token();
302
303 $headers = [
304 'Authorization' => 'Bearer ' . $token,
305 ];
306
307 $endpoint = str_replace( $this->get_base_url(), '', $agreement->userinfo_url );
308
309 return $this->http_call( $endpoint, 'GET', [], $headers );
310 }
311
312 /**
313 * @return array
314 * @throws WC_Vipps_Recurring_Config_Exception
315 * @throws WC_Vipps_Recurring_Exception
316 * @throws WC_Vipps_Recurring_Temporary_Exception
317 */
318 public function get_webhooks( string $msn ): array {
319 $token = $this->get_access_token();
320
321 $headers = [
322 'Authorization' => 'Bearer ' . $token,
323 'Merchant-Serial-Number' => $msn,
324 ];
325
326 return $this->http_call( 'webhooks/v1/webhooks', 'GET', [], $headers );
327 }
328
329 /**
330 * @return array
331 * @throws WC_Vipps_Recurring_Config_Exception
332 * @throws WC_Vipps_Recurring_Exception
333 * @throws WC_Vipps_Recurring_Temporary_Exception
334 */
335 public function register_webhook( string $msn ): array {
336 $token = $this->get_access_token();
337
338 $headers = [
339 'Authorization' => 'Bearer ' . $token,
340 'Merchant-Serial-Number' => $msn,
341 ];
342
343 $callback_url = $this->gateway->webhook_callback_url();
344
345 return $this->http_call( 'webhooks/v1/webhooks', 'POST', [
346 'url' => $callback_url,
347 'events' => [
348 'recurring.agreement-activated.v1',
349 'recurring.agreement-rejected.v1',
350 'recurring.agreement-stopped.v1',
351 'recurring.agreement-expired.v1',
352 'recurring.charge-reserved.v1',
353 'recurring.charge-captured.v1',
354 'recurring.charge-canceled.v1',
355 'recurring.charge-failed.v1',
356 ]
357 ], $headers );
358 }
359
360 /**
361 * @param string $id
362 *
363 * @return array|string
364 * @throws WC_Vipps_Recurring_Config_Exception
365 * @throws WC_Vipps_Recurring_Exception
366 * @throws WC_Vipps_Recurring_Temporary_Exception
367 */
368 public function delete_webhook( string $msn, string $id ) {
369 $token = $this->get_access_token();
370
371 $headers = [
372 'Authorization' => 'Bearer ' . $token,
373 'Merchant-Serial-Number' => $msn,
374 ];
375
376 return $this->http_call( 'webhooks/v1/webhooks/' . $id, 'DELETE', [], $headers );
377 }
378
379 /**
380 * @throws WC_Vipps_Recurring_Config_Exception
381 * @throws WC_Vipps_Recurring_Exception
382 * @throws WC_Vipps_Recurring_Temporary_Exception
383 */
384 public function checkout_poll( string $endpoint ) {
385 if ( ! str_starts_with( $endpoint, 'http' ) ) {
386 $endpoint = 'checkout/v3/session/' . $endpoint;
387 }
388
389 $token = $this->get_access_token();
390
391 $headers = [
392 'Authorization' => 'Bearer ' . $token,
393 ];
394
395 return $this->http_call( $endpoint, 'GET', [], $headers );
396 }
397
398 /**
399 * @throws WC_Vipps_Recurring_Missing_Value_Exception
400 * @throws WC_Vipps_Recurring_Config_Exception
401 * @throws WC_Vipps_Recurring_Exception
402 * @throws WC_Vipps_Recurring_Temporary_Exception
403 */
404 public function checkout_initiate( WC_Vipps_Checkout_Session $checkout_session ): WC_Vipps_Checkout_Session_Response {
405 $token = $this->get_access_token();
406
407 $headers = [
408 'Authorization' => 'Bearer ' . $token,
409 ];
410
411 $response = $this->http_call( 'checkout/v3/session', 'POST', $checkout_session->to_array(), $headers );
412
413 return new WC_Vipps_Checkout_Session_Response( $response );
414 }
415
416 private function get_base_url(): string {
417 if ( $this->get_test_mode() ) {
418 return 'https://apitest.vipps.no';
419 }
420
421 return 'https://api.vipps.no';
422 }
423
424 private function get_test_mode(): bool {
425 return $this->gateway->get_option( 'test_mode' ) === "yes" || WC_VIPPS_RECURRING_TEST_MODE;
426 }
427
428 /**
429 * @return mixed|string|null
430 * @throws WC_Vipps_Recurring_Config_Exception
431 * @throws WC_Vipps_Recurring_Exception
432 * @throws WC_Vipps_Recurring_Temporary_Exception
433 */
434 private function http_call( string $endpoint, string $method, array $data = [], array $headers = [], $encoding = 'json' ) {
435 // WC_Vipps_Recurring_Logger::log( sprintf( "Calling Vipps API endpoint: [%s] %s", $method, $endpoint ) );
436
437 $url = $endpoint;
438 if ( ! str_starts_with( $endpoint, "http" ) ) {
439 $url = $this->get_base_url() . '/' . $endpoint;
440 }
441
442 $client_id = $this->gateway->get_option( "client_id" );
443 $secret_key = $this->gateway->get_option( "secret_key" );
444 $subscription_key = $this->gateway->get_option( "subscription_key" );
445 $merchant_serial_number = $this->gateway->get_option( "merchant_serial_number" );
446
447 if ( $this->get_test_mode() ) {
448 $client_id = $this->gateway->get_option( "test_client_id" );
449 $secret_key = $this->gateway->get_option( "test_secret_key" );
450 $subscription_key = $this->gateway->get_option( "test_subscription_key" );
451 $merchant_serial_number = $this->gateway->get_option( "test_merchant_serial_number" );
452 }
453
454 if ( ! $subscription_key || ! $secret_key || ! $client_id ) {
455 throw new WC_Vipps_Recurring_Config_Exception( __( 'Your Vipps/MobilePay Recurring Payments gateway is not correctly configured.', 'woo-vipps' ) );
456 }
457
458 $system_plugin_version = WC_VIPPS_RECURRING_VERSION;
459 $checkout_enabled = get_option( WC_Vipps_Recurring_Helper::OPTION_CHECKOUT_ENABLED, false );
460
461 if ( $checkout_enabled ) {
462 $system_plugin_version = $system_plugin_version . '/checkout';
463 }
464
465 $headers = array_merge( [
466 'client_id' => $client_id,
467 'client_secret' => $secret_key,
468 'Ocp-Apim-Subscription-Key' => $subscription_key,
469 'Merchant-Serial-Number' => $merchant_serial_number,
470 'Vipps-System-Name' => 'woocommerce',
471 'Vipps-System-Version' => get_bloginfo( 'version' ) . '/' . ( defined( 'WC_VERSION' ) ? WC_VERSION : '0.0.0' ),
472 'Vipps-System-Plugin-Name' => 'woo-vipps-recurring',
473 'Vipps-System-Plugin-Version' => $system_plugin_version
474 ], $headers );
475
476 if ( $encoding === 'url' || $method === 'GET' ) {
477 $data_encoded = http_build_query( $data );
478 } else {
479 $data_encoded = json_encode( $data );
480 }
481
482 $data_len = strlen( $data_encoded );
483 $headers['Connection'] = 'close';
484
485 if ( $method !== 'GET' ) {
486 $headers['Content-length'] = $data_len;
487 $headers['Content-type'] = $encoding === 'url' ? 'application/x-www-form-urlencoded' : 'application/json';
488 }
489
490 $args = [];
491 $args['method'] = $method;
492 $args['headers'] = $headers;
493
494 if ( $method !== 'GET' ) {
495 $args['body'] = $data_encoded;
496 }
497
498 if ( $method === 'GET' && $data_encoded ) {
499 $url .= "?$data_encoded";
500 }
501
502 $response = wp_remote_request( $url, $args );
503
504 // throw WP error as a WC_Vipps_Recurring_Exception if response is not valid
505 $default_error = '';
506 if ( is_wp_error( $response ) ) {
507 $default_error = "500 " . $response->get_error_message();
508 }
509
510 // Parse the result, converting it to exceptions if necessary
511 return $this->handle_http_response( $response, $args['body'] ?? "<empty>", $endpoint, $default_error );
512 }
513
514 /**
515 * @param $response
516 * @param $request_body
517 * @param $endpoint
518 * @param $default_error
519 *
520 * @return mixed|string|null
521 * @throws WC_Vipps_Recurring_Exception
522 * @throws WC_Vipps_Recurring_Temporary_Exception
523 */
524 private function handle_http_response( $response, $request_body, $endpoint, $default_error ) {
525 // no response from Vipps
526 if ( ! $response ) {
527 $error_msg = __( 'No response from Vipps/MobilePay', 'woo-vipps' );
528 WC_Vipps_Recurring_Logger::log( sprintf( 'HTTP Response Temporary Error: %s with request body: %s', $error_msg, $request_body ) );
529
530 throw new WC_Vipps_Recurring_Temporary_Exception( $error_msg );
531 }
532
533 $status = (int) wp_remote_retrieve_response_code( $response );
534
535 $body = wp_remote_retrieve_body( $response );
536 if ( $body ) {
537 $body = json_decode( $body, true );
538 }
539
540 // As long as the status code is less than 300 and greater than 199 we can return the body
541 if ( $status < 300 && $status > 199 ) {
542 return $body;
543 }
544
545 // Rate limiting, temporary error
546 if ( $status === 429 ) {
547 $error_msg = __( "We hit Vipps/MobilePay's rate limit, we will retry later.", 'woo-vipps' );
548 throw new WC_Vipps_Recurring_Temporary_Exception( $error_msg );
549 }
550
551 // error handling
552 $error_msg = $default_error ?? '';
553 $is_idempotent_error = false;
554 $is_merchant_not_allowed_error = false;
555 $is_url_validation_error = false;
556
557 if ( $body ) {
558 if ( isset( $body['message'] ) ) {
559 $error_msg = $body['message'];
560 }
561
562 if ( isset( $body['error_description'] ) ) {
563 $error_msg = $body['error_description'];
564 }
565
566 if ( isset( $body['title'] ) ) {
567 $error_msg = $body['title'];
568
569 if ( isset( $body['detail'] ) ) {
570 $error_msg .= ' ' . $body['detail'];
571 }
572 }
573
574 $error_msg = trim( $error_msg );
575
576 /*
577 "type":"https://example.com/validation-error",
578 "title":"Your request parameters didn't validate.",
579 "detail":"The request body contains one or more errors",
580 "instance":"123e4567-e89b-12d3-a456-426655440000",
581 "status":"400",
582 "extraDetails":[
583 {
584 "name":"amount",
585 "reason":"Must be a positive integer larger than 100"
586 },
587 {
588 "name":"URL",
589 "reason":"Must use HTTPS and validate according to the API specification"
590 }
591 ]
592 */
593
594
595 // todo: implement new logic
596 /*
597 {
598 "type": "about:blank",
599 "title": "Not Found",
600 "status": 404,
601 "detail": "Agreement not found.",
602 "instance": "/vipps-recurring-merchant-api/v3/agreements/agr_GqnvsH0",
603 "contextId": "32900600-1dd4-47dc-9a02-28c9e6491c65"
604 }
605 */
606
607 // if ( isset( $body['message'] ) ) {
608 // $error_msg = $body['message'];
609 // } elseif ( isset( $body['error'] ) ) {
610 // // access token
611 // $error_msg = $body['error'];
612 // } elseif ( is_array( $body ) ) {
613 // $error_msg = '';
614 // foreach ( $body as $entry ) {
615 // if ( isset( $entry['code'] ) ) {
616 // $error_msg .= $entry['field'] . ': ' . $entry['message'] . "\n";
617 //
618 // if ( $entry['code'] === 'idempotentkey.hash.mismatch' ) {
619 // $is_idempotent_error = true;
620 // }
621 //
622 // if ( $entry['code'] === 'merchant.not.allowed.for.recurring.operation' ) {
623 // $is_merchant_not_allowed_error = true;
624 // }
625 //
626 // if ( $entry['code'] === 'merchantagreementurl.apacheurlvalidation' ) {
627 // $is_url_validation_error = true;
628 // }
629 // }
630 // }
631 // } else {
632 // $error_msg = $body;
633 // }
634 }
635
636 $localized_msg = $error_msg;
637 if ( $is_merchant_not_allowed_error ) {
638 /* translators: Link to a GitHub readme about the error */
639 $localized_msg = sprintf( __( 'Recurring payments is not yet activated for this sale unit. Read more <a href="%s" target="_blank">here</a>', 'woo-vipps' ), 'https://developer.vippsmobilepay.com/docs/APIs/recurring-api/recurring-api-faq/#why-do-i-get-the-error-merchantnotallowedforrecurringoperation' );
640 }
641
642 if ( $is_url_validation_error ) {
643 $localized_msg = __( 'Your WordPress URL is not passing Merchant Agreement URL validation. Is your website publicly accessible?', 'woo-vipps' );
644 }
645
646 if ( is_array( $request_body ) ) {
647 $request_body = json_encode( $request_body );
648 }
649
650 if ( is_array( $body ) ) {
651 $body = json_encode( $body );
652 }
653
654 WC_Vipps_Recurring_Logger::log( sprintf( 'HTTP Response Error (%s): %s (%s) with request body: %s. The response was: %s', $status, $error_msg, $endpoint, $request_body, $body ) );
655
656 $exception = new WC_Vipps_Recurring_Exception( $error_msg, $localized_msg );
657 $exception->response_code = $status;
658 $exception->is_idempotent_error = $is_idempotent_error;
659
660 throw $exception;
661 }
662 }
663