Retry a failed payment

Payment failures happen—networks timeout, customers enter wrong PINs, or mobile money accounts have insufficient balance. Instead of losing the sale, give your customers an easy way to retry. This guide shows you three strategies for recovering from failed payments, each optimized for different scenarios.


How payment retries work

When a payment fails, the order remains in the system with all its details intact. You use the /orders/pay endpoint to retry payment without creating a new order. This preserves the order ID, line items, and customer information while giving you flexibility in how to process the payment. You can retry with the same payment method (network glitch scenarios), switch to a different saved payment method (customer preference), or collect entirely new payment details (expired card, wrong account). Each retry triggers a fresh OTP and payment confirmation flow.


Scenario 1: Retry with the current payment method

Use this approach when the failure was likely temporary—a network timeout, momentary connectivity issue, or the customer accidentally dismissed the OTP prompt. The payment method itself is still valid; it just needs another attempt.

When you call /orders/pay with only the order_id, Commerce automatically retries the payment using the payment method already attached to the order. This is the simplest retry flow and works great for transient failures.

Retry with current method

POST
/orders/pay
curl https://api.zebo.dev/orders/pay \
  -H "Authorization: Bearer YOUR_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "order_id": "ord_failed_123"
  }'
  • Name
    order_id
    Type
    string
    Description

    The ID of the order with the failed payment. Find this in your original order creation response or webhook payload.

The response includes a new OTP that was sent to the customer's phone. Present your OTP confirmation UI and call /orders/confirm_payment when the customer enters the code. The payment flow is identical to the original attempt—only the payment attempt changes.


Scenario 2: Retry with a different saved payment method

Use this when the customer wants to switch to a different payment method they've previously used. Maybe their primary mobile money account has insufficient funds, or they prefer to use a different network this time. You'll pass the payment_method_id of an alternative saved method.

This only works for returning customers who have multiple saved payment methods. You can get available payment method IDs from the customer's previous orders or by calling the customer endpoint (if you've implemented customer management).

Retry with saved method

POST
/orders/pay
curl https://api.zebo.dev/orders/pay \
  -H "Authorization: Bearer YOUR_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "order_id": "ord_failed_123",
    "payment_method_id": "pm_alternative_method"
  }'
  • Name
    order_id
    Type
    string
    Description

    The ID of the order with the failed payment.

  • Name
    payment_method_id
    Type
    string
    Description

    The ID of an alternative saved payment method to use for this retry. The payment method must belong to the same customer as the original order.


Scenario 3: Retry with new payment details

Use this when the customer needs to provide completely new payment information—their old card expired, they closed their mobile money account, or they want to pay with a method they've never used before. You'll pass full payment details via payment_method_data, just like creating a new order.

This is the most flexible retry option. It works whether the customer is new or returning, and it automatically saves the new payment method for future use.

Retry with new details

POST
/orders/pay
curl https://api.zebo.dev/orders/pay \
  -H "Authorization: Bearer YOUR_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "order_id": "ord_failed_123",
    "payment_method_data": {
      "type": "mobile_money",
      "mobile_money": {
        "issuer": "vodafone",
        "number": "0209876543"
      }
    }
  }'
  • Name
    order_id
    Type
    string
    Description

    The ID of the order with the failed payment.

  • Name
    payment_method_data
    Type
    object
    Description

    Complete payment method details. Structured exactly like the payment_method_data you'd provide when creating a new order.

    • Name
      type
      Type
      string
      Description

      Payment method type. Currently only mobile_money is supported.

    • Name
      mobile_money
      Type
      object
      Description

      Mobile money payment details (required when type is mobile_money).

      • Name
        issuer
        Type
        string
        Description

        Mobile money provider: mtn, vodafone, or airteltigo.

      • Name
        number
        Type
        string
        Description

        Mobile money account number (typically a phone number). Should match the number that will receive the OTP.


Confirming the retry

Regardless of which retry method you use, the payment confirmation flow is identical. After calling /orders/pay, Commerce sends a 6-digit OTP to the customer's phone. Collect this code through your UI and call /orders/confirm_payment to complete the payment.

// After any retry method
async function confirmRetry(orderId: string, token: string) {
  const result = await commerce.orders.confirmPayment({
    order_id: orderId,
    token: token
  })
  
  if (result.order.status === 'completed') {
    console.log('Payment retry succeeded!')
  }
  
  return result
}

See the Accept a payment guide for detailed information on the confirmation flow, including handling timeouts, resending OTPs, and processing webhooks.


Best practices

Let customers choose their retry strategy

Don't automatically retry with the same method. Show a failure message that explains what happened and give customers options: "Try again with this account", "Use a different saved account", or "Pay with a new account". This respects customer preference and improves success rates.

Handle common failure patterns

Track why payments fail and adjust your retry UI accordingly. If the failure was insufficient_funds, suggest an alternative payment method immediately. If it was network_timeout, auto-retry once with the same method before asking the customer. If it was invalid_pin, explain that they need to enter the correct PIN—retrying won't help until they fix their input.

Preserve order context

When retrying, show the customer what they're paying for. Display the order summary, line items, and total prominently so they can verify everything before confirming the OTP. This prevents confusion about duplicate charges.

Set retry limits

Don't allow unlimited retries. After 3-5 failed attempts, pause and suggest the customer contact their mobile money provider or try a different payment method entirely. This prevents fraud patterns and protects against brute-force attacks.

Monitor retry success rates

Track which retry strategies work best for your customers. If retries with saved methods succeed more often than new payment details, optimize your UI to suggest saved methods first. Use this data to improve your payment flow over time.


Was this page helpful?