Installation

To install modules in your Vue Storefront application, use the following command:

Requirements

  • commercetools integration ^1.10.

Setup

Add @vsf-enterprise/adyen-commercetools to dev and prod arrays in useRawSource object:











 




 






// nuxt.config.js

export default {
  buildModules: [
    ['@vue-storefront/nuxt', {
      coreDevelopment: true,
      useRawSource: {
        dev: [
          '@vsf-enterprise/commercetools',
          '@vue-storefront/core',
          '@vsf-enterprise/adyen-commercetools'
        ],
        prod: [
          '@vsf-enterprise/commercetools',
          '@vue-storefront/core',
          '@vsf-enterprise/adyen-commercetools'
        ]
      }
    }]
  ]
};

Register @vsf-enterprise/adyen-commercetools/nuxt module with following configuration:

// nuxt.config.js

export default {
  modules: [
    ['@vsf-enterprise/adyen-commercetools/nuxt', {
      availablePaymentMethods: [
        'scheme'
      ],
      clientKey: '<ADYEN_CLIENT_KEY>',
      environment: 'test'
    }]
  ]
};

In case of using i18n module with your application please keep the proper order of modules registration. You can read more about it here (opens new window).

Add @vsf-enterprise/adyen-commercetools/server integration to the middleware with the following configuration:

// middleware.config.js

module.exports = {
  integrations: {
    // ...
    adyen: {
      location: '@vsf-enterprise/adyen-commercetools/server',
      configuration: {
        ctApi: {
          apiHost: '<CT_HOST_URL>',
          authHost: '<CT_AUTH_URL>',
          projectKey: '<CT_PROJECT_KEY>',
          clientId: '<CT_CLIENT_ID>',
          clientSecret: '<CT_CLIENT_SECRET>',
          scopes: [
            'manage_orders:<CT_PROJECT_KEY>',
            'manage_payments:<CT_PROJECT_KEY>',
            'view_orders:<CT_PROJECT_KEY>'
          ]
        },
        adyenMerchantAccount: '<ADYEN_MERCHANT_ACCOUNT>',
        adyenCheckoutApiBaseUrl: 'https://checkout-test.adyen.com',
        adyenRecurringApiBaseUrl: 'https://pal-test.adyen.com',
        adyenApiKey: '***',
        origin: 'http://localhost:3000',
        buildRedirectUrlAfterAuth (paymentAndOrder) {
          return `/checkout/thank-you?order=${paymentAndOrder.order.id}`;
        },
        buildRedirectUrlAfterError (err) {
          return '/adyen-payment-error';
        },
        buildCustomPaymentAttributes (context) {
          return {
            customPaymentAttribute: 'custom-payment-attribute-value'
          }
        },
        buildCustomOrderAttributes (context) {
          return {
            customOrderAttribute: 'custom-order-attribute-value'
          }
        },
        userSessionCookie: 'vsf-commercetools-token'
      }
    }
  }
}
  • configuration:
    • ctApi - An object containing credentials of your commercetools API client. Please refer to our documentation for commercetools integration (opens new window) for more information. Two notable differences are that:
      • the scopes array must contain manage_orders, manage_payments, and view_orders and your API client must have access to these scopes,
      • apiHost should only contain the base URL, without the path to the GraphQL endpoint. For example, https://<SHOP_DOMAIN>.com/ instead of https://<SHOP_DOMAIN>.com/vsf-ct-dev/graphql.
    • adyenMerchantAccount - name of your Adyen's merchant account,
    • adyenCheckoutApiBaseUrl - for sandbox it should be https://checkout-test.adyen.com, and for live it should be https://{PREFIX}-checkout-live.adyenpayments.com/, you can read more about it here (opens new window) - use only base URL from the linked document.
    • adyenRecurringApiBaseUrl - for sandbox it should be https://pal-test.adyen.com, and for live it should be https://{PREFIX}-pal-live.adyenpayments.com, you can read more about it here (opens new window),
    • adyenApiKey - your Adyen's API key,
    • origin - URL of your frontend. You could check it by printing out window.location.origin in the browser's console on your website.
    • buildRedirectUrlAfterAuth - (paymentAndOrder: PaymentAndOrder, succeed: boolean) => string - A method that tells the server where to redirect the user after coming back from payment gateway. You can test it using one of the test card numbers (opens new window).
    • buildRedirectUrlAfter3ds1Auth - deprecated in favor of buildRedirectUrlAfterAuth
    • buildRedirectUrlAfterError - (err: Error) => string - A method that tells the server where to redirect the user if error has been thrown inside the cardAuthAfterRedirect controller. Returns full URL Path of the checkout payment step with adyen-err query parameter equal to refused, e.g. '/checkout/payment?adyen-err=refused'
    • buildRedirectUrlAfter3ds1Error - deprecated in favor of buildRedirectUrlAfterError
    • buildRedirectUrlIfMalformedPrice - () => string - deprecated in favor of buildRedirectUrlAfterError
    • buildCustomPaymentAttributes - *optional (client: CommercetoolsClient & { cartId: string, paymentId: string }) => Promise<Record<string, any>> - A method allowing to add more fields to the makePaymentRequest custom field set on payment. The field is used to trigger the actual payment using Commercetools Adyen Integration (opens new window) and its value will be passed onto Adyen.
    • buildCustomOrderAttributes - *optional (client: CommercetoolsClient & { cartId: string, paymentId: string }) => Promise<Record<string, any>> - A method allowing to define additional fields on the OrderFromCartDraft (opens new window) object. The only fields delivered by default are the required cart and version.
    • userSessionCookie - *optional string - Used fetch current user from commercetools and build unique user identifier
type PaymentAndOrder = Payment & { order: Order }

Add an origin to the allowed origins in Adyen's dashboard. You can do it in the same place you looked for the clientKey.

The commercetools created an official Adyen integration (opens new window), which we recommend to deploy as a Google Function or an AWS Lambda. Make sure to configure and deploy both the extension (opens new window) and notification (opens new window) modules. Refer to the Adyen integration repository (opens new window) for more information.

Higher permissions for extensions

As you can see in the commercetools-adyen-integration repository, commercetools recommends using the manage_project scope for both notification and extension modules.

Usage on the frontend

At first, you need to make sure, user provided shipping information and billing address (they must be saved in commercetools). Then add PaymentAdyenProvider.vue components to the last step of the checkout process. This component will mount Adyen's Web Drop-In and handle the payment process for you.

<PaymentAdyenProvider
  :afterPay="afterPayAndOrder"
/>

afterPay props expect a callback function called after authorizing the payment is authorized and placing an order. Inside this callback, you can redirect the user to the order confirmation page and clearing the cart.

const afterPayAndOrder = async ({ order }) => {
  context.root.$router.push(`/checkout/thank-you?order=${order.id}`);
  setCart(null);
};

Adding dedicated view for redirect-based flow error

In our application, the session cookie (named vsf-commercetools-token by default) is in Strict mode. Because of that, it won't be available if the user is being redirected back from a 3rd party website. However, the Strict mode is essential for safety as the mechanism protects users from CSRF attacks. This is why in case of an error we need to redirect the user to a dedicated view inside our application and allow the user to continue manually from there.

We've prepared an example view for that purpose (and other errors that might appear in redirect-based flow). You can use it by creating a new file pages/AdyenPaymentError.vue with the following content:

<template>
  <section id="adyen-payment-error">
    <SfCallToAction
      class="banner"
      :title="$t('Payment error!')"
      :image="'/thank-you/bannerD.webp' | addBasePathFilter"
    >
      <template #description>
        <span v-if="error === 'malformed-price'">{{ $t('We stopped the payment process as it looks like the total price in your cart changed since the payment was initiated. Please provide payment data once again and we will create a payment request with the updated price.') }}</span>
        <span v-else-if="error === 'cancelled-transaction'">{{ $t('We see you cancelled the payment process. Feel free to continue shopping or click button below to come back to the payment page.') }}</span>
        <span v-else-if="error === 'creating-order-failed'">{{ $t("Unfortunately, for some reason we couldn't make an order after succesful payment. We sent refund request. Please try again with different payment method. If problem appears again, please contact the website's administrator.") }}</span>
        <span v-else-if="error === 'unprocessable-entity'">{{ $t("Unfortunately, for some reason the payment service provider couldn't process the payment. Please try again with different payment method. If problem appears again, please contact the website's administrator.") }}</span>
        <span v-else-if="error === 'payment-error'">{{ $t('The payment process failed. Feel free to continue shopping or click button below to come back to the payment page.') }}</span>
        <span v-else>{{ $t('Some unexpected error appeared. Feel free to continue shopping or click button below to come back to the payment page. Please use different payment method next time or contact website\'s administrator if it happens again.') }}</span>
      </template>
    </SfCallToAction>
    <SfButton
      link="checkout/payment"
      class="sf-button back-button color-secondary button-size"
    >
        {{ $t('Go to payment page') }}
      </SfButton>
  </section>
</template>

<script>
import { SfButton, SfCallToAction } from '@storefront-ui/vue';
import { computed, useRoute } from '@nuxtjs/composition-api';

export default {
  name: 'AdyenPaymentError',
  components: {
    SfButton,
    SfCallToAction
  },
  setup () {
    const route = useRoute();
    const error = computed(() => route.value.query.error);

    return {
      error
    };
  }
};
</script>

<style lang="scss" scoped>
#adyen-payment-error {
  box-sizing: border-box;
  @include for-desktop {
    max-width: 1240px;
    padding: 0;
    margin: 0 auto;
  }
}

.banner {
  --call-to-action-color: var(--c-text);
  --call-to-action-title-font-size: var(--h2-font-size);
  --call-to-action-title-font-weight: var(--font-weight--semibold);
  --call-to-action-text-container-width: 50%;
  @include for-desktop {
    margin: 0 0 var(--spacer-xl) 0;
  }
  &__order-number {
    display: flex;
    flex-direction: column;
    font: var(--font-weight--light) var(--font-size--sm) / 1.4
      var(--font-family--primary);
    @include for-desktop {
      flex-direction: row;
      font-size: var(--font-size--normal);
    }
  }
}

.back-button {
  --button-width: calc(100% - var(--spacer-lg));
  margin: 0 auto var(--spacer-base) auto;
  @include for-desktop {
    margin: var(--spacer-xl) auto;
    &:hover {
      color: var(--c-white);
    }
  }
}
.button-size {
  @include for-desktop {
    --button-width: 25rem;
  }
}
</style>

Then add a new route in the nuxt.config.js, by adding a new argument to the routes.push call inside router.extendRoutes:












 
 
 
 
 






export default {
  // ...
  router: {
    middleware: [],
    extendRoutes(routes, resolve) {
      routes.push(
        {
          name: 'home',
          path: '/',
          component: resolve(__dirname, 'pages/Home.vue')
        },
        {
          name: 'adyen-payment-error',
          path: '/adyen-payment-error',
          component: resolve(__dirname, 'pages/AdyenPaymentError.vue')
        },
      );
    }
  }
  // ...
}

Then the user will be able to easily come back to the payment page if they want to. Cancelled Adyen payment view

Placing an order

If the transaction is authorized, the server controller for payAndOrder/submitAdditionalPaymentDetails will place an order in commercetools and add the order object to the response. Thanks to that, we make only one request from the client to finalize/authorize payment and make an order.