PluginProbe
Subscriptions for WooCommerce with Stripe Recurring Payments / 1.9.6
Subscriptions for WooCommerce with Stripe Recurring Payments v1.9.6
2.0.0 1.11.2 1.11.1 1.11.0 1.10.9 1.10.8 1.10.7 1.10.6 1.10.5 1.10.4 1.10.3 1.10.2 1.10.1 1.10.0 1.9.6 1.9.5 trunk 1.3.0 1.3.1 1.3.2 1.4.0 1.4.1 1.4.2 1.5.0 1.5.1 All 61 releases
subscription / includes / Illuminate / Helper.php

Helper.php in Subscriptions for WooCommerce with Stripe Recurring Payments 1.9.6, at includes/Illuminate/Helper.php

1,350 lines 45.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace SpringDevs\Subscription\Illuminate;
4
5 use SpringDevs\Subscription\Illuminate\Gateways\Stripe\Stripe;
6 use SpringDevs\Subscription\Illuminate\Subscription\Subscription;
7
8 // HPOS: This file is compatible with WooCommerce High-Performance Order Storage (HPOS).
9 // All WooCommerce order data is accessed via WooCommerce CRUD methods (wc_get_order, wc_get_orders, etc.).
10 // All direct post meta access is for subscription data only, not WooCommerce order data.
11 // If you add new order data access, use WooCommerce CRUD for HPOS compatibility.
12
13 /**
14 * Class Helper || Some Helper Methods
15 *
16 * @package SpringDevs\Subscription\Illuminate
17 */
18 class Helper {
19 /**
20 * Get type's singular or plural from time_per.
21 *
22 * @param int $number timing_per.
23 * @param string $typo timing_option.
24 * @param bool $translate Whether to translate the output.
25 * @return string
26 */
27 public static function get_typos( $number, $typo, $translate = false ) {
28 switch ( strtolower( $typo ) ) {
29 case 'day':
30 case 'days':
31 return $translate
32 ? _n( 'day', 'days', $number, 'subscription' )
33 : ( (int) $number === 1 ? 'day' : 'days' );
34
35 case 'week':
36 case 'weeks':
37 return $translate
38 ? _n( 'week', 'weeks', $number, 'subscription' )
39 : ( (int) $number === 1 ? 'week' : 'weeks' );
40
41 case 'month':
42 case 'months':
43 return $translate
44 ? _n( 'month', 'months', $number, 'subscription' )
45 : ( (int) $number === 1 ? 'month' : 'months' );
46
47 case 'year':
48 case 'years':
49 return $translate
50 ? _n( 'year', 'years', $number, 'subscription' )
51 : ( (int) $number === 1 ? 'year' : 'years' );
52
53 default:
54 return $typo;
55 }
56 }
57
58 /**
59 * Get verbose status from status slug.
60 *
61 * @param string $status Status.
62 * @param bool $return_all Whether to return all statuses or a single status.
63 */
64 public static function get_verbose_status( $status, $return_all = false ): string|array {
65 $statuses = array(
66 'pending' => __( 'Pending', 'subscription' ),
67 'active' => __( 'Active', 'subscription' ),
68 'on-hold' => __( 'On Hold', 'subscription' ),
69 'expired' => __( 'Expired', 'subscription' ),
70 'pe_cancelled' => __( 'Pending Cancellation', 'subscription' ),
71 'cancelled' => __( 'Cancelled', 'subscription' ),
72 'draft' => __( 'Draft', 'subscription' ),
73 'trash' => __( 'Trash', 'subscription' ),
74 );
75
76 if ( $return_all ) {
77 return $statuses;
78 }
79
80 $status = strtolower( $status );
81 return isset( $statuses[ $status ] ) ? $statuses[ $status ] : '';
82 }
83
84 /**
85 * Generate start date
86 *
87 * @param null|string $trial Trial.
88 *
89 * @return string
90 */
91 public static function start_date( $trial = null ) {
92 if ( null === $trial ) {
93 $start_date = time();
94 } else {
95 $start_date = strtotime( $trial );
96 }
97 return wp_date( get_option( 'date_format' ), $start_date );
98 }
99
100 /**
101 * Generate next date
102 *
103 * @param string $time Time.
104 * @param null|string $trial Trial.
105 *
106 * @return string
107 */
108 public static function next_date( $time, $trial = null ) {
109 if ( null === $trial ) {
110 $start_date = time();
111 } else {
112 $start_date = strtotime( $trial );
113 }
114 return wp_date( get_option( 'date_format' ), strtotime( $time, $start_date ) );
115 }
116
117 /**
118 * Get Subscriptions
119 *
120 * Args:
121 * - status => [ any, active, pending, expired, pe_cancelled, cancelled, trash ]
122 * - user_id => user_id, -1 for all users.
123 * - posts_per_page => limit number of subscriptions.
124 * - return => return data: ids, post, subscription_data
125 *
126 * @param array $args Args.
127 */
128 public static function get_subscriptions( array $args = array() ) {
129 $default_args = array(
130 'post_type' => 'subscrpt_order',
131 'post_status' => 'active',
132 'author' => get_current_user_id(),
133 'posts_per_page' => -1,
134 'fields' => 'all',
135 'return' => 'post',
136 );
137
138 // Normalize some args.
139 if ( isset( $args['status'] ) ) {
140 $args['post_status'] = $args['status'];
141 unset( $args['status'] );
142 }
143 if ( isset( $args['user_id'] ) ) {
144 $args['author'] = $args['user_id'];
145 unset( $args['user_id'] );
146 }
147
148 // Merge default args with provided args.
149 $final_args = wp_parse_args( $args, $default_args );
150
151 if ( isset( $args['author'] ) ) {
152 if ( $args['author'] === -1 ) {
153 unset( $final_args['author'] );
154 } else {
155 $final_args['author'] = (int) $args['author'];
156 }
157 }
158
159 if ( isset( $args['product_id'] ) ) {
160 $final_args['meta_query'] = array(
161 array(
162 'key' => '_subscrpt_product_id',
163 'value' => (int) $args['product_id'],
164 ),
165 );
166 unset( $final_args['product_id'] );
167 }
168
169 // Fields check
170 $only_ids = false;
171 if ( $final_args['fields'] === 'ids' || $final_args['return'] === 'ids' ) {
172 $final_args['fields'] = 'all';
173 $only_ids = true;
174 }
175
176 // Status check
177 $statuses = $final_args['post_status'];
178 $final_args['post_status'] = 'any';
179
180 // Get all subscriptions.
181 $subscriptions = get_posts( $final_args );
182
183 // Fallback filtering.
184 // ? Sometime status filtering not works properly. So, we need to filter manually.
185 $filtered_subscriptions = [];
186
187 // Filter by status.
188 foreach ( $subscriptions as $subscription ) {
189 if ( ( is_array( $statuses ) && in_array( 'any', $statuses, true ) ) || $statuses === 'any' ) {
190 $filtered_subscriptions[] = $subscription;
191 continue;
192 }
193
194 if ( ( is_array( $statuses ) && in_array( $subscription->post_status, $statuses, true ) ) || $subscription->post_status === $statuses ) {
195 $filtered_subscriptions[] = $subscription;
196 }
197 }
198
199 // Final filtering (only ids, post, or full data)
200 $subscriptions = [];
201 foreach ( $filtered_subscriptions as $subscription ) {
202 if ( $only_ids ) {
203 $subscriptions[] = $subscription->ID;
204 } elseif ( $final_args['return'] === 'subscription_data' ) {
205 $subs_id = $subscription->ID;
206 $subscription_data = self::get_subscription_data( $subs_id );
207 $subscriptions[] = $subscription_data;
208 } else {
209 $subscriptions[] = $subscription;
210 }
211 }
212
213 return $subscriptions;
214 }
215
216 /**
217 * Check subscription exists by product ID.
218 *
219 * @param int $product_id Product ID.
220 * @param string|array $status Status.
221 *
222 * @return \WP_Post | false
223 */
224 public static function subscription_exists( int $product_id, $status ) {
225 if ( 0 === get_current_user_id() ) {
226 return false;
227 }
228
229 $args = array(
230 'post_status' => $status,
231 'fields' => 'ids',
232 'product_id' => $product_id,
233 );
234
235 $posts = self::get_subscriptions( $args );
236 return count( $posts ) > 0 ? $posts[0] : false;
237 }
238
239 /**
240 * Check if product trial exixts for an user.
241 *
242 * @param int $product_id Product ID.
243 *
244 * @return boolean
245 */
246 public static function check_trial( int $product_id ): bool {
247 return ! self::subscription_exists( $product_id, array( 'expired', 'pending', 'active', 'on-hold', 'pe_cancelled', 'cancelled' ) );
248 }
249
250 /**
251 * Rewew when expired.
252 *
253 * @param int $subscription_id Subscription ID.
254 */
255 public static function renew( int $subscription_id ) {
256 $trial = get_post_meta( $subscription_id, '_subscrpt_trial', true );
257 if ( null !== $trial ) {
258 update_post_meta( $subscription_id, '_subscrpt_trial', null );
259 }
260
261 do_action( 'subscrpt_when_product_expired', $subscription_id, true );
262 }
263
264 /**
265 * Get Subscriptions Histories
266 *
267 * @param int $order_id Order ID.
268 */
269 public static function get_subscriptions_from_order( $order_id ) {
270 global $wpdb;
271 $table_name = $wpdb->prefix . 'subscrpt_order_relation';
272 $histories = $wpdb->get_results(
273 $wpdb->prepare(
274 // @phpcs:ignore
275 'SELECT * FROM %i WHERE order_id=%d',
276 array( $table_name, $order_id )
277 )
278 );
279
280 return $histories;
281 }
282
283 /**
284 * Get Subscriptions Histories
285 *
286 * @param int $order_item_id Order item ID.
287 */
288 public static function get_subscription_from_order_item_id( $order_item_id ) {
289 global $wpdb;
290 $table_name = $wpdb->prefix . 'subscrpt_order_relation';
291 return $wpdb->get_row(
292 $wpdb->prepare(
293 // @phpcs:ignore
294 'SELECT * FROM %i WHERE order_item_id=%d',
295 array( $table_name, $order_item_id )
296 )
297 );
298 }
299
300 /**
301 * Format price with Subscription
302 *
303 * @param string $price Price.
304 * @param int $subscription_id Subscription ID.
305 * @param bool $display_trial True/False.
306 *
307 * @return string
308 */
309 public static function format_price_with_subscription( $price, $subscription_id, $display_trial = false ) {
310 $order_id = get_post_meta( $subscription_id, '_subscrpt_order_id', true );
311 $order_item_id = get_post_meta( $subscription_id, '_subscrpt_order_item_id', true );
312 $item_meta = wc_get_order_item_meta( $order_item_id, '_subscrpt_meta', true );
313
314 $order = wc_get_order( $order_id );
315 $time = '1' === $item_meta['time'] ? null : $item_meta['time'] . ' ';
316 $type = self::get_typos( $item_meta['time'], $item_meta['type'] );
317
318 $formatted_price = wc_price(
319 $price,
320 array(
321 'currency' => $order->get_currency(),
322 )
323 ) . ' / ' . $time . $type;
324
325 if ( $display_trial ) {
326 $trial = $item_meta['trial'];
327 $has_trial = isset( $item_meta['trial'] ) && strlen( $item_meta['trial'] ) > 2;
328
329 if ( $has_trial ) {
330 $trial_html = '<br/><small> + Got ' . $trial . ' free trial!</small>';
331 $formatted_price .= $trial_html;
332 }
333 }
334
335 return apply_filters( 'subscrpt_format_price_with_subscription', $formatted_price, $price, $subscription_id );
336 }
337
338 /**
339 * Format price with order item
340 *
341 * @param string $price Price.
342 * @param int $item_id Item Id.
343 * @param bool $display_trial display trial?.
344 *
345 * @return string
346 */
347 public static function format_price_with_order_item( $price, $item_id, $display_trial = false ) {
348 $order_id = wc_get_order_id_by_order_item_id( $item_id );
349 $order = wc_get_order( $order_id );
350
351 $item_meta = wc_get_order_item_meta( $item_id, '_subscrpt_meta', true );
352
353 if ( ! $item_meta || ! is_array( $item_meta ) ) {
354 return false;
355 }
356
357 $time = 1 === (int) $item_meta['time'] ? null : $item_meta['time'] . '-';
358 $type = self::get_typos( $item_meta['time'], $item_meta['type'], true );
359
360 $formatted_price = wc_price(
361 $price,
362 array(
363 'currency' => $order->get_currency(),
364 )
365 ) . ' / ' . $time . ucfirst( $type );
366
367 if ( $display_trial ) {
368 $has_trial = isset( $item_meta['trial'] ) && strlen( $item_meta['trial'] ) > 2;
369 $trial = $item_meta['trial'] ?? '';
370
371 if ( $has_trial ) {
372 // translators: %s: trial period.
373 $trial_html = '<br/><small> ' . sprintf( __( '+ %s free trial!', 'subscription' ), $trial ) . '</small>';
374 $formatted_price .= $trial_html;
375 }
376 }
377
378 return apply_filters( 'subscrpt_format_price_with_subscription', $formatted_price, $price, $item_id );
379 }
380
381 /**
382 * Get total subscriptions by product ID.
383 *
384 * @param int $product_id Product ID.
385 * @param string | array $status Status.
386 *
387 * @return \WP_Post | false
388 */
389 public static function get_total_subscriptions_from_product( int $product_id, $status = array( 'active', 'pending', 'expired', 'pe_cancelled', 'cancelled' ) ) {
390 $args = array(
391 'post_type' => 'subscrpt_order',
392 'post_status' => $status,
393 'fields' => 'ids',
394 'meta_query' => array(
395 array(
396 'key' => '_subscrpt_product_id',
397 'value' => $product_id,
398 ),
399 ),
400 );
401
402 $posts = get_posts( $args );
403
404 return count( $posts );
405 }
406
407 /**
408 * Process renewal on order.
409 *
410 * @param int $subscription_id Subscription Id.
411 * @param int $order_id Order Id.
412 * @param int $order_item_id Order Item Id.
413 *
414 * @return void
415 */
416 public static function process_order_renewal( $subscription_id, $order_id, $order_item_id ) {
417 global $wpdb;
418 $history_table = $wpdb->prefix . 'subscrpt_order_relation';
419
420 // Check if this is a split payment subscription
421 $payment_type = function_exists( 'subscrpt_get_payment_type' ) ? subscrpt_get_payment_type( $subscription_id ) : 'recurring';
422 $max_payments = function_exists( 'subscrpt_get_max_payments' ) ? subscrpt_get_max_payments( $subscription_id ) : 0;
423 $payments_made = function_exists( 'subscrpt_count_payments_made' ) ? subscrpt_count_payments_made( $subscription_id ) : 0;
424
425 $comment_content = '';
426 $activity_type = '';
427
428 if ( 'split_payment' === $payment_type && $max_payments ) {
429 $comment_content = sprintf(
430 /* translators: %1$s: order id, %2$d: payment number, %3$d: total payments */
431 __( 'Split payment installment %2$d of %3$d. Order %1$s created for subscription.', 'subscription' ),
432 $order_id,
433 $payments_made + 1, // +1 because this is a new renewal
434 $max_payments
435 );
436 $activity_type = __( 'Split Payment - Renewal', 'subscription' );
437 } else {
438 $comment_content = sprintf(
439 /* translators: order id. */
440 __( 'The order %s has been created for the subscription', 'subscription' ),
441 $order_id
442 );
443 $activity_type = __( 'Renewal Order', 'subscription' );
444 }
445
446 $comment_id = wp_insert_comment(
447 array(
448 'comment_author' => 'Subscription for WooCommerce',
449 'comment_content' => $comment_content,
450 'comment_post_ID' => $subscription_id,
451 'comment_type' => 'order_note',
452 )
453 );
454 update_comment_meta( $comment_id, '_subscrpt_activity', $activity_type );
455 update_comment_meta( $comment_id, '_subscrpt_activity_type', 'renewal_order' );
456
457 $wpdb->insert(
458 $history_table,
459 array(
460 'subscription_id' => $subscription_id,
461 'order_id' => $order_id,
462 'order_item_id' => $order_item_id,
463 'type' => 'renew',
464 )
465 );
466
467 // Fire action when split payment is renewed
468 do_action( 'subscrpt_split_payment_renewed', $subscription_id, $order_id, $order_item_id );
469 }
470
471 /**
472 * Process new subscription on order.
473 *
474 * @param \WC_Order_Item $order_item Order Item.
475 * @param string $post_status status.
476 * @param \WC_Product $product Product.
477 *
478 * @return int
479 */
480 public static function process_new_subscription_order( $order_item, $post_status, $product ) {
481 global $wpdb;
482 $history_table = $wpdb->prefix . 'subscrpt_order_relation';
483
484 // Prepare split payment arguments
485 $split_payment_args = array(
486 'product_id' => $product->get_id(),
487 'order_id' => $order_item->get_order_id(),
488 'order_item_id' => $order_item->get_id(),
489 'post_status' => $post_status,
490 'max_payments' => $product->get_meta( '_subscrpt_max_no_payment' ),
491 'timing_per' => $product->get_meta( '_subscrpt_timing_per' ),
492 'timing_option' => $product->get_meta( '_subscrpt_timing_option' ),
493 'price' => $product->get_price(),
494 );
495
496 // Allow modification of split payment arguments
497 $split_payment_args = apply_filters( 'subscrpt_split_payment_args', $split_payment_args, $order_item, $product );
498
499 $args = array(
500 'post_title' => 'Subscription',
501 'post_type' => 'subscrpt_order',
502 'post_status' => $split_payment_args['post_status'],
503 );
504 $subscription_id = wp_insert_post( $args );
505 wp_update_post(
506 array(
507 'ID' => $subscription_id,
508 'post_title' => "Subscription #{$subscription_id}",
509 )
510 );
511 // Check if this is a split payment subscription
512 $payment_type = $product->get_meta( '_subscrpt_payment_type' ) ?: 'recurring';
513 $max_payments = $product->get_meta( '_subscrpt_max_no_payment' );
514
515 $comment_content = '';
516 $activity_type = '';
517
518 if ( 'split_payment' === $payment_type && $max_payments ) {
519 $comment_content = sprintf(
520 /* translators: %1$s: order id, %2$d: max payments */
521 __( 'Split payment subscription created successfully. Order: %1$s. Total installments: %2$d.', 'subscription' ),
522 $order_item->get_order_id(),
523 $max_payments
524 );
525 $activity_type = __( 'Split Payment - New Subscription', 'subscription' );
526 } else {
527 $comment_content = sprintf(
528 /* translators: Order Id. */
529 __( 'Subscription successfully created. Order is %s', 'subscription' ),
530 $order_item->get_order_id()
531 );
532 $activity_type = __( 'New Subscription', 'subscription' );
533 }
534
535 $comment_id = wp_insert_comment(
536 array(
537 'comment_author' => 'Subscription for WooCommerce',
538 'comment_content' => $comment_content,
539 'comment_post_ID' => $subscription_id,
540 'comment_type' => 'order_note',
541 )
542 );
543 update_comment_meta( $comment_id, '_subscrpt_activity', $activity_type );
544 update_comment_meta( $comment_id, '_subscrpt_activity_type', 'subs_created' );
545
546 update_post_meta( $subscription_id, '_subscrpt_product_id', $product->get_id() );
547
548 $wpdb->insert(
549 $history_table,
550 array(
551 'subscription_id' => $subscription_id,
552 'order_id' => $order_item->get_order_id(),
553 'order_item_id' => $order_item->get_id(),
554 'type' => 'new',
555 )
556 );
557
558 // Fire action when split payment plan is created
559 do_action( 'subscrpt_split_payment_created', $subscription_id, $split_payment_args, $order_item );
560
561 return $subscription_id;
562 }
563
564 /**
565 * Get recurrings items from cart items.
566 *
567 * @param array $cart_items Cart items.
568 *
569 * @return array
570 */
571 public static function get_recurrs_from_cart( $cart_items ) {
572 $recurrs = array();
573 foreach ( $cart_items as $key => $cart_item ) {
574 $product = $cart_item['data'];
575 if ( $product->is_type( 'simple' ) && isset( $cart_item['subscription'] ) ) {
576 $cart_subscription = $cart_item['subscription'];
577 $type = ucfirst( $cart_subscription['type'] );
578
579 // Total amount with tax
580 $quantity = (int) $cart_item['quantity'];
581 $total_amount = wc_get_price_including_tax( $product, [ 'qty' => $quantity ] );
582 $timing_html = "<span class='wpsubs-subscription-timing'>&nbsp;/&nbsp;{$type}</span>";
583 $price_html = wc_price( (float) $total_amount ) . $timing_html;
584
585 $recurrs[ $key ] = array(
586 'trial_status' => ! is_null( $cart_subscription['trial'] ),
587 'price_html' => $price_html,
588 'start_date' => self::start_date( $cart_subscription['trial'] ),
589 'next_date' => self::next_date( ( $cart_subscription['time'] ?? 1 ) . ' ' . $cart_subscription['type'], $cart_subscription['trial'] ),
590 'can_user_cancel' => $cart_item['data']->get_meta( '_subscrpt_user_cancel' ),
591 'max_no_payment' => $cart_item['data']->get_meta( '_subscrpt_max_no_payment' ),
592 'price' => (float) $cart_subscription['per_cost'],
593 'quantity' => (int) $cart_item['quantity'],
594 );
595 }
596 }
597
598 return apply_filters( 'wpsubs_cart_recurring_items', $recurrs, $cart_items );
599 }
600
601 /**
602 * Check if the order has subscription item.
603 *
604 * @param \WC_Order|int $order Order object.
605 */
606 public static function order_has_subscription_item( $order ) {
607 if ( is_int( $order ) ) {
608 $order = wc_get_order( $order );
609 }
610
611 $is_subscription_order = false;
612 foreach ( $order->get_items() as $item ) {
613 $item_data = $item->get_data() ?? array();
614 $item_product_id = $item_data['product_id'] ?? 0;
615 $item_variation_id = $item_data['variation_id'] ?? 0;
616
617 $product_id = $item_variation_id ? $item_variation_id : $item_product_id;
618 $product = Subscription::get_subs_product( $product_id );
619
620 if ( $product && $product->is_enabled() ) {
621 $is_subscription_order = true;
622 break;
623 }
624 }
625 return $is_subscription_order;
626 }
627
628 /**
629 * Create renewal order when subscription expired. [wip]
630 *
631 * @param int $subscription_id Subscription ID.
632 * @throws \WC_Data_Exception Exception.
633 * @throws \Exception Exception.
634 */
635 public static function create_renewal_order( $subscription_id ) {
636 // Check if maximum payment limit has been reached
637 if ( subscrpt_is_max_payments_reached( $subscription_id ) ) {
638 // Mark subscription as expired due to limit reached
639 Action::status( 'expired', $subscription_id );
640
641 error_log( "WPS: Maximum payment limit reached for subscription #{$subscription_id}. No renewal order created." );
642 return false;
643 }
644
645 $order_item_id = get_post_meta( $subscription_id, '_subscrpt_order_item_id', true );
646 $order_id = wc_get_order_id_by_order_item_id( $order_item_id );
647 $old_order = self::check_order_for_renewal( $order_id );
648
649 if ( ! $old_order ) {
650 // The stored item may belong to a trashed or non-completed order (e.g. a renewal that was deleted). Walk the relation table newest-first to find the last completed order we can use as the renewal source.
651 foreach ( self::get_related_orders( $subscription_id ) as $row ) {
652 $candidate = wc_get_order( (int) ( $row->order_id ?? 0 ) );
653 if ( $candidate && 'completed' === $candidate->get_status() ) {
654 $old_order = $candidate;
655 $order_item_id = (int) ( $row->order_item_id ?? 0 );
656 break;
657 }
658 }
659 }
660
661 if ( ! $old_order ) {
662 subscrpt_write_log( "Old order not found for renewal. Skipping creating renewal order. [ Subscription ID: {$subscription_id} ]" );
663 return;
664 }
665
666 $order_item = $old_order->get_item( $order_item_id );
667 $subscription_price = (float) get_post_meta( $subscription_id, '_subscrpt_price', true );
668 $qty = $order_item->get_quantity();
669
670 // Subtract tax from per-unit subscription price if prices include tax. WC_Order will calculate tax on line total.
671 if ( wc_prices_include_tax() ) {
672 $product = ( $order_item instanceof \WC_Order_Item_Product ) ? $order_item->get_product() : null;
673 $tax_class = $product ? $product->get_tax_class() : '';
674 $tax_rates = \WC_Tax::get_rates( $tax_class );
675 $taxes = \WC_Tax::calc_inclusive_tax( $subscription_price, $tax_rates );
676 $subscription_price = $subscription_price - array_sum( $taxes );
677 }
678
679 $line_total = $subscription_price * $qty;
680 $product_args = array(
681 'name' => $order_item->get_name(),
682 'subtotal' => $line_total,
683 'total' => $line_total,
684 );
685
686 // creating new order.
687 $new_order_data = self::create_new_order_for_renewal( $old_order, $order_item, $product_args );
688 if ( ! $new_order_data ) {
689 subscrpt_write_log( "Failed to create renewal order. [ Subscription ID: {$subscription_id} ]" );
690 return;
691 }
692 $new_order = $new_order_data['order'];
693 $new_order_item_id = $new_order_data['order_item_id'];
694
695 self::create_renewal_history( $subscription_id, $new_order->get_id(), $new_order_item_id );
696 update_post_meta( $subscription_id, '_subscrpt_order_id', $new_order->get_id() );
697 update_post_meta( $subscription_id, '_subscrpt_order_item_id', $new_order_item_id );
698
699 self::clone_order_metadata( $new_order, $old_order );
700
701 // Allow modification of the renewal order before saving.
702 $new_order = apply_filters( 'subscrpt_before_saving_renewal_order', $new_order, $old_order, $subscription_id );
703
704 // Save the new order.
705 $new_order->calculate_totals();
706 $new_order->save();
707
708 if ( ! is_admin() && function_exists( 'wc_add_notice' ) && WC()->session ) {
709 $message = 'Renewal Order(#' . $new_order->get_id() . ') Created.';
710 if ( $new_order->has_status( 'pending' ) ) {
711 $message .= 'Please <a href="' . $new_order->get_checkout_payment_url() . '">Pay now</a>';
712 }
713 wc_add_notice( $message, 'success' );
714 }
715
716 do_action( 'subscrpt_after_create_renew_order', $new_order, $old_order, $subscription_id, false );
717
718 return $new_order;
719 }
720
721 /**
722 * Get subscription total price.
723 *
724 * @param int $subscription_id Subscription ID.
725 * @return float
726 */
727 public static function get_subscription_total( $subscription_id ) {
728 return (float) get_post_meta( $subscription_id, '_subscrpt_price', true );
729 }
730
731 /**
732 * Get subscription status.
733 *
734 * @param int $subscription_id Subscription ID.
735 * @return string
736 */
737 public static function get_subscription_status( $subscription_id ) {
738 return get_post_status( $subscription_id );
739 }
740
741 /**
742 * Check if subscription has status.
743 *
744 * @param int $subscription_id Subscription ID.
745 * @param string $status Status to check.
746 * @return bool
747 */
748 public static function subscription_has_status( $subscription_id, $status ) {
749 return self::get_subscription_status( $subscription_id ) === $status;
750 }
751
752 /**
753 * Check if subscription needs payment.
754 *
755 * @param int $subscription_id Subscription ID.
756 * @return bool
757 */
758 public static function subscription_needs_payment( $subscription_id ) {
759 return true; // Always true for now
760 }
761
762 /**
763 * Get product period (timing option).
764 *
765 * @param int $product_id Product ID.
766 * @return string
767 */
768 public static function get_product_period( $product_id ) {
769 $product = wc_get_product( $product_id );
770 return $product ? $product->get_meta( '_subscrpt_timing_option' ) : '';
771 }
772
773 /**
774 * Get product interval (timing per).
775 *
776 * @param int $product_id Product ID.
777 * @return int
778 */
779 public static function get_product_interval( $product_id ) {
780 $product = wc_get_product( $product_id );
781 return $product ? (int) $product->get_meta( '_subscrpt_timing_per' ) : 1;
782 }
783
784 /**
785 * Get product length (max payments).
786 *
787 * @param int $product_id Product ID.
788 * @return int
789 */
790 public static function get_product_length( $product_id ) {
791 $product = wc_get_product( $product_id );
792 return $product ? (int) $product->get_meta( '_subscrpt_max_no_payment' ) : 0;
793 }
794
795 /**
796 * Get product trial length.
797 *
798 * @param int $product_id Product ID.
799 * @return int
800 */
801 public static function get_product_trial_length( $product_id ) {
802 $product = wc_get_product( $product_id );
803 return $product ? (int) $product->get_meta( '_subscrpt_trial_timing_per' ) : 0;
804 }
805
806 /**
807 * Get product signup fee.
808 *
809 * @param int $product_id Product ID.
810 * @return float
811 */
812 public static function get_product_signup_fee( $product_id ) {
813 $product = wc_get_product( $product_id );
814 return $product ? (float) $product->get_meta( '_subscrpt_signup_fee' ) : 0.0;
815 }
816
817 /**
818 * Get first renewal payment time.
819 *
820 * @param int $product_id Product ID.
821 * @return int Timestamp
822 */
823 public static function get_first_renewal_payment_time( $product_id ) {
824 $product = wc_get_product( $product_id );
825 if ( ! $product ) {
826 return 0;
827 }
828
829 $trial_period = $product->get_meta( '_subscrpt_trial_timing_per' );
830 $trial_option = $product->get_meta( '_subscrpt_trial_timing_option' );
831
832 if ( ! empty( $trial_period ) && ! empty( $trial_option ) ) {
833 return strtotime( "+{$trial_period} {$trial_option}" );
834 }
835
836 return 0;
837 }
838
839 /**
840 * Update subscription next payment date.
841 *
842 * @param int $subscription_id Subscription ID.
843 * @param string $new_date New Date string.
844 * @return void
845 */
846 public static function update_subscription_next_payment_date( $subscription_id, $new_date ) {
847 update_post_meta( $subscription_id, '_subscrpt_next_date', strtotime( $new_date ) );
848 }
849
850 /**
851 * Cancel subscription.
852 *
853 * @param int $subscription_id Subscription ID.
854 * @return void
855 */
856 public static function cancel_subscription( $subscription_id ) {
857 Action::status( 'cancelled', $subscription_id );
858 }
859
860 /**
861 * Pause subscription.
862 *
863 * @param int $subscription_id Subscription ID.
864 * @return void
865 */
866 public static function pause_subscription( $subscription_id ) {
867 Action::status( 'on-hold', $subscription_id );
868 }
869
870 /**
871 * Resume subscription.
872 *
873 * @param int $subscription_id Subscription ID.
874 * @return void
875 */
876 public static function resume_subscription( $subscription_id ) {
877 Action::status( 'active', $subscription_id );
878 }
879
880 /**
881 * Mark subscription payment as complete.
882 *
883 * @param int $subscription_id Subscription ID.
884 * @param string $payment_id Payment/Transaction ID.
885 * @return void
886 */
887 public static function subscription_payment_complete( $subscription_id, $payment_id ) {
888 if ( 'active' !== get_post_status( $subscription_id ) ) {
889 Action::status( 'active', $subscription_id );
890 }
891
892 // Allow payment gateways to add their own comments/notes
893 do_action( 'subscrpt_subscription_payment_completed', $subscription_id, $payment_id );
894 }
895
896 /**
897 * Clone stripe metadata from old order.
898 *
899 * @param int $subscription_id Subscription Id.
900 * @param \WC_Order $old_order Old Order Object.
901 * @param \WC_Order $new_order New Order Object.
902 *
903 * @return void
904 */
905 public static function clone_stripe_metadata_for_renewal( $subscription_id, $old_order, $new_order ) {
906 $is_auto_renew = get_post_meta( $subscription_id, '_subscrpt_auto_renew', true );
907 if ( empty( $is_auto_renew ) && subscrpt_is_auto_renew_enabled() ) {
908 $is_auto_renew = true;
909 update_post_meta( $subscription_id, '_subscrpt_auto_renew', true );
910 }
911
912 $is_auto_renew = get_post_meta( $subscription_id, '_subscrpt_auto_renew', true );
913 $is_auto_renew = in_array( $is_auto_renew, array( 1, '1' ), true );
914
915 $is_global_auto_renew = get_option( 'wp_subscription_stripe_auto_renew', '1' );
916 $is_global_auto_renew = in_array( $is_global_auto_renew, array( 1, '1' ), true );
917
918 $stripe_supported_methods = Stripe::WPSUBS_SUPPORTED_METHODS;
919 $old_method = $old_order->get_payment_method();
920 $is_stripe_pm = ! empty( $old_method ) && in_array( $old_method, $stripe_supported_methods, true );
921
922 $has_stripe_meta = ! empty( $old_order->get_meta( '_stripe_customer_id' ) ) || ! empty( $old_order->get_meta( '_stripe_source_id' ) );
923
924 $stripe_enabled = ( ( $is_stripe_pm || $has_stripe_meta ) && $is_auto_renew && $is_global_auto_renew && subscrpt_is_auto_renew_enabled() );
925
926 if ( $stripe_enabled ) {
927 $new_order->update_meta_data( '_stripe_customer_id', $old_order->get_meta( '_stripe_customer_id' ) );
928 $new_order->update_meta_data( '_stripe_source_id', $old_order->get_meta( '_stripe_source_id' ) );
929 $new_order->set_payment_method( $old_order->get_payment_method() );
930 $new_order->set_payment_method_title( $old_order->get_payment_method_title() );
931
932 // Add debug log.
933 subscrpt_write_debug_log( "Stripe metadata cloned for renewal order #{$new_order->get_id()} from old order #{$old_order->get_id()}" );
934 } else {
935 subscrpt_write_log( "Stripe metadata not processed. Auto renewal may fail. [ Renewal order #{$new_order->get_id()}, Old order #{$old_order->get_id()} ]" );
936 subscrpt_write_debug_log( "Stripe metadata did not clone for renewal order #{$new_order->get_id()} from old order #{$old_order->get_id()}" );
937 }
938 }
939
940 /**
941 * Create history for renewal.
942 *
943 * @param int $subscription_id Subscription Id.
944 * @param int $new_order_id New Order Id.
945 * @param int $new_order_item_id New Order Item Id.
946 *
947 * @return void
948 */
949 public static function create_renewal_history( $subscription_id, $new_order_id, $new_order_item_id ) {
950 global $wpdb;
951 $history_table = $wpdb->prefix . 'subscrpt_order_relation';
952 $wpdb->insert(
953 $history_table,
954 array(
955 'subscription_id' => $subscription_id,
956 'order_id' => $new_order_id,
957 'order_item_id' => $new_order_item_id,
958 'type' => 'renew',
959 )
960 );
961
962 $comment_id = wp_insert_comment(
963 array(
964 'comment_author' => 'Subscription for WooCommerce',
965 'comment_content' => sprintf( 'Subscription Renewal order successfully created. order is %s', $new_order_id ),
966 'comment_post_ID' => $subscription_id,
967 'comment_type' => 'order_note',
968 )
969 );
970 update_comment_meta( $comment_id, '_subscrpt_activity', 'Renewal Order' );
971 update_comment_meta( $comment_id, '_subscrpt_activity_type', 'renewal_order' );
972 }
973
974 /**
975 * Get a subscription data.
976 *
977 * @param int $subscription_id Subscription ID.
978 * @return array|null
979 */
980 public static function get_subscription_data( int $subscription_id ): ?array {
981 if ( empty( get_post_meta( $subscription_id ) ) ) {
982 return null;
983 }
984
985 $subs_post = get_post( $subscription_id );
986 $user_id = (int) $subs_post->post_author ?? 0;
987
988 $product_id = get_post_meta( $subscription_id, '_subscrpt_product_id', true );
989 $product_id = ! empty( $product_id ) ? (int) $product_id : 0;
990
991 $variation_id = get_post_meta( $subscription_id, '_subscrpt_variation_id', true );
992 $variation_id = ! empty( $variation_id ) ? (int) $variation_id : 0;
993
994 $chk_product_id = $variation_id ? $variation_id : $product_id;
995
996 $status = get_post_status( $subscription_id );
997 $price = get_post_meta( $subscription_id, '_subscrpt_price', true );
998
999 $signup_fee = get_post_meta( $subscription_id, '_subscrpt_signup_fee', true );
1000 $signup_fee = ! empty( $signup_fee ) ? $signup_fee : 0;
1001
1002 $order_id = get_post_meta( $subscription_id, '_subscrpt_order_id', true );
1003 $order_item_id = get_post_meta( $subscription_id, '_subscrpt_order_item_id', true );
1004
1005 $can_user_cancel = in_array( get_post_meta( $subscription_id, '_subscrpt_user_cancel', true ), array( 1, '1', 'true', 'yes' ), true );
1006
1007 $start_datetime = (int) get_post_meta( $subscription_id, '_subscrpt_start_date', true );
1008 $start_date = ! empty( $start_datetime ) ? gmdate( DATE_RFC2822, $start_datetime ) : null;
1009
1010 $next_datetime = (int) get_post_meta( $subscription_id, '_subscrpt_next_date', true );
1011 $next_date = ! empty( $next_datetime ) ? gmdate( DATE_RFC2822, $next_datetime ) : null;
1012
1013 $timing_per = get_post_meta( $subscription_id, '_subscrpt_timing_per', true );
1014 $timing_per = empty( $timing_per ) ? get_post_meta( $chk_product_id, '_subscrpt_timing_per', true ) : $timing_per;
1015
1016 $timing_option = get_post_meta( $subscription_id, '_subscrpt_timing_option', true );
1017 $timing_option = empty( $timing_option ) ? get_post_meta( $chk_product_id, '_subscrpt_timing_option', true ) : $timing_option;
1018
1019 $trial_timing_per = get_post_meta( $subscription_id, '_subscrpt_trial_timing_per', true );
1020 $trial_timing_per = empty( $trial_timing_per ) ? get_post_meta( $chk_product_id, '_subscrpt_trial_timing_per', true ) : $trial_timing_per;
1021
1022 $trial_timing_option = get_post_meta( $subscription_id, '_subscrpt_trial_timing_option', true );
1023 $trial_timing_option = empty( $trial_timing_option ) ? get_post_meta( $chk_product_id, '_subscrpt_trial_timing_option', true ) : $trial_timing_option;
1024
1025 $is_auto_renew = in_array( get_post_meta( $subscription_id, '_subscrpt_auto_renew', true ), array( 1, '1', 'true', 'yes' ), true );
1026 $is_auto_renew = ! empty( $is_auto_renew ) ? $is_auto_renew : subscrpt_is_auto_renew_enabled();
1027
1028 $default_grace_period = (int) get_option( 'subscrpt_default_payment_grace_period', '7' );
1029 $default_grace_period = subscrpt_pro_activated() ? $default_grace_period : 0;
1030 $grace_end_datetime = $next_datetime + ( $default_grace_period * DAY_IN_SECONDS );
1031 $grace_end_date = gmdate( DATE_RFC2822, $grace_end_datetime );
1032 $grace_remaining_days = ceil( max( 0, $grace_end_datetime - time() ) / DAY_IN_SECONDS );
1033
1034 $subscription_data = array(
1035 'id' => $subscription_id,
1036 'status' => $status,
1037 'schedule' => array(
1038 'timing_per' => $timing_per,
1039 'timing_option' => $timing_option,
1040 ),
1041 'price' => $price,
1042 'signup_fee' => $signup_fee,
1043 'start_date' => $start_date,
1044 'next_date' => $next_date,
1045 'product' => array(
1046 'product_id' => $product_id,
1047 'variation_id' => $variation_id,
1048 ),
1049 'order' => array(
1050 'order_id' => $order_id,
1051 'order_item_id' => $order_item_id,
1052 ),
1053 'can_user_cancel' => $can_user_cancel,
1054 'is_auto_renew' => (bool) $is_auto_renew,
1055 'user_id' => $user_id,
1056 );
1057
1058 if ( ! empty( $trial_timing_per ) ) {
1059 $subscription_data['trial'] = array(
1060 'timing_per' => $trial_timing_per,
1061 'timing_option' => $trial_timing_option,
1062 );
1063 }
1064
1065 if (
1066 ! in_array( strtolower( $status ), array( 'cancelled', 'pending' ), true )
1067 && $next_datetime - time() <= 0
1068 && (int) $default_grace_period > 0
1069 ) {
1070 $subscription_data['grace_period'] = array(
1071 'remaining_days' => $grace_remaining_days,
1072 'end_date' => $grace_end_date,
1073 );
1074 }
1075
1076 return $subscription_data;
1077 }
1078
1079 /**
1080 * Get related orders of a subscription.
1081 *
1082 * @param int $subscription_id Subscription ID.
1083 * @return array
1084 */
1085 public static function get_related_orders( int $subscription_id ): array {
1086 global $wpdb;
1087 $table_name = $wpdb->prefix . 'subscrpt_order_relation';
1088
1089 // @phpcs:ignore
1090 $order_histories = $wpdb->get_results(
1091 $wpdb->prepare(
1092 'SELECT order_id, order_item_id, type FROM %i WHERE subscription_id=%d ORDER BY id DESC',
1093 array(
1094 $table_name,
1095 $subscription_id,
1096 )
1097 )
1098 );
1099
1100 return $order_histories;
1101 }
1102
1103 /**
1104 * Get parent order from subscription.
1105 *
1106 * @param int $subscription_id Subscription ID.
1107 */
1108 public static function get_parent_order( int $subscription_id ) {
1109 $related_orders = self::get_related_orders( $subscription_id );
1110 $last_order = end( $related_orders );
1111
1112 if ( ! $last_order || strtolower( $last_order->type ?? '' ) !== 'new' ) {
1113 foreach ( $related_orders as $order ) {
1114 if ( strtolower( $order->type ?? '' ) === 'new' ) {
1115 $last_order = $order;
1116 break;
1117 }
1118 }
1119 }
1120
1121 $parent_order_id = $last_order->order_id ?? 0;
1122 $parent_order = wc_get_order( $parent_order_id );
1123 return $parent_order;
1124 }
1125
1126 /**
1127 * Create new order for renewal.
1128 *
1129 * @param \WC_Order $old_order Old Order Object.
1130 * @param \WC_Order_Item_Product $order_item Old Order Item Object.
1131 * @param array $product_args Product args for add product.
1132 *
1133 * @return array|false
1134 */
1135 public static function create_new_order_for_renewal( \WC_Order $old_order, \WC_Order_Item_Product $order_item, array $product_args ) {
1136 $product = $order_item->get_product();
1137 $user_id = $old_order->get_user_id();
1138 $new_order = wc_create_order(
1139 array(
1140 'customer_id' => $user_id,
1141 'status' => 'pending',
1142 )
1143 );
1144 $product_meta = apply_filters( 'subscrpt_renewal_item_meta', wc_get_order_item_meta( $order_item->get_id(), '_subscrpt_meta', true ), $product, $order_item );
1145 $product_args = apply_filters( 'subscrpt_renewal_product_args', $product_args, $product, $order_item );
1146 if ( ! $product_args ) {
1147 return false;
1148 }
1149
1150 $new_order_item_id = $new_order->add_product(
1151 $product,
1152 $order_item->get_quantity(),
1153 $product_args
1154 );
1155 wc_update_order_item_meta(
1156 $new_order_item_id,
1157 '_subscrpt_meta',
1158 array(
1159 'time' => $product_meta['time'],
1160 'type' => $product_meta['type'],
1161 'trial' => null,
1162 )
1163 );
1164
1165 // Add debug log.
1166 subscrpt_write_debug_log( "Renewal order #{$new_order->get_id()} created for old order #{$old_order->get_id()}" );
1167
1168 return array(
1169 'order' => $new_order,
1170 'order_item_id' => $new_order_item_id,
1171 );
1172 }
1173
1174 /**
1175 * Check if old order is completed or deleted!
1176 *
1177 * @param mixed $old_order_id Old Order Id.
1178 *
1179 * @return \WC_Order|false
1180 */
1181 public static function check_order_for_renewal( $old_order_id ) {
1182 $old_order = wc_get_order( $old_order_id );
1183 if ( ! $old_order || 'completed' !== $old_order->get_status() ) {
1184 if ( ! is_admin() && function_exists( 'wc_add_notice' ) && WC()->session ) {
1185 return wc_add_notice( __( 'Subscription renewal isn\'t possible due to previous order not completed or deletion.', 'subscription' ), 'error' );
1186 }
1187 return false;
1188 }
1189
1190 return $old_order;
1191 }
1192
1193 /**
1194 * Get delivery info from order.
1195 *
1196 * @param \WC_Order $order Order object.
1197 * @return array
1198 */
1199 public static function get_delivery_info_from_order( \WC_Order $order ) {
1200 $customer_id = $order->get_customer_id();
1201 $customer = new \WC_Customer( $customer_id );
1202 $email = $customer->get_email();
1203
1204 // Billing info (get from order first, if empty get from customer).
1205 $billing_first_name = ! empty( $order->get_billing_first_name() ) ? $order->get_billing_first_name() : $customer->get_billing_first_name();
1206 $billing_last_name = ! empty( $order->get_billing_last_name() ) ? $order->get_billing_last_name() : $customer->get_billing_last_name();
1207 $billing_email = ! empty( $order->get_billing_email() ) ? $order->get_billing_email() : $customer->get_billing_email();
1208 $billing_phone = ! empty( $order->get_billing_phone() ) ? $order->get_billing_phone() : $customer->get_billing_phone();
1209 $billing_company = ! empty( $order->get_billing_company() ) ? $order->get_billing_company() : $customer->get_billing_company();
1210
1211 $billing_city = ! empty( $order->get_billing_city() ) ? $order->get_billing_city() : $customer->get_billing_city();
1212 $billing_state = ! empty( $order->get_billing_state() ) ? $order->get_billing_state() : $customer->get_billing_state();
1213 $billing_country = ! empty( $order->get_billing_country() ) ? $order->get_billing_country() : $customer->get_billing_country();
1214 $billing_postcode = ! empty( $order->get_billing_postcode() ) ? $order->get_billing_postcode() : $customer->get_billing_postcode();
1215 $billing_address_1 = ! empty( $order->get_billing_address_1() ) ? $order->get_billing_address_1() : $customer->get_billing_address_1();
1216 $billing_address_2 = ! empty( $order->get_billing_address_2() ) ? $order->get_billing_address_2() : $customer->get_billing_address_2();
1217
1218 // Shipping info (get from order first, if empty get from customer).
1219 $shipping_first_name = ! empty( $order->get_shipping_first_name() ) ? $order->get_shipping_first_name() : $customer->get_shipping_first_name();
1220 $shipping_last_name = ! empty( $order->get_shipping_last_name() ) ? $order->get_shipping_last_name() : $customer->get_shipping_last_name();
1221 $shipping_phone = ! empty( $order->get_shipping_phone() ) ? $order->get_shipping_phone() : $customer->get_shipping_phone();
1222 $shipping_company = ! empty( $order->get_shipping_company() ) ? $order->get_shipping_company() : $customer->get_shipping_company();
1223
1224 $shipping_city = ! empty( $order->get_shipping_city() ) ? $order->get_shipping_city() : $customer->get_shipping_city();
1225 $shipping_state = ! empty( $order->get_shipping_state() ) ? $order->get_shipping_state() : $customer->get_shipping_state();
1226 $shipping_country = ! empty( $order->get_shipping_country() ) ? $order->get_shipping_country() : $customer->get_shipping_country();
1227 $shipping_postcode = ! empty( $order->get_shipping_postcode() ) ? $order->get_shipping_postcode() : $customer->get_shipping_postcode();
1228 $shipping_address_1 = ! empty( $order->get_shipping_address_1() ) ? $order->get_shipping_address_1() : $customer->get_shipping_address_1();
1229 $shipping_address_2 = ! empty( $order->get_shipping_address_2() ) ? $order->get_shipping_address_2() : $customer->get_shipping_address_2();
1230
1231 $order_meta = [
1232 'customer_id' => $order->get_customer_id(),
1233 'email' => $email,
1234 'billing' => [
1235 'first_name' => $billing_first_name,
1236 'last_name' => $billing_last_name,
1237 'email' => $billing_email,
1238 'phone' => $billing_phone,
1239 'company' => $billing_company,
1240 'city' => $billing_city,
1241 'state' => $billing_state,
1242 'country' => $billing_country,
1243 'postcode' => $billing_postcode,
1244 'address_1' => $billing_address_1,
1245 'address_2' => $billing_address_2,
1246 ],
1247 'shipping' => [
1248 'first_name' => $shipping_first_name,
1249 'last_name' => $shipping_last_name,
1250 'phone' => $shipping_phone,
1251 'company' => $shipping_company,
1252 'city' => $shipping_city,
1253 'state' => $shipping_state,
1254 'country' => $shipping_country,
1255 'postcode' => $shipping_postcode,
1256 'address_1' => $shipping_address_1,
1257 'address_2' => $shipping_address_2,
1258 ],
1259 ];
1260
1261 return $order_meta;
1262 }
1263
1264 /**
1265 * Set delivery info to order.
1266 *
1267 * @param \WC_Order $order Order object.
1268 * @param array $order_meta Order meta data.
1269 */
1270 public static function set_delivery_info_to_order( \WC_Order $order, array $order_meta ) {
1271 // Set Billing Info.
1272 $order->set_billing_first_name( $order_meta['billing']['first_name'] ?? '' );
1273 $order->set_billing_last_name( $order_meta['billing']['last_name'] ?? '' );
1274 $order->set_billing_email( $order_meta['billing']['email'] ?? '' );
1275 $order->set_billing_phone( $order_meta['billing']['phone'] ?? '' );
1276 $order->set_billing_company( $order_meta['billing']['company'] ?? '' );
1277 $order->set_billing_city( $order_meta['billing']['city'] ?? '' );
1278 $order->set_billing_state( $order_meta['billing']['state'] ?? '' );
1279 $order->set_billing_country( $order_meta['billing']['country'] ?? '' );
1280 $order->set_billing_postcode( $order_meta['billing']['postcode'] ?? '' );
1281 $order->set_billing_address_1( $order_meta['billing']['address_1'] ?? '' );
1282 $order->set_billing_address_2( $order_meta['billing']['address_2'] ?? '' );
1283
1284 // Set Shipping Info.
1285 $order->set_shipping_first_name( $order_meta['shipping']['first_name'] ?? '' );
1286 $order->set_shipping_last_name( $order_meta['shipping']['last_name'] ?? '' );
1287 $order->set_shipping_phone( $order_meta['shipping']['phone'] ?? '' );
1288 $order->set_shipping_company( $order_meta['shipping']['company'] ?? '' );
1289 $order->set_shipping_city( $order_meta['shipping']['city'] ?? '' );
1290 $order->set_shipping_state( $order_meta['shipping']['state'] ?? '' );
1291 $order->set_shipping_country( $order_meta['shipping']['country'] ?? '' );
1292 $order->set_shipping_postcode( $order_meta['shipping']['postcode'] ?? '' );
1293 $order->set_shipping_address_1( $order_meta['shipping']['address_1'] ?? '' );
1294 $order->set_shipping_address_2( $order_meta['shipping']['address_2'] ?? '' );
1295 }
1296
1297 /**
1298 * Save meta-data from old order
1299 *
1300 * @param \WC_Order $new_order new order object.
1301 * @param \WC_Order $old_order old order object.
1302 *
1303 * @return void
1304 */
1305 public static function clone_order_metadata( $new_order, $old_order ) {
1306 // Set customer and currency info.
1307 $new_order->set_customer_id( $old_order->get_customer_id() );
1308 $new_order->set_currency( $old_order->get_currency() );
1309
1310 // Get delivery info from old order.
1311 $order_meta = self::get_delivery_info_from_order( $old_order );
1312
1313 // Check for any missing information.
1314 $missing_billing_info = true;
1315 foreach ( $order_meta['billing'] as $key => $value ) {
1316 if ( ! empty( $value ) ) {
1317 $missing_billing_info = false;
1318 break;
1319 }
1320 }
1321 $missing_shipping_info = true;
1322 foreach ( $order_meta['shipping'] as $key => $value ) {
1323 if ( ! empty( $value ) ) {
1324 $missing_shipping_info = false;
1325 break;
1326 }
1327 }
1328 $missing_info = $missing_billing_info || $missing_shipping_info;
1329
1330 // Get info from the parent order if missing.
1331 if ( $missing_info ) {
1332 $subscription = self::get_subscriptions_from_order( $old_order->get_id() );
1333 $subscription = reset( $subscription );
1334 $subscription_id = ! empty( $subscription ) ? $subscription->subscription_id : 0;
1335
1336 subscrpt_write_log( "Missing delivery info in old order #{$old_order->get_id()} for subscription #{$subscription_id}. Trying to get from parent order." );
1337
1338 $parent_order = self::get_parent_order( $subscription_id );
1339 if ( ! empty( $parent_order ) ) {
1340 $order_meta = self::get_delivery_info_from_order( $parent_order );
1341 }
1342 }
1343
1344 // Set delivery info to new order.
1345 self::set_delivery_info_to_order( $new_order, $order_meta );
1346 }
1347 }
1348
1349 // HPOS: All order data access below uses WooCommerce CRUD and is HPOS compatible.
1350