# Block Payment Method
Source: https://docs.zafapay.com/api-reference/customers/block-payment-method
api-reference/openapi.en.json post /v1/customers/payment-methods/{id}/block
Block a saved payment method to prevent future use
# List Payment Methods
Source: https://docs.zafapay.com/api-reference/customers/list-payment-methods
api-reference/openapi.en.json get /v1/customers/{customer_id}/payment-methods
List saved payment methods for a customer
# Unblock Payment Method
Source: https://docs.zafapay.com/api-reference/customers/unblock-payment-method
api-reference/openapi.en.json post /v1/customers/payment-methods/{id}/unblock
Unblock a blocked payment method to allow future use
# Capture
Source: https://docs.zafapay.com/api-reference/payments/capture
api-reference/openapi.en.json post /v1/payments/{id}/capture
Capture an authorized payment
# Create Payment
Source: https://docs.zafapay.com/api-reference/payments/create-payment
api-reference/openapi.en.json post /v1/payments
Create a new payment
# Get Payment
Source: https://docs.zafapay.com/api-reference/payments/get-payment
api-reference/openapi.en.json get /v1/payments/{id}
Retrieve payment details
# Refund
Source: https://docs.zafapay.com/api-reference/payments/refund
api-reference/openapi.en.json post /v1/payments/{id}/refund
Refund a completed payment
# Authentication
Source: https://docs.zafapay.com/en/api-reference/authentication
How to obtain and use API access tokens
## Overview
ZAFA PAY API uses Bearer Token authentication. All API requests must include an access token in the `Authorization` header.
## Obtaining Access Token
You can obtain an access token from the merchant dashboard.
Log in to the merchant dashboard ([https://app.zafapay.com](https://app.zafapay.com))
Select "Merchant Settings" from the side menu
Use the access token displayed in the "API Access" section
## API Endpoints
| Environment | Base URL | Purpose |
| ----------- | --------------------------------- | --------------------- |
| Sandbox | `https://api.sandbox.zafapay.com` | Testing & Development |
| Production | `https://api.zafapay.com` | Production |
Different access tokens are required for Sandbox and Production environments.
## Authentication Method
Set the `Authorization` header in all API requests.
```bash cURL theme={null}
curl https://api.sandbox.zafapay.com/v1/payments \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json"
```
```javascript Node.js theme={null}
const response = await fetch('https://api.sandbox.zafapay.com/v1/payments', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
```
## Authentication Errors
| Error Code | HTTP Status | Cause |
| -------------- | ----------- | ------------------------ |
| `unauthorized` | 401 | Invalid or expired token |
| `forbidden` | 403 | Account is deactivated |
### Error Response Examples
```json 401 Unauthorized theme={null}
{
"error": {
"code": "unauthorized",
"message": "Missing or invalid authorization header"
}
}
```
```json 403 Forbidden theme={null}
{
"error": {
"code": "forbidden",
"message": "Merchant account is inactive"
}
}
```
## Security Best Practices
🔒 **Store Tokens Securely**
Store access tokens in environment variables or secret management services. Never hardcode them in your code.
🛡️ **Use HTTPS**
Always make API requests over HTTPS.
🖥️ **Server-Side Calls**
Never expose access tokens in client-side (browser) code.
🔄 **Regular Rotation**
Regularly regenerate access tokens for security.
# Error Codes
Source: https://docs.zafapay.com/en/api-reference/errors
API error response details
## Error Response Format
API errors are returned in the following format.
```json theme={null}
{
"error": {
"type": "invalid_request_error",
"code": "validation_error",
"message": "Error description",
"request_id": "req_abc123xyz789"
}
}
```
Error type (`authentication_error`, `invalid_request_error`, `payment_error`, `api_error`)
Error code (see below)
Error description
Request ID (use when contacting support)
Parameter that caused the error (for validation errors)
Detailed error information (for validation errors)
## Authentication Errors
| Code | HTTP Status | Description |
| --------------------- | ----------- | ------------------------------------------ |
| `unauthorized` | 401 | Authorization header is invalid or missing |
| `merchant_not_active` | 403 | Merchant account is deactivated |
## Validation Errors
| Code | HTTP Status | Description |
| ---------------------- | ----------- | -------------------------------------------------------------- |
| `validation_error` | 400 | Invalid request parameters. Details are in the `details` field |
| `invalid_amount` | 400 | Invalid amount |
| `amount_below_minimum` | 400 | Amount is below minimum |
| `amount_above_maximum` | 400 | Amount exceeds maximum |
| `unsupported_currency` | 400 | Unsupported currency |
## Payment Errors
| Code | HTTP Status | Description |
| ------------------------- | ----------- | ----------------------------------------------- |
| `payment_failed` | 400 | Payment processing failed |
| `invalid_status` | 400 | Operation cannot be performed in current status |
| `invalid_state` | 400 | Invalid transaction state |
| `invalid_transaction` | 400 | Transaction is invalid |
| `not_supported` | 400 | This operation is not supported |
| `capture_failed` | 400 | Capture failed |
| `refund_failed` | 400 | Refund failed |
| `card_limit_exceeded` | 400 | Monthly card spending limit exceeded |
| `email_limit_exceeded` | 400 | Monthly email spending limit exceeded |
| `recurring_not_supported` | 400 | Connector does not support recurring payments |
## Webhook Payment Error Codes
When a payment fails, the `payment.failed` webhook includes detailed error information in a unified format that is consistent across all payment providers.
### Error Structure
```json theme={null}
{
"event": "payment.failed",
"error": {
"code": "card_declined",
"category": "soft_decline",
"message": "Your card was declined",
"recommended_action": "use_different_card",
"psp_error": {
"code": "card_declined",
"decline_code": "generic_decline"
}
}
}
```
### Error Categories
| Category | Description | Recommended Action |
| ---------------- | --------------------------- | -------------------------------------------- |
| `authentication` | 3DS authentication required | Redirect customer to complete authentication |
| `soft_decline` | Temporary failure | Retry or use a different card |
| `hard_decline` | Permanent failure | Do not retry with the same card |
| `gateway_error` | Payment gateway issue | Retry later |
### Unified Error Codes
| Code | Category | Description | Recommended Action |
| ------------------------- | -------------- | --------------------------- | -------------------- |
| `authentication_required` | authentication | 3DS authentication required | `contact_customer` |
| `authentication_failed` | soft\_decline | 3DS authentication failed | `retry` |
| `card_declined` | soft\_decline | Card was declined | `use_different_card` |
| `insufficient_funds` | soft\_decline | Insufficient funds | `use_different_card` |
| `expired_card` | hard\_decline | Card has expired | `use_different_card` |
| `invalid_card` | hard\_decline | Invalid card number | `use_different_card` |
| `invalid_cvc` | soft\_decline | Invalid security code | `retry` |
| `invalid_expiry` | soft\_decline | Invalid expiration date | `retry` |
| `fraud_detected` | hard\_decline | Suspected fraud | `none` |
| `stolen_card` | hard\_decline | Stolen card | `none` |
| `lost_card` | hard\_decline | Lost card | `none` |
| `processing_error` | gateway\_error | Processing error | `retry` |
| `gateway_timeout` | gateway\_error | Gateway timeout | `retry` |
| `gateway_unavailable` | gateway\_error | Gateway unavailable | `retry` |
| `unknown_error` | soft\_decline | Unknown error | `use_different_card` |
### Handling Example
```javascript theme={null}
// Webhook handler
app.post('/webhooks/zafapay', (req, res) => {
const { event, error } = req.body;
if (event === 'payment.failed' && error) {
switch (error.category) {
case 'authentication':
// Send email to customer with authentication link
notifyCustomerForAuth(error.message);
break;
case 'soft_decline':
// Can retry or ask for different card
if (error.recommended_action === 'retry') {
scheduleRetry();
} else {
requestNewCard();
}
break;
case 'hard_decline':
// Do not retry, notify customer
notifyCustomerCardInvalid(error.message);
break;
case 'gateway_error':
// Temporary issue, schedule retry
scheduleRetryWithBackoff();
break;
}
}
res.json({ received: true });
});
```
The `psp_error` field contains the original payment provider's error details for debugging purposes.
## Refund Errors
| Code | HTTP Status | Description |
| ---------------- | ----------- | ----------------------------------------------------------- |
| `invalid_amount` | 400 | Refund amount exceeds payment amount |
| `invalid_status` | 400 | Not in refundable status (only `completed` can be refunded) |
## Resource Errors
| Code | HTTP Status | Description |
| ---------------------------- | ----------- | ----------------------------------- |
| `not_found` | 404 | Specified resource not found |
| `flow_not_found` | 404 | Payment flow not found |
| `no_default_flow` | 404 | Default payment flow not configured |
| `flow_not_active` | 400 | Payment flow is disabled |
| `connector_config_not_found` | 404 | Connector configuration not found |
## Idempotency Errors
| Code | HTTP Status | Description |
| -------------------------- | ----------- | ------------------------------------------------ |
| `idempotency_key_mismatch` | 400 | Different request sent with same idempotency key |
| `idempotency_key_in_use` | 409 | Idempotency key is currently being processed |
## System Errors
| Code | HTTP Status | Description |
| ---------------- | ----------- | ----------------------------------------------- |
| `internal_error` | 500 | Internal error occurred. Please contact support |
## Error Handling Best Practices
* `4xx`: Client error (request needs modification)
* `5xx`: Server error (retriable)
Use `error.code` to implement error-type-specific handling
For `5xx` errors or `idempotency_key_in_use`, retry with exponential backoff
## Sample Code
```javascript Node.js theme={null}
async function createPayment(data) {
const response = await fetch('https://api.sandbox.zafapay.com/v1/payments', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
'Content-Type': 'application/json',
'Idempotency-Key': generateIdempotencyKey()
},
body: JSON.stringify(data)
});
if (!response.ok) {
const { error } = await response.json();
// Log request_id for support inquiries
console.error(`API Error [${error.request_id}]:`, error.code, error.message);
switch (error.type) {
case 'authentication_error':
// Token refresh required
throw new AuthError(error.message);
case 'invalid_request_error':
// Check request parameters
if (error.details) {
console.error('Validation errors:', error.details);
}
throw new ValidationError(error.message);
case 'payment_error':
// Payment failed (try another payment method)
throw new PaymentError(error.message);
case 'api_error':
// Retriable
throw new RetryableError(error.message);
default:
throw new Error(error.message);
}
}
return response.json();
}
```
# API Reference
Source: https://docs.zafapay.com/en/api-reference/introduction
Overview of ZAFA PAY API
Download our OpenAPI specification to generate client libraries or import into API tools like Postman.
Download OpenAPI (JSON)
Download the complete documentation as a text file for offline reading or AI tool integration.
Download Docs (TXT)
## API Endpoints
| Environment | Base URL |
| ----------- | --------------------------------- |
| Sandbox | `https://api.sandbox.zafapay.com` |
| Production | `https://api.zafapay.com` |
## Authentication
All API requests require Bearer token authentication.
```bash theme={null}
Authorization: Bearer YOUR_ACCESS_TOKEN
```
For instructions on obtaining an access token, see [Authentication](/en/api-reference/authentication).
## Request Format
* Content-Type: `application/json`
* Character encoding: UTF-8
## Response Format
All responses are returned in JSON format.
**Success (Create Payment):**
```json theme={null}
{
"success": true,
"status": "pending",
"transaction_id": "tx_abc123",
"gateway_transaction_id": "pi_xxxxx",
...
}
```
**Success (Get Payment):**
```json theme={null}
{
"id": "tx_abc123",
"status": "completed",
"amount": 100,
...
}
```
**Error:**
```json theme={null}
{
"error": {
"type": "invalid_request_error",
"code": "validation_error",
"message": "Error message",
"request_id": "req_abc123xyz789"
}
}
```
For error code details, see [Error Codes](/en/api-reference/errors).
## Transaction Status
| Status | Description |
| -------------------- | --------------------------------------------------------- |
| `pending` | Payment created, awaiting customer action (redirect flow) |
| `requires_action` | Awaiting frontend action (card payment) |
| `authorized` | Authorization complete (awaiting manual capture) |
| `completed` | Payment completed |
| `failed` | Payment failed |
| `canceled` | Payment canceled |
| `partially_refunded` | Partially refunded |
| `refunded` | Fully refunded |
# Webhook
Source: https://docs.zafapay.com/en/api-reference/webhooks
Receive payment event notifications
## Overview
Webhooks allow you to receive real-time notifications for payment events such as completion, failure, and refunds.
ZAFA PAY sends HTTP POST requests to your registered URL when payment status changes.
The Webhook Secret can be found in the "Merchant Settings" section of the merchant dashboard ([https://app.zafapay.com](https://app.zafapay.com)).
## Event Types
| Event | Description |
| -------------------- | ------------------------------ |
| `payment.succeeded` | Payment completed successfully |
| `payment.failed` | Payment failed |
| `payment.canceled` | Payment canceled |
| `payment.refunded` | Refund completed |
| `payment.chargeback` | Chargeback occurred |
## Payload
Event type (e.g., `payment.succeeded`)
Transaction ID
Merchant ID
Merchant name
Payment status (`succeeded`, `failed`, `canceled`, `refunded`, `chargeback`)
Payment amount (string format, e.g., `"100.00"`)
Currency code
Payment method (`card`, `depot`, etc.)
Saved card ID (`pmi_xxx` format). Only included for recurring payments or when `save_card` was used
`true` for recurring (subscription) payments
Only included as `true` when the card was saved for future use
Merchant's order ID (the value specified when creating the payment)
Product name (the value specified when creating the payment)
Customer ID (the value specified when creating the payment)
Customer's email address (the value specified when creating the payment)
Customer's phone number (the value specified when creating the payment)
Refunded amount (string format. `payment.refunded` event only)
Error message (`payment.failed` event only)
Card brand (`visa`, `mastercard`, `amex`, `jcb`, etc.)
Last 4 digits of the card number
Cardholder name
Card expiration month
Card expiration year
Metadata specified when creating the payment
Transaction creation timestamp (ISO 8601 format)
Webhook sent timestamp (ISO 8601 format)
## Payload Examples
### payment.succeeded (Payment Successful)
```json theme={null}
{
"event": "payment.succeeded",
"transaction_id": "tx_abc123",
"merchant_id": "acct_12345",
"merchant_name": "Sample Store",
"status": "succeeded",
"amount": "100.00",
"currency": "usd",
"payment_method": "card",
"payment_method_id": "pmi_abc123",
"is_recurring": false,
"save_card": true,
"external_id": "order_12345",
"product_name": "Premium Plan",
"customer_id": "cust_12345",
"email": "customer@example.com",
"tel": "09012345678",
"card_brand": "visa",
"card_last4": "4242",
"cardholder_name": "TARO YAMADA",
"card_exp_month": 12,
"card_exp_year": 2028,
"metadata": {},
"created_at": "2024-01-15T10:30:00Z",
"timestamp": "2024-01-15T10:31:00Z"
}
```
* `payment_method_id` is only included when `save_card` was used or for recurring payments
* `save_card` is only included as `true` when the card was saved in the initial payment
* `is_recurring` is `true` for recurring payments
### payment.failed (Payment Failed)
```json theme={null}
{
"event": "payment.failed",
"transaction_id": "tx_abc123",
"merchant_id": "acct_12345",
"merchant_name": "Sample Store",
"status": "failed",
"amount": "100.00",
"currency": "usd",
"payment_method": "card",
"is_recurring": false,
"external_id": "order_12345",
"product_name": "Premium Plan",
"customer_id": "cust_12345",
"email": "customer@example.com",
"tel": "09012345678",
"error": "Your card was declined.",
"card_brand": "visa",
"card_last4": "4242",
"cardholder_name": "TARO YAMADA",
"card_exp_month": 12,
"card_exp_year": 2028,
"metadata": {},
"created_at": "2024-01-15T10:30:00Z",
"timestamp": "2024-01-15T10:31:00Z"
}
```
The `error` field is only included in `payment.failed` events.
### payment.refunded (Refund Completed)
```json theme={null}
{
"event": "payment.refunded",
"transaction_id": "tx_abc123",
"merchant_id": "acct_12345",
"merchant_name": "Sample Store",
"status": "refunded",
"amount": "100.00",
"currency": "usd",
"payment_method": "card",
"is_recurring": false,
"external_id": "order_12345",
"product_name": "Premium Plan",
"customer_id": "cust_12345",
"email": "customer@example.com",
"tel": "09012345678",
"amount_refunded": "50.00",
"card_brand": "visa",
"card_last4": "4242",
"cardholder_name": "TARO YAMADA",
"card_exp_month": 12,
"card_exp_year": 2028,
"metadata": {},
"created_at": "2024-01-15T10:30:00Z",
"timestamp": "2024-01-20T14:00:00Z"
}
```
The `amount_refunded` field is only included in `payment.refunded` events. For partial refunds, it shows the refunded amount.
### payment.chargeback (Chargeback Occurred)
```json theme={null}
{
"event": "payment.chargeback",
"transaction_id": "tx_abc123",
"merchant_id": "acct_12345",
"merchant_name": "Sample Store",
"status": "chargeback",
"amount": "100.00",
"currency": "usd",
"payment_method": "card",
"is_recurring": false,
"external_id": "order_12345",
"product_name": "Premium Plan",
"customer_id": "cust_12345",
"email": "customer@example.com",
"tel": "09012345678",
"card_brand": "visa",
"card_last4": "4242",
"cardholder_name": "TARO YAMADA",
"card_exp_month": 12,
"card_exp_year": 2028,
"metadata": {},
"created_at": "2024-01-15T10:30:00Z",
"timestamp": "2024-02-01T09:00:00Z"
}
```
When a chargeback occurs, the transaction amount may be debited from the merchant. Please respond promptly.
## Signature Verification
Webhook requests include a signature header. Verify this signature to confirm the request was sent by ZAFA PAY.
### Signature Header
| Environment | Header Name |
| ----------- | ----------------------------- |
| Sandbox | `X-Zafapay-Signature-Sandbox` |
| Production | `X-Zafapay-Signature` |
### Verification Method
The signature is an HMAC-SHA256 hash of the request body (JSON string).
```javascript Node.js theme={null}
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(JSON.stringify(payload))
.digest('hex');
return signature === expectedSignature;
}
// Express.js example
app.post('/webhooks/zafapay', express.json(), (req, res) => {
const signature = req.headers['x-zafapay-signature-sandbox'];
const secret = process.env.ZAFAPAY_WEBHOOK_SECRET;
if (!verifyWebhookSignature(req.body, signature, secret)) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Process event
const { event, transaction_id, status } = req.body;
switch (event) {
case 'payment.succeeded':
// Handle successful payment
break;
case 'payment.failed':
// Handle failed payment
break;
case 'payment.refunded':
// Handle refund
break;
}
res.json({ received: true });
});
```
## Response
Return HTTP status code `2xx` when the webhook is successfully received.
```json theme={null}
{
"received": true
}
```
## Retry
ZAFA PAY automatically retries webhooks in the following cases:
* HTTP status code other than `2xx` is returned
* Connection timeout occurs (10 seconds)
### Retry Schedule
| Attempt | Delay After Failure |
| ------- | ------------------- |
| 1st | Immediate |
| 2nd | 1 minute later |
| 3rd | 5 minutes later |
| 4th | 30 minutes later |
| 5th | 2 hours later |
| 6th | 6 hours later |
A total of 6 retries are executed over approximately 9 hours. If all retries fail, the event is moved to a Dead Letter Queue (DLQ) for manual intervention.
To prevent duplicate notifications from retries, ensure idempotency using `transaction_id` as the key.
## Best Practices
Signature verification is essential to prevent unauthorized requests
The same event may be sent multiple times. Use `transaction_id` as a key to prevent duplicate processing
Process webhooks asynchronously and return `200` immediately. Long processing times will cause timeout retries
Log received payloads and processing results for debugging
# Card Payments
Source: https://docs.zafapay.com/en/guide/card-payments
How to integrate card payments with hosted checkout
## Overview
Card payments use **hosted checkout**. Simply redirect the user to the `payment_url` from the API response, and card input through 3D Secure authentication is handled automatically.
## Payment Flow
Call `POST /v1/payments` to create a payment and obtain `payment_url`
Redirect user to `payment_url`
After payment, user is automatically redirected to your configured `success_redirect_url` or `failure_redirect_url`
## Implementation Example
```javascript theme={null}
// 1. Create payment via API
const response = await fetch('https://api.sandbox.zafapay.com/v1/payments', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
'Content-Type': 'application/json'
},
body: JSON.stringify({
amount: 100,
currency: 'usd'
})
});
const payment = await response.json();
// 2. Redirect user to hosted checkout
window.location.href = payment.payment_url;
// 3. User completes payment on hosted page
// 4. User is redirected to success_redirect_url or failure_redirect_url
```
## Authorization & Capture
For two-step payments (authorize first, capture later):
```javascript theme={null}
// Step 1: Authorize
const payment = await createPayment({
amount: 100,
currency: 'usd',
capture_method: 'manual' // Authorization only
});
// Step 2: Capture (when ready to charge)
await fetch(`https://api.sandbox.zafapay.com/v1/payments/${payment.transaction_id}/capture`, {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_ACCESS_TOKEN'
}
});
```
## Test Cards
| Card Number | Description |
| ---------------- | ------------------ |
| 4242424242424242 | Successful payment |
| 4000002500003155 | Requires 3D Secure |
| 4000000000000002 | Declined |
| 4000000000000259 | Chargeback |
| 4000000000009995 | Insufficient funds |
| 4000000000009987 | Lost card |
| 4000000000009979 | Stolen card |
| 4000000000000069 | Expired card |
| 4000000000000127 | Incorrect CVC |
| 4000000000000119 | Processing error |
For complete API parameters and response details, see the [Create Payment](/api-reference/payments/create-payment) API reference.
# Depot Payments
Source: https://docs.zafapay.com/en/guide/depot-payments
How to integrate Depot e-money payments
## Overview
Depot is a **redirect-based** payment method. After creating a payment via API, redirect the customer to `payment_url` to complete the payment.
## Payment Flow
Call `POST /v1/payments` to create a payment and obtain `payment_url`
Redirect customer to `payment_url`
Customer completes payment on Depot's page
Customer is redirected to your `success_redirect_url` or `failure_redirect_url`
Optionally verify payment status using Get Payment API
## Implementation Example
```javascript theme={null}
// 1. Create payment via API
const response = await fetch('https://api.sandbox.zafapay.com/v1/payments', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
'Content-Type': 'application/json'
},
body: JSON.stringify({
amount: 100,
currency: 'usd',
external_id: 'order_12345'
})
});
const payment = await response.json();
// 2. Redirect customer to Depot payment page
window.location.href = payment.payment_url;
```
## Webhook Integration
For reliable payment confirmation, set up webhooks:
```javascript theme={null}
// Webhook handler
app.post('/webhooks/zafapay', async (req, res) => {
const event = req.body;
if (event.type === 'payment.completed') {
// Update order status
await updateOrder(event.data.external_id, 'paid');
}
res.json({ received: true });
});
```
For complete API parameters and response details, see the [Create Payment](/api-reference/payments/create-payment) API reference.
# Iframe Embedded Checkout
Source: https://docs.zafapay.com/en/guide/iframe-integration
How to embed the checkout form in an iframe
## Overview
By embedding ZAFA PAY's checkout page in an iframe, you can complete payments within your site without redirecting users.
## Result Notification Methods
There are two methods for receiving payment results.
| Method | Description | Use Case |
| --------------- | ------------------------------------------- | ------------------------------------ |
| **postMessage** | Send events to parent window via JavaScript | SPAs and JavaScript-controlled pages |
| **redirect** | Redirect the parent window | Server-side result handling |
The notification method is configured per merchant. Contact support if you need to change it.
## Implementation Steps
### 1. Create Payment Request
Create a payment request using `POST /v1/payments` as usual. Get the `payment_url` from the response.
```bash theme={null}
curl -X POST https://api.sandbox.zafapay.com/v1/payments \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"amount": 100,
"currency": "usd",
"external_id": "order_12345",
"success_redirect_url": "https://your-site.com/success",
"failure_redirect_url": "https://your-site.com/failure"
}'
```
### 2. Embed in iframe
Add the `?mode=embedded` parameter to the `payment_url` and set it in the iframe.
```html theme={null}
```
The `mode=embedded` parameter is required. Without it, a redirect will occur when the payment is completed.
***
## postMessage Method
When postMessage method is configured, payment results are sent to the parent window via JavaScript postMessage.
### Receive Events
```javascript theme={null}
window.addEventListener('message', function(event) {
// Security: Verify origin
if (event.origin !== 'https://pay.zafapay.com') {
return;
}
const data = event.data;
if (!data || !data.type) return;
switch (data.type) {
case 'payment_success':
console.log('Payment successful:', data.transactionId);
// Handle success
break;
case 'payment_failed':
console.log('Payment failed:', data.error);
// Handle failure
break;
case 'payment_cancel':
console.log('Cancelled');
// Handle cancellation
break;
case 'redirect_required':
console.log('Redirect required:', data.url);
// Handle redirect for 3D Secure etc.
window.open(data.url, '_blank');
break;
}
});
```
## Event Types
| Event | Description | Included Data |
| ------------------- | ------------------------------------ | ---------------------------- |
| `payment_success` | Payment successful | `transactionId`, `requestId` |
| `payment_failed` | Payment failed | `error`, `code` |
| `payment_cancel` | User cancelled | None |
| `redirect_required` | Redirect required for 3D Secure etc. | `url`, `reason` |
### payment\_success
Sent when payment completes successfully.
```json theme={null}
{
"type": "payment_success",
"transactionId": "tx_abc123",
"requestId": "req_xyz789"
}
```
### payment\_failed
Sent when payment fails.
```json theme={null}
{
"type": "payment_failed",
"error": "Card was declined",
"code": "card_declined"
}
```
### payment\_cancel
Sent when user clicks "Cancel and return to site".
```json theme={null}
{
"type": "payment_cancel"
}
```
### redirect\_required
Sent when redirect is required for 3D Secure authentication etc.
This event only occurs for specific payment methods. Depending on the payment method, authentication may be handled automatically, and this event may not occur.
```json theme={null}
{
"type": "redirect_required",
"url": "https://auth.example.com/3ds/...",
"reason": "3ds_required"
}
```
When redirect is required, open it in a new tab/window or navigate the parent window. Redirects within the iframe may not work due to security restrictions.
## Complete Implementation Example
```html theme={null}
Checkout Page
Payment
```
## Security Considerations
Always verify `event.origin` when receiving postMessage.
Iframe embedding only works on HTTPS sites.
For payment confirmation, always receive and verify webhooks on your server in addition to postMessage.
***
## Redirect Method
When redirect method is configured, payment results are notified by redirecting the parent window.
### Redirect URL
Users are redirected to the `success_redirect_url`, `failure_redirect_url`, or `cancel_redirect_url` specified when creating the payment.
### URL Parameters
| Status | Redirect To | Parameters |
| ------- | ---------------------- | --------------------------------------------------- |
| Success | `success_redirect_url` | `?request_id=xxx&transaction_id=xxx&status=success` |
| Failed | `failure_redirect_url` | `?request_id=xxx&transaction_id=xxx&status=failed` |
| Cancel | `cancel_redirect_url` | `?request_id=xxx&status=cancel` |
* Cancel does not include `transaction_id` because the payment was not initiated.
* If `cancel_redirect_url` is not set, cancellations will redirect to `failure_redirect_url`.
### Parameter Details
| Parameter | Description |
| ---------------- | --------------------------------------- |
| `request_id` | Payment request ID (starts with `req_`) |
| `transaction_id` | Transaction ID (starts with `tx_`) |
| `status` | `success` / `failed` / `cancel` |
### Implementation Example
```javascript theme={null}
// Server-side (Node.js example)
app.get('/payment/callback', (req, res) => {
const { request_id, transaction_id, status } = req.query;
switch (status) {
case 'success':
// Success handling (also verify with webhook)
res.redirect('/thank-you?order=' + request_id);
break;
case 'failed':
// Failure handling
res.redirect('/checkout?error=payment_failed');
break;
case 'cancel':
// Cancel handling
res.redirect('/cart');
break;
}
});
```
For payment confirmation, always receive and verify webhooks on your server in addition to URL parameters. URL parameters can be tampered with.
***
## Testing
When testing in the Sandbox environment, you can use the following test cards.
| Card Number | Result |
| --------------------- | ----------------------------- |
| `4242 4242 4242 4242` | Success |
| `4000 0000 0000 3220` | 3D Secure required |
| `4000 0000 0000 9995` | Declined (insufficient funds) |
| `4000 0000 0000 0002` | Declined (generic) |
Use any future date for expiry and any 3 digits for CVC.
# Recurring Payments
Source: https://docs.zafapay.com/en/guide/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.
Recurring payments are only available for certain connectors. Please contact us for availability.
## 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"
}
}'
```
A unique ID to identify the customer. Use your internal user ID.
Set to `true` for the initial recurring payment. The card will be saved after payment completion.
### 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"
}
```
`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
```json theme={null}
{
"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.
```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",
"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
```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
}
}'
```
The saved payment method ID (`pmi_xxx` format)
### 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"
}
```
`payment_type: "recurring"` indicates this is a recurring payment using a saved card.
### Webhook on Payment Completion
```json theme={null}
{
"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.
```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
}
```
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
```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"
}
}
```
If the card has expired or been declined by the issuer, the customer must register a new card.
## Best Practices
Map your internal user ID 1:1 with `customer_id`. Using different `customer_id` values for the same customer will scatter their card information.
Store the `payment_method_id` received in the webhook to your database for subsequent payments.
Card payments can fail due to temporary issues. Use exponential backoff for retries, and notify the customer after multiple failures.
Track saved card expiration dates and prompt customers to update their cards before expiration.
# ZAFA PAY API
Source: https://docs.zafapay.com/en/index
A unified payment API platform integrating multiple PSPs
## Overview
ZAFA PAY provides a unified payment API that integrates multiple Payment Service Providers (PSPs).
With a simple API, you can implement payments supporting multiple payment methods including credit cards and e-money.
Step-by-step guide to implement your first payment
## Key Features
Create payments with credit cards, debit cards, and more
Full or partial refund of completed payments
Check payment status and details
## Environments
| Environment | Base URL |
| ----------- | --------------------------------- |
| Sandbox | `https://api.sandbox.zafapay.com` |
| Production | `https://api.zafapay.com` |
## Support
Questions & Inquiries: [support@zafapay.com](mailto:support@zafapay.com)
# Quickstart
Source: https://docs.zafapay.com/en/quickstart
Step-by-step guide to implement your first payment
## Environments
| Environment | Admin Dashboard | API |
| ----------- | ---------------------------------------------------------- | ------------------------- |
| Sandbox | [app.sandbox.zafapay.com](https://app.sandbox.zafapay.com) | `api.sandbox.zafapay.com` |
| Production | [app.zafapay.com](https://app.zafapay.com) | `api.zafapay.com` |
Use the Sandbox environment for development and testing. Switching to production only requires changing the API endpoint URL.
## 1. Get Access Token
Get your access token from the "Merchant Settings" page in the merchant dashboard.
```bash theme={null}
# All API requests require an Authorization header
Authorization: Bearer YOUR_ACCESS_TOKEN
```
For detailed instructions, see [Authentication](/en/api-reference/authentication).
## 2. Create Payment
```bash cURL 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": 100,
"currency": "usd",
"external_id": "order_12345"
}'
```
```javascript Node.js theme={null}
const response = await fetch('https://api.sandbox.zafapay.com/v1/payments', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
'Content-Type': 'application/json'
},
body: JSON.stringify({
amount: 100,
currency: 'usd',
external_id: 'order_12345'
})
});
const payment = await response.json();
console.log(payment.payment_url);
```
**Response:**
```json theme={null}
{
"success": true,
"status": "pending",
"transaction_id": "tx_abc123",
"gateway_transaction_id": "depot_xxxxx",
"payment_url": "https://pay.sandbox.zafapay.com/checkout/tx_abc123?sig=xxxxxxxxxxxxxxxx"
}
```
## 3. Redirect Customer
Redirect the customer to the `payment_url` from the response.
After payment completion, the customer will be redirected to `success_redirect_url` or `failure_redirect_url` configured in your merchant dashboard.
## 4. Verify Payment Result
```bash theme={null}
curl https://api.sandbox.zafapay.com/v1/payments/tx_abc123 \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```
**Response:**
```json theme={null}
{
"id": "tx_abc123",
"status": "completed",
"amount": 100,
"currency": "usd"
}
```
## Next Steps
Complete API endpoint documentation
How to refund payments