重构初始化

This commit is contained in:
技术老胡
2026-04-15 11:45:46 +08:00
parent 72d72d735b
commit 7612026773
381 changed files with 28287 additions and 14717 deletions

View File

@@ -0,0 +1,248 @@
<?php
namespace app\service\payment\trace;
use app\common\base\BaseService;
use app\common\constant\LedgerConstant;
use app\common\constant\NotifyConstant;
use app\common\constant\TradeConstant;
use app\model\payment\BizOrder;
/**
* 跨域交易追踪结果组装服务。
*
* 负责把追踪查询到的原始记录组装成摘要和时间线。
*/
class TradeTraceReportService extends BaseService
{
/**
* 汇总追踪统计数据。
*/
public function buildSummary(?BizOrder $bizOrder, array $payOrders, array $refundOrders, array $settlementOrders, array $accountLedgers, array $payCallbacks): array
{
return [
'has_biz_order' => $bizOrder !== null,
'pay_order_count' => count($payOrders),
'refund_order_count' => count($refundOrders),
'settlement_order_count' => count($settlementOrders),
'ledger_count' => count($accountLedgers),
'callback_count' => count($payCallbacks),
'pay_amount_total' => $this->sumBy($payOrders, 'pay_amount'),
'refund_amount_total' => $this->sumBy($refundOrders, 'refund_amount'),
'settlement_accounted_total' => $this->sumBy($settlementOrders, 'accounted_amount'),
'ledger_amount_total' => $this->sumBy($accountLedgers, 'amount'),
];
}
/**
* 根据关联记录组装追踪时间线。
*/
public function buildTimeline(?BizOrder $bizOrder, array $payOrders, array $refundOrders, array $settlementOrders, array $accountLedgers, array $payCallbacks): array
{
$events = [];
$sortOrder = 0;
if ($bizOrder) {
$this->pushEvent($events, $sortOrder, 'biz_order', (string) $bizOrder->biz_no, 'created', $bizOrder->created_at, [
'label' => '业务单创建',
'status_text' => '创建',
'biz_no' => (string) $bizOrder->biz_no,
'merchant_order_no' => (string) $bizOrder->merchant_order_no,
]);
$this->pushEvent($events, $sortOrder, 'biz_order', (string) $bizOrder->biz_no, 'paid', $bizOrder->paid_at, [
'label' => '业务单已支付',
'status_text' => '成功',
'biz_no' => (string) $bizOrder->biz_no,
]);
$this->pushEvent($events, $sortOrder, 'biz_order', (string) $bizOrder->biz_no, 'closed', $bizOrder->closed_at, [
'label' => '业务单已关闭',
'status_text' => '关闭',
'biz_no' => (string) $bizOrder->biz_no,
]);
$this->pushEvent($events, $sortOrder, 'biz_order', (string) $bizOrder->biz_no, 'failed', $bizOrder->failed_at, [
'label' => '业务单失败',
'status_text' => '失败',
'biz_no' => (string) $bizOrder->biz_no,
]);
$this->pushEvent($events, $sortOrder, 'biz_order', (string) $bizOrder->biz_no, 'timeout', $bizOrder->timeout_at, [
'label' => '业务单超时',
'status_text' => '超时',
'biz_no' => (string) $bizOrder->biz_no,
]);
}
foreach ($payOrders as $payOrder) {
$payNo = (string) $payOrder->pay_no;
$statusTextMap = TradeConstant::orderStatusMap();
$this->pushEvent($events, $sortOrder, 'pay_order', $payNo, 'created', $payOrder->request_at, [
'label' => '支付单创建',
'status_text' => '创建',
'pay_no' => $payNo,
'biz_no' => (string) $payOrder->biz_no,
'attempt_no' => (int) ($payOrder->attempt_no ?? 0),
]);
$this->pushEvent($events, $sortOrder, 'pay_order', $payNo, 'paid', $payOrder->paid_at, [
'label' => '支付成功',
'status_text' => (string) ($statusTextMap[TradeConstant::ORDER_STATUS_SUCCESS] ?? '成功'),
'pay_no' => $payNo,
'biz_no' => (string) $payOrder->biz_no,
'channel_id' => (int) $payOrder->channel_id,
]);
$this->pushEvent($events, $sortOrder, 'pay_order', $payNo, 'closed', $payOrder->closed_at, [
'label' => '支付关闭',
'status_text' => (string) ($statusTextMap[TradeConstant::ORDER_STATUS_CLOSED] ?? '关闭'),
'pay_no' => $payNo,
'biz_no' => (string) $payOrder->biz_no,
]);
$this->pushEvent($events, $sortOrder, 'pay_order', $payNo, 'failed', $payOrder->failed_at, [
'label' => '支付失败',
'status_text' => (string) ($statusTextMap[TradeConstant::ORDER_STATUS_FAILED] ?? '失败'),
'pay_no' => $payNo,
'biz_no' => (string) $payOrder->biz_no,
'channel_error_msg' => (string) ($payOrder->channel_error_msg ?? ''),
]);
$this->pushEvent($events, $sortOrder, 'pay_order', $payNo, 'timeout', $payOrder->timeout_at, [
'label' => '支付超时',
'status_text' => (string) ($statusTextMap[TradeConstant::ORDER_STATUS_TIMEOUT] ?? '超时'),
'pay_no' => $payNo,
'biz_no' => (string) $payOrder->biz_no,
]);
}
foreach ($refundOrders as $refundOrder) {
$refundNo = (string) $refundOrder->refund_no;
$statusTextMap = TradeConstant::refundStatusMap();
$this->pushEvent($events, $sortOrder, 'refund_order', $refundNo, 'created', $refundOrder->request_at, [
'label' => '退款单创建',
'status_text' => '创建',
'refund_no' => $refundNo,
'pay_no' => (string) $refundOrder->pay_no,
]);
$this->pushEvent($events, $sortOrder, 'refund_order', $refundNo, 'processing', $refundOrder->processing_at, [
'label' => '退款处理中',
'status_text' => (string) ($statusTextMap[TradeConstant::REFUND_STATUS_PROCESSING] ?? '处理中'),
'refund_no' => $refundNo,
'pay_no' => (string) $refundOrder->pay_no,
'retry_count' => (int) ($refundOrder->retry_count ?? 0),
]);
$this->pushEvent($events, $sortOrder, 'refund_order', $refundNo, 'success', $refundOrder->succeeded_at, [
'label' => '退款成功',
'status_text' => (string) ($statusTextMap[TradeConstant::REFUND_STATUS_SUCCESS] ?? '成功'),
'refund_no' => $refundNo,
'pay_no' => (string) $refundOrder->pay_no,
]);
$this->pushEvent($events, $sortOrder, 'refund_order', $refundNo, 'failed', $refundOrder->failed_at, [
'label' => '退款失败',
'status_text' => (string) ($statusTextMap[TradeConstant::REFUND_STATUS_FAILED] ?? '失败'),
'refund_no' => $refundNo,
'pay_no' => (string) $refundOrder->pay_no,
'last_error' => (string) ($refundOrder->last_error ?? ''),
]);
}
foreach ($settlementOrders as $settlementOrder) {
$settleNo = (string) $settlementOrder->settle_no;
$statusTextMap = TradeConstant::settlementStatusMap();
$this->pushEvent($events, $sortOrder, 'settlement_order', $settleNo, 'generated', $settlementOrder->generated_at, [
'label' => '清结算单生成',
'status_text' => '生成',
'settle_no' => $settleNo,
]);
$this->pushEvent($events, $sortOrder, 'settlement_order', $settleNo, 'accounted', $settlementOrder->accounted_at, [
'label' => '清结算入账',
'status_text' => '入账',
'settle_no' => $settleNo,
'accounted_amount' => (int) ($settlementOrder->accounted_amount ?? 0),
]);
$this->pushEvent($events, $sortOrder, 'settlement_order', $settleNo, 'completed', $settlementOrder->completed_at, [
'label' => '清结算完成',
'status_text' => (string) ($statusTextMap[TradeConstant::SETTLEMENT_STATUS_SETTLED] ?? '已清算'),
'settle_no' => $settleNo,
]);
$this->pushEvent($events, $sortOrder, 'settlement_order', $settleNo, 'failed', $settlementOrder->failed_at, [
'label' => '清结算失败',
'status_text' => (string) ($statusTextMap[TradeConstant::SETTLEMENT_STATUS_REVERSED] ?? '已冲正'),
'settle_no' => $settleNo,
'fail_reason' => (string) ($settlementOrder->fail_reason ?? ''),
]);
}
foreach ($accountLedgers as $ledger) {
$this->pushEvent($events, $sortOrder, 'ledger', (string) $ledger->ledger_no, 'recorded', $ledger->created_at, [
'label' => '资金流水',
'status_text' => (string) (LedgerConstant::eventTypeMap()[$ledger->event_type] ?? '流水'),
'ledger_no' => (string) $ledger->ledger_no,
'biz_no' => (string) $ledger->biz_no,
'biz_type' => (int) $ledger->biz_type,
'biz_type_text' => (string) (LedgerConstant::bizTypeMap()[$ledger->biz_type] ?? ''),
'direction' => (int) $ledger->direction,
'direction_text' => (string) (LedgerConstant::directionMap()[$ledger->direction] ?? ''),
'amount' => (int) $ledger->amount,
]);
}
foreach ($payCallbacks as $callback) {
$this->pushEvent($events, $sortOrder, 'pay_callback', (string) ($callback['pay_no'] ?? ''), 'received', $callback['created_at'] ?? null, [
'label' => '支付回调',
'status_text' => (string) ($callback['callback_type_text'] ?? '回调'),
'pay_no' => (string) ($callback['pay_no'] ?? ''),
'channel_id' => (int) ($callback['channel_id'] ?? 0),
'verify_status' => (int) ($callback['verify_status'] ?? 0),
'verify_status_text' => (string) ($callback['verify_status_text'] ?? ''),
'process_status' => (int) ($callback['process_status'] ?? 0),
'process_status_text' => (string) ($callback['process_status_text'] ?? ''),
]);
}
usort($events, static function (array $left, array $right): int {
$cmp = strcmp((string) ($left['at'] ?? ''), (string) ($right['at'] ?? ''));
if ($cmp !== 0) {
return $cmp;
}
return ($left['_sort_order'] ?? 0) <=> ($right['_sort_order'] ?? 0);
});
foreach ($events as &$event) {
unset($event['_sort_order']);
}
unset($event);
return array_values($events);
}
/**
* 追加一条时间线事件。
*/
private function pushEvent(array &$events, int &$sortOrder, string $type, string $sourceNo, string $status, mixed $at, array $payload = []): void
{
$atText = $this->formatDateTime($at);
if ($atText === '') {
return;
}
$events[] = [
'type' => $type,
'source_no' => $sourceNo,
'status' => $status,
'status_text' => (string) ($payload['status_text'] ?? ''),
'label' => (string) ($payload['label'] ?? ''),
'at' => $atText,
'payload' => $payload,
'_sort_order' => $sortOrder++,
];
}
/**
* 汇总模型列表中的数值字段。
*/
private function sumBy(array $items, string $field): int
{
$total = 0;
foreach ($items as $item) {
$total += (int) ($item->{$field} ?? 0);
}
return $total;
}
}

View File

@@ -0,0 +1,269 @@
<?php
namespace app\service\payment\trace;
use app\common\base\BaseService;
use app\common\constant\NotifyConstant;
use app\exception\ValidationException;
use app\model\payment\BizOrder;
use app\repository\payment\trade\BizOrderRepository;
use app\repository\account\ledger\MerchantAccountLedgerRepository;
use app\repository\ops\log\PayCallbackLogRepository;
use app\repository\payment\trade\PayOrderRepository;
use app\repository\payment\trade\RefundOrderRepository;
use app\repository\payment\settlement\SettlementOrderRepository;
/**
* 跨域交易追踪查询服务。
*/
class TradeTraceService extends BaseService
{
public function __construct(
protected TradeTraceReportService $tradeTraceReportService,
protected BizOrderRepository $bizOrderRepository,
protected PayOrderRepository $payOrderRepository,
protected RefundOrderRepository $refundOrderRepository,
protected SettlementOrderRepository $settlementOrderRepository,
protected MerchantAccountLedgerRepository $merchantAccountLedgerRepository,
protected PayCallbackLogRepository $payCallbackLogRepository
) {
}
/**
* 根据追踪号查询完整交易链路。
*/
public function queryByTraceNo(string $traceNo): array
{
$traceNo = trim($traceNo);
if ($traceNo === '') {
throw new ValidationException('trace_no 不能为空');
}
$matchedBy = 'trace_no';
$bizOrder = $this->bizOrderRepository->findByTraceNo($traceNo);
if (!$bizOrder) {
$bizOrder = $this->bizOrderRepository->findByBizNo($traceNo);
if ($bizOrder) {
$matchedBy = 'biz_no';
}
}
$resolvedTraceNo = $traceNo;
if ($bizOrder) {
$resolvedTraceNo = (string) ($bizOrder->trace_no ?: $bizOrder->biz_no);
}
$payOrders = $this->loadPayOrders($resolvedTraceNo, $bizOrder);
$refundOrders = $this->loadRefundOrders($resolvedTraceNo, $bizOrder);
$settlementOrders = $this->loadSettlementOrders($resolvedTraceNo);
if (!$bizOrder) {
$bizOrder = $this->deriveBizOrder($payOrders, $refundOrders);
if ($bizOrder) {
$matchedBy = $matchedBy === 'trace_no' ? 'derived' : $matchedBy;
$resolvedTraceNo = (string) ($bizOrder->trace_no ?: $bizOrder->biz_no);
$payOrders = $this->loadPayOrders($resolvedTraceNo, $bizOrder);
$refundOrders = $this->loadRefundOrders($resolvedTraceNo, $bizOrder);
$settlementOrders = $this->loadSettlementOrders($resolvedTraceNo);
}
}
$payCallbacks = $this->loadPayCallbacks($payOrders);
$accountLedgers = $this->loadLedgers($resolvedTraceNo, $bizOrder, $payOrders, $refundOrders, $settlementOrders);
if (empty($accountLedgers) && $resolvedTraceNo !== $traceNo) {
$accountLedgers = $this->loadLedgers($traceNo, $bizOrder, $payOrders, $refundOrders, $settlementOrders);
}
if (
$bizOrder === null
&& empty($payOrders)
&& empty($refundOrders)
&& empty($settlementOrders)
&& empty($accountLedgers)
&& empty($payCallbacks)
) {
return [];
}
return [
'trace_no' => $traceNo,
'resolved_trace_no' => $resolvedTraceNo,
'matched_by' => $matchedBy,
'biz_order' => $bizOrder,
'pay_orders' => $payOrders,
'refund_orders' => $refundOrders,
'settlement_orders' => $settlementOrders,
'account_ledgers' => $accountLedgers,
'pay_callbacks' => $payCallbacks,
'summary' => $this->tradeTraceReportService->buildSummary($bizOrder, $payOrders, $refundOrders, $settlementOrders, $accountLedgers, $payCallbacks),
'timeline' => $this->tradeTraceReportService->buildTimeline($bizOrder, $payOrders, $refundOrders, $settlementOrders, $accountLedgers, $payCallbacks),
];
}
/**
* 加载支付单列表。
*/
private function loadPayOrders(string $traceNo, ?BizOrder $bizOrder): array
{
$items = $this->collectionToArray($this->payOrderRepository->listByTraceNo($traceNo));
if (!empty($items)) {
return $items;
}
if ($bizOrder) {
return $this->collectionToArray($this->payOrderRepository->listByBizNo((string) $bizOrder->biz_no));
}
return [];
}
/**
* 加载退款单列表。
*/
private function loadRefundOrders(string $traceNo, ?BizOrder $bizOrder): array
{
$items = $this->collectionToArray($this->refundOrderRepository->listByTraceNo($traceNo));
if (!empty($items)) {
return $items;
}
if ($bizOrder) {
return $this->collectionToArray($this->refundOrderRepository->listByBizNo((string) $bizOrder->biz_no));
}
return [];
}
/**
* 加载清结算单列表。
*/
private function loadSettlementOrders(string $traceNo): array
{
return $this->collectionToArray($this->settlementOrderRepository->listByTraceNo($traceNo));
}
/**
* 加载支付回调日志列表。
*/
private function loadPayCallbacks(array $payOrders): array
{
$callbacks = [];
foreach ($payOrders as $payOrder) {
foreach ($this->payCallbackLogRepository->listByPayNo((string) $payOrder->pay_no) as $callback) {
$callbacks[] = [
'id' => (int) ($callback->id ?? 0),
'pay_no' => (string) $callback->pay_no,
'channel_id' => (int) $callback->channel_id,
'callback_type' => (int) $callback->callback_type,
'callback_type_text' => (string) (NotifyConstant::callbackTypeMap()[$callback->callback_type] ?? ''),
'request_data' => $callback->request_data,
'verify_status' => (int) $callback->verify_status,
'verify_status_text' => (string) (NotifyConstant::verifyStatusMap()[$callback->verify_status] ?? ''),
'process_status' => (int) $callback->process_status,
'process_status_text' => (string) (NotifyConstant::processStatusMap()[$callback->process_status] ?? ''),
'process_result' => $callback->process_result,
'created_at' => $callback->created_at,
];
}
}
usort($callbacks, static function ($left, $right): int {
return ($right['id'] ?? 0) <=> ($left['id'] ?? 0);
});
return $callbacks;
}
/**
* 加载资金流水列表。
*/
private function loadLedgers(string $traceNo, ?BizOrder $bizOrder, array $payOrders, array $refundOrders, array $settlementOrders): array
{
$ledgers = [];
$seen = [];
foreach ($this->collectionToArray($this->merchantAccountLedgerRepository->listByTraceNo($traceNo)) as $ledger) {
$seen[(string) $ledger->ledger_no] = true;
$ledgers[] = $ledger;
}
$bizNos = [];
if ($bizOrder) {
$bizNos[] = (string) $bizOrder->biz_no;
}
foreach ($payOrders as $payOrder) {
$bizNos[] = (string) ($payOrder->pay_no ?? '');
}
foreach ($refundOrders as $refundOrder) {
$bizNos[] = (string) ($refundOrder->refund_no ?? '');
}
foreach ($settlementOrders as $settlementOrder) {
$bizNos[] = (string) ($settlementOrder->settle_no ?? '');
}
$bizNos = array_values(array_filter(array_unique($bizNos)));
foreach ($bizNos as $bizNo) {
foreach ($this->collectionToArray($this->merchantAccountLedgerRepository->listByBizNo($bizNo)) as $ledger) {
$ledgerNo = (string) ($ledger->ledger_no ?? '');
if ($ledgerNo !== '' && isset($seen[$ledgerNo])) {
continue;
}
if ($ledgerNo !== '') {
$seen[$ledgerNo] = true;
}
$ledgers[] = $ledger;
}
}
usort($ledgers, static function ($left, $right): int {
return ($right->id ?? 0) <=> ($left->id ?? 0);
});
return $ledgers;
}
/**
* 从支付单或退款单反推出业务单。
*/
private function deriveBizOrder(array $payOrders, array $refundOrders): ?BizOrder
{
if (!empty($payOrders)) {
$bizNo = (string) ($payOrders[0]->biz_no ?? '');
if ($bizNo !== '') {
$bizOrder = $this->bizOrderRepository->findByBizNo($bizNo);
if ($bizOrder) {
return $bizOrder;
}
}
}
if (!empty($refundOrders)) {
$bizNo = (string) ($refundOrders[0]->biz_no ?? '');
if ($bizNo !== '') {
$bizOrder = $this->bizOrderRepository->findByBizNo($bizNo);
if ($bizOrder) {
return $bizOrder;
}
}
}
return null;
}
/**
* 将可迭代对象转换为普通数组。
*/
private function collectionToArray(iterable $items): array
{
$rows = [];
foreach ($items as $item) {
$rows[] = $item;
}
return $rows;
}
}