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:
@@ -76,13 +76,15 @@ class PaymentRuntimeProcess
|
||||
)
|
||||
);
|
||||
|
||||
$this->runIfDue(
|
||||
'order_timeout',
|
||||
$this->intConfig('pay_order_timeout_scan_interval_seconds', 60, 5),
|
||||
fn (): array => $this->maintenanceService()->timeoutExpiredPayOrders(
|
||||
$this->intConfig('pay_order_timeout_batch_size', 100, 1)
|
||||
)
|
||||
);
|
||||
if ($this->boolConfig('pay_order_timeout_enabled', true)) {
|
||||
$this->runIfDue(
|
||||
'order_timeout',
|
||||
$this->intConfig('pay_order_timeout_scan_interval_seconds', 60, 5),
|
||||
fn (): array => $this->maintenanceService()->timeoutExpiredPayOrders(
|
||||
$this->intConfig('pay_order_timeout_batch_size', 100, 1)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ($this->boolConfig('pay_active_query_enabled', true)) {
|
||||
$this->runIfDue(
|
||||
@@ -161,7 +163,7 @@ class PaymentRuntimeProcess
|
||||
*/
|
||||
private function maintenanceService(): PaymentRuntimeMaintenanceService
|
||||
{
|
||||
return container_make(PaymentRuntimeMaintenanceService::class, []);
|
||||
return container_get(PaymentRuntimeMaintenanceService::class);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -216,6 +218,6 @@ class PaymentRuntimeProcess
|
||||
*/
|
||||
private function runtimeConfig(): SystemConfigRuntimeService
|
||||
{
|
||||
return container_make(SystemConfigRuntimeService::class, []);
|
||||
return container_get(SystemConfigRuntimeService::class);
|
||||
}
|
||||
}
|
||||
|
||||
173
app/process/ReceiptWatcherProcess.php
Normal file
173
app/process/ReceiptWatcherProcess.php
Normal file
@@ -0,0 +1,173 @@
|
||||
<?php
|
||||
|
||||
namespace app\process;
|
||||
|
||||
use app\service\payment\receipt\ReceiptWatcherService;
|
||||
use support\Log;
|
||||
use Workerman\Timer;
|
||||
use Workerman\Worker;
|
||||
|
||||
/**
|
||||
* 网页流水监听调度进程。
|
||||
*
|
||||
* 该进程不访问第三方平台,只负责把当前需要查询流水的账号和订单同步到 Redis。
|
||||
*/
|
||||
class ReceiptWatcherProcess
|
||||
{
|
||||
/**
|
||||
* 上次执行时间。
|
||||
*
|
||||
* @var array<string, int>
|
||||
*/
|
||||
private array $lastRunAt = [];
|
||||
|
||||
/**
|
||||
* 运行锁。
|
||||
*
|
||||
* @var array<string, bool>
|
||||
*/
|
||||
private array $running = [];
|
||||
|
||||
/**
|
||||
* 构造方法。
|
||||
*
|
||||
* @param array<string, mixed> $options 进程选项
|
||||
*/
|
||||
public function __construct(
|
||||
private array $options = []
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Worker 启动。
|
||||
*
|
||||
* @param Worker $worker Worker 实例
|
||||
* @return void
|
||||
*/
|
||||
public function onWorkerStart(Worker $worker): void
|
||||
{
|
||||
try {
|
||||
$this->watcherService()->refreshChannelCache();
|
||||
} catch (\Throwable $e) {
|
||||
Log::warning('[ReceiptWatcherProcess] 启动刷新账号缓存失败:' . $e->getMessage());
|
||||
}
|
||||
|
||||
$heartbeat = $this->intOption('heartbeat_seconds', 1, 1, 60);
|
||||
Timer::add($heartbeat, function (): void {
|
||||
$this->tick();
|
||||
});
|
||||
|
||||
Log::info(sprintf('[ReceiptWatcherProcess] 网页流水监听调度进程已启动 heartbeat=%s', $heartbeat));
|
||||
}
|
||||
|
||||
/**
|
||||
* 心跳调度入口。
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function tick(): void
|
||||
{
|
||||
try {
|
||||
$this->runIfDue('refresh_channels', 60, function (): array {
|
||||
return $this->watcherService()->refreshChannelCache();
|
||||
});
|
||||
|
||||
$this->runIfDue('sync_pending_orders', $this->scanIntervalSeconds(), function (): array {
|
||||
return $this->watcherService()->syncPendingOrders($this->scanBatchSize());
|
||||
});
|
||||
} catch (\Throwable $e) {
|
||||
Log::warning('[ReceiptWatcherProcess] 心跳调度失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 到期后执行任务。
|
||||
*
|
||||
* @param string $key 任务键
|
||||
* @param int $intervalSeconds 间隔秒数
|
||||
* @param callable $callback 任务回调
|
||||
* @return void
|
||||
*/
|
||||
private function runIfDue(string $key, int $intervalSeconds, callable $callback): void
|
||||
{
|
||||
$now = time();
|
||||
$lastRunAt = (int) ($this->lastRunAt[$key] ?? 0);
|
||||
if ($lastRunAt > 0 && $now - $lastRunAt < $intervalSeconds) {
|
||||
return;
|
||||
}
|
||||
if (!empty($this->running[$key])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->lastRunAt[$key] = $now;
|
||||
$this->running[$key] = true;
|
||||
|
||||
try {
|
||||
$summary = $callback();
|
||||
if ($this->hasWork($summary)) {
|
||||
Log::info(sprintf(
|
||||
'[ReceiptWatcherProcess] %s 执行完成 %s',
|
||||
$key,
|
||||
json_encode($summary, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
|
||||
));
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
Log::warning(sprintf('[ReceiptWatcherProcess] %s 执行失败:%s', $key, $e->getMessage()));
|
||||
} finally {
|
||||
$this->running[$key] = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, int> $summary 任务摘要
|
||||
* @return bool 是否有实际工作量
|
||||
*/
|
||||
private function hasWork(array $summary): bool
|
||||
{
|
||||
foreach ($summary as $value) {
|
||||
if ((int) $value > 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int 待支付订单扫描间隔
|
||||
*/
|
||||
private function scanIntervalSeconds(): int
|
||||
{
|
||||
return max(2, (int) sys_config('receipt_watcher_order_scan_interval_seconds', 3));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int 待支付订单扫描批量
|
||||
*/
|
||||
private function scanBatchSize(): int
|
||||
{
|
||||
return max(1, (int) sys_config('receipt_watcher_order_scan_batch_size', 500));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key 配置键
|
||||
* @param int $default 默认值
|
||||
* @param int $min 最小值
|
||||
* @param int $max 最大值
|
||||
* @return int 配置值
|
||||
*/
|
||||
private function intOption(string $key, int $default, int $min, int $max): int
|
||||
{
|
||||
$value = (int) ($this->options[$key] ?? $default);
|
||||
|
||||
return min($max, max($min, $value));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ReceiptWatcherService 网页流水监听服务
|
||||
*/
|
||||
private function watcherService(): ReceiptWatcherService
|
||||
{
|
||||
return container_get(ReceiptWatcherService::class);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user