PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.7.1
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.7.1
1.7.1 1.7.0 1.6.6 1.6.5 1.6.4 1.6.3 1.6.2 1.6.1 1.6.0 1.5.4 1.5.5 1.5.3 1.5.2 1.5.1 1.5.0 1.4.2 1.4.1 1.4.0 1.3.28 1.3.27 1.3.26 1.3.25 1.3.23 1.3.22 1.3.21 All 51 releases
← All changes | app/Modules/StoreManagedRenewal/Services/RenewalService.php +56 -23 1.6.0 → 1.7.1 View file →
@@ -8,12 +8,12 @@
8 8 use FluentCart\App\Models\OrderItem;
9 9 use FluentCart\App\Models\OrderTaxRate;
10 10 use FluentCart\App\Models\OrderTransaction;
11 11 use FluentCart\App\Models\Subscription;
12 -use FluentCart\App\Services\Payments\PaymentHelper;
13 12 use FluentCart\App\Services\Payments\SubscriptionHelper;
14 13 use FluentCart\App\Modules\Subscriptions\Services\SubscriptionService;
15 14 use FluentCart\App\Modules\Subscriptions\Services\SystemChargeService;
15 +use FluentCart\Api\StoreSettings;
16 16 use FluentCart\Framework\Support\Arr;
17 17 use WP_Error;
18 18
19 19 class RenewalService
@@ -33,8 +33,17 @@
33 33 if (!$parentOrder) {
34 34 return new WP_Error('parent_order_not_found', __('Parent order not found for this subscription.', 'fluent-cart'));
35 35 }
36 36
37 + if (!SubscriptionHelper::canProcessInMode($parentOrder->mode)) {
38 + return new WP_Error('store_mode_mismatch', sprintf(
39 + /* translators: 1: the subscription's payment mode (live/test), 2: the store's current mode (live/test) */
40 + __('Renewal skipped — this subscription is in %1$s mode but the store is currently in %2$s mode.', 'fluent-cart'),
41 + $parentOrder->mode,
42 + (new StoreSettings())->get('order_mode')
43 + ));
44 + }
45 +
37 46 // Get original order item — use eager-loaded collection if available, otherwise query
38 47 if ($parentOrder->relationLoaded('order_items')) {
39 48 $parentOrderItem = $parentOrder->order_items->filter(function ($item) {
40 49 return $item->payment_type === Status::ORDER_TYPE_SUBSCRIPTION;
@@ -299,8 +308,11 @@
299 308 $billingAddressData,
300 309 $shippingAddressData
301 310 );
302 311
312 + AddressHelper::copyOrderAddressMeta($childOrder->id, 'billing', $billingAddress);
313 + AddressHelper::copyOrderAddressMeta($childOrder->id, 'shipping', $shippingAddress);
314 +
303 315 foreach (['tax_id', 'vat_tax_id', 'business_info', 'store_business_info'] as $metaKey) {
304 316 $metaValue = $parentOrder->getMeta($metaKey, null);
305 317
306 318 if ($metaValue !== null && $metaValue !== '' && $metaValue !== []) {
@@ -347,10 +359,19 @@
347 359 if (!self::hasStoreManagedSubscriptions()) {
348 360 return $results;
349 361 }
350 362
351 - $subscriptions = Subscription::query()
352 - ->with(['order.order_items', 'product', 'variation'])
363 + $query = Subscription::query()
364 + ->with(['order.order_items', 'product', 'variation']);
365 +
366 + if (SubscriptionHelper::isModeGuardEnabled()) {
367 + $storeMode = (new StoreSettings())->get('order_mode');
368 + $query->whereHas('order', function ($query) use ($storeMode) {
369 + $query->where('mode', $storeMode);
370 + });
371 + }
372 +
373 + $subscriptions = $query
353 374 ->whereIn('collection_method', ['manual', 'system'])
354 375 ->whereNotIn('status', [
355 376 Status::SUBSCRIPTION_COMPLETED,
356 377 Status::SUBSCRIPTION_CANCELED,
@@ -506,16 +527,16 @@
506 527 if ($order->getMeta('renewal_processed')) {
507 528 return;
508 529 }
509 530
510 - $intervalDays = PaymentHelper::getIntervalDays($subscription->billing_interval);
511 531 $dueDate = $order->getMeta('due_date');
512 532 $paidAt = time();
533 + $schedule = SubscriptionHelper::getBillingSchedule($subscription);
513 534
514 535 if ($dueDate && $paidAt <= strtotime($dueDate)) {
515 - $nextBillingDate = gmdate('Y-m-d H:i:s', strtotime($dueDate) + ($intervalDays * 86400));
536 + $nextBillingDate = gmdate('Y-m-d H:i:s', SubscriptionHelper::addBillingInterval($dueDate, $subscription->billing_interval, $schedule));
516 537 } else {
517 - $nextBillingDate = gmdate('Y-m-d H:i:s', $paidAt + ($intervalDays * 86400));
538 + $nextBillingDate = gmdate('Y-m-d H:i:s', SubscriptionHelper::addBillingInterval($paidAt, $subscription->billing_interval, $schedule));
518 539 }
519 540
520 541 // syncSubscriptionStates derives bill_count from DB transactions, handles EOT
521 542 // (fires SubscriptionEOT internally when bill_count >= bill_times), and saves
@@ -577,10 +598,16 @@
577 598 if (!self::hasStoreManagedSubscriptions()) {
578 599 return $results;
579 600 }
580 601
581 - $pendingInvoices = Order::query()
582 - ->where('type', Status::ORDER_TYPE_RENEWAL)
602 + $invoiceQuery = Order::query()
603 + ->where('type', Status::ORDER_TYPE_RENEWAL);
604 +
605 + if (SubscriptionHelper::isModeGuardEnabled()) {
606 + $invoiceQuery->where('mode', (new StoreSettings())->get('order_mode'));
607 + }
608 +
609 + $pendingInvoices = $invoiceQuery
583 610 ->whereIn('payment_status', [
584 611 Status::PAYMENT_PENDING,
585 612 Status::PAYMENT_SCHEDULED,
586 613 Status::PAYMENT_AUTHORIZED,
@@ -646,8 +673,19 @@
646 673 }
647 674
648 675 // Past the per-interval grace period: past_due → expired
649 676 if ($invoiceAge >= $graceDays && $subscription->status === Status::SUBSCRIPTION_PAST_DUE ) {
677 + // CAS the past_due → expired write inside syncSubscriptionStates; if a
678 + // concurrent renewal payment reactivated the row, it returns null and we skip.
679 + $expired = SubscriptionService::syncSubscriptionStates($subscription, [
680 + 'status' => Status::SUBSCRIPTION_EXPIRED,
681 + 'next_billing_date' => null,
682 + ], Status::SUBSCRIPTION_PAST_DUE);
683 +
684 + if (!$expired) {
685 + continue;
686 + }
687 +
650 688 $subscription->addLog(
651 689 'Subscription expired',
652 690 sprintf(
653 691 'Unpaid invoice #%s exceeded the %d-day grace period. Subscription expired.',
@@ -656,14 +694,8 @@
656 694 ),
657 695 'error'
658 696 );
659 697
660 - // Go through syncSubscriptionStates so all lifecycle hooks fire consistently
661 - SubscriptionService::syncSubscriptionStates($subscription, [
662 - 'status' => Status::SUBSCRIPTION_EXPIRED,
663 - 'next_billing_date' => null,
664 - ]);
665 -
666 698 $results['expired']++;
667 699 continue;
668 700 }
669 701
@@ -730,18 +762,20 @@
730 762 if (!$oldDate) {
731 763 return null;
732 764 }
733 765
734 - $intervalDays = PaymentHelper::getIntervalDays($subscription->billing_interval);
735 - $newTs = strtotime($oldDate) + ($intervalDays * DAY_IN_SECONDS);
766 + $schedule = SubscriptionHelper::getBillingSchedule($subscription);
767 + $newTs = SubscriptionHelper::addBillingInterval($oldDate, $subscription->billing_interval, $schedule);
736 768
737 769 // Overdue dates: advance whole intervals until in the future. Guard the loop
738 - // so a zero/negative interval can never spin.
739 - if ($intervalDays > 0) {
740 - $now = time();
741 - while ($newTs <= $now) {
742 - $newTs += $intervalDays * DAY_IN_SECONDS;
770 + // so a zero-progress interval (broken day-count filter) can never spin.
771 + $now = time();
772 + while ($newTs <= $now) {
773 + $advanced = SubscriptionHelper::addBillingInterval($newTs, $subscription->billing_interval, $schedule);
774 + if ($advanced <= $newTs) {
775 + break;
743 776 }
777 + $newTs = $advanced;
744 778 }
745 779
746 780 $newDate = gmdate('Y-m-d H:i:s', $newTs);
747 781
@@ -891,10 +925,9 @@
891 925 if (strtotime($subscription->next_billing_date) > strtotime($dueDate)) {
892 926 return;
893 927 }
894 928
895 - $intervalDays = PaymentHelper::getIntervalDays($subscription->billing_interval);
896 - $newDate = gmdate('Y-m-d H:i:s', strtotime($dueDate) + ($intervalDays * DAY_IN_SECONDS));
929 + $newDate = gmdate('Y-m-d H:i:s', SubscriptionHelper::addBillingInterval($dueDate, $subscription->billing_interval, SubscriptionHelper::getBillingSchedule($subscription)));
897 930
898 931 $subscription->update(['next_billing_date' => $newDate]);
899 932 }
900 933