API reference
All endpoints are relative to your environment base URL:
| Environment | Base URL |
|---|---|
| Staging | https://biz-app.staging.pingbusiness.org |
| Production | https://biz-app.pingbusiness.org |
Every request requires the three authentication headers. Requests and responses are JSON (Content-Type: application/json) unless noted. Conventions (money as decimal strings, status letters, error shapes) are collected in Errors & conventions.
Endpoint index
| Area | Endpoints |
|---|---|
| Merchant & store | GET /merchants · GET /stores · GET /store/{id} |
| Customers | GET /customers · GET /customer/{id} · POST /customer |
| Products & media | GET /products · GET /product/{id} · GET /files · GET /file/{id} · GET /image/{id} · GET /download |
| Inventory | GET /inventories |
| Checkout intents | POST /intent · GET /intents · GET /intent/{id} |
| Hosted checkout | POST /pa/checkout |
| Payment recording | POST /paymentasia/record_payment |
| Subscriptions | POST /recurring/checkout · POST /recurring/tokenization/record · POST /recurring/payment/record |
| Orders | GET /orders · GET /order/{id} |
| Order items | GET /order_items · GET /order_item/{id} |
| Payments | GET /payments · GET /payment/{id} |
Merchant & store
GET /merchants
Returns your own merchant record (integration credentials are scoped to one merchant).
Query parameters
| Parameter | Type | Notes |
|---|---|---|
identifier |
string | Your merchant identifier. Recommended, for an unambiguous single-element result. |
Returns — 200, an array with your merchant object:
[
{
"id": 7,
"identifier": "9b2f0e64-…",
"name": "Acme HK Limited",
"status": "A",
"kyb_status": "approved",
"payment_gateway": "PAYMENT_ASIA",
"payment_networks": "CreditCard,Fps,PayMe",
"created_at": "…"
}
]
payment_networks is a comma-separated string — split it to get the list your checkout may offer. The API key hash is never returned.
GET /stores
Returns stores in your scope. Filter with identifier (your store UUID) to resolve your configured store's numeric id.
Returns — 200, array of Store objects:
[
{
"id": 12,
"identifier": "2c7a1d90-…",
"merchant_id": 7,
"name": "Web Store",
"mode": "T",
"details": null,
"created_at": "…",
"updated_at": null
}
]
mode is T (test) or L (live), switched from the Merchant Portal.
GET /store/{id}
Returns one store by numeric ID. Must be your configured store — anything else is 403/404.
Customers
Customer records attach checkouts to a buyer. Map your site's user IDs to Ping Business customers via the username field and reuse the records.
The Customer object
| Field | Type | Notes |
|---|---|---|
id |
integer | Numeric ID — used in customer_id fields. |
identifier |
string | Public UUID-like identifier. |
merchant_id, store_id |
integer | Your scope. Immutable. |
first_name, last_name |
string | Required on create. |
email |
string, nullable | |
phone |
string, nullable | Max 16 characters. |
shipping_address, billing_address |
string, nullable | Free-form. |
username |
string, nullable | Your stable external key for the buyer (e.g. your user ID). |
details |
string, nullable | Free text; may hold a JSON string. |
created_at, updated_at |
date/time string | Read-only. |
POST /customer — create
Create mode is selected when the body has no id.
Body parameters
| Parameter | Required | Notes |
|---|---|---|
merchant_id |
Yes | Your merchant's numeric ID; must match your credential scope. |
store_id |
Yes | Your store's numeric ID; must match your credential scope. |
first_name |
Yes | |
last_name |
Yes | |
email, phone, shipping_address, billing_address, username, details |
No | See the object table. |
Returns — 201:
{ "id": 42 }
Errors — 400 missing/invalid fields or store not in merchant; 403 outside your credential scope; 404 merchant/store not found.
POST /customer — update
Update mode is selected when the body contains id. Send only the fields to change; identifier, merchant_id, store_id, created_at, updated_at are immutable.
Returns — 200 { "message": "Customer updated successfully" }.
GET /customers
With integration credentials this is a lookup, not a browse: all three parameters below are required and username is matched exactly, returning at most one mapping.
| Parameter | Required | Notes |
|---|---|---|
merchant_id |
Yes | Must equal your scope. |
store_id |
Yes | Must equal your scope. |
username |
Yes | Exact match on your external key. |
Returns — 200, array of matching Customer objects (empty if none).
GET /customer/{id}
Returns one customer by numeric ID, if in your scope. 404 when absent, 403 outside scope.
Products & media
Products are managed in the Merchant Portal; the API surface is read-only. Only approved products (state: "A") can be sold.
The Product object
| Field | Type | Notes |
|---|---|---|
id |
integer | Used in cart lines and inventory/file filters. |
identifier |
string, nullable | Your SKU-like public identifier. |
store_id |
integer | |
name |
string | |
description |
string, nullable | |
details |
string, nullable | |
amount |
decimal string | Current unit price. |
currency |
string | 3 uppercase letters. |
recurring_frequency |
string, nullable | WEEKLY / MONTHLY / YEARLY; null for one-time products. |
recurring_intervals |
integer, nullable | |
recurring_total_execution_times |
integer, nullable | |
state |
string | A = approved/purchasable. |
created_at, updated_at |
date/time string |
A product is a subscription product when all three recurring fields are set. Never mix subscription products into an ordinary checkout.
GET /products
| Parameter | Type | Notes |
|---|---|---|
state |
string | Use A to list only purchasable products. |
name |
string | Case-insensitive substring match. |
description |
string | Case-insensitive substring match. |
identifier |
string | Case-insensitive substring match. |
recurring |
boolean-like | true = subscription products only; false = one-time only. |
store_id |
integer | Optional; must equal your configured store. |
Returns — 200, Product[] sorted by name.
GET /product/{id}
Returns one product. 404 when absent, 403 outside your store.
GET /files
Product file metadata (images, attachments).
| Parameter | Type | Notes |
|---|---|---|
product_id |
integer | Filter to one product's files. |
Returns — 200, array of ProductFile objects:
| Field | Type | Notes |
|---|---|---|
id |
integer | Use with /image/{id} and /download?file_id={id}. |
product_id |
integer | |
name |
string | Filename. |
description |
string, nullable | The value __PINGBIZ_MAIN_TITLE_IMAGE__ conventionally marks the primary image. |
size |
integer | Bytes. |
mime_type |
string | Files with an image/* type can be served via /image/{id}. |
location |
string | Internal storage metadata — never build a public URL from it. |
GET /file/{id}
One file's metadata.
GET /image/{id}
The image bytes (inline content disposition, stored MIME type). 400 if the file isn't an image. Proxy these through your backend or cache them — don't expose your API credentials to fetch them client-side.
GET /download
| Parameter | Required | Notes |
|---|---|---|
file_id |
Yes | File to download. |
Returns the file bytes as an attachment with the stored filename.
Inventory
GET /inventories
| Parameter | Required | Notes |
|---|---|---|
product_id |
Yes | The product to check. |
location |
No | Case-insensitive substring filter. |
Returns — 200, array of rows:
[
{ "id": 9, "product_id": 101, "quantity": 25, "location": "", "updated_at": "…" }
]
Total availability is the sum of all rows (quantities can be zero or negative; empty array = zero). Revalidate availability just before creating an intent.
Checkout intents
An intent is a checkout in progress. See the payments guide for the lifecycle.
The Intent object
| Field | Type | Notes |
|---|---|---|
id |
integer | |
identifier |
string | The checkout ID — used in /pa/checkout and status polling. |
order_id |
integer, nullable | Set by the platform after verified success. |
merchant_id, store_id, customer_id |
integer | Scope; immutable. |
currency |
string | Immutable after creation. |
amount |
decimal string | Immutable after creation. |
status |
string | C / R / S / F / U — see statuses. |
reference |
string, nullable | Your merchant reference (unique, ≤ 36 chars). |
intent_details |
string | Your immutable JSON checkout snapshot. |
details |
string, nullable | Mutable workflow metadata. |
expires_at |
date/time string, nullable | Optional expiry you may set. |
created_at, updated_at |
date/time string |
POST /intent — create
Create mode is selected when the body has no id.
Body parameters
| Parameter | Required | Notes |
|---|---|---|
customer_id |
Yes | The buyer. |
store_id |
Yes | Your store's numeric ID. |
currency |
Yes | 3-letter code; must match every cart product's currency. |
amount |
Yes | Decimal string — the checkout total your server computed from current approved prices. |
status |
No | Default C. Integration credentials may not create S intents (403). |
reference |
Recommended | Your merchant reference: globally unique, ≤ 36 characters. Use a fresh UUID4. |
intent_details |
Yes for checkout | JSON string. See below. |
expires_at |
No | ISO-8601 date/time. |
Do not send merchant_id (derived from your credentials — 400 if present) or order_id (checkout intents are order-less; the order comes after payment).
intent_details schema (serialized as a JSON string):
{
"source": "my_site_checkout",
"merchant_reference": "<same value as reference>",
"checkout_kind": "ordinary",
"payment_network": "CreditCard",
"cart": [ { "product_id": 101, "quantity": 2 } ],
"line_items": [
{ "product_id": 101, "quantity": 2, "unit_amount": "150.00", "amount": "300.00" }
],
"subject": "Order d3a9c2be"
}
For subscriptions use checkout_kind: "subscription", a single-line snapshot, and a subscription: {product_id, quantity} object (Subscriptions). The snapshot is immutable and is revalidated at launch: line totals must equal unit_amount × quantity, sum to the intent amount, and match current approved product prices.
Returns — 201:
{ "id": 555, "identifier": "0f0e2b1a-…" }
POST /intent — update
Update mode is selected when the body contains id. Integration credentials may update only status, details, and expires_at — and may never set status: "S" (403; success comes only from the verified payment flow). identifier, scope fields, currency, amount, and created_at are immutable (400).
Returns — 200 { "message": "Intent updated successfully" }.
GET /intents
| Parameter | Type | Notes |
|---|---|---|
identifier |
string | Poll one checkout by its checkout ID. |
status |
string | Filter by status letter (e.g. R for reconciliation sweeps). |
customer_id |
integer | |
created_from, created_to |
ISO-8601 | Inclusive lower / exclusive upper creation-time bounds. |
Returns — 200, Intent[] scoped to your store.
GET /intent/{id}
One intent by numeric ID.
Hosted checkout
POST /pa/checkout
Validates an ordinary checkout intent end-to-end and returns a signed launch form for the gateway's hosted payment page. One launch is signed per intent; identical retries return the stored response, conflicting or terminal checkouts return 409.
Body parameters
| Parameter | Required | Notes |
|---|---|---|
intent_identifier |
Yes | The intent's identifier. |
merchant_reference |
Yes | Must equal the intent's reference. |
currency |
Yes | Must equal the intent currency. |
amount |
Yes | Decimal string; must equal the intent amount. |
network |
Yes | One of your enabled payment networks — exact value (403 if disabled). |
return_url |
Yes | Public HTTPS URL on your site the customer's browser returns to. |
notify_url |
Yes | Public HTTPS URL on your backend for the authoritative server callback. |
customer_ip |
Yes | The paying customer's IP. |
customer_first_name, customer_last_name |
Yes | |
customer_email |
Yes | |
customer_phone |
Yes | |
customer_address |
Yes | Billing or shipping address; "N/A" if genuinely unavailable. |
customer_state, customer_country |
No | Default HK. |
customer_postal_code |
No | Default 000000. |
subject |
No | Payment subject shown to the customer; default Order <merchant_reference>. |
lang |
No | Gateway page language hint. |
generic |
No | Send false. |
Validation performed: intent exists, is yours, and is launchable; snapshot re-verified against current approved products and prices; network enabled; amounts consistent; Octopus totals an exact multiple of HKD 0.10; callback URLs sane. Any failure returns 400/403/409 before anything is signed.
Returns — 200:
{
"action_url": "https://payment.paymentasia.com/…",
"fields": { "…signed form fields…": "…", "sign": "…" }
}
Render fields as hidden inputs in a form POSTing to action_url in the customer's browser and auto-submit (quickstart step 5). Submit the fields exactly as returned — any change breaks the signature.
Payment recording
POST /paymentasia/record_payment
The only way a one-time checkout becomes successful. Forward the raw callback payload the gateway delivered to your notify_url (or a signed return_url hit); Ping Business verifies the signature and settles the checkout.
Body parameters
| Parameter | Required | Notes |
|---|---|---|
intent_identifier |
Yes | The checkout being settled. |
payload |
Yes | The gateway's callback fields, unmodified — the signature covers them. |
On verified success, atomically: the order is created, order items are materialized from the intent's line_items snapshot, a Payment (status: "S") is recorded, and the intent becomes S.
Returns — 200:
{
"ok": true,
"payment": { "payment_id": 77, "order_id": 123, "status": "S", "idempotent": false }
}
A verified failed result marks the intent F and returns a result without creating a payment. Exact replays return "idempotent": true — always safe to forward duplicates.
Errors — 400 invalid signature, or reference/amount/currency mismatch with the intent; 404 unknown intent; 409 conflicting terminal state.
Subscriptions
See the subscriptions guide for the full flow and constraints (HKD, CreditCard, single product, next-day start).
POST /recurring/checkout
Launches card tokenization + schedule enrollment for a subscription intent.
| Parameter | Required | Notes |
|---|---|---|
intent_identifier |
Yes | A checkout_kind: "subscription" intent. |
customer_ip |
Yes | |
return_url |
Yes | Browser return after tokenization. |
notify_url |
Yes | Server-to-server tokenization result callback. |
payment_notify_url |
Yes | Server-to-server callback for every future scheduled charge — keep it live for the schedule's lifetime. |
subject |
No | |
token_valid_date |
No | YYYY-MM-DD card-token expiry hint. |
Returns — 200 with accepted: true and a redirect link; send the customer's browser there:
{
"accepted": true,
"normalized_provider_result": { "redirect_link": "https://…" }
}
Read the link from normalized_provider_result.redirect_link, provider_response.payload.redirect_link, or top-level redirect_link, in that order.
POST /recurring/tokenization/record
Verifies and records the tokenization/enrollment result. Same forward-the-payload contract as record_payment; idempotent.
| Parameter | Required | Notes |
|---|---|---|
intent_identifier |
Yes | |
payload |
Yes | Raw gateway tokenization callback. |
Returns — 200:
{
"ok": true,
"checkout": {
"order_id": 123,
"status": "COMPLETE",
"idempotent": false,
"order_items": [ { "recurring_status": "ACTIVE", "recurring_merchant_reference": "…" } ]
}
}
Creates the order with exactly one recurring order item. No one-time Payment row is created for enrollment.
POST /recurring/payment/record
Verifies and records one scheduled charge execution. Forward every payload your payment_notify_url receives; idempotent.
| Parameter | Required | Notes |
|---|---|---|
payload |
Yes | Raw gateway execution callback (form fields or JSON). |
Returns — 200:
{
"ok": true,
"recurring_payment": {
"id": 801,
"order_item_id": 901,
"execution_number": 2,
"amount": "29.90",
"currency": "HKD",
"status": "SUCCESS",
"idempotent": false
}
}
Orders
Orders are created by the platform after verified payment (or accepted subscription enrollment) and are read-only. There is no POST /order.
The Order object
| Field | Type | Notes |
|---|---|---|
id |
integer | |
identifier |
string | Public order identifier. |
merchant_id, store_id, customer_id |
integer | |
status |
string | Single letter — see status letters. |
currency |
string | |
subtotal_amount, total_amount |
decimal string | |
details |
string, nullable | Server-generated metadata. |
created_at, updated_at |
date/time string |
GET /orders
| Parameter | Type | Notes |
|---|---|---|
customer_id |
integer | |
status |
string | Single-letter filter. |
created_from, created_to |
ISO-8601 | Creation-time bounds. |
Returns — 200, Order[] newest first, scoped to your store.
GET /order/{id}
One order. 404 absent; 403 outside scope.
Order items
The OrderItem object
| Field | Type | Notes |
|---|---|---|
id |
integer | |
order_id |
integer | |
order_status |
string, nullable | Parent order's status, included for convenience. |
product_id |
integer | |
quantity |
integer | |
unit_amount, amount |
decimal string | Checkout-time prices (immutable history). |
state |
string, nullable | D = delivered; null = not marked delivered. |
recurring_start_date |
date string, nullable | Subscription lines only. |
recurring_frequency, recurring_intervals, recurring_total_execution_times |
— | Copied plan (subscription lines). |
recurring_merchant_reference |
string, nullable | Provider schedule reference. |
recurring_status |
string, nullable | PENDING / ACTIVE / CANCELLED / COMPLETED / ERROR / UNKNOWN. |
subscription_details |
object, nullable | Sanitized audit data — raw card/tokenization material is never included. |
created_at, updated_at |
date/time string |
GET /order_items
| Parameter | Type | Notes |
|---|---|---|
order_id |
integer | All lines of one order. |
product_id |
integer | All lines for one product. |
Returns — 200, OrderItem[].
GET /order_item/{id}
One order item.
Payments
Verified successful one-time payments. Read-only; created exclusively by record_payment.
The Payment object
| Field | Type | Notes |
|---|---|---|
id |
integer | |
identifier |
string | Public payment identifier. |
order_id |
integer | |
merchant_id, store_id |
integer | |
currency |
string | |
amount |
decimal string | Verified amount. |
status |
string | S on success. |
reference |
string, nullable | Gateway reference — use it to match settlement reports and in support requests. |
details |
string, nullable | Provider metadata (normally JSON text). |
created_at |
date/time string |
GET /payments
| Parameter | Type | Notes |
|---|---|---|
order_id |
integer | |
status |
string | Single-letter filter (S). |
currency |
string | 3-letter code. |
identifier |
string | Case-insensitive substring match. |
reference |
string | Case-insensitive substring match. |
created_from, created_to |
ISO-8601 | Inclusive lower / exclusive upper bounds (timezone-aware input normalized to UTC). |
Returns — 200, Payment[] newest first.
GET /payment/{id}
One payment.