feat: 完善支付通道和收款监听链路

新增 ChannelNotifyPayloadInterface 等支付插件通知契约,规范 pay_no 定位和插件返回校验。

新增微信、支付宝、收钱吧、Postar 个人收款插件适配,支持余额识别与备注识别。

新增 receipt-watcher 后端进程、Redis 队列 job 和平台事件监听,覆盖收款流水通知、商户通知、退款派发、转账派发与清算完成。

补齐个人收款监听相关系统配置、仓储、服务费冻结明细、订单后台操作和通道测试能力。

重构支付单创建、回调、费用、风控、结算和通道统计链路,统一状态流转与幂等处理。
This commit is contained in:
技术老胡
2026-05-11 16:28:48 +08:00
parent 0e5de50337
commit fd1f53f2ee
136 changed files with 14416 additions and 3992 deletions

View File

@@ -44,43 +44,15 @@ final class AuthConstant
*/
public const CREDENTIAL_STATUS_ENABLED = 1;
/**
* API 签名类型MD5。
*/
public const API_SIGN_TYPE_MD5 = 0;
/**
* API 签名类型SHA256WithRSA。
*/
public const API_SIGN_TYPE_SHA256_WITH_RSA = 1;
/**
* API 签名类型名称MD5。
*/
public const API_SIGN_NAME_MD5 = 'MD5';
/**
* API 签名类型名称:SHA256WithRSA。
* API 签名类型名称RSA。
*/
public const API_SIGN_NAME_SHA256_WITH_RSA = 'SHA256WithRSA';
/**
* API 签名类型归一化名称SHA256WITHRSA。
*/
public const API_SIGN_NORMALIZED_SHA256_WITH_RSA = 'SHA256WITHRSA';
/**
* 获取签名类型映射。
*
* @return array<int, string> 签名类型名称表
*/
public static function signTypeMap(): array
{
return [
self::API_SIGN_TYPE_MD5 => self::API_SIGN_NAME_MD5,
self::API_SIGN_TYPE_SHA256_WITH_RSA => self::API_SIGN_NAME_SHA256_WITH_RSA,
];
}
public const API_SIGN_NAME_RSA = 'RSA';
/**
* 获取接口凭证状态映射。
@@ -108,5 +80,3 @@ final class AuthConstant
];
}
}

View File

@@ -0,0 +1,92 @@
<?php
namespace app\common\constant;
/**
* ePay 协议固定值。
*/
final class EpayProtocolConstant
{
/**
* 旧版 ePay 协议版本。
*/
public const VERSION_V1 = 'v1';
/**
* 新版 ePay 协议版本。
*/
public const VERSION_V2 = 'v2';
/**
* 页面跳转提交。
*/
public const SUBMIT_TYPE_PAGE = 'page';
/**
* API 直连提交。
*/
public const SUBMIT_TYPE_API = 'api';
/**
* 电脑浏览器。
*/
public const DEVICE_PC = 'pc';
/**
* 手机浏览器。
*/
public const DEVICE_MOBILE = 'mobile';
/**
* 手机 QQ 内浏览器。
*/
public const DEVICE_QQ = 'qq';
/**
* 微信内浏览器。
*/
public const DEVICE_WECHAT = 'wechat';
/**
* 支付宝客户端。
*/
public const DEVICE_ALIPAY = 'alipay';
/**
* 仅返回支付跳转 URL。
*/
public const DEVICE_JUMP = 'jump';
/**
* V1 支持的设备类型。
*
* @return array<int, string>
*/
public static function v1Devices(): array
{
return [
self::DEVICE_PC,
self::DEVICE_MOBILE,
self::DEVICE_QQ,
self::DEVICE_WECHAT,
self::DEVICE_ALIPAY,
self::DEVICE_JUMP,
];
}
/**
* V2 支持的设备类型。
*
* @return array<int, string>
*/
public static function v2Devices(): array
{
return [
self::DEVICE_PC,
self::DEVICE_MOBILE,
self::DEVICE_QQ,
self::DEVICE_WECHAT,
self::DEVICE_ALIPAY,
];
}
}

View File

@@ -35,6 +35,11 @@ final class EventConstant
*/
public const PAYMENT_PAY_ORDER_TIMEOUT = 'payment.pay_order.timeout';
/**
* 网页流水监听相关配置已变更。
*/
public const PAYMENT_RECEIPT_WATCHER_CONFIG_CHANGED = 'payment.receipt_watcher.config.changed';
/**
* 退款单进入成功态。
*/

View File

@@ -0,0 +1,63 @@
<?php
namespace app\common\constant;
/**
* 商户资金冻结常量。
*
* 冻结记录是提现、退款、通知等高风险动作的统一风控依据。
*/
final class FundFreezeConstant
{
/**
* 支付订单冻结。
*/
public const TYPE_PAY_ORDER = 1;
/**
* 人工指定金额冻结。
*/
public const TYPE_MANUAL_AMOUNT = 2;
/**
* 支付平台服务费预冻结。
*/
public const TYPE_PAY_FEE = 3;
/**
* 冻结中。
*/
public const STATUS_ACTIVE = 1;
/**
* 已解冻。
*/
public const STATUS_RELEASED = 2;
/**
* 获取冻结类型文案。
*
* @return array<int, string> 冻结类型文案
*/
public static function typeMap(): array
{
return [
self::TYPE_PAY_ORDER => '支付订单',
self::TYPE_MANUAL_AMOUNT => '人工指定金额',
self::TYPE_PAY_FEE => '支付平台服务费',
];
}
/**
* 获取冻结状态文案。
*
* @return array<int, string> 冻结状态文案
*/
public static function statusMap(): array
{
return [
self::STATUS_ACTIVE => '冻结中',
self::STATUS_RELEASED => '已解冻',
];
}
}

View File

@@ -43,6 +43,14 @@ final class LedgerConstant
* 转账释放流水。
*/
public const BIZ_TYPE_TRANSFER_RELEASE = 8;
/**
* 风控资金冻结流水。
*/
public const BIZ_TYPE_RISK_FREEZE = 9;
/**
* 风控资金释放流水。
*/
public const BIZ_TYPE_RISK_RELEASE = 10;
/**
* 账务事件的创建动作。
@@ -87,6 +95,8 @@ final class LedgerConstant
self::BIZ_TYPE_TRANSFER_DEDUCT => '转账扣款',
self::BIZ_TYPE_TRANSFER_FEE => '转账手续费',
self::BIZ_TYPE_TRANSFER_RELEASE => '转账释放',
self::BIZ_TYPE_RISK_FREEZE => '风控冻结',
self::BIZ_TYPE_RISK_RELEASE => '风控释放',
];
}
@@ -119,4 +129,3 @@ final class LedgerConstant
}
}

View File

@@ -20,6 +20,16 @@ final class NotifyConstant
*/
public const EVENT_SETTLEMENT_SUCCESS = 'SETTLEMENT_SUCCESS';
/**
* 商户通知成功响应。
*/
public const MERCHANT_SUCCESS_RESPONSE = 'success';
/**
* ePay 通知交易成功状态。
*/
public const EPAY_TRADE_STATUS_SUCCESS = 'TRADE_SUCCESS';
/**
* 异步通知类型。
*/

View File

@@ -0,0 +1,88 @@
<?php
namespace app\common\constant;
/**
* 支付订单后台操作常量。
*
* 操作码是后端与管理前端之间的展示协议,前端只根据本类对应的
* actions 结果渲染按钮,不再自行硬编码订单状态规则。
*/
final class PayOrderActionConstant
{
/**
* 手动补单。
*/
public const MANUAL_SUCCESS = 'manual_success';
/**
* 重新通知商户。
*/
public const RENOTIFY = 'renotify';
/**
* 主动查询上游。
*/
public const ACTIVE_QUERY = 'active_query';
/**
* API 退款。
*/
public const API_REFUND = 'api_refund';
/**
* 手动退款。
*/
public const MANUAL_REFUND = 'manual_refund';
/**
* 冻结订单。
*/
public const FREEZE = 'freeze';
/**
* 解冻订单。
*/
public const UNFREEZE = 'unfreeze';
/**
* 订单未冻结。
*/
public const FREEZE_STATUS_NORMAL = 0;
/**
* 订单已冻结。
*/
public const FREEZE_STATUS_FROZEN = 1;
/**
* 获取后台操作文案。
*
* @return array<string, string> 操作文案映射
*/
public static function actionLabelMap(): array
{
return [
self::MANUAL_SUCCESS => '手动补单',
self::RENOTIFY => '重新通知',
self::ACTIVE_QUERY => '主动查询',
self::API_REFUND => 'API退款',
self::MANUAL_REFUND => '手动退款',
self::FREEZE => '冻结订单',
self::UNFREEZE => '解冻订单',
];
}
/**
* 获取冻结状态文案。
*
* @return array<int, string> 冻结状态文案映射
*/
public static function freezeStatusMap(): array
{
return [
self::FREEZE_STATUS_NORMAL => '正常',
self::FREEZE_STATUS_FROZEN => '已冻结',
];
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace app\common\constant;
/**
* 支付队列名称常量。
*
* 队列名是生产者与消费者之间的协议,不建议写在业务服务或消费者里。
* 新增支付域队列时优先在本类登记,再分别实现投递服务与消费者。
*/
final class PaymentQueueConstant
{
/**
* 商户异步通知队列名。
*/
public const MERCHANT_NOTIFY = 'merchant_notify';
/**
* 退款上游派发队列名。
*/
public const REFUND_DISPATCH = 'refund_dispatch';
/**
* 转账上游派发队列名。
*/
public const TRANSFER_DISPATCH = 'transfer_dispatch';
/**
* 转账上游查单队列名。
*/
public const TRANSFER_QUERY = 'transfer_query';
/**
* 清算自动入账队列名。
*/
public const SETTLEMENT_COMPLETE = 'settlement_complete';
/**
* 网页流水监听通知队列名。
*/
public const RECEIPT_FLOW_NOTIFY = 'receipt_flow_notify';
}

View File

@@ -15,7 +15,7 @@ final class RouteConstant
public const CHANNEL_TYPE_PLATFORM_COLLECT = 0;
/**
* 商户自通道类型。
* 商户自通道类型。
*/
public const CHANNEL_TYPE_MERCHANT_SELF = 1;
@@ -53,7 +53,7 @@ final class RouteConstant
{
return [
self::CHANNEL_TYPE_PLATFORM_COLLECT => '平台代收',
self::CHANNEL_TYPE_MERCHANT_SELF => '商户自',
self::CHANNEL_TYPE_MERCHANT_SELF => '商户自',
];
}
@@ -86,4 +86,3 @@ final class RouteConstant
}

View File

@@ -54,21 +54,21 @@ final class TradeConstant
public const ORDER_STATUS_TIMEOUT = 5;
/**
* 手续费未处理。
* 平台服务费未处理。
*/
public const FEE_STATUS_NONE = 0;
public const SERVICE_FEE_STATUS_NONE = 0;
/**
* 手续费已冻结。
* 平台服务费已冻结。
*/
public const FEE_STATUS_FROZEN = 1;
public const SERVICE_FEE_STATUS_FROZEN = 1;
/**
* 手续费已扣除。
* 平台服务费已扣除。
*/
public const FEE_STATUS_DEDUCTED = 2;
public const SERVICE_FEE_STATUS_DEDUCTED = 2;
/**
* 手续费已释放。
* 平台服务费已释放。
*/
public const FEE_STATUS_RELEASED = 3;
public const SERVICE_FEE_STATUS_RELEASED = 3;
/**
* 清算状态为空。
@@ -142,17 +142,17 @@ final class TradeConstant
}
/**
* 获取手续费状态映射。
* 获取平台服务费状态映射。
*
* @return array<int, string> 手续费状态名称表
* @return array<int, string> 平台服务费状态名称表
*/
public static function feeStatusMap(): array
public static function serviceFeeStatusMap(): array
{
return [
self::FEE_STATUS_NONE => '无',
self::FEE_STATUS_FROZEN => '冻结',
self::FEE_STATUS_DEDUCTED => '已扣',
self::FEE_STATUS_RELEASED => '已释放',
self::SERVICE_FEE_STATUS_NONE => '无',
self::SERVICE_FEE_STATUS_FROZEN => '冻结',
self::SERVICE_FEE_STATUS_DEDUCTED => '已扣',
self::SERVICE_FEE_STATUS_RELEASED => '已释放',
];
}
@@ -301,5 +301,3 @@ final class TradeConstant
}
}

View File

@@ -11,14 +11,22 @@ final class TransferConstant
* 转账待处理状态。
*/
public const TRANSFER_STATUS_PENDING = 0;
/**
* 转账处理中状态。
*/
public const TRANSFER_STATUS_PROCESSING = 1;
/**
* 转账成功状态。
*/
public const TRANSFER_STATUS_SUCCESS = 1;
public const TRANSFER_STATUS_SUCCESS = 2;
/**
* 转账失败状态。
*/
public const TRANSFER_STATUS_FAILED = 2;
public const TRANSFER_STATUS_FAILED = 3;
/**
* 转账关闭状态。
*/
public const TRANSFER_STATUS_CLOSED = 4;
/**
* 获取转账状态映射。
@@ -29,8 +37,25 @@ final class TransferConstant
{
return [
self::TRANSFER_STATUS_PENDING => '待处理',
self::TRANSFER_STATUS_PROCESSING => '处理中',
self::TRANSFER_STATUS_SUCCESS => '成功',
self::TRANSFER_STATUS_FAILED => '失败',
self::TRANSFER_STATUS_CLOSED => '关闭',
];
}
/**
* 判断是否为转账终态。
*
* @param int $status 转账状态
* @return bool 是否终态
*/
public static function isTerminalStatus(int $status): bool
{
return in_array($status, [
self::TRANSFER_STATUS_SUCCESS,
self::TRANSFER_STATUS_FAILED,
self::TRANSFER_STATUS_CLOSED,
], true);
}
}