Headless Checkout
Using Sylius in Headless Mode with Adyen Plugin (Quick Guide)
This guide assumes that the Sylius Adyen Plugin is already installed and that your Adyen gateway (configured under code custom_adyen_gateway
) is fully set up. It focuses solely on the headless payment flow and order state transitions.
Sample Adyen Drop-In Setup (Concept Only)
Why Drop-In?
Adyen’s Drop-In component provides a ready-made, secure UI for collecting payment details, handling 3DS, and submitting to Adyen without building your own forms. It simplifies PCI compliance and accelerates testing of the complete payment lifecycle in a headless setup.
Test Controller
Create a quick test controller to render Adyen’s Drop-In in your headless environment:
// src/Controller/AdyenTestController.php
final class AdyenTestController extends AbstractController
{
#[Route('/test/adyen/drop-in/{orderToken}', name: 'test_adyen_dropin')]
public function dropin(string $orderToken): Response
{
return $this->render('adyen/test_dropin.html.twig', [
'orderToken' => $orderToken,
'gatewayCode' => 'custom_adyen_gateway',
]);
}
}
Test Twig View
{# templates/adyen/test_dropin.html.twig #}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"/>
<title>Sylius Adyen Headless Demo</title>
<script
src="https://checkoutshopper-test.cdn.adyen.com/checkoutshopper/sdk/6.18.0/adyen.js"
integrity="sha384-ZEPvk8M++Rrf/1zMUvnfdO73cZlnj/u9oAGHSeUIIgOXoW0ZrwfyB6pBcIrhDbdd"
crossorigin="anonymous"
></script>
<link
rel="stylesheet"
href="https://checkoutshopper-test.cdn.adyen.com/checkoutshopper/sdk/6.18.0/adyen.css"
integrity="sha384-lCDmpxn4G68y4vohxVVEuRcbz4iZTDh1u/FLlsCV1wSbibWKs+knhLQpgzPBqap4"
crossorigin="anonymous"
/>
</head>
<body>
<div id="dropin-container" style="max-width:400px;margin:2em auto;"></div>
<script>
(async () => {
const gatewayCode = '{{ gatewayCode }}';
const orderToken = '{{ orderToken }}';
// Fetch Drop-In configuration
const response = await fetch(`/api/v2/shop/payment/adyen/${gatewayCode}/${orderToken}`, {
headers: {
'Accept': 'application/json'
}
}
);
const config = await response.json();
const {AdyenCheckout, Dropin} = window.AdyenWeb;
const errorKey = 'sylius_adyen.runtime.payment_failed_try_again';
const errorMsg = config.translations[errorKey];
const checkout = await AdyenCheckout({
clientKey: config.clientKey,
environment: config.environment,
paymentMethodsResponse: config.paymentMethods,
amount: config.amount,
countryCode: config.billingAddress.countryCode,
locale: config.locale,
onError: (err, component) => component.setStatus('error', {message: errorMsg}),
onSubmit: (state, component) =>
fetch(config.path.payments, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(state.data)
})
.then(r => r.json())
.then(res => {
if (res.resultCode === 'Authorised') {
window.location.href = `/${config.locale}/payment/adyen/${gatewayCode}/thanks`;
} else if (res.action) {
component.handleAction(res.action);
} else {
component.setStatus('error', {message: errorMsg});
}
})
});
const dropin = new Dropin(checkout);
dropin.mount('#dropin-container');
})();
</script>
</body>
</html>
Checkout & Payment Flow
The next section covers the live payment flow — from completing the order to processing the Drop-In response:
Pick up the cart and add products.
Enter addresses: Provide billing and shipping addresses for the order (we do not recommend using US or CA country codes at this time due to known handling issues).
Select Shipping Method: Choose a shipment option.
Select Payment Method: Choose your configured gateway (
custom_adyen_gateway
).
Finalize payment
You can either complete the payment directly (which will also complete the checkout after a successful transaction) or first place the order. Once the order is in the awaiting_payment
state, proceed with the payment.
Access the Drop-In test view via the route defined in AdyenTestController
:
GET /test/adyen/drop-in/{orderToken}
This will render the Drop-In component (using the example controller shown earlier) and prompt you to enter test card details. You can find the full list of Adyen test card numbers here: https://docs.adyen.com/development-resources/testing/test-card-numbers/
Handle Drop-In Response
On error, the transaction moves to
failed
(noted both in Sylius and the Adyen Dashboard), and the customer must retry the payment.On success, Drop-In will redirect the customer to the confirmation URL provided by the plugin endpoint and Sylius will automatically transition the order to
completed
. In this case, you don’t need to call thecomplete
endpoint separately.
Drop-In Configuration Data
Adyen’s Drop-In component initializes itself using the JSON returned by the headless endpoint:
GET /api/v2/shop/payment/adyen/{custom_adyen_gateway}/{orderToken}
Below is a representative example of the configuration payload:
{
"billingAddress": {
"firstName": "John",
"lastName": "Doe",
"countryCode": "NL",
"province": null,
"city": "Sample City",
"postcode": "12345"
},
"paymentMethods": {
"paymentMethods": [
{
"brands": ["visa", "mc", "amex"],
"name": "Cards",
"type": "scheme"
}
],
"storedPaymentMethods": []
},
"clientKey": "<yourClientKey>",
"locale": "en_US",
"environment": "test",
"canBeStored": false,
"amount": {
"currency": "EUR",
"value": 5000
},
"path": {
"payments": "/en_US/payment/adyen/custom_adyen_gateway?tokenValue={orderToken}",
"paymentDetails": "/en_US/payment/adyen/details?code=custom_adyen_gateway&tokenValue={orderToken}",
"deleteToken": "/en_US/payment/adyen/custom_adyen_gateway/token/_REFERENCE_?tokenValue={orderToken}"
},
"translations": {
"sylius_adyen.runtime.payment_failed_try_again": "Payment failed, please try again"
}
}
Last updated
Was this helpful?