JSON sale
Server-to-server payment
Two steps. Step 1 is JSON on your server (you handle PAN — this sandbox is not PCI Level 1). Step 2 is a browser form only when the card needs the sandbox 3DS page.
1. Initiate
POST /pgui/services/paymentServices/initiate/payment with Content-Type: application/json.
Required: every hosted field plus CUST_NAME, PAYMENT_TYPE (CC or DC), CARD_NUMBER, CARD_EXP_DT, CVV, and HASH over the JSON you send.
TXNTYPE must be SALE. Other payment types return 113. Duplicate ORDER_ID returns 018.
{
"APP_ID": "1000221129001154",
"ORDER_ID": "S2S-001",
"TXNTYPE": "SALE",
"CUST_NAME": "Sara Khan",
"CUST_FIRST_NAME": "Sara",
"CUST_LAST_NAME": "Khan",
"CUST_CITY": "Dubai",
"CUST_STATE": "DU",
"CUST_COUNTRY": "AE",
"CUST_ZIP": "00000",
"CUST_PHONE": "0501234567",
"CUST_EMAIL": "buyer@example.test",
"AMOUNT": "1000",
"CURRENCY_CODE": "784",
"PAYMENT_TYPE": "CC",
"CARD_NUMBER": "5100000000000511",
"CARD_EXP_DT": "112029",
"CVV": "456",
"RETURN_URL": "https://merchant.example/return",
"HASH": "<128 hex>"
}Captured (Mastercard test card)
{
"RESPONSE_CODE": "000",
"STATUS": "Captured",
"IS_CHALANGE_REQUIRED": "No",
"TXN_ID": "16-digit id",
"MOP_TYPE": "MC",
"PG_DESCRIPTOR": "Test_PG_Display",
"HASH": "<signed>"
}Spelling of IS_CHALANGE_REQUIRED is as implemented (one A).
3DS pending (Visa test card)
{
"RESPONSE_CODE": "000",
"STATUS": "Enrolled",
"IS_CHALANGE_REQUIRED": "Yes",
"PG_TXN_MESSAGE": "ThreeD Authentication Pending",
"TXN_ID": "16-digit id"
}That is not success. Continue to capture.
Declined
RESPONSE_CODE=004, STATUS=Declined. Wrong CVV/expiry on a known PAN also declines.
2. Capture (3DS)
POST /pgui/jsp/capturePayment as a browser form — not JSON.
| Field | Notes |
|---|---|
| APP_ID | Same merchant |
| TXN_ID | From initiate |
| HASH | Hash of the fields you post (usually those three plus any extras you include) |
If the sale is Enrolled or IS_CHALANGE_REQUIRED=Yes, the sandbox marks it Captured. The browser is then auto-POSTed to RETURN_URL (or shown the result table if no return URL). This is a fake challenge — there is no issuer OTP.
Which processor runs the sale
Same assignment as hosted: routeForMerchant → plugin.sale. The JSON body does not include a processor id — it comes from the merchant record. See Processors.