EZ Payout API — Merchant Onboarding Guide

> **Base URL:** `https://payment.sa-by-dee.com`

> **Version:** v1 (current)

> **Auth:** Server-to-server Bearer token

> **Fee:** 5.5% platform fee (THB → THB local route)

> **Payout mode:** Manual admin gate (withdrawal within 24 hours)


1. Get Your API Token

Contact admin to receive:

  • `merchant_id` — your numeric merchant ID
  • `EZ_PAYOUT_API_TOKEN` — Bearer token for all API calls
  • Never expose this token in client-side code.** All calls must be server-to-server.


    2. Authentication

    Every request must include the token in one of two ways:

    # Preferred: X-EZ-Payout-Token header
    X-EZ-Payout-Token: <your_token>
    
    # Alternative: Authorization Bearer
    Authorization: Bearer <your_token>
    

    Merchant ID can be passed as:

  • Query param: `?merchant_id=1`
  • Header: `X-EZ-Merchant-ID: 1`
  • JSON body field: `{"merchant_id": 1}`

  • 3. API Endpoints

    3.1 Check Balances

    GET /api/ez-payout/balances?merchant_id=1
    

    Response (200):

    {
      "ok": true,
      "merchant_id": 1,
      "balances": [
        {
          "currency": "THB",
          "available_amount": 1865.00,
          "pending_amount": 0.00,
          "locked_amount": 0.00,
          "updated_at": "2026-06-14 22:04:26"
        }
      ]
    }
    

    3.2 Get Withdrawal Quote

    Get a fee quote before submitting. Quotes expire after 30 minutes.

    POST /api/ez-payout/quote
    Content-Type: application/json
    
    {
      "merchant_id": 1,
      "gross_amount": 1000,
      "source_currency": "THB",
      "payout_currency": "THB"
    }
    

    Response (200):

    {
      "ok": true,
      "quote": {
        "quote_id": "Q_abc123",
        "quote_expires_at": "2026-06-16T13:30:00Z",
        "source_currency": "THB",
        "payout_currency": "THB",
        "gross_amount": 1000.00,
        "platform_fee_rate": 0.055,
        "platform_fee_amount": 55.00,
        "net_source_amount": 945.00,
        "payout_amount": 945.00
      }
    }
    

    3.3 Submit Withdrawal Request

    Lock your balance and create a withdrawal request. This is **idempotent** — use the same `idempotency_key` to safely retry.

    POST /api/ez-payout/request
    Content-Type: application/json
    
    {
      "merchant_id": 1,
      "gross_amount": 1000,
      "source_currency": "THB",
      "payout_currency": "THB",
      "idempotency_key": "order-12345-withdraw-1",
      "quote_id": "Q_abc123",
      "destination": {
        "bank_code": "SCB",
        "account_number": "1234567890",
        "account_name": "Somchai Jaidee"
      }
    }
    

    Response (201):

    {
      "ok": true,
      "request": {
        "request_id": "WD_abc123",
        "status": "requested",
        "quote": { ... },
        "destination": { ... }
      }
    }
    

    If duplicate (200):

    {
      "ok": true,
      "duplicate": true,
      "request_id": "WD_abc123",
      "status": "requested"
    }
    

    Minimum withdrawal:** 100 THB gross

    3.4 Check Withdrawal Status

    GET /api/ez-payout/status?merchant_id=1&id=WD_abc123
    

    Response (200):

    {
      "ok": true,
      "request": {
        "request_id": "WD_abc123",
        "status": "submitted",
        "source_currency": "THB",
        "payout_currency": "THB",
        "gross_amount": 1000.00,
        "payout_amount": 945.00,
        "submit_mode": "manual_admin_gate",
        "created_at": "2026-06-16 10:00:00",
        "updated_at": "2026-06-16 10:05:00"
      }
    }
    

    3.5 List Withdrawal Requests

    GET /api/ez-payout/requests?merchant_id=1
    GET /api/ez-payout/requests?merchant_id=1&status=completed
    

    Response (200):

    {
      "ok": true,
      "merchant_id": 1,
      "requests": [ ... ]
    }
    


    4. Withdrawal State Machine

    requested → submitted → completed
         ↓           ↓
       failed ←──────┘ (rejected)
    

    | Status | Meaning |
    |--------|---------|
    | `requested` | Balance locked, waiting for admin review |
    | `submitted` | Admin approved, processing withdrawal |
    | `processing` | Provider transfer in progress |
    | `completed` | Money transferred, balance consumed |
    | `failed` | Rejected or provider failed — balance released back |


    5. Flow Summary

    1. [Your Server]  GET  /api/ez-payout/balances     → check available THB
    2. [Your Server]  POST /api/ez-payout/quote         → get fee breakdown
    3. [Your Server]  POST /api/ez-payout/request        → lock balance + create request
    4. [Admin]        Reviews & approves request
    5. [Admin]        Transfers money manually (bank/P2P)
    6. [Admin]        POST /api/ez-payout/admin/complete → mark as completed
    7. [Your Server]  GET  /api/ez-payout/status         → confirm completion
    

    Expected timeline:** Withdrawal within 24 hours (manual admin gate).


    6. Error Codes

    | HTTP | Error | Meaning |
    |------|-------|---------|
    | 400 | `merchant_id_invalid` | Missing or invalid merchant ID |
    | 400 | `ez_payout_balance_insufficient` | Not enough available balance |
    | 400 | `ez_payout_withdraw_request_not_found` | Request ID not found |
    | 403 | `ez_payout_api_token_invalid` | Wrong or missing token |
    | 404 | `ez_payout_route_not_found` | Wrong endpoint path |
    | 503 | `database_unavailable` | Temporary backend issue — retry |


    7. Idempotency

    All withdrawal requests are idempotent by `idempotency_key`.

    Generate a unique key per logical request (e.g., `order-{orderId}-withdraw-{attempt}`).

    Retrying with the same key returns the original result — safe for network failures.


    8. Security Requirements

  • **All API calls must be server-to-server** — never expose your token in browser/client code
  • Store the token as an environment variable on your server
  • Use HTTPS only (enforced)
  • Never log the raw token value

  • 9. Support

    For token issuance, balance questions, or withdrawal issues:

    Contact admin via the EZ Setup dashboard or LINE OA.