签约成功回调通知(JSON)
商户创建签约计划后,拉起支付分签约页,用户会在该页面完成计划签约,完成计划签约后微信会把相关计划签约成功信息发送给商户,商户需要接收处理,并按照文档规范返回应答。出于安全的考虑,我们对支付结果数据进行了加密,商户需要先对通知数据进行解密,才能得到支付结果数据。 官方文档 官方文档
请求头(headers)
Wechatpay-Nonce: 3d980fb850fdce97f6bfb3d248597f16
Wechatpay-Serial: 7132d72a03e93cddf8c03bbd1f37eedf********
Wechatpay-Signature: i48r5y8IQw1qpTO+ywoV...
Wechatpay-Signature-Type: WECHATPAY2-SHA256-RSA2048
Wechatpay-Timestamp: 1710048759
Request-ID: 08F78BB5AF0610D302189F99DD5C20BA56F89845-0
请求报文(body)
json
{
"id":"EV-2018022511223320873",
"create_time":"2015-05-20T13:29:35+08:00",
"resource_type":"encrypt-resource",
"event_type":"PAYSCORE.USER_SIGN_PLAN",
"resource" : {
"algorithm":"AEAD_AES_256_GCM",
"ciphertext": "...",
"nonce": "...",
"associated_data": ""
}
}
resource.ciphertext 解密后的明文格式
json
{
"sign_plan_id": "01020033210023606914000000007830",
"openid": "oBgbt4sGN3Vl76KF7QgLF37UdjDU",
"service_id": "00109000000000169034377913212082",
"mchid": "2480262201",
"appid": "wxa620f17681c6b0c7",
"merchant_sign_plan_no": "1693882928726",
"merchant_callback_url": "https://payapp.weixin.qq.com/wxvaluetest/common/indirect_bank/2492014071/listennotify",
"plan_id": "01000033210032606914000000007983",
"going_detail_no": 0,
"sign_state": "SIGN_PLAN_CANCEL",
"cancel_sign_time": "2023-09-05T10:28:50+08:00",
"cancel_sign_type": "REVOKE_SERVICE",
"cancel_reason": "",
"plan_name": "瑜伽课5节",
"plan_over_time": "2024-07-02T00:00:00+08:00",
"total_origin_price": 1000,
"deduction_quantity": 5,
"total_actual_price": 500,
"signed_detail_list": [
{
"plan_detail_no": 1,
"original_price": 200,
"plan_discount_description": "享受五折",
"actual_price": 100,
"plan_detail_state": "NOT_USED",
"order_id": "",
"merchant_plan_detail_no": "1693882928727",
"plan_detail_name": "服务费1",
"actual_pay_price": 0,
"use_time": "",
"complete_time": "",
"cancel_time": ""
}
],
"sign_time": "2023-09-05T11:03:56+08:00"
}
处理程序
js
const { Formatter, Rsa, Aes } = require('wechatpay-axios-plugin')
const {
'wechatpay-nonce': wechatpayNonce,
'wechatpay-serial': wechatpaySerial,
'wechatpay-signature': wechatpaySignature,
'wechatpay-timestamp': wechatpayTimestamp,
} = headers
let code = 'SUCCESS', message = undefined
if (Math.abs(Formatter.timestamp() - wechatpayTimestamp) > MAXIMUM_CLOCK_OFFSET) {
code = 'FAIL'
message = 'Over clock offset'
}
else
if (!Object.hasOwn(platformCertificates, wechatpaySerial)) {
code = 'FAIL'
message = 'platform certificate not exists'
}
else
if (!Rsa.verify(
Formatter.joinedByLineFeed(wechatpayTimestamp, wechatpayNonce, json),
wechatpaySignature,
platformCertificates[wechatpaySerial]
)) {
code = 'FAIL'
message = 'sign mismatched'
}
// do your business
// ...
// ...
const {
id,
create_time,
resource_type,
event_type,
resource: {
ciphertext,
nonce,
associated_data
},
} = JSON.parse(json)
const {
sign_plan_id,
openid,
sub_openid,
service_id,
mchid,
sub_mchid,
appid,
sub_appid,
merchant_sign_plan_no,
merchant_callback_url,
plan_id,
going_detail_no,
sign_state,
cancel_sign_time,
cancel_sign_type,
cancel_reason,
plan_name,
plan_over_time,
total_origin_price,
deduction_quantity,
total_actual_price,
signed_detail_list,
sign_time,
} = JSON.parse(Aes.AesGcm.decrypt(nonce, apiv3Key, ciphertext, associated_data))
// do your business
// ...
// ...
const response = { code, message }
正常应答头(headers)
Status: 200
正常应答报文(body)
json
{
"code": "SUCCESS"
}
注意:
- 同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。 推荐的做法是,当商户系统收到通知进行处理时,先检查对应业务数据的状态,并判断该通知是否已经处理。如果未处理,则再进行处理;如果已处理,则直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。
- 特别提醒:商户系统对于开启结果通知的内容一定要做签名验证,并校验通知的信息是否与商户侧的信息一致,防止数据泄露导致出现“假通知”,造成资金损失。
- 对后台通知交互时,如果微信收到应答不是成功或超时,微信认为通知失败,微信会通过一定的策略定期重新发起通知,尽可能提高通知的成功率,但微信不保证通知最终能成功。(通知频率为60s/次 - 总计11次 )