| @@ -7,8 +7,9 @@ | ||
| 7 | 7 | use FluentCart\App\Models\OrderMeta; |
| 8 | 8 | use FluentCart\App\Models\Subscription; |
| 9 | 9 | use FluentCart\App\Modules\Subscriptions\Services\SystemChargeService; |
| 10 | 10 | use FluentCart\App\Services\Payments\PaymentHelper; |
| 11 | +use FluentCart\App\Services\Payments\SubscriptionHelper; | |
| 11 | 12 | |
| 12 | 13 | class RenewalReminderService extends ReminderService |
| 13 | 14 | { |
| 14 | 15 | const META_KEY = 'renewal_reminder_state'; |
| @@ -64,8 +65,23 @@ | ||
| 64 | 65 | ]; |
| 65 | 66 | |
| 66 | 67 | do_action('fluent_cart/' . $eventName, $data); |
| 67 | 68 | |
| 69 | + if (strpos($stage, 'overdue_') === 0) { | |
| 70 | + // Legacy hook shipped in 1.6.0 for scheduled overdue reminders. | |
| 71 | + // Kept for third-party listeners; the flag tells the core mailer | |
| 72 | + // the staged email above was already sent, so it must not mail | |
| 73 | + // again — direct dispatches of the legacy hook lack the flag and | |
| 74 | + // still deliver. | |
| 75 | + $data['staged_email_dispatched'] = true; | |
| 76 | + do_action_deprecated( | |
| 77 | + 'fluent_cart/renewal_reminder_overdue', | |
| 78 | + [$data], | |
| 79 | + '1.6.3', | |
| 80 | + 'fluent_cart/' . $eventName | |
| 81 | + ); | |
| 82 | + } | |
| 83 | + | |
| 68 | 84 | $state = $this->markStageSent($state, $cycleKey, $stage); |
| 69 | 85 | $order->updateMeta(static::META_KEY, $state); |
| 70 | 86 | |
| 71 | 87 | return true; |
| @@ -328,8 +344,15 @@ | ||
| 328 | 344 | */ |
| 329 | 345 | |
| 330 | 346 | protected function isEligible(Order $order): bool |
| 331 | 347 | { |
| 348 | + // Live invoice on a test-mode store (or vice versa): don't email — the | |
| 349 | + // guard's promise covers reminders too. Checked at both scan and send | |
| 350 | + // time so pre-queued actions copied to a clone are also caught. | |
| 351 | + if (!SubscriptionHelper::canProcessInMode($order->mode)) { | |
| 352 | + return false; | |
| 353 | + } | |
| 354 | + | |
| 332 | 355 | if (!in_array($order->payment_status, static::getReminderPaymentStatuses(), true)) { |
| 333 | 356 | return false; |
| 334 | 357 | } |
| 335 | 358 | |
| @@ -416,10 +439,30 @@ | ||
| 416 | 439 | |
| 417 | 440 | protected function resolveEventName(string $stage): string |
| 418 | 441 | { |
| 419 | 442 | if (strpos($stage, 'overdue_') === 0) { |
| 420 | - return 'renewal_reminder_overdue'; | |
| 443 | + return $this->resolveOverdueEventName((int)substr($stage, strlen('overdue_'))); | |
| 421 | 444 | } |
| 422 | 445 | |
| 423 | 446 | return 'renewal_reminder_due'; |
| 447 | + } | |
| 448 | + | |
| 449 | + /** | |
| 450 | + * Map an overdue stage to a notification event by its position in the | |
| 451 | + * configured day list, not by a fixed day threshold — the escalation tone | |
| 452 | + * follows the admin's schedule (e.g. 14,30 → first at 14, final at 30). | |
| 453 | + */ | |
| 454 | + protected function resolveOverdueEventName(int $daysAfter): string | |
| 455 | + { | |
| 456 | + $days = $this->getOverdueDays(); | |
| 457 | + | |
| 458 | + if (empty($days) || $daysAfter <= min($days)) { | |
| 459 | + return 'renewal_overdue_first'; | |
| 460 | + } | |
| 461 | + | |
| 462 | + if ($daysAfter >= max($days)) { | |
| 463 | + return 'renewal_overdue_final'; | |
| 464 | + } | |
| 465 | + | |
| 466 | + return 'renewal_overdue_followup'; | |
| 424 | 467 | } |
| 425 | 468 | } |