Payments API
Payments API
Accept payments from customers via card and other payment methods.
Payment Flow
Redirect Flow
- Initiate payment - Call
POST /api/v1/payments/initwith order details - Redirect user - Send customer to the
payment_urlreturned - Customer pays - Customer completes payment on QRPay hosted page
- Receive webhook - Get notified of payment result via webhook
- Fulfill order - Process the order based on payment status
QR Code Flow
- Initiate payment - Call
POST /api/v1/payments/initwithinclude_qr: true - Display QR code - Show the QR code image to the customer
- Customer scans - Customer scans QR with their phone and opens payment page
- Customer pays - Customer completes payment on their mobile device
- Receive webhook - Get notified of payment result via webhook
- Fulfill order - Process the order based on payment status
Payment Intent Types
| Intent Type | Behavior | Use Case |
|---|---|---|
purchase_and_redeem | User pays → voucher issued and redeemed → auto-redirect | Instant fulfillment, seamless UX |
purchase | User pays → voucher issued → code shown | User saves voucher for later |
Initiate Payment
Endpoint: POST /api/v1/payments/init
Headers:
X-API-Key: <secret>Content-Type: application/jsonIdempotency-Key: <unique-id>(optional but recommended)
Request:
{ "intent_type": "purchase_and_redeem", "amount": 10000, "currency": "ZAR", "merchant_order_id": "ORDER-2025-001", "include_qr": true, "success_url": "https://merchant.example/orders/001/success", "cancel_url": "https://merchant.example/orders/001/cancel", "failure_url": "https://merchant.example/orders/001/failure", "webhook_url": "https://merchant.example/webhooks/qrpay", "customer_info": { "email": "customer@example.com", "phone": "+27821234567" }}Request Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
intent_type | string | No | purchase or purchase_and_redeem (default: purchase_and_redeem) |
amount | integer | Yes | Amount in cents (5500-300000) |
currency | string | Yes | Currency code (ZAR only) |
merchant_order_id | string | Yes | Your unique order reference (max 255 chars) |
include_qr | boolean | No | Include QR code data in response (default: false) |
success_url | string | No | Redirect URL after successful payment |
cancel_url | string | No | Redirect URL if user cancels |
failure_url | string | No | Redirect URL after failed payment |
webhook_url | string | No | URL to receive payment notifications |
customer_info | object | No | Customer email and/or phone for notifications |
Validation Rules:
amount: 5500 to 300000 cents (R55 to R3000)currency: OnlyZARsupportedmerchant_order_id: Max 255 chars, must be unique per merchant- URLs: Must be HTTPS (except in sandbox)
customer_info: Optional, used for confirmation emails/SMS
Response (200 OK):
{ "status": "success", "data": { "payment_id": "01932e5d-7f8a-7890-b123-456789abcdef", "status": "created", "payment_url": "https://pay.qrpay2.com/01932e5d-7f8a-7890-b123-456789abcdef" }, "metadata": { "timestamp": "2025-01-07T12:00:00Z", "api_version": "v1" }}Response with QR Code (when include_qr: true):
{ "status": "success", "data": { "payment_id": "01932e5d-7f8a-7890-b123-456789abcdef", "status": "created", "payment_url": "https://pay.qrpay2.com/01932e5d-7f8a-7890-b123-456789abcdef", "qr_code": { "content": "https://pay.qrpay2.com/01932e5d-7f8a-7890-b123-456789abcdef", "image_url": "https://pay.qrpay2.com/qr/eyJ1IjoiaHR0cHM6Ly9wYXku...png" } }, "metadata": { "timestamp": "2025-01-07T12:00:00Z", "api_version": "v1" }}QR Code Response Fields:
| Field | Description |
|---|---|
qr_code.content | The URL encoded in the QR code (same as payment_url) |
qr_code.image_url | Signed URL to fetch the QR code image |
Next Steps:
- Store
payment_idin your database - Redirect user to
payment_urlor display the QR code image - Wait for webhook notification
QR Code Images
When you request a QR code with include_qr: true, the response includes an image_url that serves the QR code image.
Image Formats
The QR code image URL supports two formats:
| Format | URL Suffix | Content-Type | Use Case |
|---|---|---|---|
| PNG | .png (default) | image/png | Web, mobile apps, print |
| SVG | .svg | image/svg+xml | Scalable graphics, high-DPI displays |
To get an SVG image, replace .png with .svg in the image_url:
# PNG (default)https://pay.qrpay2.com/qr/eyJ1IjoiaHR0cHM6Ly9wYXku...png
# SVGhttps://pay.qrpay2.com/qr/eyJ1IjoiaHR0cHM6Ly9wYXku...svgToken Expiry
QR code image URLs are signed and expire after 24 hours.
Display Recommendations
- Minimum size: 128x128 pixels for reliable scanning
- Recommended size: at least 256x256 pixels for most displays
Get Payment Status
Endpoint: GET /api/v1/payments/{payment_id}
curl -X GET "https://api.qrpay2.com/api/v1/payments/$PAYMENT_ID" \ -H "X-API-Key: $API_KEY"Response (200 OK):
{ "status": "success", "data": { "payment_id": "01932e5d-7f8a-7890-b123-456789abcdef", "status": "captured", "amount": 10000, "currency": "ZAR", "latest_attempt": { "status": "succeeded", "provider": "yoco", "provider_ref": "ch_abc123xyz789", "created_at": "2025-01-07T12:05:30Z" } }}Payment States
| State | Description | Terminal? |
|---|---|---|
created | Payment initialized, awaiting user action | No |
initiated | User started checkout with provider | No |
awaiting_webhook | Provider processing, awaiting confirmation | No |
captured | Payment successful, fulfillment completed | Yes |
failed | Payment failed, no charge to user | Yes |
cancelled | User cancelled payment | Yes |
Terminal States
Once a payment reaches captured, failed, or cancelled, it cannot change. These states trigger webhook notifications.
Code Examples
cURL - Basic Payment
#!/bin/bashAPI_BASE="https://api.qrpay2.com"API_KEY="sec1-prd-YOUR_KEY..."IDEMP_KEY="ORDER-123-$(date +%s)-$(uuidgen)"
curl -X POST "$API_BASE/api/v1/payments/init" \ -H "X-API-Key: $API_KEY" \ -H "Idempotency-Key: $IDEMP_KEY" \ -H "Content-Type: application/json" \ -d '{ "intent_type": "purchase_and_redeem", "amount": 10000, "currency": "ZAR", "merchant_order_id": "ORDER-2025-001", "success_url": "https://merchant.example/success", "cancel_url": "https://merchant.example/cancel", "failure_url": "https://merchant.example/failure", "webhook_url": "https://merchant.example/webhooks/qrpay" }'cURL - Payment with QR Code
#!/bin/bashAPI_BASE="https://api.qrpay2.com"API_KEY="sec1-prd-YOUR_KEY..."IDEMP_KEY="ORDER-456-$(date +%s)-$(uuidgen)"
# Create payment with QR codeRESPONSE=$(curl -s -X POST "$API_BASE/api/v1/payments/init" \ -H "X-API-Key: $API_KEY" \ -H "Idempotency-Key: $IDEMP_KEY" \ -H "Content-Type: application/json" \ -d '{ "intent_type": "purchase_and_redeem", "amount": 10000, "currency": "ZAR", "merchant_order_id": "ORDER-2025-002", "include_qr": true, "webhook_url": "https://merchant.example/webhooks/qrpay" }')
# Extract QR image URLQR_IMAGE_URL=$(echo $RESPONSE | jq -r '.data.qr_code.image_url')echo "QR Code Image: $QR_IMAGE_URL"Python - Basic Payment
import requestsimport os
API_BASE = "https://api.qrpay2.com"API_KEY = os.environ["QRPAY_API_KEY"]
def create_payment(amount_cents: int, order_id: str) -> dict: response = requests.post( f"{API_BASE}/api/v1/payments/init", headers={ "X-API-Key": API_KEY, "Content-Type": "application/json", }, json={ "intent_type": "purchase_and_redeem", "amount": amount_cents, "currency": "ZAR", "merchant_order_id": order_id, "success_url": f"https://example.com/orders/{order_id}/success", "cancel_url": f"https://example.com/orders/{order_id}/cancel", "failure_url": f"https://example.com/orders/{order_id}/failure", "webhook_url": "https://example.com/webhooks/qrpay", }, ) response.raise_for_status() return response.json()["data"]
# Usagepayment = create_payment(10000, "ORDER-2025-001")print(f"Redirect to: {payment['payment_url']}")Python - Payment with QR Code
import requestsimport os
API_BASE = "https://api.sandbox.qrpay2.com"API_KEY = os.environ["QRPAY_API_KEY"]
def create_payment_with_qr(amount_cents: int, order_id: str) -> dict: """Create a payment and get QR code for display.""" response = requests.post( f"{API_BASE}/api/v1/payments/init", headers={ "X-API-Key": API_KEY, "Content-Type": "application/json", }, json={ "intent_type": "purchase_and_redeem", "amount": amount_cents, "currency": "ZAR", "merchant_order_id": order_id, "include_qr": True, "webhook_url": "https://example.com/webhooks/qrpay", }, ) response.raise_for_status() return response.json()["data"]
def download_qr_image(image_url: str, filename: str = "payment_qr.png"): """Download QR code image to file.""" response = requests.get(image_url) response.raise_for_status() with open(filename, "wb") as f: f.write(response.content) return filename
# Usagepayment = create_payment_with_qr(10000, "ORDER-2025-002")
print(f"Payment ID: {payment['payment_id']}")print(f"Payment URL: {payment['payment_url']}")print(f"QR Content: {payment['qr_code']['content']}")print(f"QR Image URL: {payment['qr_code']['image_url']}")Error Responses
| HTTP Code | Description | Action |
|---|---|---|
| 400 | Validation failed | Fix request parameters |
| 401 | Authentication failed | Check API key |
| 409 | Duplicate merchant_order_id | Use unique order ID |
| 500 | Internal server error | Retry with backoff |