From 24daf377bf8960fc7985a4e1546dea8c6882edf4 Mon Sep 17 00:00:00 2001 From: Yu Long Date: Mon, 16 Dec 2024 17:27:07 +0100 Subject: [PATCH] feat(analytics): track api errors - makePayments: '620' - submitPaymentDetails: '621' - submitThreeDS2Fingerprint: '622' - createOrder: '623' --- .changeset/fresh-lamps-raise.md | 9 ++++++ .../lib/src/components/Giftcard/Giftcard.tsx | 8 +++++- .../ThreeDS2/callSubmit3DS2Fingerprint.ts | 4 ++- .../internal/UIElement/UIElement.tsx | 15 +++++++--- .../lib/src/core/Errors/AdyenCheckoutError.ts | 6 ++-- packages/lib/src/core/Services/http.ts | 28 +++++++++++++++---- .../src/core/Services/sessions/constants.ts | 8 ++++++ .../core/Services/sessions/create-order.ts | 4 +-- .../core/Services/sessions/make-payment.ts | 4 +-- .../core/Services/sessions/submit-details.ts | 4 +-- 10 files changed, 70 insertions(+), 20 deletions(-) create mode 100644 .changeset/fresh-lamps-raise.md diff --git a/.changeset/fresh-lamps-raise.md b/.changeset/fresh-lamps-raise.md new file mode 100644 index 000000000..5c734e6d2 --- /dev/null +++ b/.changeset/fresh-lamps-raise.md @@ -0,0 +1,9 @@ +--- +'@adyen/adyen-web': minor +--- + +Start tracking API errors for the following endpoints for analytics purposes: +- `/sessions/${session.id}/payments` +- `/sessions/${session.id}/orders` +- `/sessions/${session.id}/paymentDetails` +- `v1/submitThreeDS2Fingerprint` \ No newline at end of file diff --git a/packages/lib/src/components/Giftcard/Giftcard.tsx b/packages/lib/src/components/Giftcard/Giftcard.tsx index 1c1f09fea..3c567e33d 100644 --- a/packages/lib/src/components/Giftcard/Giftcard.tsx +++ b/packages/lib/src/components/Giftcard/Giftcard.tsx @@ -102,7 +102,13 @@ export class GiftcardElement extends UIElement { }) .catch(error => { this.setStatus(error?.message || 'error'); - if (this.props.onError) this.handleError(new AdyenCheckoutError('ERROR', error)); + if (this.props.onError) { + if (error instanceof AdyenCheckoutError) { + this.handleError(error); + } else { + this.handleError(new AdyenCheckoutError('ERROR', error)); + } + } }); }; diff --git a/packages/lib/src/components/ThreeDS2/callSubmit3DS2Fingerprint.ts b/packages/lib/src/components/ThreeDS2/callSubmit3DS2Fingerprint.ts index 91dcaccf5..06c370303 100644 --- a/packages/lib/src/components/ThreeDS2/callSubmit3DS2Fingerprint.ts +++ b/packages/lib/src/components/ThreeDS2/callSubmit3DS2Fingerprint.ts @@ -5,6 +5,7 @@ import AdyenCheckoutError from '../../core/Errors/AdyenCheckoutError'; import { THREEDS2_ERROR, THREEDS2_FINGERPRINT_SUBMIT } from './constants'; import { ANALYTICS_ERROR_TYPE, Analytics3DS2Errors } from '../../core/Analytics/constants'; import { SendAnalyticsObject } from '../../core/Analytics/types'; +import { API_ERROR_CODE } from '../../core/Services/sessions/constants'; /** * ThreeDS2DeviceFingerprint, onComplete, calls a new, internal, endpoint which @@ -15,7 +16,8 @@ export default function callSubmit3DS2Fingerprint({ data }): void { { path: `v1/submitThreeDS2Fingerprint?token=${this.props.clientKey}`, loadingContext: this.props.loadingContext, - errorLevel: 'fatal' + errorLevel: 'fatal', + errorCode: API_ERROR_CODE.submitThreeDS2Fingerprint }, { ...data diff --git a/packages/lib/src/components/internal/UIElement/UIElement.tsx b/packages/lib/src/components/internal/UIElement/UIElement.tsx index 945de0637..d65253fd9 100644 --- a/packages/lib/src/components/internal/UIElement/UIElement.tsx +++ b/packages/lib/src/components/internal/UIElement/UIElement.tsx @@ -2,10 +2,10 @@ import { h } from 'preact'; import BaseElement from '../BaseElement/BaseElement'; import PayButton from '../PayButton'; import { assertIsDropin, cleanupFinalResult, getRegulatoryDefaults, sanitizeResponse, verifyPaymentDidNotFail } from './utils'; -import AdyenCheckoutError from '../../../core/Errors/AdyenCheckoutError'; +import AdyenCheckoutError, { NETWORK_ERROR } from '../../../core/Errors/AdyenCheckoutError'; import { hasOwnProperty } from '../../../utils/hasOwnProperty'; import { Resources } from '../../../core/Context/Resources'; -import { ANALYTICS_SUBMIT_STR } from '../../../core/Analytics/constants'; +import { ANALYTICS_ERROR_TYPE, ANALYTICS_EVENT, ANALYTICS_SUBMIT_STR } from '../../../core/Analytics/constants'; import type { AnalyticsInitialEvent, SendAnalyticsObject } from '../../../core/Analytics/types'; import type { CoreConfiguration, ICore, AdditionalDetailsData } from '../../../core/types'; @@ -249,8 +249,11 @@ export abstract class UIElement

exten try { return await this.core.session.submitPayment(data); } catch (error: unknown) { - if (error instanceof AdyenCheckoutError) this.handleError(error); - else this.handleError(new AdyenCheckoutError('ERROR', 'Error when making /payments call', { cause: error })); + if (error instanceof AdyenCheckoutError) { + this.handleError(error); + } else { + this.handleError(new AdyenCheckoutError('ERROR', 'Error when making /payments call', { cause: error })); + } return Promise.reject(error); } @@ -277,6 +280,10 @@ export abstract class UIElement

exten */ this.setElementStatus('ready'); + if (error.name === NETWORK_ERROR && error.options.code) { + this.submitAnalytics({ type: ANALYTICS_EVENT.error, errorType: ANALYTICS_ERROR_TYPE.apiError, code: error.options.code }); + } + if (this.props.onError) { this.props.onError(error, this.elementRef); } diff --git a/packages/lib/src/core/Errors/AdyenCheckoutError.ts b/packages/lib/src/core/Errors/AdyenCheckoutError.ts index 4b66aeba8..9e6618f3e 100644 --- a/packages/lib/src/core/Errors/AdyenCheckoutError.ts +++ b/packages/lib/src/core/Errors/AdyenCheckoutError.ts @@ -1,5 +1,6 @@ interface CheckoutErrorOptions { cause?: any; + code?: string; } export const NETWORK_ERROR = 'NETWORK_ERROR'; @@ -35,13 +36,14 @@ class AdyenCheckoutError extends Error { }; public cause: unknown; + public options: CheckoutErrorOptions; constructor(type: keyof typeof AdyenCheckoutError.errorTypes, message?: string, options?: CheckoutErrorOptions) { super(message); this.name = AdyenCheckoutError.errorTypes[type]; - - this.cause = options?.cause; + this.options = options || {}; + this.cause = this.options.cause; } } diff --git a/packages/lib/src/core/Services/http.ts b/packages/lib/src/core/Services/http.ts index e5d2cdd5d..954224913 100644 --- a/packages/lib/src/core/Services/http.ts +++ b/packages/lib/src/core/Services/http.ts @@ -11,6 +11,14 @@ export interface HttpOptions { timeout?: number; errorLevel?: ErrorLevel; errorMessage?: string; + errorCode?: string; +} + +interface FetchErrorOptions { + message?: string; + level?: ErrorLevel; + cause?: unknown; + code?: string; } type ErrorLevel = 'silent' | 'info' | 'warn' | 'error' | 'fatal'; @@ -27,7 +35,15 @@ function isAdyenApiErrorResponse(data: any): data is AdyenApiErrorResponse { } export function http(options: HttpOptions, data?: any): Promise { - const { headers = [], errorLevel = 'warn', loadingContext = FALLBACK_CONTEXT, method = 'GET', path, timeout = DEFAULT_HTTP_TIMEOUT } = options; + const { + headers = [], + errorLevel = 'warn', + errorCode, + loadingContext = FALLBACK_CONTEXT, + method = 'GET', + path, + timeout = DEFAULT_HTTP_TIMEOUT + } = options; const request: RequestInit = { method, @@ -57,12 +73,12 @@ export function http(options: HttpOptions, data?: any): Promise { } if (isAdyenApiErrorResponse(data)) { - handleFetchError(data.message, errorLevel, data); + handleFetchError({ message: data.message, level: errorLevel, cause: data, code: errorCode }); return; } const errorMessage = options.errorMessage || `Service at ${url} is not available`; - handleFetchError(errorMessage, errorLevel, data); + handleFetchError({ message: errorMessage, level: errorLevel, cause: data, code: errorCode }); return; }) /** @@ -81,12 +97,12 @@ export function http(options: HttpOptions, data?: any): Promise { // eslint-disable-next-line @typescript-eslint/no-base-to-string,@typescript-eslint/restrict-template-expressions const errorMessage = options.errorMessage || `Call to ${url} failed. Error= ${error}`; - handleFetchError(errorMessage, errorLevel, error); + handleFetchError({ message: errorMessage, level: errorLevel, cause: error, code: errorCode }); }) ); } -function handleFetchError(message: string, level: ErrorLevel, error: unknown): void { +function handleFetchError({ message, level, cause, code }: FetchErrorOptions): void { switch (level) { case 'silent': { break; @@ -98,7 +114,7 @@ function handleFetchError(message: string, level: ErrorLevel, error: unknown): v break; } default: - throw new AdyenCheckoutError('NETWORK_ERROR', message, { cause: error }); + throw new AdyenCheckoutError('NETWORK_ERROR', message, { cause, code }); } } diff --git a/packages/lib/src/core/Services/sessions/constants.ts b/packages/lib/src/core/Services/sessions/constants.ts index ba1d38d49..8ece7c6a8 100644 --- a/packages/lib/src/core/Services/sessions/constants.ts +++ b/packages/lib/src/core/Services/sessions/constants.ts @@ -1 +1,9 @@ export const API_VERSION = 'v1'; + +// Same error code will be sent to the analytics +export const API_ERROR_CODE = { + makePayments: '620', + submitPaymentDetails: '621', + submitThreeDS2Fingerprint: '622', + createOrder: '623' +}; diff --git a/packages/lib/src/core/Services/sessions/create-order.ts b/packages/lib/src/core/Services/sessions/create-order.ts index 9c8171c1e..da0803aa3 100644 --- a/packages/lib/src/core/Services/sessions/create-order.ts +++ b/packages/lib/src/core/Services/sessions/create-order.ts @@ -1,7 +1,7 @@ import { httpPost } from '../http'; import Session from '../../CheckoutSession'; import { CheckoutSessionOrdersResponse } from '../../CheckoutSession/types'; -import { API_VERSION } from './constants'; +import { API_ERROR_CODE, API_VERSION } from './constants'; /** */ @@ -11,7 +11,7 @@ function createOrder(session: Session): Promise { sessionData: session.data }; - return httpPost({ loadingContext: session.loadingContext, path, errorLevel: 'fatal' }, data); + return httpPost({ loadingContext: session.loadingContext, path, errorLevel: 'fatal', errorCode: API_ERROR_CODE.createOrder }, data); } export default createOrder; diff --git a/packages/lib/src/core/Services/sessions/make-payment.ts b/packages/lib/src/core/Services/sessions/make-payment.ts index 827a6435e..d527591fa 100644 --- a/packages/lib/src/core/Services/sessions/make-payment.ts +++ b/packages/lib/src/core/Services/sessions/make-payment.ts @@ -1,7 +1,7 @@ import { httpPost } from '../http'; import Session from '../../CheckoutSession'; import { CheckoutSessionPaymentResponse } from '../../CheckoutSession/types'; -import { API_VERSION } from './constants'; +import { API_ERROR_CODE, API_VERSION } from './constants'; /** */ @@ -12,7 +12,7 @@ function makePayment(paymentRequest, session: Session): Promise