← 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 | |