Skip to main content

Accepting Card Payments

Card payments add reach for customers with Visa and Mastercard. This guide covers the charge flow, 3-D Secure authentication, idempotency to prevent double charges, and refunds.

🚀 Quick Start​

curl -X POST https://api.afriroute.ai/api/v1/payments/cards/charge \
-H "Authorization: Bearer $AFRIROUTE_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: charge-ORDER-A1029" \
-d '{
"token": "tok_visa_xxx",
"amount": 4500,
"currency": "NGN",
"reference": "ORDER-A1029",
"callback_url": "https://example.com/webhooks/payments"
}'

Never send raw card numbers to your server — collect them with the client-side SDK, which returns a single-use token.

🔐 3-D Secure​

Many African issuers require 3DS. When a charge needs authentication, the API returns a redirect.

const res = await fetch('https://api.afriroute.ai/api/v1/payments/cards/charge', {
method: 'POST',
headers: {
'Authorization': 'Bearer $AFRIROUTE_API_KEY',
'Content-Type': 'application/json',
'Idempotency-Key': `charge-${orderId}`
},
body: JSON.stringify({ token, amount: 4500, currency: 'NGN', reference: orderId })
});
const data = await res.json();
if (data.status === 'requires_action') {
window.location = data.redirect_url; // bank's 3DS challenge
}

After the customer completes the challenge, the final result arrives via your callback_url.

🔁 Idempotency​

Always send an Idempotency-Key. Network retries with the same key return the original charge instead of charging twice.

import requests
requests.post(
'https://api.afriroute.ai/api/v1/payments/cards/charge',
headers={'Authorization': 'Bearer $AFRIROUTE_API_KEY', 'Idempotency-Key': f'charge-{order_id}'},
json={'token': token, 'amount': 4500, 'currency': 'NGN', 'reference': order_id}
)

â†Šī¸ Refunds​

Refund full or partial amounts by transaction ID.

curl -X POST https://api.afriroute.ai/api/v1/payments/cards/refund \
-H "Authorization: Bearer $AFRIROUTE_API_KEY" \
-H "Idempotency-Key: refund-A1029" \
-d '{ "transaction_id": "txn_abc123", "amount": 4500 }'
FieldRequiredNotes
transaction_idYesThe original charge
amountNoOmit for full refund
reasonNoStored for records

💡 Best Practices​

  • Tokenize on the client — never touch raw PANs (PCI scope).
  • Send an idempotency key on every charge and refund.
  • Handle requires_action for 3DS rather than treating it as failure.
  • Confirm via webhook before fulfilling — don't trust the redirect alone.
  • Verify webhook signatures to prevent spoofed success events.
  • Refund, don't reverse, after settlement.

Last Updated: May 2026