> ## Documentation Index
> Fetch the complete documentation index at: https://docs.zafapay.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Recurring Payments

> Save card information for recurring billing

## 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.

<Note>
  Recurring payments are only available for certain gateways. Please contact us for availability.
</Note>

<Tip>
  If you use your own payment form with [Server-to-Server (S2S) payments](/en/guide/s2s-payments#save-card-for-recurring-payments), you can also save cards by passing `token`, `save_card: true`, and `customer_id` together. See the S2S guide for details.
</Tip>

## Recurring Payment Flow

```mermaid theme={null}
sequenceDiagram
    participant M as Merchant
    participant Z as ZAFA PAY
    participant C as Customer

    Note over M,C: Initial Payment (Save Card)
    M->>Z: POST /v1/payments (save_card=true)
    Z-->>M: payment_url
    M->>C: Redirect
    C->>Z: Enter card & pay
    Z-)M: Webhook (with payment_method_id)

    Note over M,C: Subsequent Payments
    M->>Z: POST /v1/payments (payment_method_id)
    Z-->>M: Payment result (instant)
    Z-)M: Webhook (is_recurring=true)
```

## Initial Payment (Save Card)

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

### Request

```bash theme={null}
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"
    }
  }'
```

<ParamField body="customer_id" type="string" required>
  A unique ID to identify the customer. Use your internal user ID.
</ParamField>

<ParamField body="save_card" type="boolean" required>
  Set to `true` for the initial recurring payment. The card will be saved after payment completion.
</ParamField>

### Response

```json theme={null}
{
  "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"
}
```

<Note>
  `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.
</Note>

Redirect the customer to `payment_url` to complete the payment.

### Webhook on Payment Completion

```json theme={null}
{
  "event": "payment.succeeded",
  "transaction_id": "tx_abc123",
  "status": "succeeded",
  "amount": "10.00",
  "currency": "usd",
  "external_id": "subscription_initial_001",
  "payment_method": "card",
  "payment_method_id": "pmi_xyz789",
  "save_card": true,
  "is_recurring": false,
  "customer_id": "cust_abc123",
  "card_brand": "visa",
  "card_last4": "4242",
  "card_country": "US",
  "card_funding": "credit",
  "metadata": { "plan": "premium" },
  "created_at": "2025-01-01T00:00:00.000Z",
  "timestamp": "2025-01-01T00:00:05.000Z"
}
```

<Tip>
  Save the `payment_method_id` (`pmi_xxx` format) for subsequent payments.
</Tip>

## List Saved Cards

Retrieve the customer's saved cards.

```bash theme={null}
curl https://api.sandbox.zafapay.com/v1/customers/cust_abc123/payment-methods \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```

### Response

```json theme={null}
{
  "customer_id": "cust_abc123",
  "payment_methods": [
    {
      "id": "pmi_xyz789",
      "type": "card",
      "card": {
        "brand": "visa",
        "last4": "4242",
        "exp_month": 12,
        "exp_year": 2028,
        "cardholder_name": "John Doe",
        "country": "US",
        "funding": "credit"
      },
      "is_default": true,
      "status": "active",
      "created_at": "2025-01-01T00:00:00.000Z"
    }
  ]
}
```

## Execute Recurring Payment

Use the saved `payment_method_id` to charge without requiring card input.

### Request

```bash theme={null}
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
    }
  }'
```

<ParamField body="payment_method_id" type="string" required>
  The saved payment method ID (`pmi_xxx` format)
</ParamField>

### Response

Recurring payments are processed instantly (no `payment_url` is returned).

```json theme={null}
{
  "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"
}
```

<Note>
  `payment_type: "recurring"` indicates this is a recurring payment using a saved card.
</Note>

### Webhook on Payment Success

```json theme={null}
{
  "event": "payment.succeeded",
  "transaction_id": "tx_def456",
  "status": "succeeded",
  "amount": "10.00",
  "currency": "usd",
  "external_id": "subscription_renewal_002",
  "payment_method": "card",
  "payment_method_id": "pmi_xyz789",
  "is_recurring": true,
  "customer_id": "cust_abc123",
  "card_brand": "visa",
  "card_last4": "4242",
  "card_country": "US",
  "card_funding": "credit",
  "metadata": { "plan": "premium", "billing_cycle": 2 },
  "created_at": "2025-02-01T00:00:00.000Z",
  "timestamp": "2025-02-01T00:00:05.000Z"
}
```

<Note>
  Recurring payment webhooks include `is_recurring: true`.
</Note>

### Webhook on Payment Failure

When a recurring payment fails, a `payment.failed` event is sent. The `error` object contains details about the failure.

```json theme={null}
{
  "event": "payment.failed",
  "transaction_id": "tx_def456",
  "status": "failed",
  "amount": "10.00",
  "currency": "usd",
  "external_id": "subscription_renewal_002",
  "payment_method": "card",
  "payment_method_id": "pmi_xyz789",
  "is_recurring": true,
  "customer_id": "cust_abc123",
  "card_brand": "visa",
  "card_last4": "4242",
  "error": {
    "code": "card_declined",
    "category": "soft_decline",
    "message": "The card was declined",
    "recommended_action": "use_different_card"
  },
  "created_at": "2025-02-01T00:00:00.000Z",
  "timestamp": "2025-02-01T00:00:05.000Z"
}
```

<Note>
  Use `error.recommended_action` to determine the appropriate response:

  | Value                | Description                                       |
  | -------------------- | ------------------------------------------------- |
  | `retry`              | Temporary error. Can retry after a delay          |
  | `contact_customer`   | Customer action needed (e.g., insufficient funds) |
  | `use_different_card` | A different card is required (e.g., expired card) |
  | `none`               | No special action required                        |
</Note>

## Managing Payment Methods

### Block a Card

Block a card if fraud is suspected.

```bash theme={null}
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

```bash theme={null}
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.

```bash theme={null}
curl -X POST https://api.sandbox.zafapay.com/v1/customers/payment-methods/pmi_xyz789/detach \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```

#### Response

```json theme={null}
{
  "id": "pmi_xyz789",
  "deleted": true
}
```

<Warning>
  Deleted cards cannot be restored. To use again, the customer must re-register their card.
</Warning>

<Note>
  **Block vs Delete:**

  * **Block**: Temporarily disable (fraud prevention). Can be unblocked later
  * **Delete (Detach)**: Permanently remove (GDPR compliance, customer deletion requests). Cannot be restored
</Note>

## Error Handling

### Recurring Not Supported

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

### Card Expired

```json theme={null}
{
  "error": {
    "type": "payment_error",
    "code": "expired_card",
    "message": "The card has expired",
    "request_id": "req_xxx"
  }
}
```

<Warning>
  If the card has expired or been declined by the issuer, the customer must register a new card.
</Warning>

## Best Practices

<Steps>
  <Step title="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.
  </Step>

  <Step title="Persist payment_method_id">
    Store the `payment_method_id` received in the webhook to your database for subsequent payments.
  </Step>

  <Step title="Implement retry strategy">
    Card payments can fail due to temporary issues. Use exponential backoff for retries, and notify the customer after multiple failures.
  </Step>

  <Step title="Monitor expiration dates">
    Track saved card expiration dates and prompt customers to update their cards before expiration.
  </Step>
</Steps>
