GrabPay
シンガポールとマレーシアで利用可能な東南アジアの主要なデジタルウォレットGrabPayからの支払いを受け付けます。
支払いフロー

この画像は、電話番号入力、OTP検証、支払い方法の選択 、最終確認を含む完全なredirectチェックアウトプロセスを示しています。
概要
GrabPayは、東南アジア全域で配車サービス、フードデリバリー、金融サービスを提供するGrabスーパーアプリに統合された決済ウォレットです。顧客は即座に確認できるGrabPay残高を使用して支払うことができます。
主な機能:
- ✅ 大規模なリーチ - シンガポールとマレーシアのユーザー
- ✅ 高速確認 - ほぼリアルタイムの支払い検証(通常数秒以内)
- ✅ モバイルファースト - スマートフォンユーザーに最適化
- ✅ 信頼できるブランド - Grabエコシステムの一部
- ✅ チェックアウトの摩擦なし - Grabアプリでのワンタップ支払い
- ✅ 地域カバレッジ - シンガポールとマレーシアをサポート
サポートされている地域
| Region | Currency | Min Amount | Max Amount | API Version |
|---|---|---|---|---|
| Singapore | SGD | $1.00 | $5,000.00 | 2017-11-02 |
| Malaysia | MYR | RM1.00 | RM1,500.00 | 2017-11-02 |
国別の取引制限
シンガポール (SGD)
| 検証レベル | 取引ごと | 1日の上限 | 月間上限 |
|---|---|---|---|
| Basic(電話のみ) | $500 | $2,000 | $5,000 |
| Plus(ID検証済み) | $500 | $5,000 | $30,000 |
マレーシア (MYR)
| 検証レベル | 取引ごと | 1日の上限 | 月間上限 |
|---|---|---|---|
| Basic(電話のみ) | RM1,500 | RM1,500 | RM3,000 |
| Plus(ID検証済み) | RM1,500 | RM5,000 | RM10,000 |
仕組み
顧客体験:
- 顧客がチェックアウト時にGrabPayを選択
- GrabPay認証ページにredirect
- Grabアプリを開く(deep link)
- 支払い詳細を確認
- PIN/生体認証で確認
- 加盟店サイトに戻る
一 般的な完了時間: 1〜2分
実装
ステップ1: GrabPay Sourceの作成
- cURL
- Node.js
- PHP
- Python
- Ruby
- Go
- Java
- C#
curl https://api.omise.co/sources \
-u skey_test_YOUR_SECRET_KEY: \
-d "type=grabpay" \
-d "amount=10000" \
-d "currency=SGD"
const omise = require('omise')({
secretKey: 'skey_test_YOUR_SECRET_KEY'
});
const source = await omise.sources.create({
type: 'grabpay',
amount: 10000, // SGD 100.00
currency: 'SGD'
});
<?php
$source = OmiseSource::create(array(
'type' => 'grabpay',
'amount' => 10000,
'currency' => 'SGD'
));
?>
import omise
omise.api_secret = 'skey_test_YOUR_SECRET_KEY'
source = omise.Source.create(
type='grabpay',
amount=10000,
currency='SGD'
)
require 'omise'
Omise.api_key = 'skey_test_YOUR_SECRET_KEY'
source = Omise::Source.create({
type: 'grabpay',
amount: 10000,
currency: 'SGD'
})
source, err := client.Sources().Create(&operations.CreateSource{
Type: "grabpay",
Amount: 10000,
Currency: "SGD",
})
Source source = client.sources().create(new Source.CreateParams()
.type("grabpay")
.amount(10000L)
.currency("SGD"));
var source = await client.Sources.Create(new CreateSourceRequest
{
Type = "grabpay",
Amount = 10000,
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'];
if (!supportedCurrencies.includes(currency)) {
return res.status(400).json({
error: 'GrabPay supports SGD and MYR only'
});
}
// Validate amount by currency
const limits = {
SGD: { min: 100, max: 500000 },
MYR: { min: 100, max: 150000 }
};
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: 100, max: 500000 },
MYR: { min: 100, max: 150000 }
};
// Checkout page
app.post('/checkout/grabpay', async (req, res) => {
try {
const { amount, currency, order_id } = req.body;
// Validate currency
if (!['SGD', 'MYR'].includes(currency)) {
return res.status(400).json({
error: 'GrabPay only supports SGD and MYR'
});
}
// 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'
};
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);