mirror of
https://gitee.com/technical-laohu/mpay_v2_webman.git
synced 2026-04-28 13:04:26 +08:00
重构初始化
This commit is contained in:
349
app/service/account/funds/MerchantAccountCommandService.php
Normal file
349
app/service/account/funds/MerchantAccountCommandService.php
Normal file
@@ -0,0 +1,349 @@
|
||||
<?php
|
||||
|
||||
namespace app\service\account\funds;
|
||||
|
||||
use app\common\base\BaseService;
|
||||
use app\common\constant\LedgerConstant;
|
||||
use app\exception\BalanceInsufficientException;
|
||||
use app\exception\ConflictException;
|
||||
use app\exception\ValidationException;
|
||||
use app\model\merchant\MerchantAccount;
|
||||
use app\model\merchant\MerchantAccountLedger;
|
||||
use app\repository\account\balance\MerchantAccountRepository;
|
||||
use app\repository\account\ledger\MerchantAccountLedgerRepository;
|
||||
|
||||
/**
|
||||
* 商户账户命令服务。
|
||||
*
|
||||
* 只负责账户创建、冻结、扣减、释放和入账等资金变更。
|
||||
*/
|
||||
class MerchantAccountCommandService extends BaseService
|
||||
{
|
||||
public function __construct(
|
||||
protected MerchantAccountRepository $accountRepository,
|
||||
protected MerchantAccountLedgerRepository $ledgerRepository
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取或创建商户账户。
|
||||
*/
|
||||
public function ensureAccount(int $merchantId): MerchantAccount
|
||||
{
|
||||
return $this->transactionRetry(function () use ($merchantId) {
|
||||
return $this->ensureAccountInCurrentTransaction($merchantId);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 在当前事务中获取或创建商户账户。
|
||||
*/
|
||||
public function ensureAccountInCurrentTransaction(int $merchantId): MerchantAccount
|
||||
{
|
||||
$account = $this->accountRepository->findForUpdateByMerchantId($merchantId);
|
||||
if ($account) {
|
||||
return $account;
|
||||
}
|
||||
|
||||
$this->accountRepository->create([
|
||||
'merchant_id' => $merchantId,
|
||||
'available_balance' => 0,
|
||||
'frozen_balance' => 0,
|
||||
]);
|
||||
|
||||
$account = $this->accountRepository->findForUpdateByMerchantId($merchantId);
|
||||
if (!$account) {
|
||||
throw new ValidationException('商户账户创建失败', ['merchant_id' => $merchantId]);
|
||||
}
|
||||
|
||||
return $account;
|
||||
}
|
||||
|
||||
public function freezeAmount(int $merchantId, int $amount, string $bizNo, string $idempotencyKey, array $extJson = [], string $traceNo = ''): MerchantAccountLedger
|
||||
{
|
||||
return $this->transactionRetry(function () use ($merchantId, $amount, $bizNo, $idempotencyKey, $extJson, $traceNo) {
|
||||
return $this->freezeAmountInCurrentTransaction($merchantId, $amount, $bizNo, $idempotencyKey, $extJson, $traceNo);
|
||||
});
|
||||
}
|
||||
|
||||
public function freezeAmountInCurrentTransaction(int $merchantId, int $amount, string $bizNo, string $idempotencyKey, array $extJson = [], string $traceNo = ''): MerchantAccountLedger
|
||||
{
|
||||
$this->assertPositiveAmount($amount);
|
||||
if ($idempotencyKey === '') {
|
||||
throw new ValidationException('幂等键不能为空');
|
||||
}
|
||||
|
||||
if ($existing = $this->findLedgerByIdempotencyKey($idempotencyKey)) {
|
||||
$this->assertLedgerMatch($existing, LedgerConstant::BIZ_TYPE_PAY_FREEZE, $bizNo, $amount, LedgerConstant::DIRECTION_OUT);
|
||||
return $existing;
|
||||
}
|
||||
|
||||
$account = $this->ensureAccountInCurrentTransaction($merchantId);
|
||||
if ((int) $account->available_balance < $amount) {
|
||||
throw new BalanceInsufficientException($merchantId, $amount, (int) $account->available_balance);
|
||||
}
|
||||
|
||||
$availableBefore = (int) $account->available_balance;
|
||||
$frozenBefore = (int) $account->frozen_balance;
|
||||
|
||||
$account->available_balance = $availableBefore - $amount;
|
||||
$account->frozen_balance = $frozenBefore + $amount;
|
||||
$account->save();
|
||||
|
||||
return $this->createLedger([
|
||||
'merchant_id' => $merchantId,
|
||||
'biz_type' => LedgerConstant::BIZ_TYPE_PAY_FREEZE,
|
||||
'biz_no' => $bizNo,
|
||||
'trace_no' => $this->normalizeTraceNo($traceNo, $bizNo),
|
||||
'event_type' => LedgerConstant::EVENT_TYPE_CREATE,
|
||||
'direction' => LedgerConstant::DIRECTION_OUT,
|
||||
'amount' => $amount,
|
||||
'available_before' => $availableBefore,
|
||||
'available_after' => (int) $account->available_balance,
|
||||
'frozen_before' => $frozenBefore,
|
||||
'frozen_after' => (int) $account->frozen_balance,
|
||||
'idempotency_key' => $idempotencyKey,
|
||||
'remark' => $extJson['remark'] ?? '余额冻结',
|
||||
'ext_json' => $extJson,
|
||||
]);
|
||||
}
|
||||
|
||||
public function deductFrozenAmount(int $merchantId, int $amount, string $bizNo, string $idempotencyKey, array $extJson = [], string $traceNo = ''): MerchantAccountLedger
|
||||
{
|
||||
return $this->transactionRetry(function () use ($merchantId, $amount, $bizNo, $idempotencyKey, $extJson, $traceNo) {
|
||||
return $this->deductFrozenAmountInCurrentTransaction($merchantId, $amount, $bizNo, $idempotencyKey, $extJson, $traceNo);
|
||||
});
|
||||
}
|
||||
|
||||
public function deductFrozenAmountInCurrentTransaction(int $merchantId, int $amount, string $bizNo, string $idempotencyKey, array $extJson = [], string $traceNo = ''): MerchantAccountLedger
|
||||
{
|
||||
$this->assertPositiveAmount($amount);
|
||||
if ($idempotencyKey === '') {
|
||||
throw new ValidationException('幂等键不能为空');
|
||||
}
|
||||
|
||||
if ($existing = $this->findLedgerByIdempotencyKey($idempotencyKey)) {
|
||||
$this->assertLedgerMatch($existing, LedgerConstant::BIZ_TYPE_PAY_DEDUCT, $bizNo, $amount, LedgerConstant::DIRECTION_OUT);
|
||||
return $existing;
|
||||
}
|
||||
|
||||
$account = $this->ensureAccountInCurrentTransaction($merchantId);
|
||||
if ((int) $account->frozen_balance < $amount) {
|
||||
throw new ValidationException('冻结余额不足', [
|
||||
'merchant_id' => $merchantId,
|
||||
'amount' => $amount,
|
||||
'frozen_balance' => (int) $account->frozen_balance,
|
||||
]);
|
||||
}
|
||||
|
||||
$availableBefore = (int) $account->available_balance;
|
||||
$frozenBefore = (int) $account->frozen_balance;
|
||||
|
||||
$account->frozen_balance = $frozenBefore - $amount;
|
||||
$account->save();
|
||||
|
||||
return $this->createLedger([
|
||||
'merchant_id' => $merchantId,
|
||||
'biz_type' => LedgerConstant::BIZ_TYPE_PAY_DEDUCT,
|
||||
'biz_no' => $bizNo,
|
||||
'trace_no' => $this->normalizeTraceNo($traceNo, $bizNo),
|
||||
'event_type' => LedgerConstant::EVENT_TYPE_SUCCESS,
|
||||
'direction' => LedgerConstant::DIRECTION_OUT,
|
||||
'amount' => $amount,
|
||||
'available_before' => $availableBefore,
|
||||
'available_after' => (int) $account->available_balance,
|
||||
'frozen_before' => $frozenBefore,
|
||||
'frozen_after' => (int) $account->frozen_balance,
|
||||
'idempotency_key' => $idempotencyKey,
|
||||
'remark' => $extJson['remark'] ?? '余额扣减',
|
||||
'ext_json' => $extJson,
|
||||
]);
|
||||
}
|
||||
|
||||
public function releaseFrozenAmount(int $merchantId, int $amount, string $bizNo, string $idempotencyKey, array $extJson = [], string $traceNo = ''): MerchantAccountLedger
|
||||
{
|
||||
return $this->transactionRetry(function () use ($merchantId, $amount, $bizNo, $idempotencyKey, $extJson, $traceNo) {
|
||||
return $this->releaseFrozenAmountInCurrentTransaction($merchantId, $amount, $bizNo, $idempotencyKey, $extJson, $traceNo);
|
||||
});
|
||||
}
|
||||
|
||||
public function releaseFrozenAmountInCurrentTransaction(int $merchantId, int $amount, string $bizNo, string $idempotencyKey, array $extJson = [], string $traceNo = ''): MerchantAccountLedger
|
||||
{
|
||||
$this->assertPositiveAmount($amount);
|
||||
if ($idempotencyKey === '') {
|
||||
throw new ValidationException('幂等键不能为空');
|
||||
}
|
||||
|
||||
if ($existing = $this->findLedgerByIdempotencyKey($idempotencyKey)) {
|
||||
$this->assertLedgerMatch($existing, LedgerConstant::BIZ_TYPE_PAY_RELEASE, $bizNo, $amount, LedgerConstant::DIRECTION_IN);
|
||||
return $existing;
|
||||
}
|
||||
|
||||
$account = $this->ensureAccountInCurrentTransaction($merchantId);
|
||||
if ((int) $account->frozen_balance < $amount) {
|
||||
throw new ValidationException('冻结余额不足', [
|
||||
'merchant_id' => $merchantId,
|
||||
'amount' => $amount,
|
||||
'frozen_balance' => (int) $account->frozen_balance,
|
||||
]);
|
||||
}
|
||||
|
||||
$availableBefore = (int) $account->available_balance;
|
||||
$frozenBefore = (int) $account->frozen_balance;
|
||||
|
||||
$account->available_balance = $availableBefore + $amount;
|
||||
$account->frozen_balance = $frozenBefore - $amount;
|
||||
$account->save();
|
||||
|
||||
return $this->createLedger([
|
||||
'merchant_id' => $merchantId,
|
||||
'biz_type' => LedgerConstant::BIZ_TYPE_PAY_RELEASE,
|
||||
'biz_no' => $bizNo,
|
||||
'trace_no' => $this->normalizeTraceNo($traceNo, $bizNo),
|
||||
'event_type' => LedgerConstant::EVENT_TYPE_REVERSE,
|
||||
'direction' => LedgerConstant::DIRECTION_IN,
|
||||
'amount' => $amount,
|
||||
'available_before' => $availableBefore,
|
||||
'available_after' => (int) $account->available_balance,
|
||||
'frozen_before' => $frozenBefore,
|
||||
'frozen_after' => (int) $account->frozen_balance,
|
||||
'idempotency_key' => $idempotencyKey,
|
||||
'remark' => $extJson['remark'] ?? '冻结余额释放',
|
||||
'ext_json' => $extJson,
|
||||
]);
|
||||
}
|
||||
|
||||
public function creditAvailableAmount(int $merchantId, int $amount, string $bizNo, string $idempotencyKey, array $extJson = [], string $traceNo = ''): MerchantAccountLedger
|
||||
{
|
||||
return $this->transactionRetry(function () use ($merchantId, $amount, $bizNo, $idempotencyKey, $extJson, $traceNo) {
|
||||
return $this->creditAvailableAmountInCurrentTransaction($merchantId, $amount, $bizNo, $idempotencyKey, $extJson, $traceNo);
|
||||
});
|
||||
}
|
||||
|
||||
public function creditAvailableAmountInCurrentTransaction(int $merchantId, int $amount, string $bizNo, string $idempotencyKey, array $extJson = [], string $traceNo = ''): MerchantAccountLedger
|
||||
{
|
||||
$this->assertPositiveAmount($amount);
|
||||
if ($idempotencyKey === '') {
|
||||
throw new ValidationException('幂等键不能为空');
|
||||
}
|
||||
|
||||
if ($existing = $this->findLedgerByIdempotencyKey($idempotencyKey)) {
|
||||
$this->assertLedgerMatch($existing, LedgerConstant::BIZ_TYPE_SETTLEMENT_CREDIT, $bizNo, $amount, LedgerConstant::DIRECTION_IN);
|
||||
return $existing;
|
||||
}
|
||||
|
||||
$account = $this->ensureAccountInCurrentTransaction($merchantId);
|
||||
$availableBefore = (int) $account->available_balance;
|
||||
$frozenBefore = (int) $account->frozen_balance;
|
||||
|
||||
$account->available_balance = $availableBefore + $amount;
|
||||
$account->save();
|
||||
|
||||
return $this->createLedger([
|
||||
'merchant_id' => $merchantId,
|
||||
'biz_type' => LedgerConstant::BIZ_TYPE_SETTLEMENT_CREDIT,
|
||||
'biz_no' => $bizNo,
|
||||
'trace_no' => $this->normalizeTraceNo($traceNo, $bizNo),
|
||||
'event_type' => LedgerConstant::EVENT_TYPE_SUCCESS,
|
||||
'direction' => LedgerConstant::DIRECTION_IN,
|
||||
'amount' => $amount,
|
||||
'available_before' => $availableBefore,
|
||||
'available_after' => (int) $account->available_balance,
|
||||
'frozen_before' => $frozenBefore,
|
||||
'frozen_after' => (int) $account->frozen_balance,
|
||||
'idempotency_key' => $idempotencyKey,
|
||||
'remark' => $extJson['remark'] ?? '清算入账',
|
||||
'ext_json' => $extJson,
|
||||
]);
|
||||
}
|
||||
|
||||
public function debitAvailableAmount(int $merchantId, int $amount, string $bizNo, string $idempotencyKey, array $extJson = [], string $traceNo = ''): MerchantAccountLedger
|
||||
{
|
||||
return $this->transactionRetry(function () use ($merchantId, $amount, $bizNo, $idempotencyKey, $extJson, $traceNo) {
|
||||
return $this->debitAvailableAmountInCurrentTransaction($merchantId, $amount, $bizNo, $idempotencyKey, $extJson, $traceNo);
|
||||
});
|
||||
}
|
||||
|
||||
public function debitAvailableAmountInCurrentTransaction(int $merchantId, int $amount, string $bizNo, string $idempotencyKey, array $extJson = [], string $traceNo = ''): MerchantAccountLedger
|
||||
{
|
||||
$this->assertPositiveAmount($amount);
|
||||
if ($idempotencyKey === '') {
|
||||
throw new ValidationException('幂等键不能为空');
|
||||
}
|
||||
|
||||
if ($existing = $this->findLedgerByIdempotencyKey($idempotencyKey)) {
|
||||
$this->assertLedgerMatch($existing, LedgerConstant::BIZ_TYPE_REFUND_REVERSE, $bizNo, $amount, LedgerConstant::DIRECTION_OUT);
|
||||
return $existing;
|
||||
}
|
||||
|
||||
$account = $this->ensureAccountInCurrentTransaction($merchantId);
|
||||
if ((int) $account->available_balance < $amount) {
|
||||
throw new BalanceInsufficientException($merchantId, $amount, (int) $account->available_balance);
|
||||
}
|
||||
|
||||
$availableBefore = (int) $account->available_balance;
|
||||
$frozenBefore = (int) $account->frozen_balance;
|
||||
|
||||
$account->available_balance = $availableBefore - $amount;
|
||||
$account->save();
|
||||
|
||||
return $this->createLedger([
|
||||
'merchant_id' => $merchantId,
|
||||
'biz_type' => LedgerConstant::BIZ_TYPE_REFUND_REVERSE,
|
||||
'biz_no' => $bizNo,
|
||||
'trace_no' => $this->normalizeTraceNo($traceNo, $bizNo),
|
||||
'event_type' => LedgerConstant::EVENT_TYPE_REVERSE,
|
||||
'direction' => LedgerConstant::DIRECTION_OUT,
|
||||
'amount' => $amount,
|
||||
'available_before' => $availableBefore,
|
||||
'available_after' => (int) $account->available_balance,
|
||||
'frozen_before' => $frozenBefore,
|
||||
'frozen_after' => (int) $account->frozen_balance,
|
||||
'idempotency_key' => $idempotencyKey,
|
||||
'remark' => $extJson['remark'] ?? '余额冲减',
|
||||
'ext_json' => $extJson,
|
||||
]);
|
||||
}
|
||||
|
||||
private function createLedger(array $data): MerchantAccountLedger
|
||||
{
|
||||
$data['ledger_no'] = $data['ledger_no'] ?? $this->generateNo('LG');
|
||||
$data['trace_no'] = trim((string) ($data['trace_no'] ?? $data['biz_no'] ?? ''));
|
||||
$data['created_at'] = $data['created_at'] ?? $this->now();
|
||||
|
||||
return $this->ledgerRepository->create($data);
|
||||
}
|
||||
|
||||
private function findLedgerByIdempotencyKey(string $idempotencyKey): ?MerchantAccountLedger
|
||||
{
|
||||
return $this->ledgerRepository->findByIdempotencyKey($idempotencyKey);
|
||||
}
|
||||
|
||||
private function assertPositiveAmount(int $amount): void
|
||||
{
|
||||
if ($amount <= 0) {
|
||||
throw new ValidationException('金额必须大于 0');
|
||||
}
|
||||
}
|
||||
|
||||
private function assertLedgerMatch(MerchantAccountLedger $ledger, int $bizType, string $bizNo, int $amount, int $direction): void
|
||||
{
|
||||
if ((int) $ledger->biz_type !== $bizType || (int) $ledger->amount !== $amount || (string) $ledger->biz_no !== $bizNo || (int) $ledger->direction !== $direction) {
|
||||
throw new ConflictException('幂等冲突', [
|
||||
'ledger_no' => (string) $ledger->ledger_no,
|
||||
'biz_type' => $bizType,
|
||||
'biz_no' => $bizNo,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
private function normalizeTraceNo(string $traceNo, string $bizNo): string
|
||||
{
|
||||
$traceNo = trim($traceNo);
|
||||
if ($traceNo !== '') {
|
||||
return $traceNo;
|
||||
}
|
||||
|
||||
return $bizNo;
|
||||
}
|
||||
}
|
||||
160
app/service/account/funds/MerchantAccountQueryService.php
Normal file
160
app/service/account/funds/MerchantAccountQueryService.php
Normal file
@@ -0,0 +1,160 @@
|
||||
<?php
|
||||
|
||||
namespace app\service\account\funds;
|
||||
|
||||
use app\common\base\BaseService;
|
||||
use app\model\merchant\MerchantAccount;
|
||||
use app\repository\account\balance\MerchantAccountRepository;
|
||||
use app\repository\account\ledger\MerchantAccountLedgerRepository;
|
||||
|
||||
/**
|
||||
* 商户账户查询服务。
|
||||
*
|
||||
* 只负责账户列表、概览和快照查询,不承载资金变更逻辑。
|
||||
*/
|
||||
class MerchantAccountQueryService extends BaseService
|
||||
{
|
||||
public function __construct(
|
||||
protected MerchantAccountRepository $accountRepository,
|
||||
protected MerchantAccountLedgerRepository $ledgerRepository
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询商户账户。
|
||||
*/
|
||||
public function paginate(array $filters = [], int $page = 1, int $pageSize = 10)
|
||||
{
|
||||
$query = $this->accountRepository->query()
|
||||
->from('ma_merchant_account as a')
|
||||
->leftJoin('ma_merchant as m', 'a.merchant_id', '=', 'm.id')
|
||||
->leftJoin('ma_merchant_group as g', 'm.group_id', '=', 'g.id')
|
||||
->select([
|
||||
'a.id',
|
||||
'a.merchant_id',
|
||||
'a.available_balance',
|
||||
'a.frozen_balance',
|
||||
'a.created_at',
|
||||
'a.updated_at',
|
||||
])
|
||||
->selectRaw("COALESCE(m.merchant_no, '') AS merchant_no")
|
||||
->selectRaw("COALESCE(m.merchant_name, '') AS merchant_name")
|
||||
->selectRaw("COALESCE(m.merchant_short_name, '') AS merchant_short_name")
|
||||
->selectRaw("COALESCE(g.group_name, '') AS merchant_group_name");
|
||||
|
||||
$keyword = trim((string) ($filters['keyword'] ?? ''));
|
||||
if ($keyword !== '') {
|
||||
$query->where(function ($builder) use ($keyword) {
|
||||
$builder->where('m.merchant_no', 'like', '%' . $keyword . '%')
|
||||
->orWhere('m.merchant_name', 'like', '%' . $keyword . '%')
|
||||
->orWhere('m.merchant_short_name', 'like', '%' . $keyword . '%')
|
||||
->orWhere('g.group_name', 'like', '%' . $keyword . '%');
|
||||
});
|
||||
}
|
||||
|
||||
$merchantId = (string) ($filters['merchant_id'] ?? '');
|
||||
if ($merchantId !== '') {
|
||||
$query->where('a.merchant_id', (int) $merchantId);
|
||||
}
|
||||
|
||||
$paginator = $query
|
||||
->orderByDesc('a.id')
|
||||
->paginate(max(1, $pageSize), ['*'], 'page', max(1, $page));
|
||||
|
||||
$paginator->getCollection()->transform(function ($row) {
|
||||
$row->available_balance_text = $this->formatAmount((int) $row->available_balance);
|
||||
$row->frozen_balance_text = $this->formatAmount((int) $row->frozen_balance);
|
||||
|
||||
return $row;
|
||||
});
|
||||
|
||||
return $paginator;
|
||||
}
|
||||
|
||||
/**
|
||||
* 资金中心概览。
|
||||
*/
|
||||
public function summary(): array
|
||||
{
|
||||
$accountStats = $this->accountRepository->query()
|
||||
->selectRaw('COUNT(*) AS account_count')
|
||||
->selectRaw('SUM(available_balance) AS total_available_balance')
|
||||
->selectRaw('SUM(frozen_balance) AS total_frozen_balance')
|
||||
->first();
|
||||
|
||||
$ledgerStats = $this->ledgerRepository->query()
|
||||
->selectRaw('COUNT(*) AS ledger_count')
|
||||
->first();
|
||||
|
||||
$totalAvailableBalance = (int) ($accountStats->total_available_balance ?? 0);
|
||||
$totalFrozenBalance = (int) ($accountStats->total_frozen_balance ?? 0);
|
||||
|
||||
return [
|
||||
'account_count' => (int) ($accountStats->account_count ?? 0),
|
||||
'ledger_count' => (int) ($ledgerStats->ledger_count ?? 0),
|
||||
'total_available_balance' => $totalAvailableBalance,
|
||||
'total_available_balance_text' => $this->formatAmount($totalAvailableBalance),
|
||||
'total_frozen_balance' => $totalFrozenBalance,
|
||||
'total_frozen_balance_text' => $this->formatAmount($totalFrozenBalance),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取商户余额快照。
|
||||
*
|
||||
* 用于后台展示和接口返回,不修改任何账户数据。
|
||||
*/
|
||||
public function getBalanceSnapshot(int $merchantId): array
|
||||
{
|
||||
$account = $this->accountRepository->findByMerchantId($merchantId);
|
||||
|
||||
if (!$account) {
|
||||
return [
|
||||
'merchant_id' => $merchantId,
|
||||
'available_balance' => 0,
|
||||
'frozen_balance' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'merchant_id' => (int) $account->merchant_id,
|
||||
'available_balance' => (int) $account->available_balance,
|
||||
'frozen_balance' => (int) $account->frozen_balance,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询商户账户详情。
|
||||
*/
|
||||
public function findById(int $id): ?MerchantAccount
|
||||
{
|
||||
$row = $this->accountRepository->query()
|
||||
->from('ma_merchant_account as a')
|
||||
->leftJoin('ma_merchant as m', 'a.merchant_id', '=', 'm.id')
|
||||
->leftJoin('ma_merchant_group as g', 'm.group_id', '=', 'g.id')
|
||||
->select([
|
||||
'a.id',
|
||||
'a.merchant_id',
|
||||
'a.available_balance',
|
||||
'a.frozen_balance',
|
||||
'a.created_at',
|
||||
'a.updated_at',
|
||||
])
|
||||
->selectRaw("COALESCE(m.merchant_no, '') AS merchant_no")
|
||||
->selectRaw("COALESCE(m.merchant_name, '') AS merchant_name")
|
||||
->selectRaw("COALESCE(m.merchant_short_name, '') AS merchant_short_name")
|
||||
->selectRaw("COALESCE(g.group_name, '') AS merchant_group_name")
|
||||
->where('a.id', $id)
|
||||
->first();
|
||||
|
||||
if (!$row) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$row->available_balance_text = $this->formatAmount((int) $row->available_balance);
|
||||
$row->frozen_balance_text = $this->formatAmount((int) $row->frozen_balance);
|
||||
|
||||
return $row;
|
||||
}
|
||||
|
||||
}
|
||||
101
app/service/account/funds/MerchantAccountService.php
Normal file
101
app/service/account/funds/MerchantAccountService.php
Normal file
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
namespace app\service\account\funds;
|
||||
|
||||
use app\common\base\BaseService;
|
||||
use app\model\merchant\MerchantAccount;
|
||||
use app\model\merchant\MerchantAccountLedger;
|
||||
|
||||
/**
|
||||
* 商户余额门面服务。
|
||||
*
|
||||
* 对外保留原有调用契约,内部委托给查询和命令两个子服务。
|
||||
*/
|
||||
class MerchantAccountService extends BaseService
|
||||
{
|
||||
public function __construct(
|
||||
protected MerchantAccountQueryService $queryService,
|
||||
protected MerchantAccountCommandService $commandService
|
||||
) {
|
||||
}
|
||||
|
||||
public function paginate(array $filters = [], int $page = 1, int $pageSize = 10)
|
||||
{
|
||||
return $this->queryService->paginate($filters, $page, $pageSize);
|
||||
}
|
||||
|
||||
public function summary(): array
|
||||
{
|
||||
return $this->queryService->summary();
|
||||
}
|
||||
|
||||
public function ensureAccount(int $merchantId): MerchantAccount
|
||||
{
|
||||
return $this->commandService->ensureAccount($merchantId);
|
||||
}
|
||||
|
||||
public function ensureAccountInCurrentTransaction(int $merchantId): MerchantAccount
|
||||
{
|
||||
return $this->commandService->ensureAccountInCurrentTransaction($merchantId);
|
||||
}
|
||||
|
||||
public function freezeAmount(int $merchantId, int $amount, string $bizNo, string $idempotencyKey, array $extJson = [], string $traceNo = ''): MerchantAccountLedger
|
||||
{
|
||||
return $this->commandService->freezeAmount($merchantId, $amount, $bizNo, $idempotencyKey, $extJson, $traceNo);
|
||||
}
|
||||
|
||||
public function freezeAmountInCurrentTransaction(int $merchantId, int $amount, string $bizNo, string $idempotencyKey, array $extJson = [], string $traceNo = ''): MerchantAccountLedger
|
||||
{
|
||||
return $this->commandService->freezeAmountInCurrentTransaction($merchantId, $amount, $bizNo, $idempotencyKey, $extJson, $traceNo);
|
||||
}
|
||||
|
||||
public function deductFrozenAmount(int $merchantId, int $amount, string $bizNo, string $idempotencyKey, array $extJson = [], string $traceNo = ''): MerchantAccountLedger
|
||||
{
|
||||
return $this->commandService->deductFrozenAmount($merchantId, $amount, $bizNo, $idempotencyKey, $extJson, $traceNo);
|
||||
}
|
||||
|
||||
public function deductFrozenAmountInCurrentTransaction(int $merchantId, int $amount, string $bizNo, string $idempotencyKey, array $extJson = [], string $traceNo = ''): MerchantAccountLedger
|
||||
{
|
||||
return $this->commandService->deductFrozenAmountInCurrentTransaction($merchantId, $amount, $bizNo, $idempotencyKey, $extJson, $traceNo);
|
||||
}
|
||||
|
||||
public function releaseFrozenAmount(int $merchantId, int $amount, string $bizNo, string $idempotencyKey, array $extJson = [], string $traceNo = ''): MerchantAccountLedger
|
||||
{
|
||||
return $this->commandService->releaseFrozenAmount($merchantId, $amount, $bizNo, $idempotencyKey, $extJson, $traceNo);
|
||||
}
|
||||
|
||||
public function releaseFrozenAmountInCurrentTransaction(int $merchantId, int $amount, string $bizNo, string $idempotencyKey, array $extJson = [], string $traceNo = ''): MerchantAccountLedger
|
||||
{
|
||||
return $this->commandService->releaseFrozenAmountInCurrentTransaction($merchantId, $amount, $bizNo, $idempotencyKey, $extJson, $traceNo);
|
||||
}
|
||||
|
||||
public function creditAvailableAmount(int $merchantId, int $amount, string $bizNo, string $idempotencyKey, array $extJson = [], string $traceNo = ''): MerchantAccountLedger
|
||||
{
|
||||
return $this->commandService->creditAvailableAmount($merchantId, $amount, $bizNo, $idempotencyKey, $extJson, $traceNo);
|
||||
}
|
||||
|
||||
public function creditAvailableAmountInCurrentTransaction(int $merchantId, int $amount, string $bizNo, string $idempotencyKey, array $extJson = [], string $traceNo = ''): MerchantAccountLedger
|
||||
{
|
||||
return $this->commandService->creditAvailableAmountInCurrentTransaction($merchantId, $amount, $bizNo, $idempotencyKey, $extJson, $traceNo);
|
||||
}
|
||||
|
||||
public function debitAvailableAmount(int $merchantId, int $amount, string $bizNo, string $idempotencyKey, array $extJson = [], string $traceNo = ''): MerchantAccountLedger
|
||||
{
|
||||
return $this->commandService->debitAvailableAmount($merchantId, $amount, $bizNo, $idempotencyKey, $extJson, $traceNo);
|
||||
}
|
||||
|
||||
public function debitAvailableAmountInCurrentTransaction(int $merchantId, int $amount, string $bizNo, string $idempotencyKey, array $extJson = [], string $traceNo = ''): MerchantAccountLedger
|
||||
{
|
||||
return $this->commandService->debitAvailableAmountInCurrentTransaction($merchantId, $amount, $bizNo, $idempotencyKey, $extJson, $traceNo);
|
||||
}
|
||||
|
||||
public function getBalanceSnapshot(int $merchantId): array
|
||||
{
|
||||
return $this->queryService->getBalanceSnapshot($merchantId);
|
||||
}
|
||||
|
||||
public function findById(int $id): ?MerchantAccount
|
||||
{
|
||||
return $this->queryService->findById($id);
|
||||
}
|
||||
}
|
||||
138
app/service/account/ledger/MerchantAccountLedgerService.php
Normal file
138
app/service/account/ledger/MerchantAccountLedgerService.php
Normal file
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
namespace app\service\account\ledger;
|
||||
|
||||
use app\common\base\BaseService;
|
||||
use app\common\constant\LedgerConstant;
|
||||
use app\model\merchant\MerchantAccountLedger;
|
||||
use app\repository\account\ledger\MerchantAccountLedgerRepository;
|
||||
|
||||
/**
|
||||
* 商户账户流水查询服务。
|
||||
*/
|
||||
class MerchantAccountLedgerService extends BaseService
|
||||
{
|
||||
/**
|
||||
* 构造函数,注入流水仓库。
|
||||
*/
|
||||
public function __construct(
|
||||
protected MerchantAccountLedgerRepository $merchantAccountLedgerRepository
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询账户流水。
|
||||
*/
|
||||
public function paginate(array $filters = [], int $page = 1, int $pageSize = 10)
|
||||
{
|
||||
$query = $this->baseQuery();
|
||||
|
||||
$keyword = trim((string) ($filters['keyword'] ?? ''));
|
||||
if ($keyword !== '') {
|
||||
$query->where(function ($builder) use ($keyword) {
|
||||
$builder->where('l.ledger_no', 'like', '%' . $keyword . '%')
|
||||
->orWhere('l.biz_no', 'like', '%' . $keyword . '%')
|
||||
->orWhere('l.trace_no', 'like', '%' . $keyword . '%')
|
||||
->orWhere('m.merchant_no', 'like', '%' . $keyword . '%')
|
||||
->orWhere('m.merchant_name', 'like', '%' . $keyword . '%')
|
||||
->orWhere('l.idempotency_key', 'like', '%' . $keyword . '%');
|
||||
});
|
||||
}
|
||||
|
||||
$merchantId = (string) ($filters['merchant_id'] ?? '');
|
||||
if ($merchantId !== '') {
|
||||
$query->where('l.merchant_id', (int) $merchantId);
|
||||
}
|
||||
|
||||
$bizType = (string) ($filters['biz_type'] ?? '');
|
||||
if ($bizType !== '') {
|
||||
$query->where('l.biz_type', (int) $bizType);
|
||||
}
|
||||
|
||||
$eventType = (string) ($filters['event_type'] ?? '');
|
||||
if ($eventType !== '') {
|
||||
$query->where('l.event_type', (int) $eventType);
|
||||
}
|
||||
|
||||
$direction = (string) ($filters['direction'] ?? '');
|
||||
if ($direction !== '') {
|
||||
$query->where('l.direction', (int) $direction);
|
||||
}
|
||||
|
||||
$paginator = $query
|
||||
->orderByDesc('l.id')
|
||||
->paginate(max(1, $pageSize), ['*'], 'page', max(1, $page));
|
||||
|
||||
$paginator->getCollection()->transform(function ($row) {
|
||||
return $this->decorateRow($row);
|
||||
});
|
||||
|
||||
return $paginator;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询流水详情。
|
||||
*/
|
||||
public function findById(int $id): ?MerchantAccountLedger
|
||||
{
|
||||
$row = $this->baseQuery()
|
||||
->where('l.id', $id)
|
||||
->first();
|
||||
|
||||
return $row ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化记录。
|
||||
*/
|
||||
private function decorateRow(object $row): object
|
||||
{
|
||||
$row->biz_type_text = (string) (LedgerConstant::bizTypeMap()[(int) $row->biz_type] ?? '未知');
|
||||
$row->event_type_text = (string) (LedgerConstant::eventTypeMap()[(int) $row->event_type] ?? '未知');
|
||||
$row->direction_text = (string) (LedgerConstant::directionMap()[(int) $row->direction] ?? '未知');
|
||||
$row->amount_text = $this->formatAmount((int) $row->amount);
|
||||
$row->available_before_text = $this->formatAmount((int) $row->available_before);
|
||||
$row->available_after_text = $this->formatAmount((int) $row->available_after);
|
||||
$row->frozen_before_text = $this->formatAmount((int) $row->frozen_before);
|
||||
$row->frozen_after_text = $this->formatAmount((int) $row->frozen_after);
|
||||
$row->created_at_text = $this->formatDateTime($row->created_at ?? null);
|
||||
$row->ext_json_text = $this->formatJson($row->ext_json ?? null);
|
||||
|
||||
return $row;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建查询。
|
||||
*/
|
||||
private function baseQuery()
|
||||
{
|
||||
return $this->merchantAccountLedgerRepository->query()
|
||||
->from('ma_merchant_account_ledger as l')
|
||||
->leftJoin('ma_merchant as m', 'l.merchant_id', '=', 'm.id')
|
||||
->leftJoin('ma_merchant_group as g', 'm.group_id', '=', 'g.id')
|
||||
->select([
|
||||
'l.id',
|
||||
'l.ledger_no',
|
||||
'l.merchant_id',
|
||||
'l.biz_type',
|
||||
'l.biz_no',
|
||||
'l.trace_no',
|
||||
'l.event_type',
|
||||
'l.direction',
|
||||
'l.amount',
|
||||
'l.available_before',
|
||||
'l.available_after',
|
||||
'l.frozen_before',
|
||||
'l.frozen_after',
|
||||
'l.idempotency_key',
|
||||
'l.remark',
|
||||
'l.ext_json',
|
||||
'l.created_at',
|
||||
])
|
||||
->selectRaw("COALESCE(m.merchant_no, '') AS merchant_no")
|
||||
->selectRaw("COALESCE(m.merchant_name, '') AS merchant_name")
|
||||
->selectRaw("COALESCE(m.merchant_short_name, '') AS merchant_short_name")
|
||||
->selectRaw("COALESCE(g.group_name, '') AS merchant_group_name");
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user