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).
availablePaymentMethods
- An array of available payment methods. For a list of all available methods, see the Payment methods (opens new window) page in Adyen's documentation.clientKey
- Your Adyen's client key. See Adyen's documentation to check how to get your client key (opens new window).environment
-test
orlive
.
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 containmanage_orders
,manage_payments
, andview_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 ofhttps://<SHOP_DOMAIN>.com/vsf-ct-dev/graphql
.
- the
adyenMerchantAccount
- name of your Adyen's merchant account,adyenCheckoutApiBaseUrl
- for sandbox it should behttps://checkout-test.adyen.com
, and for live it should behttps://{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 behttps://pal-test.adyen.com
, and for live it should behttps://{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 outwindow.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 ofbuildRedirectUrlAfterAuth
buildRedirectUrlAfterError
-(err: Error) => string
- A method that tells the server where to redirect the user if error has been thrown inside thecardAuthAfterRedirect
controller. Returns full URL Path of the checkout payment step withadyen-err
query parameter equal torefused
, e.g.'/checkout/payment?adyen-err=refused'
buildRedirectUrlAfter3ds1Error
- deprecated in favor ofbuildRedirectUrlAfterError
buildRedirectUrlIfMalformedPrice
-() => string
- deprecated in favor ofbuildRedirectUrlAfterError
buildCustomPaymentAttributes
-*optional (client: CommercetoolsClient & { cartId: string, paymentId: string }) => Promise<Record<string, any>>
- A method allowing to add more fields to themakePaymentRequest
custom field set onpayment
. 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 requiredcart
andversion
.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.
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.