Skip to content

微信 JSAPI 支付

前端只需要使用如下 API 唤起支付即可,参数由后段提供

H5 如何调用

https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_4.shtml

ts
function onBridgeReady() {
  WeixinJSBridge.invoke(
    "getBrandWCPayRequest",
    {
      appId: "wx2421b1c4370ecxxx", //公众号ID,由商户传入
      timeStamp: "1395712654", //时间戳,自1970年以来的秒数
      nonceStr: "e61463f8efa94090b1f366cccfbbb444", //随机串
      package: "prepay_id=wx21201855730335ac86f8c43d1889123400",
      signType: "RSA", //微信签名方式:
      paySign:
        "oR9d8PuhnIc+YZ8cBHFCwfgpaK9gd7vaRvkYD7rthRAZ/X+QBhcCYL21N7cHCTUxbQ+EAt6Uy+lwSN22f5YZvI45MLko8Pfso0jm46v5hqcVwrk6uddkGuT+Cdvu4WBqDzaDjnNa5UK3GfE1Wfl2gHxIIY5lLdUgWFts17D4WuolLLkiFZV+JSHMvH7eaLdT9N5GBovBwu5yYKUR7skR8Fu+LozcSqQixnlEZUfyE55feLOQTUYzLmR9pNtPbPsu6WVhbNHMS3Ss2+AehHvz+n64GDmXxbX++IOBvm2olHu3PsOUGRwhudhVf7UcGcunXt8cqNjKNqZLhLw4jq/xDg==", //微信签名
    },
    function (res) {
      if (res.err_msg == "get_brand_wcpay_request:ok") {
        // 使用以上方式判断前端返回,微信团队郑重提示:
        //res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。
      }
    }
  );
}
其他代码
ts
if (typeof WeixinJSBridge == "undefined") {
  if (document.addEventListener) {
    document.addEventListener("WeixinJSBridgeReady", onBridgeReady, false);
  } else if (document.attachEvent) {
    document.attachEvent("WeixinJSBridgeReady", onBridgeReady);
    document.attachEvent("onWeixinJSBridgeReady", onBridgeReady);
  }
} else {
  onBridgeReady();
}

以下为 server 内容


接入流程

签名

微信支付 API v3 请求都需要签名,签名不通过会被拒绝,并返回 401 Unauthorized。

官方文档:https://pay.weixin.qq.com/docs/merchant/development/interface-rules/signature-generation.html

准备条件

  • 微信支付商户号 / mchid
  • 证书序列号 / serial_no
  • API 证书(超级管理员账号获取,压缩包中)

签名串格式:

HTTP请求方法\n
URL\n
请求时间戳\n
请求随机串\n
请求报文主体\n

示例:

GET\n
/v3/certificates\n
1554208460\n
593BEC0C930BF1AFEB40B4A08C8FB242\n
\n

需要注意的是,URL 中如果有 params,是需要加 ?

微信会拒绝很久之前的请求,所以请求时间戳最好取当前。并且是秒,不是毫秒。

签名计算 示例

https://pay.weixin.qq.com/docs/merchant/development/generate-request-signature-verification/request-path-parameters-signature.html

js
const { createSign, createPrivateKey } = require("crypto");

const privateKey = createPrivateKey(`-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAptpm+qvIDCh/9wjU26SQCK26ogYkBhDrYxnAaw2JbbBsp1oD bHKk+1r381NeBUG2HEFAuU+Fr72u5ot3yKdzoF/FajAzQNKnm569/D3upKoi8mYB aST15Uig8j8qoUW1U217LL0jEHlSnHV3lcaDTXqDpTRR4Bfz9IqOgJgFZ8/oTfEo mSrjrLYef81Eyxr7ZIMQXEKKEK7V4UXKS0+/fDsiG/cXidhzt8UbTL9vqXqxM2+I DImyO+FAc/tkBG55LmzxPto1Nq0WbnZzRM/wTzrd0I/8NlevxtFbphg4evlHjFNI 7+GrqR87ViEwuAJJ9Je5QQjct5YJfFRWiZ5CMQIDAQABAoIBAGBi/GhEgezcHIg1 ltlHaFlLGuxsRbUnYwM9phVxnXk7GJlYe2/TjpERjPkIqOC6hBwwadZjJORP3FCc Mtc8PKRhjuZ377O7vU0915x2nnyLOGL1IE2AJ3iLi0ZFzTea0FPgg+5lWHM00s9F YI6qPcGtS41M+xtMWwZiYE3TBBRibHiY8ugGyaNAhiMKehyW05uApjlIF55wwCGx BkyESJpGRR/6853iHke6Ge+xVcMa9QmQdoH0QqL/8kT28PL568mJJr0Ow/83t4+d Pe70YPzKAxgUnaDsHJqO+b8qH69AEs8rTI5h2Mon6pH+bJT66KUoiXhn+Kf+4LSs henRP10CgYEA1QJSfuFOWVRjrg3N/rAIc/Ak84BTZavbyrkqBSuoTs9i/nMI/hOz VxpDntg7Bx2Tctl6sZO3GioTxKdc/YYaTKci1TKBbeginpsqEQVgwkMCy8HpvUmR fyAMqLwZC4h9+j+NiZtuoFJDTCgv+WYbasX+kWYEUM21bnSYuO7yEQsCgYEAyIdP r9uzqPgzN34Tmx+CNTa16VjhBh+zkBtXRLDLhWBeIYxoYNJARD98Pb1XZdvpkZZW Sk7MfaKo2/DomzyyyB/MbHWwAdFi3yb4y7uMJfyC1MzdUSNN3Vp579hJxHkJ+nN4 Ys76yfcEeVOLnvUT1Z0KKCdIWRdT1Lgi+X1itzMCgYBJUXlPzwGG4fNFj97d0X23 Wmt9nSgXkOYgi0eZbAOMzPmIF9R6kBFk49dur4Lx2g5Ms+r1gKC/0sfnIqxxX11i EQ1+UNoYGJUB/uql3TIG68XkmKR50P7RwRhaZBRC0gJ6xrFTMjsL2ATuC88niyvY vrn3FiRaI9RVZrDCxwxvLQKBgEXW4okEAqGBuAzGqztmkOnJoTehDdYdKmOxMgap cGiGdKJIjX3THDDoz3ONQyglnEZpTqpYoV3MTfU0BT8zt6x9bqwDnQY1D7NalmIW cqw0Mri8lQQSQKcsQLWo5aA466G/n5kCL1Qx5OwAjesRvhOyuvvbGpZ0ymyWqQ+t fLkDAoGATcul1L8y5D/wNVP1GXbXMZfBsFP3bbqy8c+Ashm6g8OLm2mGNntd5Z6h 1KkID7Yksh+dZ6t7XaPBtGACXX5Eryr537JVvdX8hAVCp5HVtaN/9VBVP8Ka2e4s VS/xeNgOMQ7uzhRPBJ8HiTmdI1nHhDnYQpGiBgQn0Z5RAkSvFMk=
-----END RSA PRIVATE KEY-----`);

function signature(conf) {
  const {
    algorithm = "sha256",
    data,
    output_format = "base64",
    privateKey,
  } = conf;

  const sign = createSign(algorithm);

  sign.update(data);
  sign.end();
  return sign.sign(privateKey, output_format);
}

function getWxSignature(param) {
  const { method, url, body = "" } = param;

  const timestamp = "1554208460";
  const nonce_str = "593BEC0C930BF1AFEB40B4A08C8FB242";
  const data = `${method}\n${url}\n${timestamp}\n${nonce_str}\n${body}\n`;

  console.log(data);

  const sign = signature({
    data,
    privateKey,
  });

  return {
    timestamp,
    nonce_str,
    signature: sign,
  };
}

const result = getWxSignature({
  method: "GET",
  url: "/v3/refund/domestic/refunds/123123123123",
});

console.log(result.signature);
console.log(
  "Lc9VXxmeonkdV8Xk9tmigQFLhl0vRWTerdmoRu01aAnYwIrD/5nsSwE1WlmZGLRlAFTNQ3QsMa0+VRDlJp1Wp5p0nO8EK68b5sJBbjouxaFciIfq1zfDWWz+jqhcMoKXI1A6dPm1AW7D4d30WsMTNzp6g23OXakIsh9LO3lUmwvTuE0BY8ncf6tNGk4wKmvXwERd/ZpoQY3MAVKz+Nakwc+2XBmzT66KcUehU5kr4IvGa/lEU5RZb/q00zP9VLdBhC/jQSX3X1UcJLCtEc4gTmib4tnmAT+bHF/e17ZAuxDNcx6rqT8gNEXqaJGG+1OflMSTU2tpyG65G4dMKdFcoA=="
);

问题

输入源“/appid”映射到字段“公众号 ID”必填性规则校验失败,此字段为必填项

{"description":"测试支付","out_trade_no":"5e19a33065b842a7bb95bb07db623a8d","appid":"wxa9462c49dd468b45","mchid":"1690694541","notify_url":"https://www.cqzmi.com/api/v2/wx/pay/notify","amount":{"total":"0.01"},"payer":{"openid":"oHeVG6gxIqnkADyR1jjxb8VzY5PU"}}
Authorization: WECHATPAY2-SHA256-RSA2048 mchid="1690694541",nonce_str="470267f053bb4d1fb75482db8368a45c",signature="CDpRDPn3aoOml7dT2Y0HBt0dk209q8SB1FvSGBBzTcgQ9kCgsZWZWqvaCgGgyFMAsLcY6tGADB+8DLIOInQEHL+Qu/nFXuz31dmdCGYtq7Ubs9uvKOMSQrFLkhDiwAbR1lMka2uQs/vn3l1P3N1ypwanBY8XVFY3beBq0IbUEnZWFnbkx7AoeC1BKWVZs3vQtkp69LDj8EPWAPRC+V5cRfsuLVBH89jM3RVoVq5pMcchK7ygpu+aDyJTt19TmvKf2+IiBxx4HmlpaktUl2Z0DG48k8VnIdw9J26tQbCsdBs0bHWe+AEg8dVGuX2mJvdUqSwwzJcQwPMl/lfHHKw+Bg==",timestamp="1730618129",serial_no="70331DBACB69CC4A05C10DA714C455F976B42E9A"\r\n