-
Notifications
You must be signed in to change notification settings - Fork 19
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #263 from synolia/feature/authorized-payment
Authorized Payment
- Loading branch information
Showing
31 changed files
with
602 additions
and
34 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
<?php | ||
declare(strict_types=1); | ||
namespace App\StateMachine; | ||
use SM\Factory\Factory; | ||
use Sylius\Component\Core\Model\OrderInterface; | ||
use Sylius\Component\Core\Model\PaymentInterface; | ||
use Sylius\Component\Payment\PaymentTransitions; | ||
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure; | ||
#[Autoconfigure(public: true)] // make the service public to be callable by winzou_state_machine | ||
class CaptureOrderProcessor | ||
{ | ||
public function __construct(private Factory $stateMachineFactory) {} | ||
public function process(OrderInterface $order): void | ||
{ | ||
$payment = $order->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 💸'); | ||
} | ||
} | ||
} | ||
``` |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace PayPlug\SyliusPayPlugPlugin\Checker; | ||
|
||
use PayPlug\SyliusPayPlugPlugin\Gateway\PayPlugGatewayFactory; | ||
use Sylius\Bundle\PayumBundle\Model\GatewayConfigInterface; | ||
use Sylius\Component\Core\Model\PaymentMethodInterface; | ||
|
||
class PayplugFeatureChecker | ||
{ | ||
public function isDeferredCaptureEnabled(PaymentMethodInterface $paymentMethod): bool | ||
{ | ||
return $this->getConfigCheckboxValue($paymentMethod, PayPlugGatewayFactory::DEFERRED_CAPTURE); | ||
} | ||
|
||
public function isIntegratedPaymentEnabled(PaymentMethodInterface $paymentMethod): bool | ||
{ | ||
return $this->getConfigCheckboxValue($paymentMethod, PayPlugGatewayFactory::INTEGRATED_PAYMENT); | ||
} | ||
|
||
public function isOneClickEnabled(PaymentMethodInterface $paymentMethod): bool | ||
{ | ||
return $this->getConfigCheckboxValue($paymentMethod, PayPlugGatewayFactory::ONE_CLICK); | ||
} | ||
|
||
private function getConfigCheckboxValue(PaymentMethodInterface $paymentMethod, string $configKey): bool | ||
{ | ||
$gatewayConfiguration = $paymentMethod->getGatewayConfig(); | ||
|
||
if (!$gatewayConfiguration instanceof GatewayConfigInterface) { | ||
return false; | ||
} | ||
|
||
if (!\array_key_exists($configKey, $gatewayConfiguration->getConfig())) { | ||
return false; | ||
} | ||
|
||
return (bool) ($gatewayConfiguration->getConfig()[$configKey] ?? false); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace PayPlug\SyliusPayPlugPlugin\Command; | ||
|
||
use Doctrine\ORM\EntityManagerInterface; | ||
use PayPlug\SyliusPayPlugPlugin\Repository\PaymentRepositoryInterface; | ||
use Psr\Log\LoggerInterface; | ||
use SM\Factory\Factory; | ||
use Sylius\Component\Payment\PaymentTransitions; | ||
use Symfony\Component\Console\Command\Command; | ||
use Symfony\Component\Console\Input\InputInterface; | ||
use Symfony\Component\Console\Input\InputOption; | ||
use Symfony\Component\Console\Output\OutputInterface; | ||
|
||
class CaptureAuthorizedPaymentCommand extends Command | ||
{ | ||
private Factory $stateMachineFactory; | ||
private PaymentRepositoryInterface $paymentRepository; | ||
private EntityManagerInterface $entityManager; | ||
private LoggerInterface $logger; | ||
|
||
public function __construct( | ||
Factory $stateMachineFactory, | ||
PaymentRepositoryInterface $paymentRepository, | ||
EntityManagerInterface $entityManager, | ||
LoggerInterface $logger, | ||
) { | ||
$this->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 | ||
{ | ||
$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.'); | ||
} | ||
|
||
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace PayPlug\SyliusPayPlugPlugin\Const; | ||
|
||
/** | ||
* Permission list that payplug can return | ||
*/ | ||
final class Permission | ||
{ | ||
public const USE_LIVE_MODE = 'use_live_mode'; | ||
public const CAN_SAVE_CARD = 'can_save_cards'; | ||
public const CAN_CREATE_DEFERRED_PAYMENT = 'can_create_deferred_payment'; | ||
public const CAN_USE_INTEGRATED_PAYMENTS = 'can_use_integrated_payments'; | ||
public const CAN_CREATE_INSTALLMENT_PLAN = 'can_create_installment_plan'; | ||
public const CAN_USE_ONEY = 'can_use_oney'; | ||
|
||
public static function getAll(): array | ||
{ | ||
return [ | ||
self::USE_LIVE_MODE, | ||
self::CAN_SAVE_CARD, | ||
self::CAN_CREATE_DEFERRED_PAYMENT, | ||
self::CAN_USE_INTEGRATED_PAYMENTS, | ||
self::CAN_CREATE_INSTALLMENT_PLAN, | ||
self::CAN_USE_ONEY, | ||
]; | ||
} | ||
|
||
public static function isPermission(string $permission): bool | ||
{ | ||
return in_array($permission, self::getAll(), true); | ||
} | ||
} |
Oops, something went wrong.