-
Notifications
You must be signed in to change notification settings - Fork 1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
NEXT-25815 - Recurring payment handler
- Loading branch information
Showing
57 changed files
with
1,998 additions
and
51 deletions.
There are no files selected for viewing
16 changes: 16 additions & 0 deletions
16
changelog/_unreleased/2023-06-13-recurring-payment-handler.md
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,16 @@ | ||
--- | ||
title: Recurring payment handler | ||
issue: NEXT-25815 | ||
author: Lennart Tinkloh | ||
author_email: l.tinkloh@shopware.com | ||
author_github: @lernhart | ||
--- | ||
# Core | ||
* Added `Shopware\Core\Checkout\Payment\Cart\PaymentHandler\RecurringPaymentHandlerInterface` to handle recurring payment captures from subscriptions. | ||
* Added `Shopware\Core\Checkout\Payment\Cart\PaymentHandler\PaymentHandlerRegistry::getRecurringPaymentHandler` to retrieve the recurring payment handler for a given payment method. | ||
* Added `Shopware\Core\Checkout\Payment\Cart\PaymentRecurringProcessor`, which is responsible for processing recurring payments and calling the payment handler. | ||
* Added `Shopware\Core\Framework\App\Payment\Handler\AppPaymentHandler::captureRecurring`, which handles app payment method and calls the app endpoint with the recurring payload. | ||
* Added `Shopware\Core\Checkout\Payment\Cart\RecurringPaymentTransactionStruct`, which is the payload sent to app endpoints during recurring capture for app payment methods. | ||
* Added `Shopware\Core\Checkout\Payment\Exception\RecurringPaymentProcessException` to signalize errors occurring during recurring payment captures. | ||
* Added `shopware.payment.method.recurring` service tag to allow plugins to add recurring payment methods. | ||
* Added `recurring_url` to app manifests to allow apps to add a recurring captured payment method. |
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
24 changes: 24 additions & 0 deletions
24
src/Core/Checkout/Payment/Cart/AbstractPaymentTransactionStructFactory.php
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,24 @@ | ||
<?php declare(strict_types=1); | ||
|
||
namespace Shopware\Core\Checkout\Payment\Cart; | ||
|
||
use Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionEntity; | ||
use Shopware\Core\Checkout\Order\OrderEntity; | ||
use Shopware\Core\Framework\Log\Package; | ||
|
||
/** | ||
* This factory is intended to be decorated in order to manipulate the structs that are used in the payment process ny the payment handlers | ||
*/ | ||
#[Package('checkout')] | ||
abstract class AbstractPaymentTransactionStructFactory | ||
{ | ||
abstract public function getDecorated(): AbstractPaymentTransactionStructFactory; | ||
|
||
abstract public function sync(OrderTransactionEntity $orderTransaction, OrderEntity $order): SyncPaymentTransactionStruct; | ||
|
||
abstract public function async(OrderTransactionEntity $orderTransaction, OrderEntity $order, string $returnUrl): AsyncPaymentTransactionStruct; | ||
|
||
abstract public function prepared(OrderTransactionEntity $orderTransaction, OrderEntity $order): PreparedPaymentTransactionStruct; | ||
|
||
abstract public function recurring(OrderTransactionEntity $orderTransaction, OrderEntity $order): RecurringPaymentTransactionStruct; | ||
} |
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
25 changes: 25 additions & 0 deletions
25
src/Core/Checkout/Payment/Cart/PaymentHandler/RecurringPaymentHandlerInterface.php
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,25 @@ | ||
<?php declare(strict_types=1); | ||
|
||
namespace Shopware\Core\Checkout\Payment\Cart\PaymentHandler; | ||
|
||
use Shopware\Core\Checkout\Payment\Cart\RecurringPaymentTransactionStruct; | ||
use Shopware\Core\Checkout\Payment\PaymentException; | ||
use Shopware\Core\Framework\Context; | ||
use Shopware\Core\Framework\Log\Package; | ||
|
||
#[Package('checkout')] | ||
interface RecurringPaymentHandlerInterface extends PaymentHandlerInterface | ||
{ | ||
/** | ||
* The captureRecurring function is called for every recurring payment of a subscription. | ||
* A successful billing agreement with the payment provider should exist at this moment. | ||
* Initial billing agreements should be handled via the other payment methods | ||
* (@see SynchronousPaymentHandlerInterface, AsynchronousPaymentHandlerInterface for instance). | ||
* The handler should only be called in the background by scheduled tasks, etc. | ||
* | ||
* Throw a @see PaymentException::recurringInterrupted() exception if an error ocurres while processing the payment | ||
* | ||
* @throws PaymentException | ||
*/ | ||
public function captureRecurring(RecurringPaymentTransactionStruct $transaction, Context $context): void; | ||
} |
89 changes: 89 additions & 0 deletions
89
src/Core/Checkout/Payment/Cart/PaymentRecurringProcessor.php
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,89 @@ | ||
<?php declare(strict_types=1); | ||
|
||
namespace Shopware\Core\Checkout\Payment\Cart; | ||
|
||
use Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionStateHandler; | ||
use Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionStates; | ||
use Shopware\Core\Checkout\Order\OrderEntity; | ||
use Shopware\Core\Checkout\Order\OrderException; | ||
use Shopware\Core\Checkout\Payment\Cart\PaymentHandler\PaymentHandlerRegistry; | ||
use Shopware\Core\Checkout\Payment\PaymentException; | ||
use Shopware\Core\Framework\Context; | ||
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository; | ||
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria; | ||
use Shopware\Core\Framework\DataAbstractionLayer\Search\Sorting\FieldSorting; | ||
use Shopware\Core\Framework\Log\Package; | ||
use Shopware\Core\System\StateMachine\Loader\InitialStateIdLoader; | ||
|
||
#[Package('checkout')] | ||
class PaymentRecurringProcessor | ||
{ | ||
/** | ||
* @internal | ||
*/ | ||
public function __construct( | ||
private readonly EntityRepository $orderRepository, | ||
private readonly InitialStateIdLoader $initialStateIdLoader, | ||
private readonly OrderTransactionStateHandler $stateHandler, | ||
private readonly PaymentHandlerRegistry $paymentHandlerRegistry, | ||
private readonly AbstractPaymentTransactionStructFactory $paymentTransactionStructFactory, | ||
) { | ||
} | ||
|
||
public function processRecurring(string $orderId, Context $context): void | ||
{ | ||
$criteria = new Criteria([$orderId]); | ||
$criteria->addAssociation('transactions.stateMachineState'); | ||
$criteria->addAssociation('transactions.paymentMethod'); | ||
$criteria->addAssociation('orderCustomer.customer'); | ||
$criteria->addAssociation('orderCustomer.salutation'); | ||
$criteria->addAssociation('transactions.paymentMethod.appPaymentMethod.app'); | ||
$criteria->addAssociation('language'); | ||
$criteria->addAssociation('currency'); | ||
$criteria->addAssociation('deliveries.shippingOrderAddress.country'); | ||
$criteria->addAssociation('billingAddress.country'); | ||
$criteria->addAssociation('lineItems'); | ||
$criteria->getAssociation('transactions')->addSorting(new FieldSorting('createdAt')); | ||
|
||
/** @var OrderEntity $order */ | ||
$order = $this->orderRepository->search($criteria, $context)->first(); | ||
|
||
if (!$order) { | ||
throw OrderException::orderNotFound($orderId); | ||
} | ||
|
||
$transactions = $order->getTransactions(); | ||
if ($transactions === null) { | ||
throw OrderException::missingTransactions($orderId); | ||
} | ||
|
||
$transactions = $transactions->filterByStateId( | ||
$this->initialStateIdLoader->get(OrderTransactionStates::STATE_MACHINE) | ||
); | ||
|
||
$transaction = $transactions->last(); | ||
if ($transaction === null) { | ||
return; | ||
} | ||
|
||
$paymentMethod = $transaction->getPaymentMethod(); | ||
if ($paymentMethod === null) { | ||
throw PaymentException::unknownPaymentMethod($transaction->getPaymentMethodId()); | ||
} | ||
|
||
$paymentHandler = $this->paymentHandlerRegistry->getRecurringPaymentHandler($paymentMethod->getId()); | ||
if (!$paymentHandler) { | ||
throw PaymentException::unknownPaymentMethod($paymentMethod->getHandlerIdentifier()); | ||
} | ||
|
||
$struct = $this->paymentTransactionStructFactory->recurring($transaction, $order); | ||
|
||
try { | ||
$paymentHandler->captureRecurring($struct, $context); | ||
} catch (PaymentException $e) { | ||
$this->stateHandler->fail($transaction->getId(), $context); | ||
|
||
throw $e; | ||
} | ||
} | ||
} |
Oops, something went wrong.