PluginProbe
Pay with Vipps and MobilePay for WooCommerce / 6.2.5
Pay with Vipps and MobilePay for WooCommerce v6.2.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.2.5, at recurring/includes/wc-vipps-recurring-helper.php

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