PluginProbe
Subscriptions for WooCommerce with Stripe Recurring Payments / 1.10.1
Subscriptions for WooCommerce with Stripe Recurring Payments v1.10.1
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.10.1, at includes/Illuminate/Helper.php

1,351 lines 46.0 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 * @return false|\WC_Order Renewal order object or false on failure.
633 * @throws \WC_Data_Exception Exception.
634 * @throws \Exception Exception.
635 */
636 public static function create_renewal_order( $subscription_id ) {
637 // Check if maximum payment limit has been reached
638 if ( subscrpt_is_max_payments_reached( $subscription_id ) ) {
639 // Mark subscription as expired due to limit reached
640 Action::status( 'expired', $subscription_id );
641
642 error_log( "WPS: Maximum payment limit reached for subscription #{$subscription_id}. No renewal order created." );
643 return false;
644 }
645
646 $order_item_id = get_post_meta( $subscription_id, '_subscrpt_order_item_id', true );
647 $order_id = wc_get_order_id_by_order_item_id( $order_item_id );
648 $old_order = self::check_order_for_renewal( $order_id );
649
650 if ( ! $old_order ) {
651 // 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.
652 foreach ( self::get_related_orders( $subscription_id ) as $row ) {
653 $candidate = wc_get_order( (int) ( $row->order_id ?? 0 ) );
654 if ( $candidate && 'completed' === $candidate->get_status() ) {
655 $old_order = $candidate;
656 $order_item_id = (int) ( $row->order_item_id ?? 0 );
657 break;
658 }
659 }
660 }
661
662 if ( ! $old_order ) {
663 subscrpt_write_log( "Old order not found for renewal. Skipping creating renewal order. [ Subscription ID: {$subscription_id} ]" );
664 return false;
665 }
666
667 $order_item = $old_order->get_item( $order_item_id );
668 $subscription_price = (float) get_post_meta( $subscription_id, '_subscrpt_price', true );
669 $qty = $order_item->get_quantity();
670
671 // Subtract tax from per-unit subscription price if prices include tax. WC_Order will calculate tax on line total.
672 if ( wc_prices_include_tax() ) {
673 $product = ( $order_item instanceof \WC_Order_Item_Product ) ? $order_item->get_product() : null;
674 $tax_class = $product ? $product->get_tax_class() : '';
675 $tax_rates = \WC_Tax::get_rates( $tax_class );
676 $taxes = \WC_Tax::calc_inclusive_tax( $subscription_price, $tax_rates );
677 $subscription_price = $subscription_price - array_sum( $taxes );
678 }
679
680 $line_total = $subscription_price * $qty;
681 $product_args = array(
682 'name' => $order_item->get_name(),
683 'subtotal' => $line_total,
684 'total' => $line_total,
685 );
686
687 // creating new order.
688 $new_order_data = self::create_new_order_for_renewal( $old_order, $order_item, $product_args );
689 if ( ! $new_order_data ) {
690 subscrpt_write_log( "Failed to create renewal order. [ Subscription ID: {$subscription_id} ]" );
691 return false;
692 }
693 $new_order = $new_order_data['order'];
694 $new_order_item_id = $new_order_data['order_item_id'];
695
696 self::create_renewal_history( $subscription_id, $new_order->get_id(), $new_order_item_id );
697 update_post_meta( $subscription_id, '_subscrpt_order_id', $new_order->get_id() );
698 update_post_meta( $subscription_id, '_subscrpt_order_item_id', $new_order_item_id );
699
700 self::clone_order_metadata( $new_order, $old_order );
701
702 // Allow modification of the renewal order before saving.
703 $new_order = apply_filters( 'subscrpt_before_saving_renewal_order', $new_order, $old_order, $subscription_id );
704
705 // Save the new order.
706 $new_order->calculate_totals();
707 $new_order->save();
708
709 if ( ! is_admin() && function_exists( 'wc_add_notice' ) && WC()->session ) {
710 $message = 'Renewal Order(#' . $new_order->get_id() . ') Created.';
711 if ( $new_order->has_status( 'pending' ) ) {
712 $message .= 'Please <a href="' . $new_order->get_checkout_payment_url() . '">Pay now</a>';
713 }
714 wc_add_notice( $message, 'success' );
715 }
716
717 do_action( 'subscrpt_after_create_renew_order', $new_order, $old_order, $subscription_id, false );
718
719 return $new_order;
720 }
721
722 /**
723 * Get subscription total price.
724 *
725 * @param int $subscription_id Subscription ID.
726 * @return float
727 */
728 public static function get_subscription_total( $subscription_id ) {
729 return (float) get_post_meta( $subscription_id, '_subscrpt_price', true );
730 }
731
732 /**
733 * Get subscription status.
734 *
735 * @param int $subscription_id Subscription ID.
736 * @return string
737 */
738 public static function get_subscription_status( $subscription_id ) {
739 return get_post_status( $subscription_id );
740 }
741
742 /**
743 * Check if subscription has status.
744 *
745 * @param int $subscription_id Subscription ID.
746 * @param string $status Status to check.
747 * @return bool
748 */
749 public static function subscription_has_status( $subscription_id, $status ) {
750 return self::get_subscription_status( $subscription_id ) === $status;
751 }
752
753 /**
754 * Check if subscription needs payment.
755 *
756 * @param int $subscription_id Subscription ID.
757 * @return bool
758 */
759 public static function subscription_needs_payment( $subscription_id ) {
760 return true; // Always true for now
761 }
762
763 /**
764 * Get product period (timing option).
765 *
766 * @param int $product_id Product ID.
767 * @return string
768 */
769 public static function get_product_period( $product_id ) {
770 $product = wc_get_product( $product_id );
771 return $product ? $product->get_meta( '_subscrpt_timing_option' ) : '';
772 }
773
774 /**
775 * Get product interval (timing per).
776 *
777 * @param int $product_id Product ID.
778 * @return int
779 */
780 public static function get_product_interval( $product_id ) {
781 $product = wc_get_product( $product_id );
782 return $product ? (int) $product->get_meta( '_subscrpt_timing_per' ) : 1;
783 }
784
785 /**
786 * Get product length (max payments).
787 *
788 * @param int $product_id Product ID.
789 * @return int
790 */
791 public static function get_product_length( $product_id ) {
792 $product = wc_get_product( $product_id );
793 return $product ? (int) $product->get_meta( '_subscrpt_max_no_payment' ) : 0;
794 }
795
796 /**
797 * Get product trial length.
798 *
799 * @param int $product_id Product ID.
800 * @return int
801 */
802 public static function get_product_trial_length( $product_id ) {
803 $product = wc_get_product( $product_id );
804 return $product ? (int) $product->get_meta( '_subscrpt_trial_timing_per' ) : 0;
805 }
806
807 /**
808 * Get product signup fee.
809 *
810 * @param int $product_id Product ID.
811 * @return float
812 */
813 public static function get_product_signup_fee( $product_id ) {
814 $product = wc_get_product( $product_id );
815 return $product ? (float) $product->get_meta( '_subscrpt_signup_fee' ) : 0.0;
816 }
817
818 /**
819 * Get first renewal payment time.
820 *
821 * @param int $product_id Product ID.
822 * @return int Timestamp
823 */
824 public static function get_first_renewal_payment_time( $product_id ) {
825 $product = wc_get_product( $product_id );
826 if ( ! $product ) {
827 return 0;
828 }
829
830 $trial_period = $product->get_meta( '_subscrpt_trial_timing_per' );
831 $trial_option = $product->get_meta( '_subscrpt_trial_timing_option' );
832
833 if ( ! empty( $trial_period ) && ! empty( $trial_option ) ) {
834 return strtotime( "+{$trial_period} {$trial_option}" );
835 }
836
837 return 0;
838 }
839
840 /**
841 * Update subscription next payment date.
842 *
843 * @param int $subscription_id Subscription ID.
844 * @param string $new_date New Date string.
845 * @return void
846 */
847 public static function update_subscription_next_payment_date( $subscription_id, $new_date ) {
848 update_post_meta( $subscription_id, '_subscrpt_next_date', strtotime( $new_date ) );
849 }
850
851 /**
852 * Cancel subscription.
853 *
854 * @param int $subscription_id Subscription ID.
855 * @return void
856 */
857 public static function cancel_subscription( $subscription_id ) {
858 Action::status( 'cancelled', $subscription_id );
859 }
860
861 /**
862 * Pause subscription.
863 *
864 * @param int $subscription_id Subscription ID.
865 * @return void
866 */
867 public static function pause_subscription( $subscription_id ) {
868 Action::status( 'on-hold', $subscription_id );
869 }
870
871 /**
872 * Resume subscription.
873 *
874 * @param int $subscription_id Subscription ID.
875 * @return void
876 */
877 public static function resume_subscription( $subscription_id ) {
878 Action::status( 'active', $subscription_id );
879 }
880
881 /**
882 * Mark subscription payment as complete.
883 *
884 * @param int $subscription_id Subscription ID.
885 * @param string $payment_id Payment/Transaction ID.
886 * @return void
887 */
888 public static function subscription_payment_complete( $subscription_id, $payment_id ) {
889 if ( 'active' !== get_post_status( $subscription_id ) ) {
890 Action::status( 'active', $subscription_id );
891 }
892
893 // Allow payment gateways to add their own comments/notes
894 do_action( 'subscrpt_subscription_payment_completed', $subscription_id, $payment_id );
895 }
896
897 /**
898 * Clone stripe metadata from old order.
899 *
900 * @param int $subscription_id Subscription Id.
901 * @param \WC_Order $old_order Old Order Object.
902 * @param \WC_Order $new_order New Order Object.
903 *
904 * @return void
905 */
906 public static function clone_stripe_metadata_for_renewal( $subscription_id, $old_order, $new_order ) {
907 $is_auto_renew = get_post_meta( $subscription_id, '_subscrpt_auto_renew', true );
908 if ( empty( $is_auto_renew ) && subscrpt_is_auto_renew_enabled() ) {
909 $is_auto_renew = true;
910 update_post_meta( $subscription_id, '_subscrpt_auto_renew', true );
911 }
912
913 $is_auto_renew = get_post_meta( $subscription_id, '_subscrpt_auto_renew', true );
914 $is_auto_renew = in_array( $is_auto_renew, array( 1, '1' ), true );
915
916 $is_global_auto_renew = get_option( 'wp_subscription_stripe_auto_renew', '1' );
917 $is_global_auto_renew = in_array( $is_global_auto_renew, array( 1, '1' ), true );
918
919 $stripe_supported_methods = Stripe::WPSUBS_SUPPORTED_METHODS;
920 $old_method = $old_order->get_payment_method();
921 $is_stripe_pm = ! empty( $old_method ) && in_array( $old_method, $stripe_supported_methods, true );
922
923 $has_stripe_meta = ! empty( $old_order->get_meta( '_stripe_customer_id' ) ) || ! empty( $old_order->get_meta( '_stripe_source_id' ) );
924
925 $stripe_enabled = ( ( $is_stripe_pm || $has_stripe_meta ) && $is_auto_renew && $is_global_auto_renew && subscrpt_is_auto_renew_enabled() );
926
927 if ( $stripe_enabled ) {
928 $new_order->update_meta_data( '_stripe_customer_id', $old_order->get_meta( '_stripe_customer_id' ) );
929 $new_order->update_meta_data( '_stripe_source_id', $old_order->get_meta( '_stripe_source_id' ) );
930 $new_order->set_payment_method( $old_order->get_payment_method() );
931 $new_order->set_payment_method_title( $old_order->get_payment_method_title() );
932
933 // Add debug log.
934 subscrpt_write_debug_log( "Stripe metadata cloned for renewal order #{$new_order->get_id()} from old order #{$old_order->get_id()}" );
935 } else {
936 subscrpt_write_log( "Stripe metadata not processed. Auto renewal may fail. [ Renewal order #{$new_order->get_id()}, Old order #{$old_order->get_id()} ]" );
937 subscrpt_write_debug_log( "Stripe metadata did not clone for renewal order #{$new_order->get_id()} from old order #{$old_order->get_id()}" );
938 }
939 }
940
941 /**
942 * Create history for renewal.
943 *
944 * @param int $subscription_id Subscription Id.
945 * @param int $new_order_id New Order Id.
946 * @param int $new_order_item_id New Order Item Id.
947 *
948 * @return void
949 */
950 public static function create_renewal_history( $subscription_id, $new_order_id, $new_order_item_id ) {
951 global $wpdb;
952 $history_table = $wpdb->prefix . 'subscrpt_order_relation';
953 $wpdb->insert(
954 $history_table,
955 array(
956 'subscription_id' => $subscription_id,
957 'order_id' => $new_order_id,
958 'order_item_id' => $new_order_item_id,
959 'type' => 'renew',
960 )
961 );
962
963 $comment_id = wp_insert_comment(
964 array(
965 'comment_author' => 'Subscription for WooCommerce',
966 'comment_content' => sprintf( 'Subscription Renewal order successfully created. order is %s', $new_order_id ),
967 'comment_post_ID' => $subscription_id,
968 'comment_type' => 'order_note',
969 )
970 );
971 update_comment_meta( $comment_id, '_subscrpt_activity', 'Renewal Order' );
972 update_comment_meta( $comment_id, '_subscrpt_activity_type', 'renewal_order' );
973 }
974
975 /**
976 * Get a subscription data.
977 *
978 * @param int $subscription_id Subscription ID.
979 * @return array|null
980 */
981 public static function get_subscription_data( int $subscription_id ): ?array {
982 if ( empty( get_post_meta( $subscription_id ) ) ) {
983 return null;
984 }
985
986 $subs_post = get_post( $subscription_id );
987 $user_id = ! empty( $subs_post ) ? (int) $subs_post->post_author : 0;
988
989 $product_id = get_post_meta( $subscription_id, '_subscrpt_product_id', true );
990 $product_id = ! empty( $product_id ) ? (int) $product_id : 0;
991
992 $variation_id = get_post_meta( $subscription_id, '_subscrpt_variation_id', true );
993 $variation_id = ! empty( $variation_id ) ? (int) $variation_id : 0;
994
995 $chk_product_id = $variation_id ? $variation_id : $product_id;
996
997 $status = get_post_status( $subscription_id ); // pending, active, cancelled, pe_cancelled, expired
998 $price = get_post_meta( $subscription_id, '_subscrpt_price', true );
999
1000 $signup_fee = get_post_meta( $subscription_id, '_subscrpt_signup_fee', true );
1001 $signup_fee = ! empty( $signup_fee ) ? $signup_fee : 0;
1002
1003 $order_id = get_post_meta( $subscription_id, '_subscrpt_order_id', true );
1004 $order_item_id = get_post_meta( $subscription_id, '_subscrpt_order_item_id', true );
1005
1006 $can_user_cancel = in_array( get_post_meta( $subscription_id, '_subscrpt_user_cancel', true ), array( 1, '1', 'true', 'yes' ), true );
1007
1008 $start_datetime = (int) get_post_meta( $subscription_id, '_subscrpt_start_date', true );
1009 $start_date = ! empty( $start_datetime ) ? gmdate( DATE_RFC2822, $start_datetime ) : null;
1010
1011 $next_datetime = (int) get_post_meta( $subscription_id, '_subscrpt_next_date', true );
1012 $next_date = ! empty( $next_datetime ) ? gmdate( DATE_RFC2822, $next_datetime ) : null;
1013
1014 $timing_per = get_post_meta( $subscription_id, '_subscrpt_timing_per', true );
1015 $timing_per = empty( $timing_per ) ? get_post_meta( $chk_product_id, '_subscrpt_timing_per', true ) : $timing_per;
1016
1017 $timing_option = get_post_meta( $subscription_id, '_subscrpt_timing_option', true );
1018 $timing_option = empty( $timing_option ) ? get_post_meta( $chk_product_id, '_subscrpt_timing_option', true ) : $timing_option;
1019
1020 $trial_timing_per = get_post_meta( $subscription_id, '_subscrpt_trial_timing_per', true );
1021 $trial_timing_per = empty( $trial_timing_per ) ? get_post_meta( $chk_product_id, '_subscrpt_trial_timing_per', true ) : $trial_timing_per;
1022
1023 $trial_timing_option = get_post_meta( $subscription_id, '_subscrpt_trial_timing_option', true );
1024 $trial_timing_option = empty( $trial_timing_option ) ? get_post_meta( $chk_product_id, '_subscrpt_trial_timing_option', true ) : $trial_timing_option;
1025
1026 $is_auto_renew = in_array( get_post_meta( $subscription_id, '_subscrpt_auto_renew', true ), array( 1, '1', 'true', 'yes' ), true );
1027 $is_auto_renew = ! empty( $is_auto_renew ) ? $is_auto_renew : subscrpt_is_auto_renew_enabled();
1028
1029 $default_grace_period = (int) get_option( 'subscrpt_default_payment_grace_period', '7' );
1030 $default_grace_period = subscrpt_pro_activated() ? $default_grace_period : 0;
1031 $grace_end_datetime = $next_datetime + ( $default_grace_period * DAY_IN_SECONDS );
1032 $grace_end_date = gmdate( DATE_RFC2822, $grace_end_datetime );
1033 $grace_remaining_days = ceil( max( 0, $grace_end_datetime - time() ) / DAY_IN_SECONDS );
1034
1035 $subscription_data = array(
1036 'id' => $subscription_id,
1037 'status' => $status,
1038 'schedule' => array(
1039 'timing_per' => $timing_per,
1040 'timing_option' => $timing_option,
1041 ),
1042 'price' => $price,
1043 'signup_fee' => $signup_fee,
1044 'start_date' => $start_date,
1045 'next_date' => $next_date,
1046 'product' => array(
1047 'product_id' => $product_id,
1048 'variation_id' => $variation_id,
1049 ),
1050 'order' => array(
1051 'order_id' => $order_id,
1052 'order_item_id' => $order_item_id,
1053 ),
1054 'can_user_cancel' => $can_user_cancel,
1055 'is_auto_renew' => (bool) $is_auto_renew,
1056 'user_id' => $user_id,
1057 );
1058
1059 if ( ! empty( $trial_timing_per ) ) {
1060 $subscription_data['trial'] = array(
1061 'timing_per' => $trial_timing_per,
1062 'timing_option' => $trial_timing_option,
1063 );
1064 }
1065
1066 if (
1067 ! in_array( strtolower( $status ), array( 'cancelled', 'pending' ), true )
1068 && $next_datetime - time() <= 0
1069 && (int) $default_grace_period > 0
1070 ) {
1071 $subscription_data['grace_period'] = array(
1072 'remaining_days' => $grace_remaining_days,
1073 'end_date' => $grace_end_date,
1074 );
1075 }
1076
1077 return $subscription_data;
1078 }
1079
1080 /**
1081 * Get related orders of a subscription.
1082 *
1083 * @param int $subscription_id Subscription ID.
1084 * @return array
1085 */
1086 public static function get_related_orders( int $subscription_id ): array {
1087 global $wpdb;
1088 $table_name = $wpdb->prefix . 'subscrpt_order_relation';
1089
1090 // @phpcs:ignore
1091 $order_histories = $wpdb->get_results(
1092 $wpdb->prepare(
1093 'SELECT order_id, order_item_id, type FROM %i WHERE subscription_id=%d ORDER BY id DESC',
1094 array(
1095 $table_name,
1096 $subscription_id,
1097 )
1098 )
1099 );
1100
1101 return $order_histories;
1102 }
1103
1104 /**
1105 * Get parent order from subscription.
1106 *
1107 * @param int $subscription_id Subscription ID.
1108 */
1109 public static function get_parent_order( int $subscription_id ) {
1110 $related_orders = self::get_related_orders( $subscription_id );
1111 $last_order = end( $related_orders );
1112
1113 if ( ! $last_order || strtolower( $last_order->type ?? '' ) !== 'new' ) {
1114 foreach ( $related_orders as $order ) {
1115 if ( strtolower( $order->type ?? '' ) === 'new' ) {
1116 $last_order = $order;
1117 break;
1118 }
1119 }
1120 }
1121
1122 $parent_order_id = $last_order->order_id ?? 0;
1123 $parent_order = wc_get_order( $parent_order_id );
1124 return $parent_order;
1125 }
1126
1127 /**
1128 * Create new order for renewal.
1129 *
1130 * @param \WC_Order $old_order Old Order Object.
1131 * @param \WC_Order_Item_Product $order_item Old Order Item Object.
1132 * @param array $product_args Product args for add product.
1133 *
1134 * @return array|false
1135 */
1136 public static function create_new_order_for_renewal( \WC_Order $old_order, \WC_Order_Item_Product $order_item, array $product_args ) {
1137 $product = $order_item->get_product();
1138 $user_id = $old_order->get_user_id();
1139 $new_order = wc_create_order(
1140 array(
1141 'customer_id' => $user_id,
1142 'status' => 'pending',
1143 )
1144 );
1145 $product_meta = apply_filters( 'subscrpt_renewal_item_meta', wc_get_order_item_meta( $order_item->get_id(), '_subscrpt_meta', true ), $product, $order_item );
1146 $product_args = apply_filters( 'subscrpt_renewal_product_args', $product_args, $product, $order_item );
1147 if ( ! $product_args ) {
1148 return false;
1149 }
1150
1151 $new_order_item_id = $new_order->add_product(
1152 $product,
1153 $order_item->get_quantity(),
1154 $product_args
1155 );
1156 wc_update_order_item_meta(
1157 $new_order_item_id,
1158 '_subscrpt_meta',
1159 array(
1160 'time' => $product_meta['time'],
1161 'type' => $product_meta['type'],
1162 'trial' => null,
1163 )
1164 );
1165
1166 // Add debug log.
1167 subscrpt_write_debug_log( "Renewal order #{$new_order->get_id()} created for old order #{$old_order->get_id()}" );
1168
1169 return array(
1170 'order' => $new_order,
1171 'order_item_id' => $new_order_item_id,
1172 );
1173 }
1174
1175 /**
1176 * Check if old order is completed or deleted!
1177 *
1178 * @param mixed $old_order_id Old Order Id.
1179 *
1180 * @return \WC_Order|false
1181 */
1182 public static function check_order_for_renewal( $old_order_id ) {
1183 $old_order = wc_get_order( $old_order_id );
1184 if ( ! $old_order || 'completed' !== $old_order->get_status() ) {
1185 if ( ! is_admin() && function_exists( 'wc_add_notice' ) && WC()->session ) {
1186 return wc_add_notice( __( 'Subscription renewal isn\'t possible due to previous order not completed or deletion.', 'subscription' ), 'error' );
1187 }
1188 return false;
1189 }
1190
1191 return $old_order;
1192 }
1193
1194 /**
1195 * Get delivery info from order.
1196 *
1197 * @param \WC_Order $order Order object.
1198 * @return array
1199 */
1200 public static function get_delivery_info_from_order( \WC_Order $order ) {
1201 $customer_id = $order->get_customer_id();
1202 $customer = new \WC_Customer( $customer_id );
1203 $email = $customer->get_email();
1204
1205 // Billing info (get from order first, if empty get from customer).
1206 $billing_first_name = ! empty( $order->get_billing_first_name() ) ? $order->get_billing_first_name() : $customer->get_billing_first_name();
1207 $billing_last_name = ! empty( $order->get_billing_last_name() ) ? $order->get_billing_last_name() : $customer->get_billing_last_name();
1208 $billing_email = ! empty( $order->get_billing_email() ) ? $order->get_billing_email() : $customer->get_billing_email();
1209 $billing_phone = ! empty( $order->get_billing_phone() ) ? $order->get_billing_phone() : $customer->get_billing_phone();
1210 $billing_company = ! empty( $order->get_billing_company() ) ? $order->get_billing_company() : $customer->get_billing_company();
1211
1212 $billing_city = ! empty( $order->get_billing_city() ) ? $order->get_billing_city() : $customer->get_billing_city();
1213 $billing_state = ! empty( $order->get_billing_state() ) ? $order->get_billing_state() : $customer->get_billing_state();
1214 $billing_country = ! empty( $order->get_billing_country() ) ? $order->get_billing_country() : $customer->get_billing_country();
1215 $billing_postcode = ! empty( $order->get_billing_postcode() ) ? $order->get_billing_postcode() : $customer->get_billing_postcode();
1216 $billing_address_1 = ! empty( $order->get_billing_address_1() ) ? $order->get_billing_address_1() : $customer->get_billing_address_1();
1217 $billing_address_2 = ! empty( $order->get_billing_address_2() ) ? $order->get_billing_address_2() : $customer->get_billing_address_2();
1218
1219 // Shipping info (get from order first, if empty get from customer).
1220 $shipping_first_name = ! empty( $order->get_shipping_first_name() ) ? $order->get_shipping_first_name() : $customer->get_shipping_first_name();
1221 $shipping_last_name = ! empty( $order->get_shipping_last_name() ) ? $order->get_shipping_last_name() : $customer->get_shipping_last_name();
1222 $shipping_phone = ! empty( $order->get_shipping_phone() ) ? $order->get_shipping_phone() : $customer->get_shipping_phone();
1223 $shipping_company = ! empty( $order->get_shipping_company() ) ? $order->get_shipping_company() : $customer->get_shipping_company();
1224
1225 $shipping_city = ! empty( $order->get_shipping_city() ) ? $order->get_shipping_city() : $customer->get_shipping_city();
1226 $shipping_state = ! empty( $order->get_shipping_state() ) ? $order->get_shipping_state() : $customer->get_shipping_state();
1227 $shipping_country = ! empty( $order->get_shipping_country() ) ? $order->get_shipping_country() : $customer->get_shipping_country();
1228 $shipping_postcode = ! empty( $order->get_shipping_postcode() ) ? $order->get_shipping_postcode() : $customer->get_shipping_postcode();
1229 $shipping_address_1 = ! empty( $order->get_shipping_address_1() ) ? $order->get_shipping_address_1() : $customer->get_shipping_address_1();
1230 $shipping_address_2 = ! empty( $order->get_shipping_address_2() ) ? $order->get_shipping_address_2() : $customer->get_shipping_address_2();
1231
1232 $order_meta = [
1233 'customer_id' => $order->get_customer_id(),
1234 'email' => $email,
1235 'billing' => [
1236 'first_name' => $billing_first_name,
1237 'last_name' => $billing_last_name,
1238 'email' => $billing_email,
1239 'phone' => $billing_phone,
1240 'company' => $billing_company,
1241 'city' => $billing_city,
1242 'state' => $billing_state,
1243 'country' => $billing_country,
1244 'postcode' => $billing_postcode,
1245 'address_1' => $billing_address_1,
1246 'address_2' => $billing_address_2,
1247 ],
1248 'shipping' => [
1249 'first_name' => $shipping_first_name,
1250 'last_name' => $shipping_last_name,
1251 'phone' => $shipping_phone,
1252 'company' => $shipping_company,
1253 'city' => $shipping_city,
1254 'state' => $shipping_state,
1255 'country' => $shipping_country,
1256 'postcode' => $shipping_postcode,
1257 'address_1' => $shipping_address_1,
1258 'address_2' => $shipping_address_2,
1259 ],
1260 ];
1261
1262 return $order_meta;
1263 }
1264
1265 /**
1266 * Set delivery info to order.
1267 *
1268 * @param \WC_Order $order Order object.
1269 * @param array $order_meta Order meta data.
1270 */
1271 public static function set_delivery_info_to_order( \WC_Order $order, array $order_meta ) {
1272 // Set Billing Info.
1273 $order->set_billing_first_name( $order_meta['billing']['first_name'] ?? '' );
1274 $order->set_billing_last_name( $order_meta['billing']['last_name'] ?? '' );
1275 $order->set_billing_email( $order_meta['billing']['email'] ?? '' );
1276 $order->set_billing_phone( $order_meta['billing']['phone'] ?? '' );
1277 $order->set_billing_company( $order_meta['billing']['company'] ?? '' );
1278 $order->set_billing_city( $order_meta['billing']['city'] ?? '' );
1279 $order->set_billing_state( $order_meta['billing']['state'] ?? '' );
1280 $order->set_billing_country( $order_meta['billing']['country'] ?? '' );
1281 $order->set_billing_postcode( $order_meta['billing']['postcode'] ?? '' );
1282 $order->set_billing_address_1( $order_meta['billing']['address_1'] ?? '' );
1283 $order->set_billing_address_2( $order_meta['billing']['address_2'] ?? '' );
1284
1285 // Set Shipping Info.
1286 $order->set_shipping_first_name( $order_meta['shipping']['first_name'] ?? '' );
1287 $order->set_shipping_last_name( $order_meta['shipping']['last_name'] ?? '' );
1288 $order->set_shipping_phone( $order_meta['shipping']['phone'] ?? '' );
1289 $order->set_shipping_company( $order_meta['shipping']['company'] ?? '' );
1290 $order->set_shipping_city( $order_meta['shipping']['city'] ?? '' );
1291 $order->set_shipping_state( $order_meta['shipping']['state'] ?? '' );
1292 $order->set_shipping_country( $order_meta['shipping']['country'] ?? '' );
1293 $order->set_shipping_postcode( $order_meta['shipping']['postcode'] ?? '' );
1294 $order->set_shipping_address_1( $order_meta['shipping']['address_1'] ?? '' );
1295 $order->set_shipping_address_2( $order_meta['shipping']['address_2'] ?? '' );
1296 }
1297
1298 /**
1299 * Save meta-data from old order
1300 *
1301 * @param \WC_Order $new_order new order object.
1302 * @param \WC_Order $old_order old order object.
1303 *
1304 * @return void
1305 */
1306 public static function clone_order_metadata( $new_order, $old_order ) {
1307 // Set customer and currency info.
1308 $new_order->set_customer_id( $old_order->get_customer_id() );
1309 $new_order->set_currency( $old_order->get_currency() );
1310
1311 // Get delivery info from old order.
1312 $order_meta = self::get_delivery_info_from_order( $old_order );
1313
1314 // Check for any missing information.
1315 $missing_billing_info = true;
1316 foreach ( $order_meta['billing'] as $key => $value ) {
1317 if ( ! empty( $value ) ) {
1318 $missing_billing_info = false;
1319 break;
1320 }
1321 }
1322 $missing_shipping_info = true;
1323 foreach ( $order_meta['shipping'] as $key => $value ) {
1324 if ( ! empty( $value ) ) {
1325 $missing_shipping_info = false;
1326 break;
1327 }
1328 }
1329 $missing_info = $missing_billing_info || $missing_shipping_info;
1330
1331 // Get info from the parent order if missing.
1332 if ( $missing_info ) {
1333 $subscription = self::get_subscriptions_from_order( $old_order->get_id() );
1334 $subscription = reset( $subscription );
1335 $subscription_id = ! empty( $subscription ) ? $subscription->subscription_id : 0;
1336
1337 subscrpt_write_log( "Missing delivery info in old order #{$old_order->get_id()} for subscription #{$subscription_id}. Trying to get from parent order." );
1338
1339 $parent_order = self::get_parent_order( $subscription_id );
1340 if ( ! empty( $parent_order ) ) {
1341 $order_meta = self::get_delivery_info_from_order( $parent_order );
1342 }
1343 }
1344
1345 // Set delivery info to new order.
1346 self::set_delivery_info_to_order( $new_order, $order_meta );
1347 }
1348 }
1349
1350 // HPOS: All order data access below uses WooCommerce CRUD and is HPOS compatible.
1351