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

  1. Go to WeChat Pay Merchant Platform
  2. Click 成为商户 (Become a Merchant)
  3. Select 个人 (Individual) if applicable
  4. Complete verification process

Enterprise Merchant

  1. Select 企业 (Enterprise)
  2. Prepare required documents:
  • Business license
  • Legal representative ID
  • Bank account information
  • Business category information
  1. Submit application
  2. Wait for review (typically 1-5 business days)

Step 2: Configure Merchant Settings

After approval:

  1. Log in to Merchant Platform
  2. Go to 账户中心 (Account Center)
  3. Note down:
  • 商户号 (Merchant ID): Your merchant identifier
  • API密钥 (API Key): For request signing

Generate API Key

  1. Go to 账户中心 > API安全
  2. Click 设置API密钥 (Set API Key)
  3. Generate a 32-character key
  4. Save it securely

Configure API Certificates

For API v3:

  1. Go to 账户中心 > API安全
  2. Click 申请API证书 (Apply for API Certificate)
  3. Download certificate files:
  • apiclient_cert.pem - Public certificate
  • apiclient_key.pem - Private key
  1. Store certificates securely

Step 3: Configure Payment Methods

Native Payment (扫码支付)

For web applications:

  1. Go to 产品中心 > Native支付
  2. Enable the product
  3. Configure:
  • 支付回调地址 (Callback URL)
  • 支付授权目录 (Payment Authorization Directory)

JSAPI Payment (公众号支付)

For WeChat in-app browser:

  1. Go to 产品中心 > JSAPI支付
  2. Link your Official Account (公众号)
  3. Configure authorized payment directories

App Payment (APP支付)

For mobile apps:

  1. Go to 产品中心 > APP支付
  2. Configure:
  • iOS Bundle ID
  • Android Package Name

Step 4: Configure Callback URL

  1. Go to 开发配置 (Development Configuration)
  2. Set notification URL:
https://yourdomain.com/v1/payment/webhook/wechat

Note: URL must be HTTPS and ICP registered domain.

Step 5: Configure in OpenDev

  1. Log in to OpenDev Platform
  2. Go to your application's Payment Configuration
  3. 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:

  1. Go to Product Tiers in OpenDev
  2. 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

  1. Use WeChat Pay sandbox environment
  2. Sandbox API endpoints: https://api.mch.weixin.qq.com/sandboxnew/
  3. 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

  1. Secure API Keys - Never expose in frontend
  2. Verify Signatures - Always verify notification signatures
  3. Use HTTPS - Required for all endpoints
  4. Certificate Security - Store certificates securely
  5. 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