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

GrabPay

シンガポール、マレーシア、タイ、インドネシア、フィリピン、ベトナム、ミャンマー、カンボジアの8か国で1億8700万人のユーザーを持つ東南アジアの主要なデジタルウォレットGrabPayからの支払いを受け付けます。

支払いフロー

GrabPay Payment Flow

この画像は、電話番号入力、OTP検証、支払い方法の選択、最終確認を含む完全なredirectチェックアウトプロセスを示しています。

概要

GrabPayは、東南アジア全域で配車サービス、フードデリバリー、金融サービスを提供するGrabスーパーアプリに統合された決済ウォレットです。顧客は即座に確認できるGrabPay残高を使用して支払うことができます。

主な機能:

  • 大規模なリーチ - 8か国で1億8700万人のユーザー
  • 高速確認 - ほぼリアルタイムの支払い検証(通常数秒以内)
  • モバイルファースト - スマートフォンユーザーに最適化
  • 信頼できるブランド - Grabエコシステムの一部
  • チェックアウトの摩擦なし - Grabアプリでのワンタップ支払い
  • 地域カバレッジ - 複数国サポート

サポートされている地域

地域通貨最小金額最大金額1日の上限
シンガポールSGD$0.50$500$5,000*
マレーシアMYRRM1.00RM1,500RM5,000*
タイTHB฿20.00฿50,000฿200,000*

*1日の上限は顧客のウォレット検証レベルによって異なります

国別の取引制限

シンガポール (SGD)

検証レベル取引ごと1日の上限月間上限
Basic(電話のみ)$500$2,000$5,000
Plus(ID検証済み)$500$5,000$30,000

マレーシア (MYR)

検証レベル取引ごと1日の上限月間上限
Basic(電話のみ)RM1,500RM1,500RM3,000
Plus(ID検証済み)RM1,500RM5,000RM10,000

タイ (THB)

検証レベル取引ごと1日の上限月間上限
Basic(電話のみ)฿50,000฿50,000฿200,000
Plus(ID検証済み)฿50,000฿200,000฿500,000

仕組み

顧客体験:

  1. 顧客がチェックアウト時にGrabPayを選択
  2. GrabPay認証ページにredirect
  3. Grabアプリを開く(deep link)
  4. 支払い詳細を確認
  5. PIN/生体認証で確認
  6. 加盟店サイトに戻る

一般的な完了時間: 1〜2分

実装

ステップ1: GrabPay Sourceの作成

curl https://api.omise.co/sources \
-u skey_test_YOUR_SECRET_KEY: \
-d "type=grabpay" \
-d "amount=10000" \
-d "currency=SGD"

レスポンス:

{
"object": "source",
"id": "src_test_5rt6s9vah5lkvi1rh9c",
"type": "grabpay",
"flow": "redirect",
"amount": 10000,
"currency": "SGD"
}

ステップ2: Chargeの作成

curl https://api.omise.co/charges \
-u skey_test_YOUR_SECRET_KEY: \
-d "amount=10000" \
-d "currency=SGD" \
-d "source=src_test_5rt6s9vah5lkvi1rh9c" \
-d "return_uri=https://yourdomain.com/payment/callback"

ステップ3: 顧客のredirect

app.post('/checkout/grabpay', async (req, res) => {
try {
const { amount, currency, order_id } = req.body;

// Validate currency
const supportedCurrencies = ['SGD', 'MYR', 'THB'];
if (!supportedCurrencies.includes(currency)) {
return res.status(400).json({
error: 'GrabPay supports SGD, MYR, and THB only'
});
}

// Validate amount by currency
const limits = {
SGD: { min: 50, max: 50000 },
MYR: { min: 100, max: 150000 },
THB: { min: 2000, max: 5000000 }
};

if (amount < limits[currency].min || amount > limits[currency].max) {
return res.status(400).json({
error: `Amount out of range for ${currency}`
});
}

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

// Create charge
const charge = await omise.charges.create({
amount: amount,
currency: currency,
source: source.id,
return_uri: `${process.env.BASE_URL}/payment/callback`,
metadata: {
order_id: order_id
}
});

// Redirect to GrabPay
res.redirect(charge.authorize_uri);

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

ステップ4: returnの処理

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') {
// 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');
}
} 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 === 'grabpay') {
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 server
const express = require('express');
const omise = require('omise')({
secretKey: process.env.OMISE_SECRET_KEY
});

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

// Amount limits by currency
const LIMITS = {
SGD: { min: 50, max: 50000 },
MYR: { min: 100, max: 150000 },
THB: { min: 2000, max: 5000000 }
};

// Checkout page
app.post('/checkout/grabpay', async (req, res) => {
try {
const { amount, currency, order_id } = req.body;

// Validate currency
if (!['SGD', 'MYR', 'THB'].includes(currency)) {
return res.status(400).json({
error: 'GrabPay only supports SGD, MYR, and THB'
});
}

// Validate amount
const { min, max } = LIMITS[currency];
if (amount < min || amount > max) {
return res.status(400).json({
error: `Amount must be between ${min} and ${max} ${currency}`
});
}

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

// Create charge
const charge = await omise.charges.create({
amount: amount,
currency: currency,
source: source.id,
return_uri: `${process.env.BASE_URL}/payment/callback`,
metadata: {
order_id: order_id,
payment_method: 'grabpay'
}
});

// Return authorization URL
res.json({
authorize_uri: charge.authorize_uri,
charge_id: charge.id
});

} catch (error) {
console.error('GrabPay 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 === 'grabpay') {
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);

voidと返金のサポート

chargeのvoid

GrabPayはcharge作成から24時間以内のvoidをサポート:

// Void immediately (full amount only)
const refund = await omise.charges.refund('chrg_test_...', {
amount: 10000 // Full amount
});

if (refund.voided) {
console.log('Charge was voided (within 24 hours)');
}

返金

30日以内全額および部分返金をサポート:

// Full refund
const fullRefund = await omise.charges.refund('chrg_test_...', {
amount: 10000
});

// Partial refund
const partialRefund = await omise.charges.refund('chrg_test_...', {
amount: 5000 // Half refund
});
返金サポート

GrabPayは元の取引から30日以内に全額および部分返金の両方をサポートします。

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

問題: 顧客がGrabアプリを持っていない

原因: 顧客がGrabPayを選択したがGrabアプリがインストールされていない

解決策:

  • 支払い前に明確な指示を表示
  • プラットフォーム(iOS/Android)を確認
  • アプリのダウンロードリンクを提供
function checkGrabApp() {
const isIOS = /iPhone|iPad|iPod/i.test(navigator.userAgent);
const isAndroid = /Android/i.test(navigator.userAgent);

if (!isIOS && !isAndroid) {
alert('GrabPay requires the Grab mobile app. Please use a mobile device.');
return false;
}

return true;
}

問題: 残高不足

エラー: 支払いが拒否されました

解決策:

  • 明確なエラーメッセージを表示
  • 顧客がGrabPayウォレットにチャージできるようにする
  • 代替決済方法を提供
if (charge.failure_code === 'insufficient_balance') {
showMessage('GrabPayの残高が不足しています。チャージするか、別の支払い方法を使用してください。');
}

問題: 通貨の不一致

エラー: サポートされていない通貨

解決策:

function validateGrabPayCurrency(currency, country) {
const currencyMap = {
'SG': 'SGD',
'MY': 'MYR',
'TH': 'THB'
};

const expectedCurrency = currencyMap[country];
if (currency !== expectedCurrency) {
throw new Error(`Use ${expectedCurrency} for ${country}`);
}
}

問題: 支払いタイムアウト

原因: 顧客が時間制限内に支払いを完了しませんでした

解決策:

  • 15分のタイムアウトを設定
  • 新しいchargeで再試行を許可
  • リマインダーを送信
const TIMEOUT = 15 * 60 * 1000; // 15 minutes

setTimeout(() => {
if (!paymentConfirmed) {
showTimeoutMessage();
allowRetry();
}
}, TIMEOUT);

ベストプラクティス

1. 明確な指示を表示

<div class="grabpay-instructions">
<h3>GrabPayで支払う</h3>
<ol>
<li>Grabアプリがインストールされていることを確認してください</li>
<li>GrabPayウォレットに十分な残高があることを確認してください</li>
<li>Grabアプリにredirectされます</li>
<li>認証して支払いを確認してください</li>
</ol>
<p>残高が足りませんか? <a href="https://grab.com/sg/pay/">今すぐチャージ</a></p>
</div>

2. モバイルdeep linkの処理

function openGrabApp(authorizeUri) {
if (/iPhone|iPad|iPod|Android/i.test(navigator.userAgent)) {
window.location = authorizeUri;

// Check if app opened
setTimeout(() => {
if (!document.hidden) {
showInstallAppMessage();
}
}, 2000);
} else {
alert('GrabPayはモバイルデバイスでのみ利用可能です');
}
}

3. 通貨別の金額検証

function validateAmount(amount, currency) {
const limits = {
SGD: { min: 50, max: 50000, label: '$' },
MYR: { min: 100, max: 150000, label: 'RM' },
THB: { min: 2000, max: 5000000, label: '฿' }
};

const { min, max, label } = limits[currency];

if (amount < min) {
return `最小金額は${label}${min / 100}です`;
}

if (amount > max) {
return `最大金額は${label}${max / 100}です`;
}

return null; // Valid
}

4. webhookを使用

// Webhook is primary notification
app.post('/webhooks/omise', handleWebhook);

// Callback is backup
app.get('/payment/callback', handleCallback);

5. 地域の違いを処理

const GRABPAY_CONFIG = {
SG: {
currency: 'SGD',
minAmount: 50,
maxAmount: 50000,
displayName: 'GrabPay (シンガポール)'
},
MY: {
currency: 'MYR',
minAmount: 100,
maxAmount: 150000,
displayName: 'GrabPay (マレーシア)'
},
TH: {
currency: 'THB',
minAmount: 2000,
maxAmount: 5000000,
displayName: 'GrabPay (タイ)'
}
};

FAQ

GrabPayをサポートしている国はどこですか?

GrabPayは、Omiseを通じてシンガポール、マレーシア、タイで利用できます。Grabは合計8か国で運営されていますが、支払い統合は現在これら3つの市場で利用可能です。

顧客はGrabアカウントが必要ですか?

はい、顧客はGrabアプリをインストールし、GrabPayウォレットをアクティブ化している必要があります。アプリは無料で、iOSとAndroidで利用できます。

取引制限はいくらですか?

制限は国によって異なります:

  • シンガポール: 取引ごと$0.50〜$500
  • マレーシア: 取引ごとRM1.00〜RM1,500
  • タイ: 取引ごと฿20〜฿50,000

1日および月間の上限は、顧客の検証レベルによって異なります。

決済にはどのくらいかかりますか?

GrabPayの決済は通常1〜3営業日以内に行われます。アカウントの具体的な決済スケジュールについては、Omiseダッシュボードを確認してください。

GrabPay支払いを返金できますか?

はい、GrabPayは元の取引から30日以内に全額および部分返金の両方をサポートします。voidは24時間以内に利用できます。

顧客の残高が不足している場合はどうなりますか?

支払いは拒否されます。顧客は以下の方法でGrabPayウォレットにチャージできます:

  • クレジット/デビットカード
  • 銀行振込
  • セブンイレブン、ファミリーマート(タイ)
  • その他の現金チャネル

明確なエラーメッセージを提供し、代替決済方法を提供してください。

GrabPayはデスクトップで動作しますか?

GrabPayにはGrabモバイルアプリが必要なため、モバイルのみです。デスクトップユーザーには代替決済方法を表示する必要があります。

テスト

テストモード

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

テスト認証情報:

  • テストAPIキー(skey_test_xxx)を使用
  • サポートされているすべての国をテスト: シンガポール、マレーシア、タイ
  • 異なる通貨をテスト: SGD、MYR、THB

テストフロー:

  1. テストAPIキーでsourceとchargeを作成
  2. 顧客をテストauthorize_uriにredirect
  3. テスト認証ページが表示されます
  4. ダッシュボードのActionsを使用して支払いの成功/失敗をシミュレート
  5. webhook配信とreturn_uri処理を検証

テスト実装:

// Test GrabPay for different countries
const testCountries = ['SG', 'MY', 'TH'];

for (const country of testCountries) {
const config = countryConfig[country];

const source = await omise.sources.create({
type: 'grabpay',
amount: config.minAmount,
currency: config.currency
});

const charge = await omise.charges.create({
amount: config.minAmount,
currency: config.currency,
source: source.id,
return_uri: 'https://example.com/callback'
});

console.log(`Test ${country}:`, charge.authorize_uri);
}

テストシナリオ:

  • 成功した支払い: 注文完了ワークフローを検証
  • 失敗した支払い: エラー処理と再試行メカニズムをテスト
  • 複数国: サポートされている各国を個別にテスト
  • 金額制限: 国固有の制限が適用されることを検証
  • 通貨検証: 各国の適切な通貨を確認
  • モバイルフロー: モバイルでのredirectとdeep linkingをテスト
  • タイムアウト: 放棄された支払い処理をテスト

重要な注意事項:

  • テストモードは実際のGrabサーバーに接続しません
  • Omiseダッシュボードを使用してテストchargeを成功/失敗としてマーク
  • 本番環境に移行する前に各国/通貨の組み合わせをテスト
  • すべてのステータス変更についてwebhookが受信されることを確認
  • モバイルをサポートする場合は、iOSとAndroidの両方でテスト

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

関連リソース

次のステップ

  1. GrabPay sourceの作成
  2. redirectフローの実装
  3. webhook処理の設定
  4. 支払いフローのテスト
  5. 本番環境へ