Skip to content

Commit

Permalink
feat(analytics): track api errors
Browse files Browse the repository at this point in the history
- makePayments: '620'
- submitPaymentDetails: '621'
- submitThreeDS2Fingerprint: '622'
- createOrder: '623'
  • Loading branch information
longyulongyu committed Dec 17, 2024
1 parent 0e730a3 commit 24daf37
Show file tree
Hide file tree
Showing 10 changed files with 70 additions and 20 deletions.
9 changes: 9 additions & 0 deletions .changeset/fresh-lamps-raise.md
Original file line number Diff line number Diff line change
@@ -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`
8 changes: 7 additions & 1 deletion packages/lib/src/components/Giftcard/Giftcard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,13 @@ export class GiftcardElement extends UIElement<GiftCardConfiguration> {
})
.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));
}
}
});
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
15 changes: 11 additions & 4 deletions packages/lib/src/components/internal/UIElement/UIElement.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -249,8 +249,11 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps> 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);
}
Expand All @@ -277,6 +280,10 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps> 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);
}
Expand Down
6 changes: 4 additions & 2 deletions packages/lib/src/core/Errors/AdyenCheckoutError.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
interface CheckoutErrorOptions {
cause?: any;
code?: string;
}

export const NETWORK_ERROR = 'NETWORK_ERROR';
Expand Down Expand Up @@ -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;
}
}

Expand Down
28 changes: 22 additions & 6 deletions packages/lib/src/core/Services/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -27,7 +35,15 @@ function isAdyenApiErrorResponse(data: any): data is AdyenApiErrorResponse {
}

export function http<T>(options: HttpOptions, data?: any): Promise<T> {
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,
Expand Down Expand Up @@ -57,12 +73,12 @@ export function http<T>(options: HttpOptions, data?: any): Promise<T> {
}

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;
})
/**
Expand All @@ -81,12 +97,12 @@ export function http<T>(options: HttpOptions, data?: any): Promise<T> {

// 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;
Expand All @@ -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 });
}
}

Expand Down
8 changes: 8 additions & 0 deletions packages/lib/src/core/Services/sessions/constants.ts
Original file line number Diff line number Diff line change
@@ -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'
};
4 changes: 2 additions & 2 deletions packages/lib/src/core/Services/sessions/create-order.ts
Original file line number Diff line number Diff line change
@@ -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';

/**
*/
Expand All @@ -11,7 +11,7 @@ function createOrder(session: Session): Promise<CheckoutSessionOrdersResponse> {
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;
4 changes: 2 additions & 2 deletions packages/lib/src/core/Services/sessions/make-payment.ts
Original file line number Diff line number Diff line change
@@ -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';

/**
*/
Expand All @@ -12,7 +12,7 @@ function makePayment(paymentRequest, session: Session): Promise<CheckoutSessionP
...paymentRequest
};

return httpPost({ loadingContext: session.loadingContext, path, errorLevel: 'fatal' }, data);
return httpPost({ loadingContext: session.loadingContext, path, errorLevel: 'fatal', errorCode: API_ERROR_CODE.makePayments }, data);
}

export default makePayment;
4 changes: 2 additions & 2 deletions packages/lib/src/core/Services/sessions/submit-details.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { httpPost } from '../http';
import Session from '../../CheckoutSession';
import { API_VERSION } from './constants';
import { API_ERROR_CODE, API_VERSION } from './constants';
import { CheckoutSessionDetailsResponse } from '../../CheckoutSession/types';

/**
Expand All @@ -12,7 +12,7 @@ function submitDetails(details, session: Session): Promise<CheckoutSessionDetail
...details
};

return httpPost({ loadingContext: session.loadingContext, path, errorLevel: 'fatal' }, data);
return httpPost({ loadingContext: session.loadingContext, path, errorLevel: 'fatal', errorCode: API_ERROR_CODE.submitPaymentDetails }, data);
}

export default submitDetails;

0 comments on commit 24daf37

Please sign in to comment.