From 573a5fe0d4611f2b04521a1229c80424d5e8a8d1 Mon Sep 17 00:00:00 2001 From: Aleksandar Mihajlovski Date: Fri, 9 Feb 2024 16:00:05 +0100 Subject: [PATCH 01/10] chore: throw error if getting error fails (#111) --- .../adyen-salesforce-pwa/lib/api/controllers/orderApi.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/adyen-salesforce-pwa/lib/api/controllers/orderApi.js b/packages/adyen-salesforce-pwa/lib/api/controllers/orderApi.js index c465e1a..71f53af 100644 --- a/packages/adyen-salesforce-pwa/lib/api/controllers/orderApi.js +++ b/packages/adyen-salesforce-pwa/lib/api/controllers/orderApi.js @@ -19,7 +19,12 @@ export class OrderApiClient { scope: `SALESFORCE_COMMERCE_API:${process.env.SFCC_REALM_ID}_${process.env.SFCC_INSTANCE_ID} ${process.env.SFCC_OAUTH_SCOPES}` }) }) - + if (!token.ok) { + const error = await token.text() + throw new Error(`${token.status} ${token.statusText}`, { + cause: error + }) + } return token.json() } From deea77d2dda013716b05e51c66884921c81535ab Mon Sep 17 00:00:00 2001 From: Aleksandar Mihajlovski Date: Mon, 12 Feb 2024 13:23:46 +0100 Subject: [PATCH 02/10] feat: upgrade api library to v16 (#113) * feat: upgrade api library to v16 * feat: bump version in read me --- packages/adyen-salesforce-pwa/README.md | 2 +- packages/adyen-salesforce-pwa/package-lock.json | 16 ++++++++++++---- packages/adyen-salesforce-pwa/package.json | 2 +- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/packages/adyen-salesforce-pwa/README.md b/packages/adyen-salesforce-pwa/README.md index 77b6e5b..ceaef6e 100644 --- a/packages/adyen-salesforce-pwa/README.md +++ b/packages/adyen-salesforce-pwa/README.md @@ -23,7 +23,7 @@ Adyen Payments Composable Storefront Integration for B2C Commerce depends on: 1. PWA v3.1.1 2. [Adyen Web v5.51.0](https://www.npmjs.com/package/@adyen/adyen-web) -3. [Adyen API Library for Node.js v14.3.0](https://www.npmjs.com/package/@adyen/api-library) +3. [Adyen API Library for Node.js v16.0.1](https://www.npmjs.com/package/@adyen/api-library) 4. Node v18 or later 5. NPM v9 or later 6. Salesforce Managed Runtime diff --git a/packages/adyen-salesforce-pwa/package-lock.json b/packages/adyen-salesforce-pwa/package-lock.json index 7b33851..c76388b 100644 --- a/packages/adyen-salesforce-pwa/package-lock.json +++ b/packages/adyen-salesforce-pwa/package-lock.json @@ -29,12 +29,20 @@ } }, "@adyen/api-library": { - "version": "14.4.0", - "resolved": "https://registry.npmjs.org/@adyen/api-library/-/api-library-14.4.0.tgz", - "integrity": "sha512-HHX9tJQxJud0ZWRgh2qGejqQMASR/Gk/u/y9M3cx4a/cFqMWBh+nWHkvTRYuGHrTf65uLzfNLT2dA/tHD/MENA==", + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@adyen/api-library/-/api-library-16.0.1.tgz", + "integrity": "sha512-xTXsNapIGisFNKDmFm74+LmdVNTmPh4AhEfjBpYmi3J9ec6i9edmZdhJ/AkaHuS7oDjXOB9MneCexMjyOF+6Hw==", "requires": { - "@types/node": "14.18.61", + "@types/node": "14.18.63", "https-proxy-agent": "5.0.1" + }, + "dependencies": { + "@types/node": { + "version": "14.18.63", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz", + "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==", + "optional": true + } } }, "@ampproject/remapping": { diff --git a/packages/adyen-salesforce-pwa/package.json b/packages/adyen-salesforce-pwa/package.json index 392f491..044ee9d 100644 --- a/packages/adyen-salesforce-pwa/package.json +++ b/packages/adyen-salesforce-pwa/package.json @@ -37,7 +37,7 @@ }, "dependencies": { "@adyen/adyen-web": "^5.51.0", - "@adyen/api-library": "^14.3.0", + "@adyen/api-library": "^16.0.1", "@salesforce/retail-react-app": "2.0.0", "dotenv": "^16.3.1", "express-validator": "^7.0.1", From 9468aba6d398e02eca27167627d1990bfa04d405 Mon Sep 17 00:00:00 2001 From: olga-adyen <98472671+olga-adyen@users.noreply.github.com> Date: Tue, 13 Feb 2024 11:06:41 +0100 Subject: [PATCH 03/10] Update README.md (#114) * Update README.md clean up the read me, remove installation instructions --> Docs only * Update README.md --- packages/adyen-salesforce-pwa/README.md | 71 +++---------------------- 1 file changed, 7 insertions(+), 64 deletions(-) diff --git a/packages/adyen-salesforce-pwa/README.md b/packages/adyen-salesforce-pwa/README.md index ceaef6e..33f5579 100644 --- a/packages/adyen-salesforce-pwa/README.md +++ b/packages/adyen-salesforce-pwa/README.md @@ -1,21 +1,10 @@ # Adyen Salesforce PWA -This npm package provides the opportunity to use Adyen as a payment service provider when building your Salesforce PWA -application. +This NPM package enables you to go live fast with payments with Adyen as a payment service provider when building your Salesforce PWA +Retail application. ---- -**NOTE** - -This version is in beta and may not be suitable for production use. -We anticipate the general availability solution to be ready by Q1 2024. - -Please be aware that the beta version is not supported by Adyen until general availability, will not be providing -technical support for this specific beta release. - -We encourage users to explore the package, provide feedback, and report any issues via -our [GitHub repository](https://github.com/Adyen/adyen-salesforce-headless-commerce-pwa.git). - ---- +To request a feature, report a bug, or report a security +vulnerability, [create a GitHub issue](https://github.com/Adyen/adyen-salesforce-headless-commerce-pwa/issues/new/choose). ## Dependencies & Requirements @@ -30,51 +19,8 @@ Adyen Payments Composable Storefront Integration for B2C Commerce depends on: ## Installation -1. Install the npm package using the following command - ```shell - npm install @adyen/adyen-salesforce-pwa - ``` -2. Import the Adyen endpoints function in the `ssr.js` file: - ```ecmascript 6 - import {registerAdyenEndpoints} from '@adyen/adyen-salesforce-pwa/dist/ssr/index.js' - ``` -3. Include it as part of the server handler callback in the `ssr.js` file before last `app.get()` handler: - - ```ecmascript 6 - const {handler} = runtime.createHandler(options, (app) => { - // ... - - registerAdyenEndpoints(app, runtime) - - app.get('*', runtime.render) - }) - ``` -4. Include Adyen checkout pages in the `routes.jsx` of your `retail-react-app`. - Check [routes.jsx](../adyen-retail-react-app/overrides/app/routes.jsx) file for reference. - -5. In your `retail-react-app` you would need to create a .env file. Check - the [example file](../adyen-retail-react-app/.env.example) in our reference application. - -6. Import `countrylist` in `constants.js` of your `retail-react-app` and export it as `SHIPPING_COUNTRY_CODES`: - - ```ecmascript 6 - import {countryList} from '@adyen/adyen-salesforce-pwa' - - export const SHIPPING_COUNTRY_CODES = countryList - ``` -7. To run the app locally with env variables execute the following command: - ```shell - npm run start:env - ``` -8. To push your env variables to the MRT environment execute the following command: - ```shell - npm run upload-env - ``` - -9. To see which env variables are present in the MRT environment execute the following command: - ```shell - npm run get-env - ``` +For set-up, installation, and Go-Live instructions, refer to [Adyen Docs](https://docs.adyen.com/plugins/salesforce-commerce-cloud/composable-storefront). +Available payment methods and features can be found on [Adyen Docs](https://docs.adyen.com/plugins/salesforce-commerce-cloud). ## Prerequisites @@ -84,11 +30,8 @@ Adyen Payments Composable Storefront Integration for B2C Commerce depends on: ## Support -To request a feature, report a bug, or report a security -vulnerability, [create a GitHub issue](https://github.com/Adyen/adyen-salesforce-headless-commerce-pwa/issues/new/choose). - For other questions, contact our [support team](https://www.adyen.help). ## License -This repository is available under the [MIT license](LICENSE). \ No newline at end of file +This repository is available under the [MIT license](LICENSE). From f6f01c8b1894b4d52e91f0f80f5ef312f89fa4ef Mon Sep 17 00:00:00 2001 From: olga-adyen <98472671+olga-adyen@users.noreply.github.com> Date: Wed, 14 Feb 2024 13:07:58 +0100 Subject: [PATCH 04/10] Olga adyen patch 1 (#115) * Update README.md revamp of read me. Review the Installation segment - we need to keep Docs only. * Update README.md --- README.md | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 7c763f0..ae73ad5 100644 --- a/README.md +++ b/README.md @@ -4,25 +4,30 @@ This integration provides the opportunity to use Adyen as a payment service prov [Download the NPM package of Adyen integration here](https://www.npmjs.com/package/@adyen/adyen-salesforce-pwa?activeTab=readme) In this GitHub repo there are two packages: -* `Adyen retail react app`: Reference application how to integrate the Adyen Payments Integration into your PWA application. This reference applicaiton should not be used in production projects. -* `Adyen salesforce pwa`: source code for the Adyen PWA NPM package. This code is published to the [NPM](https://www.npmjs.com/package/@adyen/adyen-salesforce-pwa?activeTab=readm). +* `Adyen-salesforce-pwa`: source code for the Adyen PWA NPM package, that contains the default payments integration. This code is published to the [NPM](https://www.npmjs.com/package/@adyen/adyen-salesforce-pwa?activeTab=readme). +* `Adyen-retail-react-app`: a reference application that can be used for a demo of how to integrate the Adyen Payments Integration (NPM package) into your PWA Retail application. This reference application should not be used in production projects and we do not provide support on this implementaiotn. ## Prerequisites * [Adyen test account](https://www.adyen.com/signup) * [API key](https://docs.adyen.com/development-resources/how-to-get-the-api-key) * [Client key](https://docs.adyen.com/development-resources/client-side-authentication#get-your-client-key) -* See the NPM package [Readme](https://www.npmjs.com/package/@adyen/adyen-salesforce-pwa?activeTab=readme) for installation instructions. -## Support & Maintenance +## Installation and Configuration +* Refer to [Adyen Docs](https://docs.adyen.com/plugins/salesforce-commerce-cloud/composable-storefront) for NPM set-up, installation and Go-Live instructions. +* Refer to [Adyen Docs](https://docs.adyen.com/plugins/salesforce-commerce-cloud/composable-storefront/customization-guide) for customisation instructions. +* Available Payment methods and features can be found on [Adyen Docs](https://docs.adyen.com/plugins/salesforce-commerce-cloud). -The NPM package version is in beta and may not be suitable for production use. We anticipate the General Availability solution to be ready by Q1 2024. +## Support & Maintenance -Please be aware, that the NPM package in Beta and 'Adyen retail react app` are not supported by Adyen Support. +We provide specialized integration support for major versions of the NPM package following the [SFCC B2C Support policy](https://docs.adyen.com/plugins/salesforce-commerce-cloud/#support-levels), along with permanent Adyen support. +Migration from SFRA/SG and Upgrade Guide can be found on [Adyen Docs](https://docs.adyen.com/plugins/salesforce-commerce-cloud/composable-storefront). +For other questions, contact our [support team](https://www.adyen.help). -To request a feature, report a bug, or report a security vulnerability, [create a GitHub issue](https://github.com/Adyen/adyen-salesforce-headless-commerce-pwa/issues/new/choose). +## Upgrade -For other questions, contact our [support team](https://www.adyen.help). +We recommend that you upgrade when we have a major release, such as v**2**.x.x. +You can find the latest version on our [NPM](https://www.npmjs.com/package/@adyen/adyen-salesforce-pwa?activeTab=readme) repository. ## License From 48611f77ba3b9868e201e2e001ea26436eaaf31a Mon Sep 17 00:00:00 2001 From: Aleksandar Mihajlovski Date: Wed, 14 Feb 2024 16:48:51 +0100 Subject: [PATCH 05/10] Blocked payment methods (#117) * chore: block wechat variants * chore: update tests --- .../api/controllers/tests/payment-methods.test.js | 7 ++++--- .../adyen-salesforce-pwa/lib/utils/constants.mjs | 12 ++++++++++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/packages/adyen-salesforce-pwa/lib/api/controllers/tests/payment-methods.test.js b/packages/adyen-salesforce-pwa/lib/api/controllers/tests/payment-methods.test.js index 5db2843..f971aff 100644 --- a/packages/adyen-salesforce-pwa/lib/api/controllers/tests/payment-methods.test.js +++ b/packages/adyen-salesforce-pwa/lib/api/controllers/tests/payment-methods.test.js @@ -47,6 +47,7 @@ jest.mock('../checkout-config', () => { }) describe('payment methods controller', () => { let req, res, next, consoleInfoSpy, consoleErrorSpy + let blockedPaymentMethods = ['giftcard', 'wechatpayMiniProgram', 'wechatpayQR', 'wechatpaySDK'] beforeEach(() => { req = { @@ -99,7 +100,7 @@ describe('payment methods controller', () => { expect(mockPaymentMethods).toHaveBeenCalledWith( { amount: {currency: 'USD', value: 10000}, - blockedPaymentMethods: ['giftcard'], + blockedPaymentMethods, countryCode: 'US', merchantAccount: 'mock_ADYEN_MERCHANT_ACCOUNT', shopperLocale: 'en-US', @@ -153,7 +154,7 @@ describe('payment methods controller', () => { expect(mockPaymentMethods).toHaveBeenCalledWith( { amount: {currency: 'USD', value: 10000}, - blockedPaymentMethods: ['giftcard'], + blockedPaymentMethods, countryCode: 'US', merchantAccount: 'mock_ADYEN_MERCHANT_ACCOUNT', shopperLocale: 'en-US', @@ -223,7 +224,7 @@ describe('payment methods controller', () => { await PaymentMethodsController(req, res, next) expect(mockPaymentMethods).toHaveBeenCalledWith( { - blockedPaymentMethods: ['giftcard'], + blockedPaymentMethods, countryCode: 'US', merchantAccount: 'mock_ADYEN_MERCHANT_ACCOUNT', shopperLocale: 'en-US', diff --git a/packages/adyen-salesforce-pwa/lib/utils/constants.mjs b/packages/adyen-salesforce-pwa/lib/utils/constants.mjs index 35ddf5b..258cb81 100644 --- a/packages/adyen-salesforce-pwa/lib/utils/constants.mjs +++ b/packages/adyen-salesforce-pwa/lib/utils/constants.mjs @@ -4,7 +4,10 @@ export const PAYMENT_METHODS = { } export const PAYMENT_METHOD_TYPES = { - GIFT_CARD: 'giftcard' + GIFT_CARD: 'giftcard', + WECHATPAY_MINI_PROGRAM: 'wechatpayMiniProgram', + WECHATPAY_QR: 'wechatpayQR', + WECHATPAY_SDK: 'wechatpaySDK' } export const RESULT_CODES = { @@ -20,7 +23,12 @@ export const RESULT_CODES = { REFUSED: 'Refused', } -export const BLOCKED_PAYMENT_METHODS = [PAYMENT_METHOD_TYPES.GIFT_CARD] +export const BLOCKED_PAYMENT_METHODS = [ + PAYMENT_METHOD_TYPES.GIFT_CARD, + PAYMENT_METHOD_TYPES.WECHATPAY_MINI_PROGRAM, + PAYMENT_METHOD_TYPES.WECHATPAY_QR, + PAYMENT_METHOD_TYPES.WECHATPAY_SDK +] export const SHOPPER_INTERACTIONS = { CONT_AUTH: 'ContAuth', From 6a70c605d519bc7e7f1cdb6fe2cf86cc9f023ddd Mon Sep 17 00:00:00 2001 From: Aleksandar Mihajlovski Date: Thu, 15 Feb 2024 10:36:23 +0100 Subject: [PATCH 06/10] unit tests adyen checkout (#116) * feat: upgrade api library to v16 * feat: bump version in read me * feat: added tests for adyen checkout component and increased the unit test coverage --- packages/adyen-salesforce-pwa/jest.config.js | 8 +- .../lib/components/adyenCheckout.jsx | 91 ++++++---- .../components/tests/adyenCheckout.test.js | 159 ++++++++++++++++++ .../tests/adyen-checkout-context.test.js | 89 +++++----- 4 files changed, 272 insertions(+), 75 deletions(-) create mode 100644 packages/adyen-salesforce-pwa/lib/components/tests/adyenCheckout.test.js diff --git a/packages/adyen-salesforce-pwa/jest.config.js b/packages/adyen-salesforce-pwa/jest.config.js index d5e0b56..7ab4159 100644 --- a/packages/adyen-salesforce-pwa/jest.config.js +++ b/packages/adyen-salesforce-pwa/jest.config.js @@ -10,10 +10,10 @@ module.exports = { moduleFileExtensions: ['js', 'jsx', 'mjs', 'cjs'], coverageThreshold: { global: { - branches: 80, - functions: 80, - lines: 80, - statements: 80 + branches: 85, + functions: 85, + lines: 85, + statements: 85 } }, collectCoverageFrom: [ diff --git a/packages/adyen-salesforce-pwa/lib/components/adyenCheckout.jsx b/packages/adyen-salesforce-pwa/lib/components/adyenCheckout.jsx index 39f22f2..10149f0 100644 --- a/packages/adyen-salesforce-pwa/lib/components/adyenCheckout.jsx +++ b/packages/adyen-salesforce-pwa/lib/components/adyenCheckout.jsx @@ -5,6 +5,57 @@ import {useAdyenCheckout} from '../context/adyen-checkout-context' import {Spinner, Flex} from '@chakra-ui/react' import PropTypes from 'prop-types' +export const getCheckoutConfig = ( + adyenEnvironment, + adyenPaymentMethods, + paymentMethodsConfiguration, + translations, + locale +) => { + const checkoutConfig = { + environment: adyenEnvironment?.ADYEN_ENVIRONMENT, + clientKey: adyenEnvironment?.ADYEN_CLIENT_KEY, + paymentMethodsResponse: adyenPaymentMethods, + paymentMethodsConfiguration: paymentMethodsConfiguration + } + if (translations) { + checkoutConfig.locale = locale.id + checkoutConfig.translations = translations + } + return checkoutConfig +} + +export const handleQueryParams = ( + urlParams, + checkout, + setAdyenPaymentInProgress, + paymentContainer +) => { + const redirectResult = urlParams.get('redirectResult') + const amazonCheckoutSessionId = urlParams.get('amazonCheckoutSessionId') + const adyenAction = urlParams.get('adyenAction') + + if (redirectResult) { + checkout.submitDetails({data: {details: {redirectResult}}}) + } else if (amazonCheckoutSessionId) { + setAdyenPaymentInProgress(true) + const amazonPayContainer = document.createElement('div') + const amazonPay = checkout + .create('amazonpay', { + amazonCheckoutSessionId, + showOrderButton: false + }) + .mount(amazonPayContainer) + amazonPay.submit() + } else if (adyenAction) { + const actionString = atob(adyenAction) + const action = JSON.parse(actionString) + checkout.createFromAction(action).mount(paymentContainer.current) + } else { + checkout.create('dropin').mount(paymentContainer.current) + } +} + const AdyenCheckoutComponent = (props) => { const { adyenEnvironment, @@ -20,23 +71,17 @@ const AdyenCheckoutComponent = (props) => { useEffect(() => { const urlParams = new URLSearchParams(location.search) - const redirectResult = urlParams.get('redirectResult') - const amazonCheckoutSessionId = urlParams.get('amazonCheckoutSessionId') - const adyenAction = urlParams.get('adyenAction') const createCheckout = async () => { const paymentMethodsConfiguration = await getPaymentMethodsConfiguration(props) const translations = getTranslations() - const checkoutConfig = { - environment: adyenEnvironment.ADYEN_ENVIRONMENT, - clientKey: adyenEnvironment.ADYEN_CLIENT_KEY, - paymentMethodsResponse: adyenPaymentMethods, - paymentMethodsConfiguration: paymentMethodsConfiguration - } - if (translations) { - checkoutConfig.locale = locale.id - checkoutConfig.translations = translations - } + const checkoutConfig = getCheckoutConfig( + adyenEnvironment, + adyenPaymentMethods, + paymentMethodsConfiguration, + translations, + locale + ) const checkout = await AdyenCheckout({ ...checkoutConfig, onSubmit(state, element) { @@ -58,25 +103,7 @@ const AdyenCheckoutComponent = (props) => { } }) - if (redirectResult) { - checkout.submitDetails({data: {details: {redirectResult}}}) - } else if (amazonCheckoutSessionId) { - setAdyenPaymentInProgress(true) - const amazonPayContainer = document.createElement('div') - const amazonPay = checkout - .create('amazonpay', { - amazonCheckoutSessionId, - showOrderButton: false - }) - .mount(amazonPayContainer) - amazonPay.submit() - } else if (adyenAction) { - const actionString = atob(adyenAction) - const action = JSON.parse(actionString) - checkout.createFromAction(action).mount(paymentContainer.current) - } else { - checkout.create('dropin').mount(paymentContainer.current) - } + handleQueryParams(urlParams, checkout, setAdyenPaymentInProgress, paymentContainer) } if (adyenEnvironment && paymentContainer.current && !adyenPaymentInProgress) { window.paypal = undefined diff --git a/packages/adyen-salesforce-pwa/lib/components/tests/adyenCheckout.test.js b/packages/adyen-salesforce-pwa/lib/components/tests/adyenCheckout.test.js new file mode 100644 index 0000000..b8075ff --- /dev/null +++ b/packages/adyen-salesforce-pwa/lib/components/tests/adyenCheckout.test.js @@ -0,0 +1,159 @@ +/** + * @jest-environment jest-environment-jsdom + * @jest-environment-options {"url": "http://localhost:3000/", "resources": "usable"} + */ +import {getCheckoutConfig, handleQueryParams} from '../adyenCheckout' + +describe('getCheckoutConfig', () => { + it('returns correct checkout config without translations', () => { + const adyenEnvironment = { + ADYEN_ENVIRONMENT: 'test', + ADYEN_CLIENT_KEY: 'test_client_key' + } + const adyenPaymentMethods = ['visa', 'mastercard'] + const paymentMethodsConfiguration = { + visa: {enabled: true}, + mastercard: {enabled: false} + } + const locale = {id: 'en_US'} + + const result = getCheckoutConfig( + adyenEnvironment, + adyenPaymentMethods, + paymentMethodsConfiguration, + null, + locale + ) + + expect(result).toEqual({ + environment: 'test', + clientKey: 'test_client_key', + paymentMethodsResponse: ['visa', 'mastercard'], + paymentMethodsConfiguration: { + visa: {enabled: true}, + mastercard: {enabled: false} + } + }) + }) + + it('returns correct checkout config with translations', () => { + const adyenEnvironment = { + ADYEN_ENVIRONMENT: 'test', + ADYEN_CLIENT_KEY: 'test_client_key' + } + const adyenPaymentMethods = ['visa', 'mastercard'] + const paymentMethodsConfiguration = { + visa: {enabled: true}, + mastercard: {enabled: false} + } + const translations = { + /* translations object */ + } + const locale = {id: 'en_US'} + + const result = getCheckoutConfig( + adyenEnvironment, + adyenPaymentMethods, + paymentMethodsConfiguration, + translations, + locale + ) + + expect(result).toEqual({ + environment: 'test', + clientKey: 'test_client_key', + paymentMethodsResponse: ['visa', 'mastercard'], + paymentMethodsConfiguration: { + visa: {enabled: true}, + mastercard: {enabled: false} + }, + locale: 'en_US', + translations: translations + }) + }) +}) + +describe('handleQueryParams', () => { + let urlParamsMock + let checkoutMock + let setAdyenPaymentInProgressMock + let paymentContainerMock + + beforeEach(() => { + urlParamsMock = new URLSearchParams() + checkoutMock = { + submitDetails: jest.fn(), + create: jest.fn().mockImplementation(() => { + return { + mount: jest.fn().mockImplementation(() => { + return { + submit: jest.fn() + } + }) + } + }), + mount: jest.fn().mockImplementation(() => { + return { + submit: jest.fn() + } + }), + createFromAction: jest.fn().mockImplementation(() => { + return { + mount: jest.fn() + } + }) + } + setAdyenPaymentInProgressMock = jest.fn() + paymentContainerMock = {current: document.createElement('div')} + }) + + it('handles redirectResult', () => { + urlParamsMock.set('redirectResult', 'someRedirectResult') + handleQueryParams( + urlParamsMock, + checkoutMock, + setAdyenPaymentInProgressMock, + paymentContainerMock + ) + expect(checkoutMock.submitDetails).toHaveBeenCalledWith({ + data: {details: {redirectResult: 'someRedirectResult'}} + }) + }) + + it('handles amazonCheckoutSessionId', () => { + urlParamsMock.set('amazonCheckoutSessionId', 'someAmazonCheckoutSessionId') + handleQueryParams( + urlParamsMock, + checkoutMock, + setAdyenPaymentInProgressMock, + paymentContainerMock + ) + expect(setAdyenPaymentInProgressMock).toHaveBeenCalledWith(true) + expect(checkoutMock.create).toHaveBeenCalledWith('amazonpay', { + amazonCheckoutSessionId: 'someAmazonCheckoutSessionId', + showOrderButton: false + }) + }) + + it('handles adyenAction', () => { + const adyenAction = btoa(JSON.stringify({some: 'actionData'})) + urlParamsMock.set('adyenAction', adyenAction) + handleQueryParams( + urlParamsMock, + checkoutMock, + setAdyenPaymentInProgressMock, + paymentContainerMock + ) + expect(checkoutMock.createFromAction).toHaveBeenCalled() + }) + + it('handles default case', () => { + handleQueryParams( + urlParamsMock, + checkoutMock, + setAdyenPaymentInProgressMock, + paymentContainerMock + ) + expect(checkoutMock.create).toHaveBeenCalledWith('dropin') + }) +}) diff --git a/packages/adyen-salesforce-pwa/lib/context/tests/adyen-checkout-context.test.js b/packages/adyen-salesforce-pwa/lib/context/tests/adyen-checkout-context.test.js index a202ccc..20e42f7 100644 --- a/packages/adyen-salesforce-pwa/lib/context/tests/adyen-checkout-context.test.js +++ b/packages/adyen-salesforce-pwa/lib/context/tests/adyen-checkout-context.test.js @@ -22,7 +22,6 @@ jest.mock('@salesforce/retail-react-app/app/hooks/use-current-basket', () => { })) } }) - jest.mock('react-router-dom', () => { return { useLocation: jest.fn().mockImplementation(() => { @@ -56,7 +55,12 @@ jest.mock('../../services/environment', () => ({ })) describe('', () => { - let useAccessToken, useCustomerId, useCustomerType, useMultiSite, locationSpy + let useAccessToken, + useCustomerId, + useCustomerType, + useMultiSite, + locationSpy, + setAdyenPaymentInProgress beforeEach(() => { useAccessToken = jest.fn().mockImplementation(() => { return { @@ -81,47 +85,54 @@ describe('', () => { } } }) + setAdyenPaymentInProgress = jest.fn().mockImplementation(() => { + return 'success' + }) + mockFetchEnvironment.mockImplementationOnce(() => ({ + ADYEN_ENVIRONMENT: 'test', + ADYEN_CLIENT_KEY: 'testKey' + })) + mockFetchPaymentMethods.mockImplementationOnce(() => { + return { + paymentMethods: [ + { + details: [ + { + key: 'encryptedCardNumber', + type: 'cardToken' + }, + { + key: 'encryptedSecurityCode', + type: 'cardToken' + }, + { + key: 'encryptedExpiryMonth', + type: 'cardToken' + }, + { + key: 'encryptedExpiryYear', + type: 'cardToken' + }, + { + key: 'holderName', + optional: true, + type: 'text' + } + ], + name: 'Cards', + type: 'scheme' + }, + { + name: 'Amazon Pay', + type: 'amazonpay' + } + ] + } + }) }) describe('when page is initialized', () => { it('render correct payment methods', async () => { - mockFetchEnvironment.mockImplementationOnce(() => ({ - ADYEN_ENVIRONMENT: 'test', - ADYEN_CLIENT_KEY: 'testKey' - })) - mockFetchPaymentMethods.mockImplementationOnce(() => { - return { - paymentMethods: [ - { - details: [ - { - key: 'encryptedCardNumber', - type: 'cardToken' - }, - { - key: 'encryptedSecurityCode', - type: 'cardToken' - }, - { - key: 'encryptedExpiryMonth', - type: 'cardToken' - }, - { - key: 'encryptedExpiryYear', - type: 'cardToken' - }, - { - key: 'holderName', - optional: true, - type: 'text' - } - ], - name: 'Cards', - type: 'scheme' - } - ] - } - }) const wrapper = ({children}) => ( Date: Thu, 15 Feb 2024 13:34:41 +0100 Subject: [PATCH 07/10] Feat/apple domain association (#112) * feat: apple domain association endpoint * test: add unit tests * chore: rename env var --------- Co-authored-by: Aleksandar Mihajlovski --- packages/adyen-retail-react-app/.env.example | 4 +++- .../adyen-salesforce-pwa/__mocks__/mockEnv.js | 1 + .../controllers/apple-domain-association.js | 15 +++++++++++++ .../tests/apple-domain-association.test.js | 22 +++++++++++++++++++ .../lib/api/routes/index.js | 9 ++++++++ .../lib/api/routes/tests/index.test.js | 4 ++-- .../utils/getAdyenConfigForCurrentSite.mjs | 6 +++-- .../getAdyenConfigForCurrentSite.test.js | 6 +++-- 8 files changed, 60 insertions(+), 7 deletions(-) create mode 100644 packages/adyen-salesforce-pwa/lib/api/controllers/apple-domain-association.js create mode 100644 packages/adyen-salesforce-pwa/lib/api/controllers/tests/apple-domain-association.test.js diff --git a/packages/adyen-retail-react-app/.env.example b/packages/adyen-retail-react-app/.env.example index ae474b0..864567a 100644 --- a/packages/adyen-retail-react-app/.env.example +++ b/packages/adyen-retail-react-app/.env.example @@ -6,6 +6,7 @@ RefArch_ADYEN_WEBHOOK_USER="" RefArch_ADYEN_WEBHOOK_PASSWORD="" RefArch_ADYEN_HMAC_KEY="" RefArch_SYSTEM_INTEGRATOR_NAME="" +RefArch_ADYEN_APPLE_DOMAIN_ASSOCIATION="" COMMERCE_API_CLIENT_ID="" COMMERCE_API_ORG_ID="" @@ -20,4 +21,5 @@ COMMERCE_API_CLIENT_ID_PRIVATE="" COMMERCE_API_CLIENT_SECRET="" SFCC_REALM_ID="" SFCC_INSTANCE_ID="" -SFCC_OAUTH_SCOPES="" \ No newline at end of file +SFCC_OAUTH_SCOPES="" + diff --git a/packages/adyen-salesforce-pwa/__mocks__/mockEnv.js b/packages/adyen-salesforce-pwa/__mocks__/mockEnv.js index 33c3bfc..e6b95d6 100644 --- a/packages/adyen-salesforce-pwa/__mocks__/mockEnv.js +++ b/packages/adyen-salesforce-pwa/__mocks__/mockEnv.js @@ -20,3 +20,4 @@ process.env.COMMERCE_API_CLIENT_SECRET = 'mock_COMMERCE_API_CLIENT_SECRET' process.env.SFCC_REALM_ID = 'mock_SFCC_REALM_ID' process.env.SFCC_INSTANCE_ID = 'mock_SFCC_INSTANCE_ID' process.env.SFCC_OAUTH_SCOPES = 'sfcc.orders sfcc.orders.rw sfcc.customerlists.rw' +process.env.ADYEN_APPLE_DOMAIN_ASSOCIATION = 'test' diff --git a/packages/adyen-salesforce-pwa/lib/api/controllers/apple-domain-association.js b/packages/adyen-salesforce-pwa/lib/api/controllers/apple-domain-association.js new file mode 100644 index 0000000..7649a82 --- /dev/null +++ b/packages/adyen-salesforce-pwa/lib/api/controllers/apple-domain-association.js @@ -0,0 +1,15 @@ +import Logger from './logger' +import {getAdyenConfigForCurrentSite} from '../../utils/getAdyenConfigForCurrentSite.mjs' + +function appleDomainAssociation(req, res, next) { + try { + const adyenConfig = getAdyenConfigForCurrentSite() + res.setHeader('content-type', 'text/plain') + Logger.info('AppleDomainAssociation') + res.send(`${adyenConfig.appleDomainAssociation}\n`) + } catch (err) { + return next(err) + } +} + +export {appleDomainAssociation} diff --git a/packages/adyen-salesforce-pwa/lib/api/controllers/tests/apple-domain-association.test.js b/packages/adyen-salesforce-pwa/lib/api/controllers/tests/apple-domain-association.test.js new file mode 100644 index 0000000..19d7fab --- /dev/null +++ b/packages/adyen-salesforce-pwa/lib/api/controllers/tests/apple-domain-association.test.js @@ -0,0 +1,22 @@ +import {appleDomainAssociation} from '../apple-domain-association' + +describe('appleDomainAssociation Controller', () => { + let req, res, next, consoleInfoSpy + + beforeEach(() => { + req = {} + res = { + send: jest.fn(), + setHeader: jest.fn() + } + next = jest.fn() + consoleInfoSpy = jest.spyOn(console, 'info').mockImplementation(() => {}) + }) + it('update order when success notification is received', async () => { + await appleDomainAssociation(req, res, next) + expect(res.send).toHaveBeenCalledWith('test\n') + expect(res.setHeader).toHaveBeenCalledWith('content-type', 'text/plain') + expect(consoleInfoSpy).toHaveBeenCalledTimes(1) + expect(consoleInfoSpy.mock.calls[0][0]).toContain('AppleDomainAssociation') + }) +}) diff --git a/packages/adyen-salesforce-pwa/lib/api/routes/index.js b/packages/adyen-salesforce-pwa/lib/api/routes/index.js index 0bc3a32..a637f2a 100644 --- a/packages/adyen-salesforce-pwa/lib/api/routes/index.js +++ b/packages/adyen-salesforce-pwa/lib/api/routes/index.js @@ -7,6 +7,7 @@ import {authenticate, parseNotification, validateHmac} from '../controllers/webh import {authorizationWebhookHandler} from '../controllers/authorization-webhook-handler' import {createErrorResponse} from '../../utils/createErrorResponse.mjs' import Logger from '../controllers/logger' +import {appleDomainAssociation} from '../controllers/apple-domain-association' function SuccessHandler(req, res) { Logger.info('Success') @@ -37,6 +38,10 @@ function registerAdyenEndpoints(app, runtime, overrides) { SuccessHandler ] + const appleDomainAssociationHandler = overrides?.appleDomainAssociation || [ + appleDomainAssociation + ] + app.get( '*/checkout/redirect', query('redirectResult').optional().escape(), @@ -53,6 +58,10 @@ function registerAdyenEndpoints(app, runtime, overrides) { app.post('/api/adyen/payments/details', ...paymentsDetailsHandler) app.post('/api/adyen/payments', ...paymentsHandler) app.post('/api/adyen/webhook', ...webhookHandler) + app.get( + '/.well-known/apple-developer-merchantid-domain-association', + ...appleDomainAssociationHandler + ) app.use(overrides?.ErrorHandler || ErrorHandler) } diff --git a/packages/adyen-salesforce-pwa/lib/api/routes/tests/index.test.js b/packages/adyen-salesforce-pwa/lib/api/routes/tests/index.test.js index 7301826..7e59d21 100644 --- a/packages/adyen-salesforce-pwa/lib/api/routes/tests/index.test.js +++ b/packages/adyen-salesforce-pwa/lib/api/routes/tests/index.test.js @@ -1,4 +1,4 @@ -import {registerAdyenEndpoints, SuccessHandler, ErrorHandler} from '../index' +import {ErrorHandler, registerAdyenEndpoints, SuccessHandler} from '../index' import Logger from '../../controllers/logger' jest.mock('../../controllers/logger', () => ({ @@ -30,7 +30,7 @@ describe('Adyen Endpoints', () => { const overrides = {} registerAdyenEndpoints(app, runtime, overrides) - expect(app.get).toHaveBeenCalledTimes(4) + expect(app.get).toHaveBeenCalledTimes(5) expect(app.post).toHaveBeenCalledTimes(3) expect(app.use).toHaveBeenCalledTimes(1) }) diff --git a/packages/adyen-salesforce-pwa/lib/utils/getAdyenConfigForCurrentSite.mjs b/packages/adyen-salesforce-pwa/lib/utils/getAdyenConfigForCurrentSite.mjs index 0aa600a..341bf04 100644 --- a/packages/adyen-salesforce-pwa/lib/utils/getAdyenConfigForCurrentSite.mjs +++ b/packages/adyen-salesforce-pwa/lib/utils/getAdyenConfigForCurrentSite.mjs @@ -8,7 +8,8 @@ export const getAdyenConfigForCurrentSite = (currentSiteId) => { webhookUser: setProperty(currentSiteId, ADYEN_ENV.ADYEN_WEBHOOK_USER), webhookPassword: setProperty(currentSiteId, ADYEN_ENV.ADYEN_WEBHOOK_PASSWORD), webhookHmacKey: setProperty(currentSiteId, ADYEN_ENV.ADYEN_HMAC_KEY), - liveEndpointUrlPrefix: setProperty(currentSiteId, ADYEN_ENV.ADYEN_LIVE_URL_PREFIX) + liveEndpointUrlPrefix: setProperty(currentSiteId, ADYEN_ENV.ADYEN_LIVE_URL_PREFIX), + appleDomainAssociation: setProperty(currentSiteId, ADYEN_ENV.ADYEN_APPLE_DOMAIN_ASSOCIATION) } } @@ -29,5 +30,6 @@ const ADYEN_ENV = { ADYEN_WEBHOOK_USER: 'ADYEN_WEBHOOK_USER', ADYEN_WEBHOOK_PASSWORD: 'ADYEN_WEBHOOK_PASSWORD', ADYEN_HMAC_KEY: 'ADYEN_HMAC_KEY', - ADYEN_LIVE_URL_PREFIX: 'ADYEN_LIVE_URL_PREFIX' + ADYEN_LIVE_URL_PREFIX: 'ADYEN_LIVE_URL_PREFIX', + ADYEN_APPLE_DOMAIN_ASSOCIATION: 'ADYEN_APPLE_DOMAIN_ASSOCIATION' } diff --git a/packages/adyen-salesforce-pwa/lib/utils/tests/getAdyenConfigForCurrentSite.test.js b/packages/adyen-salesforce-pwa/lib/utils/tests/getAdyenConfigForCurrentSite.test.js index d44f5bb..f87bbf9 100644 --- a/packages/adyen-salesforce-pwa/lib/utils/tests/getAdyenConfigForCurrentSite.test.js +++ b/packages/adyen-salesforce-pwa/lib/utils/tests/getAdyenConfigForCurrentSite.test.js @@ -17,7 +17,8 @@ describe('getAdyenConfigForCurrentSite', () => { webhookHmacKey: '', webhookPassword: '', webhookUser: '', - liveEndpointUrlPrefix: '' + liveEndpointUrlPrefix: '', + appleDomainAssociation: '' } const result = getAdyenConfigForCurrentSite(siteId) @@ -35,7 +36,8 @@ describe('getAdyenConfigForCurrentSite', () => { webhookHmacKey: '', webhookPassword: '', webhookUser: '', - liveEndpointUrlPrefix: '' + liveEndpointUrlPrefix: '', + appleDomainAssociation: '' }) }) }) From 15c594211a75bd9d682a7ffcf93f529a7ba1789c Mon Sep 17 00:00:00 2001 From: Shani <31096696+shanikantsingh@users.noreply.github.com> Date: Tue, 20 Feb 2024 13:24:14 +0100 Subject: [PATCH 08/10] fix: submit billing address form when not same as shipping address (#119) --- .../lib/pages/checkout/partials/payment.jsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/adyen-salesforce-pwa/lib/pages/checkout/partials/payment.jsx b/packages/adyen-salesforce-pwa/lib/pages/checkout/partials/payment.jsx index c296474..9b3a736 100644 --- a/packages/adyen-salesforce-pwa/lib/pages/checkout/partials/payment.jsx +++ b/packages/adyen-salesforce-pwa/lib/pages/checkout/partials/payment.jsx @@ -4,7 +4,7 @@ * SPDX-License-Identifier: BSD-3-Clause * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -import React, {useState} from 'react' +import React, {useEffect, useRef, useState} from 'react' import PropTypes from 'prop-types' import {FormattedMessage, useIntl} from 'react-intl' import {Box, Checkbox, Divider, Heading, Stack, Text} from '@chakra-ui/react' @@ -53,6 +53,8 @@ const Payment = ({useShopperBasketsMutation}) => { const {adyenPaymentMethods} = useAdyenCheckout() const [isSubmittingPayment] = useState(false) + const billingSameAsShippingRef = useRef() + const billingAddressForm = useForm({ mode: 'onChange', shouldUnregister: false, @@ -68,7 +70,7 @@ const Payment = ({useShopperBasketsMutation}) => { if (!isFormValid) { return } - const billingAddress = billingSameAsShipping + const billingAddress = billingSameAsShippingRef.current ? selectedShippingAddress : billingAddressForm.getValues() // Using destructuring to remove properties from the object... @@ -80,6 +82,9 @@ const Payment = ({useShopperBasketsMutation}) => { }) } + useEffect(() => { + billingSameAsShippingRef.current = billingSameAsShipping + }, [billingSameAsShipping]) const onPaymentRemoval = async () => { try { await removePaymentInstrumentFromBasket({ From 62425b95d5279e06c69457c4ccf43e83da451b46 Mon Sep 17 00:00:00 2001 From: Aleksandar Mihajlovski Date: Tue, 20 Feb 2024 15:43:07 +0100 Subject: [PATCH 09/10] feat: bump version to 1.0.0 (#120) --- packages/adyen-retail-react-app/package-lock.json | 2 +- packages/adyen-retail-react-app/package.json | 2 +- packages/adyen-salesforce-pwa/lib/utils/constants.mjs | 2 +- packages/adyen-salesforce-pwa/package-lock.json | 2 +- packages/adyen-salesforce-pwa/package.json | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/adyen-retail-react-app/package-lock.json b/packages/adyen-retail-react-app/package-lock.json index 0c66701..e420815 100644 --- a/packages/adyen-retail-react-app/package-lock.json +++ b/packages/adyen-retail-react-app/package-lock.json @@ -1,6 +1,6 @@ { "name": "adyen-retail-react-app", - "version": "1.0.0-beta.6", + "version": "1.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/adyen-retail-react-app/package.json b/packages/adyen-retail-react-app/package.json index 08e577a..3abef77 100644 --- a/packages/adyen-retail-react-app/package.json +++ b/packages/adyen-retail-react-app/package.json @@ -1,6 +1,6 @@ { "name": "adyen-retail-react-app", - "version": "1.0.0-beta.6", + "version": "1.0.0", "license": "See license in LICENSE", "engines": { "node": "^16.0.0 || ^18.0.0", diff --git a/packages/adyen-salesforce-pwa/lib/utils/constants.mjs b/packages/adyen-salesforce-pwa/lib/utils/constants.mjs index 258cb81..e5c82f8 100644 --- a/packages/adyen-salesforce-pwa/lib/utils/constants.mjs +++ b/packages/adyen-salesforce-pwa/lib/utils/constants.mjs @@ -69,4 +69,4 @@ export const ADYEN_ENVIRONMENT = { TEST: 'TEST' } -export const APPLICATION_VERSION = '1.0.0-beta.6' \ No newline at end of file +export const APPLICATION_VERSION = '1.0.0' \ No newline at end of file diff --git a/packages/adyen-salesforce-pwa/package-lock.json b/packages/adyen-salesforce-pwa/package-lock.json index c76388b..a0821e0 100644 --- a/packages/adyen-salesforce-pwa/package-lock.json +++ b/packages/adyen-salesforce-pwa/package-lock.json @@ -1,6 +1,6 @@ { "name": "@adyen/adyen-salesforce-pwa", - "version": "1.0.0-beta.6", + "version": "1.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/adyen-salesforce-pwa/package.json b/packages/adyen-salesforce-pwa/package.json index 044ee9d..e5205af 100644 --- a/packages/adyen-salesforce-pwa/package.json +++ b/packages/adyen-salesforce-pwa/package.json @@ -9,7 +9,7 @@ "payments", "components" ], - "version": "1.0.0-beta.6", + "version": "1.0.0", "main": "dist/app/index.js", "bin": { "include-env": "dist/scripts/include-env.js", From 49be7d02c54ff1610dcf3fae58aefc56a38b45a6 Mon Sep 17 00:00:00 2001 From: Aleksandar Mihajlovski Date: Thu, 22 Feb 2024 16:03:59 +0100 Subject: [PATCH 10/10] fix: add google pay script to the headers (#122) --- packages/adyen-retail-react-app/overrides/app/ssr.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/adyen-retail-react-app/overrides/app/ssr.js b/packages/adyen-retail-react-app/overrides/app/ssr.js index e06ff92..7016a50 100644 --- a/packages/adyen-retail-react-app/overrides/app/ssr.js +++ b/packages/adyen-retail-react-app/overrides/app/ssr.js @@ -77,7 +77,7 @@ const {handler} = runtime.createHandler(options, (app) => { '*.amazon.com', 'https://www.sandbox.paypal.com/xoplatform/logger/api/logger?disableSetCookie=true' ], - 'frame-src': ["'self'", '*.adyen.com', '*.paypal.com'], + 'frame-src': ["'self'", '*.adyen.com', '*.paypal.com', '*.google.com'], /* -----------------Adyen End ------------------------ */ // Do not upgrade insecure requests for local development 'upgrade-insecure-requests': isRemote() ? [] : null