Error Handling
Every failure the SDK throws is a subclass of AIOTError. Each subclass carries a machine-readable code, an HTTP statusCode, and a semantic name so you can branch on instanceof instead of parsing strings.
The hierarchy
AIOTError (base, statusCode = 500 by default)
βββ CheckoutExpiredError (410, code: CHECKOUT_EXPIRED)
βββ PaymentFailedError (402, code: PAYMENT_FAILED)
βββ ShopifyAPIError (502, code: SHOPIFY_API_ERROR)
βββ PriceConversionError (503, code: PRICE_CONVERSION_ERROR)
βββ X402APIError (facilitator status, code: X402_API_ERROR)When to expect each error
| Name | Type | Description |
|---|---|---|
CheckoutExpiredError | 410 | The session TTL elapsed. Create a new checkout; do not retry. |
PaymentFailedError | 402 | Verification or settlement failed. Inspect err.reason; the user may retry. |
ShopifyAPIError | 502 | Shopify admin GraphQL returned user errors. Inspect err.errors for the field[] + message. |
PriceConversionError | 503 | Fiat-to-USDC conversion could not reach the oracle. Usually transient; retry with backoff. |
X402APIError | variable | Facilitator returned a non-2xx. err.responseBody has the raw payload. |
AIOTError | variable | Base class; catches everything the SDK throws deliberately. |
Canonical catch patterns
Completing a payment
import {
AIOTError,
CheckoutExpiredError,
PaymentFailedError
} from '@aiot/shopify-x402'
try {
await client.completePayment(sessionId, { payment, eip7702Auth })
} catch (err) {
if (err instanceof CheckoutExpiredError) {
return res.status(410).json({ error: 'Session expired, please retry' })
}
if (err instanceof PaymentFailedError) {
return res.status(402).json({
error: err.message,
reason: err.reason,
transaction: err.transaction
})
}
if (err instanceof AIOTError) {
return res.status(err.statusCode).json({
error: err.message,
code: err.code
})
}
throw err // let your outer error handler log it
}HTTP status mapping
The Express middleware does this mapping for you via a shared handleError helper. If you roll your own framework integration, the statusCode-on-error-class pattern makes it trivial to preserve:
res.status(err instanceof AIOTError ? err.statusCode : 500).json({
error: err.message,
code: err instanceof AIOTError ? err.code : 'INTERNAL_ERROR'
})Logging vs. responding
Warning
Donβt log PaymentFailedError at error level β itβs a user-visible state,
not a system failure. Log it at info or warn, along with the session id
and reason, so you can triage without drowning on-call during normal
refund activity.
Rules of thumb:
PaymentFailedError,CheckoutExpiredErrorβ info/warn, include session idShopifyAPIError,X402APIErrorβ error, include operation + payloadPriceConversionErrorβ warn + retry with backoff- Anything else β error + pager
Related
- SDK client β where these are thrown
- Express middleware β default HTTP mapping
- API Reference β Errors