Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/v5 card adds analytics config data #2724

Merged
merged 33 commits into from
Jul 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
67f3fe9
Card passes this.props to submitAnalytics
sponglord May 29, 2024
28e1178
Added util that analyticsPreProcessor.ts can call to generate configD…
sponglord May 31, 2024
b004356
Passing on configData to endpoint
sponglord May 31, 2024
d2a1c40
Fixed accidental overwrite of analytics "type" value in rendered event
sponglord May 31, 2024
3245123
Added unit test for billingAddressMode
sponglord May 31, 2024
ff11e67
Fix for edge case where both addressLookup and billingAddressMode:'pa…
sponglord May 31, 2024
0242184
Adding more configData values, with corresponding unit tests
sponglord May 31, 2024
ee6e4b9
Added ConfigData type
sponglord May 31, 2024
0d2dfcf
Adding 5 more props & tests
sponglord May 31, 2024
f8d53a3
Adding another 5 props & tests
sponglord Jun 3, 2024
f83f6ff
Playground Card has all config options listed
sponglord Jun 4, 2024
be076bc
minimumExpiryDate is sent as a boolean
sponglord Jun 4, 2024
d257fc2
Merge branch 'main' into feature/v5_card_adds_analytics_configData
sponglord Jun 4, 2024
9a7842f
default value of showInstallmentAmounts is null, but it is alway repr…
sponglord Jun 4, 2024
ab0ee7b
billingAddressMode value is always passed on (rather than being force…
sponglord Jun 4, 2024
b776322
billingAddressAllowedCountries has default value in CardInput.default…
sponglord Jun 4, 2024
c220464
isStylesConfigured ==> hasStylesConfigured. booleans added to show if…
sponglord Jun 4, 2024
8ce43f3
Resolved some types. All expected configData for the Card component i…
sponglord Jun 24, 2024
0e03042
Updated and added to unit tests
sponglord Jun 24, 2024
7530162
Merge branch 'refs/heads/main' into feature/v5_card_adds_analytics_co…
sponglord Jun 24, 2024
42b851d
Clarifying types
sponglord Jun 24, 2024
48ad19e
Merge branch 'refs/heads/main' into feature/v5_card_adds_analytics_co…
sponglord Jul 12, 2024
f4dc219
allowing configData to be sent to endpoint. Adding props related to s…
sponglord Jul 12, 2024
76e11b2
Skipping tests until endpoint is ready
sponglord Jul 12, 2024
651c09d
Adding changeset file
sponglord Jul 12, 2024
5c84fff
Updating e2e test to reflect third party changes to markup
sponglord Jul 12, 2024
2b60a8b
Limiting configData length until endpoint is ready
sponglord Jul 16, 2024
93847c0
Adjusting unit tests to allow for presence of configData
sponglord Jul 18, 2024
d9f4f18
Prefixing booleans, indicating presence of callbacks, with "has"
sponglord Jul 18, 2024
4afc97f
Card component merges CardInput's default props into its own
sponglord Jul 18, 2024
1873819
Cleaner way to detect if default callbacks have been overridden
sponglord Jul 18, 2024
1a8deaa
Card shouldn't merge all of cardInputs default props
sponglord Jul 19, 2024
51d4251
Also send configData for Bcmc. Minor reformatting
sponglord Jul 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/forty-bulldogs-fly.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@adyen/adyen-web": minor
---

Card component adds configData for analytics (added to "rendered" Info Event)
2 changes: 1 addition & 1 deletion packages/e2e-playwright/models/redirect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class Redirect {

this.selectYourBankButton = page.getByRole('button', { name: SELECT_YOUR_BANK });

this.selectTestBankButton = page.getByRole('button', { name: TEST_BANK_NAME });
this.selectTestBankButton = page.getByText(TEST_BANK_NAME);

this.simulateSuccessButton = page.getByRole('button', { name: SIMULATION_TYPE_SUCCESS });
this.simulateFailureButton = page.getByRole('button', { name: SIMULATION_TYPE_FAILURE });
Expand Down
25 changes: 21 additions & 4 deletions packages/lib/src/components/Card/Card.Analytics.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Analytics from '../../core/Analytics';
const analyticsModule = Analytics({ analytics: {}, loadingContext: '', locale: '', clientKey: '' });

let card;
let analyticsEventObject;

import {
ANALYTICS_CONFIGURED_STR,
Expand All @@ -23,18 +24,29 @@ describe('Card: calls that generate "info" analytics should produce objects with
card = new CardElement({
modules: {
analytics: analyticsModule
}
},
type: 'card'
});

analyticsModule.createAnalyticsEvent = jest.fn(() => null);
analyticsEventObject = null;

// @ts-ignore it's a test
analyticsModule.createAnalyticsEvent = jest.fn(aObj => {
analyticsEventObject = aObj;
});
});

test('Analytics should produce an "info" event, of type "rendered", for a card PM', () => {
card.submitAnalytics({
type: ANALYTICS_RENDERED_STR
});

expect(analyticsModule.createAnalyticsEvent).toHaveBeenCalledWith({
// configData is too complex an object to fully inspect - but expect it to be there
expect(analyticsEventObject.data.configData).toBeDefined();
delete analyticsEventObject.data.configData;

// With configData removed inspect what's left
expect(analyticsEventObject).toEqual({
event: ANALYTICS_EVENT_INFO,
data: { component: card.constructor['type'], type: ANALYTICS_RENDERED_STR }
});
Expand All @@ -47,7 +59,12 @@ describe('Card: calls that generate "info" analytics should produce objects with
brand: 'mc'
});

expect(analyticsModule.createAnalyticsEvent).toHaveBeenCalledWith({
// configData is too complex an object to fully inspect - but expect it to be there
expect(analyticsEventObject.data.configData).toBeDefined();
delete analyticsEventObject.data.configData;

// With configData removed inspect what's left
expect(analyticsEventObject).toEqual({
event: ANALYTICS_EVENT_INFO,
data: { component: card.constructor['type'], type: ANALYTICS_RENDERED_STR, isStoredPaymentMethod: true, brand: 'mc' }
});
Expand Down
12 changes: 8 additions & 4 deletions packages/lib/src/components/Card/Card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { FieldErrorAnalyticsObject, SendAnalyticsObject } from '../../core/Analy
import { hasOwnProperty } from '../../utils/hasOwnProperty';
import { ERROR_CODES } from '../../core/Errors/constants';
import { getErrorMessageFromCode } from '../../core/Errors/utils';
import CardInputDefaultProps from './components/CardInput/defaultProps';

export class CardElement extends UIElement<CardElementProps> {
public static type = 'scheme';
Expand All @@ -47,10 +48,11 @@ export class CardElement extends UIElement<CardElementProps> {
}

protected static defaultProps = {
onBinLookup: () => {},
showBrandsUnderCardNumber: true,
showFormInstruction: true,
_disableClickToPay: false
_disableClickToPay: false,
doBinLookup: true,
// Merge most of CardInput's defaultProps
...reject(['type', 'setComponentRef']).from(CardInputDefaultProps)
};

public setStatus(status: UIElementStatus, props?): this {
Expand Down Expand Up @@ -82,6 +84,8 @@ export class CardElement extends UIElement<CardElementProps> {
hasCVC: !((props.brand && props.brand === 'bcmc') || props.hideCVC),
// billingAddressRequired only available for non-stored cards
billingAddressRequired: props.storedPaymentMethodId ? false : props.billingAddressRequired,
// edge case where merchant has defined both an onAddressLookup callback AND set billingAddressMode: 'partial' - which leads to some strange behaviour in the address UI
billingAddressMode: props.onAddressLookup ? CardInputDefaultProps.billingAddressMode : props.billingAddressMode,
// ...(props.brands && !props.groupTypes && { groupTypes: props.brands }),
type: props.type === 'scheme' ? 'card' : props.type,
countryCode: props.countryCode ? props.countryCode.toLowerCase() : null,
Expand Down Expand Up @@ -190,7 +194,7 @@ export class CardElement extends UIElement<CardElementProps> {
}
}

super.submitAnalytics(analyticsObj);
super.submitAnalytics(analyticsObj, this.props);
}

private onConfigSuccess = (obj: CbObjOnConfigSuccess) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,38 @@ export default {
setComponentRef: () => {},

// Settings
autoFocus: true,
billingAddressAllowedCountries: [],
billingAddressMode: AddressModeOptions.full,
billingAddressRequired: false,
billingAddressRequiredFields: ['street', 'houseNumberOrName', 'postalCode', 'city', 'stateOrProvince', 'country'],

configuration: { koreanAuthenticationRequired: false, socialSecurityNumberMode: 'auto' as SocialSecurityMode },
data: {
billingAddress: {}
},
disableIOSArrowKeys: true,
enableStoreDetails: false,
exposeExpiryDate: false,
forceCompat: false,
hasHolderName: false,
holderNameRequired: false,
enableStoreDetails: false,
hasCVC: true,
hideCVC: false,
installmentOptions: {},
keypadFix: true,
legacyInputMode: false,
maskSecurityCode: false,
minimumExpiryDate: null,
name: null, // Affects Dropin only, the name displayed in the PMList item
placeholders: {},
positionHolderNameOnTop: false,
showBrandIcon: true,
showBrandsUnderCardNumber: true,
positionHolderNameOnTop: false,
billingAddressRequired: false,
billingAddressMode: AddressModeOptions.full,
billingAddressRequiredFields: ['street', 'houseNumberOrName', 'postalCode', 'city', 'stateOrProvince', 'country'],
installmentOptions: {},
configuration: { koreanAuthenticationRequired: false, socialSecurityNumberMode: 'auto' as SocialSecurityMode },
autoFocus: true,
showInstallmentAmounts: null,
styles: {},

isPayButtonPrimaryVariant: true,
disableIOSArrowKeys: true,
exposeExpiryDate: false,

// Events
onLoad: (): any => {},
Expand All @@ -36,12 +52,5 @@ export default {
onFocus: (): any => {},
onChange: (): any => {},

// Values
data: {
billingAddress: {}
},

// Customization
styles: {},
placeholders: {}
onBinLookup: () => {} // Strictly speaking a Card level prop, but needed here for analytics.configData
};
9 changes: 8 additions & 1 deletion packages/lib/src/components/Card/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import {
CbObjOnLoad,
CbObjOnBinLookup,
CVCPolicyType,
DatePolicyType
DatePolicyType,
CbObjOnAllValid
} from '../internal/SecuredFields/lib/types';
import { ClickToPayConfiguration } from '../internal/ClickToPay/types';

Expand Down Expand Up @@ -109,6 +110,12 @@ export interface CardElementProps extends UIElementProps {
*/
onConfigSuccess?: (event: CbObjOnConfigSuccess) => void;

/**
* Called when *all* the securedFields becomes valid
* Also called again if one of the fields moves out of validity.
*/
onAllValid?: (event: CbObjOnAllValid) => void;

/**
* Called when a field becomes valid and also if a valid field changes and becomes invalid.
* For the card number field, it returns the last 4 digits of the card number.
Expand Down
5 changes: 5 additions & 0 deletions packages/lib/src/components/SecuredFields/SecuredFields.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { CbObjOnBinLookup, CbObjOnFocus } from '../internal/SecuredFields/lib/ty
import { BrandObject } from '../Card/types';
import { fieldTypeToSnakeCase, getCardImageUrl } from '../internal/SecuredFields/utils';
import { ANALYTICS_FOCUS_STR, ANALYTICS_UNFOCUS_STR } from '../../core/Analytics/constants';
import { SendAnalyticsObject } from '../../core/Analytics/types';

export class SecuredFieldsElement extends UIElement {
public static type = 'scheme';
Expand Down Expand Up @@ -40,6 +41,10 @@ export class SecuredFieldsElement extends UIElement {
};
}

protected submitAnalytics(analyticsObj: SendAnalyticsObject) {
sponglord marked this conversation as resolved.
Show resolved Hide resolved
super.submitAnalytics(analyticsObj, this.props);
}

updateStyles(stylesObj) {
if (this.componentRef?.updateStyles) this.componentRef.updateStyles(stylesObj);
return this;
Expand Down
Loading
Loading