PluginProbe
Pay with Vipps and MobilePay for WooCommerce / 6.1.0
Pay with Vipps and MobilePay for WooCommerce v6.1.0
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 5.3.4 All 184 releases
woo-vipps / recurring / includes / wc-vipps-recurring-api.php

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

664 lines 20.4 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 ->set_processing_mode( $this->retry_days > 0 ? WC_Vipps_Charge::PROCESSING_MODE_MULTIPLE_ATTEMPTS : WC_Vipps_Charge::PROCESSING_MODE_SINGLE_ATTEMPT );
208
209 $data = apply_filters( 'wc_vipps_recurring_create_charge_data', $charge->to_array(), $agreement->id, $order_id );
210
211 return $this->http_call( 'recurring/v3/agreements/' . $agreement->id . '/charges', 'POST', $data, $headers );
212 }
213
214 /**
215 * @throws WC_Vipps_Recurring_Config_Exception
216 * @throws WC_Vipps_Recurring_Exception
217 * @throws WC_Vipps_Recurring_Temporary_Exception
218 */
219 public function cancel_charge( string $agreement_id, string $charge_id, string $idempotency_key ): void {
220 $token = $this->get_access_token();
221
222 $headers = [
223 'Authorization' => 'Bearer ' . $token,
224 'Idempotency-Key' => $idempotency_key,
225 ];
226
227 $this->http_call( 'recurring/v3/agreements/' . $agreement_id . '/charges/' . $charge_id, 'DELETE', [], $headers );
228 }
229
230 /**
231 * @throws WC_Vipps_Recurring_Config_Exception
232 * @throws WC_Vipps_Recurring_Exception
233 * @throws WC_Vipps_Recurring_Temporary_Exception
234 */
235 public function get_charge( string $agreement_id, string $charge_id ): WC_Vipps_Charge {
236 $token = $this->get_access_token();
237
238 $headers = [
239 'Authorization' => 'Bearer ' . $token,
240 ];
241
242 return new WC_Vipps_Charge( $this->http_call( 'recurring/v3/agreements/' . $agreement_id . '/charges/' . $charge_id, 'GET', [], $headers ) );
243 }
244
245 /**
246 * @return mixed|string|null
247 * @throws WC_Vipps_Recurring_Config_Exception
248 * @throws WC_Vipps_Recurring_Exception
249 * @throws WC_Vipps_Recurring_Temporary_Exception
250 */
251 public function refund_charge( string $agreement_id, string $charge_id, ?int $amount = null, ?string $reason = null ) {
252 $token = $this->get_access_token();
253
254 $headers = [
255 'Authorization' => 'Bearer ' . $token,
256 'Idempotency-Key' => $this->generate_idempotency_key(),
257 ];
258
259 if ( $reason !== null && strlen( $reason ) > 99 ) {
260 $reason = mb_substr( $reason, 0, 90 );
261 }
262
263 $data = [
264 'description' => $reason ?: 'Refund',
265 ];
266
267 if ( $amount !== null ) {
268 $data = array_merge( $data, [
269 'amount' => $amount,
270 ] );
271 }
272
273 $data = apply_filters( 'wc_vipps_recurring_refund_charge_data', $data );
274
275 return $this->http_call( 'recurring/v3/agreements/' . $agreement_id . '/charges/' . $charge_id . '/refund', 'POST', $data, $headers );
276 }
277
278 /**
279 * @return WC_Vipps_Charge[]
280 * @throws WC_Vipps_Recurring_Config_Exception
281 * @throws WC_Vipps_Recurring_Exception
282 * @throws WC_Vipps_Recurring_Temporary_Exception
283 */
284 public function get_charges_for( string $agreement_id ): array {
285 $token = $this->get_access_token();
286
287 $headers = [
288 'Authorization' => 'Bearer ' . $token,
289 ];
290
291 return array_map( static function ( $charge ) {
292 return new WC_Vipps_Charge( $charge );
293 }, $this->http_call( 'recurring/v3/agreements/' . $agreement_id . '/charges', 'GET', [], $headers ) );
294 }
295
296 /**
297 * @throws WC_Vipps_Recurring_Config_Exception
298 * @throws WC_Vipps_Recurring_Exception
299 * @throws WC_Vipps_Recurring_Temporary_Exception
300 */
301 public function get_userinfo( WC_Vipps_Agreement $agreement ): array {
302 $token = $this->get_access_token();
303
304 $headers = [
305 'Authorization' => 'Bearer ' . $token,
306 ];
307
308 $endpoint = str_replace( $this->get_base_url(), '', $agreement->userinfo_url );
309
310 return $this->http_call( $endpoint, 'GET', [], $headers );
311 }
312
313 /**
314 * @return array
315 * @throws WC_Vipps_Recurring_Config_Exception
316 * @throws WC_Vipps_Recurring_Exception
317 * @throws WC_Vipps_Recurring_Temporary_Exception
318 */
319 public function get_webhooks( string $msn ): array {
320 $token = $this->get_access_token();
321
322 $headers = [
323 'Authorization' => 'Bearer ' . $token,
324 'Merchant-Serial-Number' => $msn,
325 ];
326
327 return $this->http_call( 'webhooks/v1/webhooks', 'GET', [], $headers );
328 }
329
330 /**
331 * @return array
332 * @throws WC_Vipps_Recurring_Config_Exception
333 * @throws WC_Vipps_Recurring_Exception
334 * @throws WC_Vipps_Recurring_Temporary_Exception
335 */
336 public function register_webhook( string $msn ): array {
337 $token = $this->get_access_token();
338
339 $headers = [
340 'Authorization' => 'Bearer ' . $token,
341 'Merchant-Serial-Number' => $msn,
342 ];
343
344 $callback_url = $this->gateway->webhook_callback_url();
345
346 return $this->http_call( 'webhooks/v1/webhooks', 'POST', [
347 'url' => $callback_url,
348 'events' => [
349 'recurring.agreement-activated.v1',
350 'recurring.agreement-rejected.v1',
351 'recurring.agreement-stopped.v1',
352 'recurring.agreement-expired.v1',
353 'recurring.charge-reserved.v1',
354 'recurring.charge-captured.v1',
355 'recurring.charge-canceled.v1',
356 'recurring.charge-failed.v1',
357 ]
358 ], $headers );
359 }
360
361 /**
362 * @param string $id
363 *
364 * @return array|string
365 * @throws WC_Vipps_Recurring_Config_Exception
366 * @throws WC_Vipps_Recurring_Exception
367 * @throws WC_Vipps_Recurring_Temporary_Exception
368 */
369 public function delete_webhook( string $msn, string $id ) {
370 $token = $this->get_access_token();
371
372 $headers = [
373 'Authorization' => 'Bearer ' . $token,
374 'Merchant-Serial-Number' => $msn,
375 ];
376
377 return $this->http_call( 'webhooks/v1/webhooks/' . $id, 'DELETE', [], $headers );
378 }
379
380 /**
381 * @throws WC_Vipps_Recurring_Config_Exception
382 * @throws WC_Vipps_Recurring_Exception
383 * @throws WC_Vipps_Recurring_Temporary_Exception
384 */
385 public function checkout_poll( string $endpoint ) {
386 if ( ! str_starts_with( $endpoint, 'http' ) ) {
387 $endpoint = 'checkout/v3/session/' . $endpoint;
388 }
389
390 $token = $this->get_access_token();
391
392 $headers = [
393 'Authorization' => 'Bearer ' . $token,
394 ];
395
396 return $this->http_call( $endpoint, 'GET', [], $headers );
397 }
398
399 /**
400 * @throws WC_Vipps_Recurring_Missing_Value_Exception
401 * @throws WC_Vipps_Recurring_Config_Exception
402 * @throws WC_Vipps_Recurring_Exception
403 * @throws WC_Vipps_Recurring_Temporary_Exception
404 */
405 public function checkout_initiate( WC_Vipps_Checkout_Session $checkout_session ): WC_Vipps_Checkout_Session_Response {
406 $token = $this->get_access_token();
407
408 $headers = [
409 'Authorization' => 'Bearer ' . $token,
410 ];
411
412 $response = $this->http_call( 'checkout/v3/session', 'POST', $checkout_session->to_array(), $headers );
413
414 return new WC_Vipps_Checkout_Session_Response( $response );
415 }
416
417 private function get_base_url(): string {
418 if ( $this->get_test_mode() ) {
419 return 'https://apitest.vipps.no';
420 }
421
422 return 'https://api.vipps.no';
423 }
424
425 private function get_test_mode(): bool {
426 return $this->gateway->get_option( 'test_mode' ) === "yes" || WC_VIPPS_RECURRING_TEST_MODE;
427 }
428
429 /**
430 * @return mixed|string|null
431 * @throws WC_Vipps_Recurring_Config_Exception
432 * @throws WC_Vipps_Recurring_Exception
433 * @throws WC_Vipps_Recurring_Temporary_Exception
434 */
435 private function http_call( string $endpoint, string $method, array $data = [], array $headers = [], $encoding = 'json' ) {
436 // WC_Vipps_Recurring_Logger::log( sprintf( "Calling Vipps API endpoint: [%s] %s", $method, $endpoint ) );
437
438 $url = $endpoint;
439 if ( ! str_starts_with( $endpoint, "http" ) ) {
440 $url = $this->get_base_url() . '/' . $endpoint;
441 }
442
443 $client_id = $this->gateway->get_option( "client_id" );
444 $secret_key = $this->gateway->get_option( "secret_key" );
445 $subscription_key = $this->gateway->get_option( "subscription_key" );
446 $merchant_serial_number = $this->gateway->get_option( "merchant_serial_number" );
447
448 if ( $this->get_test_mode() ) {
449 $client_id = $this->gateway->get_option( "test_client_id" );
450 $secret_key = $this->gateway->get_option( "test_secret_key" );
451 $subscription_key = $this->gateway->get_option( "test_subscription_key" );
452 $merchant_serial_number = $this->gateway->get_option( "test_merchant_serial_number" );
453 }
454
455 if ( ! $subscription_key || ! $secret_key || ! $client_id ) {
456 throw new WC_Vipps_Recurring_Config_Exception( __( 'Your Vipps/MobilePay Recurring Payments gateway is not correctly configured.', 'woo-vipps' ) );
457 }
458
459 $system_plugin_version = WC_VIPPS_RECURRING_VERSION;
460 $checkout_enabled = get_option( WC_Vipps_Recurring_Helper::OPTION_CHECKOUT_ENABLED, false );
461
462 if ( $checkout_enabled ) {
463 $system_plugin_version = $system_plugin_version . '/checkout';
464 }
465
466 $headers = array_merge( [
467 'client_id' => $client_id,
468 'client_secret' => $secret_key,
469 'Ocp-Apim-Subscription-Key' => $subscription_key,
470 'Merchant-Serial-Number' => $merchant_serial_number,
471 'Vipps-System-Name' => 'woocommerce',
472 'Vipps-System-Version' => get_bloginfo( 'version' ) . '/' . ( defined( 'WC_VERSION' ) ? WC_VERSION : '0.0.0' ),
473 'Vipps-System-Plugin-Name' => 'woo-vipps-recurring',
474 'Vipps-System-Plugin-Version' => $system_plugin_version
475 ], $headers );
476
477 if ( $encoding === 'url' || $method === 'GET' ) {
478 $data_encoded = http_build_query( $data );
479 } else {
480 $data_encoded = json_encode( $data );
481 }
482
483 $data_len = strlen( $data_encoded );
484 $headers['Connection'] = 'close';
485
486 if ( $method !== 'GET' ) {
487 $headers['Content-length'] = $data_len;
488 $headers['Content-type'] = $encoding === 'url' ? 'application/x-www-form-urlencoded' : 'application/json';
489 }
490
491 $args = [];
492 $args['method'] = $method;
493 $args['headers'] = $headers;
494
495 if ( $method !== 'GET' ) {
496 $args['body'] = $data_encoded;
497 }
498
499 if ( $method === 'GET' && $data_encoded ) {
500 $url .= "?$data_encoded";
501 }
502
503 $response = wp_remote_request( $url, $args );
504
505 // throw WP error as a WC_Vipps_Recurring_Exception if response is not valid
506 $default_error = '';
507 if ( is_wp_error( $response ) ) {
508 $default_error = "500 " . $response->get_error_message();
509 }
510
511 // Parse the result, converting it to exceptions if necessary
512 return $this->handle_http_response( $response, $args['body'] ?? "<empty>", $endpoint, $default_error );
513 }
514
515 /**
516 * @param $response
517 * @param $request_body
518 * @param $endpoint
519 * @param $default_error
520 *
521 * @return mixed|string|null
522 * @throws WC_Vipps_Recurring_Exception
523 * @throws WC_Vipps_Recurring_Temporary_Exception
524 */
525 private function handle_http_response( $response, $request_body, $endpoint, $default_error ) {
526 // no response from Vipps
527 if ( ! $response ) {
528 $error_msg = __( 'No response from Vipps/MobilePay', 'woo-vipps' );
529 WC_Vipps_Recurring_Logger::log( sprintf( 'HTTP Response Temporary Error: %s with request body: %s', $error_msg, $request_body ) );
530
531 throw new WC_Vipps_Recurring_Temporary_Exception( $error_msg );
532 }
533
534 $status = (int) wp_remote_retrieve_response_code( $response );
535
536 $body = wp_remote_retrieve_body( $response );
537 if ( $body ) {
538 $body = json_decode( $body, true );
539 }
540
541 // As long as the status code is less than 300 and greater than 199 we can return the body
542 if ( $status < 300 && $status > 199 ) {
543 return $body;
544 }
545
546 // Rate limiting, temporary error
547 if ( $status === 429 ) {
548 $error_msg = __( "We hit Vipps/MobilePay's rate limit, we will retry later.", 'woo-vipps' );
549 throw new WC_Vipps_Recurring_Temporary_Exception( $error_msg );
550 }
551
552 // error handling
553 $error_msg = $default_error ?? '';
554 $is_idempotent_error = false;
555 $is_merchant_not_allowed_error = false;
556 $is_url_validation_error = false;
557
558 if ( $body ) {
559 if ( isset( $body['message'] ) ) {
560 $error_msg = $body['message'];
561 }
562
563 if ( isset( $body['error_description'] ) ) {
564 $error_msg = $body['error_description'];
565 }
566
567 if ( isset( $body['title'] ) ) {
568 $error_msg = $body['title'];
569
570 if ( isset( $body['detail'] ) ) {
571 $error_msg .= ' ' . $body['detail'];
572 }
573 }
574
575 $error_msg = trim( $error_msg );
576
577 /*
578 "type":"https://example.com/validation-error",
579 "title":"Your request parameters didn't validate.",
580 "detail":"The request body contains one or more errors",
581 "instance":"123e4567-e89b-12d3-a456-426655440000",
582 "status":"400",
583 "extraDetails":[
584 {
585 "name":"amount",
586 "reason":"Must be a positive integer larger than 100"
587 },
588 {
589 "name":"URL",
590 "reason":"Must use HTTPS and validate according to the API specification"
591 }
592 ]
593 */
594
595
596 // todo: implement new logic
597 /*
598 {
599 "type": "about:blank",
600 "title": "Not Found",
601 "status": 404,
602 "detail": "Agreement not found.",
603 "instance": "/vipps-recurring-merchant-api/v3/agreements/agr_GqnvsH0",
604 "contextId": "32900600-1dd4-47dc-9a02-28c9e6491c65"
605 }
606 */
607
608 // if ( isset( $body['message'] ) ) {
609 // $error_msg = $body['message'];
610 // } elseif ( isset( $body['error'] ) ) {
611 // // access token
612 // $error_msg = $body['error'];
613 // } elseif ( is_array( $body ) ) {
614 // $error_msg = '';
615 // foreach ( $body as $entry ) {
616 // if ( isset( $entry['code'] ) ) {
617 // $error_msg .= $entry['field'] . ': ' . $entry['message'] . "\n";
618 //
619 // if ( $entry['code'] === 'idempotentkey.hash.mismatch' ) {
620 // $is_idempotent_error = true;
621 // }
622 //
623 // if ( $entry['code'] === 'merchant.not.allowed.for.recurring.operation' ) {
624 // $is_merchant_not_allowed_error = true;
625 // }
626 //
627 // if ( $entry['code'] === 'merchantagreementurl.apacheurlvalidation' ) {
628 // $is_url_validation_error = true;
629 // }
630 // }
631 // }
632 // } else {
633 // $error_msg = $body;
634 // }
635 }
636
637 $localized_msg = $error_msg;
638 if ( $is_merchant_not_allowed_error ) {
639 /* translators: Link to a GitHub readme about the error */
640 $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' );
641 }
642
643 if ( $is_url_validation_error ) {
644 $localized_msg = __( 'Your WordPress URL is not passing Merchant Agreement URL validation. Is your website publicly accessible?', 'woo-vipps' );
645 }
646
647 if ( is_array( $request_body ) ) {
648 $request_body = json_encode( $request_body );
649 }
650
651 if ( is_array( $body ) ) {
652 $body = json_encode( $body );
653 }
654
655 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 ) );
656
657 $exception = new WC_Vipps_Recurring_Exception( $error_msg, $localized_msg );
658 $exception->response_code = $status;
659 $exception->is_idempotent_error = $is_idempotent_error;
660
661 throw $exception;
662 }
663 }
664