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

ShopeePay

ShopeePayから支払いを受け付けます。東南アジア最大の電子商取引プラットフォームに統合されたデジタルウォレットで、1億5,000万人以上のアクティブユーザーがいます。

決済フロー

ShopeePay決済フロー

この画像は顧客の旅を示しています: ウォレットの選択、QR codeのスキャン、概要の確認、決済の確認。

概要

ShopeePayは、東南アジア最大の電子商取引プラットフォームの1つであるShopeeアプリエコシステム内の決済ウォレットです。ユーザーはShopeeプラットフォーム内外で即座に安全な取引のためにShopeePayの残高で支払うことができます。

主な機能:

  • 大規模なリーチ - 1億5,000万人以上のアクティブなShopeeユーザー
  • 即時確認 - リアルタイム決済処理
  • モバイルファースト - シームレスなモバイル決済体験
  • 信頼されたプラットフォーム - Sea Group(NYSE: SE)の一部
  • 地域カバレッジ - 複数国での利用可能性
  • キャッシュバック報酬 - ユーザーはShopeeコインを獲得

サポート地域

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

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

取引制限

タイ(THB)

認証レベル取引あたり1日の上限月間上限
ベーシック(電話のみ)฿50,000฿50,000฿200,000
プラス(ID認証済み)฿50,000฿200,000฿500,000

マレーシア(MYR)

認証レベル取引あたり1日の上限月間上限
ベーシック(電話のみ)RM1,500RM1,500RM3,000
プラス(ID認証済み)RM1,500RM5,000RM10,000

仕組み

顧客体験:

  1. 顧客がチェックアウトでShopeePayを選択
  2. ShopeePay認証ページにリダイレクト
  3. Shopeeアプリを開く(deep link)
  4. 決済詳細を確認
  5. PINまたは生体認証で認証
  6. 決済を確認
  7. 加盟店サイトに戻る

通常の完了時間: 1〜2分

実装

ステップ1: ShopeePayソースを作成

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

レスポンス:

{
"object": "source",
"id": "src_test_5rt6s9vah5lkvi1rh9c",
"type": "shopeepay",
"flow": "redirect",
"amount": 25000,
"currency": "THB"
}

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

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

ステップ3: 顧客をリダイレクト

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

// 通貨を検証
if (!['THB', 'MYR'].includes(currency)) {
return res.status(400).json({
error: 'ShopeePay supports THB and MYR only'
});
}

// 通貨別に金額を検証
const limits = {
THB: { min: 2000, max: 5000000 },
MYR: { min: 100, max: 150000 }
};

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

// ソースを作成
const source = await omise.sources.create({
type: 'shopeepay',
amount: amount,
currency: currency
});

// チャージを作成
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
}
});

// ShopeePayにリダイレクト
res.redirect(charge.authorize_uri);

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

// 通貨別の金額制限
const LIMITS = {
THB: { min: 2000, max: 5000000 },
MYR: { min: 100, max: 150000 }
};

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

// 通貨を検証
if (!['THB', 'MYR'].includes(currency)) {
return res.status(400).json({
error: 'ShopeePay only supports THB and MYR'
});
}

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

// ソースを作成
const source = await omise.sources.create({
type: 'shopeepay',
amount: amount,
currency: currency
});

// チャージを作成
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: 'shopeepay'
}
});

// 承認URLを返す
res.json({
authorize_uri: charge.authorize_uri,
charge_id: charge.id
});

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

無効化と返金サポート

チャージの無効化

ShopeePayは24時間以内の無効化をサポートしています:

// すぐに無効化(全額)
const refund = await omise.charges.refund('chrg_test_...', {
amount: 25000
});

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

返金

30日以内の全額返金のみ:

// 全額返金のみ
const refund = await omise.charges.refund('chrg_test_...', {
amount: 25000 // 全額である必要があります
});
部分返金なし

ShopeePayは部分返金をサポートしていません。30日以内の全額返金のみが許可されています。

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

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

原因: 顧客がShopeePayを選択したが、Shopeeアプリを持っていない

解決策:

function checkShopeeApp() {
if (!/Android|iPhone|iPad|iPod/i.test(navigator.userAgent)) {
alert('ShopeePayにはShopeeモバイルアプリが必要です。モバイルデバイスを使用してください。');
return false;
}
return true;
}

問題: 残高不足

エラー: 決済が拒否されました

解決策:

if (charge.failure_code === 'insufficient_balance') {
showMessage('ShopeePay残高が不足しています。Shopeeアプリでトップアップしてください。');
offerAlternativePayment();
}

問題: 決済タイムアウト

解決策:

const TIMEOUT = 15 * 60 * 1000; // 15分

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

ベストプラクティス

1. 指示を表示

<div class="shopeepay-instructions">
<h3>ShopeePayで支払う</h3>
<ol>
<li>Shopeeアプリがインストールされていることを確認</li>
<li>ShopeePayの残高が十分であることを確認</li>
<li>Shopeeアプリにリダイレクトされます</li>
<li>PINまたは生体認証で決済を確認</li>
</ol>
<p>必要に応じてShopeeアプリでShopeePay残高をトップアップしてください。</p>
</div>

2. 金額を検証

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

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

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

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

return null;
}

3. モバイルファーストデザイン

function isMobileDevice() {
return /Android|iPhone|iPad|iPod/i.test(navigator.userAgent);
}

if (!isMobileDevice()) {
// デスクトップではShopeePayオプションを非表示
document.getElementById('shopeepay-option').style.display = 'none';
}

FAQ

どの国がShopeePayをサポートしていますか?

Omiseを通じたShopeePay決済は現在、タイとマレーシアで利用可能です。

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

はい、顧客にはアクティブ化されたShopeePayウォレットを持つShopeeアプリがインストールされている必要があります。

取引制限は何ですか?
  • タイ: 取引あたり฿20 - ฿50,000
  • マレーシア: 取引あたりRM1 - RM1,500

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

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

ShopeePay決済は通常1〜3営業日以内に行われます。

ShopeePay決済を返金できますか?

はい、30日以内の全額返金がサポートされています。部分返金は利用できません。24時間以内に無効化が可能です。

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

決済は拒否されます。顧客はクレジット/デビットカード、銀行振込、またはセブン-イレブン店舗(タイ)でShopeePay残高をトップアップできます。

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

ShopeePayにはShopeeモバイルアプリが必要なため、モバイル専用です。デスクトップユーザーには代替決済方法が表示されるべきです。

テスト

テストモード

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

テスト認証情報:

  • テスト API キーを使用(skey_test_xxx)
  • サポートされているすべての通貨をテスト: THB、MYR
  • テストに実際のShopeePayアカウントは不要

テストフロー:

  1. テスト API キーでソースとチャージを作成
  2. 顧客をテストauthorize_uriにリダイレクト
  3. テスト認証ページがShopeePayフローをシミュレート
  4. Omiseダッシュボードアクションを使用してチャージを成功/失敗としてマーク
  5. webhook通知とreturn_uriコールバックを検証

テスト実装:

// 両国のShopeePayをテスト
const testConfigs = [
{ country: 'TH', currency: 'THB', amount: 2000 },
{ country: 'MY', currency: 'MYR', amount: 100 }
];

for (const config of testConfigs) {
const source = await omise.sources.create({
type: 'shopeepay',
amount: config.amount,
currency: config.currency
});

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

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

テストシナリオ:

  • 決済成功: リダイレクトフローと注文処理を完了
  • 決済失敗: エラー処理とユーザーメッセージングをテスト
  • 両国: タイ(THB)とマレーシア(MYR)を個別にテスト
  • 金額検証: 通貨ごとの最小/最大上限を検証
  • モバイルフロー: Shopeeアプリへのdeep linkをテスト
  • タイムアウト処理: 放棄された決済シナリオをテスト
  • Webhook配信: すべてのwebhookイベントが受信されることを検証

重要な注意事項:

  • テストモードは実際のShopeeサーバーに接続しません
  • ダッシュボードを使用して決済完了をシミュレート
  • 本番稼働前にTHBとMYR通貨の両方をテスト
  • すべてのチャージステータスのwebhookを検証
  • モバイル固有のフロー(アプリ切り替え、deep link)をテスト

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

関連リソース

次のステップ

  1. ShopeePayソースを作成
  2. リダイレクトフローを実装
  3. webhookを設定
  4. 統合をテスト
  5. 本番稼働