Understand Rebell’s structured error model and implement safe recovery logic across all payment flows.
Rebell APIs use a structured error model that clearly separates transport-level errors, business-level outcomes, and payment lifecycle states.You must always interpret both the HTTP status code and the business result object contained in the response body.
A request may return HTTP 200 OK while still representing a business failure. Always check the result object.
async function handleRetailPayResponse(response) { switch (response.result.resultStatus) { case 'S': // Payment successful return completeTransaction(response.paymentId); case 'U': // Processing - start polling return pollPaymentStatus(response.paymentId); case 'F': // Failed - handle specific error switch (response.result.resultCode) { case 'INVALID_CODE': return showError('Please refresh your payment code and try again'); case 'USER_REJECTED': return showError('Payment was cancelled'); case 'INSUFFICIENT_BALANCE': return showError('Insufficient balance. Please top up and try again'); default: return showError('Payment failed. Please try another payment method'); } }}
Error handling for QR Order Pay (user scans merchant QR):
Result
Action
SUCCESS
Wait for webhook and fulfill order
QR_EXPIRED
Generate a new QR code
USER_REJECTED
Keep order open, allow retry
PAYMENT_FAIL
Allow retry with new QR
ORDER_STATUS_INVALID
Create new order
QR Order Pay Error Handling
Copy
async function handleQROrderResponse(response) { if (response.result.resultStatus === 'S') { // QR generated successfully - display it return displayQRCode(response.qrCode); } // Handle errors switch (response.result.resultCode) { case 'ORDER_STATUS_INVALID': return createNewOrder(); case 'PARAM_ILLEGAL': console.error('Invalid request parameters'); return showError('Unable to generate QR code'); default: return showError('Failed to create payment. Please try again'); }}
Error handling for Link Pay (app-to-app redirect):
Result
Action
SUCCESS
Redirect user, fulfill order after webhook
PROCESSING
Wait for webhook or use Inquiry
ORDER_STATUS_INVALID
Generate a new payment link
PAYMENT_FAIL
Restart checkout
PAYMENT_IN_PROCESS
Check existing payment status
Link Pay Error Handling
Copy
async function handleLinkPayResponse(response) { if (response.result.resultStatus === 'S') { // Redirect user to Rebell return redirectToPayment(response.redirectUrl, response.appLinks); } switch (response.result.resultCode) { case 'ORDER_STATUS_INVALID': return generateNewPaymentLink(); case 'PAYMENT_IN_PROCESS': return checkExistingPayment(); default: return showError('Unable to initiate payment. Please try again'); }}
Retries must be handled carefully to avoid duplicate payments.
Safe to Retry
Never Retry
These scenarios are safe to retry:
Network timeouts (no response received)
HTTP 5xx errors (server-side issues)
Same request with the same paymentRequestId
Connection failures before response
TLS/SSL errors
Safe Retry Logic
Copy
async function safeRetry(paymentRequest, maxRetries = 3) { for (let attempt = 1; attempt <= maxRetries; attempt++) { try { const response = await callPaymentAPI(paymentRequest); // Got a response - don't retry regardless of business result return response; } catch (error) { if (isNetworkError(error) || isServerError(error)) { // Safe to retry with same paymentRequestId console.log(`Attempt ${attempt} failed, retrying...`); await delay(1000 * attempt); // Exponential backoff continue; } throw error; // Don't retry other errors } } throw new Error('Max retries exceeded');}
Never Retry These
Copy
function shouldRetry(response, error) { // Never retry business failures if (response?.result?.resultStatus === 'F') { return false; } // Never retry after success if (response?.result?.resultStatus === 'S') { return false; } // Never retry 4xx errors if (error?.status >= 400 && error?.status < 500) { return false; } return true;}
Critical: The paymentRequestId acts as the idempotency key and must be unique per payment attempt. Never generate a new paymentRequestId when retrying the same payment.