Repository navigation
[文件補充] 虛擬支付:自建 HTTP 回調驗真、query_order 與 refund_order 的完整協議 #1132
Description
Activity
- addedai-processingAI automation is processing this issueAI automation is processing this issue
on Oct 3, 2026 🤖 AI Analysis
No files found
Generated automatically by CodeBuddy CLI headless mode.
- addedai-processedAI automation already processed this issueAI automation already processed this issueand removedai-processingAI automation is processing this issueAI automation is processing this issue
on Oct 3, 2026 JasonCoffeeLab commented
on Oct 3, 2026 AuthorMore actions補充說明:本問題是虛擬支付接入文件的完整性詢問,正文已附官方參考資料,不依賴提交原始碼附件;自動分析的「No files found」尚未回答這些協議問題。
請維護者協助確認以下三項的完整官方文件或第一方範例:
- 自建 HTTP 服務接收 xpay 通知時,GET/POST 的來源驗簽、解密、接收方校驗及應答規則。CloudBase 範例由網關代處理的部分,需要說明自建服務如何完成。
- /xpay/query_order 的完整回應結構、訂單/退款狀態枚舉與最終完成判定。
- /xpay/refund_order 的完整參數、冪等與重試規則、部分退款限制、渠道適用範圍及結果確認方式。
只提供出站 HMAC 範例或接口名稱,仍不足以實作上述完整流程。若已有對應詳節,請提供確切連結、版本及適用接入方式;若這些問題應交由其他微信官方技術支援管道處理,也請指出正確入口。謝謝。
这三项的完整契约在微信开放文档。本仓库的虚拟支付参考面向微信云开发 / CloudBase:回调由平台或集成中心网关验签后再交给业务代码。自建 HTTP 接收端要自己做小程序消息推送,不能把出站
pay_sig当成入站验签。1. 自建 HTTP 接收通知
配置入口:小程序管理后台 → 开发 → 开发管理 → 消息推送。协议:
https://developers.weixin.qq.com/miniprogram/dev/framework/server-ability/message-push.html
- GET(提交配置时的 URL 校验):query 带
signature、timestamp、nonce、echostr。将 Token、timestamp、nonce做字典序排序后拼接,SHA1,与signature一致则原样返回echostr。这一步没有 body,也没有pay_sig。 - POST(发货 / 退款 / iOS 退款问询):明文模式直接读 body。安全模式(推荐)用 query 里的
msg_signature验签,用 EncodingAESKey 解密,解密后校验 AppID。兼容模式两种都有,不建议。字节布局以该页示例为准。 - Token 只参与签名,EncodingAESKey 只参与消息体加解密。二者都不是虚拟支付 AppKey。出站
pay_sig是 AppKey 对uri + '&' + post_body的 HMAC-SHA256,只用于本服务调用微信/xpay/*以及 C 端requestVirtualPayment。 - 事件字段见企业接入说明:https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/virtual-payment.html
- 发货成功应答:
<xml><ErrCode>0</ErrCode><ErrMsg><![CDATA[success]]></ErrMsg></xml>,否则平台重试,最多 15 次。重放用wx_order_id做幂等。xpay_subscribe_ios_refund_query_notify须在 3 秒内返回result_code(0 建议退款,1 拒绝)。
CloudBase 集成中心示例和微信云开发云函数收到的是网关或平台已经验签的事件,业务代码不要再套
pay_sig,也不要自己做消息推送 AES。微信云托管走内网推送;公网开启时只处理带x-wx-source的请求:https://developers.weixin.qq.com/miniprogram/dev/wxcloudservice/wxcloudrun/src/guide/weixin/push.html2.
POST /xpay/query_orderhttps://developers.weixin.qq.com/miniprogram/dev/server/API/VirtualPayment/api_query_order
POST https://api.weixin.qq.com/xpay/query_order?access_token=ACCESS_TOKEN&pay_sig=PAY_SIGpay_sig的 uri 是/xpay/query_order(不带 query),body 必须与实际发出的原始 JSON 一致。签名算法:https://developers.weixin.qq.com/minigame/introduction/commercialization/virtual-payment/signature.htmlBody:
openid、env(0 正式 / 1 沙箱)必填;order_id(即 outTradeNo)与wx_order_id二选一。本接口不支持云调用。order.status是数字,不是"paid":status 含义 2 已支付,待发货(查单兜底以此发货) 4 已发货 5 已经退款 7 退款失败 8 用户退款完成 order_type:0 普通支付,1 普通退款,7 iOS 支付,8 iOS 退款。支付单的left_fee是剩余可退金额(分)。渠道单号、结算状态等其余字段以该页Res.order为准。3.
POST /xpay/refund_orderhttps://developers.weixin.qq.com/miniprogram/dev/server/API/VirtualPayment/api_refund_order
POST https://api.weixin.qq.com/xpay/refund_order?access_token=ACCESS_TOKEN&pay_sig=PAY_SIG文档写明:此接口只是启动退款任务成功。
errcode = 0不是退款完成。返回里会有refund_order_id、refund_wx_order_id、pay_order_id、pay_wx_order_id。完成后要再查query_order,直到order.status为 5 或 8,或收到xpay_refund_notify。status 7 是退款失败。可对支付后 365 天内的订单发起。支付 180 天内退款平台退还手续费,超过 180 天不退手续费。iOS 订单不能走开发者主动退款,用户在 App Store 申请,Apple 通过后推
xpay_refund_notify。字段 必填 说明 openid 是 下单时的用户 openid order_id / wx_order_id 二选一 outTradeNo,或微信侧支付单号 refund_order_id 是 长度 [8,32],仅字母、数字、 _、-。同一次退款重试用同一值left_fee 是 当前剩余可退金额(分),必须来自最新的 query_orderrefund_fee 是 本次金额(分),须满足 (0, left_fee]。小于left_fee即部分退款biz_meta 是 长度 [0,1024],查单时原样返回 refund_reason 是 0暂无描述,1产品问题,2售后问题,3用户主动退款,4价格问题,5其他req_from 是 1人工客服,2用户自己发起,3其他env 是 0正式,1沙箱pay_sig的 uri 是/xpay/refund_order。该页调用 URL 只带access_token和pay_sig。268490014:退款进行中,用相同参数稍后重试,不要换refund_order_id。268490016:left_fee与实际不符,重新查单后再发起。268490013:禁止对已核销的单退款。以上页面就是当前第一方契约。虚拟支付参考会补上「网关已验签 vs 自建消息推送」的边界,以及查单状态、退款受理与完成的区别;完整字段表仍以微信文档为准,避免和接口改版漂移。
- GET(提交配置时的 URL 校验):query 带
JasonCoffeeLab commented
on Oct 4, 2026 AuthorMore actions謝謝補充連結及 CloudBase 與自建接收端的邊界。接入情境是企業主體、阿里雲自建 HTTP 服務,不經 CloudBase 集成中心網關。針對上一則回覆,還想確認以下三項可直接實作與驗證的契約:
- 自建接收端的入站驗簽、解密與各事件應答
請提供適用此接入方式的第一方規格或完整範例,涵蓋 GET URL 校驗、POST 明文/安全模式的簽名參數位置、簽名原串、解密封裝、AppID/接收方校驗,以及驗證用測試向量(範例輸入與預期簽名、解密結果)。也請分別列明發貨、退款通知及 iOS 退款問詢的 HTTP 狀態、Content-Type、完整應答封裝、逾時與重送規則。
對照我手上的官方文件截圖,發貨通知在 JSON 推送模式下有 JSON 應答範例,因此上一則的 XML 應答似乎需要標明適用模式,不能直接作為所有模式的通用應答。iOS 退款問詢的應答也列有 evidence/result_info,只有 result_code 尚不足以組出完整回應。請協助確認這些欄位的必填條件、層級與完整範例,以及相關文件的版本和適用事件。
- /xpay/query_order 的完整回應與最終退款判定
請提供完整回應 schema(欄位型別、必填/可選、空值及錯誤回應)、order_type/order.status 的完整枚舉與狀態轉移,並明確說明發起退款後應查支付單還是退款單:refund_order_id、refund_wx_order_id、pay_order_id、pay_wx_order_id 各自如何帶入查單參數?
尤其請區分部分退款與全額退款時,支付單及每筆退款單的狀態和金額如何變化,以及 5/8 是否在不同 order_type、渠道下都代表同一種最終完成結果。
官方退款通知截圖列明 RetCode=0 為成功、非零為失敗。因此,上一則提到「或收到 xpay_refund_notify」時,是否應再校驗 RetCode 並關聯對應退款單,才能判定成功?請提供成功、失敗及通知與查單結果暫時不一致時的第一方處理範例,避免只以通知到達就判定退款完成。
- /xpay/refund_order 的冪等、重試、部分退款與併發契約
同一 refund_order_id 重複請求時,哪些參數必須保持一致?相同鍵但金額或其他參數不同、請求逾時而結果未知、退款仍處理中、退款已成功或失敗,各自會返回什麼結果,應繼續查單、以原鍵重試,還是建立新的退款請求?
另外,請說明多次部分退款的累計限制,以及同一支付單併發退款時 left_fee 的一致性要求。特別是遇到 268490016 後重新查單取得新的 left_fee,是否能沿用原 refund_order_id 更新該參數?這與「同一次退款用相同參數重試」的適用邊界是什麼?也請補充 268490014、268490013 及其他相關失敗碼的可重試性和後續處理規則。
上述差異可能涉及文件版本、推送格式或接入模式,請提供對應的確切章節、適用版本與第一方請求/回應範例,協助確認自建服務應採用的規格。謝謝。
上一则有两处说宽了,按企业接入文档更正:
- XML 的
ErrCode只适用于推送数据格式选了 XML 的配置。JSON 推送要回 JSON。不能把 XML 当成所有模式的通用应答。 xpay_refund_notify到达不等于退款成功。请求体里的RetCode才是退款结果,响应里的ErrCode只表示这条推送已收下。
下面只写公开页面里有的内容。页面没有版本号,以当前正文的章节为准。自建 HTTP、不经 CloudBase 网关时,适用的是「开发者服务器接收消息推送」,不是云函数那一节。
1. 入站验签、解密与应答
两份第一方页面:
- 自建服务器的验签、解密和测试向量:https://developers.weixin.qq.com/miniprogram/dev/framework/server-ability/message-push.html (章节「开发者服务器接收消息推送」)
- xpay 事件字段和应答格式:https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/virtual-payment.html (章节 2.4)
消息推送页的测试向量用的是
debug_demo,不是 xpay 事件。算法和期望值可以直接拿来验证验签、解密实现;xpay 的字段表在 2.4,不要把debug_str当成发货字段。GET URL 校验(该页原文示例)
Token=AAAAAhttps://www.qq.com/revice?signature=f464b24fc39322e44b38aa78f5edd27bd1441696&echostr=4375120948345356249×tamp=1714036504&nonce=1514711492原串:Token、
timestamp、nonce字典序排序后拼接,即15147114921714036504AAAAA,SHA1 应为f464b24fc39322e44b38aa78f5edd27bd1441696。通过后响应体原样返回echostr:4375120948345356249。没有 body,也没有pay_sig。POST 明文模式(该页原文示例,数据格式 JSON)
URL:
https://www.qq.com/recive?signature=899cf89e464efb63f54ddac96b0a0a235f53aa78×tamp=1714037059&nonce=486452656签名参数在 query 的
signature。原串同样是 Token、timestamp、nonce 字典序拼接:1714037059486452656AAAAA,SHA1 应为899cf89e464efb63f54ddac96b0a0a235f53aa78。body 即明文事件。POST 安全模式(该页原文示例)
EncodingAESKey=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AppIDwxba5fad812f8e6fb9。URL 带
signature、timestamp、nonce、openid、encrypt_type=aes、msg_signature。验签用msg_signature,不要用signature。原串是 Token、timestamp、nonce、body 内Encrypt四个字段字典序拼接后 SHA1,期望046e02f8204d34f8ba5fa3b1db94908f3df2e9b3。解密:
AESKey = Base64_Decode(EncodingAESKey + "="),AES-256-CBC、PKCS#7。明文布局random(16B) + msg_len(4B, 网络字节序) + msg + appid。该示例期望random=a8eedb185eb2fecf,msg_len=167,appid=wxba5fad812f8e6fb9,msg为debug_demo的 JSON。appid必须等于本小程序。加密回包的测试向量也在同一节:明文
{"demo_resp":"good luck"},random=707722b803182950,msg_len=25,期望Encrypt=ELGduP2YcVatjqIS+eZbp80MNLoAUWvzzyJxgGzxZO/5sAvd070Bs6qrLARC9nVHm48Y4hyRbtzve1L32tmxSQ==,MsgSignature=1b9339964ed2e271e7c7b6ff2b0ef902fc94dea1。这是回包加密算法的向量,不是 xpay 业务应答。xpay 应答(企业接入 2.4「推送响应格式说明」)
格式必须和后台选定的推送数据格式一致。发货、退款通知、投诉的返回参数表相同:
ErrCode必填(0 成功,其他失败),ErrMsg可选。这里的ErrCode表示开发者已收下推送,不是RetCode。- XML 推送:
<xml><ErrCode>0</ErrCode><ErrMsg><![CDATA[success]]></ErrMsg></xml> - JSON 推送:
{"ErrCode":0,"ErrMsg":"success"} - 空串或
success等价于ErrCode=0
格式不对时最多重试 15 次。退款通知等带
RetryTimes的事件写明:从 0 开始,间隔 2、4、8、16…,最多 15 次。除 iOS 问询的 3 秒外,这两页没有写服务器处理超时秒数,也没有写应答的 HTTP 状态码和Content-Type。不能从示例补一个。安全模式的回包加密:消息推送页写明,接口没有特定回包要求时回空串或
success且不加密;其他回包内容需加密。2.4 的ErrCode示例是按 XML/JSON 给出的明文,没有「安全模式下发货 ErrCode 回包」的专用向量。iOS 问询回包带业务字段,按消息推送规则属于要加密的回包,加密算法用上面的测试向量自测。iOS 退款问询
事件
xpay_subscribe_ios_refund_query_notify。企业接入 2.4 的应答结构是IosRefundQueryResponse:字段 说明 result_code 0 建议退款,1 拒绝退款 result_info 结果描述 evidence 决策凭据。企业接入页标注必填 云函数回调页把必填写得更明确,并给出目前唯一的完整嵌套示例(这是云函数
return,不是单独的 HTTP Content-Type 说明):https://developers.weixin.qq.com/miniprogram/dev/wxcloudservice/wxcloud/guide/wechatpay/virtual-payment-callback.htmlresult_code必填,evidence必填,result_info选填。外层仍是ErrCode/ErrMsg,内层是IosRefundQueryResponse。必须 3 秒内返回;连续 3 次未应答,平台向 Apple 返回「不确定」。Apple 最终是否退款仍以之后的xpay_refund_notify为准,并且要看RetCode。2.
query_order与退款最终判定https://developers.weixin.qq.com/miniprogram/dev/server/API/VirtualPayment/api_query_order
该页的
Res.order表就是已发布的成功响应字段和类型,并带有status、order_type的完整枚举。成功时还有errcode、errmsg。错误响应是errcode/errmsg,错误码表在同一页。这张表没有标响应字段必填或可选,没有定义空值和缺省,也没有状态转移图。不能在这之外再补一套 schema。
order.status:0 初始化,1 创建成功,2 已支付待发货,3 发货中,4 已发货,5 已经退款,6 已关闭,7 退款失败,8 用户退款完成,9 回收广告金完成,10 分账回退完成。order_type:0 普通虚拟支付,1 普通退款,7 苹果 iOS 支付,8 苹果 iOS 退款。上一则把 5 和 8 都写成最终完成,这超出了枚举原文。5 的描述是「已经退款」,8 是「用户退款完成」。页面没有说它们在所有
order_type和渠道下是同一种结果,也没有部分退款与全额退款的状态、金额变化示例。已写明的金额关系只有:支付单的left_fee是退款后剩余可退金额(分);订单类型为退款单时refund_fee是该笔退款金额(分)。查哪一张单:
refund_order页写的是「启动后需要调用 query_order 来查询退款单状态,等状态变成退款完成后即为最终成功」。它没有把refund_order_id、refund_wx_order_id、pay_order_id、pay_wx_order_id映射到query_order的order_id或wx_order_id。通知里的MchRefundId、WxRefundId、MchOrderId、WxOrderId也没有对照表。所以不能把上一则的 status 判断当成已经核实的查单入参。RetCode需要校验。企业接入 2.4 对xpay_refund_notify写明:RetCode0 为成功,非 0 为失败,RetMsg为失败原因。应用MchRefundId/WxRefundId对上该笔退款。RetCode非 0 是失败通知,不是成功。通知和查单暂时不一致时,这两页没有处理示例。查单是文档指定的状态接口;不能只因为通知到达就记退款完成。3.
refund_order的幂等、重试、部分退款https://developers.weixin.qq.com/miniprogram/dev/server/API/VirtualPayment/api_refund_order
已写明的只有这些:
errcode=0只表示退款任务启动成功。268490014:退款操作进行中,稍后可以使用相同参数重试。这一句只覆盖「仍在处理中」。268490016:left_fee与实际不符,通过query_order确认。没有写确认后能否沿用原refund_order_id只改left_fee。268490013:禁止对核销状态的单退款。没有「稍后重试」。- 单次
refund_fee须在(0, left_fee],所以可以小于剩余额。left_fee必须来自查单。支付后 365 天内可发起;180 天内退手续费,超过 180 天不退手续费。 268490004的说明范围是赠送、代币支付和广告金充值,不要把它当成refund_order的成功幂等码。
同一
refund_order_id在金额或其他参数不同、请求超时结果未知、退款已成功或已失败时分别返回什么,应该查单、原键重试还是新建退款单,多次部分退款除left_fee以外的累计上限,以及同一支付单并发退款时的锁规则,接口页都没有写。「014 的相同参数」不能外推成「016 之后换left_fee仍用原键」。这些缺口不在 CloudBase 网关侧,自建 HTTP 也没有另一份 CloudBase 契约。需要微信开放平台按「企业主体、虚拟支付、开发者服务器消息推送」确认。提问入口是微信开放社区:https://developers.weixin.qq.com/community/develop/question
本仓库的虚拟支付参考会改成和上面一致:应答跟 XML/JSON 配置走,退款完成要看
RetCode和退款单查询,未发布的单号映射和幂等矩阵不再写成已确定规则。- XML 的
希望補齊微信小程序虛擬支付(xpay)的服務端接入文件,尤其是企業主體、自建 HTTP 服務接收通知的情況。
已閱讀以下官方資料:
資料已說明出站 HMAC 簽名與部分請求範例。仍希望提供下列完整官方契約,或指向適用的第一方 SDK/範例與確切文件章節:
1. 自建 HTTP 接收通知的驗真與解密
官方 wechat-notify 範例註明集成中心網關已完成驗簽/解密。因此需要確認沒有該網關時,自建接收端應完成哪些安全步驟;出站 pay_sig 算法本身不足以說明入站驗真。
2. POST /xpay/query_order
已有 openid、env、order_id 的請求範例。希望補齊完整必填參數、鑑權位置、回應結構、訂單與退款狀態枚舉、各種訂單 ID 對應關係,以及錯誤碼。尤其需要明確哪些狀態可判定支付或退款已最終完成。
3. POST /xpay/refund_order
希望補齊請求/回應欄位、退款冪等鍵及重試規則、部分與累計退款限制、支付渠道適用範圍、錯誤碼,以及發起退款後如何透過通知或查單確認最終結果。需要區分「退款任務受理成功」與「退款完成」。
若上述內容已有官方文件,請提供對應詳節及適用版本;若目前指引只覆蓋部分接入方式,也希望明確標註其範圍,避免把網關代處理或其他支付產品的規則套到自建 xpay 接收端。