Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
19 changes: 17 additions & 2 deletions packages/checkout/sdk/src/widgets/definitions/events/sale.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,21 @@ export type SaleSuccess = {
[key: string]: unknown;
};

/**
* Vendor error from custom authorization or quote webhooks.
* Present when the failure is due to a vendor-specific rejection.
*/
export type VendorError = {
code: string;
message?: string;
};

export type SaleFailedError = {
type?: string;
data?: { vendorError?: VendorError };
[key: string]: unknown;
};

/**
* Type representing a Sale Widget with type FAILURE.
* @property {string} reason
Expand All @@ -45,8 +60,8 @@ export type SaleSuccess = {
export type SaleFailed = {
/** The reason why sale transaction failed. */
reason: string;
/** The error object. */
error: Record<string, unknown>;
/** Error details. Will include vendorError if the failure is due to a vendor-specific rejection. */
error: SaleFailedError;
/** The timestamp of the failed swap. */
timestamp: number;
/** Chosen payment method */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ interface SaleFailView extends ViewType {
data?: {
errorType: SaleErrorTypes;
transactionHash?: string;
vendorError?: { code: string; message?: string };
[key: string]: unknown;
};
}
Expand Down
4 changes: 4 additions & 0 deletions packages/checkout/widgets-lib/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,10 @@
"primaryAction": "Try again",
"secondaryAction": "Cancel"
},
"SALE_AUTHORIZATION_REJECTED": {
"description": "Sorry, your purchase could not be completed.",
"secondaryAction": "Dismiss"
},
"DEFAULT_ERROR": {
"description": "Sorry, something went wrong. Please try again.",
"primaryAction": "Try again",
Expand Down
4 changes: 4 additions & 0 deletions packages/checkout/widgets-lib/src/locales/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,10 @@
"primaryAction": "もう一度試す",
"secondaryAction": "キャンセル"
},
"SALE_AUTHORIZATION_REJECTED": {
"description": "申し訳ございません。購入を完了できませんでした。",
"secondaryAction": "閉じる"
},
"DEFAULT_ERROR": {
"description": "申し訳ありませんが、何かがうまくいかなかったようです。もう一度お試しください。",
"primaryAction": "もう一度試す",
Expand Down
4 changes: 4 additions & 0 deletions packages/checkout/widgets-lib/src/locales/ko.json
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,10 @@
"primaryAction": "다시 시도",
"secondaryAction": "취소"
},
"SALE_AUTHORIZATION_REJECTED": {
"description": "죄송합니다, 구매를 완료할 수 없습니다.",
"secondaryAction": "닫기"
},
"DEFAULT_ERROR": {
"description": "죄송합니다, 문제가 발생했습니다. 다시 시도하세요.",
"primaryAction": "다시 시도",
Expand Down
4 changes: 4 additions & 0 deletions packages/checkout/widgets-lib/src/locales/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,10 @@
"primaryAction": "再试一次",
"secondaryAction": "取消"
},
"SALE_AUTHORIZATION_REJECTED": {
"description": "抱歉,您的购买无法完成。",
"secondaryAction": "关闭"
},
"DEFAULT_ERROR": {
"description": "抱歉,出了点问题。请再试一次。",
"primaryAction": "再试一次",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ export default function SaleWidget(props: SaleWidgetProps) {
biomeTheme={biomeTheme}
errorType={viewState.view.data?.errorType}
transactionHash={viewState.view.data?.transactionHash}
vendorMessage={viewState.view.data?.vendorError?.message}
blockExplorerLink={BlockExplorerService.getTransactionLink(
chainId.current as ChainId,
viewState.view.data?.transactionHash!,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ type SaleContextValues = SaleContextProps & {
paymentMethod?: SalePaymentTypes | undefined,
data?: Record<string, unknown>
) => void;
goToErrorView: (type: SaleErrorTypes, data?: Record<string, string>) => void;
goToErrorView: (type: SaleErrorTypes, data?: Record<string, unknown>) => void;
goToSuccessView: (data?: Record<string, unknown>) => void;
fundingRoutes: FundingRoute[];
disabledPaymentTypes: SalePaymentTypes[];
Expand Down Expand Up @@ -284,14 +284,21 @@ export function SaleContextProvider(props: {
);

const goToErrorView = useCallback(
(errorType: SaleErrorTypes, data: Record<string, string> = {}) => {
(
errorType: SaleErrorTypes,
data: Record<string, unknown> & { vendorError?: { code: string; message?: string } } = {},
) => {
errorRetries.current += 1;
if (errorRetries.current > MAX_ERROR_RETRIES) {
errorRetries.current = 0;
setPaymentMethod(undefined);
}

trackError('commerce', 'saleError', new Error(errorType), data);
const { vendorError, ...errorData } = data;
trackError('commerce', 'saleError', new Error(errorType), {
...errorData,
...(vendorError ? { vendorCode: vendorError.code, vendorMessage: vendorError.message || '' } : {}),
});

viewDispatch({
payload: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const defaultOrderQuote: OrderQuote = {

export type ConfigError = {
type: SaleErrorTypes;
data?: Record<string, string>;
data?: Record<string, unknown>;
};

export const useQuoteOrder = ({
Expand Down Expand Up @@ -93,6 +93,16 @@ export const useQuoteOrder = ({
});

if (!response.ok) {
if (response.status === 400) {
const { code, message } = await response.json();
setOrderQuoteError({
type: SaleErrorTypes.SALE_AUTHORIZATION_REJECTED,
data: {
vendorError: { code: code || '', message: message || undefined },
},
});
return;
}
throw new Error(`${response.status} - ${response.statusText}`);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -291,11 +291,17 @@ export const useSignOrder = (input: SignOrderInput) => {

const { ok, status } = response;
if (!ok) {
const { code } = (await response.json()) as SignApiError;
const { code, message } = (await response.json()) as SignApiError;
let errorType: SaleErrorTypes;
let errorData: { code: string; message: string } | undefined;

switch (status) {
case 400:
errorType = SaleErrorTypes.SERVICE_BREAKDOWN;
errorType = SaleErrorTypes.SALE_AUTHORIZATION_REJECTED;
errorData = {
code,
message,
};
break;
case 404:
if (code === 'insufficient_stock') {
Expand All @@ -312,7 +318,7 @@ export const useSignOrder = (input: SignOrderInput) => {
throw new Error('Unknown error');
}

setSignError({ type: errorType });
setSignError({ type: errorType, data: errorData });
return undefined;
}

Expand Down
5 changes: 4 additions & 1 deletion packages/checkout/widgets-lib/src/widgets/sale/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,9 @@ export type SignOrderInput = {

export type SignOrderError = {
type: SaleErrorTypes;
data?: Record<string, string>;
data?:
| Record<string, string>
| { vendorError: { code: string; message: string } };
};

export type ExecutedTransaction = {
Expand All @@ -85,6 +87,7 @@ export enum SaleErrorTypes {
WALLET_REJECTED_NO_FUNDS = 'WALLET_REJECTED_NO_FUNDS',
WALLET_POPUP_BLOCKED = 'WALLET_POPUP_BLOCKED',
FUNDING_ROUTE_EXECUTE_ERROR = 'FUNDING_ROUTE_EXECUTE_ERROR',
SALE_AUTHORIZATION_REJECTED = 'SALE_AUTHORIZATION_REJECTED',
}

export type OrderQuoteCurrency = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,15 @@ type SaleErrorViewProps = {
errorType: SaleErrorTypes | undefined;
transactionHash?: string;
blockExplorerLink?: string;
vendorMessage?: string;
};

export function SaleErrorView({
biomeTheme,
transactionHash,
blockExplorerLink,
errorType,
vendorMessage,
}: SaleErrorViewProps) {
const { t } = useTranslation();
const {
Expand Down Expand Up @@ -198,6 +200,13 @@ export function SaleErrorView({
onSecondaryActionClick: closeWidget,
statusType: StatusType.INFORMATION,
},
[SaleErrorTypes.SALE_AUTHORIZATION_REJECTED]: {
onSecondaryActionClick: closeWidget,
statusType: StatusType.INFORMATION,
statusIconStyles: {
fill: biomeTheme.color.status.fatal.dim,
},
},
[SaleErrorTypes.INVALID_PARAMETERS]: {
onSecondaryActionClick: closeWidget,
statusType: StatusType.ALERT,
Expand All @@ -222,11 +231,13 @@ export function SaleErrorView({
? t(`views.SALE_FAIL.errors.${currentErrorType}.secondaryAction`)
: t(`views.SALE_FAIL.errors.${SaleErrorTypes.DEFAULT}.secondaryAction`);

const useVendorMessage = currentErrorType === SaleErrorTypes.SALE_AUTHORIZATION_REJECTED
&& vendorMessage;

return {
headingText: t('views.PAYMENT_METHODS.handover.error.heading'),
subheadingText: t(
`views.SALE_FAIL.errors.${currentErrorType}.description`,
),
subheadingText: useVendorMessage
|| t(`views.SALE_FAIL.errors.${currentErrorType}.description`),
primaryButtonText: t(
`views.SALE_FAIL.errors.${currentErrorType}.primaryAction`,
),
Expand Down
Loading