Result Codes
Understanding payment result codes and how to handle each outcome
Result Codes
When you make a payment request, Plexy returns a resultCode that indicates the outcome. Understanding these codes is essential for providing a good customer experience and correctly handling payment states.
Quick Reference
| Result Code | Meaning | Action Required |
|---|---|---|
Authorised | Payment successful | Fulfill the order |
Refused | Payment declined | Show decline message |
Pending | Awaiting final result | Wait for webhook |
Received | Payment initiated | Wait for webhook |
Error | Technical error | Retry or investigate |
Cancelled | Customer cancelled | Return to checkout |
RedirectShopper | Redirect required | Redirect the customer |
IdentifyShopper | 3DS device fingerprint | Perform 3DS fingerprint |
ChallengeShopper | 3DS challenge required | Show 3DS challenge |
PresentToShopper | Show voucher/QR | Display payment info |
Successful Payment
Authorised
The payment was successfully authorized. The funds have been reserved on the customer's account (or captured, depending on your configuration).
What to do:
- Mark the order as paid
- Trigger order fulfillment (shipping, access, etc.)
- Send confirmation email to customer
if (payment.resultCode === 'Authorised') {
await db.orders.update({
where: { id: orderId },
data: {
status: 'paid',
paymentId: payment.pspReference
}
});
await fulfillOrder(orderId);
await sendConfirmationEmail(orderId);
}If you have separate authorization and capture, the payment is only reserved. You'll need to capture it later to receive the funds.
Declined Payments
Refused
The payment was declined by the card issuer, payment provider, or Plexy's risk system.
What to do:
- Display a customer-friendly message
- Suggest trying a different payment method
- Do not store the card details for retry
if (payment.resultCode === 'Refused') {
const customerMessage = getRefusalMessage(payment.refusalReasonCode);
return {
status: 'refused',
message: customerMessage,
// Log the full reason for debugging
internalReason: payment.refusalReason
};
}
function getRefusalMessage(refusalCode) {
const messages = {
'2': 'The transaction was declined. Please try a different payment method.',
'3': 'Invalid card details. Please check and try again.',
'4': 'Invalid PIN. Please try again.',
'5': 'Card expired. Please use a different card.',
'6': 'Invalid CVV. Please check and try again.',
'7': 'Insufficient funds. Please try a different card.',
'8': 'Card blocked. Please contact your bank.',
'20': 'Fraud detected. Transaction declined.',
};
return messages[refusalCode] || 'Payment declined. Please try a different payment method.';
}Common Refusal Reason Codes
| Code | Reason | Customer Message |
|---|---|---|
2 | Refused | Try a different payment method |
3 | Referral | Contact bank for approval |
4 | Acquirer Error | Technical issue, retry later |
5 | Blocked Card | Card is blocked by issuer |
6 | Expired Card | Card has expired |
7 | Invalid Amount | Amount issue, contact support |
8 | Invalid Card Number | Check card number |
9 | Issuer Unavailable | Bank unavailable, retry later |
10 | Not Supported | Card type not accepted |
11 | 3D Secure Failed | Authentication failed |
12 | Not Enough Balance | Insufficient funds |
14 | Fraud | Payment declined |
15 | Cancelled | Customer cancelled |
16 | Shopper Cancelled | Customer cancelled |
17 | Invalid Pin | Incorrect PIN |
20 | Fraud - Risk | Declined by risk system |
21 | Not Submitted | Processing error |
22 | Fraud - CVV | CVV check failed |
23 | Transaction Not Permitted | Not allowed for this card |
24 | CVC Declined | CVV check failed |
25 | Restricted Card | Card restricted |
26 | Revocation | Authorization revoked |
27 | Declined Non-Generic | Contact bank |
28 | Withdrawal Amount Exceeded | Over withdrawal limit |
29 | Withdrawal Count Exceeded | Too many withdrawals |
31 | Fraud - Raw | Fraud detected |
32 | AVS Declined | Address verification failed |
33 | Card requires online PIN | Online PIN required |
34 | No checking account | Account not found |
35 | No savings account | Account not found |
36 | Mobile PIN required | PIN required |
37 | Contactless fallback | Use chip instead |
38 | Authentication required | Additional auth needed |
Cancelled
The customer cancelled the payment before completion. This typically happens when:
- Customer clicks "back" or closes the payment page
- Customer cancels on a redirect page (e.g., bank authentication)
- Session times out
What to do:
- Return customer to the cart/checkout page
- Keep the order in "pending" state
- Allow retry with same or different payment method
if (payment.resultCode === 'Cancelled') {
return {
status: 'cancelled',
message: 'Payment was cancelled. Would you like to try again?',
redirectTo: '/checkout'
};
}Error
A technical error occurred during payment processing. This could be due to:
- Network issues
- Service unavailability
- Invalid request data
- Configuration issues
What to do:
- Log the error for investigation
- Display a generic error message
- Offer retry option
- Alert your team if errors persist
if (payment.resultCode === 'Error') {
console.error('Payment error:', {
pspReference: payment.pspReference,
errorCode: payment.errorCode,
message: payment.errorMessage
});
// Alert if this is a recurring issue
if (await isRecurringError(payment.errorCode)) {
await alertTeam('Recurring payment error', payment);
}
return {
status: 'error',
message: 'Something went wrong. Please try again or use a different payment method.',
canRetry: true
};
}Pending States
Pending
The payment result is not yet known. This is common for:
- Bank transfers
- Asynchronous payment methods
- Manual review
What to do:
- Mark the order as "awaiting payment"
- Wait for webhook notification with final result
- Show appropriate status to customer
if (payment.resultCode === 'Pending') {
await db.orders.update({
where: { id: orderId },
data: {
status: 'awaiting_payment',
paymentId: payment.pspReference
}
});
return {
status: 'pending',
message: 'Your payment is being processed. We\'ll notify you once it\'s confirmed.'
};
}Never fulfill orders with Pending status until you receive a webhook confirming the payment was successful.
Received
Similar to Pending, but specifically indicates that Plexy has received the payment request. The final result will come via webhook.
What to do:
- Same as
Pending - Common for payment methods like Boleto, iDEAL, or bank transfers
if (payment.resultCode === 'Received') {
await db.orders.update({
where: { id: orderId },
data: { status: 'payment_initiated' }
});
return {
status: 'processing',
message: 'Payment initiated. You\'ll receive confirmation shortly.'
};
}Action Required
These result codes indicate that additional steps are needed to complete the payment.
RedirectShopper
The customer must be redirected to an external page to complete authentication or authorization (e.g., bank login, payment app).
What to do:
- Store relevant state (order ID) for when customer returns
- Redirect to the URL in the action object
- Handle the return URL callback
if (payment.resultCode === 'RedirectShopper') {
const { action } = payment;
// Store session data
await db.paymentSessions.create({
data: {
orderId: orderId,
paymentData: action.paymentData,
createdAt: new Date()
}
});
return {
status: 'redirect',
url: action.url,
method: action.method,
data: action.data // For POST redirects
};
}IdentifyShopper
3D Secure 2 device fingerprinting is required. This is the first step of 3DS2 authentication.
What to do:
- Use the Plexy 3DS SDK to perform fingerprinting
- Submit the result to
/payments/details - Handle the next result code
if (payment.resultCode === 'IdentifyShopper') {
return {
status: '3ds_fingerprint',
action: payment.action
};
}
// Client-side handling
const threeDS = new Plexy3DS({ clientKey: 'YOUR_CLIENT_KEY' });
threeDS.handleAction(action);ChallengeShopper
3D Secure 2 challenge is required. The customer needs to authenticate with their bank (e.g., enter OTP, approve in banking app).
What to do:
- Display the 3DS challenge UI
- Submit the challenge result to
/payments/details
if (payment.resultCode === 'ChallengeShopper') {
return {
status: '3ds_challenge',
action: payment.action
};
}
// Client-side: Show the challenge
const threeDS = new Plexy3DS({ clientKey: 'YOUR_CLIENT_KEY' });
const challengeComponent = threeDS.create('threeDS2Challenge', {
challengeWindowSize: '05',
onComplete: (result) => submitDetails(result.data),
onError: (error) => handleError(error)
});
challengeComponent.handleAction(action);PresentToShopper
Payment information needs to be shown to the customer (e.g., voucher, QR code, bank transfer details).
What to do:
- Display the provided payment information
- Wait for webhook notification of payment completion
- Some methods have expiration times
if (payment.resultCode === 'PresentToShopper') {
const { action } = payment;
return {
status: 'show_voucher',
data: {
type: action.paymentMethodType,
reference: action.reference,
expiresAt: action.expiresAt,
downloadUrl: action.downloadUrl,
// For QR codes
qrCodeData: action.qrCodeData,
// For bank transfers
bankDetails: {
iban: action.iban,
bic: action.bic,
accountHolder: action.accountHolder
}
}
};
}Comprehensive Result Handler
Here's a complete example handling all result codes:
async function handlePaymentResult(payment, orderId) {
const { resultCode } = payment;
switch (resultCode) {
// Success
case 'Authorised':
await updateOrderStatus(orderId, 'paid');
await fulfillOrder(orderId);
return { status: 'success', redirect: '/order/confirmed' };
// Declined
case 'Refused':
await updateOrderStatus(orderId, 'payment_failed');
return {
status: 'refused',
message: getRefusalMessage(payment.refusalReasonCode),
redirect: '/checkout?error=refused'
};
case 'Cancelled':
return { status: 'cancelled', redirect: '/checkout' };
case 'Error':
logError(payment);
return {
status: 'error',
message: 'Technical error. Please try again.',
redirect: '/checkout?error=technical'
};
// Pending
case 'Pending':
case 'Received':
await updateOrderStatus(orderId, 'awaiting_payment');
return {
status: 'pending',
message: 'Payment is being processed...',
redirect: '/order/pending'
};
// Action required
case 'RedirectShopper':
await savePaymentData(orderId, payment.action.paymentData);
return { status: 'redirect', action: payment.action };
case 'IdentifyShopper':
return { status: '3ds_fingerprint', action: payment.action };
case 'ChallengeShopper':
return { status: '3ds_challenge', action: payment.action };
case 'PresentToShopper':
await updateOrderStatus(orderId, 'awaiting_payment');
return { status: 'voucher', action: payment.action };
default:
console.warn('Unknown result code:', resultCode);
return { status: 'unknown', resultCode };
}
}async def handle_payment_result(payment, order_id: str) -> dict:
result_code = payment.result_code
# Success
if result_code == 'Authorised':
await update_order_status(order_id, 'paid')
await fulfill_order(order_id)
return {'status': 'success', 'redirect': '/order/confirmed'}
# Declined
if result_code == 'Refused':
await update_order_status(order_id, 'payment_failed')
return {
'status': 'refused',
'message': get_refusal_message(payment.refusal_reason_code),
'redirect': '/checkout?error=refused'
}
if result_code == 'Cancelled':
return {'status': 'cancelled', 'redirect': '/checkout'}
if result_code == 'Error':
log_error(payment)
return {
'status': 'error',
'message': 'Technical error. Please try again.',
'redirect': '/checkout?error=technical'
}
# Pending
if result_code in ['Pending', 'Received']:
await update_order_status(order_id, 'awaiting_payment')
return {
'status': 'pending',
'message': 'Payment is being processed...',
'redirect': '/order/pending'
}
# Action required
if result_code == 'RedirectShopper':
await save_payment_data(order_id, payment.action['paymentData'])
return {'status': 'redirect', 'action': payment.action}
if result_code == 'IdentifyShopper':
return {'status': '3ds_fingerprint', 'action': payment.action}
if result_code == 'ChallengeShopper':
return {'status': '3ds_challenge', 'action': payment.action}
if result_code == 'PresentToShopper':
await update_order_status(order_id, 'awaiting_payment')
return {'status': 'voucher', 'action': payment.action}
# Unknown
print(f'Unknown result code: {result_code}')
return {'status': 'unknown', 'resultCode': result_code}Webhook Event Types
Each result code maps to webhook events you should handle:
| Result Code | Webhook Event |
|---|---|
Authorised | payment.authorised |
Refused | payment.refused |
Cancelled | payment.cancelled |
Error | payment.error |
Pending → Success | payment.authorised |
Pending → Failure | payment.refused or payment.expired |
Received → Success | payment.authorised |
Received → Failure | payment.refused or payment.expired |
Best Practices
-
Always verify via webhooks - Don't rely solely on the synchronous response. Webhooks provide the authoritative payment status.
-
Map to your order states - Create a clear mapping between Plexy result codes and your internal order states.
-
Log all results - Keep detailed logs of payment results for debugging and support.
-
Show appropriate messages - Use customer-friendly messages for declines. Avoid exposing raw error codes.
-
Handle edge cases - Plan for network failures, timeouts, and unexpected result codes.
-
Test all scenarios - Use test cards to simulate different result codes in test mode.
Next Steps
- Handle 3D Secure authentication flows
- Set up webhooks for reliable notifications
- Test your integration with test cards