Skip to content

Commit b02b4a5

Browse files
authored
fix(checkout-widgets): Ensure error view is shown when sale processing fails (#2805)
1 parent 5c69eda commit b02b4a5

File tree

5 files changed

+63
-14
lines changed

5 files changed

+63
-14
lines changed

packages/checkout/widgets-lib/src/widgets/sale/context/SaleContextProvider.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,9 @@ export function SaleContextProvider(props: {
300300
...(vendorError ? { vendorCode: vendorError.code, vendorMessage: vendorError.message || '' } : {}),
301301
});
302302

303+
// eslint-disable-next-line no-console
304+
console.error('[IMTBL]: Sale error', errorType, data);
305+
303306
viewDispatch({
304307
payload: {
305308
type: ViewActions.UPDATE_VIEW,

packages/checkout/widgets-lib/src/widgets/sale/functions/fetchFundingBalances.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ export const fetchFundingBalances = async (
4747
onFundingBalance,
4848
getAmountByCurrency,
4949
getIsGasless,
50-
onComplete,
5150
onFundingRequirement,
5251
onUpdateGasFees,
5352
} = params;
@@ -85,10 +84,6 @@ export const fetchFundingBalances = async (
8584
? undefined
8685
: getGasEstimate();
8786

88-
const handleOnComplete = () => {
89-
onComplete?.(pushToFoundBalances([]));
90-
};
91-
9287
const handleOnFundingRoute = (route) => {
9388
updateFundingBalances(getAlternativeFundingSteps([route], environment));
9489
};
@@ -99,9 +94,6 @@ export const fetchFundingBalances = async (
9994
transactionOrGasAmount,
10095
routingOptions: { bridge: false, onRamp: false, swap: true },
10196
fundingRouteFullAmount: true,
102-
onComplete: isBaseCurrency(currency.name)
103-
? handleOnComplete
104-
: undefined,
10597
onFundingRoute: isBaseCurrency(currency.name)
10698
? handleOnFundingRoute
10799
: undefined,

packages/checkout/widgets-lib/src/widgets/sale/hooks/useFundingBalances.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export const useFundingBalances = () => {
2525
>([]);
2626
const [loadingBalances, setLoadingBalances] = useState(false);
2727
const [gasFees, setGasFees] = useState<TokenBalance | undefined>();
28+
const [fundingBalancesError, setFundingBalancesError] = useState<Error | null>(null);
2829

2930
const queryFundingBalances = () => {
3031
if (
@@ -40,6 +41,7 @@ export const useFundingBalances = () => {
4041
(async () => {
4142
fetching.current = true;
4243
setLoadingBalances(true);
44+
setFundingBalancesError(null);
4345
try {
4446
const results = await fetchFundingBalances({
4547
provider,
@@ -55,9 +57,6 @@ export const useFundingBalances = () => {
5557
onFundingBalance: (foundBalances) => {
5658
setFundingBalances([...foundBalances]);
5759
},
58-
onComplete: () => {
59-
setLoadingBalances(false);
60-
},
6160
onFundingRequirement: (requirement) => {
6261
setTransactionRequirement(requirement);
6362
},
@@ -67,9 +66,10 @@ export const useFundingBalances = () => {
6766
});
6867

6968
setFundingBalancesResult(results);
70-
} catch {
71-
setLoadingBalances(false);
69+
} catch (err) {
70+
setFundingBalancesError(err instanceof Error ? err : new Error(String(err)));
7271
} finally {
72+
setLoadingBalances(false);
7373
fetching.current = false;
7474
}
7575
})();
@@ -79,6 +79,7 @@ export const useFundingBalances = () => {
7979
fundingBalances,
8080
loadingBalances,
8181
fundingBalancesResult,
82+
fundingBalancesError,
8283
transactionRequirement,
8384
gasFees,
8485
queryFundingBalances,

packages/checkout/widgets-lib/src/widgets/sale/hooks/useQuoteOrder.ts

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,35 @@ export type ConfigError = {
2929
data?: Record<string, unknown>;
3030
};
3131

32+
/**
33+
* Validates the order quote response before use. Ensures:
34+
* - Currencies are non-empty (empty usually indicates wrong project config or endpoint).
35+
* - Products are non-empty.
36+
* - Every sale item has a matching product in the quote (no missing productIds).
37+
*/
38+
function validateOrderQuote(
39+
config: OrderQuote,
40+
items: SaleItem[],
41+
): { valid: true } | { valid: false; reason: string } {
42+
if (!config.currencies?.length) {
43+
return { valid: false, reason: 'Quote returned no currencies' };
44+
}
45+
46+
const productIds = Object.keys(config.products || {});
47+
if (productIds.length === 0) {
48+
return { valid: false, reason: 'Quote returned no products' };
49+
}
50+
51+
const missing = items.filter((item) => !config.products![item.productId]);
52+
if (missing.length > 0) {
53+
return {
54+
valid: false,
55+
reason: `Quote missing products for: ${missing.map((m) => m.productId).join(', ')}`,
56+
};
57+
}
58+
return { valid: true };
59+
}
60+
3261
export const useQuoteOrder = ({
3362
items,
3463
environment,
@@ -53,6 +82,13 @@ export const useQuoteOrder = ({
5382
});
5483
};
5584

85+
const setQuoteValidationError = (reason: string) => {
86+
setOrderQuoteError({
87+
type: SaleErrorTypes.SERVICE_BREAKDOWN,
88+
data: { reason: 'Invalid order quote response', error: reason },
89+
});
90+
};
91+
5692
useEffect(() => {
5793
// Set request params
5894
if (!items?.length || !provider) return;
@@ -110,14 +146,21 @@ export const useQuoteOrder = ({
110146
await response.json(),
111147
preferredCurrency,
112148
);
149+
150+
const validation = validateOrderQuote(config, items);
151+
if (!validation.valid) {
152+
setQuoteValidationError(validation.reason);
153+
return;
154+
}
155+
113156
setOrderQuote(config);
114157
} catch (error) {
115158
setError(errorToString(error));
116159
} finally {
117160
fetching.current = false;
118161
}
119162
})();
120-
}, [environment, environmentId, queryParams]);
163+
}, [environment, environmentId, queryParams, items]);
121164

122165
useEffect(() => {
123166
// Set default currency

packages/checkout/widgets-lib/src/widgets/sale/views/OrderSummary.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,11 +185,21 @@ export function OrderSummary({ subView }: OrderSummaryProps) {
185185
fundingBalances,
186186
loadingBalances,
187187
fundingBalancesResult,
188+
fundingBalancesError,
188189
transactionRequirement,
189190
gasFees,
190191
queryFundingBalances,
191192
} = useFundingBalances();
192193

194+
// If funding balances failed to load, transition to error view
195+
useEffect(() => {
196+
if (!fundingBalancesError) return;
197+
closeHandover();
198+
goToErrorView(SaleErrorTypes.SERVICE_BREAKDOWN, {
199+
error: errorToString(fundingBalancesError),
200+
});
201+
}, [fundingBalancesError, goToErrorView, closeHandover]);
202+
193203
// Initialise funding balances
194204
useEffect(() => {
195205
if (subView !== OrderSummarySubViews.INIT || !fromTokenAddress) return;

0 commit comments

Comments
 (0)