WeChat Pay Setup
This guide covers setting up WeChat Pay (微信支付) for payment processing, primarily for the Chinese market.
Prerequisites
- WeChat Pay Merchant account (微信支付商户号)
- Business registration in China
- Access to WeChat Pay Merchant Platform
- ICP registered domain
Step 1: Apply for Merchant Account
Individual Merchant
- Go to WeChat Pay Merchant Platform
- Click 成为商户 (Become a Merchant)
- Select 个人 (Individual) if applicable
- Complete verification process
Enterprise Merchant
- Select 企业 (Enterprise)
- Prepare required documents:
- Business license
- Legal representative ID
- Bank account information
- Business category information
- Submit application
- Wait for review (typically 1-5 business days)
Step 2: Configure Merchant Settings
After approval:
- Log in to Merchant Platform
- Go to 账户中心 (Account Center)
- Note down:
- 商户号 (Merchant ID): Your merchant identifier
- API密钥 (API Key): For request signing
Generate API Key
- Go to 账户中心 > API安全
- Click 设置API密钥 (Set API Key)
- Generate a 32-character key
- Save it securely
Configure API Certificates
For API v3:
- Go to 账户中心 > API安全
- Click 申请API证书 (Apply for API Certificate)
- Download certificate files:
apiclient_cert.pem- Public certificateapiclient_key.pem- Private key
- Store certificates securely
Step 3: Configure Payment Methods
Native Payment (扫码支付)
For web applications:
- Go to 产品中心 > Native支付
- Enable the product
- Configure:
- 支付回调地址 (Callback URL)
- 支付授权目录 (Payment Authorization Directory)
JSAPI Payment (公众号支付)
For WeChat in-app browser:
- Go to 产品中心 > JSAPI支付
- Link your Official Account (公众号)
- Configure authorized payment directories
App Payment (APP支付)
For mobile apps:
- Go to 产品中心 > APP支付
- Configure:
- iOS Bundle ID
- Android Package Name
Step 4: Configure Callback URL
- Go to 开发配置 (Development Configuration)
- Set notification URL:
https://yourdomain.com/v1/payment/webhook/wechat
Note: URL must be HTTPS and ICP registered domain.
Step 5: Configure in OpenDev
- Log in to OpenDev Platform
- Go to your application's Payment Configuration
- Add WeChat Pay configuration:
{
"platform": "wechat_pay",
"enabled": true,
"config": {
"mchId": "1234567890",
"appId": "wx1234567890abcdef",
"apiKey": "your_32_char_api_key",
"apiV3Key": "your_api_v3_key",
"certPath": "/path/to/apiclient_cert.pem",
"keyPath": "/path/to/apiclient_key.pem",
"notifyUrl": "https://yourdomain.com/v1/payment/webhook/wechat"
}
}
Configuration Fields
| Field | Required | Description |
|---|---|---|
| Merchant ID | Yes | 商户号 |
| App ID | Yes | 关联的公众号/小程序/移动应用AppID |
| API Key | Yes | 32位API密钥 |
| API v3 Key | Yes | APIv3密钥 |
| Certificate Path | Yes | 证书文件路径 |
| Private Key Path | Yes | 私钥文件路径 |
| Notify URL | Yes | 支付结果回调地址 |
Step 6: Configure Product Tiers
Link WeChat Pay product configuration:
- Go to Product Tiers in OpenDev
- For each tier, add WeChat Pay info:
{
"productId": "pro_monthly",
"name": "Pro Monthly",
"platformProductIds": {
"wechat_pay": "pro_monthly_cny"
},
"price": {
"CNY": 6800
}
}
Note: WeChat Pay amounts are in CNY cents (分).
Step 7: Implement Payment
Native Payment (QR Code)
Generate payment QR code:
const WxPay = require('wechatpay-node-v3');
const pay = new WxPay({
mchid: 'YOUR_MCH_ID',
appid: 'YOUR_APP_ID',
publicKey: fs.readFileSync('apiclient_cert.pem'),
privateKey: fs.readFileSync('apiclient_key.pem'),
});
async function createNativeOrder(orderId, amount, description) {
const result = await pay.transactions_native({
description,
out_trade_no: orderId,
notify_url: 'https://yourdomain.com/webhook/wechat',
amount: {
total: amount, // In CNY cents
currency: 'CNY',
},
});
return result.code_url; // QR code URL
}
JSAPI Payment (WeChat Browser)
async function createJSAPIOrder(orderId, amount, openid) {
const result = await pay.transactions_jsapi({
description: 'Order payment',
out_trade_no: orderId,
notify_url: 'https://yourdomain.com/webhook/wechat',
amount: {
total: amount,
currency: 'CNY',
},
payer: {
openid: openid, // User's OpenID
},
});
return result; // Returns prepay_id
}
Frontend invocation:
// Get payment parameters from server
const payParams = await getPaymentParams(orderId);
// Call WeChat Pay
WeixinJSBridge.invoke('getBrandWCPayRequest', {
appId: payParams.appId,
timeStamp: payParams.timeStamp,
nonceStr: payParams.nonceStr,
package: payParams.package,
signType: payParams.signType,
paySign: payParams.paySign,
}, function(res) {
if (res.err_msg === 'get_brand_wcpay_request:ok') {
// Payment successful
}
});
App Payment (Mobile SDK)
// Android
val req = PayReq()
req.appId = appId
req.partnerId = mchId
req.prepayId = prepayId
req.nonceStr = nonceStr
req.timeStamp = timeStamp
req.packageValue = "Sign=WXPay"
req.sign = sign
api.sendReq(req)
// iOS
let req = PayReq()
req.partnerId = mchId
req.prepayId = prepayId
req.nonceStr = nonceStr
req.timeStamp = UInt32(timeStamp)!
req.package = "Sign=WXPay"
req.sign = sign
WXApi.send(req)
Step 8: Handle Notifications
const crypto = require('crypto');
app.post('/webhook/wechat', express.raw({ type: '*/*' }), async (req, res) => {
try {
// Verify signature
const timestamp = req.headers['wechatpay-timestamp'];
const nonce = req.headers['wechatpay-nonce'];
const signature = req.headers['wechatpay-signature'];
const serial = req.headers['wechatpay-serial'];
// Construct message for verification
const message = `${timestamp}\n${nonce}\n${req.body}\n`;
// Verify using WeChat public certificate
const isValid = verifySignature(message, signature, serial);
if (!isValid) {
return res.status(400).send();
}
// Decrypt notification
const notification = decryptNotification(req.body, apiV3Key);
if (notification.event_type === 'TRANSACTION.SUCCESS') {
const transaction = notification.resource;
await handlePaymentSuccess(transaction);
}
res.json({ code: 'SUCCESS', message: '' });
} catch (error) {
res.status(500).json({ code: 'FAIL', message: error.message });
}
});
function decryptNotification(body, apiV3Key) {
const data = JSON.parse(body);
const resource = data.resource;
const decipher = crypto.createDecipheriv(
'aes-256-gcm',
apiV3Key,
Buffer.from(resource.nonce, 'utf8')
);
decipher.setAuthTag(Buffer.from(resource.ciphertext.slice(-16), 'base64'));
decipher.setAAD(Buffer.from(resource.associated_data, 'utf8'));
const decrypted = Buffer.concat([
decipher.update(Buffer.from(resource.ciphertext.slice(0, -16), 'base64')),
decipher.final(),
]);
return JSON.parse(decrypted.toString('utf8'));
}
Step 9: Currency Conversion
WeChat Pay only supports CNY. Implement currency conversion:
async function convertToCNY(amountUSD) {
// Get current exchange rate
const rate = await getExchangeRate('USD', 'CNY');
// Convert and round to cents
const amountCNY = Math.round(amountUSD * rate * 100);
return amountCNY;
}
Step 10: Test the Integration
Sandbox Testing
- Use WeChat Pay sandbox environment
- Sandbox API endpoints:
https://api.mch.weixin.qq.com/sandboxnew/ - Get sandbox API key from sandbox management
Test Scenarios
| Scenario | Test Method |
|---|---|
| Successful payment | Complete normal flow |
| Payment timeout | Wait for order expiry |
| Refund | Initiate refund via API |
| Notification | Check webhook logs |
Test Tools
- WeChat Pay Developer Tools
- WeChat DevTools for Mini Programs
- Postman for API testing
Troubleshooting
Error: INVALID_REQUEST
Solutions:
- Check all required parameters are present
- Verify signature is correct
- Ensure amounts are in cents
Error: SIGN_ERROR
Solutions:
- Verify API key is correct
- Check signature algorithm (HMAC-SHA256)
- Ensure correct encoding (UTF-8)
QR Code Not Working
Solutions:
- Verify code_url format is correct
- Check Native payment is enabled
- Ensure order hasn't expired
Notification Not Received
Solutions:
- Verify callback URL is HTTPS
- Check URL returns 200 status
- Verify domain has ICP registration
Security Best Practices
- Secure API Keys - Never expose in frontend
- Verify Signatures - Always verify notification signatures
- Use HTTPS - Required for all endpoints
- Certificate Security - Store certificates securely
- Idempotency - Handle duplicate notifications
Compliance Notes
China Regulations
- Domain must have ICP registration
- Comply with PIPL data protection
- Keep transaction records for tax purposes
- Support invoice generation if required
Cross-Border Payments
For international merchants:
- Apply for cross-border payment qualification
- Currency settlement in USD/EUR available
- Additional compliance requirements apply