Skip to main content

Overview

The recurring payments feature allows you to securely save customer card information and charge subsequent payments without requiring card input. Perfect for subscriptions and recurring billing.
Recurring payments are only available for certain connectors. Please contact us for availability.

Recurring Payment Flow

Initial Payment (Save Card)

Save the card by specifying save_card: true and customer_id in the initial payment.

Request

curl -X POST https://api.sandbox.zafapay.com/v1/payments \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 10.00,
    "currency": "usd",
    "customer_id": "cust_abc123",
    "save_card": true,
    "external_id": "subscription_initial_001",
    "metadata": {
      "plan": "premium"
    }
  }'
customer_id
string
required
A unique ID to identify the customer. Use your internal user ID.
save_card
boolean
required
Set to true for the initial recurring payment. The card will be saved after payment completion.

Response

{
  "id": "req_abc123",
  "status": "pending",
  "amount": 10.00,
  "currency": "usd",
  "payment_type": "initial",
  "payment_url": "https://pay.sandbox.zafapay.com/checkout/req_abc123?token=xxx",
  "created_at": "2025-01-01T00:00:00.000Z"
}
payment_type: "initial" indicates this is an initial payment with card saving. The payment_method_id will be provided in the webhook after payment completion.
Redirect the customer to payment_url to complete the payment.

Webhook on Payment Completion

{
  "event": "payment.succeeded",
  "transaction_id": "tx_abc123",
  "status": "succeeded",
  "amount": 10.00,
  "currency": "usd",
  "payment_method_id": "pmi_xyz789",
  "save_card": true,
  "is_recurring": false,
  "customer_id": "cust_abc123",
  ...
}
Save the payment_method_id (pmi_xxx format) for subsequent payments.

List Saved Cards

Retrieve the customer’s saved cards.
curl https://api.sandbox.zafapay.com/v1/customers/cust_abc123/payment-methods \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Response

{
  "customer_id": "cust_abc123",
  "payment_methods": [
    {
      "id": "pmi_xyz789",
      "type": "card",
      "connector_id": "conn_xxx",
      "card": {
        "brand": "visa",
        "last4": "4242",
        "exp_month": 12,
        "exp_year": 2028
      },
      "is_default": true,
      "status": "active",
      "created_at": "2025-01-01T00:00:00Z"
    }
  ]
}

Execute Recurring Payment

Use the saved payment_method_id to charge without requiring card input.

Request

curl -X POST https://api.sandbox.zafapay.com/v1/payments \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 10.00,
    "currency": "usd",
    "customer_id": "cust_abc123",
    "payment_method_id": "pmi_xyz789",
    "external_id": "subscription_renewal_002",
    "metadata": {
      "plan": "premium",
      "billing_cycle": 2
    }
  }'
payment_method_id
string
required
The saved payment method ID (pmi_xxx format)

Response

Recurring payments are processed instantly (no payment_url is returned).
{
  "id": "req_def456",
  "transaction_id": "tx_def456",
  "status": "succeeded",
  "amount": 10.00,
  "currency": "usd",
  "payment_type": "recurring",
  "created_at": "2025-02-01T00:00:00.000Z"
}
payment_type: "recurring" indicates this is a recurring payment using a saved card.

Webhook on Payment Completion

{
  "event": "payment.succeeded",
  "transaction_id": "tx_def456",
  "status": "succeeded",
  "amount": 10.00,
  "currency": "usd",
  "payment_method_id": "pmi_xyz789",
  "is_recurring": true,
  "customer_id": "cust_abc123",
  ...
}
Recurring payment webhooks include is_recurring: true.

Managing Payment Methods

Block a Card

Block a card if fraud is suspected.
curl -X POST https://api.sandbox.zafapay.com/v1/customers/payment-methods/pmi_xyz789/block \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "reason": "Suspected fraud"
  }'

Unblock a Card

curl -X POST https://api.sandbox.zafapay.com/v1/customers/payment-methods/pmi_xyz789/unblock \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Delete a Card (Detach)

Use detach to permanently delete card information upon customer request.
curl -X POST https://api.sandbox.zafapay.com/v1/customers/payment-methods/pmi_xyz789/detach \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Response

{
  "id": "pmi_xyz789",
  "deleted": true
}
Deleted cards cannot be restored. To use again, the customer must re-register their card.
Block vs Delete:
  • Block: Temporarily disable (fraud prevention). Can be unblocked later
  • Delete (Detach): Permanently remove (GDPR compliance, customer deletion requests). Cannot be restored

Error Handling

Recurring Not Supported

{
  "error": {
    "type": "invalid_request_error",
    "code": "recurring_not_supported",
    "message": "Recurring payments are not supported for this connector",
    "request_id": "req_xxx"
  }
}

Card Expired

{
  "error": {
    "type": "payment_error",
    "code": "expired_card",
    "message": "The card has expired",
    "request_id": "req_xxx"
  }
}
If the card has expired or been declined by the issuer, the customer must register a new card.

Best Practices

1

Manage customer_id uniquely

Map your internal user ID 1:1 with customer_id. Using different customer_id values for the same customer will scatter their card information.
2

Persist payment_method_id

Store the payment_method_id received in the webhook to your database for subsequent payments.
3

Implement retry strategy

Card payments can fail due to temporary issues. Use exponential backoff for retries, and notify the customer after multiple failures.
4

Monitor expiration dates

Track saved card expiration dates and prompt customers to update their cards before expiration.