From e91c35c84f627f552f9d59b897b2d6040ee5d997 Mon Sep 17 00:00:00 2001 From: Goran Stamenkovski Date: Tue, 5 Dec 2023 13:16:31 +0100 Subject: [PATCH] Upgrade API version to 71 Updated the checkout API version to 71 which enabled to remove legacy endpoint for deleting stored payment methods. Also, this change allow for usage of latest version of web components (5.53.2 at the time of change). ISSUE: ACS0CAUI-1, ACS0CAUI-2 --- extension/docs/HowToRun.md | 33 +++++----- .../UpdateAmountAndExtendAuthorisation.md | 4 +- .../docs/WebComponentsIntegrationGuide.md | 2 +- extension/src/config/config.js | 4 +- extension/src/config/constants.js | 6 -- .../paymentHandler/amount-updates.handler.js | 18 +++++ .../src/service/web-component-service.js | 66 +++++++++++++++---- .../e2e/credit-card-amount-update.spec.js | 7 +- extension/test/e2e/e2e-test-utils.js | 2 +- .../fixtures/3ds-v2-init-session-form.html | 8 +-- .../fixtures/3ds-v2-make-payment-form.html | 8 +-- .../fixtures/affirm-init-session-form.html | 8 +-- .../credit-card-init-session-form.html | 8 +-- .../fixtures/klarna-init-session-form.html | 8 +-- .../fixtures/paypal-init-session-form.html | 8 +-- .../redirect-payment-form-advanced-flow.html | 8 +-- .../e2e/fixtures/redirect-payment-form.html | 8 +-- .../CreditCardInitSessionFormPage.js | 11 +++- extension/test/test-utils.js | 2 +- .../test/unit/amount-updates.handler.spec.js | 2 +- extension/test/unit/config/config.spec.js | 5 +- extension/test/unit/validator-builder.spec.js | 2 +- notification/test/fake-extension-service.js | 2 +- 23 files changed, 143 insertions(+), 87 deletions(-) diff --git a/extension/docs/HowToRun.md b/extension/docs/HowToRun.md index f44424a28..dbd9a956c 100644 --- a/extension/docs/HowToRun.md +++ b/extension/docs/HowToRun.md @@ -87,22 +87,21 @@ Extension module requires 1 environment variable to start. This environment vari ### Optional attributes -| Group | Name | Content | Default value | -| --------------- | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `adyen` | `apiBaseUrl` | [Checkout endpoint](https://docs.adyen.com/development-resources/live-endpoints#checkout-endpoints) of Adyen. | `https://checkout-test.adyen.com/v68` (even though it is not required, you **need** to specify a URL for **live environment**) | -| `adyen` | `legacyApiBaseUrl` | [Standard payment endpoint](https://docs.adyen.com/development-resources/live-endpoints#standard-payments-endpoints) of Adyen. | `https://pal-test.adyen.com/pal/servlet` (even though it is not required, you **need** to specify a URL for **live environment** to use e.g. manualCapture, refund, cancel) | -| `commercetools` | `apiUrl` | The commercetools HTTP API is hosted at that URL. | `https://api.europe-west1.gcp.commercetools.com` | -| `commercetools` | `authUrl` | The commercetools’ OAuth 2.0 service is hosted at that URL. | `https://auth.europe-west1.gcp.commercetools.com` | -| `commercetools` | `authentication` | This setting only takes effect when `basicAuth` ( a child attribute in `ADYEN_INTEGRATION_CONFIG` ) is set to `true`. It enables authentication mechanism to prevent unauthorized access to the extension module. When it is provided as a JSON object, it must contain 3 separate attributes. They are `scheme` attribute which supports `basic` type, `username` and `password` attribute defined by user. | | -| `other` | `basicAuth` | Boolean attribute to enable/disable basic authentication to prevent unauthorized 3rd-party from accessing extension endpoint | false | -| `other` | `adyenPaymentMethodsToNames` | Key-value object where key is `paymentMethod` attribute from Adyen AUTHORIZATION notification and value is the custom localized name that will be saved in CTP `payment.paymentMethodInfo.name`. | `{scheme: {en: 'Credit Card'}, pp: {en: 'PayPal'}, klarna: {en: 'Klarna'}, gpay: {en: 'Google Pay'}, affirm: {en: 'Affirm'}` | -| `other` | `removeSensitiveData` | Boolean attribute. When set to "false", Adyen fields with additional information about the payment will be saved in the interface interaction and in the custom fields. This attribute can also be overridden per request by adding `removeSensitiveData` to the request. For an example usage see [Store payment documentation](./StorePayment.md). | true | -| `other` | `addCommercetoolsLineItems` | Boolean attribute. If set to **true**, integration will add lineItems to payment methods that require lineItems on `createSessionRequest`. Payment method types that requires [lineItems](https://docs.adyen.com/api-explorer/#/CheckoutService/latest/payments__reqParam_lineItems): `klarna`, `affirm`, `afterpay`, `afterpaytouch`, `ratepay`, `facilypay`, `clearpay`, `grabpay`, `paybright`, `pix`, `zip`. | false | -| `other` | `port` | The port number on which the application will run. | 8080 | -| `other` | `logLevel` | The log level (`trace`, `debug`, `info`, `warn`, `error`, `fatal`). | `info` | -| `other` | `keepAliveTimeout` | Milliseconds to keep a socket alive after the last response ([Node.js docs](https://nodejs.org/dist/latest/docs/api/http.html#http_server_keepalivetimeout)). | Node.js default (5 seconds) | -| `other` | `generateIdempotencyKey` | If set to true, adyen-integration will generate the idempotency key for capture and refund requests. | false | -| `other` | `apiExtensionBaseUrl` | Publicly available URL of the Extension module. In case of any payment changes, [commercetools API extension](https://docs.commercetools.com/api/projects/api-extensions) will call this URL and pass the payment object in body. This attribute is used when calling `npm run setup-resources` | | +| Group | Name | Content | Default value | +| --------------- | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | +| `adyen` | `apiBaseUrl` | [Checkout endpoint](https://docs.adyen.com/development-resources/live-endpoints#checkout-endpoints) of Adyen. | `https://checkout-test.adyen.com/v71` (even though it is not required, you **need** to specify a URL for **live environment**) | +| `commercetools` | `apiUrl` | The commercetools HTTP API is hosted at that URL. | `https://api.europe-west1.gcp.commercetools.com` | +| `commercetools` | `authUrl` | The commercetools’ OAuth 2.0 service is hosted at that URL. | `https://auth.europe-west1.gcp.commercetools.com` | +| `commercetools` | `authentication` | This setting only takes effect when `basicAuth` ( a child attribute in `ADYEN_INTEGRATION_CONFIG` ) is set to `true`. It enables authentication mechanism to prevent unauthorized access to the extension module. When it is provided as a JSON object, it must contain 3 separate attributes. They are `scheme` attribute which supports `basic` type, `username` and `password` attribute defined by user. | | +| `other` | `basicAuth` | Boolean attribute to enable/disable basic authentication to prevent unauthorized 3rd-party from accessing extension endpoint | false | +| `other` | `adyenPaymentMethodsToNames` | Key-value object where key is `paymentMethod` attribute from Adyen AUTHORIZATION notification and value is the custom localized name that will be saved in CTP `payment.paymentMethodInfo.name`. | `{scheme: {en: 'Credit Card'}, pp: {en: 'PayPal'}, klarna: {en: 'Klarna'}, gpay: {en: 'Google Pay'}, affirm: {en: 'Affirm'}` | +| `other` | `removeSensitiveData` | Boolean attribute. When set to "false", Adyen fields with additional information about the payment will be saved in the interface interaction and in the custom fields. This attribute can also be overridden per request by adding `removeSensitiveData` to the request. For an example usage see [Store payment documentation](./StorePayment.md). | true | +| `other` | `addCommercetoolsLineItems` | Boolean attribute. If set to **true**, integration will add lineItems to payment methods that require lineItems on `createSessionRequest`. Payment method types that requires [lineItems](https://docs.adyen.com/api-explorer/#/CheckoutService/latest/payments__reqParam_lineItems): `klarna`, `affirm`, `afterpay`, `afterpaytouch`, `ratepay`, `facilypay`, `clearpay`, `grabpay`, `paybright`, `pix`, `zip`. | false | +| `other` | `port` | The port number on which the application will run. | 8080 | +| `other` | `logLevel` | The log level (`trace`, `debug`, `info`, `warn`, `error`, `fatal`). | `info` | +| `other` | `keepAliveTimeout` | Milliseconds to keep a socket alive after the last response ([Node.js docs](https://nodejs.org/dist/latest/docs/api/http.html#http_server_keepalivetimeout)). | Node.js default (5 seconds) | +| `other` | `generateIdempotencyKey` | If set to true, adyen-integration will generate the idempotency key for capture and refund requests. | false | +| `other` | `apiExtensionBaseUrl` | Publicly available URL of the Extension module. In case of any payment changes, [commercetools API extension](https://docs.commercetools.com/api/projects/api-extensions) will call this URL and pass the payment object in body. This attribute is used when calling `npm run setup-resources` | | ### External file configuration @@ -134,7 +133,7 @@ Please run following CURL command in order to list out all payment methods in US Make sure Affirm payment is inside the response. ```bash -curl https://checkout-test.adyen.com/v68/paymentMethods \ +curl https://checkout-test.adyen.com/v71/paymentMethods \ -H "x-API-key: YOUR_ADYEN_X-API-KEY" \ -H "content-type: application/json" \ -d '{ diff --git a/extension/docs/UpdateAmountAndExtendAuthorisation.md b/extension/docs/UpdateAmountAndExtendAuthorisation.md index 1f7373c4f..148ea2912 100644 --- a/extension/docs/UpdateAmountAndExtendAuthorisation.md +++ b/extension/docs/UpdateAmountAndExtendAuthorisation.md @@ -142,7 +142,7 @@ Additionally, it must contain `paymentPspReference` field. `paymentPspReference` { "action": "setCustomField", "name": "amountUpdatesRequest", - "value": "{\"amount\":{\"currency\":\"EUR\",\"value\":1000},\"reason\":\"DelayedCharge\",\"reference\":\"YOUR_PAYMENT_REFERENCE\",\"merchantAccount\":\"YOUR_MERCHANT_ACCOUNT\", \"paymentPspReference\":\"853592567856061C\"}" + "value": "{\"amount\":{\"currency\":\"EUR\",\"value\":1000},\"industryUsage\":\"delayedCharge\",\"reference\":\"YOUR_PAYMENT_REFERENCE\",\"merchantAccount\":\"YOUR_MERCHANT_ACCOUNT\", \"paymentPspReference\":\"853592567856061C\"}" } ] } @@ -230,7 +230,7 @@ To do `amountUpdatesRequest` multiple times, you need to remove the custom field { "action": "setCustomField", "name": "amountUpdatesRequest", - "value": "{\"amount\":{\"currency\":\"EUR\",\"value\":1000},\"reason\":\"DelayedCharge\",\"reference\":\"YOUR_PAYMENT_REFERENCE\",\"merchantAccount\":\"YOUR_MERCHANT_ACCOUNT\"}" + "value": "{\"amount\":{\"currency\":\"EUR\",\"value\":1000},\"industryUsage\":\"delayedCharge\",\"reference\":\"YOUR_PAYMENT_REFERENCE\",\"merchantAccount\":\"YOUR_MERCHANT_ACCOUNT\"}" }, { "action": "setCustomField", diff --git a/extension/docs/WebComponentsIntegrationGuide.md b/extension/docs/WebComponentsIntegrationGuide.md index e69632a07..5c9681cb1 100644 --- a/extension/docs/WebComponentsIntegrationGuide.md +++ b/extension/docs/WebComponentsIntegrationGuide.md @@ -41,7 +41,7 @@ Terms used in this guide: - **Extension module** - [extension module](https://github.com/commercetools/commercetools-adyen-integration#extension-module) configured as [commercetools HTTP API Extensions](https://docs.commercetools.com/api/projects/api-extensions) is handling checkout steps by intercepting payment modifications and communicating with Adyen API. - **Notification module** - [notification module](https://github.com/commercetools/commercetools-adyen-integration#notification-module) processes asynchronous notifications from Adyen and stores payment state changes in commercetools payment object. -The following diagram shows checkout integration flow based on [Adyen Web Components](https://docs.adyen.com/online-payments/build-your-integration?platform=Web&integration=Components&version=5.43.0#how-it-works). +The following diagram shows checkout integration flow based on [Adyen Web Components](https://docs.adyen.com/online-payments/build-your-integration?platform=Web&integration=Components&version=5.53.2#how-it-works). ![Flow](./adyen-checkout-flow-diagram.png) diff --git a/extension/src/config/config.js b/extension/src/config/config.js index e5f32503b..e7eb991a9 100644 --- a/extension/src/config/config.js +++ b/extension/src/config/config.js @@ -98,9 +98,7 @@ function getAdyenConfig(adyenMerchantAccount) { ) return { apiKey: adyenConfig.apiKey, - apiBaseUrl: adyenConfig.apiBaseUrl || 'https://checkout-test.adyen.com/v68', - legacyApiBaseUrl: - adyenConfig.legacyApiBaseUrl || 'https://pal-test.adyen.com/pal/servlet', + apiBaseUrl: adyenConfig.apiBaseUrl || 'https://checkout-test.adyen.com/v71', clientKey: adyenConfig.clientKey || '', // used only for development purpose, paypalMerchantId: adyenConfig.paypalMerchantId || '', // used only for development purpose } diff --git a/extension/src/config/constants.js b/extension/src/config/constants.js index 3cdd106c5..aaec1e448 100644 --- a/extension/src/config/constants.js +++ b/extension/src/config/constants.js @@ -43,10 +43,4 @@ export default { CTP_INTERACTION_TYPE_CREATE_SESSION_RESPONSE: 'createSessionResponse', CTP_INTERACTION_TYPE_DISABLE_STORED_PAYMENT: 'disableStoredPayment', CTP_DISABLE_STORED_PAYMENT_RESPONSE: 'disableStoredPaymentResponse', - ADYEN_LEGACY_API_VERSION: { - MANUAL_CAPTURE: 'v64', - CANCEL: 'v64', - REFUND: 'v64', - DISABLED_STORED_PAYMENT: 'v68', - }, } diff --git a/extension/src/paymentHandler/amount-updates.handler.js b/extension/src/paymentHandler/amount-updates.handler.js index 66aa2ce24..77a79d725 100644 --- a/extension/src/paymentHandler/amount-updates.handler.js +++ b/extension/src/paymentHandler/amount-updates.handler.js @@ -9,6 +9,24 @@ async function execute(paymentObject) { const amountUpdatesRequestObj = JSON.parse( paymentObject.custom.fields.amountUpdatesRequest, ) + + // Adjust request for older API version reason field. + if (amountUpdatesRequestObj.reason) { + amountUpdatesRequestObj.industryUsage = + amountUpdatesRequestObj.reason.charAt(0).toLowerCase() + + amountUpdatesRequestObj.reason.slice(1) + + delete amountUpdatesRequestObj.reason + } + + if ( + !['delayedCharge', 'noShow', 'installment'].includes( + amountUpdatesRequestObj.industryUsage, + ) + ) { + delete amountUpdatesRequestObj.industryUsage + } + const adyenMerchantAccount = paymentObject.custom.fields.adyenMerchantAccount const commercetoolsProjectKey = paymentObject.custom.fields.commercetoolsProjectKey diff --git a/extension/src/service/web-component-service.js b/extension/src/service/web-component-service.js index f3d2d0833..1206e1198 100644 --- a/extension/src/service/web-component-service.js +++ b/extension/src/service/web-component-service.js @@ -2,7 +2,6 @@ import fetch from 'node-fetch' import { serializeError } from 'serialize-error' import config from '../config/config.js' import utils from '../utils.js' -import constants from '../config/constants.js' async function getPaymentMethods(merchantAccount, getPaymentMethodsRequestObj) { const adyenCredentials = config.getAdyenConfig(merchantAccount) @@ -151,17 +150,31 @@ async function createSessionRequest( ) } -function disableStoredPayment(merchantAccount, disableStoredPaymentRequestObj) { +async function disableStoredPayment( + merchantAccount, + disableStoredPaymentRequestObj, +) { const adyenCredentials = config.getAdyenConfig(merchantAccount) - const url = - `${adyenCredentials.legacyApiBaseUrl}/Recurring/` + - `${constants.ADYEN_LEGACY_API_VERSION.DISABLED_STORED_PAYMENT}/disable` - return callAdyen( + + const recurringReference = + disableStoredPaymentRequestObj.recurringDetailReference + const url = `${adyenCredentials.apiBaseUrl}/storedPaymentMethods/${recurringReference}` + delete disableStoredPaymentRequestObj.recurringDetailReference + + const result = await callAdyen( url, merchantAccount, adyenCredentials.apiKey, disableStoredPaymentRequestObj, + [], + 'DELETE', ) + + if (!result.response) { + result.response = { response: '[detail-successfully-disabled]' } + } + + return result } async function extendRequestObjWithApplicationInfo(requestObj) { @@ -200,6 +213,7 @@ async function callAdyen( adyenApiKey, requestArg, headers, + methodOverride, ) { let returnedRequest let returnedResponse @@ -210,6 +224,7 @@ async function callAdyen( adyenApiKey, requestArg, headers, + methodOverride, ) returnedRequest = request returnedResponse = response @@ -227,6 +242,7 @@ async function fetchAsync( adyenApiKey, requestObj, headers, + methodOverride, ) { const moduleConfig = config.getModuleConfig() const removeSensitiveData = @@ -240,13 +256,19 @@ async function fetchAsync( adyenApiKey, requestObj, headers, + methodOverride, ) + if (methodOverride === 'DELETE') { + url += `?${request.body}` + delete request.body + } + try { response = await fetch(url, request) responseBodyInText = await response.text() - responseBody = JSON.parse(responseBodyInText) + responseBody = responseBodyInText ? JSON.parse(responseBodyInText) : '' } catch (err) { if (response) // Handle non-JSON format response @@ -265,20 +287,36 @@ async function fetchAsync( return { response: responseBody, request } } -function buildRequest(adyenMerchantAccount, adyenApiKey, requestObj, headers) { +function buildRequest( + adyenMerchantAccount, + adyenApiKey, + requestObj, + headers, + methodOverride, +) { // Note: ensure the merchantAccount is set with request, otherwise set // it with the value from adyenMerchantAccount payment custom field if (!requestObj.merchantAccount) requestObj.merchantAccount = adyenMerchantAccount + const requestHeaders = { + 'Content-Type': 'application/json', + 'X-Api-Key': adyenApiKey, + ...headers, + } + + if (methodOverride === 'DELETE') { + return { + method: methodOverride, + headers: requestHeaders, + body: new URLSearchParams(requestObj), + } + } + return { - method: 'POST', + method: methodOverride || 'POST', body: JSON.stringify(requestObj), - headers: { - 'Content-Type': 'application/json', - 'X-Api-Key': adyenApiKey, - ...headers, - }, + headers: requestHeaders, } } diff --git a/extension/test/e2e/credit-card-amount-update.spec.js b/extension/test/e2e/credit-card-amount-update.spec.js index 13e379a27..0e4efd1d0 100644 --- a/extension/test/e2e/credit-card-amount-update.spec.js +++ b/extension/test/e2e/credit-card-amount-update.spec.js @@ -143,7 +143,10 @@ describe('::creditCardPayment::amount-update::', () => { initPaymentSessionResult, ) expect(updatedAmountStatusCode).to.equal(200) - expect(amountUpdatesResponse.status).to.equal('received') + expect(amountUpdatesResponse.status).to.equal( + 'received', + amountUpdatesResponse.body, + ) expect(amountUpdatesInterfaceInteractions).to.have.lengthOf(1) // assert notification response from amount updates @@ -170,7 +173,7 @@ describe('::creditCardPayment::amount-update::', () => { currency: 'EUR', value: 1010, }, - reason: 'DelayedCharge', + reason: 'delayedCharge', reference: paymentAfterCreateSession.key, } const { body: paymentAfterReceivingNotification } = diff --git a/extension/test/e2e/e2e-test-utils.js b/extension/test/e2e/e2e-test-utils.js index 931572fc7..b058b8cc6 100644 --- a/extension/test/e2e/e2e-test-utils.js +++ b/extension/test/e2e/e2e-test-utils.js @@ -33,7 +33,7 @@ async function initPuppeteerBrowser() { ignoreHTTPSErrors: true, args: [ '--disable-web-security', - '--disable-features=IsolateOrigins,site-per-process', + '--disable-features=SameSiteByDefaultCookies,CookiesWithoutSameSiteMustBeSecure,IsolateOrigins,site-per-process', // user-agent is overriden to bypass the "reminder" page of localtunnel module '--user-agent=curl/7.64.1', ], diff --git a/extension/test/e2e/fixtures/3ds-v2-init-session-form.html b/extension/test/e2e/fixtures/3ds-v2-init-session-form.html index f1374c91b..653517d8d 100644 --- a/extension/test/e2e/fixtures/3ds-v2-init-session-form.html +++ b/extension/test/e2e/fixtures/3ds-v2-init-session-form.html @@ -5,8 +5,8 @@ Make Payment @@ -47,8 +47,8 @@