Headless payments
Historically, Magento2 did not support headless payments. Even though some payment providers are starting to support them, Front-Commerce provides a workaround. This guide explains how to expose an existing Magento payment method for headless usage in Front-Commerce.
Some payment methods expose REST or GraphQL APIs from their module, but most of
them often rely on Magento CheckoutSession
or CustomerSession
to persist
meaningful information across checkout steps. This guide covers the latter.
Front-Commerce’s Magento module provides a generic way to expose Magento payment modules headlessly and supports the relevant Front-Commerce payment workflows.
the implementation uses very low-level Magento mechanisms. One must be very careful about the implementation and must rigorously test the payment integration because some extensions could have nasty side effects. Read more about it in the Warnings & Known issues section if you plan to use such payment methods.
Supported Payment platforms
Our Magento2 integration currently provides native adapters for the platforms below, learn how to install each one of them from the related documentation page:
If you want to use a Payment module not yet listed above, please contact us so we can provide information about a potential upcoming native support for it.
Implement a new Magento2 Payment method Adapter
This section is not as detailed as we would love it to be. Please let us know if you need further information before we improve it.
We will explain the mechanisms available to implement your own adapter if supported Magento payment modules do not suit your needs.
Payment Adapters must implement the
\FrontCommerce\Integration\Api\HeadlessPayment\Adapter
interface no matter the
workflows they support.
Front-Commerce module will simulate a Magento action as if the page was loaded in a frontend context. It will:
- create a
RequestInterface
instance - dispatch the request through Magento’s Front Controller
- converts the response into a Front-Commerce headless API response
Adapters must implement methods that are called at key times in order to:
- allow to initialize the request
- or convert a response depending on the payment module internal implementations
redirectionFromCheckoutRedirectActionResponse
Payment flows: Redirect before order
Converts an internal Magento response to a
RedirectionInterface
value object.
public function redirectionFromCheckoutRedirectActionResponse(
Response $response,
ManagerInterface $messagesManager
): RedirectionInterface;
redirectionFromCheckoutRedirectAfterActionResponse
Payment flows: Redirect after order
Converts an internal Magento response to a
RedirectionInterface
value object.
public function redirectionFromCheckoutRedirectAfterActionResponse(
Response $response,
ManagerInterface $messagesManager
): RedirectionInterface;
prepareAfterCheckoutContext
Payment flows: Redirect after order
Populates session objects with information required by the payment action.
public function prepareAfterCheckoutContext(
CheckoutSession $checkoutSession,
CustomerSession $customerSession,
Order $order
);
checkoutRedirectUrl
Payment flows: Redirect before order, Redirect after order
Should return the url that will be dispatched internally to trigger the redirection to the payment provider in the payment module.
public function checkoutRedirectUrl(
ConfigProviderInterface $configProvider,
Payment $payment = null
): string;
changeAfterCheckoutResponseFromResult
Payment flows: Redirect after order
May populate or change the Magento response object while still in the correct store context. It can for instance render blocks or things like that…
public function changeAfterCheckoutResponseFromResult(
HttpInterface $response,
BlockFactory $blockFactory,
ResultInterface $result = null,
ObjectManagerInterface $objectManager
);
buildReturnFromPlatformProxiedAction
Payment flows: Redirect before order, Redirect after order
This is a factory to build a
ProxiedAction
matching the next step for the Customer depending on information transmitted by
the payment system in the return url the user was redirected to when coming back
to the store
public function buildReturnFromPlatformProxiedAction(
string $actionName,
array $additionalData,
$customerId = null
): ProxiedAction;
Note: in case an action is not supported, we recommend to throw an exception so Front-Commerce could gracefully prevent the checkout process by displaying a relevant information to the Customer.
Example: throw new \RuntimeException('Checkout flow not supported');
Register the Payment Adapter
Wether you've implemented a new Payment Adapter or are reusing an existing one,
adapters have to be registered so Front-Commerce's module could instantiate it
when relevant. Using the di.xml
, inject the adapter in the
FrontCommerce\Integration\Model\HeadlessPayments\AdapterFactory
$adapters
constructor param.
Below is an example from Front-Commerce's core:
<type name="FrontCommerce\Integration\Model\HeadlessPayments\AdapterFactory">
<arguments>
<argument name="adapters" xsi:type="array">
<item name="paypal_express" xsi:type="string">FrontCommerce\Integration\Model\HeadlessPayments\Adapter\PaypalExpress</item>
<item name="payzen_standard" xsi:type="string">FrontCommerce\Integration\Model\HeadlessPayments\Adapter\PayzenStandard</item>
<item name="ops_cc" xsi:type="string">FrontCommerce\Integration\Model\HeadlessPayments\Adapter\OpsCc</item>
<item name="adyen_hpp" xsi:type="string">FrontCommerce\Integration\Model\HeadlessPayments\Adapter\AdyenHpp</item>
</argument>
</arguments>
</type>
We encourage you to investigate existing Adapters' source code from Front-Commerce's core to learn about advanced patterns.
Allow the Payment's URLs
Since we're using Magento's modules, this means that we also need to use their URLs. However, in Front-Commerce, there's is an option that let's you disable the Magento's front-end in order to redirect users from Magento to Front-Commerce.
Using the di.xml
, you will need to inject routing policies in order to make
sure that the URLs needed for the payment method is allowed.
Please make sure to test your payment after you've enabled the option: "Stores > Configuration > General > General > Front-Commerce > Disable Magento Front-End"
Below is an example from Front-Commerce's core:
<type name="FrontCommerce\Integration\Observer\DisableFrontEnd">
<arguments>
<argument name="routingPolicies" xsi:type="array">
<item name="/^.*\/?\bswagger\b\//" xsi:type="const">FrontCommerce\Integration\Observer\DisableFrontEnd::ROUTING_POLICY_ACCEPT</item>
<item name="/^.*\/?\bstatic\b\//" xsi:type="const">FrontCommerce\Integration\Observer\DisableFrontEnd::ROUTING_POLICY_ACCEPT</item>
<item name="/^.*\/?\bpaypal\b\//" xsi:type="const">FrontCommerce\Integration\Observer\DisableFrontEnd::ROUTING_POLICY_ACCEPT</item>
<item name="/^.*\/?\bpayzen\b\//" xsi:type="const">FrontCommerce\Integration\Observer\DisableFrontEnd::ROUTING_POLICY_ACCEPT</item>
<item name="/^.*\/?\bops\b\//" xsi:type="const">FrontCommerce\Integration\Observer\DisableFrontEnd::ROUTING_POLICY_ACCEPT</item>
<item name="/^.*\/?\badyen\b\//" xsi:type="const">FrontCommerce\Integration\Observer\DisableFrontEnd::ROUTING_POLICY_ACCEPT</item>
</argument>
</arguments>
</type>
Warnings & Known issues
Front-Commerce Headless Payment support relies on Magento low-level internal code. The Magento classes used have some internal state for optimization purpose, and depending on Magento versions and installed modules it could lead to undesirable behaviors.
We recommend that you test payments workflows rigorously for Payment Methods relying on Front-Commerce Headless Payment mechanisms. If you need help investigating such issues, please contact us.
How does it work?
It is important to understand how Headless Payment works to understand the limitations and reasons of the issues:
- Front-Commerce calls REST endpoints provided by the Front-Commerce Magento module (headless payments API)
- the REST endpoint will load the relevant headless payment adapter
- it then bootstraps a Magento internal Request object, and initializes it with session information (user, order, quote…) by delegating some initialization to the payment adapter
- it then dispatches the request (in an emulated
frontend
Magento area) - the Magento internal HTTP response is finally converted back to a Front-Commerce headless payment response (the payment adapter is responsible for extracting and transforming data)
- the Front-Commerce headless payment endpoint returns the JSON response that is understood and used by Front-Commerce to do whatever is needed (redirect the customer to a success page, to the payment provider page …)
The steps 3 and 4 above will trigger low-level Magento mechanisms and there
are known side effects in some Magento versions. Switching from a
webapi_rest
area to a frontend
area (for another internal Magento Request)
and then switching back to the webapi_rest
area to send the API response… is
what may cause issues.
Known issues
There are still some issues we know about and were not able to solve in our module, either because Magento does not provide extension point to do it from a module or because it is difficult to do it across the whole range of Magento versions Front-Commerce supports.
Magento 2.3
No known issues! 😎
Magento 2.4.0+
"CSP can only be configured for storefront or admin area" error
This error is due to event handlers being merged and not reset upon Magento area
switch. Magento_Csp
event handlers (for frontend
area) are incorrectly
executed when sending back the webapi_rest
HTTP response.
Possible workarounds:
- disable
Magento_Csp
module: for an admin-only store, it could make sense! - patch the
Magento\Framework\Config\Data\Scoped
class with the patch attached in the related issue
Read the related issue for details.
Magento 2.4.1+
"Notice: Undefined index: Magento\Webapi\Controller\Rest" error
This error is due to the $this->_pluginInstances
attribute is not reset
properly when we switch between Magento areas.
Possible workarounds:
- call
$pluginList->getNext($type, $method, $code);
in the interceptor to reinitialize internal state properly - patch the
PluginList
class to handle this edge case (undefined index)