Skip to content

Commit

Permalink
Allow to stop recurrent profile after charge failed
Browse files Browse the repository at this point in the history
Issue: User is unable to stop recurrent payment after charge failed.

Reason is that system sets recurrent profile to state `charge_failed`
and creates new payment with new recurrent profile. Unfortunatelly
payment in customer zone points to old recurrent profile (probably
needs refactoring) and because of that user was not able to stop
(current) recurrent profile or to see if that particular payment will
be charged in future.

Fix: "Resolve" recurrent profile if it is in 'charge_failed' state and
find new valid one. Show state and possible user actions against this
valid recurrent payment profile.

Added also dates of last failed and next charge attempt.

remp/crm#1163
  • Loading branch information
markoph committed May 29, 2020
1 parent a556e08 commit 3b39835
Show file tree
Hide file tree
Showing 6 changed files with 82 additions and 16 deletions.
4 changes: 4 additions & 0 deletions src/lang/payments.cs_CZ.neon
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,10 @@ frontend:
user_stopped_requested: Zastavené na Vaši žádost
failed_recurrent: Neúspěšné opakování
success_recurrent: Automaticky prodloužená
charge_failed:
title: Automatické prodloužení selhalo
last_try: Předchozí pokus
next_try: Následující pokus
no_payments: Nemáte žádné platby
buy_offer: Můžete si předplatné zakoupit a získat tak přístup k obsahu
next_recurrent_payment: "Máte zapnuté automatické obnovování předplatného, nejbližší platba v hodnotě <strong>%charge_amount%</strong> proběhne <strong>%charge_date%</strong>."
Expand Down
4 changes: 4 additions & 0 deletions src/lang/payments.en_US.neon
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,10 @@ frontend:
user_stopped_requested: Stopped by your request
failed_recurrent: Failed recurrent payment
success_recurrent: Automatically charged
charge_failed:
title: Auto-renew failed
last_try: Previous attempt
next_try: Next attempt
no_payments: You have no payments
buy_offer: You can buy the subscription to get access to content.
next_recurrent_payment: "You have recurrent payment enabled. Your next payment with amount of <strong>%charge_amount%</strong> will occur on <strong>%charge_date%</strong>."
Expand Down
4 changes: 4 additions & 0 deletions src/lang/payments.sk_SK.neon
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,10 @@ frontend:
user_stopped_requested: Zastavené na Vašu žiadosť
failed_recurrent: Neúspešné opakovanie
success_recurrent: Automaticky predĺžená
charge_failed:
title: Automatické predĺženie zlyhalo
last_try: Predchádzajúci pokus
next_try: Nasledujúci pokus
no_payments: Nemáte žiadne platby
buy_offer: Môžete si predplatné zakúpiť a získať tak prístup ku obsahu.
next_recurrent_payment: "Máte zapnuté automatické obnovovanie predplatného, najbližšia platba v hodnote <strong>%charge_amount%</strong> prebehne <strong>%charge_date%</strong>."
Expand Down
37 changes: 37 additions & 0 deletions src/model/RecurrentPaymentsResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
use Crm\PaymentsModule\Repository\RecurrentPaymentsRepository;
use Crm\SubscriptionsModule\Repository\SubscriptionTypesRepository;
use Nette\Database\Table\ActiveRow;
use Nette\Utils\DateTime;
use Tracy\Debugger;

class RecurrentPaymentsResolver
{
Expand All @@ -16,6 +18,8 @@ class RecurrentPaymentsResolver

private $subscriptionTypesRepository;

public $lastFailedChargeAt = null;

public function __construct(
PaymentsRepository $paymentsRepository,
RecurrentPaymentsRepository $recurrentPaymentsRepository,
Expand Down Expand Up @@ -76,4 +80,37 @@ public function resolveChargeAmount(ActiveRow $recurrentPayment) : float
}
return $amount;
}

/**
* resolveFailedRecurrent checks following recurring payments after charge failed and returns last recurring payment.
*
* This method follows sequence:
* - get $recurrentPayment->payment
* - get $payment->recurringPayment (via parent_payment_id)
*
* So methods always follows original payment and it doesn't jump to recurrent payment of other subscription.
*
* If resolved is unable to find following recurring profile, it returns same payment.
*/
public function resolveFailedRecurrent(ActiveRow $recurrentPayment): ActiveRow
{
if ($recurrentPayment->state === RecurrentPaymentsRepository::STATE_CHARGE_FAILED) {
$this->lastFailedChargeAt = $recurrentPayment->charge_at;
if (isset($recurrentPayment->payment) && ($nextRecurrent = $this->recurrentPaymentsRepository->recurrent($recurrentPayment->payment))) {
$recurrentPayment = $this->resolveFailedRecurrent($nextRecurrent);
} else {
Debugger::log('Unable to find next payment for failed recurrent ID [' . $recurrentPayment->id . ']', Debugger::ERROR);
}
}
return $recurrentPayment;
}

public function getLastChargeFailedDateTime(): DateTime
{
if ($this->lastFailedChargeAt === null) {
throw new \Exception('No last charge_failed date is set. Did you call `resolveFailedRecurrent()`?');
}

return new DateTime($this->lastFailedChargeAt);
}
}
1 change: 1 addition & 0 deletions src/presenters/PaymentsPresenter.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public function renderMy()
$this->onlyLoggedIn();

$this->template->payments = $this->paymentsRepository->userPaymentsWithRecurrent($this->getUser()->getId());
$this->template->resolver = $this->recurrentPaymentsResolver;
}

public function handleReactivate($recurrentId)
Expand Down
48 changes: 32 additions & 16 deletions src/templates/Payments/my.latte
Original file line number Diff line number Diff line change
Expand Up @@ -38,25 +38,41 @@
{$payment->amount|price}
</td>
<td>
{foreach $payment->related('recurrent_payments', 'parent_payment_id') as $recurrent}
{if $recurrent->payment_id == NULL}
{if $recurrent->state == \Crm\PaymentsModule\Repository\RecurrentPaymentsRepository::STATE_ACTIVE}
{foreach $payment->related('recurrent_payments', 'parent_payment_id') as $recurrent}
{* if recurrent charge for this payment failed, try to locate following recurring profile for this payment *}
{if $recurrent->state === \Crm\PaymentsModule\Repository\RecurrentPaymentsRepository::STATE_CHARGE_FAILED}
{php $recurrent = $resolver->resolveFailedRecurrent($recurrent); }
{php $lastChargeFailDate = $resolver->getLastChargeFailedDateTime(); }
{/if}

{switch $recurrent->state}
{case \Crm\PaymentsModule\Repository\RecurrentPaymentsRepository::STATE_CHARGED}
<span class="label label-success">{_payments.frontend.my.success_recurrent}</span>
{case \Crm\PaymentsModule\Repository\RecurrentPaymentsRepository::STATE_ACTIVE}
{if isset($lastChargeFailDate)}
<span class="label label-danger">{_payments.frontend.my.charge_failed.title}</span>
<ul style="font-style: italic; margin-left: 1em;">
<li>{_payments.frontend.my.charge_failed.last_try}: {$lastChargeFailDate}</li>
<li>{_payments.frontend.my.charge_failed.next_try}: {$recurrent->charge_at}</li>
</ul>
{/if}
<a class="btn btn-sm btn-primary" n:href="recurrentStop $recurrent->id">{_payments.frontend.my.stop_recurrent}</a>
{elseif $recurrent->state == \Crm\PaymentsModule\Repository\RecurrentPaymentsRepository::STATE_USER_STOP}
{_payments.frontend.my.user_stopped}<br>
<a n:if="$recurrent->cid && $recurrent->charge_at > new \DateTime()" n:href="Reactivate! $recurrent->id" class="btn btn-sm btn-green">{_payments.frontend.my.restart_recurrent}</a>
{elseif $recurrent->state == \Crm\PaymentsModule\Repository\RecurrentPaymentsRepository::STATE_ADMIN_STOP}
{case \Crm\PaymentsModule\Repository\RecurrentPaymentsRepository::STATE_USER_STOP}
<span class="label label-info">{_payments.frontend.my.user_stopped}</span>
<span n:if="$recurrent->cid && $recurrent->charge_at > new \DateTime()">
<br><br>
<a n:href="Reactivate! $recurrent->id" class="btn btn-sm btn-green">
{_payments.frontend.my.restart_recurrent}
</a>
</span>

{case \Crm\PaymentsModule\Repository\RecurrentPaymentsRepository::STATE_ADMIN_STOP}
<span class="label label-info">{_payments.frontend.my.user_stopped_requested}</span>
{elseif $recurrent->state == \Crm\PaymentsModule\Repository\RecurrentPaymentsRepository::STATE_SYSTEM_STOP}
{case \Crm\PaymentsModule\Repository\RecurrentPaymentsRepository::STATE_SYSTEM_STOP}
<span class="label label-danger">{_payments.frontend.my.failed_recurrent}</span>
{/if}
{else}
{if $recurrent->state == \Crm\PaymentsModule\Repository\RecurrentPaymentsRepository::STATE_CHARGE_FAILED}
<span class="label label-danger">{_payments.frontend.my.failed_recurrent}</span>
{else}
<span class="label label-success">{_payments.frontend.my.success_recurrent}</span>
{/if}
{/if}
{case \Crm\PaymentsModule\Repository\RecurrentPaymentsRepository::STATE_CHARGE_FAILED}
<span class="label label-danger">{_payments.frontend.my.failed_recurrent}</span><br><br>
{/switch}
{/foreach}
</td>
<td>
Expand Down

0 comments on commit 3b39835

Please sign in to comment.