Integrate ACP payment apps with Spryker OMS configuration
Edit on GitHubThis document describes how to set up your project with the ACP payment app, ensuring seamless integration with your customized Order Management System (OMS) configuration.
Prerequisites
- Install the Order Management feature.
- Learn the basics of OMS described in Order Management feature overview, State machine cookbook, and their sub-pages.
Default ACP payment app OMS
The default ACP payment app OMS configuration is located in vendor/spryker/sales-payment/config/Zed/Oms/ForeignPaymentStateMachine01.xml
.
This configuration is assigned to each order paid with an ACP payment app payment method. Use it as a starting point to build your configuration.
XML file structure
The main OMS file includes <subprocesses>
, which function like libraries or building blocks containing their own <states>
, <transitions>
, and <events>
.
Subprocesses
A typical state machine consists of a couple of subprocesses that represent a complete order process.
Example of how to add subprocesses:
<subprocesses>
<process name="PaymentAuthorization" file="Subprocess/PaymentAuthorization01.xml"/>
<!-- Other subprocesses -->
</subprocesses>
Each process in the <subprocesses>
section has a start state, one or more final states, and the states are linked to each other in the main state machine XML file.
Subprocesses used by ForeignPaymentStateMachine01:
ItemClose01
ItemReturn01
ItemSupply01
ItemSupplyNonMarketplace01
- Marketplace-only:
MerchantOrder01
- Marketplace-only:
MerchantPayout01
- Marketplace-only:
MerchantPayoutReverse01
PaymentCancel01
PaymentCapture01
PaymentRefund01
PaymentRefund01NonMarketplace
You can use them as examples or building blocks for your own state machine definition.
Transition of orders and order items
Transitions between states in payment apps can be handled in two ways:
Approach | Description | Setup |
---|---|---|
Event-based | Trigger events based on Payment App messages. | Add Spryker\Zed\Payment\Communication\Plugin\MessageBroker\PaymentOperationsMessageHandlerPlugin to MessageBrokerDependencyProvider . |
Conditional | Use conditional transitions based on Payment App messages and condition plugins. | Add Spryker\Zed\PaymentApp\Communication\Plugin\MessageBroker\PaymentAppOperationsMessageHandlerPlugin to MessageBrokerDependencyProvider along with the necessary condition plugins. |
The following sections provide detailed instructions for configuring each option.
Transition based on event triggers
The Spryker\Zed\Payment\Communication\Plugin\MessageBroker\PaymentOperationsMessageHandlerPlugin
contains message handlers that trigger events based on messages received from the payment app.
State transitions occur automatically through asynchronous ACP messages, processed by the spryker/message-broker
module. The following sub-processes have auto-transitions:
PaymentAuthorization
PaymentCapture
PaymentRefund
PaymentCancel
The MessageBroker worker runs in the background as a cron job, checking for new messages and triggering OMS events based on the configuration in Spryker\Zed\Payment\PaymentConfig::getSupportedOrderPaymentEventTransfersList()
. You can change this configuration to fit your project’s needs.
Use this approach if your configuration is similar to the default ForeignPaymentStateMachine01
, particularly if your OMS doesn’t contain slow-running commands that could cause transition failures. The payment status on the app side can change rapidly, which may cause issues if your OMS is not in the correct state for the transition. For example, if a command between PaymentAuthorized
and PaymentCapturePending
takes too long to process and a message arrives attempting to transition from PaymentCapturePending
to PaymentCaptured
, the OMS may not be able to complete the transition because it is not yet in the required state.
Conditional transitions
This setup enables the payment app to progress through states independently of OMS while ensuring that OMS conditions execute correctly. For example, if the payment app status advances to PaymentCaptured
while the OMS remains in the new
state, querying whether the payment is authorized returns true because authorization occurs before the PaymentCaptured
state.
Spryker\Zed\PaymentApp\Communication\Plugin\MessageBroker\PaymentAppOperationsMessageHandlerPlugin
contains message handlers that store the payment status provided by the app. The OMS evaluates this status against predefined conditions to determine if the transition to the next state is possible.
The following condition plugins verify if the payment is in a needed state before allowing a transition:
IsPaymentAppPaymentStatusAuthorizationFailedConditionPlugin
IsPaymentAppPaymentStatusAuthorizedConditionPlugin
IsPaymentAppPaymentStatusCanceledConditionPlugin
IsPaymentAppPaymentStatusCancellationFailedConditionPlugin
IsPaymentAppPaymentStatusCapturedConditionPlugin
IsPaymentAppPaymentStatusCaptureFailedConditionPlugin
IsPaymentAppPaymentStatusCaptureRequestedConditionPlugin
IsPaymentAppPaymentStatusOverpaidConditionPlugin
IsPaymentAppPaymentStatusUnderpaidConditionPlugin
This approach decouples OMS from the payment app, enabling you to run slow-processing commands within the OMS while maintaining fine-grained control over state transitions.
To use this approach, add the provided condition plugins to your OMS configuration in the OmsDependencyProvider::extendConditionPlugins
method.
If some transitions don’t occur as expected, you can adjust the configuration using the Spryker\Zed\PaymentApp\PaymentAppConfig::STATUS_MAP
constant.
Automatic transitions of states
The following predefined payment event messages apply to all payment methods from the ACP App Catalog:
PaymentAuthorized
PaymentAuthorizationFailed
PaymentCanceled
PaymentCancellationFailed
PaymentCaptured
PaymentCaptureFailed
PaymentRefunded
PaymentRefundFailed
PaymentOverpaid
PaymentUnderpaid
Configure them in config_default.php
.
If a payment operation occurs offline or changes in the payment provider’s system but the corresponding message is not sent or is lost, you can manually trigger a transition in the Back Office. For instructions on changing states of orders, see Change the state of order items.
For example, if an order gets stuck in the payment capture pending
state, a Back office user can resolve the payment by clicking payment capture successful to transition the order to the payment captured
state.
Payment operation commands
The following default commands are triggered from OMS to instruct the Payment App on the next steps:
Payment/Capture
Payment/Cancel
Payment/Refund
The commands send asynchronous ACP messages to a payment app, allowing it to schedule the requested operation for a specific amount for the selected order and its items. The payment app responds with a payment event message indicating either success or failure.
The list of payment command messages is predefined:
CapturePayment
CancelPayment
RefundPayment
In the project OMS configuration, you can put these commands into the needed transition.
Configuring OMS for your project
As each project has its unique order flow and use cases, customize your OMS configuration while ensuring compatibility with ACP Catalog payment methods. Follow these steps:
- Create your own payment OMS configuration based on
ForeignPaymentStateMachine01.xml
. Copy this file along with theSubprocess/
folder to the project rootconfig/Zed/oms
. - Rename the file, for example to
MyProjectForeignPaymentStateMachine01.xml
and update the<process name="MyProjectForeignPaymentStateMachine01">
tag at the beginning of the file. - Enable your new OMS configuration in the application config file:
// config_default.php
$config[OmsConstants::PROCESS_LOCATION] = [
OmsConfig::DEFAULT_PROCESS_LOCATION, # check that you have this line
];
$config[OmsConstants::ACTIVE_PROCESSES] = [
//...
'MyProjectForeignPaymentStateMachine01',
];
$config[SalesConstants::PAYMENT_METHOD_STATEMACHINE_MAPPING] = [
//...
PaymentConfig::PAYMENT_FOREIGN_PROVIDER => 'MyProjectForeignPaymentStateMachine01',
];
Adding commands
You can add new commands to the OMS configuration by extending the OmsDependencyProvider::extendCommandPlugins
method. Example:
...
protected function extendCommandPlugins(Container $container): Container
{
$container->extend(self::COMMAND_PLUGINS, function (CommandCollectionInterface $commandCollection) {
...
// ----- External PSP
$commandCollection->add(new SendCapturePaymentMessageCommandPlugin(), 'Payment/Capture');
$commandCollection->add(new SendRefundPaymentMessageCommandPlugin(), 'Payment/Refund');
$commandCollection->add(new RefundCommandPlugin(), 'Payment/Refund/Confirm');
$commandCollection->add(new SendCancelPaymentMessageCommandPlugin(), 'Payment/Cancel');
$commandCollection->add(new MerchantPayoutCommandByOrderPlugin(), 'SalesPaymentMerchant/Payout');
$commandCollection->add(new MerchantPayoutReverseCommandByOrderPlugin(), 'SalesPaymentMerchant/ReversePayout');
return $commandCollection;
});
return $container;
}
Adding conditions
You can add new conditions to the OMS configuration by extending the OmsDependencyProvider::extendConditionPlugins
method. Example:
protected function extendConditionPlugins(Container $container): Container // phpcs:ignore SlevomatCodingStandard.Functions.UnusedParameter
{
$container->extend(self::CONDITION_PLUGINS, function (ConditionCollectionInterface $conditionCollection) {
// ----- External PSP
$conditionCollection->add(new IsMerchantPaidOutConditionPlugin(), 'SalesPaymentMerchant/IsMerchantPaidOut');
$conditionCollection->add(new IsMerchantPayoutReversedConditionPlugin(), 'SalesPaymentMerchant/IsMerchantPayoutReversed');
// ----- External PSP V2 Condition to ask for payment state instead of getting it told by the PSP
$conditionCollection->add(new IsPaymentAppPaymentStatusAuthorizationFailedConditionPlugin(), 'Payment/IsAuthorizationFailed');
$conditionCollection->add(new IsPaymentAppPaymentStatusAuthorizedConditionPlugin(), 'Payment/IsAuthorized');
$conditionCollection->add(new IsPaymentAppPaymentStatusCanceledConditionPlugin(), 'Payment/IsCanceled');
$conditionCollection->add(new IsPaymentAppPaymentStatusCancellationFailedConditionPlugin(), 'Payment/IsCancellationFailed');
$conditionCollection->add(new IsPaymentAppPaymentStatusCapturedConditionPlugin(), 'Payment/IsCaptured');
$conditionCollection->add(new IsPaymentAppPaymentStatusCaptureFailedConditionPlugin(), 'Payment/IsCaptureFailed');
$conditionCollection->add(new IsPaymentAppPaymentStatusCaptureRequestedConditionPlugin(), 'Payment/IsCaptureRequested');
$conditionCollection->add(new IsPaymentAppPaymentStatusOverpaidConditionPlugin(), 'Payment/IsOverpaid');
$conditionCollection->add(new IsPaymentAppPaymentStatusUnderpaidConditionPlugin(), 'Payment/IsUnderpaid');
return $conditionCollection;
});
return $container;
}
Customizing for your business flow
You have several options to customize your business flow:
- Without shipping
- Without return and refund
- Without payment authorization and cancellation
OMS without shipping
If your project doesn’t involve product shipping, customize the main .XML file and each subprocess in the .XML file.
For example, you can change the transition from "invoiced"
to "payment capture ready"
state, making a direct transition
from "invoiced"
to "payment capture pending"
with event = start payment capture
, which has the command "Payment/Capture"
on-enter:
<transition happy="true">
<source>invoiced</source>
<target>payment capture pending</target>
<event>start payment capture</event>
</transition>
OMS without return and refund
If your project doesn’t require returns and refunds, remove the "ItemReturn"
and "PaymentRefund"
processes from the main .XML file under <subprocesses>
and at the bottom of the file:
<!-- Remove these lines -->
<process name="ItemReturn" file="Subprocess/ItemReturn01.xml"/>
<process name="PaymentRefund" file="Subprocess/PaymentRefund01.xml"/>
Also, remove the transition in the main file using states from the removed processes:
<!-- Remove this transition -->
<transition>
<source>returned</source>
<target>payment refund ready</target>
<event>refund</event>
</transition>
Now, your OMS won’t include blocks for "ItemReturn"
and "PaymentRefund"
.
OMS without payment authorization and cancellation
For a simpler payment flow without pre-authorization, cancellation, and refunds, your configuration will be much simpler without these blocks:
- PaymentAuthorization
- PaymentCancel
- ItemReturn
- PaymentRefund
<?xml version="1.0"?>
<statemachine
xmlns="spryker:oms-01"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="spryker:oms-01 https://static.spryker.com/oms-01.xsd">
<process name="ForeignPaymentStateMachine01" main="true">
<subprocesses>
<process>PaymentCapture</process>
<process>ItemSupply</process>
<process>ItemClose</process>
</subprocesses>
<states>
<state name="new" display="oms.state.new"/>
<state name="invoiced" reserved="true" display="oms.state.waiting"/>
</states>
<transitions>
<transition happy="true">
<source>new</source>
<target>payment capture ready</target>
<event>created</event>
</transition>
<transition happy="true">
<source>payment captured</source>
<target>invoiced</target>
<event>invoice customer</event>
</transition>
<transition happy="true">
<source>invoiced</source>
<target>delivered</target>
<event>deliver</event>
</transition>
</transitions>
<events>
<event name="created" onEnter="true"/>
<event name="invoice customer" manual="true"/>
</events>
</process>
<process name="PaymentCapture" file="Subprocess/PaymentCapture01.xml"/>
<process name="ItemSupply" file="Subprocess/ItemSupply01.xml"/>
<process name="ItemClose" file="Subprocess/ItemClose01.xml"/>
</statemachine>
The most critical elements that must stay in your project
The following elements are critical and must stay in your project. They are responsible for the messages sent to the ACP payment apps to run payment operations for the specific order or order items:
\Pyz\Zed\MessageBroker\MessageBrokerDependencyProvider::PLUGINS_MESSAGE_HANDLER
has plugins:
Spryker\Zed\Payment\Communication\Plugin\MessageBroker\PaymentOperationsMessageHandlerPlugin
Spryker\Zed\Payment\Communication\Plugin\MessageBroker\PaymentMethodMessageHandlerPlugin
\Spryker\Zed\Oms\OmsDependencyProvider::COMMAND_PLUGINS
has commands:
$commandCollection->add(new SendCapturePaymentMessageCommandPlugin(), 'Payment/Capture');
$commandCollection->add(new SendRefundPaymentMessageCommandPlugin(), 'Payment/Refund');
$commandCollection->add(new SendCancelPaymentMessageCommandPlugin(), 'Payment/Cancel');
Thank you!
For submitting the form