diff --git a/README.md b/README.md index 91f254c7..7f1e8df1 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.11.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..188e7024 --- /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 00000000..0244aeba Binary files /dev/null and b/doc/images/admin_deferred_capture_feature.png differ diff --git a/rulesets/phpstan-baseline.neon b/rulesets/phpstan-baseline.neon index 13b6d8a8..409b325f 100644 --- a/rulesets/phpstan-baseline.neon +++ b/rulesets/phpstan-baseline.neon @@ -265,6 +265,11 @@ parameters: count: 1 path: ../src/Gateway/Validator/Constraints/IsCanSaveCardsValidator.php + - + message: "#^Cannot call method getData\\(\\) on mixed\\.$#" + count: 1 + path: ../src/Gateway/Validator/Constraints/PayplugPermissionValidator.php + - message: "#^Method PayPlug\\\\SyliusPayPlugPlugin\\\\Gateway\\\\Validator\\\\Constraints\\\\IsCanSaveCardsValidator\\:\\:validate\\(\\) has parameter \\$value with no type specified\\.$#" count: 1 @@ -330,6 +335,15 @@ 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 \\#1 \\$timestamp of method DateTimeImmutable\\:\\:setTimestamp\\(\\) expects int, mixed given\\.$#" + count: 1 + path: ../src/Action/CaptureAction.php + - message: "#^Parameter \\#2 \\$array of function array_key_exists expects array, mixed given\\.$#" count: 2 diff --git a/src/Action/CaptureAction.php b/src/Action/CaptureAction.php index e104a432..1f47d886 100644 --- a/src/Action/CaptureAction.php +++ b/src/Action/CaptureAction.php @@ -8,6 +8,7 @@ use Payplug\Exception\BadRequestException; use Payplug\Exception\ForbiddenException; use Payplug\Resource\Payment; +use Payplug\Resource\PaymentAuthorization; use PayPlug\SyliusPayPlugPlugin\Action\Api\ApiAwareTrait; use PayPlug\SyliusPayPlugPlugin\ApiClient\PayPlugApiClientInterface; use PayPlug\SyliusPayPlugPlugin\Entity\Card; @@ -178,6 +179,14 @@ public function execute($request): void return; } + $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; + } + $details['status'] = PayPlugApiClientInterface::INTERNAL_STATUS_ONE_CLICK; $details['hosted_payment'] = [ 'payment_url' => $payment->hosted_payment->payment_url, @@ -278,12 +287,16 @@ private function createPayment(ArrayObject $details, PaymentInterface $paymentMo } } + $this->logger->debug('[PayPlug] Create payment', [ + 'detail' => $details->getArrayCopy(), + ]); $payment = $this->payPlugApiClient->createPayment($details->getArrayCopy()); $details['payment_id'] = $payment->id; $details['is_live'] = $payment->is_live; $this->logger->debug('[PayPlug] Create payment', [ 'payment_id' => $payment->id, + 'payment' => (array) $payment, ]); return $payment; diff --git a/src/Checker/CanSaveCardChecker.php b/src/Checker/CanSaveCardChecker.php index 53583a74..1a0bd72f 100644 --- a/src/Checker/CanSaveCardChecker.php +++ b/src/Checker/CanSaveCardChecker.php @@ -12,12 +12,15 @@ class CanSaveCardChecker implements CanSaveCardCheckerInterface { - /** @var CustomerContextInterface */ - private $customerContext; + private CustomerContextInterface $customerContext; + private PayplugFeatureChecker $payplugFeatureChecker; - public function __construct(CustomerContextInterface $customerContext) - { + public function __construct( + CustomerContextInterface $customerContext, + PayplugFeatureChecker $payplugFeatureChecker, + ) { $this->customerContext = $customerContext; + $this->payplugFeatureChecker = $payplugFeatureChecker; } public function isAllowed(PaymentMethodInterface $paymentMethod): bool @@ -26,16 +29,6 @@ public function isAllowed(PaymentMethodInterface $paymentMethod): bool return false; } - $gatewayConfiguration = $paymentMethod->getGatewayConfig(); - - if (!$gatewayConfiguration instanceof GatewayConfigInterface) { - return false; - } - - if (!\array_key_exists(PayPlugGatewayFactory::ONE_CLICK, $gatewayConfiguration->getConfig())) { - return false; - } - - return (bool) $gatewayConfiguration->getConfig()[PayPlugGatewayFactory::ONE_CLICK] ?? false; + return $this->payplugFeatureChecker->isOneClickEnabled($paymentMethod); } } diff --git a/src/Checker/PayplugFeatureChecker.php b/src/Checker/PayplugFeatureChecker.php new file mode 100644 index 00000000..99cbed3b --- /dev/null +++ b/src/Checker/PayplugFeatureChecker.php @@ -0,0 +1,42 @@ +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); + } +} diff --git a/src/Command/CaptureAuthorizedPaymentCommand.php b/src/Command/CaptureAuthorizedPaymentCommand.php new file mode 100644 index 00000000..5fa70d58 --- /dev/null +++ b/src/Command/CaptureAuthorizedPaymentCommand.php @@ -0,0 +1,86 @@ +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; + } +} diff --git a/src/Const/Permission.php b/src/Const/Permission.php new file mode 100644 index 00000000..69970721 --- /dev/null +++ b/src/Const/Permission.php @@ -0,0 +1,35 @@ +canSaveCardChecker = $canSaveCard; $this->payplugCardRepository = $payplugCardRepository; $this->requestStack = $requestStack; + $this->payplugFeatureChecker = $payplugFeatureChecker; } public function create( @@ -93,7 +93,8 @@ public function create( if (PayPlugGatewayFactory::FACTORY_NAME === $gatewayFactoryName && $paymentMethod instanceof PaymentMethodInterface) { $details['allow_save_card'] = false; - $details = $this->alterPayPlugDetails($paymentMethod, $details); + $details = $this->alterPayPlugDetailsForOneClick($paymentMethod, $details); + $details = $this->alterPayPlugDetailsForDeferredCapture($paymentMethod, $details); } if (OneyGatewayFactory::FACTORY_NAME === $gatewayFactoryName) { @@ -233,7 +234,7 @@ private function addShippingInfo( ]; } - private function alterPayPlugDetails(PaymentMethodInterface $paymentMethod, ArrayObject $details): ArrayObject + private function alterPayPlugDetailsForOneClick(PaymentMethodInterface $paymentMethod, ArrayObject $details): ArrayObject { if (!$this->canSaveCardChecker->isAllowed($paymentMethod)) { return $details; @@ -266,6 +267,18 @@ private function alterPayPlugDetails(PaymentMethodInterface $paymentMethod, Arra return $details; } + private function alterPayPlugDetailsForDeferredCapture(PaymentMethodInterface $paymentMethod, ArrayObject $details): ArrayObject + { + if (!$this->payplugFeatureChecker->isDeferredCaptureEnabled($paymentMethod)) { + return $details; + } + + $details['authorized_amount'] = $details['amount']; + unset($details['amount']); + + return $details; + } + private function alterOneyDetails(ArrayObject $details): ArrayObject { $details['payment_method'] = $this->requestStack->getSession()->get('oney_payment_method', 'oney_x3_with_fees'); diff --git a/src/Gateway/Form/Extension/PayPlugGatewayConfigurationTypeExtension.php b/src/Gateway/Form/Extension/PayPlugGatewayConfigurationTypeExtension.php index 181f2b06..dd370b6d 100644 --- a/src/Gateway/Form/Extension/PayPlugGatewayConfigurationTypeExtension.php +++ b/src/Gateway/Form/Extension/PayPlugGatewayConfigurationTypeExtension.php @@ -4,10 +4,11 @@ namespace PayPlug\SyliusPayPlugPlugin\Gateway\Form\Extension; +use PayPlug\SyliusPayPlugPlugin\Const\Permission; use PayPlug\SyliusPayPlugPlugin\Gateway\Form\Type\AbstractGatewayConfigurationType; use PayPlug\SyliusPayPlugPlugin\Gateway\Form\Type\PayPlugGatewayConfigurationType; use PayPlug\SyliusPayPlugPlugin\Gateway\PayPlugGatewayFactory; -use PayPlug\SyliusPayPlugPlugin\Gateway\Validator\Constraints\IsCanSaveCards; +use PayPlug\SyliusPayPlugPlugin\Gateway\Validator\Constraints\PayplugPermission; use Symfony\Component\Form\AbstractTypeExtension; use Symfony\Component\Form\Extension\Core\Type\CheckboxType; use Symfony\Component\Form\FormBuilderInterface; @@ -31,18 +32,28 @@ public function buildForm(FormBuilderInterface $builder, array $options): void { $builder ->add(PayPlugGatewayFactory::ONE_CLICK, CheckboxType::class, [ + 'block_name' => 'payplug_checkbox', 'label' => 'payplug_sylius_payplug_plugin.form.one_click_enable', 'validation_groups' => AbstractGatewayConfigurationType::VALIDATION_GROUPS, - 'constraints' => [ - new IsCanSaveCards(), - ], + 'constraints' => [new PayplugPermission(Permission::CAN_SAVE_CARD)], 'help' => $this->translator->trans('payplug_sylius_payplug_plugin.form.one_click_help'), 'help_html' => true, 'required' => false, ]) ->add(PayPlugGatewayFactory::INTEGRATED_PAYMENT, CheckboxType::class, [ + 'block_name' => 'payplug_checkbox', 'label' => 'payplug_sylius_payplug_plugin.form.integrated_payment_enable', 'validation_groups' => AbstractGatewayConfigurationType::VALIDATION_GROUPS, + 'constraints' => [new PayplugPermission(Permission::CAN_USE_INTEGRATED_PAYMENTS)], + 'required' => false, + ]) + ->add(PayPlugGatewayFactory::DEFERRED_CAPTURE, CheckboxType::class, [ + 'block_name' => 'payplug_checkbox', + 'label' => 'payplug_sylius_payplug_plugin.form.deferred_capture_enable', + 'validation_groups' => AbstractGatewayConfigurationType::VALIDATION_GROUPS, + 'constraints' => [new PayplugPermission(Permission::CAN_CREATE_DEFERRED_PAYMENT)], + 'help' => 'payplug_sylius_payplug_plugin.form.deferred_capture_help', + 'help_html' => true, 'required' => false, ]) ->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event): void { diff --git a/src/Gateway/PayPlugGatewayFactory.php b/src/Gateway/PayPlugGatewayFactory.php index c520e02f..f078acb9 100644 --- a/src/Gateway/PayPlugGatewayFactory.php +++ b/src/Gateway/PayPlugGatewayFactory.php @@ -12,6 +12,7 @@ final class PayPlugGatewayFactory extends AbstractGatewayFactory // Custom gateway configuration keys public const ONE_CLICK = 'oneClick'; public const INTEGRATED_PAYMENT = 'integratedPayment'; + public const DEFERRED_CAPTURE = 'deferredCapture'; public const AUTHORIZED_CURRENCIES = [ 'EUR' => [ diff --git a/src/Gateway/Validator/Constraints/IsCanSaveCards.php b/src/Gateway/Validator/Constraints/IsCanSaveCards.php index b8b0b7e8..d9a9124e 100644 --- a/src/Gateway/Validator/Constraints/IsCanSaveCards.php +++ b/src/Gateway/Validator/Constraints/IsCanSaveCards.php @@ -8,6 +8,8 @@ /** * @Annotation + * + * @deprecated Use PayplugPermission constraint instead */ final class IsCanSaveCards extends Constraint { diff --git a/src/Gateway/Validator/Constraints/IsCanSaveCardsValidator.php b/src/Gateway/Validator/Constraints/IsCanSaveCardsValidator.php index a9b5e281..d520d65a 100644 --- a/src/Gateway/Validator/Constraints/IsCanSaveCardsValidator.php +++ b/src/Gateway/Validator/Constraints/IsCanSaveCardsValidator.php @@ -15,6 +15,7 @@ /** * @Annotation + * @deprecated Use PayplugPermission constraint instead */ final class IsCanSaveCardsValidator extends ConstraintValidator { diff --git a/src/Gateway/Validator/Constraints/PayplugPermission.php b/src/Gateway/Validator/Constraints/PayplugPermission.php new file mode 100644 index 00000000..90346e24 --- /dev/null +++ b/src/Gateway/Validator/Constraints/PayplugPermission.php @@ -0,0 +1,24 @@ +permission = $feature; + $this->message = $message ?? $this->message; + } +} diff --git a/src/Gateway/Validator/Constraints/PayplugPermissionValidator.php b/src/Gateway/Validator/Constraints/PayplugPermissionValidator.php new file mode 100644 index 00000000..f5d5117f --- /dev/null +++ b/src/Gateway/Validator/Constraints/PayplugPermissionValidator.php @@ -0,0 +1,60 @@ +apiClientFactory = $apiClientFactory; + } + + public function validate(mixed $value, Constraint $constraint): void + { + if (!$constraint instanceof PayplugPermission) { + throw new UnexpectedTypeException($constraint, PayplugPermission::class); + } + + if (null === $value || '' === $value) { + return; + } + + if (!is_bool($value)) { + throw new UnexpectedValueException($value, 'boolean'); + } + + if (false === $value) { + return; + } + + $secretKey = $this->context->getRoot()->getData()->getGatewayConfig()->getConfig()['secretKey']; + + try { + $client = $this->apiClientFactory->create(PayPlugGatewayFactory::FACTORY_NAME, $secretKey); + $accountPermissions = $client->getPermissions(); + + if (false === $accountPermissions[$constraint->permission]) { + $this->context + ->buildViolation($constraint->message) + ->addViolation(); + } + + return; + } catch (UnauthorizedException|\LogicException $exception) { + return; + } + } +} diff --git a/src/PaymentProcessing/CaptureAuthorizedPaymentProcessor.php b/src/PaymentProcessing/CaptureAuthorizedPaymentProcessor.php new file mode 100644 index 00000000..186fed60 --- /dev/null +++ b/src/PaymentProcessing/CaptureAuthorizedPaymentProcessor.php @@ -0,0 +1,64 @@ +apiClientFactory = $apiClientFactory; + $this->paymentNotificationHandler = $paymentNotificationHandler; + } + + public function process(PaymentInterface $payment): void + { + $details = new ArrayObject($payment->getDetails()); + $method = $payment->getMethod(); + if (!$method instanceof PaymentMethodInterface) { + return; + } + if (PayPlugGatewayFactory::FACTORY_NAME !== $method->getGatewayConfig()?->getFactoryName()) { + // Not a supported payment method + return; + } + + if (!isset($details['status']) || PayPlugApiClientInterface::STATUS_AUTHORIZED !== $details['status']) { + // Not an authorized payment, do nothing + return; + } + if (!isset($details['payment_id'])) { + // not a payplug payment id ? do nothing + return; + } + $paymentId = $details['payment_id']; + if (!is_string($paymentId)) { + throw new \LogicException('Payment id is not a string'); + } + + $client = $this->apiClientFactory->createForPaymentMethod($method); + $payplugPayment = $client->retrieve($paymentId); + + $updatedPayment = $payplugPayment->capture($client->getConfiguration()); + if (null === $updatedPayment) { + throw new \LogicException('Payment capture failed'); + } + + $this->paymentNotificationHandler->treat($payment, $updatedPayment, $details); + $payment->setDetails($details->getArrayCopy()); + } +} diff --git a/src/Repository/PaymentRepository.php b/src/Repository/PaymentRepository.php index 350e6029..e9793628 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,28 @@ 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)); + + /** @var array */ + 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', $gatewayFactoryName) + ->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/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; + } } diff --git a/src/Resources/config/services.xml b/src/Resources/config/services.xml index 975ace3f..a4a41d4f 100644 --- a/src/Resources/config/services.xml +++ b/src/Resources/config/services.xml @@ -71,6 +71,9 @@ + + + @@ -78,6 +81,11 @@ + + + + + diff --git a/src/Resources/config/services/gateway.xml b/src/Resources/config/services/gateway.xml index fccc46f0..f4e1b187 100644 --- a/src/Resources/config/services/gateway.xml +++ b/src/Resources/config/services/gateway.xml @@ -112,5 +112,10 @@ + + + + diff --git a/src/Resources/config/state_machine/sylius_payment.yml b/src/Resources/config/state_machine/sylius_payment.yml index 39f701a0..3b9b4420 100644 --- a/src/Resources/config/state_machine/sylius_payment.yml +++ b/src/Resources/config/state_machine/sylius_payment.yml @@ -10,3 +10,7 @@ winzou_state_machine: on: ["fail"] do: ["@payplug_sylius_payplug_plugin.payment_processing.abort", "process"] args: ["object"] + payplug_sylius_payplug_plugin_complete: + on: ["complete"] + do: ["@payplug_sylius_payplug_plugin.payment_processing.capture", "process"] + args: ["object"] diff --git a/src/Resources/translations/messages.en.yml b/src/Resources/translations/messages.en.yml index 23918277..44ca681b 100644 --- a/src/Resources/translations/messages.en.yml +++ b/src/Resources/translations/messages.en.yml @@ -106,6 +106,9 @@ payplug_sylius_payplug_plugin: place_order.label: 'Place order' transaction_secure.label: 'Transaction secured by' privacy_policy.label: 'Privacy Policy' + deferred_capture: + process_order_info: | + You will be charged when your order is processed. form: oney_error: Some missing information is required to pay using Oney by Payplug complete_info: @@ -126,3 +129,7 @@ payplug_sylius_payplug_plugin: Allow your customers to save their credit card details for later integrated_payment_enable: Enable payment integrated + + deferred_capture_enable: Enable deferred capture + deferred_capture_help: | + Please ensure that a delayed capture trigger has been added to the project source code. diff --git a/src/Resources/translations/messages.fr.yml b/src/Resources/translations/messages.fr.yml index 3668dcad..ee8e9289 100644 --- a/src/Resources/translations/messages.fr.yml +++ b/src/Resources/translations/messages.fr.yml @@ -125,6 +125,10 @@ payplug_sylius_payplug_plugin: place_order.label: 'Confirmer le paiement' transaction_secure.label: 'Transaction sécurisée par' privacy_policy.label: 'Politique de confidentialité' + deferred_capture: + process_order_info: | + Vous serez prélevé(é) lors du traitement de votre commande. + form: oney_error: Il y a des informations manquantes pour pouvoir payer en utilisant Oney by Payplug complete_info: @@ -146,3 +150,6 @@ payplug_sylius_payplug_plugin: d'autres transactions integrated_payment_enable: Activer le Paiement Integré + deferred_capture_enable: Activer la capture différée + deferred_capture_help: | + Attention, assurez vous qu'un déclencheur de la capture différée a bien été ajouté dans le code source du projet diff --git a/src/Resources/translations/messages.it.yml b/src/Resources/translations/messages.it.yml index 6b221a0b..f8c23bcb 100644 --- a/src/Resources/translations/messages.it.yml +++ b/src/Resources/translations/messages.it.yml @@ -106,6 +106,9 @@ payplug_sylius_payplug_plugin: place_order.label: 'Ordine' transaction_secure.label: 'Transazione protetta da' privacy_policy.label: 'Politica di confidenzialità' + deferred_capture: + process_order_info: | + L'addebito avverrà al momento dell'elaborazione dell'ordine. form: oney_error: Mancano alcune informazioni per poter pagare con “Oney by Payplug” complete_info: @@ -126,3 +129,6 @@ payplug_sylius_payplug_plugin: Consenti ai tuoi clienti di salvare i dettagli della loro carta di credito per dopo integrated_payment_enable: Abilita il pagamento integrato + deferred_capture_enable: Abilita la cattura differita + deferred_capture_help: | + Assicurarsi che nel codice sorgente del progetto sia stato aggiunto un trigger di acquisizione ritardata diff --git a/src/Resources/translations/validators.en.yml b/src/Resources/translations/validators.en.yml index 86aa248c..78facf6b 100644 --- a/src/Resources/translations/validators.en.yml +++ b/src/Resources/translations/validators.en.yml @@ -7,7 +7,12 @@ payplug_sylius_payplug_plugin: Attention. To use the payment method “Oney by Payplug“ in LIVE mode, please contact us at support@payplug.com not_valid_phone_number: This is not a valid cell phone number. - one_click: + permission: + error: | + You do not have access to this feature. For more information, + please contact us at: support@payplug.com + + one_click: # deprecated can_not_save_cards: | You do not have access to this feature. For more information, please contact us at: support@payplug.com diff --git a/src/Resources/translations/validators.fr.yml b/src/Resources/translations/validators.fr.yml index a56c8b87..e2ab2a5c 100644 --- a/src/Resources/translations/validators.fr.yml +++ b/src/Resources/translations/validators.fr.yml @@ -7,10 +7,14 @@ payplug_sylius_payplug_plugin: Vous n'avez pas encore accès à la fonctionnalité du PayLater. Pour utiliser la méthode de paiement 3x 4x Oney par Payplug en mode LIVE, merci de nous contacter à support@payplug.com not_valid_phone_number: Cette valeur n'est pas un numéro de téléphone portable valide. + permission: + error: | + Vous n'avez pas accès à cette fonctionnalité. Pour plus d'informations, + veuillez nous contacter à : support@payplug.com one_click: can_not_save_cards: | Vous n'avez pas accès à cette fonctionnalité. Pour plus d'informations, - veuillez nous contacter à : support@payplug.com + veuillez nous contacter à : support@payplug.com bancontact: can_not_save_method_with_test_key: | Le paiement par Bancontact n’est pas disponible en mode TEST. diff --git a/src/Resources/translations/validators.it.yml b/src/Resources/translations/validators.it.yml index 90eb31d8..1abcd3f6 100644 --- a/src/Resources/translations/validators.it.yml +++ b/src/Resources/translations/validators.it.yml @@ -7,10 +7,14 @@ payplug_sylius_payplug_plugin: Attenzione! Per utilizzare il metodo di pagamento “Oney by Payplug” in modalità LIVE, contattaci al seguente indirizzo: support@payplug.com not_valid_phone_number: Numero di cellulare non valido. + permission: + error: | + Non hai accesso a questa funzionalità. Per ulteriori informazioni, + contattaci al seguente indirizzo e-mail: support@payplug.com one_click: can_not_save_cards: | Non hai accesso a questa funzionalità. Per ulteriori informazioni, - contattaci al seguente indirizzo e-mail: support@payplug.com + contattaci al seguente indirizzo e-mail: support@payplug.com bancontact: can_not_save_method_with_test_key: | Il metodo di pagamento Bancontact non è disponibile in modalità TEST. diff --git a/src/Resources/views/form/form_gateway_config_row.html.twig b/src/Resources/views/form/form_gateway_config_row.html.twig index 9499140e..d4d52219 100644 --- a/src/Resources/views/form/form_gateway_config_row.html.twig +++ b/src/Resources/views/form/form_gateway_config_row.html.twig @@ -60,8 +60,8 @@ {% endblock %} -{% block _sylius_payment_method_gatewayConfig_config_oneClick_row %} -
+{% block _sylius_payment_method_gatewayConfig_config_payplug_checkbox_row %} +
{{- form_label(form) -}} {{- form_widget(form) -}} diff --git a/src/Resources/views/form/integrated.html.twig b/src/Resources/views/form/integrated.html.twig index eeed2b90..b1a30243 100644 --- a/src/Resources/views/form/integrated.html.twig +++ b/src/Resources/views/form/integrated.html.twig @@ -90,9 +90,18 @@
{% endif %} - + +
+ +
+ {% set config = paymentMethod.gatewayConfig.config %} + {% if config.deferredCapture is defined and config.deferredCapture is same as true %} +
+ {{ 'payplug_sylius_payplug_plugin.ui.deferred_capture.process_order_info'|trans }} +
+ {% endif %}