微信支付(API v3)标准流程

一、整体时序(JSAPI/小程序)

  1. 商户后台下单:你的服务器向微信「统一下单」接口发起请求,拿到 prepay_id

  2. 前端拉起支付:小程序用 prepay_id 组装参数,调用 wx.requestPayment

  3. 异步通知:支付成功后,微信回调你的 notify_url(带签名 + 加密资源)。

  4. 验签解密 & 业务落账:后台校验回调签名,AES-GCM 解密交易资源,做幂等更新订单状态;回 200。

  5. (可选)主动查单/关单:容错用。

  6. (可选)退款 & 退款回调:同样验签解密。

其它场景差异:

  • APP 支付:拉起步骤在 App SDK;

  • Native(扫码):你先拿 code_url 生成二维码;

  • H5(外部浏览器):走 H5 场景,微信外唤起。


二、名词 & 关键参数

  • 商户号mchidAppIDappidAPI v3 Key(32字节)

  • 商户私钥(RSA)+ 证书序列号:用于请求签名

  • 微信平台证书:用于验签回调/应答

  • 商户订单号out_trade_no:你方唯一、做幂等键

  • 金额单位(整数)


三、后端:统一下单(JSAPI/小程序)

HTTP:POST https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi

请求体(示例):

{
  "appid": "wx1234567890abcdef",
  "mchid": "190000****",
  "description": "VIP会员-月卡",
  "out_trade_no": "ORDER_20250902_00001",
  "notify_url": "https://your.domain/pay/wechat/notify",
  "amount": { "total": 1999, "currency": "CNY" },
  "payer": { "openid": "oUpF8uMuAJO_M2pxb1Q9zNjWeS6o" }
}

请求头需带 WECHATPAY2-SHA256-RSA2048 授权签名(含 AuthorizationWechatpay-SerialNonceTimestamp 等)。

Java(Spring Boot)下单示例(用官方 SDK,省心)

推荐:com.wechat.pay.java:wechatpay-java(API v3)

// 1) 初始化(建议单例)
CertificatesManager mgr = CertificatesManager.getInstance();
AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
    new WechatPay2Credentials(mchid, new PrivateKeySigner(merchantSerialNumber, merchantPrivateKey)),
    apiV3Key.getBytes(StandardCharsets.UTF_8)
);
mgr.putMerchant(mchid, new WechatPay2Credentials(mchid, new PrivateKeySigner(merchantSerialNumber, merchantPrivateKey)), apiV3Key.getBytes());
CloseableHttpClient httpClient = WechatPayHttpClientBuilder.create()
    .withMerchant(mchid, merchantSerialNumber, merchantPrivateKey)
    .withValidator(new WechatPay2Validator(verifier))
    .build();

// 2) 统一下单(JSAPI)
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi");
String body = """
{
  "appid":"%s","mchid":"%s","description":"VIP会员-月卡",
  "out_trade_no":"%s","notify_url":"%s",
  "amount":{"total":%d},"payer":{"openid":"%s"}
}
""".formatted(appid, mchid, outTradeNo, notifyUrl, total, openid);
httpPost.setEntity(new StringEntity(body, ContentType.APPLICATION_JSON));
try (CloseableHttpResponse resp = httpClient.execute(httpPost)) {
    String respStr = EntityUtils.toString(resp.getEntity());
    // 解析得到 prepay_id
}

返回包含 prepay_id,例如:wx201410272009395522657a690389285100


四、前端:拉起支付(小程序/Taro)

// 拿到服务端返回参数后(推荐由服务端生成这些签名参数再下发给前端)
Taro.requestPayment({
  timeStamp: payParams.timeStamp,   // 字符串
  nonceStr: payParams.nonceStr,
  package: `prepay_id=${payParams.prepayId}`,
  signType: 'RSA',
  paySign: payParams.paySign,       // 服务端用商户私钥对 (appId,timeStamp,nonceStr,package) 组串签名
  success: () => { /* 展示“支付处理中”,等待回调或主动查单 */ },
  fail: (err) => { /* 失败/取消处理 */ }
})

强烈建议这些参数由后端签好再发给前端,避免私钥泄露。


五、异步通知:验签 + 解密 + 幂等

  • 回调地址:你在 notify_url 配置的 HTTPS 接口

  • 头部包含:Wechatpay-TimestampWechatpay-NonceWechatpay-SignatureWechatpay-Serial

  • 体内 resource 使用 API v3 Key 进行 AES-256-GCM 解密

Java 验签解密(示意):

// 读取回调 Header
String timestamp = request.getHeader("Wechatpay-Timestamp");
String nonce = request.getHeader("Wechatpay-Nonce");
String signature = request.getHeader("Wechatpay-Signature");
String serial = request.getHeader("Wechatpay-Serial");

// 验签
String body = requestBodyAsString(request);
String message = timestamp + "\n" + nonce + "\n" + body + "\n";
boolean ok = verifier.verify(serial, message.getBytes(StandardCharsets.UTF_8), signature);
if (!ok) { return ResponseEntity.status(401).build(); }

// 解密
JsonNode root = objectMapper.readTree(body);
JsonNode res = root.get("resource");
String ciphertext = res.get("ciphertext").asText();
String nonceStr = res.get("nonce").asText();
String aad = res.get("associated_data").asText();

String plain = AesGcm.decryptToString(apiV3Key, nonceStr, ciphertext, aad);
// plain 内含交易信息(如 trade_state=SUCCESS, out_trade_no, transaction_id 等)

// 幂等更新:按 out_trade_no 原子落账;若已处理直接返回 200
return ResponseEntity.ok("{\"code\":\"SUCCESS\",\"message\":\"OK\"}");

回调会重试:只要你不是 200/SUCCESS,微信会按策略重投。务必幂等


六、主动查单 & 关单

  • 查单:GET /v3/pay/transactions/out-trade-no/{out_trade_no}?mchid=...

  • 关单:POST /v3/pay/transactions/out-trade-no/{out_trade_no}/close

用于:前端失败但实际已扣款、或回调迟迟未到等。


七、退款 & 回调(API v3)

  • 申请退款:POST /v3/refund/domestic/refunds(用支付单 transaction_id 或 out_trade_no

  • 退款回调同样验签解密,按 out_refund_no 幂等处理。


八、常见坑 & 最佳实践

  • 证书与密钥

    • 商户私钥妥善保管;只在服务端使用。

    • 定期轮换平台证书(AutoUpdateCertificatesVerifier 可自动拉取)。

  • 时区/时间戳:统一用 UTC 秒级;签名串的换行与顺序严格一致。

  • 金额单位:分(int);计算金额用整数BigDecimal避免精度丢失。

  • 重复回调/并发:以 out_trade_no 做悲观/乐观锁或去重表。

  • 安全:回调仅接受 HTTPS,且白名单校验 & CSRF 防御

  • 沙箱/联调:先走仿真/沙箱(注意沙箱的密钥与生产不同);线下全链路打通再切生产。

  • 服务商模式(子商户):多一个 sub_mchid/sub_appid 维度,签名流程相同。

  • 合单/分账/电商收付通:按业务再扩展,依旧围绕「签名、拉起、回调、验签」。


九、极简落地清单

  • [ ] 在商户平台创建商户号、开通对应支付场景

  • [ ] 配置 appidmchidAPI v3 Key、下载商户私钥、保存证书序列号

  • [ ] 后端:打通统一下单 → 回调验签解密(幂等)

  • [ ] 前端:拿后端参数 requestPayment 拉起

  • [ ] 容错:查单/关单

  • [ ] 退款链路与对账

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐