微信小程序支付全流程实战指南(Node.js后端篇)

一、支付流程核心架构

1.1 支付交互时序图

bash 复制代码
小程序端 → 商户服务端 → 微信支付API → 回调通知
1. 创建本地订单 →
2. 请求统一下单 →
3. 返回支付参数 →
4. 发起微信支付 →
5. 接收支付结果 →
6. 验证回调签名 →
7. 更新订单状态

1.2 必备配置清单

  • ✅ 微信支付商户号(mch_id)
  • ✅ API v3密钥(32位随机字符串)
  • ✅ 小程序appId与商户号绑定
  • ✅ 服务器IP白名单配置
  • ✅ 下载API证书(apiclient_cert.p12)

二、Node.js服务端核心实现

2.1 初始化支付模块

javascript 复制代码
// wechat-pay.js
const crypto = require('crypto');
const fs = require('fs');
const axios = require('axios');

const config = {
  appId: 'wx1234567890',    // 小程序ID
  mchId: '1600000000',      // 商户号
  apiKey: 'your_api_v3_key',// API密钥
  notifyUrl: 'https://yourdomain.com/pay/notify' // 支付回调地址
};

// 证书加载
const cert = {
  pfx: fs.readFileSync('./apiclient_cert.p12'),
  passphrase: config.mchId  // 商户号作为密码
};

// 创建带证书的axios实例
const payClient = axios.create({
  baseURL: 'https://api.mch.weixin.qq.com',
  httpsAgent: new https.Agent(cert)
});

2.2 统一下单接口

javascript 复制代码
async function createPayment(orderInfo) {
  const nonceStr = crypto.randomBytes(16).toString('hex');
  const timestamp = Math.floor(Date.now() / 1000);
  
  const params = {
    appid: config.appId,
    mchid: config.mchId,
    description: orderInfo.desc,
    out_trade_no: orderInfo.tradeNo,
    notify_url: config.notifyUrl,
    amount: {
      total: orderInfo.amount, // 单位:分
      currency: 'CNY'
    },
    payer: {
      openid: orderInfo.openid // 小程序获取的用户openid
    }
  };

  // 构造签名
  const signature = generateSignature('POST', '/v3/pay/transactions/jsapi', 
    timestamp, nonceStr, params);

  try {
    const response = await payClient.post('/v3/pay/transactions/jsapi', params, {
      headers: {
        'Authorization': `WECHATPAY2-SHA256-RSA2048 ${signature}`,
        'Accept': 'application/json',
        'Content-Type': 'application/json'
      }
    });
    
    return response.data;
  } catch (error) {
    console.error('统一下单失败:', error.response.data);
    throw new Error('支付创建失败');
  }
}

// 签名生成算法
function generateSignature(method, url, timestamp, nonce, body) {
  const signStr = `${method}\n${url}\n${timestamp}\n${nonce}\n${JSON.stringify(body)}\n`;
  const sign = crypto.createSign('RSA-SHA256');
  sign.update(signStr);
  return sign.sign(config.apiKey, 'base64');
}

三、小程序端支付触发

3.1 获取支付参数

javascript 复制代码
// pages/pay.js
Page({
  handlePayment() {
    wx.login({
      success: (res) => {
        wx.request({
          url: '/api/create-payment',
          method: 'POST',
          data: {
            code: res.code,
            amount: 100, // 单位分
            goodsId: '123'
          },
          success: (res) => {
            this.invokePayment(res.data);
          }
        });
      }
    });
  },

  invokePayment(payParams) {
    wx.requestPayment({
      timeStamp: payParams.timeStamp,
      nonceStr: payParams.nonceStr,
      package: payParams.package,
      signType: 'RSA',
      paySign: payParams.paySign,
      success: (res) => {
        console.log('支付成功', res);
      },
      fail: (err) => {
        console.error('支付失败', err);
      }
    });
  }
});

四、支付回调处理

4.1 接收通知接口

javascript 复制代码
// routes/pay.js
router.post('/pay/notify', async (ctx) => {
  const headers = ctx.headers;
  const body = ctx.request.body;

  // 验证签名
  const verifyResult = verifySignature(
    headers['wechatpay-timestamp'],
    headers['wechatpay-nonce'],
    headers['wechatpay-signature'],
    ctx.request.rawBody
  );

  if (!verifyResult) {
    ctx.status = 403;
    return;
  }

  // 处理业务逻辑
  if (body.event_type === 'TRANSACTION.SUCCESS') {
    const result = await processPayment(body.resource.ciphertext);
    if (result.success) {
      ctx.status = 200;
      ctx.body = { code: 'SUCCESS', message: 'OK' };
    }
  }
});

// 解密回调数据
async function processPayment(ciphertext) {
  const decrypt = crypto.createDecipheriv('aes-256-gcm', config.apiKey, '');
  let decrypted = decrypt.update(ciphertext, 'base64', 'utf8');
  decrypted += decrypt.final('utf8');
  return JSON.parse(decrypted);
}

五、关键安全策略

5.1 防重复支付

javascript 复制代码
// 使用Redis实现幂等性控制
const redis = require('redis');
const client = redis.createClient();

async function checkDuplicate(tradeNo) {
  const key = `payment:${tradeNo}`;
  const exists = await client.set(key, 'LOCK', 'NX', 'EX', 300);
  return exists === null;
}

5.2 支付状态查询

javascript 复制代码
async function queryPaymentStatus(tradeNo) {
  const url = `/v3/pay/transactions/out-trade-no/${tradeNo}?mchid=${config.mchId}`;
  const response = await payClient.get(url);
  return {
    status: response.data.trade_state,
    amount: response.data.amount.total
  };
}

六、调试技巧与常见问题

6.1 沙箱环境配置

javascript 复制代码
// 修改请求地址为沙箱环境
const payClient = axios.create({
  baseURL: 'https://api.mch.weixin.qq.com/sandboxnew',
  // ...其他配置不变
});

6.2 典型错误排查表

错误码 原因分析 解决方案
APPID_MCHID_NOT_MATCH 小程序与商户号未绑定 登录商户平台-关联设置
INVALID_REQUEST 签名验证失败 检查参数顺序和编码格式
USERPAYING 用户支付中 建议10秒后查询订单状态
OUT_TRADE_NO_USED 商户订单号重复 检查订单号生成逻辑

结语:支付系统的三重境界

  1. 能用:完成基础支付流程
  2. 可靠:处理网络超时、异步通知、对账机制
  3. 安全:防御重放攻击、保证数据一致性、实现熔断降级

建议开发完成后进行以下验证:

  • 模拟支付金额篡改(前端传1元,实际支付0.01元)
  • 测试网络中断后的订单状态一致性
  • 验证高并发场景下的库存扣减逻辑

支付系统开发如同走钢丝,唯有严谨与敬畏方能平稳落地。愿本文能助您少走弯路,代码永无PAYMENT_ERROR

相关推荐
举个栗子dhy11 分钟前
如何处理动态地址栏参数,以及Object.entries() 、Object.fromEntries()和URLSearchParams.entries()使用
javascript
学习OK呀13 分钟前
后端上手学习React Router基础知识
前端
宁静_致远14 分钟前
React Native 技术栈:基于 macOS 开发平台的 iOS 应用开发指南
前端·javascript·react native
H5开发新纪元15 分钟前
VS Code 插件开发实战:代码截图工具
javascript·visual studio code
Mike_jia17 分钟前
Cronicle终极指南:轻量级分布式任务管理系统的企业实践
前端
qq_5432485223 分钟前
web基础+HTTP+HTML+apache
前端
userkang29 分钟前
消失的前后端,崛起的智能体
前端·人工智能·后端·ai·硬件工程
前端付豪29 分钟前
🚀 2025 年 React 全攻略:40 个高频问题深度解析与实战指南
前端·react.js
五号厂房30 分钟前
论Ant Design ProTable 中如何优雅设置搜索框默认值
前端
不浪brown38 分钟前
WebGISer的福利!基于Cesium+Vue3的智慧数字农田项目,开源!
前端·cesium