メインコンテンツへスキップ

TrueMoney QR

タイのTrueMoneyの3000万人以上のユーザーからオフラインQRコード決済を受け付けます。顧客はTrueMoneyアプリでQRコードをスキャンし、複数の支払い元から選択します。

概要

TrueMoney QRは、顧客がTrueMoneyモバイルアプリを使用して決済するためのスキャン可能なQRコードを生成するオフライン決済方法です。リダイレクトフローとは異なり、顧客はWebサイトや販売時点情報管理システムに表示されたQRコードをスキャンして、スマートフォンで決済を完了します。

主な機能:

  • オフライン決済 - リダイレクト不要、顧客がQRコードをスキャン
  • 複数の資金源 - ウォレット、カード、銀行口座、Pay Next
  • 大規模なユーザーベース - タイのTrueMoneyユーザー3000万人以上
  • 柔軟な最小金額 - ほとんどの決済方法で最低0.01バーツから
  • 迅速な決済 - 従来の銀行振込より高速
  • POSに対応 - 店舗内およびオンライン決済に対応

サポート地域

地域通貨最小金額最大金額備考
タイTHB฿0.01*฿50,000*最小金額は資金源により異なる

資金源別の最小金額

顧客はQRコードをスキャンする際に、希望する決済方法を選択できます。各方法には異なる最小要件があります:

資金源最小金額備考
ウォレット残高฿0.01TrueMoneyウォレットの資金
クレジット/デビットカード฿0.01TrueMoney経由のカード決済
Pay Next(全額払い)฿0.01後払い(全額)
Pay Next Extra(全額払い)฿0.01拡張後払いオプション(全額)
銀行口座銀行により異なる最小金額は発行銀行に依存
資金源の選択

顧客がQRコードをスキャンすると、TrueMoneyアプリ内で決済元を選択します。加盟店は顧客が選択する資金源を制御できません。すべての資金源に対応する最小取引金額を設定するか、最小要件を明確に伝えてください。

仕組み

決済フロー

TrueMoney QR決済フロー

顧客体験:

  1. 顧客がチェックアウトでTrueMoney QRを選択
  2. 加盟店が画面にQRコードを表示
  3. 顧客がTrueMoneyアプリを開く
  4. 顧客がQRコードをスキャン
  5. 顧客が決済元(ウォレット/カード/銀行/Pay Next)を選択
  6. 顧客が決済を承認
  7. 決済完了

実装

ステップ1: TrueMoney QRソースの作成

curl https://api.omise.co/sources \
-u skey_test_YOUR_SECRET_KEY: \
-d "type=truemoney_qr" \
-d "amount=50000" \
-d "currency=THB"

レスポンス:

{
"object": "source",
"id": "src_test_5rt6s9vah5lkvi1rh9c",
"type": "truemoney_qr",
"flow": "offline",
"amount": 50000,
"currency": "THB"
}

ステップ2: チャージの作成

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"

レスポンスにはQRコードが含まれます:

{
"object": "charge",
"id": "chrg_test_5rt6s9vah5lkvi1rh9d",
"amount": 50000,
"currency": "THB",
"status": "pending",
"source": {
"object": "source",
"type": "truemoney_qr",
"flow": "offline",
"scannable_code": {
"object": "barcode",
"type": "qr",
"image": {
"download_uri": "https://api.omise.co/charges/chrg_test_.../documents/docu_test_.../downloads/..."
}
}
}
}

ステップ3: QRコードの表示

app.post('/create-truemoney-qr-payment', async (req, res) => {
try {
const { amount, order_id } = req.body;

// Validate amount
if (amount < 1 || amount > 5000000) {
return res.status(400).json({
error: 'Amount must be between ฿0.01 and ฿50,000'
});
}

// Create source
const source = await omise.sources.create({
type: 'truemoney_qr',
amount: amount,
currency: 'THB'
});

// 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
}
});

// Get QR code image URL
const qrCodeUrl = charge.source.scannable_code.image.download_uri;

// Return to frontend
res.json({
charge_id: charge.id,
qr_code_url: qrCodeUrl,
amount: amount
});

} catch (error) {
console.error('TrueMoney QR payment error:', error);
res.status(500).json({ error: error.message });
}
});

フロントエンドの表示:

<div class="payment-container">
<h2>TrueMoneyでスキャンして支払う</h2>
<div class="qr-code-container">
<img id="qrCode" src="" alt="TrueMoney QR Code" />
</div>
<p class="amount">金額: <strong>฿<span id="amount"></span></strong></p>
<div class="instructions">
<h3>お支払い方法:</h3>
<ol>
<li>スマートフォンでTrueMoneyアプリを開く</li>
<li>「QRスキャン」ボタンをタップ</li>
<li>上記のコードをスキャン</li>
<li>決済方法を選択(ウォレット/カード/銀行/Pay Next)</li>
<li>決済を承認</li>
</ol>
</div>
</div>

<script>
async function createPayment(amount, orderId) {
const response = await fetch('/create-truemoney-qr-payment', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ amount, order_id: orderId })
});

const data = await response.json();

// Display QR code
document.getElementById('qrCode').src = data.qr_code_url;
document.getElementById('amount').textContent = (data.amount / 100).toFixed(2);

// Start polling for payment status
pollPaymentStatus(data.charge_id);
}

async function pollPaymentStatus(chargeId) {
const interval = setInterval(async () => {
const response = await fetch(`/check-payment-status/${chargeId}`);
const data = await response.json();

if (data.status === 'successful') {
clearInterval(interval);
window.location.href = '/payment-success';
} else if (data.status === 'failed') {
clearInterval(interval);
window.location.href = '/payment-failed';
}
}, 3000); // Check every 3 seconds
}
</script>

ステップ4: Webhookの処理

app.post('/webhooks/omise', (req, res) => {
const event = req.body;

if (event.key === 'charge.complete' && event.data.source.type === 'truemoney_qr') {
const charge = event.data;

if (charge.status === 'successful') {
// Process order
processOrder(charge.metadata.order_id);
console.log(`TrueMoney QR payment successful: ${charge.id}`);
} else if (charge.status === 'failed') {
// Handle failure
handleFailedPayment(charge.metadata.order_id, charge.failure_message);
}
}

res.sendStatus(200);
});

完全な実装例

// Express.js server with TrueMoney QR
const express = require('express');
const omise = require('omise')({
secretKey: process.env.OMISE_SECRET_KEY
});

const app = express();
app.use(express.json());

// Create payment and get QR code
app.post('/checkout/truemoney-qr', async (req, res) => {
try {
const { amount, order_id } = req.body;

// Validate amount
if (amount < 1 || amount > 5000000) {
return res.status(400).json({
error: 'Amount must be between ฿0.01 and ฿50,000'
});
}

// Create source
const source = await omise.sources.create({
type: 'truemoney_qr',
amount: amount,
currency: 'THB'
});

// 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,
created_at: new Date().toISOString()
}
});

// Get QR code URL
const qrCodeUrl = charge.source.scannable_code.image.download_uri;

res.json({
success: true,
charge_id: charge.id,
qr_code_url: qrCodeUrl,
amount: amount
});

} catch (error) {
console.error('TrueMoney QR error:', error);
res.status(500).json({
success: false,
error: error.message
});
}
});

// Check payment status (for polling)
app.get('/check-payment-status/:chargeId', async (req, res) => {
try {
const charge = await omise.charges.retrieve(req.params.chargeId);
res.json({
status: charge.status,
paid: charge.paid,
amount: charge.amount
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});

// Webhook handler (primary method)
app.post('/webhooks/omise', (req, res) => {
const event = req.body;

if (event.key === 'charge.complete') {
const charge = event.data;

if (charge.source.type === 'truemoney_qr') {
if (charge.status === 'successful') {
updateOrderStatus(charge.metadata.order_id, 'paid');
sendConfirmation(charge.metadata.order_id);
} else {
updateOrderStatus(charge.metadata.order_id, 'failed');
}
}
}

res.sendStatus(200);
});

app.listen(3000);

ボイドと返金のサポート

チャージのボイド

TrueMoney QRは当日のみボイドをサポートしています:

// Void same-day (full amount only)
const refund = await omise.charges.refund('chrg_test_...', {
amount: 50000 // Must be full amount
});

if (refund.voided) {
console.log('Charge was voided (same-day)');
}
当日のボイドのみ

ボイドはチャージが作成された当日のみ利用可能です。深夜を過ぎると、代わりに返金を使用する必要があります。

返金

30日以内の返金が可能です。サポートは資金源により異なります:

// Full refund (all payment methods)
const refund = await omise.charges.refund('chrg_test_...', {
amount: 50000 // Full amount
});

// Partial refund (Wallet, Bank Account, Pay Next)
const partialRefund = await omise.charges.refund('chrg_test_...', {
amount: 25000 // Half amount
});

資金源別の返金サポート:

決済方法全額返金部分返金期間
ウォレット残高✅ あり✅ あり30日以内
銀行口座✅ あり✅ あり30日以内
Pay Next✅ あり✅ あり30日以内
Pay Next Extra✅ あり✅ あり30日以内
クレジット/デビットカード✅ あり(翌日以降)❌ なし30日以内
クレジット/デビットカードの返金

カード決済は全額返金のみをサポートし、取引日以降(翌日以降)のみ可能です。当日のカード返金はサポートされていません。

よくある問題とトラブルシューティング

問題: QRコードが表示されない

原因:

  • 無効な画像URL
  • CORS制限
  • ネットワークの問題

解決策:

// Add error handling for QR code loading
<img
src={qrCodeUrl}
onError={(e) => {
console.error('QR code load failed');
e.target.src = '/images/qr-placeholder.png';
showRetryButton();
}}
alt="TrueMoney QR Code"
/>

問題: 決済タイムアウト

原因: 顧客がタイムアウト期間内にQRコードをスキャンしなかった

解決策:

// Set reasonable timeout and allow regeneration
setTimeout(() => {
if (!paymentConfirmed) {
showExpiredMessage();
allowNewQRGeneration();
}
}, 15 * 60 * 1000); // 15 minutes

問題: 顧客がスキャンしたが決済が失敗した

原因:

  • 残高/クレジット不足
  • 銀行が拒否
  • 1日の限度額超過

解決策:

  • 明確なエラーメッセージを表示
  • 別の決済方法での再試行を許可
  • カスタマーサポートの連絡先を提供

問題: Webhookが受信されない

原因: ネットワークの問題またはWebhook設定

解決策:

// Implement polling as backup
function pollPaymentStatus(chargeId) {
const maxAttempts = 20; // 1 minute total
let attempts = 0;

const interval = setInterval(async () => {
attempts++;
const charge = await checkChargeStatus(chargeId);

if (charge.status !== 'pending' || attempts >= maxAttempts) {
clearInterval(interval);
handlePaymentResult(charge);
}
}, 3000);
}

ベストプラクティス

1. 明確なQRコードを表示

.qr-code-container {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
text-align: center;
}

.qr-code-container img {
max-width: 300px;
width: 100%;
height: auto;
}

2. 決済手順を表示

<div class="payment-instructions">
<h3>お支払い方法</h3>
<ol>
<li>TrueMoneyアプリを開く</li>
<li>「QRスキャン」をタップ</li>
<li>QRコードにカメラを向ける</li>
<li>決済方法を選択</li>
<li>決済を確認</li>
</ol>

<div class="funding-sources">
<p>以下でお支払いいただけます:</p>
<ul>
<li>💰 TrueMoneyウォレット残高</li>
<li>💳 クレジット/デビットカード</li>
<li>🏦 銀行口座</li>
<li>📆 Pay Next(後払い)</li>
</ul>
</div>
</div>

3. 決済ステータスの処理

// Use both webhook and polling
class PaymentMonitor {
constructor(chargeId) {
this.chargeId = chargeId;
this.resolved = false;
}

startMonitoring() {
// Primary: webhook
this.setupWebhookListener();

// Backup: polling
this.startPolling();

// Timeout: expire after 15 minutes
this.setupTimeout();
}

handleSuccess() {
if (!this.resolved) {
this.resolved = true;
this.stopPolling();
redirectToSuccess();
}
}
}

4. 資金源別の金額検証

// Inform users of minimum requirements
function displayMinimumAmounts() {
return `
<div class="minimum-info">
<p>最小決済金額:</p>
<ul>
<li>ウォレット/カード/Pay Next: ฿0.01</li>
<li>銀行口座: 銀行により異なる</li>
</ul>
</div>
`;
}

5. リトライロジックの実装

function createPaymentWithRetry(amount, orderId, maxRetries = 3) {
let attempts = 0;

async function attempt() {
attempts++;
try {
return await createTrueMoneyQRPayment(amount, orderId);
} catch (error) {
if (attempts < maxRetries && error.recoverable) {
await delay(2000 * attempts); // Exponential backoff
return attempt();
}
throw error;
}
}

return attempt();
}

FAQ

TrueMoney QRとは何ですか?

TrueMoney QRは、顧客がTrueMoneyアプリでQRコードをスキャンして決済を完了するオフライン決済方法です。リダイレクト方式とは異なり、決済は完全に顧客のモバイルアプリ内で行われます。

TrueMoney QRとTrueMoney Walletの違いは何ですか?
  • TrueMoney QR: オフライン、QRコードベース、顧客がアプリでコードをスキャン
  • TrueMoney Wallet: オンラインリダイレクト、電話番号 + OTP認証

どちらも同じTrueMoneyエコシステムにアクセスしますが、決済フローが異なります。

どの資金源が部分返金をサポートしていますか?

ウォレット残高、銀行口座、Pay Next、Pay Next Extraが部分返金をサポートしています。クレジット/デビットカードは全額返金のみをサポートしています(翌日以降)。

QRコードの有効期限はどのくらいですか?

QRコードは通常、非アクティブ状態が15〜20分続くと期限切れになります。顧客がより多くの時間を必要とする場合は、新しいQRコードを生成してください。

店舗内決済にTrueMoney QRを使用できますか?

はい!TrueMoney QRは販売時点情報管理システムに最適です。POSディスプレイにQRコードを表示するか、顧客がスキャンできるようにレシートに印刷してください。

顧客の銀行口座に資金が不足している場合はどうなりますか?

決済は失敗します。顧客は以下の対応ができます:

  • TrueMoneyウォレットにチャージ
  • 別の資金源を使用(カード、別の銀行口座)
  • 対象であればPay Nextを試す

テスト

テストモード

TrueMoney QRは、テストAPIキーを使用してテストモードでテストできます:

テストフロー:

  1. テストAPIキーを使用してソースとチャージを作成
  2. テストQRコードURLを受け取る
  3. テストモードでは、ダッシュボードでチャージを成功/失敗として手動でマーク
  4. ステータス変更に基づいてWebhookがトリガーされる

ステータス変更のテスト:

// Create test charge
const charge = await omise.charges.create({
amount: 50000,
currency: 'THB',
source: testSourceId,
return_uri: 'https://example.com/callback'
});

// Get test QR code
console.log(charge.source.scannable_code.image.download_uri);

// 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

テストシナリオ:

  • 成功した決済: 注文処理ワークフローを検証
  • 失敗した決済: エラー処理とリトライロジックをテスト
  • タイムアウト: 期限切れQRコードのシナリオをテスト
  • Webhook配信: すべてのWebhookが適切に受信されることを確認
  • 返金: 全額および部分返金フローをテスト

重要な注意事項:

  • テストQRコードは統合テスト専用です
  • テストチャージは実際のTrueMoneyサーバーに接続しません
  • ダッシュボードを使用して決済完了をシミュレート
  • 本番稼働前に必ずWebhook処理をテスト

包括的なテストガイドラインについては、テストドキュメントを参照してください。

関連リソース

次のステップ

  1. TrueMoney QRソースを作成
  2. 顧客にQRコードを表示
  3. 決済Webhookを処理
  4. 決済フローをテスト
  5. 本番稼働