Skip to content

Commit

Permalink
NEXT-31047 - Improve payment handlers
Browse files Browse the repository at this point in the history
  • Loading branch information
mstegmeyer authored and lernhart committed Jun 27, 2024
1 parent 0e36c26 commit 6204df8
Show file tree
Hide file tree
Showing 141 changed files with 4,695 additions and 1,799 deletions.
8 changes: 8 additions & 0 deletions .bc-exclude.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
'**/src/Core/Framework/Adapter/Asset/AssetInstallCommand.php', // symfony configure
'**/src/Core/Framework/App/Payment/Payload/Struct/RecurringPayPayload.php', // missed internal
'**/src/Core/Framework/App/Payment/Payload/Struct/SyncPayPayload.php', // missed internal
'**/src/Core/Checkout/Payment/Cart/PaymentHandler/CashPayment.php', // duplicate class declarations for compatibility reasons
'**/src/Core/Checkout/Payment/Cart/PaymentHandler/DebitPayment.php', // duplicate class declarations for compatibility reasons
'**/src/Core/Checkout/Payment/Cart/PaymentHandler/DefaultPayment.php', // duplicate class declarations for compatibility reasons
'**/src/Core/Checkout/Payment/Cart/PaymentHandler/InvoicePayment.php', // duplicate class declarations for compatibility reasons
'**/src/Core/Checkout/Payment/Cart/PaymentHandler/PrePayment.php', // duplicate class declarations for compatibility reasons
],
'errors' => [
'Shopware\\\\Core\\\\System\\\\SystemConfig\\\\Util\\\\ConfigReader#\\$xsdFile', // Can not be inspected through reflection (__DIR__ constant)
Expand Down Expand Up @@ -43,6 +48,9 @@
'Shopware\\\\Core\\\\Framework\\\\App\\\\Manifest\\\\Xml\\\\Storefront',
'Shopware\\\\Core\\\\Framework\\\\App\\\\Manifest\\\\Xml\\\\MainModule',

// Abstract internal class is not understood
'Shopware\\\\Core\\\\Framework\\\\App\\\\Payment\\\\Response\\\\AbstractResponse',

// Removed property, which was unintentionally added
'Property Shopware\\\\Core\\\\Framework\\\\Rule\\\\Container\\\\OrRule#\\$count was removed',

Expand Down
32 changes: 32 additions & 0 deletions changelog/_unreleased/2024-05-13-improve-payment-handlers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
title: Improve payment handlers & general payment process
issue: NEXT-31047
---
# Core
* Added new `AbstractPaymentHandler` to replace all existing payment handler interfaces
* Deprecated `AsyncPaymentHandlerInterface`, `PreparedPaymentHandlerInterface`, `SyncPaymentHandlerInterface`, `RefundPaymentHandlerInterface`, `RecurringPaymentHandlerInterface`
* Deprecated runtime fields `synchronous`, `asynchronous`, `prepared`, `refund`, `recurring` in `PaymentMethodEntity`
___
# Next Major Version Changes

## Payment: Reworked payment handlers
* The payment handlers have been reworked to provide a more flexible and consistent way to handle payments.
* The new `AbstractPaymentHandler` class should be used to implement payment handlers.
* The following interfaces have been deprecated:
* `AsyncPaymentHandlerInterface`
* `PreparedPaymentHandlerInterface`
* `SyncPaymentHandlerInterface`
* `RefundPaymentHandlerInterface`
* `RecurringPaymentHandlerInterface`
* Synchronous and asynchronous payments have been merged to return an optional redirect response.


## Payment: Capture step of prepared payments removed
* The method `capture` has been removed from the `PreparedPaymentHandler` interface. This method is no longer being called for apps.
* Use the `pay` method instead for capturing previously validated payments.

## App System: Payment: payment states
* For asynchronous payments, the default payment state `unconfirmed` was used for the `pay` call and `paid` for `finalized`. This is no longer the case. Payment states are no longer set by default.

## App system: Payment: finalize step
* The `finalize` step now transmits the `queryParameters` under the object key `requestData` as other payment calls
25 changes: 0 additions & 25 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -1935,31 +1935,6 @@ parameters:
count: 3
path: src/Core/Content/Test/Product/Api/ProductApiTest.php

-
message: "#^Call to static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertNotNull\\(\\) with string will always evaluate to true\\.$#"
count: 4
path: src/Core/Content/Test/Product/Cart/ProductLineItemCommandValidatorTest.php

-
message: "#^Cannot call method getPayload\\(\\) on Shopware\\\\Core\\\\Framework\\\\DataAbstractionLayer\\\\Entity\\|null\\.$#"
count: 1
path: src/Core/Content/Test/Product/Cart/ProductLineItemCommandValidatorTest.php

-
message: "#^Cannot call method getProductId\\(\\) on Shopware\\\\Core\\\\Framework\\\\DataAbstractionLayer\\\\Entity\\|null\\.$#"
count: 1
path: src/Core/Content/Test/Product/Cart/ProductLineItemCommandValidatorTest.php

-
message: "#^Cannot call method getQuantity\\(\\) on Shopware\\\\Core\\\\Framework\\\\DataAbstractionLayer\\\\Entity\\|null\\.$#"
count: 1
path: src/Core/Content/Test/Product/Cart/ProductLineItemCommandValidatorTest.php

-
message: "#^Cannot call method getReferencedId\\(\\) on Shopware\\\\Core\\\\Framework\\\\DataAbstractionLayer\\\\Entity\\|null\\.$#"
count: 1
path: src/Core/Content/Test/Product/Cart/ProductLineItemCommandValidatorTest.php

-
message: "#^Call to static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertInstanceOf\\(\\) with arguments 'Shopware\\\\\\\\Core\\\\\\\\Checkout\\\\\\\\Cart\\\\\\\\Price\\\\\\\\Struct\\\\\\\\CalculatedPrice', Shopware\\\\Core\\\\Checkout\\\\Cart\\\\Price\\\\Struct\\\\CalculatedPrice and string will always evaluate to true\\.$#"
count: 2
Expand Down
8 changes: 7 additions & 1 deletion phpstan.neon.dist
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@ parameters:
- src/Core/Test/Stub/Redis/RedisCompatibility.php
- src/Core/Test/Stub/Redis/RedisMultiCompatibility.php

# tag:v6.7.0 - does not fully understand overwrite multiple definitions of DefaultPayment
- src/Core/Checkout/Payment/Cart/PaymentHandler/DebitPayment.php
- src/Core/Checkout/Payment/Cart/PaymentHandler/DefaultPayment.php
- src/Core/Checkout/Payment/Cart/PaymentHandler/InvoicePayment.php
- src/Core/Checkout/Payment/Cart/PaymentHandler/PrePayment.php

ignoreErrors:
# The symfony extension checks against the "normal" container, not the test container
# Therefore some services in the tests are not found and the extension can not infer that all private services are public during test execution
Expand Down Expand Up @@ -175,7 +181,7 @@ parameters:
- src/Core/System/NumberRange/NumberRangeDefinition.php

# Internal deprecations of Shopware are handled in other places
- '#deprecated.*class Shopware\\#'
- '#deprecated.*(class|interface) Shopware\\#'

- # tests are allowed to add dummy classes in the same file
message: '#Multiple class\/interface\/trait is not allowed in single file#'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -667,6 +667,7 @@ describe('module/sw-import-export/components/sw-import-export-entity-path-select
'transactions.stateId',
'transactions.stateMachineState',
'transactions.updatedAt',
'transactions.validationData',
'transactions.versionId',
]);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
class TransactionTransformer
{
/**
* @return array<int, array<string, string|CalculatedPrice>>
* @return array<int, array<string, string|CalculatedPrice|array<array-key, mixed>|null>>
*/
public static function transformCollection(
TransactionCollection $transactions,
Expand All @@ -28,7 +28,7 @@ public static function transformCollection(
}

/**
* @return array<string, string|CalculatedPrice>
* @return array<string, string|CalculatedPrice|array<array-key, mixed>|null>
*/
public static function transform(
Transaction $transaction,
Expand All @@ -39,6 +39,7 @@ public static function transform(
'paymentMethodId' => $transaction->getPaymentMethodId(),
'amount' => $transaction->getAmount(),
'stateId' => $stateId,
'validationData' => $transaction->getValidationStruct()?->jsonSerialize(),
];
}
}
20 changes: 13 additions & 7 deletions src/Core/Checkout/Cart/SalesChannel/CartOrderRoute.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@
use Shopware\Core\Checkout\Order\OrderEntity;
use Shopware\Core\Checkout\Order\SalesChannel\OrderService;
use Shopware\Core\Checkout\Payment\PaymentException;
use Shopware\Core\Checkout\Payment\PaymentProcessor;
use Shopware\Core\Checkout\Payment\PreparedPaymentService;
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\Feature;
use Shopware\Core\Framework\Log\Package;
use Shopware\Core\Framework\Plugin\Exception\DecorationPatternException;
use Shopware\Core\Framework\Validation\DataBag\DataBag;
Expand All @@ -43,6 +45,7 @@ public function __construct(
private readonly AbstractCartPersister $cartPersister,
private readonly EventDispatcherInterface $eventDispatcher,
private readonly PreparedPaymentService $preparedPaymentService,
private readonly PaymentProcessor $paymentProcessor,
private readonly TaxProviderProcessor $taxProviderProcessor,
private readonly AbstractCheckoutGatewayRoute $checkoutGatewayRoute,
private readonly CartContextHasher $cartContextHasher,
Expand Down Expand Up @@ -76,7 +79,7 @@ public function order(Cart $cart, SalesChannelContext $context, RequestDataBag $
$this->addCustomerComment($calculatedCart, $data);
$this->addAffiliateTracking($calculatedCart, $data);

$preOrderPayment = Profiler::trace('checkout-order::pre-payment', fn () => $this->preparedPaymentService->handlePreOrderPayment($calculatedCart, $data, $context));
$preOrderPayment = Profiler::trace('checkout-order::pre-payment', fn () => $this->paymentProcessor->validate($calculatedCart, $data, $context));

$orderId = Profiler::trace('checkout-order::order-persist', fn () => $this->orderPersister->persist($calculatedCart, $context));

Expand Down Expand Up @@ -120,12 +123,15 @@ public function order(Cart $cart, SalesChannelContext $context, RequestDataBag $

$this->cartPersister->delete($context->getToken(), $context);

try {
Profiler::trace('checkout-order::post-payment', function () use ($orderEntity, $data, $context, $preOrderPayment): void {
$this->preparedPaymentService->handlePostOrderPayment($orderEntity, $data, $context, $preOrderPayment);
});
} catch (PaymentException) {
throw CartException::invalidPaymentButOrderStored($orderId);
// @deprecated tag:v6.7.0 - remove post payment completely
if (!Feature::isActive('v6.7.0.0')) {
try {
Profiler::trace('checkout-order::post-payment', function () use ($orderEntity, $data, $context, $preOrderPayment): void {
$this->preparedPaymentService->handlePostOrderPayment($orderEntity, $data, $context, $preOrderPayment);
});
} catch (PaymentException) {
throw CartException::invalidPaymentButOrderStored($orderId);
}
}

return new CartOrderRouteResponse($orderEntity);
Expand Down
12 changes: 12 additions & 0 deletions src/Core/Checkout/Cart/Transaction/Struct/Transaction.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ class Transaction extends Struct
*/
protected $paymentMethodId;

protected ?Struct $validationStruct = null;

public function __construct(
CalculatedPrice $amount,
string $paymentMethodId
Expand Down Expand Up @@ -47,6 +49,16 @@ public function setPaymentMethodId(string $paymentMethodId): void
$this->paymentMethodId = $paymentMethodId;
}

public function getValidationStruct(): ?Struct
{
return $this->validationStruct;
}

public function setValidationStruct(?Struct $validationStruct): void
{
$this->validationStruct = $validationStruct;
}

public function getApiAlias(): string
{
return 'cart_transaction';
Expand Down
1 change: 1 addition & 0 deletions src/Core/Checkout/DependencyInjection/cart.xml
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@
<argument type="service" id="Shopware\Core\Checkout\Cart\CartPersister"/>
<argument type="service" id="event_dispatcher"/>
<argument type="service" id="Shopware\Core\Checkout\Payment\PreparedPaymentService"/>
<argument type="service" id="Shopware\Core\Checkout\Payment\PaymentProcessor"/>
<argument type="service" id="Shopware\Core\Checkout\Cart\TaxProvider\TaxProviderProcessor"/>
<argument type="service" id="Shopware\Core\Checkout\Gateway\SalesChannel\CheckoutGatewayRoute"/>
<argument type="service" id="Shopware\Core\Checkout\Cart\CartContextHasher"/>
Expand Down
28 changes: 26 additions & 2 deletions src/Core/Checkout/DependencyInjection/payment.xml
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,21 @@
<argument type="service" id="Shopware\Core\Checkout\Payment\Cart\PaymentTransactionStructFactory"/>
</service>

<service id="Shopware\Core\Checkout\Payment\Controller\PaymentController" public="true">
<service id="Shopware\Core\Checkout\Payment\PaymentProcessor">
<argument type="service" id="Shopware\Core\Checkout\Payment\Cart\Token\JWTFactoryV2"/>
<argument type="service" id="Shopware\Core\Checkout\Payment\Cart\PaymentHandler\PaymentHandlerRegistry"/>
<argument type="service" id="order_transaction.repository"/>
<argument type="service" id="Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionStateHandler"/>
<argument type="service" id="logger"/>
<argument type="service" id="Shopware\Core\Checkout\Payment\Cart\PaymentTransactionStructFactory"/>
<argument type="service" id="Shopware\Core\System\StateMachine\Loader\InitialStateIdLoader"/>
<argument type="service" id="router"/>
<argument type="service" id="Shopware\Core\System\SystemConfig\SystemConfigService"/>
<argument type="service" id="Shopware\Core\Checkout\Payment\PaymentService"/>
</service>

<service id="Shopware\Core\Checkout\Payment\Controller\PaymentController" public="true">
<argument type="service" id="Shopware\Core\Checkout\Payment\PaymentProcessor"/>
<argument type="service" id="Shopware\Core\Checkout\Cart\Order\OrderConverter"/>
<argument type="service" id="Shopware\Core\Checkout\Payment\Cart\Token\JWTFactoryV2"/>
<argument type="service" id="order.repository"/>
Expand All @@ -71,15 +84,18 @@
<argument type="service" id="Doctrine\DBAL\Connection"/>
<argument type="service" id="Shopware\Core\Checkout\Order\Aggregate\OrderTransactionCaptureRefund\OrderTransactionCaptureRefundStateHandler"/>
<argument type="service" id="Shopware\Core\Checkout\Payment\Cart\PaymentHandler\PaymentHandlerRegistry"/>
<argument type="service" id="Shopware\Core\Checkout\Payment\Cart\PaymentTransactionStructFactory"/>
</service>

<service id="Shopware\Core\Checkout\Payment\Cart\PaymentRecurringProcessor" public="true">
<argument type="service" id="order.repository"/>
<argument type="service" id="order_transaction.repository"/>
<argument type="service" id="Shopware\Core\System\StateMachine\Loader\InitialStateIdLoader"/>
<argument type="service" id="Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionStateHandler"/>
<argument type="service" id="Shopware\Core\Checkout\Payment\Cart\PaymentHandler\PaymentHandlerRegistry"/>
<argument type="service" id="Shopware\Core\Checkout\Payment\Cart\PaymentTransactionStructFactory"/>
<argument type="service" id="event_dispatcher"/>
<argument type="service" id="logger"/>
</service>

<service id="shopware.jwt_signer" class="Lcobucci\JWT\Signer\Rsa\Sha256">
Expand All @@ -92,6 +108,7 @@
</service>

<service id="Shopware\Core\Checkout\Payment\Cart\PaymentHandler\PaymentHandlerRegistry">
<argument type="tagged_locator" tag="shopware.payment.method"/>
<argument type="tagged_locator" tag="shopware.payment.method.sync"/>
<argument type="tagged_locator" tag="shopware.payment.method.async"/>
<argument type="tagged_locator" tag="shopware.payment.method.prepared"/>
Expand All @@ -104,30 +121,35 @@
<argument type="service" id="Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionStateHandler"/>

<tag name="shopware.payment.method.sync"/>
<tag name="shopware.payment.method"/>
</service>

<service id="Shopware\Core\Checkout\Payment\Cart\PaymentHandler\DebitPayment">
<argument type="service" id="Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionStateHandler"/>

<tag name="shopware.payment.method.sync"/>
<tag name="shopware.payment.method"/>
</service>

<service id="Shopware\Core\Checkout\Payment\Cart\PaymentHandler\CashPayment">
<argument type="service" id="Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionStateHandler"/>

<tag name="shopware.payment.method.sync"/>
<tag name="shopware.payment.method"/>
</service>

<service id="Shopware\Core\Checkout\Payment\Cart\PaymentHandler\InvoicePayment">
<argument type="service" id="Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionStateHandler"/>

<tag name="shopware.payment.method.sync"/>
<tag name="shopware.payment.method"/>
</service>

<service id="Shopware\Core\Checkout\Payment\Cart\PaymentHandler\DefaultPayment">
<argument type="service" id="Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionStateHandler"/>

<tag name="shopware.payment.method.sync"/>
<tag name="shopware.payment.method"/>
</service>

<service id="Shopware\Core\Checkout\Payment\DataAbstractionLayer\PaymentHandlerIdentifierSubscriber">
Expand Down Expand Up @@ -175,8 +197,10 @@
</service>

<service id="Shopware\Core\Checkout\Payment\SalesChannel\HandlePaymentMethodRoute" public="true">
<argument type="service" id="Shopware\Core\Checkout\Payment\PaymentService"/>
<argument type="service" id="Shopware\Core\Checkout\Payment\PaymentProcessor"/>
<argument type="service" id="Shopware\Core\Framework\Validation\DataValidator"/>
<argument type="service" id="Shopware\Core\System\SalesChannel\Context\SalesChannelContextService"/>
<argument type="service" id="currency.repository"/>
</service>
</services>
</container>
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Shopware\Core\Framework\DataAbstractionLayer\Field\Flag\PrimaryKey;
use Shopware\Core\Framework\DataAbstractionLayer\Field\Flag\Required;
use Shopware\Core\Framework\DataAbstractionLayer\Field\IdField;
use Shopware\Core\Framework\DataAbstractionLayer\Field\JsonField;
use Shopware\Core\Framework\DataAbstractionLayer\Field\ManyToOneAssociationField;
use Shopware\Core\Framework\DataAbstractionLayer\Field\OneToManyAssociationField;
use Shopware\Core\Framework\DataAbstractionLayer\Field\ReferenceVersionField;
Expand Down Expand Up @@ -62,6 +63,7 @@ protected function defineFields(): FieldCollection
(new ReferenceVersionField(OrderDefinition::class))->addFlags(new ApiAware(), new Required()),
(new FkField('payment_method_id', 'paymentMethodId', PaymentMethodDefinition::class))->addFlags(new ApiAware(), new Required()),
(new CalculatedPriceField('amount', 'amount'))->addFlags(new ApiAware(), new Required()),
(new JsonField('validation_data', 'validationData'))->addFlags(new ApiAware()),

(new StateMachineStateField('state_id', 'stateId', OrderTransactionStates::STATE_MACHINE))->addFlags(new ApiAware(), new Required()),
(new ManyToOneAssociationField('stateMachineState', 'state_id', StateMachineStateDefinition::class, 'id'))->addFlags(new ApiAware()),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ class OrderTransactionEntity extends Entity

protected ?OrderTransactionCaptureCollection $captures = null;

/**
* @var array<string, mixed>
*/
protected array $validationData = [];

public function getOrderId(): string
{
return $this->orderId;
Expand Down Expand Up @@ -149,4 +154,20 @@ public function setOrderVersionId(string $orderVersionId): void
{
$this->orderVersionId = $orderVersionId;
}

/**
* @return array<string, mixed>
*/
public function getValidationData(): array
{
return $this->validationData;
}

/**
* @param array<string, mixed> $validationData
*/
public function setValidationData(array $validationData): void
{
$this->validationData = $validationData;
}
}
Loading

0 comments on commit 6204df8

Please sign in to comment.