Skip to content

Commit

Permalink
v5 Adding subType to 3DS2 analytics events (#2680)
Browse files Browse the repository at this point in the history
* Adding subType to 3DS2 analytics events

* Adding changeset file

* Small fixes around Sonarcloud Maintainability

* Calculating "result" for 3DS2 "completed" analytics events. (Currently commented out)

* Adding "result" for 3DS2 "completed" analytics events.
  • Loading branch information
sponglord committed May 17, 2024
1 parent f93746b commit 5c6bea0
Show file tree
Hide file tree
Showing 8 changed files with 93 additions and 24 deletions.
5 changes: 5 additions & 0 deletions .changeset/tricky-impalas-accept.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@adyen/adyen-web": minor
---

Adding subType to 3DS2 analytics events
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ import useImage from '../../../../core/Context/useImage';
import { ActionHandledReturnObject } from '../../../types';
import { THREEDS2_FULL, THREEDS2_NUM } from '../../config';
import { SendAnalyticsObject } from '../../../../core/Analytics/types';
import { Analytics3DS2Events } from '../../../../core/Analytics/constants';
import { ErrorObject } from '../../../../core/Errors/types';

class PrepareChallenge3DS2 extends Component<PrepareChallenge3DS2Props, PrepareChallenge3DS2State> {
public static defaultProps = {
public static readonly defaultProps = {
onComplete: () => {},
onError: () => {},
onActionHandled: () => {}
Expand All @@ -41,7 +42,6 @@ class PrepareChallenge3DS2 extends Component<PrepareChallenge3DS2Props, PrepareC
this.setStatusError({
errorInfo:
'Challenge Data missing one or more of the following properties (acsURL | acsTransID | messageVersion | threeDSServerTransID)'
// errorObj: challengeData // TODO Decide if we want to expose this data
});
return;
}
Expand All @@ -60,19 +60,24 @@ class PrepareChallenge3DS2 extends Component<PrepareChallenge3DS2Props, PrepareC
}

public onActionHandled = (rtnObj: ActionHandledReturnObject) => {
this.props.onSubmitAnalytics({ type: THREEDS2_FULL, message: rtnObj.actionDescription });
this.props.onSubmitAnalytics({
type: THREEDS2_FULL,
message: rtnObj.actionDescription,
subtype: Analytics3DS2Events.CHALLENGE_IFRAME_LOADED
});

this.props.onActionHandled(rtnObj);
};

public onFormSubmit = (msg: string) => {
this.props.onSubmitAnalytics({
type: THREEDS2_FULL,
message: msg
message: msg,
subtype: Analytics3DS2Events.CHALLENGE_DATA_SENT
});
};

setStatusComplete(resultObj) {
setStatusComplete(resultObj, isTimeout = false) {
this.setState({ status: 'complete' }, () => {
/**
* Create the data in the way that the /details endpoint expects.
Expand All @@ -82,17 +87,40 @@ class PrepareChallenge3DS2 extends Component<PrepareChallenge3DS2Props, PrepareC
const resolveDataFunction = this.props.useOriginalFlow ? createOldChallengeResolveData : createChallengeResolveData;
const data = resolveDataFunction(this.props.dataKey, resultObj.transStatus, this.props.paymentData);

// Create log object - the process is completed, one way or another
/** Calculate "result" for analytics */
let result: string;

switch (resultObj?.transStatus) {
case 'Y':
result = 'success';
break;
case 'N':
result = 'failed';
break;
case 'U':
result = isTimeout ? 'timeout' : 'cancelled';
break;
default:
}
if (resultObj?.errorCode) {
result = 'noTransStatus';
}

/** Create log object - the process is completed, one way or another */
const analyticsObject: SendAnalyticsObject = {
type: THREEDS2_FULL,
message: `${THREEDS2_NUM} challenge has completed`
message: `${THREEDS2_NUM} challenge has completed`,
subtype: Analytics3DS2Events.CHALLENGE_COMPLETED,
result
};

// Send log to analytics endpoint
this.props.onSubmitAnalytics(analyticsObject);

// Equals a call to onAdditionalDetails (mapped in actionTypes.ts) - except for 3DS2InMDFlow which doesn't handle an action
// and instead creates a new ThreeDS2Challenge component, with an onComplete prop
/**
* Equals a call to onAdditionalDetails (mapped in actionTypes.ts) - except for 3DS2InMDFlow which doesn't handle an action
* and instead creates a new ThreeDS2Challenge component, with an onComplete prop
*/
this.props.onComplete(data);
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ import { FingerPrintData, ResultObject } from '../../types';
import { ActionHandledReturnObject } from '../../../types';
import { THREEDS2_FULL, THREEDS2_NUM } from '../../config';
import { SendAnalyticsObject } from '../../../../core/Analytics/types';
import { Analytics3DS2Events } from '../../../../core/Analytics/constants';
import { ErrorObject } from '../../../../core/Errors/types';

class PrepareFingerprint3DS2 extends Component<PrepareFingerprint3DS2Props, PrepareFingerprint3DS2State> {
public static type = 'scheme';
public static readonly type = 'scheme';

public static defaultProps = {
public static readonly defaultProps = {
onComplete: () => {},
onError: () => {},
paymentData: '',
Expand All @@ -33,8 +34,7 @@ class PrepareFingerprint3DS2 extends Component<PrepareFingerprint3DS2Props, Prep
};
} else {
this.state = { status: 'error' };
// TODO - confirm that we should do this, or is it possible to proceed to the challenge anyway?
// ...in which case we should console.debug the error object and then call: this.setStatusComplete({ threeDSCompInd: 'N' });

this.props.onError({
errorCode: this.props.dataKey,
message: 'Missing fingerprintToken parameter'
Expand All @@ -43,15 +43,20 @@ class PrepareFingerprint3DS2 extends Component<PrepareFingerprint3DS2Props, Prep
}

public onActionHandled = (rtnObj: ActionHandledReturnObject) => {
// Leads to an "iframe loaded" log action
this.props.onSubmitAnalytics({ type: THREEDS2_FULL, message: rtnObj.actionDescription });
this.props.onSubmitAnalytics({
type: THREEDS2_FULL,
message: rtnObj.actionDescription,
subtype: Analytics3DS2Events.FINGERPRINT_IFRAME_LOADED
});

this.props.onActionHandled(rtnObj);
};

public onFormSubmit = (msg: string) => {
this.props.onSubmitAnalytics({
type: THREEDS2_FULL,
message: msg
message: msg,
subtype: Analytics3DS2Events.FINGERPRINT_DATA_SENT
});
};

Expand All @@ -67,7 +72,7 @@ class PrepareFingerprint3DS2 extends Component<PrepareFingerprint3DS2Props, Prep
this.setState({ status: 'retrievingFingerPrint' });
}

setStatusComplete(resultObj: ResultObject) {
setStatusComplete(resultObj: ResultObject, isTimeout = false) {
this.setState({ status: 'complete' }, () => {
/**
* Create the data in the way that the endpoint expects:
Expand All @@ -77,10 +82,29 @@ class PrepareFingerprint3DS2 extends Component<PrepareFingerprint3DS2Props, Prep
const resolveDataFunction = this.props.useOriginalFlow ? createOldFingerprintResolveData : createFingerprintResolveData;
const data = resolveDataFunction(this.props.dataKey, resultObj, this.props.paymentData);

/** The fingerprint process is completed, one way or another */
/** Calculate "result" for analytics */
let result: string;

switch (resultObj?.threeDSCompInd) {
case 'Y':
result = 'success';
break;
case 'N': {
result = isTimeout ? 'timeout' : 'failed'; // timed-out; or, 'failed' ("N" being the result returned from the threeDSMethodURL)
break;
}
case 'U':
result = 'noThreeDSMethodURL';
break;
default:
}

/** Create log object - the fingerprint process is completed, one way or another */
const analyticsObject: SendAnalyticsObject = {
type: THREEDS2_FULL,
message: `${THREEDS2_NUM} fingerprinting has completed`
message: `${THREEDS2_NUM} fingerprinting has completed`,
subtype: Analytics3DS2Events.FINGERPRINT_COMPLETED,
result
};

// Send log to analytics endpoint
Expand All @@ -107,7 +131,7 @@ class PrepareFingerprint3DS2 extends Component<PrepareFingerprint3DS2Props, Prep
*/
const errorCodeObject = handleErrorCode(fingerprint.errorCode);
console.debug('### PrepareFingerprint3DS2::fingerprint timed-out:: errorCodeObject=', errorCodeObject);
this.setStatusComplete(fingerprint.result);
this.setStatusComplete(fingerprint.result, true);
}}
showSpinner={showSpinner}
{...fingerPrintData}
Expand Down
4 changes: 2 additions & 2 deletions packages/lib/src/core/Analytics/analyticsPreProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,10 @@ export const analyticsPreProcessor = (analyticsModule: AnalyticsModule) => {

// General 3DS2 log events: "action handled" (i.e. iframe loaded), data sent, process completed
case THREEDS2_FULL: {
const { message, metadata } = analyticsObj;
const { message, metadata, subtype, result } = analyticsObj;
analyticsModule.createAnalyticsEvent({
event: ANALYTICS_EVENT_LOG,
data: { component, type, message, metadata }
data: { component, type, message, metadata, subtype, result }
});
break;
}
Expand Down
9 changes: 9 additions & 0 deletions packages/lib/src/core/Analytics/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,12 @@ export const errorCodeMapping = {
export const ANALYTICS_EXPRESS_PAGES_ARRAY = ['cart', 'minicart', 'pdp', 'checkout'];

export const ALLOWED_ANALYTICS_DATA = ['applicationInfo', 'checkoutAttemptId'];

export enum Analytics3DS2Events {
FINGERPRINT_DATA_SENT = 'fingerprintDataSentWeb',
FINGERPRINT_IFRAME_LOADED = 'fingerprintIframeLoaded',
FINGERPRINT_COMPLETED = 'fingerprintCompleted',
CHALLENGE_DATA_SENT = 'challengeDataSentWeb',
CHALLENGE_IFRAME_LOADED = 'challengeIframeLoaded',
CHALLENGE_COMPLETED = 'challengeCompleted'
}
1 change: 1 addition & 0 deletions packages/lib/src/core/Analytics/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ export interface AnalyticsObject {
issuer?: string;
isExpress?: boolean;
expressPage?: string;
result?: string;
}

export type ANALYTICS_EVENT = 'log' | 'error' | 'info';
Expand Down
4 changes: 3 additions & 1 deletion packages/lib/src/core/Analytics/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { AnalyticsData, AnalyticsObject, CreateAnalyticsObject } from './types';
import { ANALYTICS_ACTION_STR, ANALYTICS_VALIDATION_ERROR_STR, ALLOWED_ANALYTICS_DATA, errorCodeMapping } from './constants';
import uuid from '../../utils/uuid';
import { ERROR_CODES, ERROR_MSG_INCOMPLETE_FIELD } from '../Errors/constants';
import { THREEDS2_FULL } from '../../components/ThreeDS2/config';

export const getUTCTimestamp = () => Date.now();

Expand Down Expand Up @@ -31,7 +32,8 @@ export const createAnalyticsObject = (aObj: CreateAnalyticsObject): AnalyticsObj
...(aObj.event === 'error' && { code: aObj.code, errorType: aObj.errorType, message: aObj.message }), // error event
/** LOG */
...(aObj.event === 'log' && { type: aObj.type, message: aObj.message }), // log event
...(aObj.event === 'log' && aObj.type === ANALYTICS_ACTION_STR && { subType: aObj.subtype }), // only added if we have a log event of Action type
...(aObj.event === 'log' && (aObj.type === ANALYTICS_ACTION_STR || aObj.type === THREEDS2_FULL) && { subType: aObj.subtype }), // only added if we have a log event of Action type or ThreeDS2
...(aObj.event === 'log' && aObj.type === THREEDS2_FULL && { result: aObj.result }), // only added if we have a log event ThreeDS2 type
/** INFO */
...(aObj.event === 'info' && { type: aObj.type, target: aObj.target }), // info event
...(aObj.event === 'info' && aObj.issuer && { issuer: aObj.issuer }), // relates to issuerLists
Expand Down
2 changes: 1 addition & 1 deletion packages/playground/src/pages/Cards/Cards.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import '../../style.scss';
import { MockReactApp } from './MockReactApp';
import { searchFunctionExample } from '../../utils';

const onlyShowCard = false;
const onlyShowCard = true;

const showComps = {
clickToPay: true,
Expand Down

0 comments on commit 5c6bea0

Please sign in to comment.