# 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