Touch 'n Go eWallet
マレーシアで最も人気のあるデジタルウォレットTouch 'n Go eWalletから支払いを受け付けます。1700万人以上のユーザーと全国の広範な加盟店ネットワークを持っています。
概要
ユーザー数は概算であり、公開されている情報に基づいています。実際のアクティブユーザー数は異なる場合があります。
Touch 'n Go eWallet(TNG eWallet)はマレーシアの主要なデジタル決済プラットフォームで、もともと高速道路の通行料金で知られていましたが、現在はフル機能のデジタルウォレットに拡大しています。ユーザーはTNG eWalletの残高で、オンラインとオフラインの両方で即座に安全な取引を行うことができます。
主な特徴:
- ✅ 1700万人以上のユーザー - マレーシアで最も人気のあるeウォレット
- ✅ 即時確認 - リアルタイムの支払い処理
- ✅ 広範な採用 - マレーシア全土で受入
- ✅ 信頼されたブランド - Touch 'n Go Groupの一部
- ✅ 取引手数料なし - 顧客向け
- ✅ リワードプログラム - ユーザーはGO+ポイントを獲得
サポート地域
| 地域 | 通貨 | 最小金額 | 最大金額 | 日次制限 |
|---|---|---|---|---|
| マレーシア | MYR | RM1.00 | RM30,000 | RM30,000* |
*日次制限は顧客のウォレット確認レベルによって異なります
確認レベル別の取引制限
| 確認レベル | 1取引あたり | 日次制限 | 月次制限 |
|---|---|---|---|
| Lite (電話 + IC) | RM1,500 | RM1,500 | RM10,000 |
| Full (銀行リンク済み) | RM30,000 | RM30,000 | RM100,000 |
仕組み
顧客体験:
- 顧客がチェックアウトで「Touch 'n Go eWallet」を選択
- TNG認証ページにリダイレクト
- TNG eWalletアプリを開く(ディープリンク)
- 支払い詳細を確認
- 6桁のPINまたは生体認証で認証
- 支払いを確認
- 加盟店サイトに戻る
一般的な完了時間: 30秒〜2分
実装
ステップ1: Touch 'n Goソースを作成
- cURL
- Node.js
- PHP
- Python
- Ruby
- Go
- Java
- C#
curl https://api.omise.co/sources \
-u skey_test_YOUR_SECRET_KEY: \
-d "type=touch_n_go" \
-d "amount=15000" \
-d "currency=MYR"
const omise = require('omise')({
secretKey: 'skey_test_YOUR_SECRET_KEY'
});
const source = await omise.sources.create({
type: 'touch_n_go',
amount: 15000, // MYR 150.00
currency: 'MYR'
});
<?php
$source = OmiseSource::create(array(
'type' => 'touch_n_go',
'amount' => 15000,
'currency' => 'MYR'
));
?>
import omise
omise.api_secret = 'skey_test_YOUR_SECRET_KEY'
source = omise.Source.create(
type='touch_n_go',
amount=15000,
currency='MYR'
)
require 'omise'
Omise.api_key = 'skey_test_YOUR_SECRET_KEY'
source = Omise::Source.create({
type: 'touch_n_go',
amount: 15000,
currency: 'MYR'
})
source, err := client.Sources().Create(&operations.CreateSource{
Type: "touch_n_go",
Amount: 15000,
Currency: "MYR",
})
Source source = client.sources().create(new Source.CreateParams()
.type("touch_n_go")
.amount(15000L)
.currency("MYR"));
var source = await client.Sources.Create(new CreateSourceRequest
{
Type = "touch_n_go",
Amount = 15000,
Currency = "MYR"
});
レスポンス:
{
"object": "source",
"id": "src_test_5rt6s9vah5lkvi1rh9c",
"type": "touch_n_go",
"flow": "redirect",
"amount": 15000,
"currency": "MYR"
}
ステップ2: チャージを作成
curl https://api.omise.co/charges \
-u skey_test_YOUR_SECRET_KEY: \
-d "amount=15000" \
-d "currency=MYR" \
-d "source=src_test_5rt6s9vah5lkvi1rh9c" \
-d "return_uri=https://yourdomain.com/payment/callback"
ステップ3: 顧客をリダイレクト
app.post('/checkout/touch-n-go', async (req, res) => {
try {
const { amount, order_id } = req.body;
// 金額を検証
if (amount < 100 || amount > 3000000) {
return res.status(400).json({
error: 'Amount must be between RM1 and RM30,000'
});
}
// ソースを作成
const source = await omise.sources.create({
type: 'touch_n_go',
amount: amount,
currency: 'MYR'
});
// チャージを作成
const charge = await omise.charges.create({
amount: amount,
currency: 'MYR',
source: source.id,
return_uri: `${process.env.BASE_URL}/payment/callback`,
metadata: {
order_id: order_id
}
});
// Touch 'n Goにリダイレクト
res.redirect(charge.authorize_uri);
} catch (error) {
console.error('Touch n Go error:', error);
res.status(500).json({ error: error.message });
}
});
ステップ4: リターンを処理
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') {
await processOrder(charge.metadata.order_id);
res.redirect('/payment-success');
} else if (charge.status === 'failed') {
res.redirect('/payment-failed?reason=' + charge.failure_message);
} else {
res.redirect('/payment-pending');
}
} catch (error) {
res.redirect('/payment-error');
}
});
ステップ5: Webhookを処理
app.post('/webhooks/omise', (req, res) => {
const event = req.body;
if (event.key === 'charge.complete' && event.data.source.type === 'touch_n_go') {
const charge = event.data;
if (charge.status === 'successful') {
processOrder(charge.metadata.order_id);
} else if (charge.status === 'failed') {
handleFailedPayment(charge.metadata.order_id);
}
}
res.sendStatus(200);
});
完全な実装例
// Express.jsサーバー
const express = require('express');
const omise = require('omise')({
secretKey: process.env.OMISE_SECRET_KEY
});
const app = express();
app.use(express.json());
app.post('/checkout/touch-n-go', async (req, res) => {
try {
const { amount, order_id } = req.body;
// 金額を検証 (RM1 - RM30,000)
if (amount < 100 || amount > 3000000) {
return res.status(400).json({
error: 'Amount must be between RM1 and RM30,000'
});
}
// ソースを作成
const source = await omise.sources.create({
type: 'touch_n_go',
amount: amount,
currency: 'MYR'
});
// チャージを作成
const charge = await omise.charges.create({
amount: amount,
currency: 'MYR',
source: source.id,
return_uri: `${process.env.BASE_URL}/payment/callback`,
metadata: {
order_id: order_id,
payment_method: 'touch_n_go'
}
});
// 認証URLを返す
res.json({
authorize_uri: charge.authorize_uri,
charge_id: charge.id
});
} catch (error) {
console.error('TNG error:', error);
res.status(500).json({ error: error.message });
}
});
// コールバックハンドラ
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ハンドラ
app.post('/webhooks/omise', (req, res) => {
const event = req.body;
if (event.key === 'charge.complete') {
const charge = event.data;
if (charge.source.type === 'touch_n_go') {
if (charge.status === 'successful') {
updateOrderStatus(charge.metadata.order_id, 'paid');
sendConfirmation(charge.metadata.customer_email);
} else {
updateOrderStatus(charge.metadata.order_id, 'failed');
}
}
}
res.sendStatus(200);
});
app.listen(3000);
取消と返金のサポート
チャージの取消
Touch 'n Goは24時間以内の取消をサポートします:
// 即座に取消(全額)
const refund = await omise.charges.refund('chrg_test_...', {
amount: 15000
});
if (refund.voided) {
console.log('Charge was voided (within 24 hours)');
}
返金
30日以内に全額返金のみ:
// 全額返金のみ
const refund = await omise.charges.refund('chrg_test_...', {
amount: 15000 // 全額である必要があります
});
Touch 'n Go eWalletは一部返金をサポートしていません。30日以内に全額返金のみが許可されています。
一般的な問題とトラブルシューティング
問題: 顧客がTNG eWalletアプリを持っていない
原因: 顧客がTouch 'n Goを選択したがアプリを持っていない
解決策:
function checkMobile() {
if (!/Android|iPhone|iPad|iPod/i.test(navigator.userAgent)) {
alert('Touch n Go eWallet requires the mobile app. Please use a mobile device.');
return false;
}
return true;
}
問題: 残高不足
エラー: 支払い拒否
解決策:
if (charge.failure_code === 'insufficient_balance') {
showMessage('Insufficient TNG eWallet balance. Please reload your wallet.');
offerAlternativePayment();
}
問題: 支払いタイムアウト
解決策:
const TIMEOUT = 15 * 60 * 1000; // 15分
setTimeout(() => {
if (!paymentConfirmed) {
showTimeoutMessage();
allowRetry();
}
}, TIMEOUT);
問題: 異なる通貨エラー
原因: Touch 'n GoはMYRのみをサポート
解決策:
if (currency !== 'MYR') {
throw new Error('Touch n Go only supports MYR currency');
}
ベストプラクティス
1. 指示を 表示
<div class="tng-instructions">
<h3>Pay with Touch 'n Go eWallet</h3>
<ol>
<li>Ensure you have TNG eWallet app installed</li>
<li>Check your wallet has sufficient balance</li>
<li>You'll be redirected to the TNG eWallet app</li>
<li>Enter your 6-digit PIN or use biometric</li>
<li>Confirm the payment</li>
</ol>
<p>Need to reload? Use online banking, credit card, or at retailers.</p>
</div>
2. 金額を検証
function validateTNGAmount(amount) {
const MIN = 100; // RM1.00
const MAX = 3000000; // RM30,000.00
if (amount < MIN) {
return 'Minimum amount is RM1.00';
}
if (amount > MAX) {
return 'Maximum amount is RM30,000.00';
}
return null;
}
3. モバイル専用オプション
// モバイルでのみTNGを表示
if (/Android|iPhone|iPad|iPod/i.test(navigator.userAgent)) {
document.getElementById('tng-option').style.display = 'block';
} else {
document.getElementById('tng-option').style.display = 'none';
}
4. Webhookを使用
// Webhookが主要な通知
app.post('/webhooks/omise', handleWebhook);
// コールバックはバックアップ
app.get('/payment/callback', handleCallback);
FAQ
Touch 'n Go eWalletとは何ですか?
Touch 'n Go eWalletはマレーシアで最も人気のあるデジタルウォレットで、1700万人以上のユーザーを持っています。もともと高速道路の通行料金の支払いで知られていましたが、現在はマレーシア全土で広く受け入れられているフル機能のeウォレットです。
顧客はTNG eWalletアカウントが必要ですか?
はい、顧客はTouch 'n Go eWalletアプリがインストールされ、アクティブなアカウントと十分な残高が必要で す。
取引制限は何ですか?
- Liteユーザー: 取引あたりRM1〜RM1,500、日次RM1,500
- Fullユーザー: 取引あたりRM1〜RM30,000、日次RM30,000
顧客はアプリで確認レベルをアップグレードできます。
決済にはどのくらいかかりますか?
Touch 'n Go eWalletの決済は通常1〜3営業日以内に行われます。決済スケジュールについてはOmiseダッシュボードをご確認ください。
Touch 'n Go支払いを返金できますか?
はい、30日以内に全額返金がサポートされています。一部返金は利用できません。24時間以内の取消が可能です。
顧客の残高が不足している場合はどうなりますか?
支払いは拒否されます。顧客は次の方法でTNG eWalletをリロードできます:
- オンラインバンキング
- クレジット/デビットカード
- 7-Eleven、99 Speedmart、MyNews
- Petronasステーション
- ATM
Touch 'n Goはデスクトップで動作しますか?
いいえ、Touch 'n Go eWalletにはモバイルアプリが必要です。デスクトップユーザーには代替決済手段を表示する必要があります。
テスト
テストモード
Touch 'n Go eWalletはテストAPIキーを使用してテストできます。テストモードでは:
テスト認証情報:
- テストAPIキーを使用 (skey_test_xxx)
- 通貨: MYR(マレーシアリンギット)
- テストに実際のTouch 'n Goアカウントは不要
テストフロー:
- テストAPIキーでソースとチャージを作成
- 顧客がテスト
authorize_uriにリダイレクト - テストページがTouch 'n Go認証をシミュレート
- Omiseダッシュボードのアクションを使用してチャージを成功/失敗としてマーク
- Webhook通知とコールバックを確認
テスト実装:
// Touch 'n Go支払いをテスト
const source = await omise.sources.create({
type: 'touch_n_go',
amount: 5000, // RM50.00
currency: 'MYR'
});
const charge = await omise.charges.create({
amount: 5000,
currency: 'MYR',
source: source.id,
return_uri: 'https://example.com/callback'
});
console.log('Test authorize URL:', charge.authorize_uri);
テストシナリオ:
- 支払い成功: 注文履行フローを確認
- 支払い失敗: エラー処理をテスト
- 金額制限: RM1最小とさまざまな金額をテスト
- モバイルフロー: TNGアプリへのディープリンクをテスト
- 残高不足: 低ウォレット残高をシミュレート
- タイムアウト: 放棄された支払いシナリオをテスト
- Webhook配信: すべてのWebhookイベントを確認
重要な注意事項:
- テストモードは実際のTNGサーバーに接続しません
- ダッシュボードを使用して支払い結果をシミュレート
- モバイルアプリフローを徹底的にテスト
- すべてのステータスのWebhook処理を確認
- 金額検証ロジックをテスト
包括的なテストガイドラインについては、テストドキュメントを参照してください。
関連リソース
- デジタルウォレット概要 - すべてのウォレットオプション
- Boost - マレーシアの代替ウォレット
- GrabPay - 多国ウォレット
- DuitNow QR - マレーシアのQR決済
- 返金 - 返金ポリシー