API reference
Payments
Create direct and hosted-checkout payments, look them up, and understand the amounts you'll see in responses and webhooks.
| Endpoint | Purpose |
|---|---|
| POST/api/v1/payments/create | Create a payment. For cards, you send the card details and HansaPay charges the card directly. |
| POST/api/v1/payments/checkout | Create a payment and get a link to HansaPay's hosted checkout page, where the payer enters their details. |
| POST/api/v1/payments/query | Look up a payment. |
Which of the first two to use:
- Checkout (
/payments/checkout) is the simplest. You never handle card data; you redirect the payer tocheckout_url. The payer can also choose Apple Pay or Google Pay on that page, if they are enabled for you. - Direct (
/payments/createwithCARD) requires you to collect card details on your own pages, which brings you into scope for card-data security rules (PCI DSS). - For wallets (
APPLE_PAY,GOOGLE_PAY,CASH_APP), both endpoints return a link to send the payer to.
Common request fields
These fields are used by both /payments/create and /payments/checkout, for every payment method.
| Field | Type | Required | Description |
|---|---|---|---|
payment_method | string | Yes | CARD, APPLE_PAY, GOOGLE_PAY, CASH_APP, PAYPAL or PIX. Upper case. See availability. |
merchant_order_no | string | Yes | Your order number, at most 64 characters. Must be unique among all your orders and refunds. See Idempotency. |
trans_amount | object | Yes | {"currency": "USD", "value": "12.50"}. See Formats. |
notify_url | string | Yes | Where HansaPay sends webhooks for this order. At most 255 characters. |
return_url | string | No | Where the payer is sent after paying. At most 255 characters. |
trade_info | object | Yes | goods_name (required, at most 128 characters) and description (optional, at most 255 characters). |
metadata | string | No | Your own data, returned unchanged in queries and webhooks. At most 255 characters. |
payer | object | No | Payer details: email, mobile, user_name, user_agent, ext_type, payer_id, document. All optional. |
client_ip | string | No | The payer's IP address. |
Direct card payment
POST/api/v1/payments/createPOST/api/v1/payments/create with "payment_method": "CARD". In addition to the common fields:
| Field | Type | Required | Description |
|---|---|---|---|
card.card_number | string | Yes | 13 to 19 digits |
card.cardholder_name | string | Yes | |
card.exp_month | integer | Yes | 1 to 12 |
card.exp_year | integer | Yes | Four digits, for example 2029 |
card.cvc | string | Yes | 3 or 4 digits |
billing_address.country | string | Yes | Two-letter country code, for example US |
billing_address.first_name, last_name | string | No | At most 64 characters each |
billing_address.email | string | No | Used for the payment receipt; if empty, payer.email is used |
billing_address.phone, state, city, address, street_number, postal_code, document | string | No | |
shipping_address | object | No | If sent, country, first_name, last_name, email, phone, state, city, address and postal_code are all required |
device_ip | string | Yes | The payer's device IP address |
browser | object | No | Browser details for 3-D Secure: accept, user_agent, accept_language, java_enabled (boolean), color_depth, screen_height, screen_width, time_zone_offset, referer |
Request and response for a card that is charged straight away:
{
"payment_method": "CARD",
"merchant_order_no": "ORDER-1001-069I",
"trans_amount": {
"currency": "USD",
"value": "25.00"
},
"notify_url": "https://merchant.example.com/hansapay/webhook",
"return_url": "https://shop.example.com/orders/complete",
"trade_info": {
"goods_name": "Pro plan, 1 month",
"description": "Subscription renewal"
},
"metadata": "customer-8841",
"card": {
"card_number": "4242424242424242",
"cardholder_name": "Test Payer",
"exp_month": 12,
"exp_year": 2030,
"cvc": "123"
},
"billing_address": {
"country": "US",
"first_name": "Test",
"last_name": "Payer",
"email": "[email protected]",
"city": "San Francisco",
"address": "1 Market St",
"postal_code": "94105"
},
"device_ip": "203.0.113.10"
}BODY='{"payment_method":"CARD","merchant_order_no":"ORDER-1001-069I","trans_amount":{"currency":"USD","value":"25.00"},"notify_url":"https://merchant.example.com/hansapay/webhook","return_url":"https://shop.example.com/orders/complete","trade_info":{"goods_name":"Pro plan, 1 month","description":"Subscription renewal"},"metadata":"customer-8841","card":{"card_number":"4242424242424242","cardholder_name":"Test Payer","exp_month":12,"exp_year":2030,"cvc":"123"},"billing_address":{"country":"US","first_name":"Test","last_name":"Payer","email":"[email protected]","city":"San Francisco","address":"1 Market St","postal_code":"94105"},"device_ip":"203.0.113.10"}'
TS=$(date +%s); NONCE=$(openssl rand -hex 8)
SIGN=$(printf '%s\n%s\n%s\n%s' "$MERCHANT_ID" "$TS" "$NONCE" "$BODY" \
| openssl dgst -sha256 -hmac "$SECRET_KEY" | sed 's/^.* //')
curl https://api-test.hansapay.io/api/v1/payments/create \
-H "Content-Type: application/json" \
-H "X-MerchantID: $MERCHANT_ID" -H "X-Timestamp: $TS" \
-H "X-Nonce: $NONCE" -H "X-Sign: $SIGN" \
--data-raw "$BODY"// callHansaPay() is on the Authentication page, under "Signing in code".
const { rawBody } = await callHansaPay(BASE_URL, MERCHANT_ID, SECRET_KEY, "/payments/create", {
payment_method: "CARD",
merchant_order_no: "ORDER-1001-069I",
trans_amount: {
currency: "USD",
value: "25.00"
},
notify_url: "https://merchant.example.com/hansapay/webhook",
return_url: "https://shop.example.com/orders/complete",
trade_info: {
goods_name: "Pro plan, 1 month",
description: "Subscription renewal"
},
metadata: "customer-8841",
card: {
card_number: "4242424242424242",
cardholder_name: "Test Payer",
exp_month: 12,
exp_year: 2030,
cvc: "123"
},
billing_address: {
country: "US",
first_name: "Test",
last_name: "Payer",
email: "[email protected]",
city: "San Francisco",
address: "1 Market St",
postal_code: "94105"
},
device_ip: "203.0.113.10"
})
const { code, msg, data } = JSON.parse(rawBody)# call_hansapay() is on the Authentication page, under "Signing in code".
headers, raw = call_hansapay(BASE_URL, MERCHANT_ID, SECRET_KEY, "/payments/create", {
"payment_method": "CARD",
"merchant_order_no": "ORDER-1001-069I",
"trans_amount": {
"currency": "USD",
"value": "25.00",
},
"notify_url": "https://merchant.example.com/hansapay/webhook",
"return_url": "https://shop.example.com/orders/complete",
"trade_info": {
"goods_name": "Pro plan, 1 month",
"description": "Subscription renewal",
},
"metadata": "customer-8841",
"card": {
"card_number": "4242424242424242",
"cardholder_name": "Test Payer",
"exp_month": 12,
"exp_year": 2030,
"cvc": "123",
},
"billing_address": {
"country": "US",
"first_name": "Test",
"last_name": "Payer",
"email": "[email protected]",
"city": "San Francisco",
"address": "1 Market St",
"postal_code": "94105",
},
"device_ip": "203.0.113.10",
})
res = json.loads(raw)POST /api/v1/payments/create HTTP/1.1
Content-Type: application/json
X-MerchantID: M_74209884
X-Timestamp: 1790420275
X-Nonce: 8484f4a2742f05a2
X-Sign: 3477cc5a5ac3d6115c692674ec38080466b0e35896711f465e90e1c8c838488a
{
"payment_method": "CARD",
"merchant_order_no": "ORDER-1001-069I",
"trans_amount": {
"currency": "USD",
"value": "25.00"
},
"notify_url": "https://merchant.example.com/hansapay/webhook",
"return_url": "https://shop.example.com/orders/complete",
"trade_info": {
"goods_name": "Pro plan, 1 month",
"description": "Subscription renewal"
},
"metadata": "customer-8841",
"card": {
"card_number": "4242424242424242",
"cardholder_name": "Test Payer",
"exp_month": 12,
"exp_year": 2030,
"cvc": "123"
},
"billing_address": {
"country": "US",
"first_name": "Test",
"last_name": "Payer",
"email": "[email protected]",
"city": "San Francisco",
"address": "1 Market St",
"postal_code": "94105"
},
"device_ip": "203.0.113.10"
}{
"code": 0,
"msg": "success",
"data": {
"payment_method": "CARD",
"order_no": "362191050694987776",
"merchant_order_no": "ORDER-1001-069I",
"status": "SUCCESS",
"trans_amount": {
"currency": "USD",
"value": "25.00"
},
"next_action": "SUCCESS",
"masked_card_number": "424242******4242",
"created_at": "2026-09-26T18:57:55+08:00"
}
}When the card needs 3-D Secure, next_action is 3DS_VERIFICATION_REQUIRED. Send the payer to three_ds_url; the result arrives by webhook.
{
"payment_method": "CARD",
"merchant_order_no": "ORDER-1002-069I",
"trans_amount": {
"currency": "USD",
"value": "30.00"
},
"notify_url": "https://merchant.example.com/hansapay/webhook",
"return_url": "https://shop.example.com/orders/complete",
"trade_info": {
"goods_name": "Pro plan, 1 month",
"description": "Subscription renewal"
},
"metadata": "customer-8841",
"card": {
"card_number": "4000000000003220",
"cardholder_name": "Test Payer",
"exp_month": 12,
"exp_year": 2030,
"cvc": "123"
},
"billing_address": {
"country": "US",
"first_name": "Test",
"last_name": "Payer",
"email": "[email protected]",
"city": "San Francisco",
"address": "1 Market St",
"postal_code": "94105"
},
"device_ip": "203.0.113.10"
}BODY='{"payment_method":"CARD","merchant_order_no":"ORDER-1002-069I","trans_amount":{"currency":"USD","value":"30.00"},"notify_url":"https://merchant.example.com/hansapay/webhook","return_url":"https://shop.example.com/orders/complete","trade_info":{"goods_name":"Pro plan, 1 month","description":"Subscription renewal"},"metadata":"customer-8841","card":{"card_number":"4000000000003220","cardholder_name":"Test Payer","exp_month":12,"exp_year":2030,"cvc":"123"},"billing_address":{"country":"US","first_name":"Test","last_name":"Payer","email":"[email protected]","city":"San Francisco","address":"1 Market St","postal_code":"94105"},"device_ip":"203.0.113.10"}'
TS=$(date +%s); NONCE=$(openssl rand -hex 8)
SIGN=$(printf '%s\n%s\n%s\n%s' "$MERCHANT_ID" "$TS" "$NONCE" "$BODY" \
| openssl dgst -sha256 -hmac "$SECRET_KEY" | sed 's/^.* //')
curl https://api-test.hansapay.io/api/v1/payments/create \
-H "Content-Type: application/json" \
-H "X-MerchantID: $MERCHANT_ID" -H "X-Timestamp: $TS" \
-H "X-Nonce: $NONCE" -H "X-Sign: $SIGN" \
--data-raw "$BODY"// callHansaPay() is on the Authentication page, under "Signing in code".
const { rawBody } = await callHansaPay(BASE_URL, MERCHANT_ID, SECRET_KEY, "/payments/create", {
payment_method: "CARD",
merchant_order_no: "ORDER-1002-069I",
trans_amount: {
currency: "USD",
value: "30.00"
},
notify_url: "https://merchant.example.com/hansapay/webhook",
return_url: "https://shop.example.com/orders/complete",
trade_info: {
goods_name: "Pro plan, 1 month",
description: "Subscription renewal"
},
metadata: "customer-8841",
card: {
card_number: "4000000000003220",
cardholder_name: "Test Payer",
exp_month: 12,
exp_year: 2030,
cvc: "123"
},
billing_address: {
country: "US",
first_name: "Test",
last_name: "Payer",
email: "[email protected]",
city: "San Francisco",
address: "1 Market St",
postal_code: "94105"
},
device_ip: "203.0.113.10"
})
const { code, msg, data } = JSON.parse(rawBody)# call_hansapay() is on the Authentication page, under "Signing in code".
headers, raw = call_hansapay(BASE_URL, MERCHANT_ID, SECRET_KEY, "/payments/create", {
"payment_method": "CARD",
"merchant_order_no": "ORDER-1002-069I",
"trans_amount": {
"currency": "USD",
"value": "30.00",
},
"notify_url": "https://merchant.example.com/hansapay/webhook",
"return_url": "https://shop.example.com/orders/complete",
"trade_info": {
"goods_name": "Pro plan, 1 month",
"description": "Subscription renewal",
},
"metadata": "customer-8841",
"card": {
"card_number": "4000000000003220",
"cardholder_name": "Test Payer",
"exp_month": 12,
"exp_year": 2030,
"cvc": "123",
},
"billing_address": {
"country": "US",
"first_name": "Test",
"last_name": "Payer",
"email": "[email protected]",
"city": "San Francisco",
"address": "1 Market St",
"postal_code": "94105",
},
"device_ip": "203.0.113.10",
})
res = json.loads(raw)POST /api/v1/payments/create HTTP/1.1
Content-Type: application/json
X-MerchantID: M_74209884
X-Timestamp: 1790420276
X-Nonce: 57d61c8cd7b86e64
X-Sign: af4704b259d3b63bd6cb7d400723d584712c2ffa800caead7e320b0e2dd1af0f
{
"payment_method": "CARD",
"merchant_order_no": "ORDER-1002-069I",
"trans_amount": {
"currency": "USD",
"value": "30.00"
},
"notify_url": "https://merchant.example.com/hansapay/webhook",
"return_url": "https://shop.example.com/orders/complete",
"trade_info": {
"goods_name": "Pro plan, 1 month",
"description": "Subscription renewal"
},
"metadata": "customer-8841",
"card": {
"card_number": "4000000000003220",
"cardholder_name": "Test Payer",
"exp_month": 12,
"exp_year": 2030,
"cvc": "123"
},
"billing_address": {
"country": "US",
"first_name": "Test",
"last_name": "Payer",
"email": "[email protected]",
"city": "San Francisco",
"address": "1 Market St",
"postal_code": "94105"
},
"device_ip": "203.0.113.10"
}{
"code": 0,
"msg": "success",
"data": {
"payment_method": "CARD",
"order_no": "362191055023509504",
"merchant_order_no": "ORDER-1002-069I",
"status": "3DS_VERIFICATION_REQUIRED",
"trans_amount": {
"currency": "USD",
"value": "30.00"
},
"next_action": "3DS_VERIFICATION_REQUIRED",
"masked_card_number": "400000******3220",
"three_ds_url": "https://provider.example.com/3ds/PI17904202767090018",
"created_at": "2026-09-26T18:57:56+08:00"
}
}When the card is declined, the request fails with Payment request failed|paymentFailed, and a payment.updated webhook with status FAILED follows for the same order. The error response does not include the order_no; the webhook does.
{
"payment_method": "CARD",
"merchant_order_no": "ORDER-1003-069I",
"trans_amount": {
"currency": "USD",
"value": "18.00"
},
"notify_url": "https://merchant.example.com/hansapay/webhook",
"return_url": "https://shop.example.com/orders/complete",
"trade_info": {
"goods_name": "Pro plan, 1 month",
"description": "Subscription renewal"
},
"metadata": "customer-8841",
"card": {
"card_number": "4000000000000002",
"cardholder_name": "Test Payer",
"exp_month": 12,
"exp_year": 2030,
"cvc": "123"
},
"billing_address": {
"country": "US",
"first_name": "Test",
"last_name": "Payer",
"email": "[email protected]",
"city": "San Francisco",
"address": "1 Market St",
"postal_code": "94105"
},
"device_ip": "203.0.113.10"
}BODY='{"payment_method":"CARD","merchant_order_no":"ORDER-1003-069I","trans_amount":{"currency":"USD","value":"18.00"},"notify_url":"https://merchant.example.com/hansapay/webhook","return_url":"https://shop.example.com/orders/complete","trade_info":{"goods_name":"Pro plan, 1 month","description":"Subscription renewal"},"metadata":"customer-8841","card":{"card_number":"4000000000000002","cardholder_name":"Test Payer","exp_month":12,"exp_year":2030,"cvc":"123"},"billing_address":{"country":"US","first_name":"Test","last_name":"Payer","email":"[email protected]","city":"San Francisco","address":"1 Market St","postal_code":"94105"},"device_ip":"203.0.113.10"}'
TS=$(date +%s); NONCE=$(openssl rand -hex 8)
SIGN=$(printf '%s\n%s\n%s\n%s' "$MERCHANT_ID" "$TS" "$NONCE" "$BODY" \
| openssl dgst -sha256 -hmac "$SECRET_KEY" | sed 's/^.* //')
curl https://api-test.hansapay.io/api/v1/payments/create \
-H "Content-Type: application/json" \
-H "X-MerchantID: $MERCHANT_ID" -H "X-Timestamp: $TS" \
-H "X-Nonce: $NONCE" -H "X-Sign: $SIGN" \
--data-raw "$BODY"// callHansaPay() is on the Authentication page, under "Signing in code".
const { rawBody } = await callHansaPay(BASE_URL, MERCHANT_ID, SECRET_KEY, "/payments/create", {
payment_method: "CARD",
merchant_order_no: "ORDER-1003-069I",
trans_amount: {
currency: "USD",
value: "18.00"
},
notify_url: "https://merchant.example.com/hansapay/webhook",
return_url: "https://shop.example.com/orders/complete",
trade_info: {
goods_name: "Pro plan, 1 month",
description: "Subscription renewal"
},
metadata: "customer-8841",
card: {
card_number: "4000000000000002",
cardholder_name: "Test Payer",
exp_month: 12,
exp_year: 2030,
cvc: "123"
},
billing_address: {
country: "US",
first_name: "Test",
last_name: "Payer",
email: "[email protected]",
city: "San Francisco",
address: "1 Market St",
postal_code: "94105"
},
device_ip: "203.0.113.10"
})
const { code, msg, data } = JSON.parse(rawBody)# call_hansapay() is on the Authentication page, under "Signing in code".
headers, raw = call_hansapay(BASE_URL, MERCHANT_ID, SECRET_KEY, "/payments/create", {
"payment_method": "CARD",
"merchant_order_no": "ORDER-1003-069I",
"trans_amount": {
"currency": "USD",
"value": "18.00",
},
"notify_url": "https://merchant.example.com/hansapay/webhook",
"return_url": "https://shop.example.com/orders/complete",
"trade_info": {
"goods_name": "Pro plan, 1 month",
"description": "Subscription renewal",
},
"metadata": "customer-8841",
"card": {
"card_number": "4000000000000002",
"cardholder_name": "Test Payer",
"exp_month": 12,
"exp_year": 2030,
"cvc": "123",
},
"billing_address": {
"country": "US",
"first_name": "Test",
"last_name": "Payer",
"email": "[email protected]",
"city": "San Francisco",
"address": "1 Market St",
"postal_code": "94105",
},
"device_ip": "203.0.113.10",
})
res = json.loads(raw)POST /api/v1/payments/create HTTP/1.1
Content-Type: application/json
X-MerchantID: M_74209884
X-Timestamp: 1790420276
X-Nonce: abb8bc92feb8b39e
X-Sign: 2b56b10e0a89a9cef0cd6af78055a621aafd5ed52b337dd838e0728f116bcd2e
{
"payment_method": "CARD",
"merchant_order_no": "ORDER-1003-069I",
"trans_amount": {
"currency": "USD",
"value": "18.00"
},
"notify_url": "https://merchant.example.com/hansapay/webhook",
"return_url": "https://shop.example.com/orders/complete",
"trade_info": {
"goods_name": "Pro plan, 1 month",
"description": "Subscription renewal"
},
"metadata": "customer-8841",
"card": {
"card_number": "4000000000000002",
"cardholder_name": "Test Payer",
"exp_month": 12,
"exp_year": 2030,
"cvc": "123"
},
"billing_address": {
"country": "US",
"first_name": "Test",
"last_name": "Payer",
"email": "[email protected]",
"city": "San Francisco",
"address": "1 Market St",
"postal_code": "94105"
},
"device_ip": "203.0.113.10"
}{
"code": 422,
"msg": "Payment request failed|paymentFailed"
}Response fields:
| Field | Description |
|---|---|
payment_method | CARD |
order_no | HansaPay's order number |
merchant_order_no | Your order number |
status | See Payment statuses |
trans_amount | The order amount, in the currency you sent. See Amounts and currency. |
next_action | What to do next. See next_action. |
masked_card_number | The first six and last four digits of the card |
three_ds_url | Present when next_action is 3DS_VERIFICATION_REQUIRED |
created_at | When the order was created |
This response has no requested_amount, even when the amount was rounded to a price point. Use /payments/query or the webhook to see it.
Wallet payment
POST/api/v1/payments/createPOST/api/v1/payments/create with payment_method set to APPLE_PAY, GOOGLE_PAY or CASH_APP takes only the common fields. The response gives a link to send the payer to:
{
"payment_method": "GOOGLE_PAY",
"merchant_order_no": "ORDER-1004-069I",
"trans_amount": {
"currency": "USD",
"value": "19.99"
},
"notify_url": "https://merchant.example.com/hansapay/webhook",
"return_url": "https://shop.example.com/orders/complete",
"trade_info": {
"goods_name": "Pro plan, 1 month",
"description": "Subscription renewal"
},
"metadata": "customer-8841",
"payer": {
"email": "[email protected]"
}
}BODY='{"payment_method":"GOOGLE_PAY","merchant_order_no":"ORDER-1004-069I","trans_amount":{"currency":"USD","value":"19.99"},"notify_url":"https://merchant.example.com/hansapay/webhook","return_url":"https://shop.example.com/orders/complete","trade_info":{"goods_name":"Pro plan, 1 month","description":"Subscription renewal"},"metadata":"customer-8841","payer":{"email":"[email protected]"}}'
TS=$(date +%s); NONCE=$(openssl rand -hex 8)
SIGN=$(printf '%s\n%s\n%s\n%s' "$MERCHANT_ID" "$TS" "$NONCE" "$BODY" \
| openssl dgst -sha256 -hmac "$SECRET_KEY" | sed 's/^.* //')
curl https://api-test.hansapay.io/api/v1/payments/create \
-H "Content-Type: application/json" \
-H "X-MerchantID: $MERCHANT_ID" -H "X-Timestamp: $TS" \
-H "X-Nonce: $NONCE" -H "X-Sign: $SIGN" \
--data-raw "$BODY"// callHansaPay() is on the Authentication page, under "Signing in code".
const { rawBody } = await callHansaPay(BASE_URL, MERCHANT_ID, SECRET_KEY, "/payments/create", {
payment_method: "GOOGLE_PAY",
merchant_order_no: "ORDER-1004-069I",
trans_amount: {
currency: "USD",
value: "19.99"
},
notify_url: "https://merchant.example.com/hansapay/webhook",
return_url: "https://shop.example.com/orders/complete",
trade_info: {
goods_name: "Pro plan, 1 month",
description: "Subscription renewal"
},
metadata: "customer-8841",
payer: {
email: "[email protected]"
}
})
const { code, msg, data } = JSON.parse(rawBody)# call_hansapay() is on the Authentication page, under "Signing in code".
headers, raw = call_hansapay(BASE_URL, MERCHANT_ID, SECRET_KEY, "/payments/create", {
"payment_method": "GOOGLE_PAY",
"merchant_order_no": "ORDER-1004-069I",
"trans_amount": {
"currency": "USD",
"value": "19.99",
},
"notify_url": "https://merchant.example.com/hansapay/webhook",
"return_url": "https://shop.example.com/orders/complete",
"trade_info": {
"goods_name": "Pro plan, 1 month",
"description": "Subscription renewal",
},
"metadata": "customer-8841",
"payer": {
"email": "[email protected]",
},
})
res = json.loads(raw)POST /api/v1/payments/create HTTP/1.1
Content-Type: application/json
X-MerchantID: M_74209884
X-Timestamp: 1790420276
X-Nonce: 0ec19c9c092902fc
X-Sign: c0cd8856d9898b01b6dfc6b406af98cb538f1e7c192bf4f4a8ff9c8cf195fb66
{
"payment_method": "GOOGLE_PAY",
"merchant_order_no": "ORDER-1004-069I",
"trans_amount": {
"currency": "USD",
"value": "19.99"
},
"notify_url": "https://merchant.example.com/hansapay/webhook",
"return_url": "https://shop.example.com/orders/complete",
"trade_info": {
"goods_name": "Pro plan, 1 month",
"description": "Subscription renewal"
},
"metadata": "customer-8841",
"payer": {
"email": "[email protected]"
}
}{
"code": 0,
"msg": "success",
"data": {
"order_no": "362191055195475968",
"merchant_order_no": "ORDER-1004-069I",
"payment_method": "GOOGLE_PAY",
"status": "PENDING",
"trans_amount": {
"currency": "USD",
"value": "19.99"
},
"next_action": "REDIRECT",
"pay_url": "https://provider.example.com/hosted/PI17904202767470020",
"created_at": "2026-09-26T18:57:56+08:00"
}
}Response fields: order_no, merchant_order_no, payment_method, status, trans_amount, requested_amount (only when rounded), next_action, pay_url (when next_action is REDIRECT), qr_code (when next_action is QRCODE) and created_at.
Hosted checkout
POST/api/v1/payments/checkoutPOST/api/v1/payments/checkout takes only the common fields. Card details, billing_address and device_ip are not accepted here; the payer enters them on the checkout page.
{
"payment_method": "CARD",
"merchant_order_no": "ORDER-1005-069I",
"trans_amount": {
"currency": "USD",
"value": "49.99"
},
"notify_url": "https://merchant.example.com/hansapay/webhook",
"return_url": "https://shop.example.com/orders/complete",
"trade_info": {
"goods_name": "Pro plan, 1 month",
"description": "Subscription renewal"
},
"metadata": "customer-8841",
"payer": {
"email": "[email protected]"
}
}BODY='{"payment_method":"CARD","merchant_order_no":"ORDER-1005-069I","trans_amount":{"currency":"USD","value":"49.99"},"notify_url":"https://merchant.example.com/hansapay/webhook","return_url":"https://shop.example.com/orders/complete","trade_info":{"goods_name":"Pro plan, 1 month","description":"Subscription renewal"},"metadata":"customer-8841","payer":{"email":"[email protected]"}}'
TS=$(date +%s); NONCE=$(openssl rand -hex 8)
SIGN=$(printf '%s\n%s\n%s\n%s' "$MERCHANT_ID" "$TS" "$NONCE" "$BODY" \
| openssl dgst -sha256 -hmac "$SECRET_KEY" | sed 's/^.* //')
curl https://api-test.hansapay.io/api/v1/payments/checkout \
-H "Content-Type: application/json" \
-H "X-MerchantID: $MERCHANT_ID" -H "X-Timestamp: $TS" \
-H "X-Nonce: $NONCE" -H "X-Sign: $SIGN" \
--data-raw "$BODY"// callHansaPay() is on the Authentication page, under "Signing in code".
const { rawBody } = await callHansaPay(BASE_URL, MERCHANT_ID, SECRET_KEY, "/payments/checkout", {
payment_method: "CARD",
merchant_order_no: "ORDER-1005-069I",
trans_amount: {
currency: "USD",
value: "49.99"
},
notify_url: "https://merchant.example.com/hansapay/webhook",
return_url: "https://shop.example.com/orders/complete",
trade_info: {
goods_name: "Pro plan, 1 month",
description: "Subscription renewal"
},
metadata: "customer-8841",
payer: {
email: "[email protected]"
}
})
const { code, msg, data } = JSON.parse(rawBody)# call_hansapay() is on the Authentication page, under "Signing in code".
headers, raw = call_hansapay(BASE_URL, MERCHANT_ID, SECRET_KEY, "/payments/checkout", {
"payment_method": "CARD",
"merchant_order_no": "ORDER-1005-069I",
"trans_amount": {
"currency": "USD",
"value": "49.99",
},
"notify_url": "https://merchant.example.com/hansapay/webhook",
"return_url": "https://shop.example.com/orders/complete",
"trade_info": {
"goods_name": "Pro plan, 1 month",
"description": "Subscription renewal",
},
"metadata": "customer-8841",
"payer": {
"email": "[email protected]",
},
})
res = json.loads(raw)POST /api/v1/payments/checkout HTTP/1.1
Content-Type: application/json
X-MerchantID: M_74209884
X-Timestamp: 1790420276
X-Nonce: 5ae7b2f2c0bb6677
X-Sign: d1416d769fc3b88d363ab86de82f45212e8e61921b1f8001f86c198e64b2fb9f
{
"payment_method": "CARD",
"merchant_order_no": "ORDER-1005-069I",
"trans_amount": {
"currency": "USD",
"value": "49.99"
},
"notify_url": "https://merchant.example.com/hansapay/webhook",
"return_url": "https://shop.example.com/orders/complete",
"trade_info": {
"goods_name": "Pro plan, 1 month",
"description": "Subscription renewal"
},
"metadata": "customer-8841",
"payer": {
"email": "[email protected]"
}
}{
"code": 0,
"msg": "success",
"data": {
"payment_method": "CARD",
"order_no": "362191055250001920",
"merchant_order_no": "ORDER-1005-069I",
"status": "CHECKOUT_REQUIRED",
"trans_amount": {
"currency": "USD",
"value": "49.99"
},
"next_action": "CHECKOUT_REQUIRED",
"token": "39cb37ce-03e2-47a6-80f6-3cf8ff463371",
"checkout_url": "https://pay.example.com/pay/39cb37ce-03e2-47a6-80f6-3cf8ff463371",
"created_at": "2026-09-26T18:57:56+08:00"
}
}Response fields:
| Field | Description |
|---|---|
order_no, merchant_order_no, payment_method, created_at | As for direct payments |
status | CHECKOUT_REQUIRED for cards; PENDING for wallets |
trans_amount | The order amount, in the currency you sent |
requested_amount | Only when the amount was rounded to a price point |
next_action | CHECKOUT_REQUIRED for cards, REDIRECT for wallets |
checkout_url | Send the payer here |
token | The last part of checkout_url. You do not need it. |
Save checkout_url as soon as you receive it. It cannot be retrieved again: /payments/query does not return it, and repeating the request with the same merchant_order_no is refused as a duplicate. The link expires after 30 minutes. An order whose link expired unpaid stays in CHECKOUT_REQUIRED; create a new order to try again.
On the checkout page, the payer may switch from card to Apple Pay or Google Pay. The order's payment_method then changes, and queries and webhooks report the wallet.
next_action
next_action tells your integration what to do after creating a payment. Dispatch on it, not on status.
| next_action | What to do | Field to use |
|---|---|---|
CHECKOUT_REQUIRED | Redirect the payer to the hosted checkout page | checkout_url |
REDIRECT | Redirect the payer to the payment page | pay_url or checkout_url |
3DS_VERIFICATION_REQUIRED | Redirect the payer to their bank's 3-D Secure page | three_ds_url |
QRCODE | Show the QR code to the payer. No available payment method uses this today. | qr_code |
SUCCESS | Nothing; the payment succeeded | |
PROCESSING, PENDING | Nothing; wait for the webhook | |
FAILED | Nothing; the payment failed |
Earlier integration material may describe a render_type field with the values qrcode, redirect, mobile and card. That field is not part of the merchant API; use next_action.
Query a payment
POST/api/v1/payments/queryPOST/api/v1/payments/query with exactly one of:
| Field | Description |
|---|---|
order_no | HansaPay's order number |
merchant_order_no | Your order number |
{
"merchant_order_no": "ORDER-1001-069I"
}BODY='{"merchant_order_no":"ORDER-1001-069I"}'
TS=$(date +%s); NONCE=$(openssl rand -hex 8)
SIGN=$(printf '%s\n%s\n%s\n%s' "$MERCHANT_ID" "$TS" "$NONCE" "$BODY" \
| openssl dgst -sha256 -hmac "$SECRET_KEY" | sed 's/^.* //')
curl https://api-test.hansapay.io/api/v1/payments/query \
-H "Content-Type: application/json" \
-H "X-MerchantID: $MERCHANT_ID" -H "X-Timestamp: $TS" \
-H "X-Nonce: $NONCE" -H "X-Sign: $SIGN" \
--data-raw "$BODY"// callHansaPay() is on the Authentication page, under "Signing in code".
const { rawBody } = await callHansaPay(BASE_URL, MERCHANT_ID, SECRET_KEY, "/payments/query", {
merchant_order_no: "ORDER-1001-069I"
})
const { code, msg, data } = JSON.parse(rawBody)# call_hansapay() is on the Authentication page, under "Signing in code".
headers, raw = call_hansapay(BASE_URL, MERCHANT_ID, SECRET_KEY, "/payments/query", {
"merchant_order_no": "ORDER-1001-069I",
})
res = json.loads(raw)POST /api/v1/payments/query HTTP/1.1
Content-Type: application/json
X-MerchantID: M_74209884
X-Timestamp: 1790420276
X-Nonce: 3537ecfe65a3d99a
X-Sign: 44520dfebb5607601422ce72135282a359057bce6924b81baa58eedc62484245
{
"merchant_order_no": "ORDER-1001-069I"
}{
"code": 0,
"msg": "success",
"data": {
"payment_method": "CARD",
"order_no": "362191050694987776",
"merchant_order_no": "ORDER-1001-069I",
"status": "SUCCESS",
"trans_amount": {
"currency": "USD",
"value": "25.00"
},
"paid_at": "2026-09-26T18:57:55+08:00",
"created_at": "2026-09-26T18:57:55+08:00",
"masked_card_number": "424242******4242",
"trade_info": {
"goods_name": "Pro plan, 1 month",
"description": "Subscription renewal"
},
"metadata": "customer-8841"
}
}Response fields: payment_method, order_no, merchant_order_no, status, trans_amount, requested_amount (only when rounded), paid_at (once paid), created_at, masked_card_number (cards), trade_info and metadata.
A query for a payment that is still in progress also asks the provider for the latest status, so querying is a reliable fallback when a webhook is late.
Payment statuses
| Status | Meaning | Final |
|---|---|---|
PENDING | Created; waiting for the payer or the provider | No |
CHECKOUT_REQUIRED | Waiting for the payer on the hosted checkout page | No |
3DS_VERIFICATION_REQUIRED | Waiting for the payer to complete 3-D Secure | No |
PROCESSING | Submitted; waiting for the provider's result | No |
SUCCESS | Paid | Yes, but see below |
FAILED | Not paid | Usually; see below |
PARTIAL_REFUNDED | Paid, and part of it has been refunded | No |
REFUNDED | Paid, and fully refunded | Yes |
- A payment that has been refunded changes to
PARTIAL_REFUNDEDorREFUNDED, but nopayment.updatedwebhook is sent for that change. You receiverefund.updatedinstead. - In rare cases a card payment reported as
FAILEDis later confirmed by the provider as paid, and changes toSUCCESS. You receive a secondpayment.updatedwebhook when that happens. Do not treatFAILEDas permanent in your fulfilment logic.
Amounts and currency
Orders in other currencies
HansaPay settles in USD. When you send an order in another currency, HansaPay converts it to USD at the current exchange rate plus a small margin, and the payer is charged the USD amount.
Which amount you see depends on where you look:
| Where | trans_amount |
|---|---|
/payments/create, /payments/checkout, /payments/query responses | The currency and amount you sent |
payment.updated and refund.updated webhooks | The USD amount charged |
| Your settlement records in the portal | The USD amount |
Example: you create an order for EUR 10.00. With an exchange rate of 1 EUR = 1.0870 USD and a margin of 0.5%, the payer is charged USD 10.92.
- The create response and every query report
{"currency": "EUR", "value": "10.00"}. - The
payment.updatedwebhook reports{"currency": "USD", "value": "10.92"}.
The exchange rate is not returned by the API. If you need the USD amount, take it from the webhook.
For orders in USD, all of these report the same amount.
Price points
On some card channels the charged amount is rounded down to a fixed price point (see What to expect). The card price points are, in USD:
9.99 10.99 11.99 12.99 13.99 14.99 17.99 19.99 24.99 29.99 30.99 39.99
49.99 59.99 99.99 124.99 129.99 149.99 199.99 249.99 299.99 399.99 499.99The amount is rounded down to the highest price point that is not above it. It is refused if it is below 9.99, above 499.99, or would drop by more than 5%. Examples: 10.00 becomes 9.99; 12.50 becomes 11.99; 17.50 is refused (14.99 would be 14% lower).
Apple Pay and Google Pay use a longer list, which includes every card price point, so a card checkout that the payer switches to Apple Pay is not rounded a second time.
When an amount is rounded:
| Where | trans_amount | requested_amount |
|---|---|---|
/payments/create (card) | The charged amount | Not present |
/payments/checkout, /payments/query | The charged amount (USD orders) | Your original amount, in USD |
payment.updated webhook | The charged amount, in USD | Your original amount, in USD |
For an order in another currency, rounding applies to the converted USD amount. trans_amount in the responses then still shows your original currency and amount, while the webhook shows the rounded USD amount.
{
"payment_method": "CARD",
"merchant_order_no": "ORDER-1007-069I",
"trans_amount": {
"currency": "USD",
"value": "12.50"
},
"notify_url": "https://merchant.example.com/hansapay/webhook",
"return_url": "https://shop.example.com/orders/complete",
"trade_info": {
"goods_name": "Pro plan, 1 month",
"description": "Subscription renewal"
},
"metadata": "customer-8841",
"payer": {
"email": "[email protected]"
}
}BODY='{"payment_method":"CARD","merchant_order_no":"ORDER-1007-069I","trans_amount":{"currency":"USD","value":"12.50"},"notify_url":"https://merchant.example.com/hansapay/webhook","return_url":"https://shop.example.com/orders/complete","trade_info":{"goods_name":"Pro plan, 1 month","description":"Subscription renewal"},"metadata":"customer-8841","payer":{"email":"[email protected]"}}'
TS=$(date +%s); NONCE=$(openssl rand -hex 8)
SIGN=$(printf '%s\n%s\n%s\n%s' "$MERCHANT_ID" "$TS" "$NONCE" "$BODY" \
| openssl dgst -sha256 -hmac "$SECRET_KEY" | sed 's/^.* //')
curl https://api-test.hansapay.io/api/v1/payments/checkout \
-H "Content-Type: application/json" \
-H "X-MerchantID: $MERCHANT_ID" -H "X-Timestamp: $TS" \
-H "X-Nonce: $NONCE" -H "X-Sign: $SIGN" \
--data-raw "$BODY"// callHansaPay() is on the Authentication page, under "Signing in code".
const { rawBody } = await callHansaPay(BASE_URL, MERCHANT_ID, SECRET_KEY, "/payments/checkout", {
payment_method: "CARD",
merchant_order_no: "ORDER-1007-069I",
trans_amount: {
currency: "USD",
value: "12.50"
},
notify_url: "https://merchant.example.com/hansapay/webhook",
return_url: "https://shop.example.com/orders/complete",
trade_info: {
goods_name: "Pro plan, 1 month",
description: "Subscription renewal"
},
metadata: "customer-8841",
payer: {
email: "[email protected]"
}
})
const { code, msg, data } = JSON.parse(rawBody)# call_hansapay() is on the Authentication page, under "Signing in code".
headers, raw = call_hansapay(BASE_URL, MERCHANT_ID, SECRET_KEY, "/payments/checkout", {
"payment_method": "CARD",
"merchant_order_no": "ORDER-1007-069I",
"trans_amount": {
"currency": "USD",
"value": "12.50",
},
"notify_url": "https://merchant.example.com/hansapay/webhook",
"return_url": "https://shop.example.com/orders/complete",
"trade_info": {
"goods_name": "Pro plan, 1 month",
"description": "Subscription renewal",
},
"metadata": "customer-8841",
"payer": {
"email": "[email protected]",
},
})
res = json.loads(raw)POST /api/v1/payments/checkout HTTP/1.1
Content-Type: application/json
X-MerchantID: M_74209884
X-Timestamp: 1790420276
X-Nonce: 1d3b7c3ca6869a67
X-Sign: 0299bea8a801160a058982bcfc78d216cf5da49c4416e7f6d2b3e4093374f1eb
{
"payment_method": "CARD",
"merchant_order_no": "ORDER-1007-069I",
"trans_amount": {
"currency": "USD",
"value": "12.50"
},
"notify_url": "https://merchant.example.com/hansapay/webhook",
"return_url": "https://shop.example.com/orders/complete",
"trade_info": {
"goods_name": "Pro plan, 1 month",
"description": "Subscription renewal"
},
"metadata": "customer-8841",
"payer": {
"email": "[email protected]"
}
}{
"code": 0,
"msg": "success",
"data": {
"payment_method": "CARD",
"order_no": "362191055719763968",
"merchant_order_no": "ORDER-1007-069I",
"status": "CHECKOUT_REQUIRED",
"trans_amount": {
"currency": "USD",
"value": "11.99"
},
"requested_amount": {
"currency": "USD",
"value": "12.50"
},
"next_action": "CHECKOUT_REQUIRED",
"token": "f4b89eab-7548-4dca-9104-83bd4672827d",
"checkout_url": "https://pay.example.com/pay/f4b89eab-7548-4dca-9104-83bd4672827d",
"created_at": "2026-09-26T18:57:56+08:00"
}
}