From d554f50475d64e7b0c9bf9c9fbb577edf29fb799 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jib=C3=A9=20Barth?= Date: Mon, 9 Dec 2024 15:06:00 +0100 Subject: [PATCH 1/4] PPSYL-111 - Trigger capture auto from a command --- .../CaptureAuthorizedPaymentCommand.php | 80 +++++++++++++++++++ src/Repository/PaymentRepository.php | 24 ++++++ src/Repository/PaymentRepositoryInterface.php | 5 ++ src/Resources/config/services.xml | 5 ++ 4 files changed, 114 insertions(+) create mode 100644 src/Command/CaptureAuthorizedPaymentCommand.php diff --git a/src/Command/CaptureAuthorizedPaymentCommand.php b/src/Command/CaptureAuthorizedPaymentCommand.php new file mode 100644 index 00000000..921fcecf --- /dev/null +++ b/src/Command/CaptureAuthorizedPaymentCommand.php @@ -0,0 +1,80 @@ +stateMachineFactory = $stateMachineFactory; + $this->paymentRepository = $paymentRepository; + $this->entityManager = $entityManager; + $this->logger = $logger; + + parent::__construct(); + } + + protected function configure(): void + { + $this->setName('payplug:capture-authorized-payments') + ->setDescription('Capture payplug authorized payments older than X days (default 6)') + ->addOption('days', 'd', InputOption::VALUE_OPTIONAL, 'Number of days to wait before capturing authorized payments', 6) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $payments = $this->paymentRepository->findAllAuthorizedOlderThanDays($input->getOption('days')); + if (\count($payments) === 0) { + $this->logger->debug('[Payplug] No authorized payments found.'); + } + + foreach ($payments as $i => $payment) { + $stateMachine = $this->stateMachineFactory->get($payment, PaymentTransitions::GRAPH); + $this->logger->info('[Payplug] Capturing payment {paymentId} (order #{orderNumber})', [ + 'paymentId' => $payment->getId(), + 'orderNumber' => $payment->getOrder()?->getNumber() ?? 'N/A', + ]); + $output->writeln(sprintf('Capturing payment %d (order #%s)', $payment->getId(), $payment->getOrder()?->getNumber() ?? 'N/A')); + + try { + $stateMachine->apply(PaymentTransitions::TRANSITION_COMPLETE); + } catch (\Throwable $e) { + $this->logger->critical('[Payplug] Error while capturing payment {paymentId}', [ + 'paymentId' => $payment->getId(), + 'exception' => $e->getMessage(), + ]); + continue; + } + + if ($i % 10 === 0) { + $this->entityManager->flush(); + } + } + + $this->entityManager->flush(); + + return Command::SUCCESS; + } +} diff --git a/src/Repository/PaymentRepository.php b/src/Repository/PaymentRepository.php index 350e6029..fd32b407 100644 --- a/src/Repository/PaymentRepository.php +++ b/src/Repository/PaymentRepository.php @@ -4,6 +4,7 @@ namespace PayPlug\SyliusPayPlugPlugin\Repository; +use PayPlug\SyliusPayPlugPlugin\Gateway\PayPlugGatewayFactory; use Sylius\Bundle\CoreBundle\Doctrine\ORM\PaymentRepository as BasePaymentRepository; use Sylius\Component\Core\Model\PaymentInterface; @@ -34,4 +35,27 @@ public function findOneByPayPlugPaymentId(string $payplugPaymentId): PaymentInte ->getSingleResult() ; } + + public function findAllAuthorizedOlderThanDays(int $days, ?string $gatewayFactoryName = null): array + { + if (null === $gatewayFactoryName) { + // For now, only this gateway support authorized payments + $gatewayFactoryName = PayPlugGatewayFactory::FACTORY_NAME; + } + + $date = (new \DateTime())->modify(sprintf('-%d days', $days)); + + return $this->createQueryBuilder('o') + ->innerJoin('o.method', 'method') + ->innerJoin('method.gatewayConfig', 'gatewayConfig') + ->where('o.state = :state') + ->andWhere('o.updatedAt < :date') + ->andWhere('gatewayConfig.factoryName = :factoryName') + ->setParameter('state', PaymentInterface::STATE_AUTHORIZED) + ->setParameter('factoryName', PayPlugGatewayFactory::FACTORY_NAME) + ->setParameter('date', $date) + ->getQuery() + ->getResult() + ; + } } diff --git a/src/Repository/PaymentRepositoryInterface.php b/src/Repository/PaymentRepositoryInterface.php index 0ebb58b4..6f35d267 100644 --- a/src/Repository/PaymentRepositoryInterface.php +++ b/src/Repository/PaymentRepositoryInterface.php @@ -12,4 +12,9 @@ interface PaymentRepositoryInterface extends BasePaymentRepositoryInterface public function findAllActiveByGatewayFactoryName(string $gatewayFactoryName): array; public function findOneByPayPlugPaymentId(string $payplugPaymentId): PaymentInterface; + + /** + * @return array + */ + public function findAllAuthorizedOlderThanDays(int $days, ?string $gatewayFactoryName = null): array; } diff --git a/src/Resources/config/services.xml b/src/Resources/config/services.xml index 023cd034..a4a41d4f 100644 --- a/src/Resources/config/services.xml +++ b/src/Resources/config/services.xml @@ -81,6 +81,11 @@ + + + + + From 3eca477790537c91fec70e5143bbe422be6ed541 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jib=C3=A9=20Barth?= Date: Mon, 9 Dec 2024 15:07:13 +0100 Subject: [PATCH 2/4] PPSYL-111 - Add authorized support in PaymentResolverState This service is called by the 'payplug:update-payment-state' and allow to put the correct status on a payment --- src/Resolver/PaymentStateResolver.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/Resolver/PaymentStateResolver.php b/src/Resolver/PaymentStateResolver.php index 2fc65bd6..e7d5282c 100644 --- a/src/Resolver/PaymentStateResolver.php +++ b/src/Resolver/PaymentStateResolver.php @@ -5,6 +5,8 @@ namespace PayPlug\SyliusPayPlugPlugin\Resolver; use Doctrine\ORM\EntityManagerInterface; +use Payplug\Resource\Payment; +use Payplug\Resource\PaymentAuthorization; use PayPlug\SyliusPayPlugPlugin\ApiClient\PayPlugApiClientInterface; use PayPlug\SyliusPayPlugPlugin\Gateway\PayPlugGatewayFactory; use Payum\Core\Model\GatewayConfigInterface; @@ -67,6 +69,10 @@ public function resolve(PaymentInterface $payment): void case null !== $payment->failure: $this->applyTransition($paymentStateMachine, PaymentTransitions::TRANSITION_FAIL); + break; + case $this->isAuthorized($payment): + $this->applyTransition($paymentStateMachine, PaymentTransitions::TRANSITION_AUTHORIZE); + break; default: $this->applyTransition($paymentStateMachine, PaymentTransitions::TRANSITION_PROCESS); @@ -81,4 +87,17 @@ private function applyTransition(StateMachineInterface $paymentStateMachine, str $paymentStateMachine->apply($transition); } } + + private function isAuthorized(Payment $payment): bool + { + $now = new \DateTimeImmutable(); + if ($payment->__isset('authorization') && + $payment->__get('authorization') instanceof PaymentAuthorization && + null !== $payment->__get('authorization')->__get('expires_at') && + $now < $now->setTimestamp($payment->__get('authorization')->__get('expires_at'))) { + return true; + } + + return false; + } } From 066a68b83dca4fe3c9478c7fc16b16bcd74c3ee3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jib=C3=A9=20Barth?= Date: Mon, 9 Dec 2024 15:09:23 +0100 Subject: [PATCH 3/4] PPSYL-113 - Document deferred capture process --- README.md | 4 + doc/authorized_payment.md | 97 ++++++++++++++++++ doc/images/admin_deferred_capture_feature.png | Bin 0 -> 25617 bytes 3 files changed, 101 insertions(+) create mode 100644 doc/authorized_payment.md create mode 100644 doc/images/admin_deferred_capture_feature.png diff --git a/README.md b/README.md index 91f254c7..bfd9a1a2 100644 --- a/README.md +++ b/README.md @@ -234,6 +234,10 @@ This library is under the MIT license. For better Oney integration, you can check the [Oney enhancement documentation](doc/oney_enhancement.md). +## Authorized Payment + +Since 1.10.0, the plugin supports the authorized payment feature. You can check the [Authorized Payment documentation](doc/authorized_payment.md). + ## Doc - [Development](doc/development.md) - [Release Process](RELEASE.md) diff --git a/doc/authorized_payment.md b/doc/authorized_payment.md new file mode 100644 index 00000000..e245fa63 --- /dev/null +++ b/doc/authorized_payment.md @@ -0,0 +1,97 @@ +# Authorized Payment + +This feature allow merchant to deferred the capture of the payment. +The payment is authorized and the capture can be done later. + +> [!IMPORTANT] +> The authorized payment feature is only available for the "PayPlug" payment gateway. + +## Activation + +On the payment method configuration, you can enable the deferred catpure feature. + +![admin_deferred_capture_feature.png](images/admin_deferred_capture_feature.png) + +## Trigger the capture + +### Periodically + +An authorized payment is valid for 7 days. +You can trigger the capture of the authorized payment by running the following command: + +```bash +$ bin/console payplug:capture-authorized-payments --days=6 +``` + +It will capture all authorized payments that are older than 6 days. + +> [!TIP] +> You can add this command to a cron job to automate the capture of the authorized payments. + +### Programmatically + +An authorized payment is in state `AUTHORIZED`. +A capture trigger is placed on the complete transition for such payments. + +```yaml +winzou_state_machine: + sylius_payment: + callbacks: + before: + payplug_sylius_payplug_plugin_complete: + on: ["complete"] + do: ["@payplug_sylius_payplug_plugin.payment_processing.capture", "process"] + args: ["object"] +``` +> [!NOTE] +> This configuration is already added by the plugin. + +For example, if you want to trigger the capture when an order is shipped, you can create a callback on the `sylius_order_shipping` state machine. + +```yaml +winzou_state_machine: + sylius_order_shipping: + callbacks: + before: + app_ensure_capture_payment: + on: ["ship"] + do: ["@App\StateMachine\CaptureOrderProcessor", "process"] + args: ["object"] +``` + +```php +getLastPayment(PaymentInterface::STATE_AUTHORIZED); + if (null === $payment) { + // No payment in authorized state, nothing to do here + return; + } + + $this->stateMachineFactory + ->get($payment, PaymentTransitions::GRAPH) + ->apply(PaymentTransitions::TRANSITION_COMPLETE); + + if (PaymentInterface::STATE_COMPLETED !== $payment->getState()) { + throw new \LogicException('Oh no! Payment capture failed 💸'); + } + } +} +``` diff --git a/doc/images/admin_deferred_capture_feature.png b/doc/images/admin_deferred_capture_feature.png new file mode 100644 index 0000000000000000000000000000000000000000..0244aeba96d615963fcd8692cc948163c4fcb4b0 GIT binary patch literal 25617 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�>yo^~B2~WZOu+>zdTD65TmxS7M0KLKd&BYl1yjr5bgEe} zWBg|I(WW(3W0{tVwy*F)#t&Tm-P1=OPp4iDExV{>8xIg{bd;Gr$O}NbA97R5i+h)? zF$Yu6YuB|C1z!rzLw`A-E~~(}48x!dHepZjr=b_Mx3AovU6J&iVrof&a|^uWJlV$t z_uGHt=nWNSObxY;z5eDlRz3R*VR~37%sTAmhcA#wQC5yeVTP_)p2ufb#f&j_;d_O- zoJtN+*1Iw?fq!#QX8c6Djz*r;c!RTKYdl_nGyEeb^^+eg0S!YiK2KLDX#cXI$)E;P zWc&bCS!1!-{dgW3bN{@+Wv><<2kmvM)K{vWH*^4-d}c3Esn^?cSlm3DAW%#Ri6D@ zq@G2=_zMM%>;7AX>7=Mxss7uAlfO-pO@o~Wg8U-qoBm+6wqz&NH0JzxM@j+gVz;x$ z^ouBGm&J;hH2G0yn*x
    3rAd1y}W!&$C&Qia4=hIj7@G?ec=ml^F%noEw)-T+Oo zhrHJGaCiKQqr-woxA^;m(Kjy#J&%PhQRy+JoVT;g6oT;8=_48;h2Fn2z-P@u_DKTl z5>l^IPUn+5ivAS4BUW%Bp0OEXAeGFTkN!TP-ZxPaqI(*T8&yrPK1>oNHIV8l!X&Ot zth0&N3x>?$Jk2j}p1>YxQ_F7kH%x%GUP8E#t>`m%u@CtUV&m=pPZG=jJT?9Q`vjde zyA35Qe5U+&3XlK80qUE6x$RmkS1gZ$bS$qDF);Elr$_L}3DyY8+5Jp#p=rX~9=FEw zCI|7|kw0(>M}o8QoG7{J?05so!i9HzU`O@myBpLH7wNQ4`}f1nhyMiCq4%H;iw;r3 zG+nsh9~rVw9;O0*?0=sJm(|0|xFhE{wct)nxPL$1+pg->({`=>xn#WI|2**haB9s% zrO0bwG-q!(f0#Y-Z@(tJYxbAWR>I5N6}w8@=&OHw*bCn@^WYmG#{^Sy6g`ah!Qe9_ t{&B#&3uMRO%s06NgTv#7 Date: Tue, 10 Dec 2024 14:25:55 +0100 Subject: [PATCH 4/4] PPSYL-111 - PHPstan issues --- rulesets/phpstan-baseline.neon | 5 +++++ src/Command/CaptureAuthorizedPaymentCommand.php | 8 +++++++- src/Repository/PaymentRepository.php | 3 ++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/rulesets/phpstan-baseline.neon b/rulesets/phpstan-baseline.neon index dd16d290..ca6b9030 100644 --- a/rulesets/phpstan-baseline.neon +++ b/rulesets/phpstan-baseline.neon @@ -330,6 +330,11 @@ parameters: count: 1 path: ../src/Handler/PaymentNotificationHandler.php + - + message: "#^Parameter \\#1 \\$timestamp of method DateTimeImmutable\\:\\:setTimestamp\\(\\) expects int, mixed given\\.$#" + count: 1 + path: ../src/Resolver/PaymentStateResolver.php + - message: "#^Parameter \\#2 \\$array of function array_key_exists expects array, mixed given\\.$#" count: 2 diff --git a/src/Command/CaptureAuthorizedPaymentCommand.php b/src/Command/CaptureAuthorizedPaymentCommand.php index 921fcecf..5fa70d58 100644 --- a/src/Command/CaptureAuthorizedPaymentCommand.php +++ b/src/Command/CaptureAuthorizedPaymentCommand.php @@ -45,7 +45,13 @@ protected function configure(): void protected function execute(InputInterface $input, OutputInterface $output): int { - $payments = $this->paymentRepository->findAllAuthorizedOlderThanDays($input->getOption('days')); + $days = \filter_var($input->getOption('days'), FILTER_VALIDATE_INT); + if (false === $days) { + throw new \InvalidArgumentException('Invalid number of days provided.'); + } + + $payments = $this->paymentRepository->findAllAuthorizedOlderThanDays($days); + if (\count($payments) === 0) { $this->logger->debug('[Payplug] No authorized payments found.'); } diff --git a/src/Repository/PaymentRepository.php b/src/Repository/PaymentRepository.php index fd32b407..e9793628 100644 --- a/src/Repository/PaymentRepository.php +++ b/src/Repository/PaymentRepository.php @@ -45,6 +45,7 @@ public function findAllAuthorizedOlderThanDays(int $days, ?string $gatewayFactor $date = (new \DateTime())->modify(sprintf('-%d days', $days)); + /** @var array */ return $this->createQueryBuilder('o') ->innerJoin('o.method', 'method') ->innerJoin('method.gatewayConfig', 'gatewayConfig') @@ -52,7 +53,7 @@ public function findAllAuthorizedOlderThanDays(int $days, ?string $gatewayFactor ->andWhere('o.updatedAt < :date') ->andWhere('gatewayConfig.factoryName = :factoryName') ->setParameter('state', PaymentInterface::STATE_AUTHORIZED) - ->setParameter('factoryName', PayPlugGatewayFactory::FACTORY_NAME) + ->setParameter('factoryName', $gatewayFactoryName) ->setParameter('date', $date) ->getQuery() ->getResult()