PluginProbe
Pay with Vipps and MobilePay for WooCommerce / 6.1.5
Pay with Vipps and MobilePay for WooCommerce v6.1.5
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 6.0.0 5.4.3 5.4.2 All 187 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.5, 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 add_action( 'wp_loaded', [ $this, 'register_scripts' ] );
104
105 // Prevent previews and prefetches of the Vipps Checkout page starting and creating orders
106 add_action( 'wp_head', [ $this, 'wp_head' ] );
107
108 // The Vipps MobilePay Checkout feature which overrides the normal checkout process uses a shortcode
109 add_shortcode( 'vipps_recurring_checkout', [ $this, 'shortcode' ] );
110
111 add_action( 'wc_vipps_recurring_before_cron_check_order_status', [ $this, 'check_order_status' ] );
112 add_action( 'wc_vipps_recurring_before_rest_api_check_order_status', [ $this, 'check_order_status' ] );
113
114 // For Checkout, we need to know any time and as soon as the cart changes, so fold all the events into a single one
115 add_action( 'woocommerce_add_to_cart', function () {
116 do_action( 'vipps_recurring_cart_changed', 'woocommerce_add_to_cart' );
117 }, 10, 0 );
118
119 // Cart coupon applied
120 add_action( 'woocommerce_applied_coupon', function () {
121 do_action( 'vipps_recurring_cart_changed', 'woocommerce_applied_coupon' );
122 }, 10, 0 );
123
124 // Cart emptied
125 add_action( 'woocommerce_cart_emptied', function () {
126 do_action( 'vipps_recurring_cart_changed', 'woocommerce_cart_emptied' );
127 }, 10, 0 );
128
129 // After updating quantities
130 add_action( 'woocommerce_after_cart_item_quantity_update', function () {
131 do_action( 'vipps_recurring_cart_changed', 'woocommerce_after_cart_item_quantity_update' );
132 }, 10, 0 );
133
134 // Blocks and ajax
135 add_action( 'woocommerce_cart_item_removed', function () {
136 do_action( 'vipps_recurring_cart_changed', 'woocommerce_cart_item_removed' );
137 }, 10, 0 );
138
139 // Restore deleted entry
140 add_action( 'woocommerce_cart_item_restored', function () {
141 do_action( 'vipps_recurring_cart_changed', 'woocommerce_cart_item_restored' );
142 }, 10, 0 );
143
144 // Normal cart form update
145 add_filter( 'woocommerce_update_cart_action_cart_updated', function ( $updated ) {
146 do_action( 'vipps_recurring_cart_changed', 'woocommerce_update_cart_action_cart_updated' );
147
148 return $updated;
149 } );
150
151 // Then handle the actual cart change
152 add_action( 'vipps_recurring_cart_changed', [ $this, 'cart_changed' ] );
153
154 add_action( 'wc_vipps_recurring_checkout_callback', [ $this, 'handle_callback' ], 10, 2 );
155
156 // Handle cancelled orders
157 add_action( 'wc_vipps_recurring_check_charge_status_no_agreement', [ $this, 'maybe_cancel_initial_order' ] );
158
159 add_filter( 'wcs_user_has_subscription', [ $this, 'user_has_subscription' ], 10, 4 );
160 }
161
162 public function admin_init(): void {
163 // Checkout page
164 add_filter( 'woocommerce_settings_pages', array( $this, 'woocommerce_settings_pages' ) );
165 }
166
167 /**
168 * @throws WC_Vipps_Recurring_Exception
169 * @throws WC_Vipps_Recurring_Temporary_Exception
170 * @throws WC_Vipps_Recurring_Config_Exception
171 */
172 public function maybe_create_session(): array {
173 $redirect_url = null;
174 $token = null;
175 $url = null;
176
177 $session = WC_Vipps_Recurring_Checkout::get_instance()->current_pending_session();
178
179 if ( isset( $session['redirect_url'] ) ) {
180 $redirect_url = $session['redirect_url'];
181 }
182
183 if ( isset( $session['session']['token'] ) ) {
184 $token = $session['session']['token'];
185 $src = $session['session']['checkoutFrontendUrl'];
186 $url = $src;
187 }
188
189 if ( $url ) {
190 $pending_order_id = WC_Vipps_Recurring_Helper::get_checkout_pending_order_id();
191
192 return [
193 'success' => true,
194 'src' => $url,
195 'redirect_url' => $redirect_url,
196 'token' => $token,
197 'order_id' => $pending_order_id
198 ];
199 }
200
201 $session = null;
202
203 try {
204 $partial_order_id = WC_Gateway_Vipps_Recurring::get_instance()->create_partial_order( true );
205
206 $order = wc_get_order( $partial_order_id );
207 $auth_token = WC_Gateway_Vipps_Recurring::get_instance()->api->generate_idempotency_key();
208
209 $order->update_meta_data( WC_Vipps_Recurring_Helper::META_ORDER_EXPRESS_AUTH_TOKEN, $auth_token );
210 $order->save();
211
212 WC()->session->set( WC_Vipps_Recurring_Helper::SESSION_CHECKOUT_PENDING_ORDER_ID, $partial_order_id );
213 WC()->session->set( WC_Vipps_Recurring_Helper::SESSION_ORDER_EXPRESS_AUTH_TOKEN, $auth_token );
214
215 do_action( 'wc_vipps_recurring_checkout_order_created', $order );
216 } catch ( Exception $exception ) {
217 return [
218 'success' => false,
219 'msg' => $exception->getMessage(),
220 'src' => null,
221 'redirect_url' => null,
222 'order_id' => 0
223 ];
224 }
225
226 $order = wc_get_order( $partial_order_id );
227
228 $session_orders = WC()->session->get( WC_Vipps_Recurring_Helper::SESSION_ORDERS );
229 if ( ! $session_orders ) {
230 $session_orders = [];
231 }
232
233 $session_orders[ $partial_order_id ] = 1;
234 WC()->session->set( WC_Vipps_Recurring_Helper::SESSION_PENDING_ORDER_ID, $partial_order_id );
235 WC()->session->set( WC_Vipps_Recurring_Helper::SESSION_ORDERS, $session_orders );
236
237 $customer_id = get_current_user_id();
238 if ( $customer_id ) {
239 $customer = new WC_Customer( $customer_id );
240 } else {
241 $customer = WC()->customer;
242 }
243
244 if ( $customer ) {
245 $customer_info['email'] = $customer->get_billing_email();
246 $customer_info['firstName'] = $customer->get_billing_first_name();
247 $customer_info['lastName'] = $customer->get_billing_last_name();
248 $customer_info['streetAddress'] = $customer->get_billing_address_1();
249 $address2 = trim( $customer->get_billing_address_2() );
250
251 if ( ! empty( $address2 ) ) {
252 $customer_info['streetAddress'] = $customer_info['streetAddress'] . ", " . $address2;
253 }
254 $customer_info['city'] = $customer->get_billing_city();
255 $customer_info['postalCode'] = $customer->get_billing_postcode();
256 $customer_info['country'] = $customer->get_billing_country();
257
258 // Currently Vipps requires all phone numbers to have area codes and NO +. We can't guarantee that at all, but try for Norway
259 $normalized_phone_number = WC_Vipps_Recurring_Helper::normalize_phone_number( $customer->get_billing_phone(), $customer_info['country'] );
260 if ( $normalized_phone_number ) {
261 $customer_info['phoneNumber'] = $normalized_phone_number;
262 }
263 }
264
265 $keys = [ 'firstName', 'lastName', 'streetAddress', 'postalCode', 'country', 'phoneNumber' ];
266 foreach ( $keys as $k ) {
267 if ( empty( $customer_info[ $k ] ) ) {
268 $customer_info = [];
269 break;
270 }
271 }
272 $customer_info = apply_filters( 'wc_vipps_recurring_customer_info', $customer_info, $order );
273
274 // todo: throw an error if we try to purchase a product with a location based shipping method?
275 try {
276 $checkout = WC_Vipps_Recurring_Checkout::get_instance();
277 $gateway = $checkout->gateway();
278
279 // hack - fake "Anonymous Vipps/MobilePay User"
280 $fake_user = false;
281 if ( ! $order->get_customer_id( 'edit' ) ) {
282 $fake_user = true;
283
284 $order->set_customer_id( $this->gateway()->create_or_get_anonymous_system_customer()->get_id() );
285 $order->save();
286 }
287
288 // Check if we already have a subscription on this order, otherwise create one
289 if ( wcs_order_contains_subscription( $order, 'any' ) ) {
290 $subscriptions = wcs_get_subscriptions_for_order( $order );
291 } else {
292 $subscriptions = $gateway->create_partial_subscriptions_from_order( $order );
293 }
294
295 // reset hack
296 if ( $fake_user ) {
297 $order->set_customer_id( 0 );
298 $order->save();
299 }
300
301 $subscription = array_pop( $subscriptions );
302
303 // Remove this action to avoid making an unnecessary API request
304 remove_action( 'woocommerce_order_after_calculate_totals', [
305 $this->gateway(),
306 'update_agreement_price_in_app'
307 ] );
308
309 $subscription->calculate_totals();
310 $subscription->save();
311
312 $agreement = $gateway->create_vipps_agreement_from_order( $order, $subscription );
313
314 $checkout_subscription = ( new WC_Vipps_Checkout_Session_Subscription() )
315 ->set_amount(
316 ( new WC_Vipps_Checkout_Session_Amount() )
317 ->set_value( $agreement->pricing->amount )
318 ->set_currency( $agreement->pricing->currency )
319 )
320 ->set_product_name( $agreement->product_name )
321 ->set_interval( $agreement->interval )
322 ->set_merchant_agreement_url( $agreement->merchant_agreement_url );
323
324 if ( $agreement->campaign ) {
325 $checkout_subscription = $checkout_subscription->set_campaign( $agreement->campaign );
326 }
327
328 if ( $agreement->product_description ) {
329 $checkout_subscription = $checkout_subscription->set_product_description( $agreement->product_description );
330 }
331
332 $configuration = ( new WC_Vipps_Checkout_Session_Configuration() )
333 ->set_user_flow( WC_Vipps_Checkout_Session_Configuration::USER_FLOW_WEB_REDIRECT )
334 ->set_customer_interaction( WC_Vipps_Checkout_Session_Configuration::CUSTOMER_INTERACTION_NOT_PRESENT )
335 ->set_elements( WC_Vipps_Checkout_Session_Configuration::ELEMENTS_FULL )
336 ->set_require_user_info( empty( $customer->email ) )
337 ->set_show_order_summary( true );
338
339 $countries = array_keys( ( new WC_Countries() )->get_allowed_countries() );
340 $allowed_countries = apply_filters( 'woo_vipps_recurring_checkout_countries', $countries, $order->get_id() );
341 if ( $allowed_countries ) {
342 $configuration->set_countries( [ 'supported' => $allowed_countries ] );
343 }
344
345 $customer = new WC_Vipps_Checkout_Session_Customer( $customer_info );
346
347 // Create a checkout session dto
348 $checkout_session = ( new WC_Vipps_Checkout_Session() )
349 ->set_type( WC_Vipps_Checkout_Session::TYPE_SUBSCRIPTION )
350 ->set_subscription( $checkout_subscription )
351 ->set_merchant_info(
352 ( new WC_Vipps_Checkout_Session_Merchant_Info() )
353 ->set_callback_url( $gateway->webhook_callback_url() )
354 ->set_return_url( $agreement->merchant_redirect_url )
355 ->set_callback_authorization_token( $auth_token )
356 )
357 ->set_prefill_customer( $customer )
358 ->set_configuration( $configuration );
359
360 if ( $agreement->initial_charge ) {
361 $reference = $agreement->initial_charge->order_id;
362
363 $checkout_transaction = ( new WC_Vipps_Checkout_Session_Transaction() )
364 ->set_reference( $reference )
365 ->set_amount(
366 ( new WC_Vipps_Checkout_Session_Amount() )
367 ->set_value( $agreement->initial_charge->amount )
368 ->set_currency( $agreement->pricing->currency )
369 )
370 ->set_order_summary( $checkout->make_order_summary( $order ) );
371
372 if ( $agreement->initial_charge->description ) {
373 $checkout_transaction = $checkout_transaction->set_payment_description( $agreement->initial_charge->description );
374 }
375
376 $checkout_session = $checkout_session->set_transaction( $checkout_transaction );
377
378 WC_Vipps_Recurring_Helper::update_meta_data( $order, WC_Vipps_Recurring_Helper::META_CHARGE_ID, $reference );
379 }
380
381 $checkout_session = apply_filters( 'wc_vipps_recurring_checkout_session', $checkout_session, $order );
382
383 $session = WC_Gateway_Vipps_Recurring::get_instance()->api->checkout_initiate( $checkout_session );
384
385 $order = wc_get_order( $partial_order_id );
386 WC_Vipps_Recurring_Helper::update_meta_data( $order, WC_Vipps_Recurring_Helper::META_CHARGE_PENDING, true );
387 WC_Vipps_Recurring_Helper::update_meta_data( $order, WC_Vipps_Recurring_Helper::META_ORDER_INITIAL, true );
388 WC_Vipps_Recurring_Helper::update_meta_data( $order, WC_Vipps_Recurring_Helper::META_ORDER_CHECKOUT_SESSION, $session->to_array() );
389
390 $session_poll = WC_Gateway_Vipps_Recurring::get_instance()->api->checkout_poll( $session->polling_url );
391 WC_Vipps_Recurring_Helper::update_meta_data( $order, WC_Vipps_Recurring_Helper::META_ORDER_CHECKOUT_SESSION_ID, $session_poll['sessionId'] );
392
393 $order->add_order_note( __( 'Vipps/MobilePay recurring checkout payment initiated', 'woo-vipps' ) );
394 $order->add_order_note( __( 'Customer passed to Vipps/MobilePay checkout', 'woo-vipps' ) );
395 $order->save();
396
397 $token = $session->token;
398 $src = $session->checkout_frontend_url;
399 $url = $src;
400 } catch ( Exception $e ) {
401 WC_Vipps_Recurring_Logger::log( sprintf( "Could not initiate Vipps/MobilePay checkout session: %s", $e->getMessage() ) );
402
403 return [
404 'success' => false,
405 'msg' => $e->getMessage(),
406 'src' => null,
407 'redirect_url' => null,
408 'order_id' => $partial_order_id
409 ];
410 }
411
412 if ( $url || $redirect_url ) {
413 return [
414 'success' => true,
415 'msg' => 'session started',
416 'src' => $url,
417 'redirect_url' => $redirect_url,
418 'token' => $token,
419 'order_id' => $partial_order_id
420 ];
421 }
422
423 return [
424 'success' => false,
425 'msg' => __( 'Could not start Vipps/MobilePay checkout session', 'woo-vipps' ),
426 'src' => $url,
427 'redirect_url' => $redirect_url,
428 'order_id' => $partial_order_id
429 ];
430 }
431
432 public function maybe_login_checkout_user( int $order_id, ?string $key = null ): void {
433 if ( ! $key ) {
434 return;
435 }
436
437 $order = wc_get_order( $order_id );
438 if ( ! $order ) {
439 return;
440 }
441
442 $order_key_db = $order->get_order_key( 'code' );
443 if ( $order_key_db !== $key ) {
444 return;
445 }
446
447 // Attempt to log the user in
448 $session_auth_token = WC()->session->get( WC_Vipps_Recurring_Helper::SESSION_ORDER_EXPRESS_AUTH_TOKEN );
449 $order_auth_token = WC_Vipps_Recurring_Helper::get_meta( $order, WC_Vipps_Recurring_Helper::META_ORDER_EXPRESS_AUTH_TOKEN );
450
451 if ( $order_auth_token !== $session_auth_token ) {
452 return;
453 }
454
455 $user = $order->get_user();
456
457 wc_set_customer_auth_cookie( $user->ID );
458 WC()->session->set( WC_Vipps_Recurring_Helper::SESSION_ORDER_EXPRESS_AUTH_TOKEN, null );
459 }
460
461 public function cart_changed( string $source ): void {
462 $pending_order_id = is_a( WC()->session, 'WC_Session' ) ? WC()->session->get( WC_Vipps_Recurring_Helper::SESSION_CHECKOUT_PENDING_ORDER_ID ) : false;
463 $order = $pending_order_id ? wc_get_order( $pending_order_id ) : null;
464
465 if ( ! $order ) {
466 return;
467 }
468
469 WC_Vipps_Recurring_Logger::log( sprintf( "Checkout cart changed while session %d in progress, attempting to cancel. Source: %s", $order->get_id(), $source ) );
470 $this->abandon_checkout_order( $order );
471 }
472
473 /**
474 * @throws WC_Vipps_Recurring_Config_Exception
475 * @throws WC_Data_Exception
476 * @throws WC_Vipps_Recurring_Exception
477 * @throws WC_Vipps_Recurring_Temporary_Exception
478 */
479 public function check_order_status( $order_id ): void {
480 $lock_name = "vipps_recurring_checkout_check_order_status_$order_id";
481 $lock = get_transient( $lock_name );
482
483 if ( $lock ) {
484 return;
485 }
486
487 set_transient( $lock_name, uniqid( '', true ), 5 );
488
489 $order = wc_get_order( $order_id );
490
491 if ( ! WC_Vipps_Recurring_Helper::get_meta( $order, WC_Vipps_Recurring_Helper::META_ORDER_IS_CHECKOUT ) ) {
492 return;
493 }
494
495 $session = WC_Vipps_Recurring_Helper::get_meta( $order, WC_Vipps_Recurring_Helper::META_ORDER_CHECKOUT_SESSION );
496 if ( ! is_array( $session ) ) {
497 return;
498 }
499
500 $session = $this->gateway()->api->checkout_poll( $session['pollingUrl'] );
501
502 $this->handle_payment( $order, $session );
503 }
504
505 public function woocommerce_settings_pages( $settings ) {
506 $checkout_enabled = get_option( WC_Vipps_Recurring_Helper::OPTION_CHECKOUT_ENABLED, false );
507 if ( ! $checkout_enabled ) {
508 return $settings;
509 }
510
511 // Find out where the end of the section advanced_page_options is
512 $i = 0;
513 $count = count( $settings );
514
515 for ( ; $i < $count; $i ++ ) {
516 if ( $settings[ $i ]['type'] === 'sectionend' && $settings[ $i ]['id'] === 'advanced_page_options' ) {
517 break;
518 }
519 }
520
521 if ( $i < $count ) {
522 array_splice( $settings, $i, 0, [
523 [
524 'title' => __( 'Vipps/MobilePay Recurring Checkout Page', 'woo-vipps' ),
525 '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' ),
526 'id' => 'woocommerce_vipps_recurring_checkout_page_id',
527 'type' => 'single_select_page_with_search',
528 'default' => '',
529 'class' => 'wc-page-search',
530 'css' => 'min-width:300px;',
531 'args' => [
532 'exclude' =>
533 [
534 wc_get_page_id( 'myaccount' ),
535 ],
536 ],
537 'desc_tip' => true,
538 'autoload' => false,
539 ]
540 ] );
541 }
542
543 return $settings;
544 }
545
546 public function woocommerce_get_checkout_page_id( $id ): int {
547 $checkout_enabled = get_option( WC_Vipps_Recurring_Helper::OPTION_CHECKOUT_ENABLED, false );
548
549 if ( ! $checkout_enabled ) {
550 return $id;
551 }
552
553 $checkout_id = $this->gateway()->checkout_is_available();
554 if ( $checkout_id ) {
555 return $checkout_id;
556 }
557
558 return $id;
559 }
560
561 public function template_redirect(): void {
562 global $post;
563
564 if ( $post && is_page() && has_shortcode( $post->post_content, 'vipps_recurring_checkout' ) ) {
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( '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