- 后端
- 金融科技
【免费下载链接】pay
可能是我用过的最优雅的 Alipay/WeChat/Unipay/江苏银行 的支付 SDK 扩展包了
导读
本篇技术指南聚焦 yansongda/pay 扩展包中的抖音(Douyin)通用交易系统退款能力,完整覆盖退款全生命周期:通过Pay::douyin()->refund($order)创建退款、以_action参数分发退款审核、通过query()查询退款单状态,以及处理type=pre_create_refund退款申请回调并构造同步应答。读完本文,你将掌握抖音退款各环节的参数要求、SDK 底层插件调用链与验签原理,能直接在自己的 Laravel / ThinkPHP 等项目中落地可用的退款功能。
一、能力总览:refund() 一个入口,两个动作
抖音通用交易系统的退款能力在 SDK 中统一收敛到refund()方法,通过_action参数在「创建退款」与「退款审核」两个动作之间分发,两个动作均返回Collection:
| 方法名 | 参数 | 返回值 | 分发动作(_action) |
|---|---|---|---|
| refund | array $order | Collection | default(创建退款)/audit(退款审核) |
从源码结构看,这一分发逻辑由 RefundShortcut 实现:它读取$params['_action'],默认值为DouyinAction::REFUND_DEFAULT(即default),命中后返回对应的插件管线;传入audit时走退款审核管线,其他值则抛出PARAMS_SHORTCUT_ACTION_INVALID异常。DouyinAction常量定义在 DouyinAction.php。
refund()的实际入口位于 Douyin Provider,它先派发MethodCalled事件,再通过__call路由到\Yansongda\Pay\Shortcut\Douyin\RefundShortcut,最终由 Artful 管线完成「获取 client_token → 构建请求 → 发送 → 校验响应 → 解析」的全流程。
与支付宝、微信不同,抖音交易系统不提供
cancel()/close()接口,Provider 中对这两个方法直接抛出「抖音不支持 cancel/close API」异常(见 Douyin.php)。
二、创建退款(_action默认)
2.1 最小可用示例
Pay::config($config); $order = [ 'order_id' => 'xxxx', 'out_refund_no' => '202408040747147327R', 'refund_reason' => [ '测试退款', ], // 'notify_url' => 'https://yansongda.cn/douyin/refund/notify', // 选填,默认取配置中的 notify_url ]; $result = Pay::douyin()->refund($order);2.2 订单配置参数
order_id(抖音侧订单号):必填,标识要对哪笔订单发起退款。out_refund_no(商户侧退款单号):必填,商户系统内全局唯一,退款结果查询与回调应答都依赖它。refund_reason[](退款理由):必填,数组类型,具体元素结构(如code/text)按抖音官方文档填充。notify_url(退款结果回调地址):选填。不传时 SDK 自动注入配置中的notify_url;若配置中也为空,则平台使用下单时传入的地址。
其余参数与抖音官方接口无任何差别,SDK 对业务字段一律透传,兼容所有官方能力,完整「请求参数」请参考官方「发起退款」API 文档(/api/trade_basic/v1/developer/refund_create/)。
2.3 源码级解析:notify_url 的注入规则
创建退款由 RefundPlugin 装载,其关键行为:
- 校验 payload:使用
filter_params()过滤后为空则抛出「参数异常: 抖音创建退款,缺少必要的业务参数」(对应异常码PARAMS_NECESSARY_PARAMS_MISSING)。 - 合并
_method = POST、_url = /api/trade_basic/v1/developer/refund_create/。 - notify_url 注入规则:仅当业务方未显式传
notify_url且配置中的notify_url非空时,才把配置值合入 payload——显式传入优先,配置值兜底。
该规则在 RefundPluginTest.php 中有两个方向相反的测试佐证:未传notify_url时自动注入https://yansongda.cn/douyin/notify;显式传入时配置值不被覆盖。测试同时断言创建退款请求不注入app_id字段(业务接口均无该字段)。
创建退款完整管线见 RefundShortcut::defaultPlugins():StartPlugin → ObtainClientTokenPlugin → RefundPlugin → AddPayloadBodyPlugin → AddRadarPlugin → ResponsePlugin → ParserPlugin。其中ObtainClientTokenPlugin负责注入access-token(支持外部传入_access_token,否则自动获取并进程内缓存,缓存 key 为app_id,提前 60 秒过期,见 DouyinTrait::getDouyinClientToken());ResponsePlugin统一校验 HTTP 状态码为 2xx 且响应顶层err_no === 0,非 0 时抛出业务异常(见 ResponsePlugin.php)。
三、退款审核(_action = audit)
3.1 场景说明
用户发起退款申请后,若退款单状态为「待审核」,开发者需通过退款审核接口同步审核结果(同意/拒绝),此时refund()的_action必须为audit。
3.2 代码示例
Pay::config($config); $order = [ 'refund_id' => '7398108028895054107', 'refund_audit_status' => 1, // 1:同意退款;2:拒绝退款 // 'deny_message' => '商品已核销,无法退款', // 拒绝(refund_audit_status 为 2)时必填 '_action' => 'audit', ]; $result = Pay::douyin()->refund($order);3.3 订单配置参数
refund_id(抖音侧退款单号):必填。refund_audit_status(审核状态):必填,1同意退款 /2拒绝退款。deny_message(拒绝原因):refund_audit_status为2(拒绝)时必填。
其余参数与官方接口一致,SDK 全部透传,完整「请求参数」请参考官方「退款审核」API 文档(对应接口/api/trade_basic/v1/developer/refund_audit_callback/)。
3.4 源码级解析:审核插件的透传行为
退款审核由 AuditPlugin 装载,行为特征:
- 空 payload(过滤后)直接抛「参数异常: 抖音退款审核,缺少必要的业务参数」。
- 合并
_method = POST、_url = /api/trade_basic/v1/developer/refund_audit_callback/,业务字段refund_id/refund_audit_status/deny_message原样透传。 - 不注入
app_id、notify_url——AuditPluginTest.php 断言 payload 键值仅为refund_id、refund_audit_status、_method、_url。 - 注意:SDK 对
deny_message仅透传,不做必填强校验(官方服务端校验),拒绝时务必自行带上。
四、查询退款单(query()的 refund 动作)
退款结果的主动查询走query()方法,_action为refund,与创建退款、退款审核相互配合:
Pay::config($config); $order = [ 'out_refund_no' => '202408040747147327R', '_action' => 'refund', ]; $result = Pay::douyin()->query($order);查询退款的参数规则:refund_id(抖音侧退款单号)、out_refund_no(商户侧退款单号)、order_id(抖音侧订单号,按订单查询上限 50 条)三选一必填,其余参数与官方一致,对应接口/api/trade_basic/v1/developer/refund_query/。
底层由 RefundQueryPlugin 装载,同样只合并_method/_url并透传业务字段;QueryPluginTest.php 验证了refund_id、out_refund_no、order_id均可透传且不注入app_id/notify_url。query()的_action分发逻辑见 QueryShortcut,支持order(默认)/cps/refund三种动作。
五、响应退款申请回调(type=pre_create_refund)
5.1 回调机制与同步应答的必要性
用户从交易模板组件发起退款后,平台会向开发者服务端发送type=pre_create_refund的退款申请回调。SDK 统一通过callback()入口处理(验签 + 解析msg),同步应答体由业务方自行构造并返回(详见 接收抖音回调)。
:::warning 注意 若不响应(未按下方格式应答),平台会一直重试并导致用户退款卡单,请务必实现同步应答。 :::
callback()返回的Collection中会包含refund_id、out_refund_no、need_refund_audit、refund_audit_deadline等退款申请信息,其中out_refund_no仅在该退款单已存在时非空,因此业务方需要自行生成商户侧退款单号并据此应答。
5.2 业务方应答示例(Laravel 风格)
// $result = Pay::douyin()->callback($request); // $result 中包含 refund_id、order_id、need_refund_audit、refund_audit_deadline 等退款申请信息 return response()->json([ 'err_no' => 0, 'err_tips' => 'success', 'data' => [ 'out_refund_no' => '202408040747147327R', // 业务方自行生成的商户侧退款单号 'order_entry_schema' => $result->get('order_entry_schema'), ], ]);5.3 应答 JSON 结构
{ "err_no": 0, "err_tips": "success", "data": { "out_refund_no": "202408040747147327R", "order_entry_schema": { "path": "pages/refund/detail", "params": "{\"out_refund_no\":\"202408040747147327R\"}" } } }各字段含义请参考抖音官方「退款申请回调」文档。注意order_entry_schema中的params是字符串化的 JSON(内部再包一层双引号),需按官方格式原样返回。
5.4 源码级解析:callback() 的验签与解析
- 统一回调入口在 Douyin Provider::callback():仅接受
ServerRequestInterface或null,传array会抛出「抖音新交易系统回调需要携带回调头信息以完成验签」异常;传null时通过ServerRequest::fromGlobals()自动获取请求(建议仅在 php-fpm 下使用,swoole 请显式传请求实例)。 - CallbackPlugin 负责:用
douyin_public_key(抖音平台公钥)验签 → 校验 body 顶层type存在且为非空字符串 → 解析msg为Collection并设置NoHttpRequestDirection(不发 HTTP 请求,直接返回解析结果)。 - 验签细节见 DouyinTrait::verifyDouyinTradeSign():取请求头
Byte-Timestamp/Byte-Nonce-Str/Byte-Signature与原始 body,拼成{Byte-Timestamp}\n{Byte-Nonce-Str}\n{原始body}\n三行验签串,以SHA256withRSA校验。必须使用未经解析的原始 body——body 经框架解析/排序后验签必失败,因此请将框架原样的 PSR-7 请求传入。 - 三类回调(
payment支付结果 /refund退款结果 /pre_create_refund退款申请)统一走callback(),SDK 仅校验顶层type存在,业务方需按 body 的type自行分发处理;CallbackPluginTest.php 验证了pre_create_refund回调解析后need_refund_audit等字段可正常读取,篡改签名则抛出InvalidSignException(异常码SIGN_ERROR)。
六、配置与运行环境前提
抖音退款功能依赖的配置项由 DouyinConfig 承载,其中app_id与app_secret为必填(缺失时抛出CONFIG_DOUYIN_INVALID,见 DouyinConfigTest.php),其余常用项:
| 配置键 | 说明 | 是否必填 |
|---|---|---|
| app_id | 抖音小程序 App ID(tt开头) | 必填 |
| app_secret | 应用密钥 | 必填 |
| app_private_key | 应用私钥(发起请求签名用,SHA256-RSA2048) | 视功能需要 |
| douyin_public_key | 抖音平台公钥(回调验签用) | 回调必填 |
| notify_url | 退款结果回调地址(创建退款未传时自动注入) | 选填 |
| mode | 运行模式:PayMode::NORMAL/PayMode::SANDBOX(兼容历史 int 写法0/1/2) | 选填 |
请求侧签名由 DouyinTrait::getDouyinTradeSign() 实现:按{method}\n{uri}\n{timestamp}\n{nonce}\n{body}\n拼串后用应用私钥做 SHA256 签名,产出SHA256-RSA2048 appid="...",nonce_str="...",timestamp="...",key_version=1,signature="..."格式的access-token请求头(注意与回调验签串格式不同,勿混淆)。沙箱模式下请求域名切换为https://open-sandbox.douyin.com(见 Douyin Provider::URL),便于联调。
七、常见问题与注意事项
- 退款申请回调必须同步应答:不按
err_no=0格式应答,平台会持续重试并卡单。 order_entry_schema必须原样透传:callback()返回的 Collection 中已包含该字段,直接$result->get('order_entry_schema')即可,不要自行构造。out_refund_no自行生成:退款申请回调里该字段可能为空,商户侧退款单号由业务方生成并回传。- 回调请求不要手动解析重建:验签依赖原始 body 与
Byte-*请求头,请直接传入框架原样的 PSR-7 请求。 - 拒绝退款时带上
deny_message:SDK 仅透传不校验,缺了会被官方服务端拒绝。 _action非法值会抛异常:refund()只接受default/audit,query()只接受order/cps/refund。
关联文档
- 退款审核(audit)
- 查询退款单(query)
- 接收抖音回调与验签说明
- 确认回调应答(success)
- 抖音更多方便的插件
- 后端
- 金融科技
【免费下载链接】pay
可能是我用过的最优雅的 Alipay/WeChat/Unipay/江苏银行 的支付 SDK 扩展包了
相关推荐
Yansongda Pay 抖音小程序退款实战:退款创建、退款审核与退款申请回调完整指南
Yansongda Pay 抖音小程序退款实战:退款创建、退款审核与退款申请回调完整指南 本篇指南以 Yansongda Pay 开源支付 SDK 中抖音通用交
金融科技后端WeiXinMPSDK 微信支付退款实战:TenPayOldV3.Refund 申请退款与 RefundNotifyUrl 回调解密全流程
WeiXinMPSDK 微信支付退款实战:TenPayOldV3.Refund 申请退款与 RefundNotifyUrl 回调解密全流程 本文基于 WeiXi
后端即时通讯金融科技yansongda/pay 翼支付(Bestpay)退款实战指南:refund 方法、订单参数与异步退款回调全流程解析
yansongda/pay 翼支付(Bestpay)退款实战指南:refund 方法、订单参数与异步退款回调全流程解析 本文围绕 yansongda/pay 开
后端金融科技
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考