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-helper.php

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

587 lines 17.0 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 /**
6 * Provides static methods as helpers.
7 *
8 * @since 4.0.0
9 */
10 class WC_Vipps_Recurring_Helper {
11 /**
12 * Brand
13 */
14 public const BRAND_VIPPS = 'vipps';
15
16 public const BRAND_MOBILEPAY = 'mobilepay';
17
18 /**
19 * Charges
20 */
21 public const META_CHARGE_FAILED = '_vipps_recurring_failed_charge';
22 public const META_CHARGE_FAILED_REASON = '_vipps_recurring_failed_charge_reason';
23 public const META_CHARGE_FAILED_DESCRIPTION = '_vipps_recurring_failed_charge_description';
24 public const META_CHARGE_CAPTURED = '_vipps_recurring_captured';
25 public const META_CHARGE_PENDING = '_vipps_recurring_pending_charge';
26 public const META_CHARGE_ID = '_charge_id';
27 public const META_CHARGE_LATEST_STATUS = '_vipps_recurring_latest_api_status';
28
29 /**
30 * Agreements
31 */
32 public const META_AGREEMENT_ID = '_agreement_id';
33
34 /**
35 * Product
36 */
37 public const META_PRODUCT_DIRECT_CAPTURE = '_vipps_recurring_direct_capture';
38 public const META_PRODUCT_DESCRIPTION_SOURCE = '_vipps_recurring_product_description_source';
39 public const META_PRODUCT_DESCRIPTION_TEXT = '_vipps_recurring_product_description_text';
40
41 /**
42 * Orders
43 */
44 public const META_ORDER_STOCK_REDUCED = '_order_stock_reduced';
45 public const META_ORDER_TRANSACTION_ID = '_transaction_id';
46 public const META_ORDER_INITIAL = '_vipps_recurring_initial';
47 public const META_ORDER_ZERO_AMOUNT = '_vipps_recurring_zero_amount';
48 public const META_ORDER_IDEMPOTENCY_KEY = '_idempotency_key';
49 public const META_ORDER_CHECKOUT_SESSION = '_vipps_recurring_checkout_session';
50 public const META_ORDER_CHECKOUT_SESSION_ID = '_vipps_recurring_checkout_session_id';
51 public const META_ORDER_IS_CHECKOUT = '_vipps_recurring_is_checkout';
52 public const META_ORDER_IS_EXPRESS = '_vipps_recurring_is_express';
53 public const META_ORDER_EXPRESS_AUTH_TOKEN = '_vipps_recurring_express_auth_token';
54 public const META_ORDER_NEEDS_SHIPPING = '_vipps_recurring_needs_shipping';
55 public const META_ORDER_RESERVED_CAPTURE = '_vipps_recurring_reserved_capture';
56 public const META_ORDER_DIRECT_CAPTURE = '_vipps_recurring_direct_capture';
57
58 /**
59 * Subscription
60 */
61 public const META_SUBSCRIPTION_WAITING_FOR_GATEWAY_CHANGE = '_vipps_recurring_waiting_for_gateway_change';
62 public const META_SUBSCRIPTION_UPDATE_IN_APP = '_vipps_recurring_update_in_app';
63 public const META_SUBSCRIPTION_UPDATE_IN_APP_DESCRIPTION_PREFIX = '_vipps_recurring_update_in_app_description_prefix';
64 public const META_SUBSCRIPTION_LATEST_FAILED_CHARGE_REASON = '_vipps_recurring_latest_failed_charge_reason';
65 public const META_SUBSCRIPTION_LATEST_FAILED_CHARGE_DESCRIPTION = '_vipps_recurring_latest_failed_charge_description';
66 public const META_SUBSCRIPTION_RENEWING_WITH_VIPPS = '_vipps_recurring_renewing_with_vipps';
67 public const META_SUBSCRIPTION_MARKED_FOR_DELETION = '_vipps_recurring_marked_for_deletion';
68
69 public const OPTION_CONFIGURED = '_woo_vipps_recurring_configured';
70 public const OPTION_CHECKOUT_ENABLED = '_woo_vipps_recurring_checkout_enabled';
71 public const OPTION_WEBHOOKS = '_woo_vipps_recurring_webhooks';
72 public const OPTION_ANONYMOUS_SYSTEM_CUSTOMER_ID = '_woo_vipps_recurring_anonymous_system_customer_id';
73
74 public const SESSION_CHECKOUT_PENDING_ORDER_ID = '_vipps_recurring_checkout_pending_order_id';
75 public const SESSION_ORDERS = '_vipps_recurring_session_orders';
76 public const SESSION_PENDING_ORDER_ID = '_vipps_recurring_session_pending_order_id';
77 public const SESSION_ADDRESS_HASH = '_vipps_recurring_address_hash';
78 public const SESSION_ORDER_EXPRESS_AUTH_TOKEN = '_vipps_recurring_order_express_auth_token';
79
80 public const FAKE_USER_EMAIL = '[email protected]';
81
82 /**
83 * Whether we are successfully connected to the Vipps/MobilePay API
84 *
85 * @return bool
86 */
87 public static function is_connected(): bool {
88 return intval( get_option( self::OPTION_CONFIGURED, 0 ) ) === 1;
89 }
90
91 /**
92 * Get Vipps/MobilePay amount to pay
93 *
94 * @param float|int $total Amount due.
95 *
96 * @return int
97 */
98 public static function get_vipps_amount( $total ): int {
99 return absint( wc_format_decimal( ( (float) $total * 100 ), wc_get_price_decimals() ) ); // In cents.
100 }
101
102 /**
103 * @param null $setting
104 *
105 * @return mixed|string|void
106 */
107 public static function get_settings( $setting = null ) {
108 $all_settings = get_option( 'woocommerce_vipps_recurring_settings', [] );
109
110 if ( null === $setting ) {
111 return $all_settings;
112 }
113
114 return $all_settings[ $setting ] ?? '';
115 }
116
117 /**
118 * Checks if WC version is less than passed in version.
119 *
120 * @param string $version Version to check against.
121 *
122 * @return bool
123 * @since 1.0.0
124 */
125 public static function is_wc_lt( string $version ): bool {
126 if ( defined( 'WC_VERSION' ) ) {
127 return version_compare( WC_VERSION, $version, '<' );
128 }
129
130 return true;
131 }
132
133
134 /**
135 * Checks if WP version is less than passed in version.
136 *
137 * @param string $version Version to check against.
138 *
139 * @return bool
140 * @since 1.1.1
141 */
142 public static function is_wp_lt( string $version ): bool {
143 global $wp_version;
144
145 return version_compare( $wp_version, $version, '<' );
146 }
147
148 /**
149 * Checks if a phone number is valid according to Vipps/MobilePay standards
150 *
151 * @param $phone_number
152 *
153 * @return bool
154 */
155 public static function is_valid_phone_number( $phone_number ): bool {
156 return strlen( $phone_number ) >= 8 && strlen( $phone_number ) <= 16;
157 }
158
159 public static function normalize_phone_number( $phone_number, $country = '' ) {
160 $phone_number = preg_replace( "![^0-9]!", "", strval( $phone_number ) );
161 $phone_number = ltrim( $phone_number, "0" );
162
163 // Try to reconstruct phone numbers from information provided
164 if ( strlen( $phone_number ) == 8 && $country == 'NO' ) {
165 $phone_number = '47' . $phone_number;
166 }
167
168 if ( ! preg_match( "/^\d{10,15}$/", $phone_number ) ) {
169 $phone_number = false;
170 }
171
172 return $phone_number;
173 }
174
175 /**
176 * @param DateTime $date
177 *
178 * @return string
179 */
180 public static function get_rfc_3999_date( DateTime $date ): string {
181 return $date->format( 'Y-m-d\TH:i:s\Z' );
182 }
183
184 /**
185 * @param string|DateTime $date
186 *
187 * @return string
188 */
189 public static function rfc_3999_date_to_unix( $date ): string {
190 if ( $date instanceof DateTime ) {
191 $date = self::get_rfc_3999_date( $date );
192 }
193
194 return strtotime( $date );
195 }
196
197 /**
198 * Gets the id of a resource
199 *
200 * @param $resource
201 *
202 * @return mixed
203 */
204 public static function get_id( $resource ) {
205 return method_exists( $resource, 'get_id' )
206 ? $resource->get_id()
207 : $resource->id;
208 }
209
210 /**
211 * Gets meta data from a resource
212 *
213 * @param WC_Order|WC_Subscription|WC_Product $resource
214 * @param $meta_key
215 *
216 * @return mixed
217 */
218 public static function get_meta( $resource, $meta_key ) {
219 if ( ! $resource ) {
220 return null;
221 }
222
223 return self::is_wc_lt( '2.6.0' )
224 ? get_post_meta( self::get_id( $resource ), $meta_key, true )
225 : $resource->get_meta( $meta_key );
226 }
227
228 /**
229 * Updates meta data on a resource
230 *
231 * @param $resource
232 * @param $meta_key
233 * @param $meta_value
234 */
235 public static function update_meta_data( $resource, $meta_key, $meta_value ): void {
236 self::is_wc_lt( '2.6.0' )
237 ? update_post_meta( self::get_id( $resource ), $meta_key, $meta_value )
238 : $resource->update_meta_data( $meta_key, $meta_value );
239 }
240
241 /**
242 * Deletes meta data on a resource
243 *
244 * @param $resource
245 * @param $meta_key
246 */
247 public static function delete_meta_data( $resource, $meta_key ): void {
248 self::is_wc_lt( '2.6.0' )
249 ? delete_post_meta( self::get_id( $resource ), $meta_key )
250 : $resource->delete_meta_data( $meta_key );
251 }
252
253 /**
254 * @param $order
255 *
256 * @return mixed
257 */
258 public static function get_agreement_id_from_order( $order ) {
259 $agreement_id = self::get_meta( $order, self::META_AGREEMENT_ID );
260
261 if ( ! empty( $agreement_id ) ) {
262 return $agreement_id;
263 }
264
265 $subscriptions = wcs_get_subscriptions_for_order( $order, [ 'order_type' => 'any' ] );
266 $subscription = array_shift( $subscriptions );
267
268 return self::get_meta( $subscription, self::META_AGREEMENT_ID );
269 }
270
271 /**
272 * @param $order
273 *
274 * @return mixed
275 */
276 public static function is_charge_captured_for_order( $order ) {
277 return self::get_meta( $order, self::META_CHARGE_CAPTURED );
278 }
279
280 public static function can_capture_charge_for_order( $order ): bool {
281 return (int) self::get_meta( $order, self::META_CHARGE_PENDING ) === 1
282 && ! self::is_charge_captured_for_order( $order )
283 && in_array( self::get_latest_api_status_from_order( $order ), [
284 WC_Vipps_Charge::STATUS_RESERVED,
285 WC_Vipps_Charge::STATUS_PARTIALLY_CAPTURED
286 ] )
287 && ! (int) WC_Vipps_Recurring_Helper::get_meta( $order, WC_Vipps_Recurring_Helper::META_ORDER_ZERO_AMOUNT );
288 }
289
290 /**
291 * @param $order
292 *
293 * @return mixed
294 */
295 public static function get_charge_id_from_order( $order ) {
296 $charge_id = self::get_meta( $order, self::META_CHARGE_ID );
297
298 if ( ! empty( $charge_id ) ) {
299 return $charge_id;
300 }
301
302 return $order->get_transaction_id();
303 }
304
305 /**
306 * @param $order
307 *
308 * @return mixed
309 */
310 public static function get_latest_api_status_from_order( $order ) {
311 return self::get_meta( $order, self::META_CHARGE_LATEST_STATUS );
312 }
313
314 /**
315 * @param $order
316 * @param $status
317 */
318 public static function set_latest_api_status_for_order( $order, $status ): void {
319 self::update_meta_data( $order, self::META_CHARGE_LATEST_STATUS, $status );
320 }
321
322 /**
323 * @param $order
324 *
325 * @return mixed
326 */
327 public static function get_payment_method( $order ) {
328 return self::is_wc_lt( '3.0' )
329 ? $order->payment_method
330 : $order->get_payment_method();
331 }
332
333 /**
334 * @param $order
335 * @param $transaction_id
336 */
337 public static function set_transaction_id_for_order( $order, $transaction_id ): void {
338 self::is_wc_lt( '3.0' )
339 ? update_post_meta( $order->id, self::META_ORDER_TRANSACTION_ID, $transaction_id )
340 : $order->set_transaction_id( $transaction_id );
341 }
342
343 /**
344 * @param $order
345 *
346 * @return false|mixed
347 */
348 public static function get_transaction_id_for_order( $order ) {
349 $transaction_id = self::is_wc_lt( '3.0' )
350 ? get_post_meta( self::get_id( $order ), self::META_ORDER_TRANSACTION_ID )
351 : $order->get_transaction_id();
352
353 if ( is_int( $transaction_id ) && ! $transaction_id ) {
354 $transaction_id = false;
355 }
356
357 return apply_filters( 'wc_vipps_recurring_transaction_id_for_order', $transaction_id, $order );
358 }
359
360 /**
361 * @param $order
362 * @param $charge_id
363 */
364 public static function set_order_as_pending( $order, $charge_id ): void {
365 self::update_meta_data( $order, self::META_CHARGE_PENDING, true );
366 self::update_meta_data( $order, self::META_CHARGE_CAPTURED, true );
367 self::update_meta_data( $order, self::META_CHARGE_ID, $charge_id );
368 }
369
370 /**
371 * @param $order
372 */
373 public static function set_order_as_not_pending( $order ): void {
374 self::update_meta_data( $order, self::META_CHARGE_PENDING, false );
375 self::update_meta_data( $order, self::META_CHARGE_CAPTURED, false );
376 }
377
378 /**
379 * @param $order
380 * @param WC_Vipps_Charge $charge
381 */
382 public static function set_order_charge_failed( $order, WC_Vipps_Charge $charge ): void {
383 self::set_order_as_not_pending( $order );
384 self::update_meta_data( $order, self::META_CHARGE_FAILED, true );
385
386 self::set_order_failure_reasons( $order, $charge );
387 }
388
389 /**
390 * @param $order
391 * @param WC_Vipps_Charge $charge
392 */
393 public static function set_order_failure_reasons( $order, WC_Vipps_Charge $charge ): void {
394 $subscriptions = self::get_subscriptions_for_order( $order );
395 $subscription = $subscriptions[ array_key_first( $subscriptions ) ];
396
397 if ( isset( $charge->failure_reason ) ) {
398 self::update_meta_data( $order, self::META_CHARGE_FAILED_REASON, $charge->failure_reason );
399 // set on subscription too, this is useful for plugins like WP Sheet Editor
400 self::update_meta_data( $subscription, self::META_SUBSCRIPTION_LATEST_FAILED_CHARGE_REASON, $charge->failure_reason );
401 }
402
403 if ( isset( $charge->failure_description ) ) {
404 self::update_meta_data( $order, self::META_CHARGE_FAILED_DESCRIPTION, $charge->failure_description );
405 // set on subscription too, this is useful for plugins like WP Sheet Editor
406 self::update_meta_data( $subscription, self::META_SUBSCRIPTION_LATEST_FAILED_CHARGE_DESCRIPTION, $charge->failure_description );
407 }
408
409 $order->save();
410 $subscription->save();
411 }
412
413 public static function get_subscriptions_for_order( $order ): array {
414 return wcs_order_contains_renewal( $order ) ? wcs_get_subscriptions_for_renewal_order( $order ) : wcs_get_subscriptions_for_order( $order );
415 }
416
417 /**
418 * @param WC_Subscription $subscription
419 */
420 public static function set_update_in_app_completed( WC_Subscription $subscription ): void {
421 $subscription->delete_meta_data( self::META_SUBSCRIPTION_UPDATE_IN_APP );
422 $subscription->delete_meta_data( self::META_SUBSCRIPTION_UPDATE_IN_APP_DESCRIPTION_PREFIX );
423 $subscription->save();
424 }
425
426 /**
427 * @param $order
428 * @param $charge_id
429 */
430 public static function set_order_charge_not_failed( $order, $charge_id ): void {
431 self::update_meta_data( $order, self::META_CHARGE_FAILED, false );
432 self::set_order_as_pending( $order, $charge_id );
433 }
434
435 /**
436 * @param $order
437 *
438 * @return mixed
439 */
440 public static function is_charge_failed_for_order( $order ): bool {
441 return (bool) self::get_meta( $order, self::META_CHARGE_FAILED );
442 }
443
444 /**
445 * @param $order
446 *
447 * @return mixed
448 */
449 public static function get_failure_reason_for_order( $order ) {
450 return self::get_meta( $order, self::META_CHARGE_FAILED_REASON );
451 }
452
453 /**
454 * @param $order
455 *
456 * @return mixed
457 */
458 public static function get_failure_description_for_order( $order ) {
459 return self::get_meta( $order, self::META_CHARGE_FAILED_DESCRIPTION );
460 }
461
462 /**
463 * @param WC_Product $product
464 *
465 * @return string|null
466 */
467 public static function get_product_description( WC_Product $product ): ?string {
468 $source = self::get_meta( $product, self::META_PRODUCT_DESCRIPTION_SOURCE );
469
470 $description = null;
471 if ( $source === 'short_description' ) {
472 $description = $product->get_short_description();
473 }
474
475 $custom_description = self::get_meta( $product, self::META_PRODUCT_DESCRIPTION_TEXT );
476 if ( $source === 'custom' && ! empty( $custom_description ) ) {
477 $description = $custom_description;
478 }
479
480 return $description;
481 }
482
483 /**
484 * @param $order
485 *
486 * @return mixed
487 */
488 public static function is_stock_reduced_for_order( $order ) {
489 return self::get_meta( $order, self::META_ORDER_STOCK_REDUCED );
490 }
491
492 /**
493 * @param $order
494 */
495 public static function reduce_stock_for_order( $order ): void {
496 self::is_wc_lt( '3.0' )
497 ? $order->reduce_order_stock()
498 : wc_reduce_stock_levels( self::get_id( $order ) );
499 }
500
501 public static function get_payment_redirect_url( WC_Order $order, bool $is_gateway_change = false ): string {
502 if ( $is_gateway_change ) {
503 return filter_var( get_permalink( get_option( 'woocommerce_myaccount_page_id' ) ), FILTER_VALIDATE_URL )
504 ? get_permalink( get_option( 'woocommerce_myaccount_page_id' ) )
505 : wc_get_account_endpoint_url( 'dashboard' );
506 }
507
508 $order_id = self::get_id( $order );
509 $order_key = $order->get_order_key();
510
511 return home_url() . "/vipps-mobilepay-recurring-payment?order_id=$order_id&key=$order_key";
512 }
513
514 public static function get_order_lock_name( $order ): string {
515 return 'order_lock_' . self::get_id( $order );
516 }
517
518 public static function lock_order( $order ): bool {
519 if ( get_transient( self::get_order_lock_name( $order ) ) ) {
520 return false;
521 }
522
523 set_transient( self::get_order_lock_name( $order ), uniqid( '', true ), 30 );
524
525 add_action( 'shutdown', function () use ( $order ) {
526 WC_Gateway_Vipps_Recurring::get_instance()->unlock_order( $order );
527 } );
528
529 return true;
530 }
531
532 public static function order_locked( $order ): bool {
533 return get_transient( self::get_order_lock_name( $order ) ) !== false;
534 }
535
536 public static function get_checkout_pending_order_id() {
537 return is_a( WC()->session, 'WC_Session' ) ? WC()->session->get( self::SESSION_CHECKOUT_PENDING_ORDER_ID ) : false;
538 }
539
540 public static function get_failed_retries_amount_for_order( $order ): int {
541 $retries = \WCS_Retry_Manager::store()->get_retries(
542 [
543 'order_id' => $order->get_id(),
544 ],
545 'ids'
546 );
547
548 return count( $retries );
549 }
550
551 public static function generate_vipps_order_id( $order, $prefix ) {
552 $order_id = self::get_id( $order );
553 $vipps_order_id = $prefix . $order_id;
554
555 if ( strlen( $vipps_order_id ) < 8 ) {
556 $vipps_order_id = $prefix . str_pad( "" . $order_id, 8 - strlen( $prefix ), "0", STR_PAD_LEFT );
557 }
558
559 // To prevent re-using an existing order id, we need to check if the order has failed retries.
560 // Append the number of retries if there are.
561 $failed_renewals = self::get_failed_retries_amount_for_order( $order );
562 if ( $failed_renewals > 0 ) {
563 $vipps_order_id = $vipps_order_id . "-" . $failed_renewals;
564 }
565
566 return apply_filters( 'wc_vipps_recurring_order_id', $vipps_order_id, $prefix, $order );
567 }
568
569 public static function add_meta_query_to_args( array $args, string $key, string $compare, $value, bool $hpos ): array {
570 if ( $hpos ) {
571 $args['meta_query'] = [
572 [
573 'key' => $key,
574 'compare' => $compare,
575 'value' => $value
576 ]
577 ];
578 } else {
579 $args['meta_key'] = $key;
580 $args['meta_compare'] = $compare;
581 $args['meta_value'] = $value;
582 }
583
584 return $args;
585 }
586 }
587