Suzaa API - Complete Documentation
Table of Contents
- Overview
- What is Suzaa?
- API Architecture
- Authentication
- Endpoints
- Payment Request Lifecycle
- Integration Examples
- Error Handling
- Best Practices
- Webhooks (Future)
- Security
- Rate Limits
- Testing
Overview
The Suzaa API is a RESTful API that allows merchants to create non-custodial cryptocurrency payment requests (invoices). It generates payment links with QR codes that display:
- Merchant’s wallet address
- Payment amount (converted to cryptocurrency)
- Order reference/description
- Expiration time
Important: Suzaa is NOT a payment processor, gateway, or custodial service. It’s a payment request/invoicing tool. All cryptocurrency payments go directly to the merchant’s wallet on the blockchain.
Base URL
Production: https://api.suzaa.comAPI Version
Current Version: v1 (included in base URL path)What is Suzaa?
What Suzaa Does
Suzaa is a payment request generation service that:
- Creates Payment Requests - Generates unique payment links with embedded payment information
- Displays QR Codes - Shows QR codes containing wallet address and payment amount
- Tracks Status - Records when customers claim they’ve paid
- Provides UI - Offers a clean payment page for customers
- Manages Expiration - Automatically expires old payment requests
What Suzaa Does NOT Do
- ❌ Does NOT process payments
- ❌ Does NOT hold cryptocurrency
- ❌ Does NOT act as a custodian
- ❌ Does NOT provide wallets
- ❌ Does NOT verify blockchain transactions (merchant’s responsibility)
- ❌ Does NOT handle refunds
- ❌ Does NOT require customer accounts
The Flow
Merchant System → Suzaa API → Payment Request Created
↓
Customer Shown Payment Link
↓
QR Code with Wallet Address
↓
Customer Sends Payment on Blockchain
↓
Customer Clicks "I Paid"
↓
Redirects Back to Merchant
↓
Merchant Verifies Transaction on Blockchain
↓
Fulfills Order if ValidKey Point: The actual payment happens on the blockchain, between the customer’s wallet and the merchant’s wallet. Suzaa just facilitates the request and provides the interface.
API Architecture
RESTful Design
- HTTP Methods: POST, GET, PUT, DELETE
- Data Format: JSON
- Authentication: Bearer tokens (API keys)
- Response Format: JSON with standardized structure
Standard Response Structure
All API responses follow this structure:
{
"success": true | false,
"data": {
// Response data here
},
"error": {
// Only present if success: false
"code": "ERROR_CODE",
"message": "Human-readable error message"
}
}Authentication
API Keys
Suzaa uses API key authentication with Bearer tokens.
Obtaining an API Key
- Create a Suzaa merchant account at https://app.suzaa.com
- Navigate to Settings → API Keys
- Click “Generate New API Key”
- Copy your API key (starts with
sza_live_orsza_test_)
Key Types
- Live Keys -
sza_live_*- For production - Test Keys -
sza_test_*- For development/testing
Using API Keys
Include the API key in the Authorization header:
Authorization: Bearer sza_live_b20a7aa260a229853af02fd73fffdcd8a71ede41eaf775f5228fda2f265ef506Example Request
curl -X POST https://api.suzaa.com/payments/requests \
-H "Authorization: Bearer sza_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"amount": "100.00", "description": "Order #123"}'Security Best Practices
- ✅ Never expose API keys in client-side code
- ✅ Store keys in environment variables
- ✅ Use different keys for development and production
- ✅ Rotate keys periodically
- ✅ Revoke compromised keys immediately
Endpoints
1. Create Payment Request
Create a new payment request (invoice) for a customer.
Endpoint:
POST /payments/requestsHeaders:
Content-Type: application/json
Authorization: Bearer YOUR_API_KEYRequest Body:
{
"amount": "100.00", // Required: Amount in USD (string or number)
"description": "Order #12345", // Required: Description/reference (max 500 chars)
"expiryTime": 60, // Required: Expiration in minutes (1-1440)
"redirectUrl": "https://..." // Optional: Where to redirect after payment
}Field Details:
| Field | Type | Required | Description |
|---|---|---|---|
amount | string/number | Yes | Payment amount in USD. Can be string or number. Examples: "100.00", 100, 99.99 |
description | string | Yes | Order description or reference. Shown to customer. Max 500 characters. |
expiryTime | integer | Yes | Time until payment request expires, in minutes. Min: 1, Max: 1440 (24 hours). Default: 60 |
redirectUrl | string | No | Absolute URL (starting with http:// or https://) where customer will be redirected after clicking “I Paid” or “Cancel”. Max 500 characters. |
Response (Success - 201 Created):
{
"success": true,
"data": {
"success": true,
"paymentRequestId": "fd74f812-268c-43bc-9e42-924ce36ce1ff",
"linkId": "825757/20251213/0007",
"paymentUrl": "https://app.suzaa.com/recipient/825757/20251213/0007",
"expiresAt": "2025-12-13T15:52:28.353Z",
"message": "Payment request created successfully"
}
}Response Fields:
| Field | Type | Description |
|---|---|---|
paymentRequestId | UUID | Unique identifier for this payment request (internal database ID) |
linkId | string | Human-readable payment link ID. Format: {merchantId}/{date}/{sequenceNumber} |
paymentUrl | string | Full URL to payment page. Share this with customer or redirect them here. |
expiresAt | ISO 8601 | Timestamp when this payment request will expire |
message | string | Human-readable success message |
Response (Error - 400 Bad Request):
{
"success": false,
"error": {
"code": "INVALID_AMOUNT",
"message": "Amount must be a positive number"
}
}Response (Error - 401 Unauthorized):
{
"success": false,
"error": {
"code": "INVALID_API_KEY",
"message": "Invalid or missing API key"
}
}Example (Node.js):
const axios = require('axios');
async function createPaymentRequest() {
try {
const response = await axios.post('https://api.suzaa.com/payments/requests', {
amount: '150.00',
description: 'Premium Subscription - 1 Year',
expiryTime: 120,
redirectUrl: 'https://mysite.com/payment-return'
}, {
headers: {
'Authorization': 'Bearer sza_live_YOUR_API_KEY',
'Content-Type': 'application/json'
}
});
console.log('Payment URL:', response.data.data.paymentUrl);
console.log('Link ID:', response.data.data.linkId);
return response.data.data;
} catch (error) {
console.error('Error:', error.response.data);
throw error;
}
}Example (PHP):
<?php
function createPaymentRequest($apiKey, $amount, $description, $expiryTime = 60, $redirectUrl = null) {
$data = [
'amount' => $amount,
'description' => $description,
'expiryTime' => $expiryTime
];
if ($redirectUrl) {
$data['redirectUrl'] = $redirectUrl;
}
$ch = curl_init('https://api.suzaa.com/payments/requests');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . $apiKey,
'Content-Type: application/json'
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode !== 201) {
throw new Exception('API Error: ' . $response);
}
$result = json_decode($response, true);
return $result['data'];
}
// Usage
try {
$payment = createPaymentRequest(
'sza_live_YOUR_API_KEY',
'100.00',
'Order #12345',
60,
'https://mysite.com/return'
);
echo "Payment URL: " . $payment['paymentUrl'] . "\n";
echo "Link ID: " . $payment['linkId'] . "\n";
} catch (Exception $e) {
echo "Error: " . $e->getMessage() . "\n";
}Example (Python):
import requests
import json
def create_payment_request(api_key, amount, description, expiry_time=60, redirect_url=None):
url = 'https://api.suzaa.com/payments/requests'
headers = {
'Authorization': f'Bearer {api_key}',
'Content-Type': 'application/json'
}
data = {
'amount': str(amount),
'description': description,
'expiryTime': expiry_time
}
if redirect_url:
data['redirectUrl'] = redirect_url
response = requests.post(url, headers=headers, json=data)
if response.status_code != 201:
raise Exception(f'API Error: {response.text}')
result = response.json()
return result['data']
# Usage
try:
payment = create_payment_request(
api_key='sza_live_YOUR_API_KEY',
amount=100.00,
description='Order #12345',
expiry_time=60,
redirect_url='https://mysite.com/return'
)
print(f"Payment URL: {payment['paymentUrl']}")
print(f"Link ID: {payment['linkId']}")
except Exception as e:
print(f"Error: {e}")2. Get Payment Request Details
Retrieve details about a specific payment request.
Endpoint:
GET /payments/requests/{paymentRequestId}Headers:
Authorization: Bearer YOUR_API_KEYPath Parameters:
| Parameter | Type | Description |
|---|---|---|
paymentRequestId | UUID | The payment request ID returned when creating the request |
Response (Success - 200 OK):
{
"success": true,
"data": {
"paymentRequestId": "fd74f812-268c-43bc-9e42-924ce36ce1ff",
"linkId": "825757/20251213/0007",
"amount": "100.00",
"description": "Order #12345",
"status": "pending",
"paymentUrl": "https://app.suzaa.com/recipient/825757/20251213/0007",
"redirectUrl": "https://mysite.com/return",
"expiryTime": 60,
"expiresAt": "2025-12-13T15:52:28.353Z",
"createdAt": "2025-12-13T14:52:28.353Z",
"claimedAt": null
}
}Status Values:
| Status | Description |
|---|---|
pending | Payment request created, waiting for customer |
claimed_paid | Customer clicked “I Paid” button |
canceled | Customer clicked “Cancel” button |
expired | Payment request expired before completion |
Example (cURL):
curl -X GET https://api.suzaa.com/payments/requests/fd74f812-268c-43bc-9e42-924ce36ce1ff \
-H "Authorization: Bearer sza_live_YOUR_API_KEY"3. List Payment Requests
Get a paginated list of your payment requests.
Endpoint:
GET /payments/requestsHeaders:
Authorization: Bearer YOUR_API_KEYQuery Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
page | integer | 1 | Page number (1-indexed) |
limit | integer | 20 | Items per page (1-100) |
status | string | all | Filter by status: pending, claimed_paid, canceled, expired, all |
sort | string | created_desc | Sort order: created_asc, created_desc, amount_asc, amount_desc |
Response (Success - 200 OK):
{
"success": true,
"data": {
"requests": [
{
"paymentRequestId": "fd74f812-268c-43bc-9e42-924ce36ce1ff",
"linkId": "825757/20251213/0007",
"amount": "100.00",
"description": "Order #12345",
"status": "claimed_paid",
"createdAt": "2025-12-13T14:52:28.353Z",
"expiresAt": "2025-12-13T15:52:28.353Z"
}
// ... more requests
],
"pagination": {
"page": 1,
"limit": 20,
"total": 156,
"totalPages": 8
}
}
}Example:
curl -X GET "https://api.suzaa.com/payments/requests?page=1&limit=10&status=claimed_paid" \
-H "Authorization: Bearer sza_live_YOUR_API_KEY"4. Cancel Payment Request
Cancel a pending payment request.
Endpoint:
DELETE /payments/requests/{paymentRequestId}Headers:
Authorization: Bearer YOUR_API_KEYResponse (Success - 200 OK):
{
"success": true,
"data": {
"message": "Payment request cancelled successfully",
"paymentRequestId": "fd74f812-268c-43bc-9e42-924ce36ce1ff",
"status": "canceled"
}
}Example:
curl -X DELETE https://api.suzaa.com/payments/requests/fd74f812-268c-43bc-9e42-924ce36ce1ff \
-H "Authorization: Bearer sza_live_YOUR_API_KEY"5. Get Public Payment Request
Get payment request details without authentication (for displaying payment page).
Endpoint:
GET /public/payment/{linkId}No authentication required - This is a public endpoint used by the payment page.
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
linkId | string | The link ID (e.g., 825757/20251213/0007) |
Response (Success - 200 OK):
{
"success": true,
"data": {
"linkId": "825757/20251213/0007",
"amount": "100.00",
"amountCrypto": "0.00234567",
"cryptocurrency": "BTC",
"walletAddress": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh",
"description": "Order #12345",
"qrCodeUrl": "https://app.suzaa.com/qr/825757/20251213/0007.png",
"status": "pending",
"expiresAt": "2025-12-13T15:52:28.353Z",
"redirectUrl": "https://mysite.com/return",
"merchantName": "My Store"
}
}This endpoint is called by:
- The payment page frontend to display payment details
- Your integration to check payment status without authentication
Payment Request Lifecycle
State Diagram
CREATE
↓
[PENDING] ──────────────────→ [EXPIRED]
↓ ↑
Customer Action Timeout
↓
┌──────┴──────┐
↓ ↓
[CLAIMED_PAID] [CANCELED]
↓
Merchant Verifies
on BlockchainStatus Flow
-
Created (
pending)- Payment request just created
- Customer hasn’t taken action yet
- Waiting for customer to pay
-
Customer Claims Payment (
claimed_paid)- Customer clicked “I Paid” button
- Does NOT mean payment verified
- Merchant should verify on blockchain
-
Customer Cancels (
canceled)- Customer clicked “Cancel” button
- Can create new payment request if needed
-
Expires (
expired)expiryTimeelapsed without action- Cannot be used anymore
- Merchant should create new request
Important Notes
Status claimed_paid does NOT mean verified payment!
When status is claimed_paid:
- Customer CLAIMS they paid
- Merchant MUST verify on blockchain
- Check your wallet for incoming transaction
- Match amount and timestamp
- Only fulfill order after blockchain confirmation
This is the merchant’s responsibility - Suzaa doesn’t verify blockchain transactions.
Integration Examples
E-Commerce Integration
// 1. Customer clicks "Pay with Crypto"
app.post('/checkout', async (req, res) => {
const order = await createOrder(req.body);
// 2. Create Suzaa payment request
const payment = await suzaa.createPaymentRequest({
amount: order.total,
description: `Order #${order.id}`,
expiryTime: 60,
redirectUrl: `https://mystore.com/order/${order.id}/confirm`
});
// 3. Store payment link ID with order
await updateOrder(order.id, {
suzaa_link_id: payment.linkId,
suzaa_payment_url: payment.paymentUrl
});
// 4. Redirect customer to Suzaa payment page
res.redirect(payment.paymentUrl);
});
// 5. Customer returns after clicking "I Paid"
app.get('/order/:id/confirm', async (req, res) => {
const status = req.query.status; // claimed_paid or canceled
const paymentId = req.query.paymentId;
if (status === 'claimed_paid') {
// Mark order as "pending verification"
await updateOrder(req.params.id, {
status: 'pending_verification',
suzaa_payment_id: paymentId
});
res.render('thank-you', {
message: 'Payment claimed! We will verify and process your order soon.'
});
} else {
res.render('cancelled', {
message: 'Payment cancelled. Please try again or choose another method.'
});
}
});
// 6. Admin manually verifies blockchain transaction
app.post('/admin/orders/:id/verify-payment', async (req, res) => {
const order = await getOrder(req.params.id);
// Admin checks blockchain and confirms transaction exists
// Then marks order as paid
await updateOrder(req.params.id, {
status: 'paid',
verified_at: new Date()
});
// Ship the order
await shipOrder(req.params.id);
});Subscription Service Integration
# Create recurring payment request
def create_subscription_payment(user_id, plan):
payment = suzaa_api.create_payment_request(
amount=plan.price,
description=f"Subscription: {plan.name} - Month {get_current_month()}",
expiry_time=1440, # 24 hours
redirect_url=f"https://myapp.com/subscription/callback/{user_id}"
)
# Store payment details
db.subscriptions.update(user_id, {
'payment_link_id': payment['linkId'],
'payment_url': payment['paymentUrl'],
'status': 'awaiting_payment'
})
# Email payment link to user
send_email(
to=user.email,
subject="Your Monthly Subscription Payment",
body=f"Please complete your payment: {payment['paymentUrl']}"
)
return paymentInvoice System Integration
<?php
// Generate invoice and payment request
function createInvoice($customerId, $items) {
$total = calculateTotal($items);
// Create payment request
$payment = SuzaaAPI::createPaymentRequest([
'amount' => $total,
'description' => "Invoice #" . generateInvoiceNumber(),
'expiryTime' => 2880, // 48 hours
'redirectUrl' => "https://myapp.com/invoices/paid"
]);
// Create invoice record
$invoice = Invoice::create([
'customer_id' => $customerId,
'items' => json_encode($items),
'total' => $total,
'suzaa_link_id' => $payment['linkId'],
'suzaa_payment_url' => $payment['paymentUrl'],
'status' => 'unpaid'
]);
// Send invoice email with payment link
sendInvoiceEmail($customerId, $invoice, $payment['paymentUrl']);
return $invoice;
}Error Handling
Error Response Structure
{
"success": false,
"error": {
"code": "ERROR_CODE",
"message": "Human-readable error description",
"field": "fieldName" // Optional: which field caused the error
}
}Common Error Codes
| HTTP Status | Error Code | Description | Solution |
|---|---|---|---|
| 400 | INVALID_AMOUNT | Amount is not a valid number or is negative | Send positive number as string or number |
| 400 | INVALID_DESCRIPTION | Description is missing or too long | Provide description under 500 chars |
| 400 | INVALID_EXPIRY_TIME | Expiry time not in valid range (1-1440) | Use minutes between 1 and 1440 |
| 400 | INVALID_REDIRECT_URL | URL format is invalid | Use absolute URL with http:// or https:// |
| 400 | MISSING_REQUIRED_FIELD | Required field not provided | Check request body has all required fields |
| 401 | INVALID_API_KEY | API key is missing or invalid | Check Authorization header and API key |
| 401 | EXPIRED_API_KEY | API key has expired | Generate new API key |
| 403 | INSUFFICIENT_PERMISSIONS | API key doesn’t have required permissions | Check API key permissions in dashboard |
| 404 | PAYMENT_REQUEST_NOT_FOUND | Payment request ID doesn’t exist | Verify the payment request ID |
| 409 | PAYMENT_REQUEST_ALREADY_COMPLETED | Cannot modify completed payment request | Create new payment request |
| 429 | RATE_LIMIT_EXCEEDED | Too many requests | Wait and retry with exponential backoff |
| 500 | INTERNAL_SERVER_ERROR | Server error | Retry request, contact support if persists |
| 503 | SERVICE_UNAVAILABLE | Service temporarily unavailable | Retry with exponential backoff |
Error Handling Best Practices
async function createPaymentWithRetry(data, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const response = await suzaaAPI.createPaymentRequest(data);
return response.data;
} catch (error) {
if (error.response) {
const status = error.response.status;
const errorCode = error.response.data.error?.code;
// Don't retry client errors (4xx)
if (status >= 400 && status < 500) {
if (errorCode === 'RATE_LIMIT_EXCEEDED' && attempt < maxRetries) {
// Wait before retry
await sleep(2000 * attempt);
continue;
}
// Other 4xx errors - don't retry
throw new Error(`API Error: ${error.response.data.error.message}`);
}
// Retry server errors (5xx)
if (status >= 500 && attempt < maxRetries) {
await sleep(1000 * Math.pow(2, attempt)); // Exponential backoff
continue;
}
}
// Network error or max retries reached
if (attempt === maxRetries) {
throw error;
}
await sleep(1000 * attempt);
}
}
}Best Practices
1. Store Payment Details
Always store these fields from the API response:
{
paymentRequestId: "fd74f812...", // For API queries
linkId: "825757/20251213/0007", // Human-readable reference
paymentUrl: "https://app.suzaa.com/...", // For customer
expiresAt: "2025-12-13T15:52:28.353Z" // To check expiration
}2. Handle Expiration
// Check if payment request expired
function isExpired(expiresAt) {
return new Date(expiresAt) < new Date();
}
// If expired, create new payment request
if (isExpired(payment.expiresAt)) {
payment = await createNewPaymentRequest(order);
}3. Verify Blockchain Transactions
CRITICAL: Always verify transactions on the blockchain before fulfilling orders!
// Example verification workflow
async function verifyPayment(order, linkId) {
// 1. Check your wallet for incoming transactions
const wallet = await getWalletTransactions(YOUR_WALLET_ADDRESS);
// 2. Find transaction matching:
// - Amount (within acceptable range due to fees)
// - Timestamp (around when customer claimed payment)
const payment = wallet.transactions.find(tx => {
const amountMatch = Math.abs(tx.amount - order.total_crypto) < 0.0001;
const timeMatch = Math.abs(tx.timestamp - order.claimed_at) < 3600; // 1 hour
return amountMatch && timeMatch;
});
// 3. Verify confirmations
if (payment && payment.confirmations >= REQUIRED_CONFIRMATIONS) {
// Payment verified!
return { verified: true, txHash: payment.hash };
}
return { verified: false };
}4. Handle redirectUrl Properly
// Build complete redirect URL with all necessary info
const redirectUrl = new URL('https://mysite.com/payment-callback');
redirectUrl.searchParams.set('order_id', order.id);
redirectUrl.searchParams.set('session', session.id);
// Don't add payment status here - Suzaa will add it
const payment = await suzaa.createPaymentRequest({
amount: order.total,
description: `Order #${order.id}`,
expiryTime: 60,
redirectUrl: redirectUrl.toString()
});5. Provide Clear Customer Communication
// After customer claims payment
async function handlePaymentClaimed(order) {
// Update order status
order.status = 'pending_verification';
await order.save();
// Send confirmation email
await sendEmail(order.customer.email, {
subject: 'Payment Received - Awaiting Confirmation',
body: `
Thank you for your payment!
We've received notification that you sent payment for Order #${order.id}.
We're now verifying your transaction on the blockchain. This usually
takes a few minutes to a few hours depending on network conditions.
Once verified, we'll send you a shipping confirmation.
Payment Details:
- Amount: ${order.total} USD
- Suzaa Payment ID: ${order.suzaa_link_id}
`
});
}6. Implement Timeout Handling
// Set timeout for blockchain verification
setTimeout(async () => {
const order = await getOrder(orderId);
if (order.status === 'pending_verification') {
// Still not verified after X hours
await sendEmail(order.customer.email, {
subject: 'Payment Verification Delayed',
body: `
We're still waiting to see your payment on the blockchain.
Please ensure you:
1. Sent the correct amount
2. Sent to the correct address
3. Paid the network fee
If you need assistance, please reply to this email with your
transaction hash.
`
});
}
}, 6 * 60 * 60 * 1000); // 6 hours7. Use Idempotency
// Prevent duplicate payment requests for same order
async function getOrCreatePaymentRequest(order) {
// Check if payment request already exists
if (order.suzaa_link_id) {
const existing = await suzaa.getPaymentRequest(order.suzaa_payment_request_id);
// Reuse if not expired
if (new Date(existing.expiresAt) > new Date()) {
return existing;
}
}
// Create new payment request
const payment = await suzaa.createPaymentRequest({
amount: order.total,
description: `Order #${order.id}`,
expiryTime: 60,
redirectUrl: getRedirectUrl(order.id)
});
// Store with order
order.suzaa_payment_request_id = payment.paymentRequestId;
order.suzaa_link_id = payment.linkId;
order.suzaa_payment_url = payment.paymentUrl;
await order.save();
return payment;
}Webhooks (Future)
Note: Webhooks are not currently implemented but are planned for future releases.
Planned Webhook Events
// When customer claims payment
{
"event": "payment.claimed",
"paymentRequestId": "fd74f812...",
"linkId": "825757/20251213/0007",
"timestamp": "2025-12-13T15:52:28.353Z"
}
// When payment request expires
{
"event": "payment.expired",
"paymentRequestId": "fd74f812...",
"linkId": "825757/20251213/0007",
"timestamp": "2025-12-13T16:52:28.353Z"
}Currently, use the redirect URL mechanism for callbacks.
Security
API Key Security
# Good: Environment variable
export SUZAA_API_KEY="sza_live_YOUR_KEY"
# Bad: Hardcoded
const apiKey = "sza_live_YOUR_KEY"; // DON'T DO THIS
# Good: Use in code
const apiKey = process.env.SUZAA_API_KEY;HTTPS Only
Always use HTTPS in production. Suzaa API rejects HTTP requests.
Input Validation
// Validate amounts
function validateAmount(amount) {
const num = parseFloat(amount);
if (isNaN(num) || num <= 0 || num > 1000000) {
throw new Error('Invalid amount');
}
return num.toFixed(2);
}
// Validate redirect URLs
function validateRedirectUrl(url) {
try {
const parsed = new URL(url);
if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {
throw new Error('Invalid protocol');
}
return url;
} catch (e) {
throw new Error('Invalid URL');
}
}Preventing Double-Spending
Customers might try to use the same payment for multiple orders:
// Track which payment links have been used
async function checkPaymentNotUsed(linkId) {
const existingOrder = await Order.findOne({ suzaa_link_id: linkId });
if (existingOrder && existingOrder.status !== 'cancelled') {
throw new Error('Payment link already used');
}
}Rate Limits
Current Limits
| Endpoint | Rate Limit | Window |
|---|---|---|
POST /payments/requests | 100 requests | per minute |
GET /payments/requests | 300 requests | per minute |
GET /payments/requests/{id} | 300 requests | per minute |
DELETE /payments/requests/{id} | 50 requests | per minute |
GET /public/payment/{linkId} | 1000 requests | per minute |
Rate Limit Headers
Responses include rate limit information:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1702468800Handling Rate Limits
async function createPaymentWithRateLimit(data) {
try {
return await suzaa.createPaymentRequest(data);
} catch (error) {
if (error.response?.status === 429) {
const resetTime = error.response.headers['x-ratelimit-reset'];
const waitTime = (resetTime * 1000) - Date.now();
console.log(`Rate limited. Waiting ${waitTime}ms`);
await sleep(waitTime);
// Retry
return await suzaa.createPaymentRequest(data);
}
throw error;
}
}Testing
Test API Keys
Use test API keys (starting with sza_test_) for development:
const apiKey = process.env.NODE_ENV === 'production'
? process.env.SUZAA_LIVE_KEY
: process.env.SUZAA_TEST_KEY;Test Mode Behavior
Test mode:
- Uses test wallet addresses
- Payments don’t require real cryptocurrency
- All features work identically to production
- Data isolated from production
Example Test
describe('Suzaa Payment Integration', () => {
it('should create payment request', async () => {
const payment = await suzaa.createPaymentRequest({
amount: '100.00',
description: 'Test Order',
expiryTime: 60
});
expect(payment).toHaveProperty('paymentUrl');
expect(payment).toHaveProperty('linkId');
expect(payment.paymentUrl).toContain('app.suzaa.com');
});
it('should handle expired payment', async () => {
const payment = await suzaa.createPaymentRequest({
amount: '50.00',
description: 'Test',
expiryTime: 1 // 1 minute
});
// Wait for expiration
await sleep(65000); // 65 seconds
const details = await suzaa.getPaymentRequest(payment.paymentRequestId);
expect(details.status).toBe('expired');
});
});Appendix
Complete TypeScript Interface
// Request Types
interface CreatePaymentRequest {
amount: string | number;
description: string;
expiryTime: number;
redirectUrl?: string;
}
// Response Types
interface PaymentRequestResponse {
success: true;
data: {
success: true;
paymentRequestId: string;
linkId: string;
paymentUrl: string;
expiresAt: string;
message: string;
};
}
interface PaymentRequestDetails {
paymentRequestId: string;
linkId: string;
amount: string;
description: string;
status: 'pending' | 'claimed_paid' | 'canceled' | 'expired';
paymentUrl: string;
redirectUrl: string | null;
expiryTime: number;
expiresAt: string;
createdAt: string;
claimedAt: string | null;
}
interface ErrorResponse {
success: false;
error: {
code: string;
message: string;
field?: string;
};
}
// API Client
class SuzaaAPI {
constructor(private apiKey: string, private baseUrl: string = 'https://api.suzaa.com') {}
async createPaymentRequest(data: CreatePaymentRequest): Promise<PaymentRequestResponse['data']> {
const response = await fetch(`${this.baseUrl}/payments/requests`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
const result = await response.json();
if (!result.success) {
throw new Error(result.error.message);
}
return result.data;
}
async getPaymentRequest(id: string): Promise<PaymentRequestDetails> {
const response = await fetch(`${this.baseUrl}/payments/requests/${id}`, {
headers: {
'Authorization': `Bearer ${this.apiKey}`
}
});
const result = await response.json();
if (!result.success) {
throw new Error(result.error.message);
}
return result.data;
}
}Support
Getting Help
- Documentation: https://docs.suzaa.com
- API Status: https://status.suzaa.com
- Support Email: api-support@suzaa.com
- GitHub Issues: https://github.com/suzaadev/suzaa-api/issues
Reporting Issues
When reporting API issues, include:
- Endpoint used
- Request body (remove sensitive data)
- Response received
- Expected behavior
- Timestamp of request
- Your merchant ID (not API key!)
Last Updated: December 13, 2024
API Version: v1
Document Version: 1.0.0