diff --git a/.changeset/wise-tigers-sniff.md b/.changeset/wise-tigers-sniff.md new file mode 100644 index 0000000000..9046887ee8 --- /dev/null +++ b/.changeset/wise-tigers-sniff.md @@ -0,0 +1,5 @@ +--- +"@adyen/adyen-web": patch +--- + +Add analytics to ascertain when certain "wallets" are explicitly used as express payment methods diff --git a/packages/lib/src/components/AmazonPay/AmazonPay.tsx b/packages/lib/src/components/AmazonPay/AmazonPay.tsx index c40bfe9486..9743491656 100644 --- a/packages/lib/src/components/AmazonPay/AmazonPay.tsx +++ b/packages/lib/src/components/AmazonPay/AmazonPay.tsx @@ -7,11 +7,25 @@ import { AmazonPayElementData, AmazonPayElementProps, CheckoutDetailsRequest } f import defaultProps from './defaultProps'; import { getCheckoutDetails } from './services'; import './AmazonPay.scss'; +import { SendAnalyticsObject } from '../../core/Analytics/types'; +import { ANALYTICS_RENDERED_STR } from '../../core/Analytics/constants'; export class AmazonPayElement extends UIElement { public static type = 'amazonpay'; protected static defaultProps = defaultProps; + protected submitAnalytics(analyticsObj: SendAnalyticsObject) { + let extraAnalyticsObject = {}; + if (analyticsObj.type === ANALYTICS_RENDERED_STR) { + const isExpress = this.props.isExpress; + const expressPage = this.props.expressPage ?? null; + extraAnalyticsObject = { + isExpress, + ...(isExpress && expressPage && { expressPage }) // We only care about the expressPage value if isExpress is true + }; + } + super.submitAnalytics({ ...analyticsObj, ...extraAnalyticsObject }); + } formatProps(props) { return { ...props, diff --git a/packages/lib/src/components/AmazonPay/defaultProps.ts b/packages/lib/src/components/AmazonPay/defaultProps.ts index a5155aff4b..30953b97b8 100644 --- a/packages/lib/src/components/AmazonPay/defaultProps.ts +++ b/packages/lib/src/components/AmazonPay/defaultProps.ts @@ -13,7 +13,8 @@ const defautProps: Partial = { showSignOutButton: false, showPayButton: true, onClick: resolve => resolve(), - onSignOut: resolve => resolve() + onSignOut: resolve => resolve(), + isExpress: false }; export default defautProps; diff --git a/packages/lib/src/components/AmazonPay/types.ts b/packages/lib/src/components/AmazonPay/types.ts index d6fc6a8811..1b6a669d4a 100644 --- a/packages/lib/src/components/AmazonPay/types.ts +++ b/packages/lib/src/components/AmazonPay/types.ts @@ -68,6 +68,17 @@ export interface AmazonPayElementProps extends UIElementProps { onClick: (resolve, reject) => Promise; onError: (error, component) => void; onSignOut: (resolve, reject) => Promise; + + /** + * Used for analytics + */ + expressPage?: 'cart' | 'minicart' | 'pdp' | 'checkout'; + + /** + * Used for analytics + * @defaultValue false + */ + isExpress?: boolean; } export interface AmazonPayComponentProps extends AmazonPayElementProps { diff --git a/packages/lib/src/components/ApplePay/ApplePay.tsx b/packages/lib/src/components/ApplePay/ApplePay.tsx index b1cbfe4e00..f4af44ed94 100644 --- a/packages/lib/src/components/ApplePay/ApplePay.tsx +++ b/packages/lib/src/components/ApplePay/ApplePay.tsx @@ -10,8 +10,9 @@ import { preparePaymentRequest } from './payment-request'; import { resolveSupportedVersion, mapBrands } from './utils'; import { ApplePayElementProps, ApplePayElementData, ApplePaySessionRequest, OnAuthorizedCallback } from './types'; import AdyenCheckoutError from '../../core/Errors/AdyenCheckoutError'; -import { ANALYTICS_INSTANT_PAYMENT_BUTTON, ANALYTICS_SELECTED_STR } from '../../core/Analytics/constants'; +import { ANALYTICS_INSTANT_PAYMENT_BUTTON, ANALYTICS_RENDERED_STR, ANALYTICS_SELECTED_STR } from '../../core/Analytics/constants'; import { DecodeObject } from '../types'; +import { SendAnalyticsObject } from '../../core/Analytics/types'; const latestSupportedVersion = 14; @@ -26,6 +27,19 @@ class ApplePayElement extends UIElement { this.validateMerchant = this.validateMerchant.bind(this); } + protected submitAnalytics(analyticsObj: SendAnalyticsObject) { + let extraAnalyticsObject = {}; + if (analyticsObj.type === ANALYTICS_RENDERED_STR) { + const isExpress = this.props.isExpress; + const expressPage = this.props.expressPage ?? null; + extraAnalyticsObject = { + isExpress, + ...(isExpress && expressPage && { expressPage }) // We only care about the expressPage value if isExpress is true + }; + } + super.submitAnalytics({ ...analyticsObj, ...extraAnalyticsObject }); + } + /** * Formats the component props */ diff --git a/packages/lib/src/components/ApplePay/defaultProps.ts b/packages/lib/src/components/ApplePay/defaultProps.ts index 23f59463cc..4c83d141d4 100644 --- a/packages/lib/src/components/ApplePay/defaultProps.ts +++ b/packages/lib/src/components/ApplePay/defaultProps.ts @@ -17,6 +17,8 @@ const defaultProps = { initiative: 'web', + isExpress: false, + /** * https://developer.apple.com/documentation/apple_pay_on_the_web/applepaypaymentrequest/1916120-lineitems * A set of line items that explain recurring payments and additional charges and discounts. diff --git a/packages/lib/src/components/ApplePay/types.ts b/packages/lib/src/components/ApplePay/types.ts index 27d5d07302..197557055f 100644 --- a/packages/lib/src/components/ApplePay/types.ts +++ b/packages/lib/src/components/ApplePay/types.ts @@ -32,6 +32,17 @@ export type OnAuthorizedCallback = ( ) => void; export interface ApplePayElementProps extends UIElementProps { + /** + * Used for analytics + * @defaultValue false + */ + isExpress?: boolean; + + /** + * Used for analytics + */ + expressPage?: 'cart' | 'minicart' | 'pdp' | 'checkout'; + /** * The Apple Pay version number your website supports. * @default highest supported version by the shopper device diff --git a/packages/lib/src/components/GooglePay/GooglePay.tsx b/packages/lib/src/components/GooglePay/GooglePay.tsx index 0f99410067..fa8bd23363 100644 --- a/packages/lib/src/components/GooglePay/GooglePay.tsx +++ b/packages/lib/src/components/GooglePay/GooglePay.tsx @@ -7,13 +7,27 @@ import { GooglePayProps } from './types'; import { mapBrands, getGooglePayLocale } from './utils'; import collectBrowserInfo from '../../utils/browserInfo'; import AdyenCheckoutError from '../../core/Errors/AdyenCheckoutError'; -import { ANALYTICS_INSTANT_PAYMENT_BUTTON, ANALYTICS_SELECTED_STR } from '../../core/Analytics/constants'; +import { ANALYTICS_INSTANT_PAYMENT_BUTTON, ANALYTICS_RENDERED_STR, ANALYTICS_SELECTED_STR } from '../../core/Analytics/constants'; +import { SendAnalyticsObject } from '../../core/Analytics/types'; class GooglePay extends UIElement { public static type = 'paywithgoogle'; public static defaultProps = defaultProps; protected googlePay = new GooglePayService(this.props); + protected submitAnalytics(analyticsObj: SendAnalyticsObject) { + let extraAnalyticsObject = {}; + if (analyticsObj.type === ANALYTICS_RENDERED_STR) { + const isExpress = this.props.isExpress; + const expressPage = this.props.expressPage ?? null; + extraAnalyticsObject = { + isExpress, + ...(isExpress && expressPage && { expressPage }) // We only care about the expressPage value if isExpress is true + }; + } + super.submitAnalytics({ ...analyticsObj, ...extraAnalyticsObject }); + } + /** * Formats the component data input * For legacy support - maps configuration.merchantIdentifier to configuration.merchantId diff --git a/packages/lib/src/components/GooglePay/defaultProps.ts b/packages/lib/src/components/GooglePay/defaultProps.ts index da96f8f813..9495f7b246 100644 --- a/packages/lib/src/components/GooglePay/defaultProps.ts +++ b/packages/lib/src/components/GooglePay/defaultProps.ts @@ -1,6 +1,8 @@ export default { environment: 'TEST', + isExpress: false, + // isReadyToPayRequest existingPaymentMethodRequired: false, diff --git a/packages/lib/src/components/GooglePay/types.ts b/packages/lib/src/components/GooglePay/types.ts index ec0ad1b216..e8084e5a82 100644 --- a/packages/lib/src/components/GooglePay/types.ts +++ b/packages/lib/src/components/GooglePay/types.ts @@ -99,6 +99,17 @@ export interface GooglePayProps extends UIElementProps { */ emailRequired?: boolean; + /** + * Used for analytics + */ + expressPage?: 'cart' | 'minicart' | 'pdp' | 'checkout'; + + /** + * Used for analytics + * @defaultValue false + */ + isExpress?: boolean; + /** * Set to true to request a full shipping address. * @defaultValue false diff --git a/packages/lib/src/components/PayPal/Paypal.tsx b/packages/lib/src/components/PayPal/Paypal.tsx index 2cc2516669..67d6e4bc51 100644 --- a/packages/lib/src/components/PayPal/Paypal.tsx +++ b/packages/lib/src/components/PayPal/Paypal.tsx @@ -9,6 +9,8 @@ import CoreProvider from '../../core/Context/CoreProvider'; import AdyenCheckoutError from '../../core/Errors/AdyenCheckoutError'; import { ERRORS } from './constants'; import { createShopperDetails } from './utils/create-shopper-details'; +import { SendAnalyticsObject } from '../../core/Analytics/types'; +import { ANALYTICS_RENDERED_STR } from '../../core/Analytics/constants'; class PaypalElement extends UIElement { public static type = 'paypal'; @@ -28,6 +30,19 @@ class PaypalElement extends UIElement { this.handleOnShippingOptionsChange = this.handleOnShippingOptionsChange.bind(this); } + protected submitAnalytics(analyticsObj: SendAnalyticsObject) { + let extraAnalyticsObject = {}; + if (analyticsObj.type === ANALYTICS_RENDERED_STR) { + const isExpress = this.props.isExpress; + const expressPage = this.props.expressPage ?? null; + extraAnalyticsObject = { + isExpress, + ...(isExpress && expressPage && { expressPage }) // We only care about the expressPage value if isExpress is true + }; + } + super.submitAnalytics({ ...analyticsObj, ...extraAnalyticsObject }); + } + formatProps(props: PayPalElementProps): PayPalElementProps { const { merchantId, intent: intentFromConfig } = props.configuration; const isZeroAuth = props.amount?.value === 0; diff --git a/packages/lib/src/components/PayPal/types.ts b/packages/lib/src/components/PayPal/types.ts index f02ce8aa17..3a375cb73c 100644 --- a/packages/lib/src/components/PayPal/types.ts +++ b/packages/lib/src/components/PayPal/types.ts @@ -165,10 +165,15 @@ interface PayPalCommonProps { onShippingOptionsChange?: (data: any, actions: { reject: (reason?: string) => Promise }) => Promise; /** - * Identifies if the payment is Express. + * Identifies if the payment is Express. Also used for analytics. * @defaultValue false */ isExpress?: boolean; + + /** + * Used for analytics + */ + expressPage?: 'cart' | 'minicart' | 'pdp' | 'checkout'; } export interface PayPalConfig { diff --git a/packages/lib/src/core/Analytics/Analytics.test.ts b/packages/lib/src/core/Analytics/Analytics.test.ts index c1fcf98768..628b992560 100644 --- a/packages/lib/src/core/Analytics/Analytics.test.ts +++ b/packages/lib/src/core/Analytics/Analytics.test.ts @@ -17,7 +17,7 @@ const amount: PaymentAmount = { value: 50000, currency: 'USD' }; const mockCheckoutAttemptId = '123456'; -const event = { +const setUpEvent = { containerWidth: 100, component: 'card', flavor: 'components' @@ -61,7 +61,7 @@ describe('Analytics initialisation and event queue', () => { test('Should not fire any calls if analytics is disabled', () => { const analytics = Analytics({ analytics: { enabled: false }, loadingContext: '', locale: '', clientKey: '', amount }); - analytics.setUp(event); + analytics.setUp(setUpEvent); expect(collectIdPromiseMock).not.toHaveBeenCalled(); expect(logEventPromiseMock).not.toHaveBeenCalled(); }); @@ -69,18 +69,49 @@ describe('Analytics initialisation and event queue', () => { test('Will not call the collectId endpoint if telemetry is disabled, but will call the logEvent (analytics pixel)', () => { const analytics = Analytics({ analytics: { telemetry: false }, loadingContext: '', locale: '', clientKey: '', amount }); expect(collectIdPromiseMock).not.toHaveBeenCalled(); - analytics.setUp(event); + analytics.setUp(setUpEvent); expect(collectIdPromiseMock).not.toHaveBeenCalled(); - expect(logEventPromiseMock).toHaveBeenCalledWith({ ...event }); + expect(logEventPromiseMock).toHaveBeenCalledWith({ ...setUpEvent }); }); - test('Calls the collectId endpoint by default, adding expected fields', async () => { - analytics.setUp(event); + test('Calls the collectId endpoint by default, adding expected fields, including sanitising the passed analyticsData object', async () => { + const applicationInfo = { + merchantApplication: { + name: 'merchant_application_name', + version: 'version' + }, + externalPlatform: { + name: 'external_platform_name', + version: 'external_platform_version', + integrator: 'getSystemIntegratorName' + } + }; + + analytics = Analytics({ + analytics: { + analyticsData: { + applicationInfo, + // @ts-ignore - this is one of the things we're testing + foo: { + bar: 'val' + } + } + }, + loadingContext: '', + locale: '', + clientKey: '', + amount + }); + + analytics.setUp(setUpEvent); expect(collectIdPromiseMock).toHaveBeenCalled(); await Promise.resolve(); // wait for the next tick - expect(collectIdPromiseMock).toHaveBeenCalledWith({ ...event }); + + const enhancedSetupEvent = { ...setUpEvent, applicationInfo }; + + expect(collectIdPromiseMock).toHaveBeenCalledWith({ ...enhancedSetupEvent }); expect(analytics.getCheckoutAttemptId()).toEqual(mockCheckoutAttemptId); }); @@ -91,7 +122,7 @@ describe('Analytics initialisation and event queue', () => { }; const analytics = Analytics({ analytics: { payload }, loadingContext: '', locale: '', clientKey: '', amount }); - analytics.setUp(event); + analytics.setUp(setUpEvent); expect(collectIdPromiseMock).toHaveLength(0); }); diff --git a/packages/lib/src/core/Analytics/Analytics.ts b/packages/lib/src/core/Analytics/Analytics.ts index b343f5cf1f..d7a00cb54f 100644 --- a/packages/lib/src/core/Analytics/Analytics.ts +++ b/packages/lib/src/core/Analytics/Analytics.ts @@ -5,7 +5,7 @@ import { ANALYTICS_EVENT, AnalyticsInitialEvent, AnalyticsObject, AnalyticsProps import { ANALYTICS_EVENT_ERROR, ANALYTICS_EVENT_INFO, ANALYTICS_EVENT_LOG, ANALYTICS_INFO_TIMER_INTERVAL, ANALYTICS_PATH } from './constants'; import { debounce } from '../../utils/debounce'; import { AnalyticsModule } from '../../components/types'; -import { createAnalyticsObject } from './utils'; +import { createAnalyticsObject, processAnalyticsData } from './utils'; import { analyticsPreProcessor } from './analyticsPreProcessor'; let capturedCheckoutAttemptId = null; @@ -16,7 +16,8 @@ const Analytics = ({ loadingContext, locale, clientKey, analytics, amount, analy const defaultProps = { enabled: true, telemetry: true, - checkoutAttemptId: null + checkoutAttemptId: null, + analyticsData: {} }; const props = { ...defaultProps, ...analytics }; @@ -71,13 +72,17 @@ const Analytics = ({ loadingContext, locale, clientKey, analytics, amount, analy setUp: async (initialEvent: AnalyticsInitialEvent) => { const { enabled, payload, telemetry } = props; // TODO what is payload, is it ever used? - // console.log('### Analytics::setUp:: initialEvent', initialEvent); + const analyticsData = processAnalyticsData(props.analyticsData); if (enabled === true) { if (telemetry === true && !capturedCheckoutAttemptId) { try { // fetch a new checkoutAttemptId if none is already available - const checkoutAttemptId = await collectId({ ...initialEvent, ...(payload && { ...payload }) }); + const checkoutAttemptId = await collectId({ + ...initialEvent, + ...(payload && { ...payload }), + ...(Object.keys(analyticsData).length && { ...analyticsData }) + }); capturedCheckoutAttemptId = checkoutAttemptId; } catch (e) { console.warn(`Fetching checkoutAttemptId failed.${e ? ` Error=${e}` : ''}`); diff --git a/packages/lib/src/core/Analytics/analyticsPreProcessor.ts b/packages/lib/src/core/Analytics/analyticsPreProcessor.ts index 1024b01773..f0d43e3ac4 100644 --- a/packages/lib/src/core/Analytics/analyticsPreProcessor.ts +++ b/packages/lib/src/core/Analytics/analyticsPreProcessor.ts @@ -29,7 +29,16 @@ export const analyticsPreProcessor = (analyticsModule: AnalyticsModule) => { */ // Called from BaseElement (when component mounted) or, from DropinComponent (after mounting, when it has finished resolving all the PM promises) // &/or, from DropinComponent when a PM is selected - case ANALYTICS_RENDERED_STR: + case ANALYTICS_RENDERED_STR: { + const { isStoredPaymentMethod, brand, isExpress, expressPage } = analyticsObj; + const data = { component, type, isStoredPaymentMethod, brand, isExpress, expressPage }; + + analyticsModule.createAnalyticsEvent({ + event: ANALYTICS_EVENT_INFO, + data + }); + break; + } case ANALYTICS_CONFIGURED_STR: { const { isStoredPaymentMethod, brand } = analyticsObj; const data = { component, type, isStoredPaymentMethod, brand }; diff --git a/packages/lib/src/core/Analytics/types.ts b/packages/lib/src/core/Analytics/types.ts index 9d4907b112..d6033c55a0 100644 --- a/packages/lib/src/core/Analytics/types.ts +++ b/packages/lib/src/core/Analytics/types.ts @@ -7,6 +7,31 @@ export interface Experiment { experimentName?: string; } +export interface AnalyticsData { + /** + * Relates to PMs used within Plugins + * https://docs.adyen.com/development-resources/application-information/?tab=integrator_built_2#application-information-fields + * @internal + */ + applicationInfo?: { + externalPlatform: { + name: string; + version: string; + integrator: string; + }; + merchantApplication: { + name: string; + version: string; + }; + merchantDevice?: { + os: string; + osVersion: string; + }; + }; +} + +export const ALLOWED_ANALYTICS_DATA = ['applicationInfo']; + export interface AnalyticsOptions { /** * Enable/Disable all analytics @@ -32,6 +57,11 @@ export interface AnalyticsOptions { * List of experiments to be sent in the collectId call // TODO - still used? */ experiments?: Experiment[]; + + /** + * A wrapper to pass data needed when analytics is setup + */ + analyticsData?: AnalyticsData; } export type AnalyticsProps = Pick & { analyticsContext?: string }; @@ -52,6 +82,8 @@ export interface AnalyticsObject { validationErrorCode?: string; validationErrorMessage?: string; issuer?: string; + isExpress?: boolean; + expressPage?: string; } export type ANALYTICS_EVENT = 'log' | 'error' | 'info'; diff --git a/packages/lib/src/core/Analytics/utils.ts b/packages/lib/src/core/Analytics/utils.ts index 2868964b8d..e102ae209d 100644 --- a/packages/lib/src/core/Analytics/utils.ts +++ b/packages/lib/src/core/Analytics/utils.ts @@ -1,4 +1,4 @@ -import { AnalyticsObject, CreateAnalyticsObject } from './types'; +import { ALLOWED_ANALYTICS_DATA, AnalyticsData, AnalyticsObject, CreateAnalyticsObject } from './types'; import { ANALYTICS_ACTION_STR, ANALYTICS_VALIDATION_ERROR_STR, errorCodeMapping } from './constants'; import uuid from '../../utils/uuid'; import { ERROR_CODES, ERROR_MSG_INCOMPLETE_FIELD } from '../Errors/constants'; @@ -35,6 +35,7 @@ export const createAnalyticsObject = (aObj: CreateAnalyticsObject): AnalyticsObj /** INFO */ ...(aObj.event === 'info' && { type: aObj.type, target: aObj.target }), // info event ...(aObj.event === 'info' && aObj.issuer && { issuer: aObj.issuer }), // relates to issuerLists + ...(aObj.event === 'info' && { isExpress: aObj.isExpress, expressPage: aObj.expressPage }), // relates to Plugins & detecting Express PMs ...(aObj.event === 'info' && aObj.isStoredPaymentMethod && { isStoredPaymentMethod: aObj.isStoredPaymentMethod, brand: aObj.brand }), // only added if we have an info event about a storedPM ...(aObj.event === 'info' && aObj.type === ANALYTICS_VALIDATION_ERROR_STR && { @@ -53,3 +54,10 @@ const mapErrorCodesForAnalytics = (errorCode: string, target: string) => { return errorCodeMapping[errorCode] ?? errorCode; }; + +export const processAnalyticsData = (analyticsData: AnalyticsData) => { + return Object.keys(analyticsData).reduce((r, e) => { + if (ALLOWED_ANALYTICS_DATA.includes(e)) r[e] = analyticsData[e]; + return r; + }, {}); +}; diff --git a/packages/playground/src/pages/Cards/Cards.js b/packages/playground/src/pages/Cards/Cards.js index f97b8f0a49..820e430cb9 100644 --- a/packages/playground/src/pages/Cards/Cards.js +++ b/packages/playground/src/pages/Cards/Cards.js @@ -8,18 +8,18 @@ import '../../style.scss'; import { MockReactApp } from './MockReactApp'; import { searchFunctionExample } from '../../utils'; -const onlyShowCard = false; +const onlyShowCard = true; const showComps = { - clickToPay: true, - storedCard: true, - card: true, - cardWithInstallments: true, - cardInReact: true, - bcmcCard: true, - avsCard: true, - avsPartialCard: true, - kcpCard: true + // clickToPay: true, + // storedCard: true, + // card: true, + // cardWithInstallments: true, + // cardInReact: true, + // bcmcCard: true, + avsCard: true + // avsPartialCard: true, + // kcpCard: true }; const disclaimerMessage = { message: 'By continuing you accept the %{linkText} of MyStore', @@ -47,7 +47,8 @@ getPaymentMethods({ amount, shopperLocale }).then(async paymentMethodsResponse = }, risk: { enabled: false - } + }, + srConfig: { moveFocus: false, showPanel: true } }); // Stored Card @@ -77,7 +78,8 @@ getPaymentMethods({ amount, shopperLocale }).then(async paymentMethodsResponse = }, onBinLookup: obj => { console.log('### Cards::onBinLookup:: obj=', obj); - } + }, + onFieldValid: obj => console.log('### Cards::onFieldValid:: obj', obj) }) .mount('.card-field'); } @@ -136,18 +138,20 @@ getPaymentMethods({ amount, shopperLocale }).then(async paymentMethodsResponse = billingAddressAllowedCountries: ['US', 'CA', 'GB'], // billingAddressRequiredFields: ['postalCode', 'country'], + onAddressLookup: searchFunctionExample, + // data: - data: { - holderName: 'J. Smith', - billingAddress: { - street: 'Infinite Loop', - postalCode: '95014', - city: 'Cupertino', - houseNumberOrName: '1', - country: 'US', - stateOrProvince: 'CA' - } - }, + // data: { + // holderName: 'J. Smith', + // billingAddress: { + // street: 'Infinite Loop', + // postalCode: '95014', + // city: 'Cupertino', + // houseNumberOrName: '1', + // country: 'US', + // stateOrProvince: 'CA' + // } + // }, onError: obj => { console.log('component level merchant defined error handler for Card obj=', obj); } diff --git a/packages/playground/src/pages/Wallets/Wallets.js b/packages/playground/src/pages/Wallets/Wallets.js index c855af90ef..ee69712d89 100644 --- a/packages/playground/src/pages/Wallets/Wallets.js +++ b/packages/playground/src/pages/Wallets/Wallets.js @@ -19,7 +19,22 @@ getPaymentMethods({ amount, shopperLocale }).then(async paymentMethodsResponse = onError(error) { console.log(error); }, - showPayButton: true + showPayButton: true, + analytics: { + analyticsData: { + applicationInfo: { + merchantApplication: { + name: 'merchant_application_name', + version: 'version' + }, + externalPlatform: { + name: 'external_platform_name', + version: 'external_platform_version', + integrator: 'getSystemIntegratorName' + } + } + } + } }); // Cash App Pay @@ -165,6 +180,10 @@ getPaymentMethods({ amount, shopperLocale }).then(async paymentMethodsResponse = // Payment info countryCode: 'NL', + // Analytics info + isExpress: true, + expressPage: 'pdp', + // Merchant config (required) // configuration: { // gatewayMerchantId: 'TestMerchant', // name of MerchantAccount