mirror of
https://gitee.com/technical-laohu/mpay_v2_webman.git
synced 2026-05-17 06:20:25 +08:00
feat: 完善支付通道和收款监听链路
新增 ChannelNotifyPayloadInterface 等支付插件通知契约,规范 pay_no 定位和插件返回校验。 新增微信、支付宝、收钱吧、Postar 个人收款插件适配,支持余额识别与备注识别。 新增 receipt-watcher 后端进程、Redis 队列 job 和平台事件监听,覆盖收款流水通知、商户通知、退款派发、转账派发与清算完成。 补齐个人收款监听相关系统配置、仓储、服务费冻结明细、订单后台操作和通道测试能力。 重构支付单创建、回调、费用、风控、结算和通道统计链路,统一状态流转与幂等处理。
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
namespace app\repository\payment\config;
|
||||
|
||||
use app\common\base\BaseRepository;
|
||||
use app\common\constant\CommonConstant;
|
||||
use app\model\payment\PaymentChannel;
|
||||
|
||||
/**
|
||||
@@ -122,6 +123,83 @@ class PaymentChannelRepository extends BaseRepository
|
||||
->where('merchant_id', $merchantId)
|
||||
->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询网页流水监听可用通道。
|
||||
*
|
||||
* @param array<int, string> $pluginCodes 支持监听的插件编码
|
||||
* @param array $columns 字段列表
|
||||
* @return \Illuminate\Database\Eloquent\Collection<int, PaymentChannel> 通道列表
|
||||
*/
|
||||
public function listReceiptWatcherChannels(array $pluginCodes, array $columns = ['*'])
|
||||
{
|
||||
$pluginCodes = array_values(array_filter(array_map(static fn ($code): string => trim((string) $code), $pluginCodes)));
|
||||
if ($pluginCodes === []) {
|
||||
return $this->model->newCollection();
|
||||
}
|
||||
|
||||
return $this->model->newQuery()
|
||||
->where('status', CommonConstant::STATUS_ENABLED)
|
||||
->where('api_config_id', '>', 0)
|
||||
->whereIn('plugin_code', $pluginCodes)
|
||||
->orderBy('api_config_id')
|
||||
->orderBy('id')
|
||||
->get($columns);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询指定插件配置下的通道 ID。
|
||||
*
|
||||
* @param string $pluginCode 插件编码
|
||||
* @param int $apiConfigId 插件配置ID
|
||||
* @return array<int, int> 通道 ID 列表
|
||||
*/
|
||||
public function idsByPluginConfig(string $pluginCode, int $apiConfigId): array
|
||||
{
|
||||
if (trim($pluginCode) === '' || $apiConfigId <= 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $this->model->newQuery()
|
||||
->where('plugin_code', $pluginCode)
|
||||
->where('api_config_id', $apiConfigId)
|
||||
->pluck('id')
|
||||
->map(fn ($id): int => (int) $id)
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据插件配置和支付方式解析流水对应通道。
|
||||
*
|
||||
* @param string $pluginCode 插件编码
|
||||
* @param int $apiConfigId 插件配置ID
|
||||
* @param string $payTypeCode 支付方式编码
|
||||
* @return PaymentChannel|null 支付通道
|
||||
*/
|
||||
public function findReceiptFlowChannel(string $pluginCode, int $apiConfigId, string $payTypeCode): ?PaymentChannel
|
||||
{
|
||||
$pluginCode = trim($pluginCode);
|
||||
$payTypeCode = trim($payTypeCode);
|
||||
if ($pluginCode === '' || $apiConfigId <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$query = $this->model->newQuery()
|
||||
->from('ma_payment_channel as c')
|
||||
->where('c.plugin_code', $pluginCode)
|
||||
->where('c.api_config_id', $apiConfigId)
|
||||
->where('c.status', CommonConstant::STATUS_ENABLED)
|
||||
->orderBy('c.sort_no')
|
||||
->orderBy('c.id');
|
||||
|
||||
if ($payTypeCode !== '') {
|
||||
$query->join('ma_payment_type as t', 'c.pay_type_id', '=', 't.id')
|
||||
->where('t.code', $payTypeCode);
|
||||
}
|
||||
|
||||
/** @var PaymentChannel|null $channel */
|
||||
$channel = $query->first(['c.*']);
|
||||
return $channel;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -53,9 +53,27 @@ class PaymentPluginConfRepository extends BaseRepository
|
||||
->whereKey($id)
|
||||
->first($columns);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据配置 ID 批量查询插件配置。
|
||||
*
|
||||
* @param array<int, int> $ids 配置 ID 列表
|
||||
* @param array $columns 字段列表
|
||||
* @return \Illuminate\Database\Eloquent\Collection<int, PaymentPluginConf> 插件配置列表
|
||||
*/
|
||||
public function listByIds(array $ids, array $columns = ['*'])
|
||||
{
|
||||
$ids = array_values(array_unique(array_filter(array_map('intval', $ids))));
|
||||
if ($ids === []) {
|
||||
return $this->model->newCollection();
|
||||
}
|
||||
|
||||
return $this->model->newQuery()
|
||||
->whereIn('id', $ids)
|
||||
->get($columns);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -61,6 +61,7 @@ class PaymentPollGroupBindRepository extends BaseRepository
|
||||
'b.remark',
|
||||
't.code as pay_type_code',
|
||||
't.name as pay_type_name',
|
||||
't.icon as pay_type_icon',
|
||||
'p.group_name as poll_group_name',
|
||||
'p.route_mode',
|
||||
]);
|
||||
@@ -70,4 +71,3 @@ class PaymentPollGroupBindRepository extends BaseRepository
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -49,10 +49,28 @@ class PaymentTypeRepository extends BaseRepository
|
||||
->where('code', $code)
|
||||
->first($columns);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据支付方式 ID 批量查询字典。
|
||||
*
|
||||
* @param array<int, int> $ids 支付方式 ID 列表
|
||||
* @param array $columns 字段列表
|
||||
* @return \Illuminate\Database\Eloquent\Collection<int, PaymentType> 支付方式列表
|
||||
*/
|
||||
public function listByIds(array $ids, array $columns = ['*'])
|
||||
{
|
||||
$ids = array_values(array_unique(array_filter(array_map('intval', $ids))));
|
||||
if ($ids === []) {
|
||||
return $this->model->newCollection();
|
||||
}
|
||||
|
||||
return $this->model->newQuery()
|
||||
->whereIn('id', $ids)
|
||||
->get($columns);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -226,8 +226,156 @@ class PayOrderRepository extends BaseRepository
|
||||
'c.name as channel_name',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询网页流水监听需要关注的待支付订单。
|
||||
*
|
||||
* @param array<int, string> $pluginCodes 插件编码列表
|
||||
* @param string $now 当前时间
|
||||
* @param int $limit 限制条数
|
||||
* @return \Illuminate\Database\Eloquent\Collection<int, PayOrder> 支付单列表
|
||||
*/
|
||||
public function listReceiptWatcherPendingOrders(array $pluginCodes, string $now, int $limit = 500)
|
||||
{
|
||||
$pluginCodes = array_values(array_filter(array_map(static fn ($code): string => trim((string) $code), $pluginCodes)));
|
||||
if ($pluginCodes === []) {
|
||||
return $this->model->newCollection();
|
||||
}
|
||||
|
||||
return $this->model->newQuery()
|
||||
->from('ma_pay_order as po')
|
||||
->join('ma_payment_channel as c', 'po.channel_id', '=', 'c.id')
|
||||
->leftJoin('ma_payment_type as t', 'po.pay_type_id', '=', 't.id')
|
||||
->whereIn('po.status', TradeConstant::orderMutableStatuses())
|
||||
->whereIn('c.plugin_code', $pluginCodes)
|
||||
->where('c.status', 1)
|
||||
->where(function ($query) use ($now): void {
|
||||
$query->whereNull('po.expire_at')
|
||||
->orWhere('po.expire_at', '>', $now);
|
||||
})
|
||||
->orderBy('po.request_at')
|
||||
->orderBy('po.id')
|
||||
->limit(max(1, $limit))
|
||||
->get([
|
||||
'po.id',
|
||||
'po.pay_no',
|
||||
'po.channel_id',
|
||||
'po.pay_type_id',
|
||||
'po.pay_amount',
|
||||
'po.channel_order_no',
|
||||
'po.channel_trade_no',
|
||||
'po.request_at',
|
||||
'po.expire_at',
|
||||
'po.created_at',
|
||||
'c.plugin_code',
|
||||
'c.api_config_id',
|
||||
't.code as pay_type',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询同一收款账号下已占用的识别金额。
|
||||
*
|
||||
* @param array<int, int> $channelIds 通道ID列表
|
||||
* @param string $excludePayNo 排除的支付单号
|
||||
* @param string $now 当前时间
|
||||
* @return array<int, int> 已占用金额列表,单位分
|
||||
*/
|
||||
public function listUsedReceiptAmounts(array $channelIds, string $excludePayNo, string $now): array
|
||||
{
|
||||
$channelIds = array_values(array_unique(array_filter(array_map('intval', $channelIds))));
|
||||
if ($channelIds === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $this->model->newQuery()
|
||||
->whereIn('channel_id', $channelIds)
|
||||
->where('pay_no', '<>', $excludePayNo)
|
||||
->whereIn('status', TradeConstant::orderMutableStatuses())
|
||||
->where('expire_at', '>', $now)
|
||||
->lockForUpdate()
|
||||
->pluck('pay_amount')
|
||||
->map(fn ($amount): int => (int) $amount)
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据第三方流水号匹配支付单。
|
||||
*
|
||||
* @param array<int, int> $channelIds 通道ID列表
|
||||
* @param string $orderNo 第三方流水号
|
||||
* @param array $columns 字段列表
|
||||
* @return PayOrder|null 支付单
|
||||
*/
|
||||
public function findByReceiptChannelOrder(array $channelIds, string $orderNo, array $columns = ['*']): ?PayOrder
|
||||
{
|
||||
$channelIds = array_values(array_unique(array_filter(array_map('intval', $channelIds))));
|
||||
$orderNo = trim($orderNo);
|
||||
if ($channelIds === [] || $orderNo === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->model->newQuery()
|
||||
->whereIn('channel_id', $channelIds)
|
||||
->where(function ($query) use ($orderNo): void {
|
||||
$query->where('channel_order_no', $orderNo)
|
||||
->orWhere('channel_trade_no', $orderNo);
|
||||
})
|
||||
->orderByDesc('id')
|
||||
->first($columns);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据金额查询同一收款账号下有效待支付订单。
|
||||
*
|
||||
* @param array<int, int> $channelIds 通道ID列表
|
||||
* @param int $amount 金额,单位分
|
||||
* @param int $payTypeId 支付方式ID
|
||||
* @param string $now 当前时间
|
||||
* @param array $columns 字段列表
|
||||
* @return \Illuminate\Database\Eloquent\Collection<int, PayOrder> 支付单列表
|
||||
*/
|
||||
public function listMutableReceiptOrdersByAmount(array $channelIds, int $amount, int $payTypeId, string $now, array $columns = ['*'])
|
||||
{
|
||||
$channelIds = array_values(array_unique(array_filter(array_map('intval', $channelIds))));
|
||||
if ($channelIds === []) {
|
||||
return $this->model->newCollection();
|
||||
}
|
||||
|
||||
$query = $this->model->newQuery()
|
||||
->whereIn('channel_id', $channelIds)
|
||||
->where('pay_amount', $amount)
|
||||
->whereIn('status', TradeConstant::orderMutableStatuses())
|
||||
->where('expire_at', '>', $now);
|
||||
|
||||
if ($payTypeId > 0) {
|
||||
$query->where('pay_type_id', $payTypeId);
|
||||
}
|
||||
|
||||
return $query->get($columns);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询同一收款账号下有效待支付订单,用于备注匹配。
|
||||
*
|
||||
* @param array<int, int> $channelIds 通道ID列表
|
||||
* @param string $now 当前时间
|
||||
* @param array $columns 字段列表
|
||||
* @return \Illuminate\Database\Eloquent\Collection<int, PayOrder> 支付单列表
|
||||
*/
|
||||
public function listMutableReceiptOrders(array $channelIds, string $now, array $columns = ['*'])
|
||||
{
|
||||
$channelIds = array_values(array_unique(array_filter(array_map('intval', $channelIds))));
|
||||
if ($channelIds === []) {
|
||||
return $this->model->newCollection();
|
||||
}
|
||||
|
||||
return $this->model->newQuery()
|
||||
->whereIn('channel_id', $channelIds)
|
||||
->whereIn('status', TradeConstant::orderMutableStatuses())
|
||||
->where('expire_at', '>', $now)
|
||||
->get($columns);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -157,6 +157,23 @@ class RefundOrderRepository extends BaseRepository
|
||||
->first($columns);
|
||||
}
|
||||
|
||||
/**
|
||||
* 锁定指定支付单下会占用可退余额的退款单。
|
||||
*
|
||||
* @param string $payNo 支付单号
|
||||
* @param array<int, int> $statuses 状态列表
|
||||
* @param array $columns 字段列表
|
||||
* @return \Illuminate\Database\Eloquent\Collection<int, RefundOrder> 退款单列表
|
||||
*/
|
||||
public function listForUpdateByPayNoAndStatuses(string $payNo, array $statuses, array $columns = ['*'])
|
||||
{
|
||||
return $this->model->newQuery()
|
||||
->where('pay_no', $payNo)
|
||||
->whereIn('status', $statuses)
|
||||
->lockForUpdate()
|
||||
->get($columns);
|
||||
}
|
||||
|
||||
/**
|
||||
* 统计商户下的退款订单数量。
|
||||
*
|
||||
@@ -172,4 +189,3 @@ class RefundOrderRepository extends BaseRepository
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user