Providers Paylinks API
Unified payment link management across all BNPL and direct payment providers
Unified Providers Paylinks API
A single, provider-agnostic API for BNPL payment links (Tabby, Tamara, MisPay). GET /api/v1/providers also lists gateway sync processors (stripe, moyasar, tap, dinero, paypal, hyperpay); those IDs are not valid for paylink create — use Payments Orders instead.
Live on the Rust host (DV-73): GET /providers, GET /providers/config/{provider}, list/get/cancel paylinks, create, and capture all answer. GET /providers/{provider}/config is not registered (mux 404). GET …/paylinks/{id} polls Tabby / Tamara / MisPay the way Go does; a vendor error is swallowed and the stored row is still 200. No live BNPL in the differential corpus — vendor I/O is a recorded stub. Auth still runs first.
Served endpoints require a Bearer token. Errors on this family use the platform envelope ({"data":{},"message":"…","status":N}), sentenized, not {"error","code"}.
Supported Providers
| Provider | Type | Checkout / role | Regions |
|---|---|---|---|
tabby | bnpl | QR code paylinks | KSA, UAE, Kuwait |
tamara | bnpl | SMS paylinks | KSA, UAE, Bahrain |
mispay | bnpl | QR code paylinks | KSA |
stripe | gateway_sync | Stripe PaymentIntents sync (stripe-payments) | Global (account) |
moyasar | gateway_sync | Moyasar payments sync (moyasar-payments) | MENA |
tap | gateway_sync | Tap charges list sync (tap-payments) | GCC / Tap markets |
dinero | gateway_sync | Dinero status refresh (dinero-payments) | KSA / Dinero markets |
paypal | gateway_sync | PayPal reporting sync (paypal-commerce) | Global (account) |
hyperpay | gateway_sync | OPPWA query sync (hyperpay-checkout) | Per acquirer / host |
List Providers
Returns all registered payment providers and their metadata.
GET /api/v1/providers
Authorization: Bearer <token>Response
Each entry includes id, name, description, type, regions, and when applicable app_slug (integration app used by Payments Orders).
{
"providers": [
{
"id": "dinero",
"name": "Dinero Pay",
"description": "Dinero Pay: refresh configured payment_ids…",
"type": "gateway_sync",
"regions": "KSA and supported Dinero markets",
"app_slug": "dinero-payments"
},
{
"id": "mispay",
"name": "MisPay",
"description": "Split-in-4 BNPL with QR code checkout for POS",
"type": "bnpl",
"regions": "KSA",
"app_slug": "mispay-pos-paylinks"
}
],
"total": 9
}Provider Configuration
Returns provider-specific configuration, required fields, and endpoint documentation.
GET /api/v1/providers/config/{provider}
Authorization: Bearer <token>Create Payment Link
POST /api/v1/providers/paylinks
Authorization: Bearer <token>
Content-Type: application/jsonServed. Create talks to Tabby / Tamara / MisPay through PaylinkVendorPort (PgPaylinkVendor on the live money bundle). Gateway-sync ids (stripe, paypal, …) stay 400. Differential verification used a recorded httptest stub — no live BNPL.
The field table below is the Go contract (GET /providers/config/{provider} still lists these required_fields).
Common Fields
| Field | Type | Required | Description |
|---|---|---|---|
provider | string | ✅ | BNPL provider ID only: tabby, tamara, mispay (not stripe / moyasar / tap / dinero) |
org_app_id | string | ✅ | Organization app ID for the installed provider app |
order_id | string | ✅ | Your order reference ID |
amount | string/number | ✅ | Payment amount (e.g. "100.00") |
currency | string | ✅ | Currency code (SAR, AED, KWD) |
description | string | Order description | |
buyer_name | string | Customer name | |
buyer_email | string | Customer email | |
buyer_phone | string | Customer phone | |
lang | string | Checkout language (ar or en) | |
items | array | Line items | |
metadata | object | Provider-specific metadata |
Provider-Specific Fields
Tamara
| Field | Type | Description |
|---|---|---|
phone_number | string | Required. Customer phone for SMS checkout |
payment_type | string | pay_by_instalments (default) or pay_by_later |
store_code | string | Store identifier for multi-store setups |
locale | string | Locale for checkout page (defaults to ar_SA) |
The React App Store Tabby step lists and cancels existing rows. It does not offer create.
Get Payment Link Status
GET /api/v1/providers/paylinks/{id}
Authorization: Bearer <token>Retrieves the stored row after Go's refreshProviderPaylinkStatus poll (Tabby RetrievePayment, Tamara GetOrderDetails, MisPay TrackCheckout). A vendor error is swallowed and the stored row is still 200. A missing or cross-tenant id is 404 Payment link not found. (not an existence oracle). Amount and the other scalar fields are strings. List and get share this shape. List does not refresh.
Response
{
"id": "abc123",
"provider": "tabby",
"org_app_id": "...",
"order_id": "POS-1234",
"status": "authorized",
"amount": "150.00",
"currency": "SAR",
"checkout_url": "https://checkout.tabby.sa/...",
"qr_code_url": "https://checkout.tabby.sa/qr/..."
}List Payment Links
GET /api/v1/providers/paylinks?provider=tabby&org_app_id=YOUR_ORG_APP_ID
Authorization: Bearer <token>{"items":[…],"total":n}. Optional query parameters:
provider— Filter by provider (e.g.tabby,tamara,mispay)org_app_id— Filter by organization app
An empty list is also the answer when the org has no installs, the requested org_app_id is not one of them, or the query failed. A client cannot tell those apart.
Capture/Finalize Payment
POST /api/v1/providers/paylinks/{id}/capture
Authorization: Bearer <token>
Content-Type: application/jsonServed. Capture / finalize talks to the same vendor port. A missing or cross-tenant id is 404 Payment link not found. MisPay needs a checkout_id (GET refresh can fill it after create).
Cancel Payment Link
Cancels a pending payment link. Served. The local row is set to cancelled whether or not the vendor cancel succeeds (Tabby close / Tamara void+cancel errors are discarded; MisPay has no remote cancel). A missing id is 404 Payment link not found.
POST /api/v1/providers/paylinks/{id}/cancel
Authorization: Bearer <token>Response
{
"success": true,
"paylink_id": "abc123",
"provider": "tabby",
"status": "cancelled"
}Provider-specific endpoints
There are none. Every payment link operation goes through the unified /api/v1/providers/paylinks endpoints with "provider" in the body or query, and provider configuration is GET /api/v1/providers/config/{provider}.
POS Integration Flow
POS flow is create → display QR/SMS → wait → webhook → capture, same as Go:
- Create —
POST /api/v1/providers/paylinks(BNPL ids only) - List / get — cashier tracks rows; GET
{id}may refresh vendor status - Cancel —
POST …/{id}/cancelmarks the local row cancelled - Capture —
POST …/{id}/captureafter the vendor session is payable