TrueMoney QR
Topics covered on this page
TrueMoney QR (Offline)
Accept offline payments from TrueMoney users through your store using the TrueMoney QR (Offline) payment method.
This guide walks you through the payment flow and details how to implement it.
How to enable
- Supported countries: Thailand
- Minimum API version:
2017-11-02
To enable TrueMoney QR, send an email requesting this feature to support@omise.co. You will need to review and accept new terms and conditions.
Payment flow

Customers paying via TrueMoney QR go through an offline payment flow. Once the charge is created, it can only be authorized offline — the customer must scan the generated QR code with the TrueMoney app on their phone to complete the payment.
After the customer selects TrueMoney QR as their preferred payment method, your site generates the QR code. The customer scans it to complete the payment.
TrueMoney QR supports the following payment methods:
- TrueMoney Wallet (Wallet Balance)
- Bank Account
- Credit/Debit Card
- Pay Next (Full payment)
- Pay Next Extra (Full payment)
Implementation
To create a charge using TrueMoney QR, make the following API requests:
- Create a payment source (
type:truemoney_qr) using Omise.js or one of the mobile SDKs (iOS or Android). - Create a charge using the source identifier from step 1.
- After receiving the charge completion webhook event, retrieve the charge to verify its status (optional, but recommended).
Use your public key to create the TrueMoney QR source on the client (a customer's browser or mobile phone). Use your secret key to create the TrueMoney QR charge on the server.
If both source creation and charge must happen server-side, you can combine them in a single API request using your secret key.
Creating a source
When the customer confirms they wish to pay with TrueMoney QR, create a source specifying amount, currency, and type.
| Parameter | Type | Description |
|---|---|---|
amount |
integer | (Required) See Limits |
currency |
string | (Required) THB |
type |
string | (Required) truemoney_qr |
The following examples demonstrate creating a TrueMoney QR source for ฿4,000. Replace omise_public_key and $OMISE_PUBLIC_KEY with the test public key from your dashboard.
In Omise.js, the
typeparameter is the first argument passed to thecreateSourcemethod.
Omise.setPublicKey(omise_public_key);
Omise.createSource('truemoney_qr', {
"amount": 400000,
"currency": "THB",
}, function(statusCode, response) {
console.log(response);
});
For testing, you can make the same request using curl:
curl https://api.omise.co/sources \
-u $OMISE_PUBLIC_KEY: \
-d "amount=400000" \
-d "currency=THB" \
-d "type=truemoney_qr"
Example response:
{
"object": "source",
"id": "src_test_616e6qe9duz2cthauum",
"livemode": false,
"location": "/sources/src_test_616e6qe9duz2cthauum",
"amount": 400000,
"barcode": null,
"bank": null,
"created_at": "2024-09-23T03:18:39Z",
"currency": "THB",
"email": null,
"flow": "offline",
"installment_term": null,
"ip": "35.198.236.178",
"absorption_type": null,
"name": null,
"mobile_number": null,
"phone_number": null,
"platform_type": null,
"scannable_code": null,
"billing": null,
"shipping": null,
"items": [],
"references": null,
"provider_references": null,
"store_id": null,
"store_name": null,
"terminal_id": null,
"type": "truemoney_qr",
"zero_interest_installments": null,
"charge_status": "unknown",
"receipt_amount": null,
"discounts": [],
"promotion_code": null
}
The id attribute is the source identifier (begins with src).
Creating a charge
Create a charge specifying source, amount, and currency:
source— the source identifier returned in the previous step.amountandcurrency— must match the values used when creating the source.
Replace $OMISE_SECRET_KEY with the test secret key from your dashboard. Replace $SOURCE_ID with the id of the source.
curl https://api.omise.co/charges \
-u $OMISE_SECRET_KEY: \
-d "amount=400000" \
-d "currency=THB" \
-d "return_uri=http://example.com/orders/345678/complete" \
-d "source=$SOURCE_ID"
Example response:
{
"object": "charge",
"id": "chrg_test_616e6qhdvp3gv5hb31y",
"location": "/charges/chrg_test_616e6qhdvp3gv5hb31y",
"amount": 400000,
"acquirer_reference_number": null,
"net": 0,
"fee": 0,
"fee_vat": 0,
"interest": 0,
"interest_vat": 0,
"funding_amount": 400000,
"refunded_amount": 0,
"transaction_fees": {
"fee_flat": null,
"fee_rate": null,
"vat_rate": "7.0"
},
"platform_fee": {
"fixed": null,
"amount": null,
"percentage": null
},
"currency": "THB",
"funding_currency": "THB",
"ip": null,
"refunds": {
"object": "list",
"data": [],
"limit": 20,
"offset": 0,
"total": 0,
"location": "/charges/chrg_test_616e6qhdvp3gv5hb31y/refunds",
"order": "chronological",
"from": "1970-01-01T00:00:00Z",
"to": "2024-09-23T03:18:40Z"
},
"link": null,
"description": null,
"metadata": {},
"card": null,
"source": {
"object": "source",
"id": "src_test_616e6q22leajy1hszrg",
"livemode": false,
"location": "/sources/src_test_616e6q22leajy1hszrg",
"amount": 400000,
"barcode": null,
"bank": null,
"created_at": "2024-09-23T03:18:38Z",
"currency": "THB",
"email": null,
"flow": "offline",
"installment_term": null,
"ip": "35.198.236.178",
"absorption_type": null,
"name": null,
"mobile_number": null,
"phone_number": null,
"platform_type": null,
"scannable_code": {
"object": "barcode",
"type": "qr",
"image": {
"object": "document",
"livemode": false,
"id": "docu_test_616e6qjb5979wb1nopf",
"deleted": false,
"filename": "qrcode.png",
"location": "/charges/chrg_test_616e6qhdvp3gv5hb31y/documents/docu_test_616e6qjb5979wb1nopf",
"kind": "qr",
"download_uri": "https://api.omise.co/charges/chrg_test_616e6qhdvp3gv5hb31y/documents/docu_test_616e6qjb5979wb1nopf/downloads/0AF28F77E29F13C1",
"created_at": "2024-09-23T03:18:40Z"
}
},
"billing": null,
"shipping": null,
"items": [],
"references": null,
"provider_references": {
"reference_number_1": "pay2_test_616e6qhftlwd4241yc1",
"reference_number_2": null
},
"store_id": null,
"store_name": null,
"terminal_id": null,
"type": "truemoney_qr",
"zero_interest_installments": null,
"charge_status": "pending",
"receipt_amount": null,
"discounts": [],
"promotion_code": null
},
"schedule": null,
"linked_account": null,
"customer": null,
"dispute": null,
"transaction": null,
"failure_code": null,
"failure_message": null,
"status": "pending",
"authorize_uri": "https://pay.omise.co/payments/pay2_test_616e6qhftlwd4241yc1/authorize?acs=false",
"return_uri": "http://example.com/orders/345678/complete",
"created_at": "2024-09-23T03:18:40Z",
"paid_at": null,
"authorized_at": null,
"expires_at": "2024-09-24T03:18:40Z",
"expired_at": null,
"reversed_at": null,
"zero_interest_installments": false,
"branch": null,
"terminal": null,
"device": null,
"authorized": false,
"capturable": false,
"capture": true,
"disputable": false,
"livemode": false,
"refundable": false,
"partially_refundable": false,
"reversed": false,
"reversible": false,
"voided": false,
"paid": false,
"expired": false,
"can_perform_void": false,
"approval_code": null
}
Creating a source and charge
Alternatively, you can create and charge a source in a single API request using your secret key:
curl https://api.omise.co/charges \
-u $OMISE_SECRET_KEY: \
-d "amount=400000" \
-d "currency=THB" \
-d "return_uri=http://example.com/orders/345678/complete" \
-d "source[type]=truemoney_qr"
Completing the charge
A newly created charge has its status set to pending. Other possible values are successful, failed, and expired.
The following sequence diagram illustrates the full payment flow:
Presenting the QR code
After the charge is created, display the QR code to the customer so they can scan it with the TrueMoney app. The QR code image is available as a download_uri nested inside the charge object:
charge
└── source
└── scannable_code
└── image
└── download_uri (URL of the QR code image)
Render the download_uri value as an img tag on your checkout page:
<img src="{download_uri}" alt="TrueMoney QR code" />
Example scannable_code object:
{
"object": "barcode",
"type": "qr",
"image": {
"object": "document",
"livemode": false,
"id": "docu_test_616e6q70sr2xc8a3ugl",
"deleted": false,
"filename": "qrcode.png",
"location": "/charges/chrg_test_616e6q4xsj0jk1y0ozi/documents/docu_test_616e6q70sr2xc8a3ugl",
"kind": "qr",
"download_uri": "https://api.omise.co/charges/chrg_test_616e6q4xsj0jk1y0ozi/documents/docu_test_616e6q70sr2xc8a3ugl/downloads/989A209A9C571BFB",
"created_at": "2024-09-23T03:18:38Z"
}
}
Note: The QR code is valid for 24 hours from charge creation, as indicated by the
expires_atfield on the charge object. After this window, the charge status changes toexpiredand the QR code can no longer be used. You should display a countdown or expiry notice to the customer.
Authorizing the charge
The customer scans the QR code using the TrueMoney app on their phone. This authorizes the charge with TrueMoney's offline provider.
Receiving the charge completion event
Use webhook events to be notified when a charge is completed. Configure a webhook endpoint on your dashboard to receive the following events:
| Event | Description |
|---|---|
charge.create |
Fired when the charge is created with status: pending. |
charge.complete |
Fired when the charge is authorized (successfully or not). |
Checking the charge status
After receiving the charge.complete event, retrieve the charge using its id and verify that the status in the charge object matches the status in the webhook event.
| Status | Meaning |
|---|---|
successful |
Payment was received. Fulfill the order. |
failed |
Payment failed. Check failure_code and failure_message for details. |
expired |
The customer did not scan the QR code within 24 hours. The charge cannot be reused. |
Failure codes:
| Failure Code | Description | Recommended action |
|---|---|---|
failed_processing |
General payment processing failure. | Ask the customer to retry or select an alternative payment method. |
Testing
You can simulate the full payment flow in test mode without real funds.
- Create a source and charge using your test public and secret keys.
- Open the Omise dashboard and navigate to the charge.
- Click Actions and select Mark as Successful or Mark as Failed to simulate the customer scanning the QR code.
- Verify that your webhook endpoint receives the
charge.completeevent and that your application updates the order status accordingly.
Test mode keys are prefixed with
pkey_test_(public) andskey_test_(secret). Live mode keys are prefixed withpkey_andskey_.
Voids and refunds
TrueMoney charges can only be voided on the same day as the transaction authorization. Refunds can be made up to 30 days after the charge is created.
| Cancel within a day (Voids) | Cancel next day (Refunds) |
|||
|---|---|---|---|---|
| Payment Methods | Fully | Partial | Fully | Partial |
| TrueMoney Wallet (Wallet Balance) |
✅ | ✅ | ✅ | |
| Bank Account | ✅ | ✅ | ✅ | |
| Credit/Debit Card | ✅ | ✅ | ||
| Pay Next (Full payment) | ✅ | ✅ | ||
| Pay Next Extra (Full payment) | ✅ | ✅ | ||
See the Refunds API documentation for information on refunding a TrueMoney charge.
- All five payment methods support full voids on the same day as authorization.
- No payment method supports partial voids.
- For refunds, TrueMoney Wallet (Wallet Balance) and Bank Account support both full and partial refunds.
- Credit/Debit Card, Pay Next, and Pay Next Extra support full refunds only. No partial refunds are available for those three methods.
Limits
All amounts are in the smallest currency unit (satang). For example, ฿20.00 = 2000.
TrueMoney Wallet
| Limit | Amount (satang) | Amount (THB) |
|---|---|---|
| Minimum | 2000 |
฿20.00 |
| Maximum | 5000000 |
฿50,000.00 |
Pay Next
| Limit | Amount (satang) | Amount (THB) |
|---|---|---|
| Minimum | 30000 |
฿300.00 |
Note: Maximum limit for Pay Next depends on each business.
Pay Next Extra
| Limit | Amount (satang) | Amount (THB) |
|---|---|---|
| Minimum | 100000 |
฿1,000.00 |
Note: Maximum limit for Pay Next Extra depends on each business.
FAQ
What happens if the customer does not scan the QR code in time?
The charge expires after 24 hours, as shown in the expires_at field of the charge object. When this happens, the charge status changes to expired. The QR code can no longer be used, and you must create a new charge to accept payment from the customer.
Can I create a new charge if the previous one expired?
Yes. Create a new source and charge using the same flow. The expired charge has no effect on the new one.
Can I partially void a TrueMoney charge?
No. Voids on TrueMoney charges are full only. Partial cancellations are not supported within the same day.
Which payment methods support partial refunds?
Only TrueMoney Wallet (Wallet Balance) and Bank Account support partial refunds. Credit/Debit Card, Pay Next, and Pay Next Extra support full refunds only.
What should I do if I receive a failed_processing failure code?
This indicates a general processing failure on TrueMoney's side. Ask the customer to retry the payment. If the problem persists, advise the customer to check their TrueMoney app or select a different payment method. Contact support@omise.co if failures continue in live mode.
How do I know whether to void or refund a charge?
If you need to cancel a charge on the same day it was authorized, use a void. If you need to cancel or return funds on a subsequent day (up to 30 days after charge creation), use a refund. See the Voids and refunds table for per-payment-method support.
Is TrueMoney QR available outside Thailand?
No. TrueMoney QR is currently supported in Thailand only and processes payments in THB.
Where can I find my API keys?
See How to access Omise API keys.
What are the minimum and maximum payment amounts for TrueMoney QR?
- The minimum and maximum amounts depend on the payment method used at the TrueMoney app.
- TrueMoney Wallet has a minimum of
2000(฿20.00) and a maximum of5000000(฿50,000.00). - Pay Next has a minimum of
30000(฿300.00). - Pay Next Extra has a minimum of
100000(฿1,000.00). - The maximum limits for Pay Next and Pay Next Extra depend on each business. See Limits for the full breakdown.
Why is my charge being rejected — could it be a limits issue?
If a charge fails immediately at creation, verify that the amount meets the minimum for the intended payment method and does not exceed the maximum where applicable. All amounts must be submitted in satang (the smallest currency unit) — for example, ฿300.00 must be submitted as 30000, not 300.