Skip to main content

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.

Looking for QR code payments?

For offline QR code-based payments, see TrueMoney QR. This page covers the online redirect flow.

Payment Flow​

TrueMoney Payment Flow

The image above shows the complete redirect payment flow using one-time password (OTP) verification.

Overview​

Information Accuracy

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 Statistics

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​

RegionCurrencyMin AmountMax AmountDaily Limit
ThailandTHBā¸ŋ20.00ā¸ŋ100,000ā¸ŋ40,000 - ā¸ŋ200,000*

*Daily limits vary based on customer's KYC verification level

Transaction Limits by Verification Level​

Verification LevelPer TransactionDaily LimitMonthly 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,000Unlimited

How It Works​

Customer Experience:

  1. Customer selects TrueMoney at checkout
  2. Enters mobile phone number
  3. Receives OTP via SMS
  4. Enters OTP to authorize
  5. Confirms payment amount
  6. Returns to merchant site

Implementation​

Step 1: Create TrueMoney Source​

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"

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
});
No Partial Refunds

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:

  1. Create a source and charge using test API keys
  2. You'll receive an authorize_uri for redirect
  3. In test mode, manually mark charges as successful/failed in dashboard
  4. 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.

Next Steps​

  1. Create TrueMoney source
  2. Implement redirect flow
  3. Set up webhook handling
  4. Test payment flow
  5. Go live