TrueMoney Wallet
Accept online payments from one of Thailand's most popular e-wallets with 30M+ active users. Customers are redirected to enter their phone number and authorize with OTP.
For offline QR code-based payments, see TrueMoney QR. This page covers the online redirect flow.
Payment Flowâ

The image above shows the complete redirect payment flow using one-time password (OTP) verification.
Overviewâ
Payment method features, limits, user statistics, and fees are subject to change. Information is based on publicly available sources and may not reflect your specific merchant agreement. Always refer to official Omise documentation and your merchant dashboard for current, binding information.
User numbers are approximate and based on publicly available information. Actual active user counts may vary.
TrueMoney Wallet is a leading mobile payment solution in Thailand with over 30M+ users. The wallet allows customers to pay using their mobile phone number with OTP verification for added security.
Key Features:
- â Large user base - 30M+ active users in Thailand
- â Simple flow - Phone number + OTP authentication
- â Quick settlement - Faster than traditional banking
- â Mobile-first - Optimized for smartphone users
- â Trusted brand - Part of Ascend Group (CP Group)
Supported Regionsâ
| Region | Currency | Min Amount | Max Amount | Daily Limit |
|---|---|---|---|---|
| Thailand | THB | ā¸ŋ20.00 | ā¸ŋ100,000 | ā¸ŋ40,000 - ā¸ŋ200,000* |
*Daily limits vary based on customer's KYC verification level
Transaction Limits by Verification Levelâ
| Verification Level | Per Transaction | Daily Limit | Monthly Limit |
|---|---|---|---|
| Basic (Phone only) | ā¸ŋ100,000 | ā¸ŋ40,000 | ā¸ŋ200,000 |
| Plus (ID card) | ā¸ŋ100,000 | ā¸ŋ100,000 | ā¸ŋ500,000 |
| Premium (Bank account) | ā¸ŋ100,000 | ā¸ŋ200,000 | Unlimited |
How It Worksâ
Customer Experience:
- Customer selects TrueMoney at checkout
- Enters mobile phone number
- Receives OTP via SMS
- Enters OTP to authorize
- Confirms payment amount
- Returns to merchant site
Implementationâ
Step 1: Create TrueMoney Sourceâ
- cURL
- Node.js
- PHP
- Python
curl https://api.omise.co/sources \
-u skey_test_YOUR_SECRET_KEY: \
-d "type=truemoney" \
-d "amount=50000" \
-d "currency=THB" \
-d "phone_number=+66876543210"
const omise = require('omise')({
secretKey: 'skey_test_YOUR_SECRET_KEY'
});
const source = await omise.sources.create({
type: 'truemoney',
amount: 50000, // THB 500.00
currency: 'THB',
phone_number: '+66876543210'
});
<?php
$source = OmiseSource::create(array(
'type' => 'truemoney',
'amount' => 50000,
'currency' => 'THB',
'phone_number' => '+66876543210'
));
?>
import omise
omise.api_secret = 'skey_test_YOUR_SECRET_KEY'
source = omise.Source.create(
type='truemoney',
amount=50000,
currency='THB',
phone_number='+66876543210'
)
Response:
{
"object": "source",
"id": "src_test_5rt6s9vah5lkvi1rh9c",
"type": "truemoney",
"flow": "redirect",
"amount": 50000,
"currency": "THB",
"phone_number": "+66876543210"
}
Step 2: Create Chargeâ
curl https://api.omise.co/charges \
-u skey_test_YOUR_SECRET_KEY: \
-d "amount=50000" \
-d "currency=THB" \
-d "source=src_test_5rt6s9vah5lkvi1rh9c" \
-d "return_uri=https://yourdomain.com/payment/callback"
Step 3: Redirect Customerâ
app.post('/create-truemoney-payment', async (req, res) => {
// Create source
const source = await omise.sources.create({
type: 'truemoney',
amount: req.body.amount,
currency: 'THB',
phone_number: req.body.phone_number
});
// Create charge
const charge = await omise.charges.create({
amount: req.body.amount,
currency: 'THB',
source: source.id,
return_uri: 'https://yourdomain.com/payment/callback',
metadata: {
order_id: req.body.order_id
}
});
// Redirect customer
res.redirect(charge.authorize_uri);
});
Step 4: Handle Returnâ
app.get('/payment/callback', async (req, res) => {
const chargeId = req.query.charge_id;
// Retrieve charge status
const charge = await omise.charges.retrieve(chargeId);
if (charge.status === 'successful') {
// Payment successful
await processOrder(charge.metadata.order_id);
res.redirect('/payment-success');
} else if (charge.status === 'failed') {
// Payment failed
res.redirect('/payment-failed?reason=' + charge.failure_message);
} else {
// Still pending
res.redirect('/payment-pending');
}
});
Step 5: Handle Webhookâ
app.post('/webhooks/omise', (req, res) => {
const event = req.body;
if (event.key === 'charge.complete' && event.data.source.type === 'truemoney') {
const charge = event.data;
if (charge.status === 'successful') {
processOrder(charge.metadata.order_id);
}
}
res.sendStatus(200);
});
Complete Implementation Exampleâ
// Express.js server
const express = require('express');
const omise = require('omise')({
secretKey: process.env.OMISE_SECRET_KEY
});
const app = express();
app.use(express.json());
// Checkout page
app.post('/checkout/truemoney', async (req, res) => {
try {
const { amount, phone_number, order_id } = req.body;
// Validate phone number format
if (!/^\+66\d{9}$/.test(phone_number)) {
return res.status(400).json({
error: 'Invalid phone number. Use format: +66876543210'
});
}
// Check amount limits
if (amount < 2000 || amount > 10000000) {
return res.status(400).json({
error: 'Amount must be between ā¸ŋ20 and ā¸ŋ100,000'
});
}
// Create source
const source = await omise.sources.create({
type: 'truemoney',
amount: amount,
currency: 'THB',
phone_number: phone_number
});
// Create charge
const charge = await omise.charges.create({
amount: amount,
currency: 'THB',
source: source.id,
return_uri: `${process.env.BASE_URL}/payment/callback`,
metadata: {
order_id: order_id,
customer_phone: phone_number
}
});
// Return authorization URL
res.json({
authorize_uri: charge.authorize_uri,
charge_id: charge.id
});
} catch (error) {
console.error('TrueMoney payment error:', error);
res.status(500).json({ error: error.message });
}
});
// Callback handler
app.get('/payment/callback', async (req, res) => {
try {
const chargeId = req.query.charge_id;
const charge = await omise.charges.retrieve(chargeId);
if (charge.status === 'successful') {
res.redirect(`/order-success?order=${charge.metadata.order_id}`);
} else {
res.redirect(`/payment-failed?charge=${chargeId}`);
}
} catch (error) {
res.redirect('/payment-error');
}
});
// Webhook handler
app.post('/webhooks/omise', (req, res) => {
const event = req.body;
if (event.key === 'charge.complete') {
const charge = event.data;
if (charge.source.type === 'truemoney') {
if (charge.status === 'successful') {
// Process order
updateOrderStatus(charge.metadata.order_id, 'paid');
sendConfirmation(charge.metadata.customer_phone);
} else {
// Handle failure
updateOrderStatus(charge.metadata.order_id, 'failed');
}
}
}
res.sendStatus(200);
});
app.listen(3000);
Void and Refund Supportâ
Voiding Chargesâ
TrueMoney supports voiding within 24 hours of charge creation:
// Void immediately (full or partial)
const refund = await omise.charges.refund('chrg_test_...', {
amount: 50000 // Full amount
});
if (refund.voided) {
console.log('Charge was voided (within 24 hours)');
}
Refundsâ
Full refunds only within 30 days:
// Full refund only
const refund = await omise.charges.refund('chrg_test_...', {
amount: 50000 // Must be full amount
});
TrueMoney Wallet does NOT support partial refunds. Only full refunds are allowed within 30 days.
Common Issues & Troubleshootingâ
Issue: "Invalid phone number"â
Causes:
- Wrong format
- Missing country code
- Non-Thai number
Solution:
function validateThaiPhone(phone) {
// Accept formats: +66876543210, 0876543210
let normalized = phone.replace(/\s/g, '');
if (normalized.startsWith('0')) {
normalized = '+66' + normalized.substring(1);
}
if (!/^\+66\d{9}$/.test(normalized)) {
throw new Error('Invalid Thai phone number');
}
return normalized;
}
Issue: Customer exceeds daily limitâ
Error: Transaction rejected
Solution:
- Customer needs to upgrade TrueMoney account verification
- Split payment across multiple days
- Use alternative payment method
Issue: Payment pending too longâ
Cause: Customer didn't complete OTP verification
Solution:
- Set 15-20 minute timeout
- Allow customer to retry with new charge
- Show clear instructions
Issue: Return URI not calledâ
Cause: Customer closed browser
Solution:
- Implement webhook handling (more reliable)
- Provide order status check page
- Send SMS confirmation to customer
Best Practicesâ
1. Validate Phone Numbersâ
const phoneRegex = /^\+66[0-9]{9}$/;
if (!phoneRegex.test(phoneNumber)) {
return res.status(400).json({
error: 'Please enter a valid Thai phone number (e.g., +66876543210)'
});
}
2. Show Clear Instructionsâ
<div class="truemoney-instructions">
<h3>Pay with TrueMoney Wallet</h3>
<ol>
<li>Enter your TrueMoney-registered phone number</li>
<li>You'll receive an OTP via SMS</li>
<li>Enter the OTP to authorize payment</li>
<li>Confirm the amount</li>
</ol>
<p>Make sure you have sufficient balance in your TrueMoney Wallet.</p>
</div>
3. Handle Timeoutsâ
// Set reasonable timeout
setTimeout(() => {
if (!paymentConfirmed) {
showTimeoutMessage();
allowRetry();
}
}, 15 * 60 * 1000); // 15 minutes
4. Use Webhooksâ
Don't rely solely on redirect callbacks:
// Webhook is more reliable
app.post('/webhooks/omise', handleWebhook);
// Callback is backup
app.get('/payment/callback', handleCallback);
5. Provide Customer Supportâ
metadata: {
order_id: orderId,
customer_phone: phoneNumber,
customer_email: email,
support_ticket: ticketId
}
FAQâ
What is TrueMoney Wallet?
TrueMoney Wallet is a mobile payment app in Thailand with 30M+ users. Customers can load money into their wallet and pay using their phone number with OTP authentication.
Do customers need to register first?
Yes, customers must have an existing TrueMoney Wallet account. They can download the app and register with their Thai phone number.
What are the transaction fees?
Check your Omise dashboard for current rates. E-wallet fees are typically lower than credit cards.
How long does settlement take?
Settlements are typically faster than credit cards. Check with Omise support for specific timelines.
Can international customers use TrueMoney?
TrueMoney is only available for customers in Thailand with Thai phone numbers and Thai bank accounts.
What if customer has insufficient balance?
Payment will fail. Customer can top up their TrueMoney Wallet via:
- 7-Eleven stores
- Bank transfer
- Credit/debit card
- TrueMoney kiosks
Then retry the payment.
Testingâ
Test Modeâ
TrueMoney Wallet can be tested in test mode using your test API keys. In test mode:
Test Credentials:
- Use any valid Thai phone number format (+66XXXXXXXXX)
- Test charges will not actually debit customer accounts
- All test data uses test API keys (skey_test_xxx)
Test Flow:
- Create a source and charge using test API keys
- You'll receive an
authorize_urifor redirect - In test mode, manually mark charges as successful/failed in dashboard
- Webhooks will be triggered based on status changes
Testing Status Changes:
// Create test charge
const charge = await omise.charges.create({
amount: 50000,
currency: 'THB',
source: testSourceId,
return_uri: 'https://example.com/callback'
});
// In test mode, use Omise Dashboard to:
// 1. Navigate to the charge
// 2. Use "Actions" menu to mark as successful or failed
// 3. Verify webhook handling
Test Scenarios:
- Successful payment: Verify order fulfillment workflow
- Failed payment: Test error handling and retry logic
- Timeout: Test abandoned payment scenarios
- Webhook delivery: Ensure all webhooks are properly received
Important Notes:
- Test mode QR codes will redirect but won't connect to real TrueMoney servers
- Use the dashboard to simulate payment completion
- Always test webhook handling before going live
- Verify amount limits and validation logic
For comprehensive testing guidelines, see the Testing Documentation.
Related Resourcesâ
- Digital Wallets Overview - All wallet options
- TrueMoney QR - Offline QR payments
- GrabPay - Alternative wallet
- Rabbit LINE Pay - Another Thai wallet
- Refunds - Refund policies
- Testing - Test TrueMoney integration