PluginProbe
Pay with Vipps and MobilePay for WooCommerce / 6.1.7
Pay with Vipps and MobilePay for WooCommerce v6.1.7
6.3.1 6.2.6 6.3.0 6.2.5 6.2.4 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 All 190 releases
woo-vipps / recurring / includes / wc-vipps-recurring-checkout.php

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

1,189 lines 43.1 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 class WC_Vipps_Recurring_Checkout {
6 private static ?WC_Vipps_Recurring_Checkout $instance = null;
7
8 private ?WC_Gateway_Vipps_Recurring $gateway = null;
9
10 /**
11 * Returns the *Singleton* instance of this class.
12 *
13 * @return WC_Vipps_Recurring_Checkout The *Singleton* instance.
14 */
15 public static function get_instance(): WC_Vipps_Recurring_Checkout {
16 if ( null === self::$instance ) {
17 self::$instance = new self();
18 }
19
20 return self::$instance;
21 }
22
23 public static function register_hooks(): void {
24 $instance = WC_Vipps_Recurring_Checkout::get_instance();
25 add_action( 'init', [ $instance, 'init' ] );
26 // Higher priority than the single payments Vipps plugin
27 add_filter( 'woocommerce_get_checkout_page_id', [ $instance, 'woocommerce_get_checkout_page_id' ], 20 );
28 add_action( 'template_redirect', [ $instance, 'template_redirect' ] );
29
30 if ( is_admin() ) {
31 add_action( 'admin_init', [ $instance, 'admin_init' ] );
32 }
33 }
34
35 public function gateway(): ?WC_Gateway_Vipps_Recurring {
36 if ( $this->gateway ) {
37 return $this->gateway;
38 }
39
40 $this->gateway = WC_Vipps_Recurring::get_instance()->gateway();
41
42 return $this->gateway;
43 }
44
45 public function maybe_load_cart(): void {
46 if ( version_compare( WC_VERSION, '3.6.0', '>=' ) && WC()->is_rest_api_request() ) {
47 if ( empty( $_SERVER['REQUEST_URI'] ) ) {
48 return;
49 }
50
51 $rest_prefix = WC_Vipps_Recurring_Checkout_Rest_Api::$api_namespace;
52 $req_uri = esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) );
53
54 $is_my_endpoint = ( false !== strpos( $req_uri, $rest_prefix ) );
55
56 if ( ! $is_my_endpoint ) {
57 return;
58 }
59
60 require_once WC_ABSPATH . 'includes/wc-cart-functions.php';
61 require_once WC_ABSPATH . 'includes/wc-notice-functions.php';
62
63 if ( null === WC()->session ) {
64 $session_class = apply_filters( 'woocommerce_session_handler', 'WC_Session_Handler' ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
65
66 // Prefix session class with global namespace if not already namespaced
67 if ( false === strpos( $session_class, '\\' ) ) {
68 $session_class = '\\' . $session_class;
69 }
70
71 WC()->session = new $session_class();
72 WC()->session->init();
73 }
74
75 /**
76 * For logged in customers, pull data from their account rather than the
77 * session which may contain incomplete data.
78 */
79 if ( is_null( WC()->customer ) ) {
80 if ( is_user_logged_in() ) {
81 WC()->customer = new WC_Customer( get_current_user_id() );
82 } else {
83 WC()->customer = new WC_Customer( get_current_user_id(), true );
84 }
85
86 // Customer should be saved during shutdown.
87 add_action( 'shutdown', array( WC()->customer, 'save' ), 10 );
88 }
89
90 // Load Cart.
91 if ( null === WC()->cart ) {
92 WC()->cart = new WC_Cart();
93 }
94 }
95 }
96
97 public function init(): void {
98 require_once __DIR__ . '/wc-vipps-recurring-checkout-rest-api.php';
99 WC_Vipps_Recurring_Checkout_Rest_Api::get_instance();
100
101 add_action( 'wp_loaded', [ $this, 'maybe_load_cart' ], 5 );
102
103 // Prevent previews and prefetches of the Vipps Checkout page starting and creating orders
104 add_action( 'wp_head', [ $this, 'wp_head' ] );
105
106 // The Vipps MobilePay Checkout feature which overrides the normal checkout process uses a shortcode
107 add_shortcode( 'vipps_recurring_checkout', [ $this, 'shortcode' ] );
108
109 add_action( 'wc_vipps_recurring_before_cron_check_order_status', [ $this, 'check_order_status' ] );
110 add_action( 'wc_vipps_recurring_before_rest_api_check_order_status', [ $this, 'check_order_status' ] );
111
112 // For Checkout, we need to know any time and as soon as the cart changes, so fold all the events into a single one
113 add_action( 'woocommerce_add_to_cart', function () {
114 do_action( 'vipps_recurring_cart_changed', 'woocommerce_add_to_cart' );
115 }, 10, 0 );
116
117 // Cart coupon applied
118 add_action( 'woocommerce_applied_coupon', function () {
119 do_action( 'vipps_recurring_cart_changed', 'woocommerce_applied_coupon' );
120 }, 10, 0 );
121
122 // Cart emptied
123 add_action( 'woocommerce_cart_emptied', function () {
124 do_action( 'vipps_recurring_cart_changed', 'woocommerce_cart_emptied' );
125 }, 10, 0 );
126
127 // After updating quantities
128 add_action( 'woocommerce_after_cart_item_quantity_update', function () {
129 do_action( 'vipps_recurring_cart_changed', 'woocommerce_after_cart_item_quantity_update' );
130 }, 10, 0 );
131
132 // Blocks and ajax
133 add_action( 'woocommerce_cart_item_removed', function () {
134 do_action( 'vipps_recurring_cart_changed', 'woocommerce_cart_item_removed' );
135 }, 10, 0 );
136
137 // Restore deleted entry
138 add_action( 'woocommerce_cart_item_restored', function () {
139 do_action( 'vipps_recurring_cart_changed', 'woocommerce_cart_item_restored' );
140 }, 10, 0 );
141
142 // Normal cart form update
143 add_filter( 'woocommerce_update_cart_action_cart_updated', function ( $updated ) {
144 do_action( 'vipps_recurring_cart_changed', 'woocommerce_update_cart_action_cart_updated' );
145
146 return $updated;
147 } );
148
149 // Then handle the actual cart change
150 add_action( 'vipps_recurring_cart_changed', [ $this, 'cart_changed' ] );
151
152 add_action( 'wc_vipps_recurring_checkout_callback', [ $this, 'handle_callback' ], 10, 2 );
153
154 // Handle cancelled orders
155 add_action( 'wc_vipps_recurring_check_charge_status_no_agreement', [ $this, 'maybe_cancel_initial_order' ] );
156
157 add_filter( 'wcs_user_has_subscription', [ $this, 'user_has_subscription' ], 10, 4 );
158 }
159
160 public function admin_init(): void {
161 // Checkout page
162 add_filter( 'woocommerce_settings_pages', array( $this, 'woocommerce_settings_pages' ) );
163 }
164
165 /**
166 * @throws WC_Vipps_Recurring_Exception
167 * @throws WC_Vipps_Recurring_Temporary_Exception
168 * @throws WC_Vipps_Recurring_Config_Exception
169 */
170 public function maybe_create_session(): array {
171 $redirect_url = null;
172 $token = null;
173 $url = null;
174
175 $session = WC_Vipps_Recurring_Checkout::get_instance()->current_pending_session();
176
177 if ( isset( $session['redirect_url'] ) ) {
178 $redirect_url = $session['redirect_url'];
179 }
180
181 if ( isset( $session['session']['token'] ) ) {
182 $token = $session['session']['token'];
183 $src = $session['session']['checkoutFrontendUrl'];
184 $url = $src;
185 }
186
187 if ( $url ) {
188 $pending_order_id = WC_Vipps_Recurring_Helper::get_checkout_pending_order_id();
189
190 return [
191 'success' => true,
192 'src' => $url,
193 'redirect_url' => $redirect_url,
194 'token' => $token,
195 'order_id' => $pending_order_id
196 ];
197 }
198
199 $session = null;
200
201 try {
202 $partial_order_id = WC_Gateway_Vipps_Recurring::get_instance()->create_partial_order( true );
203
204 $order = wc_get_order( $partial_order_id );
205 $auth_token = WC_Gateway_Vipps_Recurring::get_instance()->api->generate_idempotency_key();
206
207 $order->update_meta_data( WC_Vipps_Recurring_Helper::META_ORDER_EXPRESS_AUTH_TOKEN, $auth_token );
208 $order->save();
209
210 WC()->session->set( WC_Vipps_Recurring_Helper::SESSION_CHECKOUT_PENDING_ORDER_ID, $partial_order_id );
211 WC()->session->set( WC_Vipps_Recurring_Helper::SESSION_ORDER_EXPRESS_AUTH_TOKEN, $auth_token );
212
213 do_action( 'wc_vipps_recurring_checkout_order_created', $order );
214 } catch ( Exception $exception ) {
215 return [
216 'success' => false,
217 'msg' => $exception->getMessage(),
218 'src' => null,
219 'redirect_url' => null,
220 'order_id' => 0
221 ];
222 }
223
224 $order = wc_get_order( $partial_order_id );
225
226 $session_orders = WC()->session->get( WC_Vipps_Recurring_Helper::SESSION_ORDERS );
227 if ( ! $session_orders ) {
228 $session_orders = [];
229 }
230
231 $session_orders[ $partial_order_id ] = 1;
232 WC()->session->set( WC_Vipps_Recurring_Helper::SESSION_PENDING_ORDER_ID, $partial_order_id );
233 WC()->session->set( WC_Vipps_Recurring_Helper::SESSION_ORDERS, $session_orders );
234
235 $customer_id = get_current_user_id();
236 if ( $customer_id ) {
237 $customer = new WC_Customer( $customer_id );
238 } else {
239 $customer = WC()->customer;
240 }
241
242 if ( $customer ) {
243 $customer_info['email'] = $customer->get_billing_email();
244 $customer_info['firstName'] = $customer->get_billing_first_name();
245 $customer_info['lastName'] = $customer->get_billing_last_name();
246 $customer_info['streetAddress'] = $customer->get_billing_address_1();
247 $address2 = trim( $customer->get_billing_address_2() );
248
249 if ( ! empty( $address2 ) ) {
250 $customer_info['streetAddress'] = $customer_info['streetAddress'] . ", " . $address2;
251 }
252 $customer_info['city'] = $customer->get_billing_city();
253 $customer_info['postalCode'] = $customer->get_billing_postcode();
254 $customer_info['country'] = $customer->get_billing_country();
255
256 // Currently Vipps requires all phone numbers to have area codes and NO +. We can't guarantee that at all, but try for Norway
257 $normalized_phone_number = WC_Vipps_Recurring_Helper::normalize_phone_number( $customer->get_billing_phone(), $customer_info['country'] );
258 if ( $normalized_phone_number ) {
259 $customer_info['phoneNumber'] = $normalized_phone_number;
260 }
261 }
262
263 $keys = [ 'firstName', 'lastName', 'streetAddress', 'postalCode', 'country', 'phoneNumber' ];
264 foreach ( $keys as $k ) {
265 if ( empty( $customer_info[ $k ] ) ) {
266 $customer_info = [];
267 break;
268 }
269 }
270 $customer_info = apply_filters( 'wc_vipps_recurring_customer_info', $customer_info, $order );
271
272 // todo: throw an error if we try to purchase a product with a location based shipping method?
273 try {
274 $checkout = WC_Vipps_Recurring_Checkout::get_instance();
275 $gateway = $checkout->gateway();
276
277 // hack - fake "Anonymous Vipps/MobilePay User"
278 $fake_user = false;
279 if ( ! $order->get_customer_id( 'edit' ) ) {
280 $fake_user = true;
281
282 $order->set_customer_id( $this->gateway()->create_or_get_anonymous_system_customer()->get_id() );
283 $order->save();
284 }
285
286 // Check if we already have a subscription on this order, otherwise create one
287 if ( wcs_order_contains_subscription( $order, 'any' ) ) {
288 $subscriptions = wcs_get_subscriptions_for_order( $order );
289 } else {
290 $subscriptions = $gateway->create_partial_subscriptions_from_order( $order );
291 }
292
293 // reset hack
294 if ( $fake_user ) {
295 $order->set_customer_id( 0 );
296 $order->save();
297 }
298
299 $subscription = array_pop( $subscriptions );
300
301 // Remove this action to avoid making an unnecessary API request
302 remove_action( 'woocommerce_order_after_calculate_totals', [
303 $this->gateway(),
304 'update_agreement_price_in_app'
305 ] );
306
307 $subscription->calculate_totals();
308 $subscription->save();
309
310 $agreement = $gateway->create_vipps_agreement_from_order( $order, $subscription );
311
312 $checkout_subscription = ( new WC_Vipps_Checkout_Session_Subscription() )
313 ->set_amount(
314 ( new WC_Vipps_Checkout_Session_Amount() )
315 ->set_value( $agreement->pricing->amount )
316 ->set_currency( $agreement->pricing->currency )
317 )
318 ->set_product_name( $agreement->product_name )
319 ->set_interval( $agreement->interval )
320 ->set_merchant_agreement_url( $agreement->merchant_agreement_url );
321
322 if ( $agreement->campaign ) {
323 $checkout_subscription = $checkout_subscription->set_campaign( $agreement->campaign );
324 }
325
326 if ( $agreement->product_description ) {
327 $checkout_subscription = $checkout_subscription->set_product_description( $agreement->product_description );
328 }
329
330 $configuration = ( new WC_Vipps_Checkout_Session_Configuration() )
331 ->set_user_flow( WC_Vipps_Checkout_Session_Configuration::USER_FLOW_WEB_REDIRECT )
332 ->set_customer_interaction( WC_Vipps_Checkout_Session_Configuration::CUSTOMER_INTERACTION_NOT_PRESENT )
333 ->set_elements( WC_Vipps_Checkout_Session_Configuration::ELEMENTS_FULL )
334 ->set_require_user_info( empty( $customer->email ) )
335 ->set_show_order_summary( true );
336
337 $countries = array_keys( ( new WC_Countries() )->get_allowed_countries() );
338 $allowed_countries = apply_filters( 'woo_vipps_recurring_checkout_countries', $countries, $order->get_id() );
339 if ( $allowed_countries ) {
340 $configuration->set_countries( [ 'supported' => $allowed_countries ] );
341 }
342
343 $customer = new WC_Vipps_Checkout_Session_Customer( $customer_info );
344
345 // Create a checkout session dto
346 $checkout_session = ( new WC_Vipps_Checkout_Session() )
347 ->set_type( WC_Vipps_Checkout_Session::TYPE_SUBSCRIPTION )
348 ->set_subscription( $checkout_subscription )
349 ->set_merchant_info(
350 ( new WC_Vipps_Checkout_Session_Merchant_Info() )
351 ->set_callback_url( $gateway->webhook_callback_url() )
352 ->set_return_url( $agreement->merchant_redirect_url )
353 ->set_callback_authorization_token( $auth_token )
354 )
355 ->set_prefill_customer( $customer )
356 ->set_configuration( $configuration );
357
358 if ( $agreement->initial_charge ) {
359 $reference = $agreement->initial_charge->order_id;
360
361 $checkout_transaction = ( new WC_Vipps_Checkout_Session_Transaction() )
362 ->set_reference( $reference )
363 ->set_amount(
364 ( new WC_Vipps_Checkout_Session_Amount() )
365 ->set_value( $agreement->initial_charge->amount )
366 ->set_currency( $agreement->pricing->currency )
367 )
368 ->set_order_summary( $checkout->make_order_summary( $order ) );
369
370 if ( $agreement->initial_charge->description ) {
371 $checkout_transaction = $checkout_transaction->set_payment_description( $agreement->initial_charge->description );
372 }
373
374 $checkout_session = $checkout_session->set_transaction( $checkout_transaction );
375
376 WC_Vipps_Recurring_Helper::update_meta_data( $order, WC_Vipps_Recurring_Helper::META_CHARGE_ID, $reference );
377 }
378
379 $checkout_session = apply_filters( 'wc_vipps_recurring_checkout_session', $checkout_session, $order );
380
381 $session = WC_Gateway_Vipps_Recurring::get_instance()->api->checkout_initiate( $checkout_session );
382
383 $order = wc_get_order( $partial_order_id );
384 WC_Vipps_Recurring_Helper::update_meta_data( $order, WC_Vipps_Recurring_Helper::META_CHARGE_PENDING, true );
385 WC_Vipps_Recurring_Helper::update_meta_data( $order, WC_Vipps_Recurring_Helper::META_ORDER_INITIAL, true );
386 WC_Vipps_Recurring_Helper::update_meta_data( $order, WC_Vipps_Recurring_Helper::META_ORDER_CHECKOUT_SESSION, $session->to_array() );
387
388 $session_poll = WC_Gateway_Vipps_Recurring::get_instance()->api->checkout_poll( $session->polling_url );
389 WC_Vipps_Recurring_Helper::update_meta_data( $order, WC_Vipps_Recurring_Helper::META_ORDER_CHECKOUT_SESSION_ID, $session_poll['sessionId'] );
390
391 $order->add_order_note( __( 'Vipps/MobilePay recurring checkout payment initiated', 'woo-vipps' ) );
392 $order->add_order_note( __( 'Customer passed to Vipps/MobilePay checkout', 'woo-vipps' ) );
393 $order->save();
394
395 $token = $session->token;
396 $src = $session->checkout_frontend_url;
397 $url = $src;
398 } catch ( Exception $e ) {
399 WC_Vipps_Recurring_Logger::log( sprintf( "Could not initiate Vipps/MobilePay checkout session: %s", $e->getMessage() ) );
400
401 return [
402 'success' => false,
403 'msg' => $e->getMessage(),
404 'src' => null,
405 'redirect_url' => null,
406 'order_id' => $partial_order_id
407 ];
408 }
409
410 if ( $url || $redirect_url ) {
411 return [
412 'success' => true,
413 'msg' => 'session started',
414 'src' => $url,
415 'redirect_url' => $redirect_url,
416 'token' => $token,
417 'order_id' => $partial_order_id
418 ];
419 }
420
421 return [
422 'success' => false,
423 'msg' => __( 'Could not start Vipps/MobilePay checkout session', 'woo-vipps' ),
424 'src' => $url,
425 'redirect_url' => $redirect_url,
426 'order_id' => $partial_order_id
427 ];
428 }
429
430 public function maybe_login_checkout_user( int $order_id, ?string $key = null ): void {
431 if ( ! $key ) {
432 return;
433 }
434
435 $order = wc_get_order( $order_id );
436 if ( ! $order ) {
437 return;
438 }
439
440 $order_key_db = $order->get_order_key( 'code' );
441 if ( $order_key_db !== $key ) {
442 return;
443 }
444
445 // Attempt to log the user in
446 $session_auth_token = WC()->session->get( WC_Vipps_Recurring_Helper::SESSION_ORDER_EXPRESS_AUTH_TOKEN );
447 $order_auth_token = WC_Vipps_Recurring_Helper::get_meta( $order, WC_Vipps_Recurring_Helper::META_ORDER_EXPRESS_AUTH_TOKEN );
448
449 if ( $order_auth_token !== $session_auth_token ) {
450 return;
451 }
452
453 $user = $order->get_user();
454
455 wc_set_customer_auth_cookie( $user->ID );
456 WC()->session->set( WC_Vipps_Recurring_Helper::SESSION_ORDER_EXPRESS_AUTH_TOKEN, null );
457 }
458
459 public function cart_changed( string $source ): void {
460 $pending_order_id = is_a( WC()->session, 'WC_Session' ) ? WC()->session->get( WC_Vipps_Recurring_Helper::SESSION_CHECKOUT_PENDING_ORDER_ID ) : false;
461 $order = $pending_order_id ? wc_get_order( $pending_order_id ) : null;
462
463 if ( ! $order ) {
464 return;
465 }
466
467 WC_Vipps_Recurring_Logger::log( sprintf( "Checkout cart changed while session %d in progress, attempting to cancel. Source: %s", $order->get_id(), $source ) );
468 $this->abandon_checkout_order( $order );
469 }
470
471 /**
472 * @throws WC_Vipps_Recurring_Config_Exception
473 * @throws WC_Data_Exception
474 * @throws WC_Vipps_Recurring_Exception
475 * @throws WC_Vipps_Recurring_Temporary_Exception
476 */
477 public function check_order_status( $order_id ): void {
478 $lock_name = "vipps_recurring_checkout_check_order_status_$order_id";
479 $lock = get_transient( $lock_name );
480
481 if ( $lock ) {
482 return;
483 }
484
485 set_transient( $lock_name, uniqid( '', true ), 5 );
486
487 $order = wc_get_order( $order_id );
488
489 if ( ! WC_Vipps_Recurring_Helper::get_meta( $order, WC_Vipps_Recurring_Helper::META_ORDER_IS_CHECKOUT ) ) {
490 return;
491 }
492
493 $session = WC_Vipps_Recurring_Helper::get_meta( $order, WC_Vipps_Recurring_Helper::META_ORDER_CHECKOUT_SESSION );
494 if ( ! is_array( $session ) ) {
495 return;
496 }
497
498 $session = $this->gateway()->api->checkout_poll( $session['pollingUrl'] );
499
500 $this->handle_payment( $order, $session );
501 }
502
503 public function woocommerce_settings_pages( $settings ) {
504 $checkout_enabled = get_option( WC_Vipps_Recurring_Helper::OPTION_CHECKOUT_ENABLED, false );
505 if ( ! $checkout_enabled ) {
506 return $settings;
507 }
508
509 // Find out where the end of the section advanced_page_options is
510 $i = 0;
511 $count = count( $settings );
512
513 for ( ; $i < $count; $i ++ ) {
514 if ( $settings[ $i ]['type'] === 'sectionend' && $settings[ $i ]['id'] === 'advanced_page_options' ) {
515 break;
516 }
517 }
518
519 if ( $i < $count ) {
520 array_splice( $settings, $i, 0, [
521 [
522 'title' => __( 'Vipps/MobilePay Recurring Checkout Page', 'woo-vipps' ),
523 'desc' => __( 'This page is used for the alternative Vipps/MobilePay checkout page, which you can choose to use instead of the normal WooCommerce checkout page. ', 'woo-vipps' ) . sprintf( __( 'Page contents: [%1$s]', 'woocommerce' ), 'vipps_recurring_checkout' ),
524 'id' => 'woocommerce_vipps_recurring_checkout_page_id',
525 'type' => 'single_select_page_with_search',
526 'default' => '',
527 'class' => 'wc-page-search',
528 'css' => 'min-width:300px;',
529 'args' => [
530 'exclude' =>
531 [
532 wc_get_page_id( 'myaccount' ),
533 ],
534 ],
535 'desc_tip' => true,
536 'autoload' => false,
537 ]
538 ] );
539 }
540
541 return $settings;
542 }
543
544 public function woocommerce_get_checkout_page_id( $id ): int {
545 $checkout_enabled = get_option( WC_Vipps_Recurring_Helper::OPTION_CHECKOUT_ENABLED, false );
546
547 if ( ! $checkout_enabled ) {
548 return $id;
549 }
550
551 $checkout_id = $this->gateway()->checkout_is_available();
552 if ( $checkout_id ) {
553 return $checkout_id;
554 }
555
556 return $id;
557 }
558
559 public function template_redirect(): void {
560 global $post;
561
562 if ( $post && is_page() && has_shortcode( $post->post_content, 'vipps_recurring_checkout' ) ) {
563 $this->register_scripts();
564
565 add_filter( 'woocommerce_is_checkout', '__return_true' );
566
567 add_filter( 'body_class', function ( $classes ) {
568 $classes[] = 'vipps-recurring-checkout';
569 $classes[] = 'woocommerce-checkout'; // Required by Pixel Your Site
570
571 return apply_filters( 'wc_vipps_recurring_checkout_body_class', $classes );
572 } );
573
574 // Suppress the title for this page
575 $post_to_hide_title_for = $post->ID;
576 add_filter( 'the_title', function ( $title, $postid = 0 ) use ( $post_to_hide_title_for ) {
577 if ( ! is_admin() && $postid == $post_to_hide_title_for && is_singular() && in_the_loop() ) {
578 $title = "";
579 }
580
581 return $title;
582 }, 10, 2 );
583
584 wc_nocache_headers();
585 }
586 }
587
588 public function wp_head(): void {
589 // If we have a Vipps MobilePay Checkout page, stop iOS from giving previews of it that
590 // starts the session - iOS should use the visibility API of the browser for this, but it doesn't as of 2021-11-11
591 $checkout_id = wc_get_page_id( 'vipps_recurring_checkout' );
592 if ( $checkout_id ) {
593 $url = get_permalink( $checkout_id );
594 echo "<style> a[href=\"$url\"] { -webkit-touch-callout: none; } </style>\n";
595 }
596 }
597
598 public function register_scripts(): void {
599 $sdk_url = 'https://checkout.vipps.no/vippsCheckoutSDK.js';
600 wp_register_script( 'woo-vipps-recurring-sdk', $sdk_url );
601
602 // Register our React component
603 wp_enqueue_style(
604 'woo-vipps-recurring-checkout',
605
606 WC_VIPPS_RECURRING_PLUGIN_URL . '/assets/build/checkout.css', [],
607 filemtime( WC_VIPPS_RECURRING_PLUGIN_PATH . '/assets/build/checkout.css' )
608 );
609
610 $asset = require WC_VIPPS_RECURRING_PLUGIN_PATH . '/assets/build/checkout.asset.php';
611
612 wp_enqueue_script(
613 'woo-vipps-recurring-checkout',
614 WC_VIPPS_RECURRING_PLUGIN_URL . '/assets/build/checkout.js',
615 array_merge( $asset['dependencies'], [ 'woo-vipps-recurring', 'woo-vipps-recurring-sdk' ] ),
616 filemtime( WC_VIPPS_RECURRING_PLUGIN_PATH . '/assets/build/checkout.js' ),
617 true
618 );
619 }
620
621 /**
622 * @throws WC_Vipps_Recurring_Exception
623 * @throws WC_Vipps_Recurring_Temporary_Exception
624 * @throws WC_Vipps_Recurring_Config_Exception
625 */
626 public function shortcode() {
627 global $wp;
628
629 if ( is_admin() || wp_doing_ajax() || ( defined( 'REST_REQUEST' ) && REST_REQUEST ) ) {
630 return false;
631 }
632
633 // No point in expanding this unless we are actually doing the checkout. IOK 2021-09-03
634 wc_maybe_define_constant( 'WOOCOMMERCE_CHECKOUT', true );
635 add_filter( 'wc_vipps_recurring_is_vipps_checkout', '__return_true' );
636
637 if ( is_wc_endpoint_url( 'order-received' ) ) {
638 $order_id = absint( $wp->query_vars['order-received'] );
639 $this->maybe_login_checkout_user( $order_id, $_GET['key'] ?? null );
640 }
641
642 // Defer to the normal code for endpoints IOK 2022-12-09
643 if ( is_wc_endpoint_url( 'order-pay' ) || is_wc_endpoint_url( 'order-received' ) ) {
644 return do_shortcode( "[woocommerce_checkout]" );
645 }
646
647 if ( ! WC()->cart || WC()->cart->is_empty() ) {
648 $this->abandon_checkout_order( false );
649 ob_start();
650 wc_get_template( 'cart/cart-empty.php' );
651
652 return ob_get_clean();
653 }
654
655 // Previously registered, now enqueue this script which should then appear in the footer.
656 wp_enqueue_script( 'woo-vipps-recurring-checkout' );
657
658 do_action( 'vipps_recurring_checkout_before_get_session' );
659
660 // Localize script with variables we need to reveal to the frontend
661 $data = $this->current_pending_session();
662 if ( empty( $data['session'] ) ) {
663 $data['session'] = $this->maybe_create_session();
664 }
665
666 $data['language'] = Vipps::instance()->get_customer_language();
667
668 wp_add_inline_script( 'woo-vipps-recurring-checkout', 'window.VippsRecurringCheckout = ' . wp_json_encode( $data ), 'before' );
669
670 return '<div id="vipps-mobilepay-recurring-checkout"></div>';
671 }
672
673 public function make_order_summary( $order ): WC_Vipps_Checkout_Session_Transaction_Order_Summary {
674 $order_lines = [];
675
676 $bottom_line = ( new WC_Vipps_Checkout_Session_Transaction_Order_Summary_Bottom_Line() )
677 ->set_currency( $order->get_currency() )
678 ->set_gift_card_amount( apply_filters( 'wc_vipps_recurring_order_gift_card_amount', 0, $order ) * 100 )
679 ->set_tip_amount( apply_filters( 'wc_vipps_recurring_order_tip_amount', 0, $order ) * 100 )
680 ->set_terminal_id( apply_filters( 'wc_vipps_recurring_order_terminal_id', 'woocommerce', $order ) )
681 ->set_receipt_number( strval( WC_Vipps_Recurring_Helper::get_id( $order ) ) );
682
683 foreach ( $order->get_items() as $order_item ) {
684 $order_line = [];
685 $product_id = $order_item->get_product_id(); // sku can be tricky
686 $total_no_tax = $order_item->get_total();
687 $tax = $order_item->get_total_tax();
688 $total = $tax + $total_no_tax;
689 $subtotal_no_tax = $order_item->get_subtotal();
690 $subtotal_tax = $order_item->get_subtotal_tax();
691 $subtotal = $subtotal_no_tax + $subtotal_tax;
692 $quantity = $order_item->get_quantity();
693 $unit_price = $subtotal / $quantity;
694
695 // Must do this to avoid rounding errors, since we get floats instead of money here :(
696 $discount = round( 100 * $subtotal ) - round( 100 * $total );
697 if ( $discount < 0 ) {
698 $discount = 0;
699 }
700
701 $product = wc_get_product( $product_id );
702 $url = home_url( "/" );
703 if ( $product ) {
704 $url = get_permalink( $product_id );
705 }
706
707 if ( $subtotal_no_tax == 0 ) {
708 $tax_percentage = 0;
709 } else {
710 $tax_percentage = ( ( $subtotal - $subtotal_no_tax ) / $subtotal_no_tax ) * 100;
711 }
712 $tax_percentage = abs( round( $tax_percentage ) );
713
714 $unit_info = [];
715 $order_line['name'] = $order_item->get_name();
716 $order_line['id'] = strval( $product_id );
717 $order_line['totalAmount'] = round( $total * 100 );
718 $order_line['totalAmountExcludingTax'] = round( $total_no_tax * 100 );
719 $order_line['totalTaxAmount'] = round( $tax * 100 );
720 $order_line['taxRate'] = round( $tax_percentage * 100 );
721
722 $unit_info['unitPrice'] = round( $unit_price * 100 );
723 $unit_info['quantity'] = strval( $quantity );
724 $unit_info['quantityUnit'] = 'PCS';
725 $order_line['unitInfo'] = $unit_info;
726 $order_line['discount'] = $discount;
727 $order_line['productUrl'] = $url;
728 $order_line['isShipping'] = false;
729
730 $order_lines[] = $order_line;
731 }
732
733 foreach ( $order->get_items( 'fee' ) as $order_item ) {
734 $order_line = [];
735 $total_no_tax = $order_item->get_total();
736 $tax = $order_item->get_total_tax();
737 $total = $tax + $total_no_tax;
738 $tax_percentage = ( ( $total - $total_no_tax ) / $total_no_tax ) * 100;
739 $tax_percentage = abs( round( $tax_percentage ) );
740 $order_line['name'] = $order_item->get_name();
741 $order_line['id'] = substr( sanitize_title( $order_line['name'] ), 0, 254 );
742 $order_line['totalAmount'] = round( $total * 100 );
743 $order_line['totalAmountExcludingTax'] = round( $total_no_tax * 100 );
744 $order_line['totalTaxAmount'] = round( $tax * 100 );
745 $order_line['discount'] = 0;
746 $order_line['taxRate'] = round( $tax_percentage * 100 );
747
748 $order_lines[] = $order_line;
749 }
750
751 // Handle shipping
752 foreach ( $order->get_items( 'shipping' ) as $order_item ) {
753 $order_line = [];
754 $order_line['name'] = $order_item->get_name();
755 $order_line['id'] = strval( $order_item->get_method_id() );
756 if ( method_exists( $order_item, 'get_instance_id' ) ) {
757 $order_line['id'] .= ":" . $order_item->get_instance_id();
758 }
759
760 $total_no_tax = $order_item->get_total();
761 $tax = $order_item->get_total_tax();
762 $total = $tax + $total_no_tax;
763 $subtotal_no_tax = $total_no_tax;
764 $subtotal_tax = $tax;
765 $subtotal = $subtotal_no_tax + $subtotal_tax;
766
767 if ( $subtotal_no_tax == 0 ) {
768 $tax_percentage = 0;
769 } else {
770 $tax_percentage = ( ( $subtotal - $subtotal_no_tax ) / $subtotal_no_tax ) * 100;
771 }
772 $tax_percentage = abs( round( $tax_percentage ) );
773
774 $order_line['totalAmount'] = round( $total * 100 );
775 $order_line['totalAmountExcludingTax'] = round( $total_no_tax * 100 );
776 $order_line['totalTaxAmount'] = round( $tax * 100 );
777 $order_line['taxRate'] = round( $tax_percentage * 100 );
778
779 $unit_info = [];
780 $unit_info['unitPrice'] = round( $total * 100 );
781 $unit_info['quantity'] = strval( 1 );
782 $unit_info['quantityUnit'] = 'PCS';
783
784 $order_line['unitInfo'] = $unit_info;
785 $discount = 0;
786 $order_line['discount'] = $discount;
787 $order_line['isShipping'] = true;
788
789 $order_lines[] = $order_line;
790 }
791
792 return ( new WC_Vipps_Checkout_Session_Transaction_Order_Summary() )
793 ->set_order_lines( $order_lines )
794 ->set_order_bottom_line( $bottom_line );
795 }
796
797 /**
798 * @throws WC_Vipps_Recurring_Config_Exception
799 * @throws WC_Vipps_Recurring_Exception
800 * @throws WC_Vipps_Recurring_Temporary_Exception
801 */
802 public function current_pending_session(): array {
803 // If this is set, this is a currently pending order which is maybe still valid
804 $pending_order_id = WC_Vipps_Recurring_Helper::get_checkout_pending_order_id();
805 $order = $pending_order_id ? wc_get_order( $pending_order_id ) : null;
806
807 # If we do have an order, we need to check if it is 'pending', and if not, we have to check its payment status
808 $payment_status = null;
809 $redirect = null;
810
811 if ( $order ) {
812 if ( $order->get_status() === 'pending' ) {
813 $payment_status = 'INITIATED'; // Just assume this for now
814 } else {
815 $payment_status = $this->gateway()->check_charge_status( $pending_order_id, true ) ?? 'UNKNOWN';
816 }
817
818 if ( $payment_status === 'SUCCESS' ) {
819 $redirect = apply_filters( 'wc_vipps_recurring_merchant_redirect_url', WC_Vipps_Recurring_Helper::get_payment_redirect_url( $order ) );
820 }
821 }
822
823 if ( in_array( $payment_status, [ 'authorized', 'complete' ] ) ) {
824 $this->abandon_checkout_order( false );
825 } elseif ( $payment_status == 'cancelled' ) {
826 WC_Vipps_Recurring_Logger::log( sprintf( "[%s] Vipps/MobilePay checkout session cancelled (pending session)", $order->get_id() ) );
827
828 // This will mostly just wipe the session.
829 $this->abandon_checkout_order( $order );
830 }
831
832 // Now if we don't have an order right now, we should not have a session either, so fix that
833 if ( ! $order ) {
834 $this->abandon_checkout_order( false );
835 }
836
837 // Now check the orders vipps session if it exist
838 $session = $order ? $order->get_meta( WC_Vipps_Recurring_Helper::META_ORDER_CHECKOUT_SESSION ) : false;
839
840 // A single word or array containing session data, containing token and frontendFrameUrl
841 // ERROR EXPIRED FAILED
842 $session_status = $session ? $this->get_checkout_status( $session ) : null;
843
844 // If this is the case, there is no redirect, but the session is gone, so wipe the order and session.
845 if ( in_array( $session_status, [ 'ERROR', 'EXPIRED', 'FAILED' ] ) ) {
846 WC_Vipps_Recurring_Logger::log( sprintf( "[%s] Vipps/MobilePay checkout session is gone", $order->get_id() ) );
847 $this->abandon_checkout_order( $order );
848 }
849
850 // This will return either a valid vipps session, nothing, or redirect.
851 return [
852 'success' => (bool) $order,
853 'order' => $order ? $order->get_id() : false,
854 'session' => $session,
855 'redirect_url' => $redirect
856 ];
857 }
858
859 /**
860 * @param $session
861 *
862 * @return string
863 */
864 public function get_checkout_status( $session ): string {
865 if ( $session && isset( $session['token'] ) ) {
866 try {
867 WC_Vipps_Recurring_Logger::log( "Polling checkout from get_checkout_status" );
868 $response = $this->gateway()->api->checkout_poll( $session['pollingUrl'] );
869
870 return $response['sessionState'] ?? "PaymentInitiated";
871 } catch ( WC_Vipps_Recurring_Exception $e ) {
872 if ( $e->response_code == 400 ) {
873 return 'PaymentInitiated';
874 } else if ( $e->response_code == 404 ) {
875 return 'SessionExpired';
876 } else {
877 WC_Vipps_Recurring_Logger::log( sprintf( "Error polling status - error message %s", $e->getMessage() ) );
878
879 return 'ERROR';
880 }
881 } catch ( Exception $e ) {
882 WC_Vipps_Recurring_Logger::log( sprintf( "Error polling status - error message %s", $e->getMessage() ) );
883
884 return 'ERROR';
885 }
886 }
887
888 return "ERROR";
889 }
890
891 public function abandon_checkout_order( $order ) {
892 if ( WC()->session ) {
893 WC()->session->set( WC_Vipps_Recurring_Helper::SESSION_CHECKOUT_PENDING_ORDER_ID, 0 );
894 WC()->session->set( WC_Vipps_Recurring_Helper::SESSION_ADDRESS_HASH, false );
895 }
896
897 if ( is_a( $order, 'WC_Order' ) && $order->get_status() === 'pending' ) {
898 // We want to kill orders that have failed, or that the user has abandoned. To do this,
899 // we must ensure that no race or other mechanism kills the order while or after being paid.
900 // if order is in the process of being finalized, don't kill it
901 if ( WC_Vipps_Recurring_Helper::order_locked( $order ) ) {
902 return false;
903 }
904
905 // Get it again to ensure we have all the info, and check status again
906 clean_post_cache( $order->get_id() );
907 $order = wc_get_order( $order->get_id() );
908 if ( $order->get_status() !== 'pending' ) {
909 return false;
910 }
911
912 // And to be extra sure, check status at Vipps/MobilePay
913 $session = $order->get_meta( WC_Vipps_Recurring_Helper::META_ORDER_CHECKOUT_SESSION );
914 $poll_endpoint = ( $session && isset( $session['pollingUrl'] ) ) ? $session['pollingUrl'] : false;
915
916 if ( $poll_endpoint ) {
917 try {
918 WC_Vipps_Recurring_Logger::log( "Polling checkout from abandon_checkout_order" );
919 $poll_data = $this->gateway()->api->checkout_poll( $poll_endpoint );
920 $session_state = ( ! empty( $poll_data ) && is_array( $poll_data ) && isset( $poll_data['sessionState'] ) ) ? $poll_data['sessionState'] : "";
921 WC_Vipps_Recurring_Logger::log( sprintf( "[%s] Checking Checkout status on cart/order change: %s", $order->get_id(), $session_state ) );
922 if ( $session_state === 'PaymentSuccessful' || $session_state === 'PaymentInitiated' ) {
923 // If we have started payment, we do not kill the order.
924 WC_Vipps_Recurring_Logger::log( sprintf( "[%s] Checkout payment started - cannot cancel", $order->get_id() ) );
925
926 return false;
927 }
928 } catch ( Exception $e ) {
929 WC_Vipps_Recurring_Logger::log( sprintf( '[%s] Could not get Checkout status for order. Order is still in progress while cancelling', $order->get_id() ) );
930 }
931 }
932
933 // NB: This can *potentially* be revived by a callback!
934 WC_Vipps_Recurring_Logger::log( sprintf( '[%s] Cancelling Checkout order because order changed', $order->get_id() ) );
935 $order->set_status( 'cancelled', __( "Order specification changed - order abandoned by customer in Checkout", 'woo-vipps' ), false );
936
937 // Also mark for deletion and remove stored session
938 WC_Vipps_Recurring_Helper::delete_meta_data( $order, WC_Vipps_Recurring_Helper::META_ORDER_CHECKOUT_SESSION );
939
940 // Stop checking the status of this order in cron
941 WC_Vipps_Recurring_Helper::update_meta_data( $order, WC_Vipps_Recurring_Helper::META_CHARGE_PENDING, false );
942
943 // This is dealt with by a cron schedule
944 if ( $this->gateway()->get_option( 'checkout_cleanup_abandoned_orders' ) === 'yes' ) {
945 $subscriptions = wcs_get_subscriptions_for_order( $order->get_id() );
946 foreach ( $subscriptions as $subscription ) {
947 WC_Vipps_Recurring_Helper::update_meta_data( $subscription, WC_Vipps_Recurring_Helper::META_SUBSCRIPTION_MARKED_FOR_DELETION, 1 );
948 $subscription->save();
949 }
950 }
951
952 $order->save();
953 }
954 }
955
956 /**
957 * @throws WC_Vipps_Recurring_Exception
958 * @throws WC_Vipps_Recurring_Temporary_Exception
959 * @throws WC_Vipps_Recurring_Config_Exception
960 * @throws WC_Data_Exception
961 */
962 public function handle_callback( array $body, string $authorization_token ): void {
963 WC_Vipps_Recurring_Logger::log( sprintf( "Handling Vipps/MobilePay Checkout callback with body: %s", json_encode( $body ) ) );
964
965 $args = WC_Vipps_Recurring_Helper::add_meta_query_to_args( [], WC_Vipps_Recurring_Helper::META_ORDER_CHECKOUT_SESSION_ID, '=', $body['sessionId'], $this->gateway()->use_high_performance_order_storage() );
966
967 $orders = wc_get_orders( $args );
968
969 if ( empty( $orders ) ) {
970 WC_Vipps_Recurring_Logger::log( sprintf( "Found no order ids in Vipps/MobilePay Checkout callback for session id: %s", $body['sessionId'] ) );
971
972 return;
973 }
974
975 $order = array_pop( $orders );
976
977 $stored_authorization_token = WC_Vipps_Recurring_Helper::get_meta( $order, WC_Vipps_Recurring_Helper::META_ORDER_EXPRESS_AUTH_TOKEN );
978 if ( $authorization_token !== $stored_authorization_token ) {
979 WC_Vipps_Recurring_Logger::log( sprintf( "[%s] Invalid authorization token for session id %s.", WC_Vipps_Recurring_Helper::get_id( $order ), $body['sessionId'] ) );
980
981 return;
982 }
983
984 $this->handle_payment( $order, $body );
985 }
986
987 /**
988 * @param WC_Order $order
989 * @param array $session
990 *
991 * @return void
992 * @throws WC_Data_Exception
993 * @throws WC_Vipps_Recurring_Config_Exception
994 * @throws WC_Vipps_Recurring_Exception
995 * @throws WC_Vipps_Recurring_Temporary_Exception
996 * @throws Exception
997 */
998 public function handle_payment( WC_Order $order, array $session ): void {
999 $order_id = WC_Vipps_Recurring_Helper::get_id( $order );
1000
1001 if ( empty( $session['subscriptionDetails']['agreementId'] ) ) {
1002 return;
1003 }
1004
1005 $agreement_id = $session['subscriptionDetails']['agreementId'];
1006 $status = $session['sessionState'];
1007
1008 WC_Vipps_Recurring_Logger::log( sprintf( "[%s] Handling Vipps/MobilePay Checkout payment for agreement ID %s with status %s", $order_id, $agreement_id, $status ) );
1009
1010 // This makes sure we are covered by all our normal cron checks as well
1011 WC_Vipps_Recurring_Helper::update_meta_data( $order, WC_Vipps_Recurring_Helper::META_AGREEMENT_ID, $agreement_id );
1012
1013 $order_charge_id = WC_Vipps_Recurring_Helper::get_meta( $order, WC_Vipps_Recurring_Helper::META_CHARGE_ID );
1014 if ( empty( $order_charge_id ) ) {
1015 $charges = $this->gateway()->api->get_charges_for( $agreement_id );
1016 /** @var WC_Vipps_Charge $charge */
1017 $charge = array_pop( $charges );
1018
1019 WC_Vipps_Recurring_Helper::update_meta_data( $order, WC_Vipps_Recurring_Helper::META_CHARGE_ID, $charge->id );
1020 }
1021
1022 $order->save();
1023
1024 // "SessionCreated" "PaymentInitiated" "SessionExpired" "PaymentSuccessful" "PaymentTerminated"
1025 if ( in_array( $status, [ 'SessionExpired', 'PaymentTerminated' ] ) ) {
1026 $this->abandon_checkout_order( $order );
1027
1028 return;
1029 }
1030
1031 if ( $status !== 'PaymentSuccessful' ) {
1032 return;
1033 }
1034
1035 // On success, we might have to create a user as well, if they don't already exist, this is because Woo Subscriptions REQUIRE a user.
1036 $email = $session['billingDetails']['email'];
1037 $has_real_user = trim( $order->get_billing_email() ) !== trim( WC_Vipps_Recurring_Helper::FAKE_USER_EMAIL );
1038
1039 if ( ! $has_real_user ) {
1040 $user = get_user_by( 'email', $email );
1041
1042 if ( $user ) {
1043 $has_real_user = true;
1044 $order->set_customer_id( $user->ID );
1045 $order->save();
1046 }
1047 }
1048
1049 if ( ! $has_real_user ) {
1050 WC_Vipps_Recurring_Logger::log( sprintf( "[%s] Handling Vipps/MobilePay Checkout payment: creating a new customer", $order_id ) );
1051
1052 $firstname = $session['billingDetails']['firstName'];
1053 $lastname = $session['billingDetails']['lastName'];
1054 $name = $firstname;
1055
1056 $userdata = [
1057 'user_nicename' => $name,
1058 'display_name' => "$firstname $lastname",
1059 'nickname' => $firstname,
1060 'first_name' => $firstname,
1061 'last_name' => $lastname
1062 ];
1063
1064 $username = apply_filters( 'woo_vipps_express_checkout_new_username', '', $email, $userdata, $order );
1065 $user_id = wc_create_new_customer( $email, $username, wp_generate_password(), $userdata );
1066
1067 $customer = new WC_Customer( $user_id );
1068 $this->maybe_update_billing_and_shipping( $customer, $session );
1069
1070 WC_Vipps_Recurring_Logger::log( sprintf( "[%s] Handling Vipps/MobilePay Checkout payment: replacing customer with new id %s", $order_id, $user_id ) );
1071
1072 $order->set_customer_id( $user_id );
1073 $order->save();
1074
1075 $customer = new WC_Customer( $user_id );
1076 do_action( 'woo_vipps_express_checkout_new_customer', $customer, $order->get_id() );
1077 }
1078
1079 // Refresh order
1080 $order = wc_get_order( $order_id );
1081
1082 $this->maybe_update_billing_and_shipping( $order, $session );
1083
1084 // Update subscription with the correct customer id, and agreement id
1085 $existing_subscriptions = wcs_get_subscriptions_for_order( $order );
1086
1087 // Create a subscription if we have no subscription, because it might've been deleted previously if this session has been recovered.
1088 if ( empty( $existing_subscriptions ) ) {
1089 $existing_subscriptions = $this->gateway()->create_partial_subscriptions_from_order( $order );
1090 }
1091
1092 // If we still have no subscriptions, something is wrong. Log it
1093 if ( empty( $existing_subscriptions ) ) {
1094 WC_Vipps_Recurring_Logger::log( sprintf( "[%s] Unable to create a subscription for Checkout order and agreement ID %s", $order_id, $agreement_id ) );
1095
1096 return;
1097 }
1098
1099 /** @var WC_Subscription $subscription */
1100 $subscription = array_pop( $existing_subscriptions );
1101
1102 if ( ! $subscription ) {
1103 return;
1104 }
1105
1106 WC_Vipps_Recurring_Helper::update_meta_data( $subscription, WC_Vipps_Recurring_Helper::META_AGREEMENT_ID, $agreement_id );
1107
1108 $subscription->set_customer_id( $order->get_customer_id( 'edit' ) );
1109 wcs_copy_order_address( $order, $subscription );
1110 $subscription->save();
1111
1112 $this->gateway()->check_charge_status( $order_id, true );
1113
1114 // This is passed off to regular cron or API handling after this point
1115 WC_Vipps_Recurring_Logger::log( sprintf( "[%s] Finished handling Vipps/MobilePay Checkout payment for agreement ID %s", $order_id, $agreement_id ) );
1116 }
1117
1118 public function maybe_update_billing_and_shipping( $object, $session ): void {
1119 $contact = $session['billingDetails'] ?? $session['shippingDetails'];
1120
1121 if ( empty( $contact ) ) {
1122 return;
1123 }
1124
1125 if ( trim( $object->get_billing_email() ) === trim( WC_Vipps_Recurring_Helper::FAKE_USER_EMAIL ) ) {
1126 $object->set_billing_email( $contact['email'] );
1127 }
1128
1129 $object->set_billing_phone( '+' . $contact['phoneNumber'] );
1130 $object->set_billing_first_name( $contact['firstName'] );
1131 $object->set_billing_last_name( $contact['lastName'] );
1132 $object->set_billing_address_1( $contact['streetAddress'] );
1133 $object->set_billing_city( $contact['city'] );
1134 $object->set_billing_postcode( $contact['postalCode'] );
1135 $object->set_billing_country( $contact['country'] );
1136
1137 if ( isset( $session['shippingDetails'] ) ) {
1138 $contact = $session['shippingDetails'];
1139 }
1140
1141 $object->set_shipping_first_name( $contact['firstName'] );
1142 $object->set_shipping_last_name( $contact['lastName'] );
1143 $object->set_shipping_address_1( $contact['streetAddress'] );
1144 $object->set_shipping_city( $contact['city'] );
1145 $object->set_shipping_postcode( $contact['postalCode'] );
1146 $object->set_shipping_country( $contact['country'] );
1147
1148 $object->save();
1149 }
1150
1151 public function maybe_cancel_initial_order( WC_Order $order ): void {
1152 $created = $order->get_date_created();
1153 $now = time();
1154
1155 try {
1156 $timestamp = $created->getTimestamp();
1157 } catch ( Exception $e ) {
1158 // PHP 8 gives ValueError for certain older versions of WooCommerce here.
1159 $timestamp = intval( $created->format( 'U' ) );
1160 }
1161
1162 $passed = $now - $timestamp;
1163 $minutes = ( $passed / 60 );
1164
1165 if ( $order->get_status() === 'pending' && $minutes > 120 ) {
1166 $this->abandon_checkout_order( $order );
1167 }
1168 }
1169
1170 public function user_has_subscription( $has_subscription, $user_id, $product_id, $status ) {
1171 $subscriptions = wcs_get_users_subscriptions( $user_id );
1172
1173 foreach ( $subscriptions as $subscription ) {
1174 if ( ! WC_Vipps_Recurring_Helper::get_meta( $subscription, WC_Vipps_Recurring_Helper::META_ORDER_IS_CHECKOUT ) ) {
1175 continue;
1176 }
1177
1178 // You do not have a subscription simply because you have a pending subscription.
1179 // Checkout subscriptions are created BEFORE a payment is made.
1180 if ( $subscription->has_status( 'pending' ) ) {
1181 $has_subscription = false;
1182 break;
1183 }
1184 }
1185
1186 return $has_subscription;
1187 }
1188 }
1189