From a2edddb12cef11422340879e16355bda6ec5ba5d Mon Sep 17 00:00:00 2001 From: Dominik Skerhak Date: Wed, 21 Jul 2021 14:06:26 +0200 Subject: [PATCH] Fix creation of recurrent with invalid charge_at date If `subscription_types.fixed_end` is set, next `charge_at` is always set to value `subscription_types.fixed_end - recurrent_charge_before` which can be (when current date is close to or after `fixed_end`) before `subscriptions.start_time`. `RecurrentPaymentsRepository->calculateChargeAt()` now throws exception if calculated next `charge_at` is invalid (recurrent payment's next charge shouldn't be before subscriptions start date or before current datetime). And if `RecurrentPaymentsRepository->createFromPayment()` catches this exception, recurrent payment is stopped (marked as stopped by system). remp/crm#1662 --- .../RecurrentPaymentsRepository.php | 35 ++++++++++++++++--- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/src/model/Repositories/RecurrentPaymentsRepository.php b/src/model/Repositories/RecurrentPaymentsRepository.php index 1cd4b90..b2a9407 100644 --- a/src/model/Repositories/RecurrentPaymentsRepository.php +++ b/src/model/Repositories/RecurrentPaymentsRepository.php @@ -78,7 +78,10 @@ final public function createFromPayment( ?float $customChargeAmount = null ): ?IRow { if (!in_array($payment->status, [PaymentsRepository::STATUS_PAID, PaymentsRepository::STATUS_PREPAID], true)) { - Debugger::log("Could not create recurrent payment from payment [{$payment->id}], invalid payment status: [{$payment->status}]"); + Debugger::log( + "Could not create recurrent payment from payment [{$payment->id}], invalid payment status: [{$payment->status}]", + Debugger::ERROR + ); return null; } @@ -92,7 +95,12 @@ final public function createFromPayment( $retries = count((array)$retries); if (!$chargeAt) { - $chargeAt = $this->calculateChargeAt($payment); + try { + $chargeAt = $this->calculateChargeAt($payment); + } catch (\Exception $e) { + Debugger::log($e, Debugger::ERROR); + return null; + } } $recurrentPayment = $this->add( @@ -346,6 +354,9 @@ final public function getDuplicate() ->fetchAll(); } + /** + * @throws Exception If calculated next charge at date is invalid (before subscription's start date / in past) + */ final public function calculateChargeAt($payment) { $subscriptionType = $payment->subscription_type; @@ -353,6 +364,13 @@ final public function calculateChargeAt($payment) $endTime = clone $subscription->end_time; + if ($endTime <= new DateTime()) { + throw new Exception( + "Calculated next charge of recurrent payment would be in the past." . + " Check payment [{$payment->id}] and subscription [{$subscription->id}]." + ); + } + $chargeBefore = null; if (!$chargeBefore) { $chargeBefore = $subscriptionType->recurrent_charge_before; @@ -371,7 +389,16 @@ final public function calculateChargeAt($payment) if ($chargeBefore) { $newEndTime = (clone $endTime)->sub(new \DateInterval("PT{$chargeBefore}H")); if ($newEndTime < $subscription->start_time) { - Debugger::log("Calculated next charge of recurrent payment would be sooner than subscription start time. Check subscription: " . $subscription->id, Debugger::WARNING); + throw new Exception( + "Calculated next charge of recurrent payment would be before subscription's start time." . + " Check payment [{$payment->id}] and subscription [{$subscription->id}]." + ); + } + if ($newEndTime <= new DateTime()) { + throw new Exception( + "Calculated next charge of recurrent payment would be in the past." . + " Check payment [{$payment->id}] and subscription [{$subscription->id}]." + ); } $endTime = $newEndTime; } @@ -412,7 +439,7 @@ final public function latestSuccessfulRecurrentPayment($recurrentPayment) if (!$previousRecurrentCharge) { return null; } - + return $this->latestSuccessfulRecurrentPayment($previousRecurrentCharge); } }