WeChat Pay UPM
POSシステムを通じて顧客の決済バーコードをスキャンし、WeChat Payユーザーからの店舗内決済を受け付けます。
他のWeChat Payオプション
オンライン決済については、WeChat Payをご覧ください。加盟店提示型QRコード(C scan B)については、WeChat Pay MPMをご覧ください。
概要
WeChat Pay User-Presented Mode(UPM)は、加盟店が顧客のWeChatアプリに表示されたバーコードをスキャンするオフライン決済方式です。「B scan C」(Business scans Customer)とも呼ばれ、POSターミナルにバーコードスキャナーを備えた大量取引の小売環境に最適です。
主な特徴:
- 高速な精算 - 数秒でスキャンと処理が完了
- オフラインフロー - リダイレクト不要、バーコードベースの決済
- 大規模なユーザーベース - WeChatの13億人以上のユーザーにアクセス
- 馴染みのある体験 - 商品バーコードのスキャンと同様
- クロスボーダー - 中国人観光客からの決済を受け付け
- POS連携 - 既存のバーコードスキャナーで動作
対応地域
| 地域 | 通貨 | 最小金額 | 最大金額 | APIバージョン |
|---|---|---|---|---|
| タイ | THB | 20.00 | 150,000.00 | 2017-11-02 |
クロスボーダー決済
WeChat Pay UPMは、中国人観光客をターゲットとする加盟店にとって特に価値があります。顧客はWeChatウォレットからCNYで支払い、加盟店はTHBで決済を受け取ります。
仕組み
決済フロー:
- 顧客がスマートフォンでWeChatアプリを開く
- 顧客が「Pay」に移動し、決済バーコードを表示
- 加盟店がPOSターミナルでバーコードをスキャン
- スキャンしたバーコード値で課金を作成
- 顧客がWeChatアプリで決済を確認(PIN/生体認証が必要な場合あり)
- 決済が処理され、双方が確認を受け取る
一般的な完了時間: 10〜30秒
実装
バーコードで課金を作成
UPMでは、スキャンしたバーコード値で直接課金を作成します。他の決済方法とは異なり、別途ソース作成ステップはなく、課金と同時にインラインでソースが作成されます。
- cURL
- Node.js
- PHP
- Python
- Ruby
- Go
- Java
- C#
curl https://api.omise.co/charges \
-u $OMISE_SECRET_KEY: \
-d "amount=150000" \
-d "currency=THB" \
-d "source[type]=wechat_pay_upm" \
-d "source[barcode]=130991292552725093"
const omise = require('omise')({
secretKey: 'skey_test_YOUR_SECRET_KEY'
});
const charge = await omise.charges.create({
amount: 150000, // THB 1,500.00
currency: 'THB',
source: {
type: 'wechat_pay_upm',
barcode: '130991292552725093' // Scanned from customer's WeChat
}
});
console.log('Charge status:', charge.status);
console.log('Charge ID:', charge.id);
<?php
$charge = OmiseCharge::create([
'amount' => 150000,
'currency' => 'THB',
'source' => [
'type' => 'wechat_pay_upm',
'barcode' => '130991292552725093'
]
]);
echo "Status: " . $charge['status'];
echo "Charge ID: " . $charge['id'];
?>
import omise
omise.api_secret = 'skey_test_YOUR_SECRET_KEY'
charge = omise.Charge.create(
amount=150000,
currency='THB',
source={
'type': 'wechat_pay_upm',
'barcode': '130991292552725093'
}
)
print(f"Status: {charge.status}")
print(f"Charge ID: {charge.id}")
require 'omise'
Omise.api_key = 'skey_test_YOUR_SECRET_KEY'
charge = Omise::Charge.create({
amount: 150000,
currency: 'THB',
source: {
type: 'wechat_pay_upm',
barcode: '130991292552725093'
}
})
puts "Status: #{charge.status}"
puts "Charge ID: #{charge.id}"
charge, err := client.Charges().Create(&operations.CreateCharge{
Amount: 150000,
Currency: "THB",
Source: &operations.Source{
Type: "wechat_pay_upm",
Barcode: "130991292552725093",
},
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Status: %s\n", charge.Status)
Map<String, Object> sourceParams = new HashMap<>();
sourceParams.put("type", "wechat_pay_upm");
sourceParams.put("barcode", "130991292552725093");
Charge charge = client.charges().create(new Charge.CreateParams()
.amount(150000L)
.currency("THB")
.source(sourceParams));
System.out.println("Status: " + charge.getStatus());
System.out.println("Charge ID: " + charge.getId());
var charge = await client.Charges.Create(new CreateChargeRequest
{
Amount = 150000,
Currency = "THB",
Source = new PaymentSource
{
Type = "wechat_pay_upm",
Barcode = "130991292552725093"
}
});
Console.WriteLine($"Status: {charge.Status}");
Console.WriteLine($"Charge ID: {charge.Id}");
レスポンス:
{
"object": "charge",
"id": "chrg_test_5rt6s9vah5lkvi1rh9d",
"amount": 150000,
"currency": "THB",
"status": "pending",
"source": {
"object": "source",
"id": "src_test_5rt6s9vah5lkvi1rh9c",
"type": "wechat_pay_upm",
"flow": "offline",
"barcode": "130991292552725093"
},
"created_at": "2024-01-15T10:30:00Z"
}
課金レスポンスの処理
app.post('/pos/wechat-payment', async (req, res) => {
const { amount, barcode, order_id } = req.body;
// 金額を検証
if (amount < 2000 || amount > 15000000) {
return res.status(400).json({
success: false,
error: 'Amount must be between 20 and 150,000'
});
}
// バーコード形式を検証
if (!barcode || barcode.length < 10) {
return res.status(400).json({
success: false,
error: 'Invalid barcode'
});
}
try {
const charge = await omise.charges.create({
amount: amount,
currency: 'THB',
source: {
type: 'wechat_pay_upm',
barcode: barcode
},
metadata: {
order_id: order_id,
pos_terminal: 'POS-001'
}
});
if (charge.status === 'successful') {
// 決済が即座に成功
printReceipt(charge);
res.json({
success: true,
charge_id: charge.id,
status: 'successful'
});
} else if (charge.status === 'pending') {
// 顧客がWeChatアプリで確認する必要あり
// Webhook通知を待つ
res.json({
success: true,
charge_id: charge.id,
status: 'pending',
message: 'Please ask customer to confirm payment in WeChat'
});
} else {
res.json({
success: false,
status: charge.status,
message: charge.failure_message || 'Payment failed'
});
}
} catch (error) {
console.error('WeChat Pay UPM error:', error);
res.status(500).json({
success: false,
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 && charge.source.type === 'wechat_pay_upm') {
if (charge.status === 'successful') {
// 決済確認
notifyPOS(charge.id, 'success');
updateOrderStatus(charge.metadata.order_id, 'paid');
console.log(`WeChat Pay UPM successful: ${charge.id}`);
} else if (charge.status === 'failed') {
// 決済失敗
notifyPOS(charge.id, 'failed', charge.failure_message);
updateOrderStatus(charge.metadata.order_id, 'failed');
console.log(`WeChat Pay UPM failed: ${charge.id}, reason: ${charge.failure_message}`);
}
}
}
res.status(200).send('OK');
});
バーコードスキャナー連携
スキャナー要件
POSシステムと互換性のある標準的なバーコードスキャナーであれば、WeChat Pay UPMに使用できます。スキャナーは顧客のWeChatアプリから数値バーコードを読み取る必要があります。
対応スキャナー:
- USBバーコードスキャナー(1D/2D)
- Bluetoothバーコードスキャナー
- 内蔵POSスキャナー
- スキャンソフトウェア搭載のモバイルデバイスカメラ
バーコード形式
WeChat決済バーコードの特徴:
- 18桁の数値コード
- 特定のプレフィックス(10、11、12、13、14、15)で始まる
- 有効期限が限られている(通常1分)
// WeChatバーコード形式を検証
function isValidWeChatBarcode(barcode) {
// WeChatバーコードは通常18桁
// 特定のプレフィックスで始まる
const validPrefixes = ['10', '11', '12', '13', '14', '15'];
if (!/^\d{18}$/.test(barcode)) {
return false;
}
const prefix = barcode.substring(0, 2);
return validPrefixes.includes(prefix);
}
POS連携例
// POSターミナル連携
class WeChatPayUPM {
constructor(omise) {
this.omise = omise;
}
async processPayment(barcode, amount, orderId) {
// 入力を検証
if (!this.validateBarcode(barcode)) {
return { success: false, error: 'Invalid barcode format' };
}
if (!this.validateAmount(amount)) {
return { success: false, error: 'Invalid amount' };
}
try {
const charge = await this.omise.charges.create({
amount: amount,
currency: 'THB',
source: {
type: 'wechat_pay_upm',
barcode: barcode
},
metadata: {
order_id: orderId,
timestamp: new Date().toISOString()
}
});
return {
success: true,
chargeId: charge.id,
status: charge.status,
requiresConfirmation: charge.status === 'pending'
};
} catch (error) {
return {
success: false,
error: error.message
};
}
}
validateBarcode(barcode) {
return /^\d{18}$/.test(barcode);
}
validateAmount(amount) {
return amount >= 2000 && amount <= 15000000;
}
}
// 使用例
const pos = new WeChatPayUPM(omise);
const result = await pos.processPayment('130991292552725093', 150000, 'ORD-12345');
if (result.success) {
if (result.requiresConfirmation) {
displayMessage('Please ask customer to confirm in WeChat app');
waitForWebhook(result.chargeId);
} else {
printReceipt(result.chargeId);
}
} else {
displayError(result.error);
}
課金ステータス値
| ステータス | 説明 |
|---|---|
pending | 課金作成済み、WeChatアプリでの顧客確認待ち |
successful | 決済が正常に完了 |
failed | 決済が拒否または失敗 |
expired | 課金が期限切れ(24時間以内に承認なし) |
課金の有効期限
WeChat Pay UPM課金は、顧客が承認しない場合、24時間後に期限切れになります。店舗内決済の場合、これは通常、顧客が取引を中止したことを示します。
失敗コード
| コード | 説明 | 推奨アクション |
|---|---|---|
payment_expired | 決済承認が期限切れ | 新しい課金を作成 |
payment_rejected | WeChat/銀行により決済が拒否された | 再試行または別の決済方法を依頼 |
insufficient_fund | ウォレットの残高不足 | チャージまたは別の決済方法を依頼 |
failed_processing | 一般的な処理エラー | 取引を再試行 |
invalid_barcode | バーコードが無効または期限切れ | 顧客に決済コードの更新を依頼 |
// 失敗コードの処理
function handlePaymentFailure(charge) {
const failureCode = charge.failure_code;
const failureMessage = charge.failure_message;
const errorMessages = {
'payment_expired': 'Payment expired. Please try again.',
'payment_rejected': 'Payment was declined. Please try a different payment method.',
'insufficient_fund': 'Insufficient funds. Please top up your WeChat wallet.',
'failed_processing': 'Processing error. Please try again.',
'invalid_barcode': 'Invalid barcode. Please refresh your payment code in WeChat.'
};
return errorMessages[failureCode] || failureMessage || 'Payment failed. Please try again.';
}
返金サポート
WeChat Pay UPMは、元の取引から90日以内の全額および部分返金をサポートしています。
全額返金
// 全額返金
const refund = await omise.charges.createRefund('chrg_test_...', {
amount: 150000 // 全額
});
console.log('Refund ID:', refund.id);
console.log('Refund status:', refund.status);
部分返金
// 部分返金
const partialRefund = await omise.charges.createRefund('chrg_test_...', {
amount: 50000 // 部分金額
});
cURLでの返金
# 全額返金
curl https://api.omise.co/charges/chrg_test_xxx/refunds \
-u $OMISE_SECRET_KEY: \
-d "amount=150000"
# 部分返金
curl https://api.omise.co/charges/chrg_test_xxx/refunds \
-u $OMISE_SECRET_KEY: \
-d "amount=50000"
返金タイムライン
- 返金は1〜3営業日以内に処理されます
- 顧客はWeChatウォレットで返金を受け取ります
- 元の課金金額まで複数の部分返金が可能です