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

WeChat Pay

WeChat Payを通じて、中国および世界中のWeChatの13億人以上のメッセージングアプリユーザーからの支払いを受け付けます。WeChatエコシステムにシームレスに統合された、中国の2大モバイル決済プラットフォームの1つです。

概要

WeChat Pay(中国ではWeixin Pay)は、WeChatメッセージングアプリに直接統合されたTencentのモバイル決済ソリューションです。WeChatメッセージング(13億人以上のユーザー)と統合されており、WeChat Payは中国の2大決済プラットフォームの1つで、Alipayと並んで中国で最大の決済方法の1つです。WeChat Payは、QRコード、アプリ内購入、ミニプログラムを通じて即座の決済を可能にします。

主な機能:

  • WeChatと統合 - 中国および海外の中国人コミュニティ全体で13億人以上のメッセージングアプリユーザーにアクセス
  • WeChat統合 - メッセージングアプリエコシステム内でのシームレスな決済
  • QRコード決済 - 高速で非接触の決済体験
  • クロスボーダーサポート - 世界中の中国人観光客からの支払いを受け付け
  • マルチ通貨 - THB、SGD、MYR、JPY、USDで決済
  • ミニプログラム - WeChat内でeコマース体験を構築

サポートされている地域

地域通貨最小金額最大金額決済
タイTHB฿1.00฿100,000THB
シンガポールSGDS$0.10S$5,000SGD
マレーシアMYRRM1.00RM20,000MYR
日本JPY¥100¥1,000,000JPY
香港HKDHK$1.00HK$50,000HKD
クロスボーダー決済

WeChat Payは、中国人観光客やクロスボーダーeコマースを対象とするマーチャントにとって特に価値があります。顧客はCNYで支払い、現地通貨で決済を受け取ります。

仕組み

顧客体験:

  1. 顧客がチェックアウト時に「WeChat Pay」を選択
  2. QRコードが画面に表示される
  3. 顧客がWeChatアプリを開いてQRコードをスキャン
  4. WeChatで取引詳細を確認
  5. WeChat Payパスワードまたは生体認証で支払いを確認
  6. マーチャントウェブサイトに戻る
  7. 支払い確認を受け取る

通常の完了時間: 30-90秒

実装

ステップ1: WeChat Payソースの作成

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

レスポンス:

{
"object": "source",
"id": "src_test_5rt6s9vah5lkvi1rh9c",
"type": "wechat_pay",
"flow": "redirect",
"amount": 10000,
"currency": "THB",
"scannable_code": {
"type": "qr",
"image": {
"uri": "https://omise.co/qr/...",
"download_uri": "https://api.omise.co/..."
}
}
}

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

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

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

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

// 金額を検証
if (amount < 100 || amount > 10000000) {
return res.status(400).json({
error: '金額は฿1から฿100,000の間である必要があります'
});
}

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

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

// QRコードURLを返して表示
res.json({
qr_code_url: charge.source.scannable_code.image.uri,
charge_id: charge.id,
expires_at: new Date(Date.now() + 5 * 60 * 1000) // 5分
});

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

ステップ4: QRコードUI表示

<!DOCTYPE html>
<html>
<head>
<title>WeChat Pay チェックアウト</title>
<style>
.wechat-pay-container {
max-width: 400px;
margin: 50px auto;
text-align: center;
padding: 30px;
border: 1px solid #e0e0e0;
border-radius: 10px;
}
.qr-code {
width: 300px;
height: 300px;
margin: 20px auto;
border: 1px solid #ddd;
padding: 10px;
background: white;
}
.wechat-logo {
width: 60px;
height: 60px;
margin-bottom: 15px;
}
.countdown {
font-size: 18px;
color: #666;
margin-top: 15px;
}
.instructions {
color: #666;
margin: 20px 0;
line-height: 1.6;
}
</style>
</head>
<body>
<div class="wechat-pay-container">
<img src="/images/wechat-logo.svg" class="wechat-logo" alt="WeChat Pay">
<h2>WeChatでスキャンして支払う</h2>

<div class="qr-code">
<img id="qr-image" src="" alt="WeChat Pay QR Code" style="width: 100%; height: 100%;">
</div>

<div class="instructions">
<ol style="text-align: left;">
<li>スマートフォンでWeChatアプリを開く</li>
<li>「+」アイコンをタップして「QRコードをスキャン」を選択</li>
<li>上記のQRコードをスキャン</li>
<li>WeChatで支払いを確認</li>
</ol>
</div>

<div class="countdown">
残り時間: <span id="timer">5:00</span>
</div>

<p style="color: #999; font-size: 14px; margin-top: 20px;">
支払い金額: <strong id="amount"></strong>
</p>
</div>

<script>
// カウントダウンタイマー
let timeLeft = 300; // 5分
const timerElement = document.getElementById('timer');

const countdown = setInterval(() => {
timeLeft--;
const minutes = Math.floor(timeLeft / 60);
const seconds = timeLeft % 60;
timerElement.textContent = `${minutes}:${seconds.toString().padStart(2, '0')}`;

if (timeLeft <= 0) {
clearInterval(countdown);
alert('QRコードの有効期限が切れました。もう一度お試しください。');
window.location.href = '/checkout';
}
}, 1000);

// 支払いステータスをポーリング
const chargeId = new URLSearchParams(window.location.search).get('charge_id');

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

if (data.status === 'successful') {
clearInterval(checkStatus);
clearInterval(countdown);
window.location.href = '/payment-success';
} else if (data.status === 'failed') {
clearInterval(checkStatus);
clearInterval(countdown);
window.location.href = '/payment-failed';
}
} catch (error) {
console.error('Status check error:', error);
}
}, 3000); // 3秒ごとにチェック

// QRコードを読み込む
fetch('/api/create-wechat-payment', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
amount: 10000,
order_id: 'ORD-12345'
})
})
.then(res => res.json())
.then(data => {
document.getElementById('qr-image').src = data.qr_code_url;
document.getElementById('amount').textContent = `฿${(data.amount / 100).toFixed(2)}`;
});
</script>
</body>
</html>

ステップ5: Webhookの処理

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

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

if (charge.status === 'successful') {
processOrder(charge.metadata.order_id);
sendConfirmationEmail(charge.metadata.customer_email);
} 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());

// WeChat Pay支払いを作成
app.post('/api/create-wechat-payment', async (req, res) => {
try {
const { amount, order_id, customer_email } = req.body;

// 金額を検証(฿1 - ฿100,000)
if (amount < 100 || amount > 10000000) {
return res.status(400).json({
error: '金額は฿1から฿100,000の間である必要があります'
});
}

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

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

// QRコードを返す
res.json({
charge_id: charge.id,
qr_code_url: charge.source.scannable_code.image.uri,
amount: charge.amount,
expires_at: new Date(Date.now() + 5 * 60 * 1000)
});

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

// 支払いステータスを確認(ポーリング用)
app.get('/api/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
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});

// Webhookハンドラー(主要な通知方法)
app.post('/webhooks/omise', (req, res) => {
const event = req.body;

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

if (charge.source.type === 'wechat_pay') {
if (charge.status === 'successful') {
updateOrderStatus(charge.metadata.order_id, 'paid');
sendConfirmationEmail(charge.metadata.customer_email);

// 成功した支払いをログ
console.log(`WeChat Pay payment successful: ${charge.id}`);
} else {
updateOrderStatus(charge.metadata.order_id, 'failed');

// 失敗した支払いをログ
console.log(`WeChat Pay payment failed: ${charge.id}, reason: ${charge.failure_message}`);
}
}
}

res.sendStatus(200);
});

// ヘルパー関数
async function updateOrderStatus(orderId, status) {
// データベースで注文を更新
await db.orders.update({ id: orderId }, { status: status });
}

async function sendConfirmationEmail(email) {
// 確認メールを送信
// 実装はメールサービスに依存
}

app.listen(3000, () => {
console.log('Server running on port 3000');
});

返金サポート

WeChat Payは90日以内全額および一部返金をサポートしています:

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

// 一部返金
const partialRefund = await omise.charges.refund('chrg_test_...', {
amount: 5000 // 半額返金
});
返金タイムライン
  • 返金は1〜3営業日以内に処理されます
  • 顧客はWeChatウォレットで返金を受け取ります
  • 元の取引から90日以内にサポート
返金ポリシー

返金期間とポリシーは変更される場合があります。Omise APIドキュメントまたはマーチャントダッシュボードで常に現在の返金機能を確認してください。

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

問題: QRコードがスキャンできない

原因: 顧客が非互換のQRスキャナーを使用しているか、コードの有効期限が切れている

解決策:

// QRコードが新鮮で有効であることを確認
function displayQRCode(qrCodeUrl, expiresAt) {
const qrImage = document.getElementById('qr-code');
qrImage.src = qrCodeUrl;

// 有効期限の警告を表示
const timeUntilExpiry = new Date(expiresAt) - Date.now();
if (timeUntilExpiry < 60000) { // 1分未満
showWarning('QRコードがまもなく期限切れになります!今すぐスキャンしてください。');
}
}

// 更新オプションを提供
function refreshQRCode() {
// 新しいQRコードを生成
createNewPayment();
}

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

原因: 顧客が5分以内に支払いを完了しなかった

解決策:

// 適切なタイムアウトを設定
const QR_EXPIRY_TIME = 5 * 60 * 1000; // 5分

setTimeout(() => {
if (!paymentConfirmed) {
showMessage('支払いセッションが期限切れになりました。新しい支払いを作成してください。');
enableRetry();
}
}, QR_EXPIRY_TIME);

問題: モバイルでWeChatアプリが開かない

原因: モバイルブラウザでのディープリンク問題

解決策:

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

if (isMobileDevice()) {
// WeChatアプリを直接開こうとする
window.location = `weixin://dl/business/?t=${encodeURIComponent(qrCodeData)}`;

// QRコード表示へのフォールバック
setTimeout(() => {
if (document.hidden === false) {
displayQRCodeFallback();
}
}, 2000);
} else {
// デスクトップ: QRコードを表示
displayQRCode();
}

問題: 通貨換算の混乱

原因: 顧客がCNYを見る、マーチャントがTHBを見る

解決策:

// 両方の通貨を明確に表示
function displayPaymentAmount(amountTHB, exchangeRate) {
const amountCNY = amountTHB * exchangeRate;

return `
<div class="payment-amount">
<p>支払額: ¥${amountCNY.toFixed(2)} CNY</p>
<p class="small">マーチャント受取額: ฿${(amountTHB / 100).toFixed(2)} THB</p>
<p class="small">為替レート: 1 THB = ${exchangeRate.toFixed(4)} CNY</p>
</div>
`;
}

問題: 支払いが保留中のまま

原因: ネットワーク問題またはWeChatからの遅延通知

解決策:

// 堅牢なステータスチェックを実装
async function checkPaymentStatus(chargeId, maxAttempts = 10) {
let attempts = 0;

const checkInterval = setInterval(async () => {
attempts++;

try {
const charge = await omise.charges.retrieve(chargeId);

if (charge.status === 'successful') {
clearInterval(checkInterval);
handleSuccess(charge);
} else if (charge.status === 'failed') {
clearInterval(checkInterval);
handleFailure(charge);
} else if (attempts >= maxAttempts) {
clearInterval(checkInterval);
handleTimeout(charge);
}
} catch (error) {
console.error('Status check error:', error);
}
}, 3000); // 3秒ごとにチェック
}

ベストプラクティス

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

// 高品質のQRコード表示を生成
function displayQRCode(qrUrl) {
return `
<div class="qr-container">
<img src="${qrUrl}"
alt="WeChat Pay QR Code"
style="width: 300px; height: 300px; padding: 20px; background: white; border: 2px solid #09BB07;">
<p style="color: #09BB07; margin-top: 15px;">
<strong>WeChatでスキャンして支払う</strong>
</p>
</div>
`;
}

2. カウントダウンタイマーを実装

function startCountdown(duration, onExpire) {
let timeLeft = duration;
const display = document.getElementById('timer');

const timer = setInterval(() => {
const minutes = Math.floor(timeLeft / 60);
const seconds = timeLeft % 60;

display.textContent = `${minutes}:${seconds.toString().padStart(2, '0')}`;

// 1分で警告
if (timeLeft === 60) {
display.style.color = 'red';
showWarning('残り1分のみ!');
}

if (--timeLeft < 0) {
clearInterval(timer);
onExpire();
}
}, 1000);

return timer;
}

3. 信頼性のためWebhooksを使用

// Webhookはポーリングよりも信頼性が高い
app.post('/webhooks/omise', handleWebhook);

// ポーリングはUI更新のバックアップ
const statusCheckInterval = setInterval(checkStatus, 3000);

4. 金額制限を検証

function validateWeChatPayAmount(amount, currency) {
const limits = {
'THB': { min: 100, max: 10000000 },
'SGD': { min: 10, max: 500000 },
'MYR': { min: 100, max: 2000000 },
'JPY': { min: 100, max: 100000000 }
};

const limit = limits[currency];
if (!limit) {
return '通貨がサポートされていません';
}

if (amount < limit.min) {
return `最小金額は${formatCurrency(limit.min, currency)}です`;
}

if (amount > limit.max) {
return `最大金額は${formatCurrency(limit.max, currency)}です`;
}

return null; // 有効
}

5. クロスボーダーシナリオの処理

// 通貨換算に関する明確な情報を提供
function displayCrossBorderInfo(amountLocal, currency) {
return `
<div class="cross-border-info">
<h4>クロスボーダー決済</h4>
<ul>
<li>中国元(CNY)で請求されます</li>
<li>銀行/WeChatが通貨換算を処理します</li>
<li>マーチャント受取額: ${formatCurrency(amountLocal, currency)}</li>
<li>最終金額は支払い時の為替レートによります</li>
</ul>
</div>
`;
}

6. モバイル最適化

// モバイルを検出してより良いUXを提供
if (isMobileDevice()) {
// モバイルでより大きなQRコードを表示
qrCodeElement.style.width = '90vw';
qrCodeElement.style.maxWidth = '400px';

// モバイル用の指示を追加
showInstructions('スクリーンショットを撮るかタップしてQRコードを保存し、WeChatを開いてスキャンします');
}

FAQ

WeChat Payとは何ですか?

WeChat Payは、WeChatメッセージングアプリに統合されたTencentのモバイル決済ソリューションです。13億人以上のユーザーを持ち、Alipayと並んで中国の2大決済方法の1つです。顧客はQRコードまたはアプリ内決済を使用して即座に支払うことができます。

WeChat Payを受け入れるために中国の銀行口座が必要ですか?

いいえ、中国の銀行口座は必要ありません。Omiseがクロスボーダー決済の複雑さを処理します。顧客はWeChatウォレットからCNYで支払い、現地通貨(THB、SGD、MYR、JPYなど)で決済を受け取ります。

取引制限は何ですか?

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

  • タイ: ฿1 - ฿100,000
  • シンガポール: S$0.10 - S$5,000
  • マレーシア: RM1 - RM20,000
  • 日本: ¥100 - ¥1,000,000

個々の顧客制限は、WeChat Pay確認レベルと銀行設定によって異なる場合があります。

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

WeChat Pay決済は通常、取引後2〜3営業日以内に行われます。正確なタイミングは、マーチャント契約と決済スケジュールによって異なります。特定の日付については、Omiseダッシュボードを確認してください。

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

はい、WeChat Payは元の取引から90日以内の全額および一部返金をサポートしています。返金は1〜3営業日以内に処理され、顧客のWeChatウォレットに返されます。

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

WeChat Pay QRコードは通常5分後に期限切れになります。期限切れ後、顧客用に新しいQRコードを生成する必要があります。残り時間を顧客に通知するために、常にカウントダウンタイマーを表示してください。

顧客は中国国外から支払うことができますか?

はい、WeChat Payはクロスボーダー決済をサポートしています。中国人観光客や海外の中国人ユーザーは、中国ベースのWeChat Payアカウントを使用して支払うことができます。ただし、中国以外のWeChatアカウントを持つユーザーは、地域と確認状況に応じて決済機能が制限される場合があります。

WeChat PayとAlipayの違いは何ですか?

どちらも同様の機能を持つ主要な中国のデジタルウォレットです:

  • WeChat Pay: 13億人のユーザー、WeChatメッセージングと統合、ソーシャル決済に強い
  • Alipay: 10億人以上のユーザー、Alibabaから、eコマースに強い

中国市場で最大のリーチを得るには、両方の決済方法を実装してください。

中国語を表示する必要がありますか?

必須ではありませんが、中国語で決済指示を表示すると、中国人顧客のコンバージョン率が向上します。WeChat Payインターフェース自体は常に顧客の優先言語(通常は中国語)です。

テスト

テストモード

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

テスト認証情報:

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

テストフロー:

  1. テストAPIキーでソースとチャージを作成
  2. QRコードがテストモードで生成される
  3. テストQRコードが表示されるが、実際のWeChatには接続されない
  4. Omiseダッシュボードアクションを使用して、チャージを成功/失敗としてマーク
  5. Webhook処理を検証

テスト実装:

// 異なる通貨のWeChat Payをテスト
const testConfigs = [
{ currency: 'THB', amount: 10000 },
{ currency: 'SGD', amount: 1000 },
{ currency: 'MYR', amount: 1000 },
{ currency: 'JPY', amount: 10000 }
];

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

const charge = await omise.charges.create({
amount: config.amount,
currency: config.currency,
source: source.id
});

console.log(`Test ${config.currency} QR:`, charge.source.scannable_code.image.download_uri);
}

テストシナリオ:

  • 支払い成功: 注文完了ワークフローを検証
  • 支払い失敗: エラー処理をテスト
  • QRコード表示: デスクトップ/モバイルでQRレンダリングをテスト
  • 複数の通貨: サポートされているすべての通貨をテスト
  • 金額制限: 通貨ごとの最小/最大を検証
  • QR期限切れ: タイムアウト処理をテスト
  • モバイル vs デスクトップ: 両方のフローをテスト
  • Webhook配信: すべてのWebhook通知を検証

重要な注意事項:

  • テストモードQRコードは実際のWeChatアプリでスキャンできません
  • ダッシュボードを使用して支払い完了をシミュレート
  • 本番開始前にサポートされているすべての通貨をテスト
  • すべてのステータスのWebhook処理を検証
  • モバイルQRスキャンとデスクトップQR表示の両方をテスト
  • 中国人顧客をターゲットにする場合は中国語サポートをテスト

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

関連リソース

次のステップ

  1. WeChat Payソースを作成
  2. QRコード表示を実装
  3. Webhook処理を設定
  4. 支払いフローをテスト
  5. 本番開始