Skip to content

Commit

Permalink
Events to add ability to modify payment (including recurrent payment)…
Browse files Browse the repository at this point in the history
… before charge

remp/crm#2136
  • Loading branch information
miroc committed Dec 8, 2021
1 parent a1bd049 commit 69ac90f
Show file tree
Hide file tree
Showing 8 changed files with 200 additions and 101 deletions.
179 changes: 92 additions & 87 deletions src/commands/RecurrentPaymentsChargeCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

namespace Crm\PaymentsModule\Commands;

use Crm\ApplicationModule\Commands\DecoratedCommandTrait;
use Crm\ApplicationModule\Config\ApplicationConfig;
use Crm\PaymentsModule\Events\BeforeRecurrentPaymentChargeEvent;
use Crm\PaymentsModule\GatewayFactory;
use Crm\PaymentsModule\GatewayFail;
use Crm\PaymentsModule\Gateways\ExternallyChargedRecurrentPaymentInterface;
Expand All @@ -29,6 +31,8 @@

class RecurrentPaymentsChargeCommand extends Command
{
use DecoratedCommandTrait;

private $recurrentPaymentsRepository;

private $paymentsRepository;
Expand Down Expand Up @@ -86,32 +90,25 @@ protected function execute(InputInterface $input, OutputInterface $output)
{
$start = microtime(true);

$output->writeln('');
$output->writeln('<info>***** Recurrent Payment *****</info>');
$output->writeln('');

$chargeableRecurrentPayments = $this->recurrentPaymentsRepository->getChargeablePayments();

$output->writeln('Date: ' . (new DateTime())->format(DATE_RFC3339));
$output->writeln('Charging: <info>' . $chargeableRecurrentPayments->count('*') . '</info> payments');
$output->writeln('');
$this->line('Charging: <info>' . $chargeableRecurrentPayments->count('*') . '</info> payments');
$this->line('');

foreach ($chargeableRecurrentPayments as $recurrentPayment) {
try {
$this->validateRecurrentPayment($recurrentPayment);
} catch (RecurrentPaymentFastCharge $exception) {
$msg = 'RecurringPayment_id: ' . $recurrentPayment->id . ' Card_id: ' . $recurrentPayment->cid . ' User_id: ' . $recurrentPayment->user_id . ' Error: Fast charge';
Debugger::log($msg, Debugger::EXCEPTION);
$output->writeln('<error>' . $msg . '</error>');
$this->error($msg);
continue;
}

$subscriptionType = $this->recurrentPaymentsResolver->resolveSubscriptionType($recurrentPayment);
$customChargeAmount = $this->recurrentPaymentsResolver->resolveCustomChargeAmount($recurrentPayment);

if (isset($recurrentPayment->payment_id) && $recurrentPayment->payment_id != null) {
$payment = $this->paymentsRepository->find($recurrentPayment->payment_id);
} else {
if (!isset($recurrentPayment->payment_id) || $recurrentPayment->payment_id === null) {
$additionalAmount = 0;
$additionalType = null;
$parentPayment = $recurrentPayment->parent_payment;
Expand Down Expand Up @@ -204,96 +201,104 @@ protected function execute(InputInterface $input, OutputInterface $output)
null,
true
);

$this->recurrentPaymentsRepository->update($recurrentPayment, [
'payment_id' => $payment->id,
]);
}

$this->recurrentPaymentsRepository->update($recurrentPayment, [
'payment_id' => $payment->id,
]);
$this->chargeRecurrentPayment($recurrentPayment, $customChargeAmount);
}

/** @var RecurrentPaymentInterface $gateway */
$gateway = $this->gatewayFactory->getGateway($payment->payment_gateway->code);
try {
if ($payment->status === PaymentsRepository::STATUS_PAID) {
$this->recurrentPaymentsProcessor->processChargedRecurrent(
$recurrentPayment,
$payment->status,
$gateway->getResultCode(),
$gateway->getResultMessage(),
$customChargeAmount
);
} else {
$result = $gateway->charge($payment, $recurrentPayment->cid);
switch ($result) {
case RecurrentPaymentInterface::CHARGE_OK:
$paymentStatus = PaymentsRepository::STATUS_PAID;
$chargeAt = null;
if ($gateway instanceof ExternallyChargedRecurrentPaymentInterface) {
$paymentStatus = $gateway->getChargedPaymentStatus();
$chargeAt = $gateway->getLatestReceiptExpiration();
}
$this->recurrentPaymentsProcessor->processChargedRecurrent(
$recurrentPayment,
$paymentStatus,
$gateway->getResultCode(),
$gateway->getResultMessage(),
$customChargeAmount,
$chargeAt
);
break;
case RecurrentPaymentInterface::CHARGE_PENDING:
$this->recurrentPaymentsProcessor->processPendingRecurrent($recurrentPayment);
break;
default:
throw new \Exception('unhandled charge result provided by gateway: ' . $result);
}
}
} catch (RecurrentPaymentFailTry $exception) {
$this->recurrentPaymentsProcessor->processFailedRecurrent(
$end = microtime(true);
$duration = $end - $start;

$this->line('');
$this->line('EndDate: ' . (new DateTime())->format(DATE_RFC3339));
$this->info('All done. Took ' . round($duration, 2) . ' sec.');
$this->line('');

return Command::SUCCESS;
}

private function chargeRecurrentPayment($recurrentPayment, $customChargeAmount)
{
$this->emitter->emit(new BeforeRecurrentPaymentChargeEvent($recurrentPayment->payment, $recurrentPayment->cid)); // ability to modify payment
$payment = $this->paymentsRepository->find($recurrentPayment->payment_id); // reload

/** @var RecurrentPaymentInterface $gateway */
$gateway = $this->gatewayFactory->getGateway($payment->payment_gateway->code);
try {
if ($payment->status === PaymentsRepository::STATUS_PAID) {
$this->recurrentPaymentsProcessor->processChargedRecurrent(
$recurrentPayment,
$payment->status,
$gateway->getResultCode(),
$gateway->getResultMessage(),
$customChargeAmount
);
} catch (RecurrentPaymentFailStop $exception) {
$this->recurrentPaymentsProcessor->processStoppedRecurrent(
$recurrentPayment,
$gateway->getResultCode(),
$gateway->getResultMessage()
);
} catch (GatewayFail $exception) {
$this->recurrentPaymentsProcessor->processRecurrentChargeError(
$recurrentPayment,
$exception->getCode(),
$exception->getMessage(),
$customChargeAmount
);
} else {
$result = $gateway->charge($payment, $recurrentPayment->cid);
switch ($result) {
case RecurrentPaymentInterface::CHARGE_OK:
$paymentStatus = PaymentsRepository::STATUS_PAID;
$chargeAt = null;
if ($gateway instanceof ExternallyChargedRecurrentPaymentInterface) {
$paymentStatus = $gateway->getChargedPaymentStatus();
$chargeAt = $gateway->getLatestReceiptExpiration();
}
$this->recurrentPaymentsProcessor->processChargedRecurrent(
$recurrentPayment,
$paymentStatus,
$gateway->getResultCode(),
$gateway->getResultMessage(),
$customChargeAmount,
$chargeAt
);
break;
case RecurrentPaymentInterface::CHARGE_PENDING:
$this->recurrentPaymentsProcessor->processPendingRecurrent($recurrentPayment);
break;
default:
throw new \Exception('unhandled charge result provided by gateway: ' . $result);
}
}

$this->paymentLogsRepository->add(
$gateway->isSuccessful() ? 'OK' : 'ERROR',
json_encode($gateway->getResponseData()),
'recurring-payment-automatic-charge',
$payment->id
} catch (RecurrentPaymentFailTry $exception) {
$this->recurrentPaymentsProcessor->processFailedRecurrent(
$recurrentPayment,
$gateway->getResultCode(),
$gateway->getResultMessage(),
$customChargeAmount
);
} catch (RecurrentPaymentFailStop $exception) {
$this->recurrentPaymentsProcessor->processStoppedRecurrent(
$recurrentPayment,
$gateway->getResultCode(),
$gateway->getResultMessage()
);
} catch (GatewayFail $exception) {
$this->recurrentPaymentsProcessor->processRecurrentChargeError(
$recurrentPayment,
$exception->getCode(),
$exception->getMessage(),
$customChargeAmount
);

$now = new DateTime();
$output->writeln("[{$now->format(DATE_RFC3339)}] Recurrent payment: #{$recurrentPayment->id} (<comment>cid {$recurrentPayment->cid}</comment>)");
$output->writeln(" * status: <info>{$gateway->getResultCode()}</info>");
$output->writeln(" * message: {$gateway->getResultMessage()}");
}

$end = microtime(true);
$duration = $end - $start;

$output->writeln('');
$output->writeln('EndDate: ' . (new DateTime())->format(DATE_RFC3339));
$output->writeln('<info>All done. Took ' . round($duration, 2) . ' sec.</info>');
$output->writeln('');

return Command::SUCCESS;
$this->paymentLogsRepository->add(
$gateway->isSuccessful() ? 'OK' : 'ERROR',
json_encode($gateway->getResponseData()),
'recurring-payment-automatic-charge',
$payment->id
);

$now = new DateTime();
$this->line("[{$now->format(DATE_RFC3339)}] Recurrent payment: #{$recurrentPayment->id} (<comment>cid {$recurrentPayment->cid}</comment>)");
$this->line(" * status: <info>{$gateway->getResultCode()}</info>");
$this->line(" * message: {$gateway->getResultMessage()}");
}

public function getSubscriptionTypeItemsForCustomChargeAmount($subscriptionType, $customChargeAmount)
protected function getSubscriptionTypeItemsForCustomChargeAmount($subscriptionType, $customChargeAmount)
{
$items = SubscriptionTypePaymentItem::fromSubscriptionType($subscriptionType);

Expand Down
13 changes: 11 additions & 2 deletions src/commands/SingleChargeCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@

namespace Crm\PaymentsModule\Commands;

use Crm\PaymentsModule\Events\BeforeRecurrentPaymentChargeEvent;
use Crm\PaymentsModule\GatewayFactory;
use Crm\PaymentsModule\PaymentItem\PaymentItemContainer;
use Crm\PaymentsModule\Repository\PaymentsRepository;
use Crm\PaymentsModule\Repository\RecurrentPaymentsRepository;
use Crm\SubscriptionsModule\PaymentItem\SubscriptionTypePaymentItem;
use Crm\SubscriptionsModule\Repository\SubscriptionTypesRepository;
use League\Event\Emitter;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
Expand All @@ -23,17 +25,21 @@ class SingleChargeCommand extends Command

private $subscriptionTypesRepository;

private Emitter $emitter;

public function __construct(
RecurrentPaymentsRepository $recurrentPaymentsRepository,
GatewayFactory $gatewayFactory,
PaymentsRepository $paymentsRepository,
SubscriptionTypesRepository $subscriptionTypesRepository
SubscriptionTypesRepository $subscriptionTypesRepository,
Emitter $emitter
) {
parent::__construct();
$this->recurrentPaymentsRepository = $recurrentPaymentsRepository;
$this->gatewayFactory = $gatewayFactory;
$this->paymentsRepository = $paymentsRepository;
$this->subscriptionTypesRepository = $subscriptionTypesRepository;
$this->emitter = $emitter;
}

protected function configure()
Expand Down Expand Up @@ -126,7 +132,10 @@ protected function execute(InputInterface $input, OutputInterface $output)
true
);

$gateway = $this->gatewayFactory->getGateway($recurrentPayment->payment_gateway->code);
$this->emitter->emit(new BeforeRecurrentPaymentChargeEvent($payment, $recurrentPayment->cid)); // ability to modify payment
$payment = $this->paymentsRepository->find($payment->id); // reload

$gateway = $this->gatewayFactory->getGateway($payment->payment_gateway->code);
$gateway->charge($payment, $recurrentPayment->cid);
$this->paymentsRepository->updateStatus($payment, PaymentsRepository::STATUS_PAID);

Expand Down
10 changes: 10 additions & 0 deletions src/dataproviders/PaymentReturnGatewayDataProviderInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace Crm\PaymentsModule\DataProvider;

use Crm\ApplicationModule\DataProvider\DataProviderInterface;

interface PaymentReturnGatewayDataProviderInterface extends DataProviderInterface
{
public function provide(array $params): string;
}
20 changes: 20 additions & 0 deletions src/events/BeforePaymentBeginEvent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace Crm\PaymentsModule\Events;

use League\Event\AbstractEvent;

class BeforePaymentBeginEvent extends AbstractEvent
{
private $payment;

public function __construct($payment)
{
$this->payment = $payment;
}

public function getPayment()
{
return $this->payment;
}
}
34 changes: 34 additions & 0 deletions src/events/BeforeRecurrentPaymentChargeEvent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

namespace Crm\PaymentsModule\Events;

use League\Event\AbstractEvent;

class BeforeRecurrentPaymentChargeEvent extends AbstractEvent
{
private $payment;

private $token;

/**
* Same parameters that go to RecurrentPaymentInterface#charge()
*
* @param $payment
* @param $token
*/
public function __construct($payment, $token)
{
$this->payment = $payment;
$this->token = $token;
}

public function getPayment()
{
return $this->payment;
}

public function getToken()
{
return $this->token;
}
}
Loading

0 comments on commit 69ac90f

Please sign in to comment.