Quickstart: accept a payment
This guide takes you from an approved product to a verified, recorded payment on your existing website. Time: ~30 minutes.
You'll need: your three credentials (Getting started), at least one approved product, and a public HTTPS URL on your site for the payment callbacks.
Throughout, $BASE is https://biz-app.staging.pingbusiness.org and every request carries the three auth headers from Authentication.
Step 1 — Look up your catalog
Read your approved products so you checkout against live prices, never hard-coded ones:
curl "$BASE/products?state=A" -H "..."
[
{
"id": 101,
"identifier": "SKU-TSHIRT-01",
"store_id": 12,
"name": "Logo T-Shirt",
"amount": "150.00",
"currency": "HKD",
"recurring_frequency": null,
"state": "A"
}
]
Only products with state: "A" are purchasable. amount is a decimal string — do all money math in decimal, never binary floating point.
Step 2 — Create (or reuse) a customer
Each checkout is attached to a customer record in your store's scope. Map your own user IDs to Ping Business customer IDs and reuse them:
curl -X POST "$BASE/customer" -H "..." -H "Content-Type: application/json" -d '{
"merchant_id": 7,
"store_id": 12,
"first_name": "Ada",
"last_name": "Lovelace",
"email": "ada@example.com",
"phone": "+85212345678",
"billing_address": "1 Queen'\''s Road Central, Hong Kong",
"username": "your-site-user-id-8841"
}'
{ "id": 42 }
merchant_id, store_id, first_name, and last_name are required (the numeric IDs come from your merchant/store reads in Getting started). Use username to store your own stable user ID so you can find the record again with GET /customers?merchant_id=7&store_id=12&username=your-site-user-id-8841 (all three parameters are required for integration credentials, and username matches exactly).
Step 3 — Create a checkout intent
The intent is the checkout: it fixes the currency, the total, and an immutable snapshot of what's being bought. Your server computes the total from the product prices it just read.
curl -X POST "$BASE/intent" -H "..." -H "Content-Type: application/json" -d '{
"customer_id": 42,
"store_id": 12,
"currency": "HKD",
"amount": "300.00",
"status": "C",
"reference": "d3a9c2be-6d3f-4e0b-9a51-0c9f6a6c1234",
"intent_details": "{\"source\":\"my_site_checkout\",\"merchant_reference\":\"d3a9c2be-6d3f-4e0b-9a51-0c9f6a6c1234\",\"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\"}"
}'
{ "id": 555, "identifier": "0f0e2b1a-77aa-4a2e-bb0e-2a2d6f70abcd" }
Rules that matter here:
referenceis your merchant reference: it must be globally unique and at most 36 characters. A fresh UUID4 per checkout is the recommended pattern.intent_detailsis a JSON string (serialized object) containingcheckout_kind: "ordinary", the selectedpayment_network, and a fullline_itemssnapshot (product_id,quantity,unit_amount,amountper line). The API revalidates this snapshot against the current approved catalog when you launch checkout — stale prices or unapproved products are rejected.- Do not send
order_idormerchant_id— checkout intents are order-less (the order is created after payment), and the merchant is derived from your credentials. - The returned
identifieris yourcheckout_idfrom here on. Store it with the pending checkout in your database.
Step 4 — Request the signed payment launch
Ask the API for a signed gateway form for this intent:
curl -X POST "$BASE/pa/checkout" -H "..." -H "Content-Type: application/json" -d '{
"intent_identifier": "0f0e2b1a-77aa-4a2e-bb0e-2a2d6f70abcd",
"merchant_reference": "d3a9c2be-6d3f-4e0b-9a51-0c9f6a6c1234",
"currency": "HKD",
"amount": "300.00",
"network": "CreditCard",
"subject": "Order d3a9c2be",
"return_url": "https://shop.example.com/pingbiz/return/0f0e2b1a-77aa-4a2e-bb0e-2a2d6f70abcd",
"notify_url": "https://shop.example.com/pingbiz/notify/0f0e2b1a-77aa-4a2e-bb0e-2a2d6f70abcd",
"customer_ip": "203.0.113.10",
"customer_first_name": "Ada",
"customer_last_name": "Lovelace",
"customer_email": "ada@example.com",
"customer_phone": "+85212345678",
"customer_address": "1 Queen'\''s Road Central, Hong Kong",
"customer_state": "HK",
"customer_country": "HK",
"customer_postal_code": "000000",
"generic": false
}'
{
"action_url": "https://payment.paymentasia.com/...",
"fields": {
"merchant_reference": "d3a9c2be-6d3f-4e0b-9a51-0c9f6a6c1234",
"currency": "HKD",
"amount": "300.00",
"sign": "…signed by Ping Business…"
}
}
The API revalidates everything (network enabled for your merchant, amounts match the intent snapshot, products still approved and priced as snapshotted, callback URLs well-formed), then signs one launch per intent. Retrying the identical request returns the same saved response; a conflicting or already-terminal checkout returns 409.
Step 5 — Send the customer to pay
Render an auto-submitting form in the customer's browser with the returned action_url and fields:
<form id="pay" method="POST" action="{{action_url}}">
<!-- one hidden input per key in `fields` -->
<input type="hidden" name="merchant_reference" value="...">
<input type="hidden" name="sign" value="...">
...
</form>
<script>document.getElementById('pay').submit();</script>
The customer completes payment on the gateway's hosted page. Card data never touches your servers.
Step 6 — Handle the callbacks
Two things come back, and you must treat them very differently:
notify_url (authoritative). The gateway's server POSTs a signed result to your notify_url. Forward the raw payload straight to Ping Business for verification and recording:
curl -X POST "$BASE/paymentasia/record_payment" -H "..." -H "Content-Type: application/json" -d '{
"intent_identifier": "0f0e2b1a-77aa-4a2e-bb0e-2a2d6f70abcd",
"payload": { "...every field the gateway posted, unmodified..." }
}'
{
"ok": true,
"payment": { "payment_id": 77, "order_id": 123, "status": "S", "idempotent": false }
}
Ping Business verifies the gateway signature, checks the amount/currency/reference against the intent, then atomically creates the order, its order items (from the intent's snapshot), and the payment, and marks the intent S. A verified failed result marks the intent F without creating anything. Exact replays are idempotent ("idempotent": true) — safe to forward duplicates.
return_url (display only). The customer's browser lands here after paying. If it carries callback fields, you may forward them to record_payment too (same endpoint, same idempotency), but a bare browser hit proves nothing. Show "processing / success / failed" based on the intent status, never on the fact that the customer arrived.
Step 7 — Confirm and fulfill
Read the intent to get the durable state:
curl "$BASE/intents?identifier=0f0e2b1a-77aa-4a2e-bb0e-2a2d6f70abcd" -H "..."
| Intent status | Meaning | What you do |
|---|---|---|
C |
Created, not yet launched/paid | Keep waiting or let the customer retry |
R |
Redirected to the gateway, in progress | Poll with a bounded interval |
S |
Paid and verified — terminal | Fulfill the order; clear the cart |
F |
Failed — terminal | Offer to try again |
U |
Unknown — needs reconciliation | Show "being confirmed"; contact support if persistent |
Fulfill only on S. The order and payment are now readable via GET /orders, GET /order_items?order_id=..., and GET /payments?order_id=....
What's next
- Payments guide — the full checkout lifecycle, edge cases, and reconciliation.
- Subscriptions — recurring billing with card tokenization.
- API reference — every endpoint and field.