Adyen Express Checkout

Last updated

This guide will instruct you on how to implement Adyen’s Express Checkout in your store. This way of paying for the order is different from how Centra usually collects payment: it allows to obtain a customer's address directly from Google or Apple instead of collecting it as a separate step in the checkout process.

Currently we support two payment methods via Express Checkout:

  • Google Pay (or GP),
  • Apple Pay (or AP).

Below you can find a diagram explaining how the overall flow of Express Checkout looks like. Do not worry if some concepts are unclear, all will be explained later.

Contexts#

Before we go into implementation details, let us first break down an important aspect of Express Checkout. In general, it can be used as part of the standard checkout process and be included in the basket widget or checkout page. However, it also unlocks the ability of an express purchase from the product details page, called PDP from now on. This means we have two distinct contexts: Checkout and PDP.

There is not much additional work related to the Checkout context. There is, however, additional work required to handle the selection for PDP context: initializing the selection should be delayed until the payment sheet widget is actually opened.

Implementation#

In order to start working on your implementation, you will need to retrieve a configuration JSON. It contains values necessary to initialize the HTML widget. See below how to call the endpoint to receive only the minimal configuration required:

Checkout API#

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 await fetch( <the URL to the Checkout API> + '/widgets', { "method": 'POST', "headers": { "API-Token": sessionToken, "Authorization": "Bearer " + sharedSecret }, "body": JSON.stringify({ "plugins": [ { "uri": "<the URI field for the chosen Adyen plugin>", "additionalData": { // in minor units, so for example 100.00 is 10000 for a currency with 2 decimals "amount": 10000, // line items are used for display only, so the price should be already be formatted "lineItems": [ { "name": "Product name", "price": "100.12" } ] } } ] }) } );

In response you will receive a JSON object that you can use for your own implementation:

1 2 3 4 5 6 7 8 9 { "token": <null or token depending on the API-Token header>, "<plugin uri>": [ { "name": "express_checkout_adyen", "contents": "<escaped JSON configuration, see below for contents>" } ] }

Storefront API#

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 { expressCheckoutWidgets( plugins: [{ uri: "<plugin uri>", additionalData: { returnUrl: "<return url>", // line items are used for display only, so the price should be already be formatted amount: 10000, lineItems: [{ name: "Product", price: "100.00" }] } }] ) { list { name widgets { name contents } } userErrors { message path } } }

Response contents:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 { "data": { "expressCheckoutWidgets": { "list": [ { "name": "<plugin uri>", "widgets": [ { "name": "express_checkout_adyen_<apple_pay|google_pay>", "contents": "<script contents>" } ] } ], "userErrors": [ // will be an empty list for successful requests { "message": "<error message>", "path": [ "<error path>" ] } ] } }, "extensions": { "token": "<session token>" } }

JSON configuration contents for both APIs#

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 { "context": "<test|live>", "country": "<country id>", "languageCode": "<language code>", "shippingMethods": [ { "id": "<shipping method id>", "label": "<price>: <name>", "description": "" }, // other methods ... ], "modificationAmount": { "amount": 10000, "currency": "<currency code>" }, "billingPhoneNumberRequired": <defined in Checkout API plugin configuration>, "shippingPhoneNumberRequired": <defined in Checkout API plugin configuration>, "paymentMethodsResponse": { "paymentMethods": [ { "configuration": { "merchantId": "<merchant id>", "gatewayMerchantId": "<gateway merchant id>" }, "name": "Google Pay", "type": "<paywithgoogle|googlepay>" } ] }, "clientKey": "<client key>", "paymentMethod": "<plugin uri>" }

Remaining steps#

The next steps would be to follow the instructions provided by Adyen on how to create the payment sheet widget for Apple Pay and Google Pay:

https://docs.adyen.com/payment-methods/apple-pay/web-component/express-checkout/ https://docs.adyen.com/payment-methods/google-pay/web-component/express-checkout/

Below you can find general tips on implementing the logic:

  1. You need to update the selection data on payment sheet initialization and address update. Set the received address data on the selection and it will calculate the new amount and available shipping methods. You would use the following callbacks to receive the selected address data:
    a. Google Pay - paymentDataCallbacks -> onPaymentDataChanged,
    b. Apple Pay - onShippingContactSelected.

  2. Changing the shipping method also requires you to update the selection. The callbacks here are:
    a. Google Pay - paymentDataCallbacks -> onPaymentDataChanged,
    b. Apple Pay - onShippingMethodSelected.

  3. When customer confirms the payment, it is split into two, potentially three callbacks:
    a. onAuthorized -> this callback is fired first and receives the final customer data (shipping address and method, email, etc.). You should store the selection data received here, but there is no way of updating the payment amount anymore.
    b. onSubmit -> this callback receives payment data from PSP, which needs to be passed to the backend. Then the data received from Adyen should be used to finish the payment inside of the payment sheet.
    c. [Google Pay only] After onSubmit, depending on the context of the payment and risk configuration in the merchant account, 3D Secure 2 flow may be triggered. This will require the onAdditionalDetails callback to be handled.

  4. Once the payment is handled, the customer should be redirected to the payment confirmation page. This would happen in the onPaymentCompleted callback. Shipping address/method updates errors should be displayed in the payment sheet. Payment failures should cause the customer to be redirected to the payment failed page.

  5. If any action needs to be performed when the widget is closed, the onError callback can be used.

1 2 3 4 5 onError: (error, component) => { if (error.name === 'CANCEL') { // perform additional actions } }

Below is a table summarizing the above:

CallbackCheckout API endpoint to callStorefront API mutation to call
GP: paymentDataCallbacks -> onPaymentDataChanged, AP: onShippingContactSelectedPOST /payment with {"paymentInitiateOnly": true}paymentInstructions with { paymentInitiateOnly: true }
GP: paymentDataCallbacks -> onPaymentDataChanged, AP: onShippingMethodSelectedPUT /shipping-methods/{shippingMethodId}setShippingMethod with { id: $shippingMethodId }
onAuthorizedno call, but address data needs to be stored until onSubmitno call, but address data needs to be stored until centra_express_checkout_payment_callback event
onSubmitPOST /payment with details received from the PSPpaymentInstructions with details received from the PSP
onAdditionalDetailsPOST /payment-result with data received from the callback eventpaymentResult with data received from the callback event

Ingrid compatibility#

Unfortunately, in its current state the Ingrid plugin is not usable with Express Checkout. Ingrid has its own frontend widget that the customer needs to interact with in order to get the shipping options. There is no way to make it compatible with the Express Checkout widget. This means that orders placed via the Express Checkout widget can only utilize the standard Centra shipping options. Moreover, if the plugin is enabled, it will interfere with the Express Checkout orders. It will communicate with Ingrid and also update the selection with default shipping option and other relevant attributes. There are three places where such interference happens:

  1. When initializing the selection with items,
  2. Submitting address submission,
  3. Finalizing the payment.

Because we cannot disable this behaviour by default, a workaround was required. These are the necessary steps you will need to perform in order for Express Checkout to not be negatively impacted by an enabled Ingrid v2 plugin:

  1. When initializing the widget for the first time, fetch the first shipping method from the selection response and perform the shipping method update on the selection: a. Checkout API: PUT /shipping-methods/{shippingMethodId},
    b. Storefront API: setShippingMethod mutation with { id: $shippingMethodId }.
  2. Every payment submission (either address data or the payment response from PSP) should contain paymentMethodSpecificFields.express: true parameter. This will inform Centra that Ingrid should not be involved:
    a. Checkout API: POST /payment,
    b. Storefront API: paymentInstructions mutation.
  3. Same as above, but when submitting payment results. This would happen when redirecting the customer to the payment confirmation page. paymentMethodFields.express: true needs to be passed in the payload:
    a. Checkout API: POST /payment-result,
    b. Storefront API: paymentResult mutation.

With these steps in place, the Express Checkout order will be placed outside of Ingrid. If you are depending on Ingrid as an important part of your checkout process, this will probably prevent you from using Adyen’s Express Checkout at all.