Skip to main content
Version: 3.x

Adyen

Saleor version required: 3.14

Overview​

Adyen App is a payment integration app that allows merchants using the Saleor e-commerce platform to accept online payments from customers using Adyen as their payment processor. In addition to processing payments, the Saleor App Payment Adyen provides merchants with tools for managing refunds and chargebacks.

caution

To configure the Adyen App, you must have an account with Adyen.

The Adyen App allows for integrations with Adyen Web Drop-in, Adyen iOS Drop-in, Adyen Android Drop-in, and Adyen React Native Drop-in. It uses the Adyen Drop-in Advanced Flow.

info

Adyen App uses Adyen Checkout API v70 and Management API v1.

Capabilities​

The Adyen App implements the following Saleor sync webhooks related to transactions:

Furthermore, it's also prepared to handle async Adyen webhooks.

Adyen App follows the flow described in detail in the Saleor Payment App documentation.

Limitations​

This section contains known limitations of this App.

Maximum timeout for Adyen calls is 15 seconds​

Saleor synchronous webhooks have a maximum response time limit of 20 seconds. The app restricts Adyen response time to 15 seconds to allow graceful error handling. If Adyen surpasses this limit, the App will return a FAILURE status with an appropriate error message (Timeout Error).

Maximum timeout for Saleor API calls is 5 seconds​

The apps restricts Saleor API response time to 5 seconds for TransactionInitializeSession and TransactionProcessSession subscriptions. If Saleor API surpasses this limit, the App will gracefully continue processing.

If such timeout happens, the created TransactionItem will not have the metadata from Adyen on additionalDetails object, that include payment method type, credit card brand, etc.

Configuration​

For Adyen to appear as available payment gateway, you need to install it in the Saleor Dashboard. You must obtain the API key from Adyen and paste it into the Adyen App configuration form. Then, a wizard will guide you through the process of configuring the Adyen App, setting up the webhook to receive notifications from Adyen, generating the HMAC key, and adding allowed origins for the Client Key that's used on your Storefront.

Configuring Adyen​

Video introduction to Adyen configuration in Saleor:

You can install the Adyen app directly from your Saleor Dashboard. Go to the Apps section and click the Install button next to the Adyen app. Follow the instructions on the screen. After the installation is complete, select the Adyen app from the list of installed applications.

Creating new API Credentials​

To create new Adyen API credentials head over to the Adyen dashboard, then Developers -> API credentials. Click Create new credential and choose Web service user.

In Server settings -> Authentication section generate new API key.

In Client settings -> Authentication section generate new client key.

Go back to the Adyen app configuration in the Saleor Dashboard. Provide a configuration name that you will be familiar with. In the API Key field provide the key from Server settings -> Authentication section. Select TEST environment.

caution

The configuration will not work until you save API credentials in the Adyen dashboard.

Save API credentials in the Adyen dashboard, head over to the Adyen app configuration, and click Save. The rest of the form fields have been enabled.

In Client key field provide key from Client settings -> Authentication section. Select the merchant account you want to use from the dropdown.

Webook configuration​

In Adyen dashboard head over to Developers -> Webhooks. Click the + Webhook button and choose Standard webhook. Provide a description for the webhook.

In the Server configuration section provide URL the Saleor Adyen app has generated for you. You can copy it from the Webhook URL input in the Adyen configuration form. Leave default settings and click Apply.

In Merchant accounts select Include only specific merchant accounts and choose the merchant account you would like to use.

In Events, apart from events selected by default, select EXPIRE. Deselect ORDER_OPENED event.

In Additional settings -> Risk select Include the originalReference for CHARGEBACK_REVERSED events.

In Security -> Basic authentication provide username and password. Apply changes. Use the same username and password in the webhook username and webhook password fields in the Adyen application.

Generate a new HMAC key in the Security -> HMAC Key section and copy it.

Apply the configuration details in the Saleor Adyen app then in Adyen dashboard save the new Adyen webhook. Save the configuration. If everything went well, you should see a new button Make test webhook call. Use it to test if your configuration is correct.

caution

API Key provided inside the configuration must have the following permissions set in Adyen Dashboard:

  • "Management API - Accounts read and write"
  • "Management API - API credentials read and write"
  • "Management API - Payment methods read"
  • "Management API - Stores read and write"
  • "Management API - Webhooks read and write"
  • "Checkout encrypted cardholder data"
  • "Merchant Recurring role"
  • "Checkout webservice role"
  • "Merchant PAL Webservice role"

Usage in Storefront or mobile apps​

Adyen App can be used to integrate with Adyen APIs. By using a set of GraphQL mutations, one can interact with Adyen to authorize, capture, refund, and cancel payments.

Adyen Web Drop-in component

Getting payment gateways​

The first step is to fetch the Checkout object including availablePaymentGateways field. The availablePaymentGateways field contains a list of payment gateways available for given checkout. The Adyen App should be one of the payment gateways available in the list. Its id is app.saleor.adyen - defined in app's manifest.

query {
checkout(id: "Q2hlY2tvdXQ6YWY3MDJkMGQtMzM0NC00NjMxLTlkNmEtMDk4Yzk1ODhlNmMy") {
availablePaymentGateways {
id
name
}
}
}

The response:

{
"data": {
"checkout": {
"availablePaymentGateways": [
{
"id": "app.saleor.adyen",
"name": "Adyen"
}
]
}
}
}
note

availablePaymentGateways may contain other Payment Apps as well as legacy Plugins configured in the Dashboard. You should ignore the ones that you don't want to use for a specific checkout.

Obtaining Adyen payment methods​

Next, you need to fetch configured payment methods from Adyen. To do that, use the paymentGatewayInitialize mutation. The mutation returns a PaymentGatewayInitialize object with data field containing a list of payment methods. The data field is an object with the following fields:

{
paymentMethodsResponse: PaymentMethodsResponse;
clientKey: string;
environment: "LIVE" | "TEST";
errors?: SyncWebhookAppErrors;
}

Where PaymentMethodsResponse is the result of calling Adyen's /paymentMethods endpoint and is described in the Adyen documentation. SyncWebhookAppErrors is described below.

If errors field doesn't exist or is an empty array, paymentMethodsResponse, clientKey and environment should be used to initialize Adyen Drop-in.

mutation {
paymentGatewayInitialize(
id: "Q2hlY2tvdXQ6YWY3MDJkMGQtMzM0NC00NjMxLTlkNmEtMDk4Yzk1ODhlNmMy"
amount: 54.24
paymentGateways: [{ id: "app.saleor.adyen" }]
) {
gatewayConfigs {
id
data
errors {
field
message
code
}
}
errors {
field
message
code
}
}
}
Expand â–¼

The response:

{
"data": {
"paymentGatewayInitialize": {
"gatewayConfigs": [
{
"id": "app.saleor.adyen",
"data": {
"paymentMethodsResponse": {
"paymentMethods": [
{
"brands": ["visa", "mc"],
"name": "Credit Card",
"type": "scheme"
}
]
},
"clientKey": "test_AHSJKADHK12731KDSALD11DSADASA003",
"environment": "TEST"
},
"errors": []
}
],
"errors": []
}
}
}
Expand â–¼
tip

For instructions on how to add, remove or constraint payment methods from Adyen, please consult the Adyen payment methods documentation.

Paying with Adyen​

After a user has interacted with the Adyen Drop-in and entered payment details, Drop-in event data along with other information should be passed to the transactionInitialize mutation as the paymentGateway.data field. The mutation returns the TransactionInitialize object with a data field containing the following fields:

{
paymentResponse: PaymentResponse;
errors?: SyncWebhookAppErrors;
}

Where PaymentResponse is the result of calling Adyen's /payments endpoint and is described in the Adyen documentation. SyncWebhookAppErrors is described below.

If the errors field doesn't exist or is an empty array, pass the paymentResponse to Adyen Drop-in. The Drop-in will handle the response and display the result to the user or require additional actions to proceed.

mutation AdyenTransactionInitialize($data: JSON!) {
transactionInitialize(
id: "Q2hlY2tvdXQ6YWY3MDJkMGQtMzM0NC00NjMxLTlkNmEtMDk4Yzk1ODhlNmMy"
action: AUTHORIZATION
amount: 54.24
paymentGateway: { id: "app.saleor.adyen", data: $data }
) {
transactionEvent {
pspReference
amount {
amount
currency
}
type
}
data
errors {
field
message
code
}
}
}
Expand â–¼

Where $data is the object provided by Adyen Drop-in in the onSubmit (Web, React Native), didSubmit (iOS) or makePaymentsCall (Android) callback. Specifically, the following fields may be passed inside the $data:

  • paymentMethod (required)
  • browserInfo (required in browsers)
  • returnUrl (required for some payment methods)
  • origin (optional)
  • channel (optional, "Web" by default)
  • order (optional)
note

Moreover, Saleor Adyen App automatically provides the following fields to Adyen:

  • reference
  • shopperReference
  • additionalData.manualCapture (when action is authorization)
  • merchantAccount
  • countryCode
  • shopperLocale
  • amount
  • authenticationData.threeDSRequestData.nativeThreeDS ("preferred")
  • metadata ({ transactionId, channelId, checkoutId, orderId })
  • lineItems
  • shopperEmail
  • shopperName
  • telephoneNumber
  • deliveryAddress
  • billingAddress
  • company.name

Response:

{
"data": {
"transactionInitialize": {
"transactionEvent": {
"pspReference": "XXXX9XXXXXXXXX99",
"amount": {
"amount": 54.24,
"currency": "EUR"
},
"type": "AUTHORIZATION_SUCCESS"
},
"data": {
"paymentResponse": {
"additionalData": {
"paymentMethod": "visa"
},
"amount": {
"currency": "EUR",
"value": 5424
},
"merchantReference": "SOME_MERCHANT_ID_",
"paymentMethod": {
"brand": "visa",
"type": "scheme"
},
"pspReference": "XXXX9XXXXXXXXX99",
"resultCode": "Authorised"
}
},
"errors": []
}
}
}
Expand â–¼

Performing additional actions (optional)​

Optionally, additional actions may be required: authentication of payment with 3D Secure, scan of a QR code, or logging in to the bank to complete the payment. In this case, transactionProcess mutation should be used.

mutation AdyenTransactionProcess($id: ID!, $data: JSON) {
transactionProcess(id: $id, data: $data) {
transaction {
id
actions
}
transactionEvent {
message
type
}
data
errors {
field
code
message
}
}
}

Where $data is the object provided by Adyen Drop-in in the onAdditionalDetails (Web, React Native), didProvide (iOS) or makeDetailsCall (Android) callback. The response is similar to the one from transactionInitialize but the data field has a different shape:

{
paymentDetailsResponse: PaymentDetailsResponse
errors?: SyncWebhookAppErrors;
}

PaymentDetailsResponse is the result of calling Adyen's /payments/details endpoint and is described in the Adyen documentation. SyncWebhookAppErrors is described below.

If the errors field doesn't exist or is an empty array, pass the paymentDetailsResponse back to Adyen Drop-in. The Drop-in will handle the response and display the result to the user or again require additional actions to proceed.

Repeat the step until the payment is successful or fails.

caution

Many payment methods are not settled synchronously. Sometimes it takes seconds, minutes, hours, or even days for a payment to go through. Adyen App will automatically handle Adyen webhook notifications and create transaction events in Saleor (see transactionEventReport).

Apple Pay onValidateMerchant​

To implement Apple Pay integration through Adyen and use your own Apple Pay certificate, you must implement onValidateMerchant (Web, React Native) or onvalidatemerchant (iOS). The Adyen Saleor App provides a way to validate the merchant using the paymentGatewayInitialize mutation:

mutation PaymentGatewayInitialize($checkoutId: ID!, $data: JSON) {
paymentGatewayInitialize(
paymentGateways: [{ id: "app.saleor.adyen", data: $data }]
id: $checkoutId
) {
gatewayConfigs {
id
data
errors {
field
message
code
}
}
errors {
field
message
code
}
}
}
Expand â–¼

and provide the following JSON in $data:

{
"action": "APPLEPAY_onvalidatemerchant",
"validationURL": "…",
"domain": "…",
"merchantIdentifier": "…",
"merchantName": "…"
}

All the parameters should be provided according to Apple Pay documentation on the Adyen website.

Additional endpoints (optional)​

To use some payment methods inside Adyen Drop-in you may have to implement the following callbacks:

  • onBalanceCheck (Web, React Native), checkBalance (iOS) or checkBalance (Android)
  • onOrderRequest (Web, React Native), requestOrder (iOS) or createOrder (Android)
  • onOrderCancel (Web, React Native), cancelOrder (iOS) or cancelOrder (Android)

For example, these methods are required for Gift card split-payments.

note

Adyen's orders and requests for balance checks are not saved in Saleor. Each payment linked to Adyen's order will be stored as a separate transaction in Saleor.

Orders link transactions on the Adyen level. For example, if a user cancels an order (by removing gift card payment inside the Drop-in), every payment linked to that order will be refunded or voided.

onBalanceCheck​

To call the /paymentMethods/balance endpoint use the paymentGatewayInitialize mutation:

mutation PaymentGatewayInitialize($checkoutId: ID!, $data: JSON) {
paymentGatewayInitialize(
paymentGateways: [{ id: "app.saleor.adyen", data: $data }]
id: $checkoutId
) {
gatewayConfigs {
id
data
errors {
field
message
code
}
}
errors {
field
message
code
}
}
}
Expand â–¼

and provide the following JSON in $data:

{
"action": "checkBalance",
"paymentMethod": {
"type": "giftcard",
"brand": "givex",
"encryptedCardNumber": "...",
"encryptedSecurityCode": "..."
}
}

The contents of the paymentMethod field come from the Adyen Drop-in.

Example onBalanceCheck implementation in TypeScript could look like this (depending on your GraphQL client):

async onBalanceCheck(resolve, reject, data) {
const {
paymentGatewayInitialize: { gatewayConfigs },
} = await client.request(PaymentGatewayInitialize, {
checkoutId,
data: { action: "checkBalance", paymentMethod: data.paymentMethod },
});
const response = gatewayConfigs[0].data.giftCardBalanceResponse;
resolve(response);
}
note

Error handling is intentionally omitted for brevity.

The response received from Saleor with data from the Adyen app will be:

{
"data": {
"paymentGatewayInitialize": {
"gatewayConfigs": [
{
"id": "app.saleor.adyen",
"data": {
"giftCardBalanceResponse": {
"balance": {
"currency": "EUR",
"value": 5000
},
"pspReference": "BK4C…NN82",
"resultCode": "NotEnoughBalance"
}
}
}
]
}
}
}
Expand â–¼

onOrderRequest​

Similarly to onBalanceCheck, to call the /orders endpoint use the paymentGatewayInitialize mutation and pass the following $data:

{
"action": "createOrder"
}

The onOrderCreate implementation in TypeScript could look like this (depending on your GraphQL client):

async onOrderCreate(resolve, reject, data) {
const {
paymentGatewayInitialize: { gatewayConfigs },
} = await client.request(PaymentGatewayInitialize, {
id: checkoutId,
data: { action: "createOrder" },
});
const response = gatewayConfigs[0].data.orderCreateResponse;
resolve(response);
}

The response received from Saleor with data from the /orders endpoint will be:

{
"data": {
"paymentGatewayInitialize": {
"gatewayConfigs": [
{
"id": "app.saleor.adyen",
"data": {
"orderCreateResponse": {
"amount": {
"currency": "EUR",
"value": 12000
},
"expiresAt": "2023-07-04T11:34:02Z",
"orderData": "...",
"pspReference": "W...82",
"reference": "5f0d76d5-aaed-40d3-87ff-bd34d6849f95,Q2hhbm5lbDoy,'c'",
"remainingAmount": {
"currency": "EUR",
"value": 12000
},
"resultCode": "Success"
}
}
}
]
}
}
}
Expand â–¼

Once the order is created, you may complete the payment. Use the data received from the Drop-in inside the onSubmit method. It includes the order property - Adyen App will charge the customer with the amount specified in the order's data.

For example: if you have only 20 EUR left on a gift card, and the order's total amount is 50 EUR, order data will firstly charge the gift card with the available amount (20 EUR) and the 2nd payment method with the outstanding order amount (50 - 20 = 30 EUR).

warning

Adyen app uses a pspReference field internally to link the notifications from ORDER_CLOSED Adyen webhook events

This field shouldn't be used by any external system, as it can change at any time without further notice.

onOrderCancel​

Should be called when the user removes a payment method in a pending Adyen order, for example when there was already a partial charge for a gift card but the user decided to use a different payment method.

To call the /orders/cancel endpoint, similarly to onBalanceCheck, use the paymentGatewayInitialize mutation and pass the following $data:

{
"action": "cancelOrder",
"orderData": "..."
"pspReference": "..."
}

The response from that mutation will be:

{
"data": {
"transactionProcess": {
"data": {
"orderCancelResponse": {
"pspReference": "...",
"resultCode": "Received"
}
}
}
}
}

The onOrderCancel implementation in TypeScript could look like this (depending on your GraphQL client):

async onOrderCancel({order}) {
const {
paymentGatewayInitialize: { gatewayConfigs },
} = await client.request(PaymentGatewayInitialize, {
id: checkoutId,
data: {
action: "cancelOrder",
pspReference: order.pspReference,
orderData: order.orderData,
},
});
const response = gatewayConfigs[0].data.orderCancelResponse;

if (response.resultCode !== "Received") {
throw new Error("Cannot cancel order");
}

checkout.update({order: undefined});
}
Expand â–¼

Handling errors​

The three mutations described above may return data.errors field. The existence of this field determines that the request was unsuccessful. errors is an array of SyncWebhookAppError objects. The SyncWebhookAppError object has the following fields:

{
code?: string;
message?: string;
details?: JSONObject;
}

The code field is a string identifying the error. One of the following values is allowed:

  • UnknownError
  • JsonSchemaError
  • MissingSaleorApiUrlError
  • MissingAuthDataError
  • HttpClientError
note

This list may be extended in the future. Make sure your app handles unknown error codes.

The message field is a human-readable message describing the error.

The details field is an object containing additional information about the error. It may contain two fields:

Example:

{
"data": {
"transactionInitialize": {
"transactionEvent": {
"pspReference": "",
"amount": {
"amount": 54.24,
"currency": "EUR"
},
"type": "AUTHORIZATION_FAILURE"
},
"data": {
"errors": [
{
"code": "HttpClientError",
"message": "HTTP Exception: 422. : Unable to decrypt data",
"details": {
"errorCode": "174",
"statusCode": 422
}
}
],
"paymentResponse": {}
},
"errors": []
}
}
}
Expand â–¼

Was this page helpful?