news 2026/10/6 2:39:26

yansongda/pay 微信关闭订单(close)实战指南:普通订单、合单关闭与源码路由解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
yansongda/pay 微信关闭订单(close)实战指南:普通订单、合单关闭与源码路由解析
  • 金融科技
  • 后端

【免费下载链接】pay

可能是我用过的最优雅的 Alipay/WeChat/Douyin/Unipay/江苏银行 的支付 SDK 扩展包了

项目地址:https://gitcode.com/gh_mirrors/pa/pay
点击查看免费下载

导读

本文聚焦 yansongda/pay(本仓库即该项目源码)中Pay::wechat()->close($order)关闭微信支付订单的完整用法,覆盖 JSAPI、App、H5、小程序、Native 五类普通订单以及合单订单的关闭场景。文章不仅给出可直接运行的代码示例,还会结合CloseShortcut、WechatAction常量与各场景ClosePlugin源码,讲清_action路由规则、必传参数校验、服务商模式差异与插件调用链,帮助读者在真实项目中安全、准确地关闭微信支付订单。


一、方法概览

微信支付关闭订单对应快捷方法如下(摘自 web/docs/v3/wechat/close.md):

方法名参数返回值
closearray $ordernull

从当前仓库源码看,src/Provider/Wechat.php 中close()方法的实际签名是public function close(array $order): Collection|Rocket:它会先派发MethodCalled事件(便于日志与埋点),再通过__call('close', [$order])走 Shortcut 路由,最后返回一个空的Collection实例。因此实际调用中你无需关心返回值,关单结果以微信服务端返回为准。

该方法同时覆盖两类场景:

  1. 关闭普通订单:通过_action指定 JSAPI / App / H5 / 小程序 / Native 五种场景;
  2. 关闭合单订单:通过combine_out_trade_no+sub_orders参数发起合单关单。

二、关闭普通订单

2.1 最小可用示例

Pay::config($config); $order = [ 'out_trade_no' => '1514027114', // '_action' => 'jsapi', // jsapi 关单,默认 // '_action' => 'app', // app 关单 // '_action' => 'combine', // 合单关单 // '_action' => 'h5', // h5 关单 // '_action' => 'miniapp', // 小程序关单 // '_action' => 'native', // native 关单 ]; $result = Pay::wechat()->close($order);

要点说明:

  • out_trade_no为必传参数:它是商户系统内部订单号,是关闭订单的唯一业务标识。底层插件在装载时会先校验该参数,缺失会直接抛出参数异常(详见下文“参数校验”一节)。
  • 不传_action时默认按 JSAPI 关单:在 src/Shortcut/Wechat/CloseShortcut.php 中,$params['_action'] ?? WechatAction::CLOSE_DEFAULT默认值为'default',而defaultPlugins()返回的就是 JSAPI 的插件链,与文档注释“jsapi 关单,默认”一致。
  • 所有“客观参数”由扩展包自动补齐:如mchid(直连商户)或sp_mchid/sub_mchid(服务商模式)等请求体字段均由各场景ClosePlugin根据配置自动注入,你只需传入订单类主观参数。

2.2 订单配置参数

所有订单配置参数和官方无任何差别,兼容所有功能,所有参数请参考以下 API 查看「请求参数」一栏:

  • JSAPI订单
  • APP订单
  • 合单订单
  • H5订单
  • 小程序订单
  • Native订单

关于miniapp的提醒:文档注释中写的是'_action' => 'miniapp',但当前仓库源码里对应常量是WechatAction::CLOSE_MINI = 'mini'(见 src/Action/WechatAction.php),CloseShortcut的match也仅匹配'mini'。实际开发中请传'_action' => 'mini',若传'miniapp'会落入默认分支并抛出InvalidParamsException。


三、关闭合单订单

3.1 示例一:显式合单参数

Pay::config($config); $order = [ 'combine_out_trade_no' => '1514027114', 'sub_orders' => '123456' ]; $result = Pay::wechat()->close($order);

3.2 示例二:显式_action指定合单

//$order = [ // 'out_trade_no' => '1514027114', // 'sub_orders' => '123456', // '_action' => 'combine', //]; $result = Pay::wechat()->close($order);

合单关单的核心机制:在 src/Shortcut/Wechat/CloseShortcut.php 中,只要参数里出现combine_out_trade_no或sub_orders中的任意一个,CloseShortcut就会优先自动路由到合单关单插件链,无需再显式传_action => 'combine'。这一分支判断在_action解析之前执行,因此两种写法效果等价。

3.3 订单配置参数

合单关单同样保持与官方接口完全一致的参数语义,combine_out_trade_no、sub_orders等字段会原样透传给请求体,所有参数请参考这里,查看「请求参数」一栏。

按官方接口语义,sub_orders应为子订单信息数组;文档示例中的'123456'仅为示意,实际项目中请按官方「请求参数」要求组装为合法结构。


四、_action路由与插件调用链源码解析

关闭订单的入口是 src/Shortcut/Wechat/CloseShortcut.php 中getPlugins()的分发逻辑,其路由规则与 src/Action/WechatAction.php 中定义的动作常量一一对应:

_action值常量场景对应插件
不传(默认)CLOSE_DEFAULT = 'default'JSAPI 关单Jsapi\ClosePlugin
jsapiCLOSE_JSAPI = 'jsapi'JSAPI 关单Jsapi\ClosePlugin
appCLOSE_APP = 'app'App 关单App\ClosePlugin
h5CLOSE_H5 = 'h5'H5 关单H5\ClosePlugin
miniCLOSE_MINI = 'mini'小程序关单Mini\ClosePlugin
nativeCLOSE_NATIVE = 'native'Native 关单Native\ClosePlugin
combine(或参数含combine_out_trade_no/sub_orders)CLOSE_COMBINE = 'combine'合单关单Combine\ClosePlugin

每种动作最终返回一组插件链,以 JSAPI 为例(同样适用于 App、H5、小程序、Native、合单,仅中间的业务插件不同):

StartPlugin::class, // 请求生命周期开始 JsapiClosePlugin::class, // 组装关单请求 URL 与请求体 AddPayloadBodyPlugin::class, // 序列化请求体 AddPayloadSignaturePlugin::class, // 生成微信支付 V3 签名 AddRadarPlugin::class, // 平台证书管理(验签公钥等) VerifySignaturePlugin::class,// 校验响应签名 ResponsePlugin::class, // 统一响应处理 ParserPlugin::class, // 解析最终结果

该插件链可通过 tests/Shortcut/Wechat/CloseShortcutTest.php 中的testDefault()、testCombine()、testCombineParams()等用例逐一验证。若传入不支持的_action(如'virtual'、'foo'),match会落入default分支,抛出InvalidParamsException,错误码为Exception::PARAMS_SHORTCUT_ACTION_INVALID(测试用例testVirtual()、testFoo()已覆盖此行为)。


五、底层 ClosePlugin 实现细节

5.1 普通关单:Jsapi / App / Mini 插件

以 src/Plugin/Wechat/V3/Pay/Jsapi/ClosePlugin.php 为例(App 与 Mini 的实现完全一致):

  1. 校验out_trade_no:从rocket->getPayload()取出out_trade_no,为空则抛出InvalidParamsException(错误码PARAMS_NECESSARY_PARAMS_MISSING),提示信息形如“Jsapi 关闭订单,参数缺少out_trade_no”。
  2. 组装请求端点:
    • 直连商户:POST /v3/pay/transactions/out-trade-no/{out_trade_no}/close
    • 服务商模式:POST /v3/pay/partner/transactions/out-trade-no/{out_trade_no}/close
  3. 按模式注入商户号:
    • 直连模式:请求体只含mchid(取自配置);
    • 服务商模式(Pay::MODE_SERVICE === $config->getMode()):请求体为sp_mchid(配置)与sub_mchid(优先取请求参数中的sub_mchid,缺省回落到配置的sub_mchid)。

从源码结构看,H5、Native 的ClosePlugin与上述实现保持一致,均校验out_trade_no并请求同一关单端点(对应文件位于 src/Plugin/Wechat/V3/Pay/H5/ClosePlugin.php 与 src/Plugin/Wechat/V3/Pay/Native/ClosePlugin.php)。

5.2 合单关单:Combine 插件

src/Plugin/Wechat/V3/Pay/Combine/ClosePlugin.php 的处理略有不同:

  1. 校验combine_out_trade_no:缺失时抛出“合单关单,参数缺少combine_out_trade_no”的InvalidParamsException。
  2. 组装请求端点:POST /v3/combine-transactions/out-trade-no/{combine_out_trade_no}/close(直连与服务商模式 URL 相同)。
  3. 自动注入combine_appid:优先取请求参数中的combine_appid,缺省回落为配置中的mp_app_id(公众号 appid)。
  4. 参数清理:通过exceptPayload('combine_out_trade_no')将combine_out_trade_no从最终请求体中剔除(它只用于拼接 URL),而sub_orders等其他字段原样保留透传。

六、参数校验与异常处理小结

关闭订单过程中可能出现的两类核心异常(均由 src/Exception/Exception.php 定义错误码):

场景异常信息错误码
普通关单缺out_trade_no“Jsapi/App/Mini 关闭订单,参数缺少out_trade_no”PARAMS_NECESSARY_PARAMS_MISSING
合单关单缺combine_out_trade_no“合单关单,参数缺少combine_out_trade_no”PARAMS_NECESSARY_PARAMS_MISSING
_action非法“不支持的_action [...]”PARAMS_SHORTCUT_ACTION_INVALID

建议在实际业务中先通过查询订单(Pay::wechat()->find())确认订单状态再决定是否关单,避免对已支付或已关闭的订单重复发起关闭请求。


七、总结

Pay::wechat()->close($order)以极简的数组入参覆盖了微信支付 V3 全部六种关单场景:默认 JSAPI 关单、通过_action切换 App/H5/小程序/Native,以及通过combine_out_trade_no/sub_orders自动路由合单关单。其背后是CloseShortcut的轻量路由 + 各场景ClosePlugin的端点组装,配合插件链自动完成签名、验签、证书与响应解析。理解 src/Shortcut/Wechat/CloseShortcut.php、src/Action/WechatAction.php 与各 ClosePlugin 源码,即可在直连与服务商两种模式下稳定地集成关单能力。

  • 金融科技
  • 后端

【免费下载链接】pay

可能是我用过的最优雅的 Alipay/WeChat/Douyin/Unipay/江苏银行 的支付 SDK 扩展包了

项目地址:https://gitcode.com/gh_mirrors/pa/pay
点击查看免费下载

相关推荐

上一篇:中间人攻击模拟实战指南:基于 Anthropic-Cybersecurity-Skills 的 ARP 欺骗与 TLS 拦截测试
下一篇:MongoDB 冒烟测试套件(Smoke Test Suites)实战指南:面向迭代开发的本地快速回归方案

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/6 2:38:25

【2027最新精品大数据】基于大数据的北京网格化城市管理问题数据 (附源码资料)数据分析,可视化大屏_毕设选题推荐_大数据项目_数据挖掘_毕设指导_Hadoop

💖💖作者:计算机毕业设计江挽 💙💙个人简介:曾长期从事计算机专业培训教学,本人也热爱上课教学,语言擅长Java、微信小程序、Python、Golang、安卓Android等,开发项目包括…

作者头像 李华
网站建设 2026/10/6 2:37:51

teach - SKILL

name: teach description: Teach the user a new skill or concept, within this workspace. disable-model-invocation: true argument-hint: “What would you like to learn about?” category: “education” risk: “safe” source: “community” source_repo: “mattpo…

作者头像 李华
网站建设 2026/10/6 2:37:37

原型设计工具:Penpot、OpenPencil、Open CoDesign、Editable Design

继原型设计工具:Figma、Stitch、Claude Design、Open Design、OpenPencil、AutoFigure-Edit,本文介绍几个AI增加的原型设计工具。 Penpot 官网,开源(GitHub,54.8K Star,3.6K Fork)的设计与原型…

作者头像 李华
网站建设 2026/10/6 2:35:22

Linux -- 进程概念

1.基本概念与操作课本概念:程序的⼀个执⾏实例,正在执⾏的程序等内核观点:担当分配系统资源(CPU时间,内存)的实体。当前:进程 内核数据结构(task_struct) ⾃⼰的程序代码和数据1.1 描述进程--…

作者头像 李华
网站建设 2026/10/6 2:32:38

达梦数据库-报错-13-[-108]:打开重做日志失败

目录 一、环境信息 二、问题描述 三、问题分析 1、重做日志权限 2、重做日志损坏 3、归档容量到达上限,自动清理,需备份归档被删除 四、模拟实验 1、归档配置 2、备份归档多次 3、日志报错 4、线程映射 5、归档配置修改 6、备份归档 五、问…

作者头像 李华