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

WeChat Pay MPM

加盟店がPOS端末で表示するQRコードを顧客がスキャンするMerchant-Presented Mode(MPM)を使用して、WeChat Payによるオフライン店舗決済を受け付けます。

その他のWeChat Payオプション

オンライン決済についてはWeChat Payをご覧ください。顧客が提示するバーコードのスキャンについてはWeChat Pay UPMをご覧ください。

概要

WeChat Pay Merchant-Presented Mode(MPM)は、「C scan B」(顧客が加盟店をスキャン)とも呼ばれ、加盟店が各取引に対して固有のQRコードを生成・表示するオフライン決済方法です。顧客はWeChatアプリを使用してこのQRコードをスキャンし、決済を完了します。

このフローは、店舗小売環境、レストラン、加盟店がチェックアウトプロセスを管理し、端末、タブレット、または印刷されたレシートに決済用QRコードを表示するあらゆる実店舗のPOSに最適です。

主な特徴:

  • 店舗決済 - 小売店、レストラン、実店舗に最適
  • 顧客アプリのインストール不要 - 既存のWeChatアプリで動作(13億人以上のユーザー)
  • 迅速なチェックアウト - 顧客は数秒でスキャンして支払い
  • ハードウェア不要 - 任意の画面でQRを表示または印刷
  • クロスボーダー - 中国人観光客からの決済を受け付け
  • オフライン対応 - 顧客のインターネット接続が限られた環境でも動作

対応地域

地域通貨最小金額最大金額APIバージョン
タイTHB20.00(2,000サタン)150,000.00(15,000,000サタン)2017-11-02

WeChat Pay MPMを有効にするには、support@omise.coにメールでこの機能をリクエストしてください。

クロスボーダー決済

WeChat Pay MPMは、中国人観光客をターゲットとする加盟店にとって特に価値があります。顧客はWeChatウォレットからCNYで支払い、加盟店はTHBで決済を受け取ります。

仕組み

決済フロー:

  1. 加盟店がOmise APIを通じてソースタイプwechat_pay_mpmで課金を作成
  2. QRコードがPOS端末、タブレット、または印刷物に表示される
  3. 顧客がWeChatアプリを開き、「スキャン」に移動
  4. 顧客が加盟店が表示するQRコードをスキャン
  5. 顧客が取引詳細を確認し、支払いを承認
  6. 加盟店が支払い確認のWebhook通知を受信
  7. 加盟店が顧客にレシートまたは確認を提供

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

実装

ステップ1:ソース付き課金の作成

WeChat Pay MPMでは、ソースと課金を単一のAPI呼び出しで作成するか、個別に作成できます。

# Create source
curl https://api.omise.co/sources \
-u $OMISE_SECRET_KEY: \
-d "type=wechat_pay_mpm" \
-d "amount=150000" \
-d "currency=THB"

# Create charge with source
curl https://api.omise.co/charges \
-u $OMISE_SECRET_KEY: \
-d "amount=150000" \
-d "currency=THB" \
-d "source=src_test_xxx"

レスポンス:

{
"object": "charge",
"id": "chrg_test_5rt6s9vah5lkvi1rh9c",
"amount": 150000,
"currency": "THB",
"status": "pending",
"source": {
"object": "source",
"id": "src_test_5rt6s9vah5lkvi1rh9c",
"type": "wechat_pay_mpm",
"flow": "offline",
"amount": 150000,
"currency": "THB",
"scannable_code": {
"type": "qr",
"image": {
"download_uri": "https://api.omise.co/charges/chrg_test_.../documents/qr_code.png"
}
}
},
"expires_at": "2024-01-15T14:30:00Z"
}

ステップ2:POSにQRコードを表示

charge.source.scannable_code.image.download_uriからQRコードをPOS端末に表示します。

// POS Terminal Display Logic
async function displayPaymentQR(orderId, amount) {
try {
// Create charge
const response = await fetch('/api/create-wechat-mpm-charge', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
order_id: orderId,
amount: amount
})
});

const data = await response.json();

// Display QR code on POS terminal
const qrDisplay = document.getElementById('pos-qr-display');
qrDisplay.innerHTML = `
<div class="payment-screen">
<h2>Scan to Pay with WeChat</h2>
<img src="${data.qr_code_url}" alt="WeChat Pay QR" />
<p class="amount">Amount: ฿${(amount / 100).toFixed(2)}</p>
<p class="timer">Expires in: <span id="countdown">2:00:00</span></p>
</div>
`;

// Start expiration countdown
startCountdown(data.expires_at, 'countdown');

// Start polling for payment status
pollPaymentStatus(data.charge_id);

} catch (error) {
showError('Failed to create payment. Please try again.');
}
}

ステップ3:Webhook通知の処理

const express = require('express');
const app = express();

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

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

if (charge.source.type === 'wechat_pay_mpm') {
if (charge.status === 'successful') {
// Payment successful
notifyPOSTerminal(charge.metadata.terminal_id, {
status: 'success',
charge_id: charge.id,
amount: charge.amount
});

// Print receipt
printReceipt(charge);

// Update order status
updateOrderStatus(charge.metadata.order_id, 'paid');

} else if (charge.status === 'failed') {
// Payment failed
notifyPOSTerminal(charge.metadata.terminal_id, {
status: 'failed',
failure_code: charge.failure_code,
failure_message: charge.failure_message
});
}
}
}

res.sendStatus(200);
});

QRコード表示

表示要件

スキャン成功率を最適化するため、以下のガイドラインに従ってください:

要件推奨事項
最小サイズ3cm x 3cm(1.2" x 1.2")
推奨サイズ5cm x 5cm(2" x 2")以上
解像度印刷用300 DPI
コントラスト白背景に暗いQR
余白最小4モジュールの白い境界線

POS端末の例

<style>
.wechat-mpm-display {
background: #fff;
padding: 30px;
text-align: center;
border-radius: 10px;
max-width: 400px;
margin: 0 auto;
}
.wechat-mpm-display .qr-container {
background: #fff;
padding: 20px;
border: 3px solid #09BB07;
border-radius: 8px;
display: inline-block;
margin: 20px 0;
}
.wechat-mpm-display .qr-code {
width: 200px;
height: 200px;
}
.wechat-mpm-display .amount {
font-size: 28px;
font-weight: bold;
color: #333;
}
.wechat-mpm-display .timer {
color: #666;
font-size: 16px;
}
.wechat-mpm-display .timer.warning {
color: #ff6600;
}
.wechat-mpm-display .timer.expired {
color: #ff0000;
}
.wechat-logo {
width: 50px;
height: 50px;
}
</style>

<div class="wechat-mpm-display">
<img src="/images/wechat-pay-logo.svg" class="wechat-logo" alt="WeChat Pay" />
<h2>Scan to Pay</h2>

<div class="qr-container">
<img id="qr-code" class="qr-code" src="" alt="WeChat Pay QR Code" />
</div>

<p class="amount">฿<span id="display-amount">1,500.00</span></p>
<p class="timer">Expires in: <span id="countdown">2:00:00</span></p>

<div class="instructions">
<p>1. Open WeChat</p>
<p>2. Tap "Discover" then "Scan"</p>
<p>3. Scan this QR code</p>
</div>
</div>

課金有効期限の設定

デフォルトでは、WeChat Pay MPM課金は2時間後に期限切れになります。ソース作成時に有効期限をカスタマイズできます。

カスタム有効期限

curl https://api.omise.co/sources \
-u $OMISE_SECRET_KEY: \
-d "type=wechat_pay_mpm" \
-d "amount=150000" \
-d "currency=THB" \
-d "expires_at=2024-01-15T15:00:00Z"

有効期限の制限

設定
最小1分
最大2時間
デフォルト2時間

カスタム有効期限の実装

const source = await omise.sources.create({
type: 'wechat_pay_mpm',
amount: 150000,
currency: 'THB',
// Expire in 30 minutes
expires_at: new Date(Date.now() + 30 * 60 * 1000).toISOString()
});
有効期限のベストプラクティス

混雑する小売環境では、顧客が期限切れのQRコードをスキャンしようとするのを防ぐため、より短い有効期限(5〜15分)を検討してください。常に残り時間を表示するカウントダウンタイマーを表示してください。

課金ステータス値

ステータス説明
pendingQRコードが表示され、顧客のスキャンと支払いを待機中
successful支払いが正常に完了
failed支払いが失敗または拒否
expired支払い完了前にQRコードが期限切れ
reversed支払いが取り消された(無効化)

失敗コード

コード説明推奨アクション
payment_expired顧客が支払いを完了する前にQRコードが期限切れ新しいQRコードを生成
payment_rejectedWeChat/発行銀行により支払いが拒否顧客にWeChatウォレットの確認を依頼
insufficient_fund顧客の残高が不足顧客にチャージまたは別の支払い方法の使用を依頼
failed_processing一般的な処理エラー再試行またはサポートに連絡
invalid_account顧客のWeChat Payアカウントの問題顧客にアカウントステータスの確認を依頼

失敗の処理

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

if (event.key === 'charge.complete' && event.data.status === 'failed') {
const charge = event.data;
const failureCode = charge.failure_code;

switch (failureCode) {
case 'payment_expired':
// Offer to generate new QR code
notifyPOS('QR expired. Generate new payment?');
break;

case 'insufficient_fund':
// Suggest alternative payment
notifyPOS('Insufficient funds. Suggest another payment method.');
break;

case 'payment_rejected':
// Customer may need to verify account
notifyPOS('Payment rejected. Ask customer to check WeChat wallet.');
break;

default:
notifyPOS(`Payment failed: ${charge.failure_message}`);
}
}

res.sendStatus(200);
});

返金

WeChat Pay MPMは、元の取引から90日以内全額返金および一部返金をサポートしています。

返金の作成

# Full refund
curl https://api.omise.co/charges/chrg_test_xxx/refunds \
-u $OMISE_SECRET_KEY: \
-d "amount=150000"

# Partial refund
curl https://api.omise.co/charges/chrg_test_xxx/refunds \
-u $OMISE_SECRET_KEY: \
-d "amount=50000"

返金ポリシー

項目詳細
返金期間取引日から90日
全額返金対応
一部返金対応
複数回返金対応(元の金額まで)
処理時間1〜3営業日
返金先

返金は顧客のWeChat Payウォレットに戻されます。返金が処理されると、顧客はWeChatアプリで通知を受け取ります。

POS統合のベストプラクティス

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

function displayQRCode(qrUrl, amount) {
// Ensure QR code is prominently displayed
return `
<div class="pos-payment-screen">
<div class="qr-wrapper" style="
background: white;
padding: 20px;
border: 4px solid #09BB07;
display: inline-block;
">
<img src="${qrUrl}"
alt="Scan to Pay"
style="width: 250px; height: 250px;" />
</div>
<h2 style="color: #09BB07;">Scan with WeChat</h2>
<p class="amount">฿${(amount / 100).toFixed(2)}</p>
</div>
`;
}

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

function startCountdown(expiresAt, elementId) {
const expiryTime = new Date(expiresAt).getTime();

const timer = setInterval(() => {
const now = Date.now();
const timeLeft = expiryTime - now;

if (timeLeft <= 0) {
clearInterval(timer);
document.getElementById(elementId).textContent = 'EXPIRED';
document.getElementById(elementId).classList.add('expired');
handleExpiredQR();
return;
}

const hours = Math.floor(timeLeft / (1000 * 60 * 60));
const minutes = Math.floor((timeLeft % (1000 * 60 * 60)) / (1000 * 60));
const seconds = Math.floor((timeLeft % (1000 * 60)) / 1000);

document.getElementById(elementId).textContent =
`${hours}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;

// Warning when less than 5 minutes remain
if (timeLeft < 5 * 60 * 1000) {
document.getElementById(elementId).classList.add('warning');
}
}, 1000);

return timer;
}

3. 支払いステータスのポーリング(バックアップ)

Webhookが主要な通知方法ですが、バックアップとしてポーリングを実装してください:

async function pollPaymentStatus(chargeId, interval = 3000, maxAttempts = 40) {
let attempts = 0;

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

try {
const response = await fetch(`/api/charge-status/${chargeId}`);
const data = await response.json();

if (data.status === 'successful') {
clearInterval(poll);
handlePaymentSuccess(data);
} else if (data.status === 'failed') {
clearInterval(poll);
handlePaymentFailure(data);
} else if (data.status === 'expired') {
clearInterval(poll);
handleExpiredQR();
} else if (attempts >= maxAttempts) {
clearInterval(poll);
// Max attempts reached, rely on webhook
}
} catch (error) {
console.error('Status check error:', error);
}
}, interval);

return poll;
}

4. ネットワーク問題の処理

async function createChargeWithRetry(orderData, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
const response = await fetch('/api/create-wechat-mpm-charge', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(orderData)
});

if (response.ok) {
return await response.json();
}
} catch (error) {
if (i === maxRetries - 1) {
throw new Error('Failed to create charge after multiple attempts');
}
// Wait before retry
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
}
}
}

5. キャンセル/再生成オプションの提供

function showPaymentControls(chargeId) {
return `
<div class="payment-controls">
<button onclick="cancelPayment('${chargeId}')" class="btn-cancel">
Cancel Payment
</button>
<button onclick="regenerateQR()" class="btn-regenerate">
New QR Code
</button>
</div>
`;
}

async function cancelPayment(chargeId) {
// Note: Cannot cancel pending charge, but can expire/void
if (confirm('Cancel this payment and return to checkout?')) {
// Clear display and return to order entry
clearPaymentScreen();
returnToOrderEntry();
}
}

6. すべての取引をログ記録

function logTransaction(charge, status) {
const logEntry = {
timestamp: new Date().toISOString(),
charge_id: charge.id,
order_id: charge.metadata?.order_id,
terminal_id: charge.metadata?.terminal_id,
amount: charge.amount,
currency: charge.currency,
status: status,
failure_code: charge.failure_code || null
};

// Store in local DB for reconciliation
saveToLocalLog(logEntry);

// Send to central logging system
sendToLoggingService(logEntry);
}

よくある質問

Merchant-Presented Mode(MPM)とは何ですか?

MPMは「C scan B」(顧客が加盟店をスキャン)とも呼ばれ、加盟店がQRコードを表示し、顧客がウォレットアプリでそれをスキャンするオフライン決済方法です。これは、加盟店がチェックアウト体験を管理する店舗内WeChat Pay取引の標準的なフローです。

WeChat Pay MPMとWeChat Pay(オンライン)の違いは何ですか?

WeChat Pay(オンライン)は、顧客が支払いを完了するためにWeChatにリダイレクトされるeコマースとWebチェックアウト用に設計されています。WeChat Pay MPMは、端末にQRコードが表示され、顧客が店内でスキャンする実店舗のPOS環境用に設計されています。

機能WeChat Pay(オンライン)WeChat Pay MPM
ユースケースeコマース店舗POS
フローリダイレクトオフラインQR
ソースタイプwechat_paywechat_pay_mpm
MPMとUPMの違いは何ですか?

MPM(Merchant-Presented Mode)では、加盟店がQRコードを表示し、顧客がそれをスキャンします。UPM(User-Presented Mode)では、顧客がWeChatアプリからバーコードを表示し、加盟店がバーコードスキャナーでそれをスキャンします。UPMは通常より高速ですが、バーコードスキャンハードウェアが必要です。

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

QRコードはデフォルトで2時間後に期限切れになります。ソース作成時に1分から2時間の間で有効期限をカスタマイズできます。残り時間をスタッフと顧客に知らせるため、常にカウントダウンタイマーを表示してください。

MPM取引を無効化または返金できますか?

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

MPMには特別なハードウェアが必要ですか?

MPMには特別なハードウェアは必要ありません。任意の画面(POS端末、タブレット、モニター)でQRコードを表示するか、印刷することもできます。顧客は自分のスマートフォンとWeChatアプリを使用してスキャンし、支払います。

関連リソース