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 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-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 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/README.md b/packages/adyen-salesforce-pwa/README.md index 77b6e5b..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 @@ -23,58 +12,15 @@ 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 ## 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). 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/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/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/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() } 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/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/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/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}) => ( { 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({ diff --git a/packages/adyen-salesforce-pwa/lib/utils/constants.mjs b/packages/adyen-salesforce-pwa/lib/utils/constants.mjs index 35ddf5b..e5c82f8 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', @@ -61,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/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: '' }) }) }) diff --git a/packages/adyen-salesforce-pwa/package-lock.json b/packages/adyen-salesforce-pwa/package-lock.json index 7b33851..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": { @@ -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..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", @@ -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",