API reference

Payments

Create direct and hosted-checkout payments, look them up, and understand the amounts you'll see in responses and webhooks.

EndpointPurpose
POST/api/v1/payments/createCreate a payment. For cards, you send the card details and HansaPay charges the card directly.
POST/api/v1/payments/checkoutCreate a payment and get a link to HansaPay's hosted checkout page, where the payer enters their details.
POST/api/v1/payments/queryLook 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 to checkout_url. The payer can also choose Apple Pay or Google Pay on that page, if they are enabled for you.
  • Direct (/payments/create with CARD) 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.

FieldTypeRequiredDescription
payment_methodstringYesCARD, APPLE_PAY, GOOGLE_PAY, CASH_APP, PAYPAL or PIX. Upper case. See availability.
merchant_order_nostringYesYour order number, at most 64 characters. Must be unique among all your orders and refunds. See Idempotency.
trans_amountobjectYes{"currency": "USD", "value": "12.50"}. See Formats.
notify_urlstringYesWhere HansaPay sends webhooks for this order. At most 255 characters.
return_urlstringNoWhere the payer is sent after paying. At most 255 characters.
trade_infoobjectYesgoods_name (required, at most 128 characters) and description (optional, at most 255 characters).
metadatastringNoYour own data, returned unchanged in queries and webhooks. At most 255 characters.
payerobjectNoPayer details: email, mobile, user_name, user_agent, ext_type, payer_id, document. All optional.
client_ipstringNoThe payer's IP address.

Direct card payment

POST/api/v1/payments/create

POST/api/v1/payments/create with "payment_method": "CARD". In addition to the common fields:

FieldTypeRequiredDescription
card.card_numberstringYes13 to 19 digits
card.cardholder_namestringYes
card.exp_monthintegerYes1 to 12
card.exp_yearintegerYesFour digits, for example 2029
card.cvcstringYes3 or 4 digits
billing_address.countrystringYesTwo-letter country code, for example US
billing_address.first_name, last_namestringNoAt most 64 characters each
billing_address.emailstringNoUsed for the payment receipt; if empty, payer.email is used
billing_address.phone, state, city, address, street_number, postal_code, documentstringNo
shipping_addressobjectNoIf sent, country, first_name, last_name, email, phone, state, city, address and postal_code are all required
device_ipstringYesThe payer's device IP address
browserobjectNoBrowser 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"
}
200 · response
{
  "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"
}
200 · response
{
  "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"
}
200 · response
{
  "code": 422,
  "msg": "Payment request failed|paymentFailed"
}

Response fields:

FieldDescription
payment_methodCARD
order_noHansaPay's order number
merchant_order_noYour order number
statusSee Payment statuses
trans_amountThe order amount, in the currency you sent. See Amounts and currency.
next_actionWhat to do next. See next_action.
masked_card_numberThe first six and last four digits of the card
three_ds_urlPresent when next_action is 3DS_VERIFICATION_REQUIRED
created_atWhen 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/create

POST/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]"
  }
}
200 · response
{
  "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/checkout

POST/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]"
  }
}
200 · response
{
  "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:

FieldDescription
order_no, merchant_order_no, payment_method, created_atAs for direct payments
statusCHECKOUT_REQUIRED for cards; PENDING for wallets
trans_amountThe order amount, in the currency you sent
requested_amountOnly when the amount was rounded to a price point
next_actionCHECKOUT_REQUIRED for cards, REDIRECT for wallets
checkout_urlSend the payer here
tokenThe 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_actionWhat to doField to use
CHECKOUT_REQUIREDRedirect the payer to the hosted checkout pagecheckout_url
REDIRECTRedirect the payer to the payment pagepay_url or checkout_url
3DS_VERIFICATION_REQUIREDRedirect the payer to their bank's 3-D Secure pagethree_ds_url
QRCODEShow the QR code to the payer. No available payment method uses this today.qr_code
SUCCESSNothing; the payment succeeded
PROCESSING, PENDINGNothing; wait for the webhook
FAILEDNothing; 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/query

POST/api/v1/payments/query with exactly one of:

FieldDescription
order_noHansaPay's order number
merchant_order_noYour order number
{
  "merchant_order_no": "ORDER-1001-069I"
}
200 · response
{
  "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

StatusMeaningFinal
PENDINGCreated; waiting for the payer or the providerNo
CHECKOUT_REQUIREDWaiting for the payer on the hosted checkout pageNo
3DS_VERIFICATION_REQUIREDWaiting for the payer to complete 3-D SecureNo
PROCESSINGSubmitted; waiting for the provider's resultNo
SUCCESSPaidYes, but see below
FAILEDNot paidUsually; see below
PARTIAL_REFUNDEDPaid, and part of it has been refundedNo
REFUNDEDPaid, and fully refundedYes
  • A payment that has been refunded changes to PARTIAL_REFUNDED or REFUNDED, but no payment.updated webhook is sent for that change. You receive refund.updated instead.
  • In rare cases a card payment reported as FAILED is later confirmed by the provider as paid, and changes to SUCCESS. You receive a second payment.updated webhook when that happens. Do not treat FAILED as 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:

Wheretrans_amount
/payments/create, /payments/checkout, /payments/query responsesThe currency and amount you sent
payment.updated and refund.updated webhooksThe USD amount charged
Your settlement records in the portalThe 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.updated webhook 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:

Text
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.99

The 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:

Wheretrans_amountrequested_amount
/payments/create (card)The charged amountNot present
/payments/checkout, /payments/queryThe charged amount (USD orders)Your original amount, in USD
payment.updated webhookThe charged amount, in USDYour 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]"
  }
}
200 · response
{
  "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"
  }
}