mirror of
https://gitee.com/technical-laohu/mpay_v2_webman.git
synced 2026-04-27 12:34:28 +08:00
重构初始化
This commit is contained in:
119
app/service/payment/config/PaymentChannelCommandService.php
Normal file
119
app/service/payment/config/PaymentChannelCommandService.php
Normal file
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
namespace app\service\payment\config;
|
||||
|
||||
use app\common\base\BaseService;
|
||||
use app\common\constant\CommonConstant;
|
||||
use app\exception\PaymentException;
|
||||
use app\model\payment\PaymentChannel;
|
||||
use app\repository\merchant\base\MerchantRepository;
|
||||
use app\repository\payment\config\PaymentChannelRepository;
|
||||
use app\repository\payment\config\PaymentPluginRepository;
|
||||
use app\repository\payment\config\PaymentTypeRepository;
|
||||
|
||||
/**
|
||||
* 支付通道命令服务。
|
||||
*/
|
||||
class PaymentChannelCommandService extends BaseService
|
||||
{
|
||||
public function __construct(
|
||||
protected MerchantRepository $merchantRepository,
|
||||
protected PaymentChannelRepository $paymentChannelRepository,
|
||||
protected PaymentPluginRepository $paymentPluginRepository,
|
||||
protected PaymentTypeRepository $paymentTypeRepository
|
||||
) {
|
||||
}
|
||||
|
||||
public function findById(int $id): ?PaymentChannel
|
||||
{
|
||||
return $this->paymentChannelRepository->find($id);
|
||||
}
|
||||
|
||||
public function create(array $data): PaymentChannel
|
||||
{
|
||||
$this->assertChannelNameUnique((string) ($data['name'] ?? ''));
|
||||
$this->assertMerchantExists($data);
|
||||
$this->assertPluginSupportsPayType($data);
|
||||
|
||||
return $this->paymentChannelRepository->create($data);
|
||||
}
|
||||
|
||||
public function update(int $id, array $data): ?PaymentChannel
|
||||
{
|
||||
$this->assertChannelNameUnique((string) ($data['name'] ?? ''), $id);
|
||||
$this->assertMerchantExists($data);
|
||||
$this->assertPluginSupportsPayType($data);
|
||||
|
||||
if (!$this->paymentChannelRepository->updateById($id, $data)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->paymentChannelRepository->find($id);
|
||||
}
|
||||
|
||||
public function delete(int $id): bool
|
||||
{
|
||||
return $this->paymentChannelRepository->deleteById($id);
|
||||
}
|
||||
|
||||
private function assertMerchantExists(array $data): void
|
||||
{
|
||||
if (!array_key_exists('merchant_id', $data)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$merchantId = (int) $data['merchant_id'];
|
||||
if ($merchantId === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$this->merchantRepository->find($merchantId)) {
|
||||
throw new PaymentException('所属商户不存在', 40209, [
|
||||
'merchant_id' => $merchantId,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
private function assertPluginSupportsPayType(array $data): void
|
||||
{
|
||||
$pluginCode = trim((string) ($data['plugin_code'] ?? ''));
|
||||
$payTypeId = (int) ($data['pay_type_id'] ?? 0);
|
||||
|
||||
if ($pluginCode === '' || $payTypeId <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$plugin = $this->paymentPluginRepository->findByCode($pluginCode);
|
||||
$paymentType = $this->paymentTypeRepository->find($payTypeId);
|
||||
|
||||
if (!$plugin || !$paymentType) {
|
||||
return;
|
||||
}
|
||||
|
||||
$payTypes = is_array($plugin->pay_types) ? $plugin->pay_types : [];
|
||||
$payTypeCodes = array_values(array_filter(array_map(static fn ($item) => trim((string) $item), $payTypes)));
|
||||
$payTypeCode = trim((string) $paymentType->code);
|
||||
|
||||
if ($payTypeCode === '' || !in_array($payTypeCode, $payTypeCodes, true)) {
|
||||
throw new PaymentException('支付插件不支持当前支付方式', 40210, [
|
||||
'plugin_code' => $pluginCode,
|
||||
'pay_type_code' => $payTypeCode,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
private function assertChannelNameUnique(string $name, int $ignoreId = 0): void
|
||||
{
|
||||
$name = trim($name);
|
||||
if ($name === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->paymentChannelRepository->existsByName($name, $ignoreId)) {
|
||||
throw new PaymentException('通道名称已存在', 40215, [
|
||||
'name' => $name,
|
||||
'ignore_id' => $ignoreId,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
228
app/service/payment/config/PaymentChannelQueryService.php
Normal file
228
app/service/payment/config/PaymentChannelQueryService.php
Normal file
@@ -0,0 +1,228 @@
|
||||
<?php
|
||||
|
||||
namespace app\service\payment\config;
|
||||
|
||||
use app\common\base\BaseService;
|
||||
use app\common\constant\CommonConstant;
|
||||
use app\model\payment\PaymentChannel;
|
||||
use app\repository\payment\config\PaymentChannelRepository;
|
||||
|
||||
/**
|
||||
* 支付通道查询服务。
|
||||
*/
|
||||
class PaymentChannelQueryService extends BaseService
|
||||
{
|
||||
public function __construct(
|
||||
protected PaymentChannelRepository $paymentChannelRepository
|
||||
) {
|
||||
}
|
||||
|
||||
public function enabledOptions(): array
|
||||
{
|
||||
return $this->paymentChannelRepository->query()
|
||||
->from('ma_payment_channel as c')
|
||||
->where('c.status', CommonConstant::STATUS_ENABLED)
|
||||
->orderBy('c.sort_no')
|
||||
->orderByDesc('c.id')
|
||||
->get([
|
||||
'c.id',
|
||||
'c.name',
|
||||
])
|
||||
->map(function (PaymentChannel $channel): array {
|
||||
return [
|
||||
'label' => sprintf('%s(%d)', (string) $channel->name, (int) $channel->id),
|
||||
'value' => (int) $channel->id,
|
||||
];
|
||||
})
|
||||
->values()
|
||||
->all();
|
||||
}
|
||||
|
||||
public function searchOptions(array $filters = [], int $page = 1, int $pageSize = 20): array
|
||||
{
|
||||
$query = $this->paymentChannelRepository->query()
|
||||
->from('ma_payment_channel as c')
|
||||
->leftJoin('ma_merchant as m', 'c.merchant_id', '=', 'm.id')
|
||||
->leftJoin('ma_payment_type as t', 'c.pay_type_id', '=', 't.id')
|
||||
->select([
|
||||
'c.id',
|
||||
'c.name',
|
||||
'c.merchant_id',
|
||||
'c.channel_mode',
|
||||
'c.pay_type_id',
|
||||
'c.plugin_code',
|
||||
'c.status',
|
||||
])
|
||||
->selectRaw("COALESCE(m.merchant_no, '') AS merchant_no")
|
||||
->selectRaw("COALESCE(m.merchant_name, '') AS merchant_name")
|
||||
->selectRaw("COALESCE(t.name, '') AS pay_type_name");
|
||||
|
||||
$ids = $this->normalizeIds($filters['ids'] ?? []);
|
||||
if (!empty($ids)) {
|
||||
$query->whereIn('c.id', $ids);
|
||||
} else {
|
||||
$query->where('c.status', CommonConstant::STATUS_ENABLED);
|
||||
|
||||
$keyword = trim((string) ($filters['keyword'] ?? ''));
|
||||
if ($keyword !== '') {
|
||||
$query->where(function ($builder) use ($keyword) {
|
||||
$builder->where('c.name', 'like', '%' . $keyword . '%')
|
||||
->orWhere('c.plugin_code', 'like', '%' . $keyword . '%')
|
||||
->orWhere('m.merchant_no', 'like', '%' . $keyword . '%')
|
||||
->orWhere('m.merchant_name', 'like', '%' . $keyword . '%');
|
||||
});
|
||||
}
|
||||
|
||||
if (($payTypeId = (int) ($filters['pay_type_id'] ?? 0)) > 0) {
|
||||
$query->where('c.pay_type_id', $payTypeId);
|
||||
}
|
||||
|
||||
if (array_key_exists('merchant_id', $filters) && $filters['merchant_id'] !== '' && $filters['merchant_id'] !== null) {
|
||||
$query->where('c.merchant_id', (int) $filters['merchant_id']);
|
||||
}
|
||||
|
||||
if (array_key_exists('channel_mode', $filters) && $filters['channel_mode'] !== '' && $filters['channel_mode'] !== null) {
|
||||
$query->where('c.channel_mode', (int) $filters['channel_mode']);
|
||||
}
|
||||
|
||||
$excludeIds = $this->normalizeIds($filters['exclude_ids'] ?? []);
|
||||
if (!empty($excludeIds)) {
|
||||
$query->whereNotIn('c.id', $excludeIds);
|
||||
}
|
||||
}
|
||||
|
||||
$paginator = $query
|
||||
->orderBy('c.sort_no')
|
||||
->orderByDesc('c.id')
|
||||
->paginate(max(1, $pageSize), ['*'], 'page', max(1, $page));
|
||||
|
||||
return [
|
||||
'list' => collect($paginator->items())
|
||||
->map(function ($channel): array {
|
||||
return [
|
||||
'label' => sprintf('%s(%d)', (string) $channel->name, (int) $channel->id),
|
||||
'value' => (int) $channel->id,
|
||||
'merchant_id' => (int) $channel->merchant_id,
|
||||
'merchant_no' => (string) ($channel->merchant_no ?? ''),
|
||||
'merchant_name' => (string) ($channel->merchant_name ?? ''),
|
||||
'channel_mode' => (int) $channel->channel_mode,
|
||||
'pay_type_id' => (int) $channel->pay_type_id,
|
||||
'pay_type_name' => (string) ($channel->pay_type_name ?? ''),
|
||||
'plugin_code' => (string) $channel->plugin_code,
|
||||
];
|
||||
})
|
||||
->values()
|
||||
->all(),
|
||||
'total' => (int) $paginator->total(),
|
||||
'page' => (int) $paginator->currentPage(),
|
||||
'size' => (int) $paginator->perPage(),
|
||||
];
|
||||
}
|
||||
|
||||
public function routeOptions(array $filters = []): array
|
||||
{
|
||||
$query = $this->paymentChannelRepository->query()
|
||||
->from('ma_payment_channel as c')
|
||||
->leftJoin('ma_payment_type as t', 'c.pay_type_id', '=', 't.id')
|
||||
->where('c.status', CommonConstant::STATUS_ENABLED)
|
||||
->select([
|
||||
'c.id',
|
||||
'c.name',
|
||||
'c.merchant_id',
|
||||
'c.channel_mode',
|
||||
'c.pay_type_id',
|
||||
'c.plugin_code',
|
||||
])
|
||||
->selectRaw("COALESCE(t.name, '') AS pay_type_name");
|
||||
|
||||
if (($payTypeId = (int) ($filters['pay_type_id'] ?? 0)) > 0) {
|
||||
$query->where('c.pay_type_id', $payTypeId);
|
||||
}
|
||||
|
||||
if (array_key_exists('merchant_id', $filters) && $filters['merchant_id'] !== '') {
|
||||
$query->where('c.merchant_id', (int) $filters['merchant_id']);
|
||||
}
|
||||
|
||||
return $query
|
||||
->orderBy('c.sort_no')
|
||||
->orderByDesc('c.id')
|
||||
->get()
|
||||
->map(function (PaymentChannel $channel): array {
|
||||
return [
|
||||
'label' => sprintf('%s(%d)', (string) $channel->name, (int) $channel->id),
|
||||
'value' => (int) $channel->id,
|
||||
'merchant_id' => (int) $channel->merchant_id,
|
||||
'channel_mode' => (int) $channel->channel_mode,
|
||||
'pay_type_id' => (int) $channel->pay_type_id,
|
||||
'plugin_code' => (string) $channel->plugin_code,
|
||||
'pay_type_name' => (string) ($channel->pay_type_name ?? ''),
|
||||
];
|
||||
})
|
||||
->values()
|
||||
->all();
|
||||
}
|
||||
|
||||
public function paginate(array $filters = [], int $page = 1, int $pageSize = 10)
|
||||
{
|
||||
$query = $this->paymentChannelRepository->query()
|
||||
->from('ma_payment_channel as c')
|
||||
->leftJoin('ma_merchant as m', 'c.merchant_id', '=', 'm.id')
|
||||
->select([
|
||||
'c.*',
|
||||
])
|
||||
->selectRaw("COALESCE(m.merchant_no, '') AS merchant_no")
|
||||
->selectRaw("COALESCE(m.merchant_name, '') AS merchant_name");
|
||||
|
||||
$keyword = trim((string) ($filters['keyword'] ?? ''));
|
||||
if ($keyword !== '') {
|
||||
$query->where(function ($builder) use ($keyword) {
|
||||
$builder->where('c.name', 'like', '%' . $keyword . '%')
|
||||
->orWhere('c.plugin_code', 'like', '%' . $keyword . '%')
|
||||
->orWhere('m.merchant_no', 'like', '%' . $keyword . '%')
|
||||
->orWhere('m.merchant_name', 'like', '%' . $keyword . '%');
|
||||
});
|
||||
}
|
||||
|
||||
if (array_key_exists('merchant_id', $filters) && $filters['merchant_id'] !== '') {
|
||||
$query->where('c.merchant_id', (int) $filters['merchant_id']);
|
||||
}
|
||||
|
||||
if (($payTypeId = (int) ($filters['pay_type_id'] ?? 0)) > 0) {
|
||||
$query->where('c.pay_type_id', $payTypeId);
|
||||
}
|
||||
|
||||
$pluginCode = trim((string) ($filters['plugin_code'] ?? ''));
|
||||
if ($pluginCode !== '') {
|
||||
$query->where('c.plugin_code', 'like', '%' . $pluginCode . '%');
|
||||
}
|
||||
|
||||
if (array_key_exists('channel_mode', $filters) && $filters['channel_mode'] !== '') {
|
||||
$query->where('c.channel_mode', (int) $filters['channel_mode']);
|
||||
}
|
||||
|
||||
if (array_key_exists('status', $filters) && $filters['status'] !== '') {
|
||||
$query->where('c.status', (int) $filters['status']);
|
||||
}
|
||||
|
||||
return $query
|
||||
->orderBy('c.sort_no')
|
||||
->orderByDesc('c.id')
|
||||
->paginate(max(1, $pageSize), ['*'], 'page', max(1, $page));
|
||||
}
|
||||
|
||||
public function findById(int $id): ?PaymentChannel
|
||||
{
|
||||
return $this->paymentChannelRepository->find($id);
|
||||
}
|
||||
|
||||
private function normalizeIds(array|string|int $ids): array
|
||||
{
|
||||
if (is_string($ids)) {
|
||||
$ids = array_filter(array_map('trim', explode(',', $ids)));
|
||||
} elseif (!is_array($ids)) {
|
||||
$ids = [$ids];
|
||||
}
|
||||
|
||||
return array_values(array_filter(array_map(static fn ($id) => (int) $id, $ids), static fn ($id) => $id > 0));
|
||||
}
|
||||
}
|
||||
58
app/service/payment/config/PaymentChannelService.php
Normal file
58
app/service/payment/config/PaymentChannelService.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace app\service\payment\config;
|
||||
|
||||
use app\common\base\BaseService;
|
||||
use app\model\payment\PaymentChannel;
|
||||
|
||||
/**
|
||||
* 支付通道门面服务。
|
||||
*/
|
||||
class PaymentChannelService extends BaseService
|
||||
{
|
||||
public function __construct(
|
||||
protected PaymentChannelQueryService $queryService,
|
||||
protected PaymentChannelCommandService $commandService
|
||||
) {
|
||||
}
|
||||
|
||||
public function enabledOptions(): array
|
||||
{
|
||||
return $this->queryService->enabledOptions();
|
||||
}
|
||||
|
||||
public function searchOptions(array $filters = [], int $page = 1, int $pageSize = 20): array
|
||||
{
|
||||
return $this->queryService->searchOptions($filters, $page, $pageSize);
|
||||
}
|
||||
|
||||
public function routeOptions(array $filters = []): array
|
||||
{
|
||||
return $this->queryService->routeOptions($filters);
|
||||
}
|
||||
|
||||
public function paginate(array $filters = [], int $page = 1, int $pageSize = 10)
|
||||
{
|
||||
return $this->queryService->paginate($filters, $page, $pageSize);
|
||||
}
|
||||
|
||||
public function findById(int $id): ?PaymentChannel
|
||||
{
|
||||
return $this->queryService->findById($id);
|
||||
}
|
||||
|
||||
public function create(array $data): PaymentChannel
|
||||
{
|
||||
return $this->commandService->create($data);
|
||||
}
|
||||
|
||||
public function update(int $id, array $data): ?PaymentChannel
|
||||
{
|
||||
return $this->commandService->update($id, $data);
|
||||
}
|
||||
|
||||
public function delete(int $id): bool
|
||||
{
|
||||
return $this->commandService->delete($id);
|
||||
}
|
||||
}
|
||||
228
app/service/payment/config/PaymentPluginConfService.php
Normal file
228
app/service/payment/config/PaymentPluginConfService.php
Normal file
@@ -0,0 +1,228 @@
|
||||
<?php
|
||||
|
||||
namespace app\service\payment\config;
|
||||
|
||||
use app\common\base\BaseService;
|
||||
use app\exception\PaymentException;
|
||||
use app\model\payment\PaymentPluginConf;
|
||||
use app\repository\payment\config\PaymentPluginConfRepository;
|
||||
use app\repository\payment\config\PaymentPluginRepository;
|
||||
|
||||
/**
|
||||
* 支付插件配置服务。
|
||||
*
|
||||
* 负责插件公共配置的增删改查和下拉选项输出。
|
||||
*/
|
||||
class PaymentPluginConfService extends BaseService
|
||||
{
|
||||
public function __construct(
|
||||
protected PaymentPluginConfRepository $paymentPluginConfRepository,
|
||||
protected PaymentPluginRepository $paymentPluginRepository
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询插件配置。
|
||||
*/
|
||||
public function paginate(array $filters = [], int $page = 1, int $pageSize = 10)
|
||||
{
|
||||
$query = $this->paymentPluginConfRepository->query()
|
||||
->from('ma_payment_plugin_conf as c')
|
||||
->leftJoin('ma_payment_plugin as p', 'c.plugin_code', '=', 'p.code')
|
||||
->select([
|
||||
'c.id',
|
||||
'c.plugin_code',
|
||||
'c.config',
|
||||
'c.settlement_cycle_type',
|
||||
'c.settlement_cutoff_time',
|
||||
'c.remark',
|
||||
'c.created_at',
|
||||
'c.updated_at',
|
||||
])
|
||||
->selectRaw("COALESCE(NULLIF(p.name, ''), c.plugin_code) AS plugin_name");
|
||||
|
||||
$keyword = trim((string) ($filters['keyword'] ?? ''));
|
||||
if ($keyword !== '') {
|
||||
$query->where(function ($builder) use ($keyword) {
|
||||
$builder->where('c.plugin_code', 'like', '%' . $keyword . '%')
|
||||
->orWhere('c.remark', 'like', '%' . $keyword . '%')
|
||||
->orWhere('p.name', 'like', '%' . $keyword . '%');
|
||||
});
|
||||
}
|
||||
|
||||
$pluginCode = trim((string) ($filters['plugin_code'] ?? ''));
|
||||
if ($pluginCode !== '') {
|
||||
$query->where('c.plugin_code', $pluginCode);
|
||||
}
|
||||
|
||||
return $query
|
||||
->orderByDesc('c.id')
|
||||
->paginate(max(1, $pageSize), ['*'], 'page', max(1, $page));
|
||||
}
|
||||
|
||||
/**
|
||||
* 按 ID 查询插件配置。
|
||||
*/
|
||||
public function findById(int $id): ?PaymentPluginConf
|
||||
{
|
||||
return $this->paymentPluginConfRepository->find($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增插件配置。
|
||||
*/
|
||||
public function create(array $data): PaymentPluginConf
|
||||
{
|
||||
$payload = $this->normalizePayload($data);
|
||||
$this->assertPluginExists((string) $payload['plugin_code']);
|
||||
|
||||
return $this->paymentPluginConfRepository->create($payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改插件配置。
|
||||
*/
|
||||
public function update(int $id, array $data): ?PaymentPluginConf
|
||||
{
|
||||
$payload = $this->normalizePayload($data);
|
||||
$this->assertPluginExists((string) $payload['plugin_code']);
|
||||
|
||||
if (!$this->paymentPluginConfRepository->updateById($id, $payload)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->paymentPluginConfRepository->find($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除插件配置。
|
||||
*/
|
||||
public function delete(int $id): bool
|
||||
{
|
||||
return $this->paymentPluginConfRepository->deleteById($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询插件配置下拉选项。
|
||||
*/
|
||||
public function options(?string $pluginCode = null): array
|
||||
{
|
||||
$pluginCode = trim((string) $pluginCode);
|
||||
|
||||
$query = $this->paymentPluginConfRepository->query()
|
||||
->from('ma_payment_plugin_conf as c')
|
||||
->leftJoin('ma_payment_plugin as p', 'c.plugin_code', '=', 'p.code')
|
||||
->select([
|
||||
'c.id',
|
||||
'c.plugin_code',
|
||||
])
|
||||
->selectRaw("COALESCE(NULLIF(p.name, ''), c.plugin_code) AS plugin_name")
|
||||
->orderByDesc('c.id');
|
||||
|
||||
if ($pluginCode !== '') {
|
||||
$query->where('c.plugin_code', $pluginCode);
|
||||
}
|
||||
|
||||
return $query->get()->map(function ($item): array {
|
||||
$pluginName = trim((string) ($item->plugin_name ?? ''));
|
||||
$pluginCode = trim((string) ($item->plugin_code ?? ''));
|
||||
$label = $pluginName !== '' ? $pluginName : $pluginCode;
|
||||
|
||||
return [
|
||||
'label' => sprintf('%s(%d)', $label, (int) $item->id),
|
||||
'value' => (int) $item->id,
|
||||
'plugin_code' => $pluginCode,
|
||||
'plugin_name' => $pluginName !== '' ? $pluginName : $pluginCode,
|
||||
];
|
||||
})->values()->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* 远程查询插件配置选择项。
|
||||
*/
|
||||
public function searchOptions(array $filters = [], int $page = 1, int $pageSize = 20): array
|
||||
{
|
||||
$query = $this->paymentPluginConfRepository->query()
|
||||
->from('ma_payment_plugin_conf as c')
|
||||
->leftJoin('ma_payment_plugin as p', 'c.plugin_code', '=', 'p.code')
|
||||
->select([
|
||||
'c.id',
|
||||
'c.plugin_code',
|
||||
])
|
||||
->selectRaw("COALESCE(NULLIF(p.name, ''), c.plugin_code) AS plugin_name")
|
||||
->orderByDesc('c.id');
|
||||
|
||||
$ids = $filters['ids'] ?? [];
|
||||
if (is_array($ids) && $ids !== []) {
|
||||
$query->whereIn('c.id', array_map('intval', $ids));
|
||||
} else {
|
||||
$pluginCode = trim((string) ($filters['plugin_code'] ?? ''));
|
||||
if ($pluginCode !== '') {
|
||||
$query->where('c.plugin_code', $pluginCode);
|
||||
}
|
||||
|
||||
$keyword = trim((string) ($filters['keyword'] ?? ''));
|
||||
if ($keyword !== '') {
|
||||
$query->where(function ($builder) use ($keyword) {
|
||||
$builder->where('c.plugin_code', 'like', '%' . $keyword . '%')
|
||||
->orWhere('p.name', 'like', '%' . $keyword . '%')
|
||||
->orWhere('c.remark', 'like', '%' . $keyword . '%');
|
||||
|
||||
if (ctype_digit($keyword)) {
|
||||
$builder->orWhere('c.id', (int) $keyword);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$paginator = $query->paginate(max(1, $pageSize), ['*'], 'page', max(1, $page));
|
||||
|
||||
return [
|
||||
'list' => collect($paginator->items())->map(function ($item): array {
|
||||
$pluginName = trim((string) ($item->plugin_name ?? ''));
|
||||
$pluginCode = trim((string) ($item->plugin_code ?? ''));
|
||||
$label = $pluginName !== '' ? $pluginName : $pluginCode;
|
||||
|
||||
return [
|
||||
'label' => sprintf('%s(%d)', $label, (int) $item->id),
|
||||
'value' => (int) $item->id,
|
||||
'plugin_code' => $pluginCode,
|
||||
'plugin_name' => $pluginName !== '' ? $pluginName : $pluginCode,
|
||||
];
|
||||
})->values()->all(),
|
||||
'total' => $paginator->total(),
|
||||
'page' => $paginator->currentPage(),
|
||||
'size' => $paginator->perPage(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 标准化写入数据。
|
||||
*/
|
||||
private function normalizePayload(array $data): array
|
||||
{
|
||||
return [
|
||||
'plugin_code' => trim((string) ($data['plugin_code'] ?? '')),
|
||||
'config' => is_array($data['config'] ?? null) ? $data['config'] : [],
|
||||
'settlement_cycle_type' => (int) ($data['settlement_cycle_type'] ?? 1),
|
||||
'settlement_cutoff_time' => trim((string) ($data['settlement_cutoff_time'] ?? '23:59:59')) ?: '23:59:59',
|
||||
'remark' => trim((string) ($data['remark'] ?? '')),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验插件是否存在。
|
||||
*/
|
||||
private function assertPluginExists(string $pluginCode): void
|
||||
{
|
||||
if ($pluginCode === '') {
|
||||
throw new PaymentException('插件编码不能为空', 40230);
|
||||
}
|
||||
|
||||
if (!$this->paymentPluginRepository->findByCode($pluginCode)) {
|
||||
throw new PaymentException('支付插件不存在', 40231, [
|
||||
'plugin_code' => $pluginCode,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
207
app/service/payment/config/PaymentPluginService.php
Normal file
207
app/service/payment/config/PaymentPluginService.php
Normal file
@@ -0,0 +1,207 @@
|
||||
<?php
|
||||
|
||||
namespace app\service\payment\config;
|
||||
|
||||
use app\common\base\BaseService;
|
||||
use app\exception\PaymentException;
|
||||
use app\model\payment\PaymentPlugin;
|
||||
use app\repository\payment\config\PaymentPluginRepository;
|
||||
|
||||
/**
|
||||
* 支付插件管理服务。
|
||||
*
|
||||
* 负责插件目录同步、插件列表查询,以及 JSON 字段写入前的归一化。
|
||||
*/
|
||||
class PaymentPluginService extends BaseService
|
||||
{
|
||||
/**
|
||||
* 构造函数,注入支付插件仓库。
|
||||
*/
|
||||
public function __construct(
|
||||
protected PaymentPluginRepository $paymentPluginRepository,
|
||||
protected PaymentPluginSyncService $paymentPluginSyncService
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询支付插件。
|
||||
*/
|
||||
public function paginate(array $filters = [], int $page = 1, int $pageSize = 10)
|
||||
{
|
||||
$query = $this->paymentPluginRepository->query();
|
||||
|
||||
$keyword = trim((string) ($filters['keyword'] ?? ''));
|
||||
if ($keyword !== '') {
|
||||
$query->where(function ($builder) use ($keyword) {
|
||||
$builder->where('code', 'like', '%' . $keyword . '%')
|
||||
->orWhere('name', 'like', '%' . $keyword . '%')
|
||||
->orWhere('class_name', 'like', '%' . $keyword . '%');
|
||||
});
|
||||
}
|
||||
|
||||
$code = trim((string) ($filters['code'] ?? ''));
|
||||
if ($code !== '') {
|
||||
$query->where('code', 'like', '%' . $code . '%');
|
||||
}
|
||||
|
||||
$name = trim((string) ($filters['name'] ?? ''));
|
||||
if ($name !== '') {
|
||||
$query->where('name', 'like', '%' . $name . '%');
|
||||
}
|
||||
|
||||
if (array_key_exists('status', $filters) && $filters['status'] !== '') {
|
||||
$query->where('status', (int) $filters['status']);
|
||||
}
|
||||
|
||||
return $query
|
||||
->orderBy('code')
|
||||
->paginate(max(1, $pageSize), ['*'], 'page', max(1, $page));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询启用中的支付插件选项。
|
||||
*/
|
||||
public function enabledOptions(): array
|
||||
{
|
||||
return $this->paymentPluginRepository->enabledList(['code', 'name'])
|
||||
->map(function (PaymentPlugin $plugin): array {
|
||||
return [
|
||||
'label' => sprintf('%s(%s)', (string) $plugin->name, (string) $plugin->code),
|
||||
'value' => (string) $plugin->code,
|
||||
'code' => (string) $plugin->code,
|
||||
'name' => (string) $plugin->name,
|
||||
];
|
||||
})
|
||||
->values()
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* 远程查询支付插件选择项。
|
||||
*/
|
||||
public function searchOptions(array $filters = [], int $page = 1, int $pageSize = 20): array
|
||||
{
|
||||
$query = $this->paymentPluginRepository->query()
|
||||
->where('status', 1)
|
||||
->select(['code', 'name', 'pay_types'])
|
||||
->orderBy('code');
|
||||
|
||||
$ids = $filters['ids'] ?? [];
|
||||
if (is_array($ids) && $ids !== []) {
|
||||
$query->whereIn('code', array_values(array_filter(array_map('strval', $ids))));
|
||||
} else {
|
||||
$keyword = trim((string) ($filters['keyword'] ?? ''));
|
||||
if ($keyword !== '') {
|
||||
$query->where(function ($builder) use ($keyword) {
|
||||
$builder->where('code', 'like', '%' . $keyword . '%')
|
||||
->orWhere('name', 'like', '%' . $keyword . '%');
|
||||
});
|
||||
}
|
||||
|
||||
$payTypeCode = trim((string) ($filters['pay_type_code'] ?? ''));
|
||||
if ($payTypeCode !== '') {
|
||||
$query->whereJsonContains('pay_types', $payTypeCode);
|
||||
}
|
||||
}
|
||||
|
||||
$paginator = $query->paginate(max(1, $pageSize), ['*'], 'page', max(1, $page));
|
||||
|
||||
return [
|
||||
'list' => collect($paginator->items())->map(function (PaymentPlugin $plugin): array {
|
||||
return [
|
||||
'label' => sprintf('%s(%s)', (string) $plugin->name, (string) $plugin->code),
|
||||
'value' => (string) $plugin->code,
|
||||
'code' => (string) $plugin->code,
|
||||
'name' => (string) $plugin->name,
|
||||
'pay_types' => is_array($plugin->pay_types) ? array_values($plugin->pay_types) : [],
|
||||
];
|
||||
})->values()->all(),
|
||||
'total' => $paginator->total(),
|
||||
'page' => $paginator->currentPage(),
|
||||
'size' => $paginator->perPage(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询通道配置场景使用的支付插件选项。
|
||||
*/
|
||||
public function channelOptions(): array
|
||||
{
|
||||
return $this->paymentPluginRepository->enabledList([
|
||||
'code',
|
||||
'name',
|
||||
'pay_types',
|
||||
])
|
||||
->map(function (PaymentPlugin $plugin): array {
|
||||
return [
|
||||
'label' => sprintf('%s(%s)', (string) $plugin->name, (string) $plugin->code),
|
||||
'value' => (string) $plugin->code,
|
||||
'code' => (string) $plugin->code,
|
||||
'name' => (string) $plugin->name,
|
||||
'pay_types' => is_array($plugin->pay_types) ? array_values($plugin->pay_types) : [],
|
||||
];
|
||||
})
|
||||
->values()
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* 按插件编码查询插件。
|
||||
*/
|
||||
public function findByCode(string $code): ?PaymentPlugin
|
||||
{
|
||||
return $this->paymentPluginRepository->findByCode($code);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询插件配置结构。
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function getSchema(string $code): array
|
||||
{
|
||||
$plugin = $this->paymentPluginRepository->findByCode($code);
|
||||
if (!$plugin) {
|
||||
throw new PaymentException('支付插件不存在', 404, [
|
||||
'plugin_code' => $code,
|
||||
]);
|
||||
}
|
||||
|
||||
return [
|
||||
'config_schema' => is_array($plugin->config_schema) ? array_values($plugin->config_schema) : [],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新支付插件。
|
||||
*/
|
||||
public function update(string $code, array $data): ?PaymentPlugin
|
||||
{
|
||||
$payload = [];
|
||||
if (array_key_exists('status', $data)) {
|
||||
$payload['status'] = (int) $data['status'];
|
||||
}
|
||||
|
||||
if (array_key_exists('remark', $data)) {
|
||||
$payload['remark'] = trim((string) $data['remark']);
|
||||
}
|
||||
|
||||
if ($payload === []) {
|
||||
return $this->paymentPluginRepository->findByCode($code);
|
||||
}
|
||||
|
||||
if (!$this->paymentPluginRepository->updateByKey($code, $payload)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->paymentPluginRepository->findByCode($code);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从插件目录刷新并同步支付插件定义。
|
||||
*/
|
||||
public function refreshFromClasses(): array
|
||||
{
|
||||
return $this->paymentPluginSyncService->refreshFromClasses();
|
||||
}
|
||||
}
|
||||
122
app/service/payment/config/PaymentPluginSyncService.php
Normal file
122
app/service/payment/config/PaymentPluginSyncService.php
Normal file
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
namespace app\service\payment\config;
|
||||
|
||||
use app\common\base\BaseService;
|
||||
use app\common\interface\PayPluginInterface;
|
||||
use app\common\interface\PaymentInterface;
|
||||
use app\exception\PaymentException;
|
||||
use app\model\payment\PaymentPlugin;
|
||||
use app\repository\payment\config\PaymentPluginRepository;
|
||||
|
||||
/**
|
||||
* 支付插件同步服务。
|
||||
*
|
||||
* 负责扫描插件目录、实例化插件类并同步数据库定义。
|
||||
*/
|
||||
class PaymentPluginSyncService extends BaseService
|
||||
{
|
||||
public function __construct(
|
||||
protected PaymentPluginRepository $paymentPluginRepository
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 从插件目录刷新并同步支付插件定义。
|
||||
*/
|
||||
public function refreshFromClasses(): array
|
||||
{
|
||||
$directory = base_path() . DIRECTORY_SEPARATOR . 'app' . DIRECTORY_SEPARATOR . 'common' . DIRECTORY_SEPARATOR . 'payment';
|
||||
$files = glob($directory . DIRECTORY_SEPARATOR . '*.php') ?: [];
|
||||
|
||||
$rows = [];
|
||||
foreach ($files as $file) {
|
||||
$shortClassName = pathinfo($file, PATHINFO_FILENAME);
|
||||
$className = 'app\\common\\payment\\' . $shortClassName;
|
||||
$plugin = $this->instantiatePlugin($className);
|
||||
if (!$plugin) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$code = trim((string) $plugin->getCode());
|
||||
if ($code === '') {
|
||||
throw new PaymentException('支付插件编码不能为空', 40220, ['class_name' => $className]);
|
||||
}
|
||||
|
||||
if (isset($rows[$code])) {
|
||||
throw new PaymentException('支付插件编码重复', 40221, [
|
||||
'plugin_code' => $code,
|
||||
'class_name' => $className,
|
||||
]);
|
||||
}
|
||||
|
||||
$rows[$code] = [
|
||||
'code' => $plugin->getCode(),
|
||||
'name' => $plugin->getName(),
|
||||
'class_name' => $shortClassName,
|
||||
'config_schema' => $plugin->getConfigSchema(),
|
||||
'pay_types' => $plugin->getEnabledPayTypes(),
|
||||
'transfer_types' => $plugin->getEnabledTransferTypes(),
|
||||
'version' => $plugin->getVersion(),
|
||||
'author' => $plugin->getAuthorName(),
|
||||
'link' => $plugin->getAuthorLink(),
|
||||
];
|
||||
}
|
||||
|
||||
ksort($rows);
|
||||
|
||||
$existing = $this->paymentPluginRepository->query()
|
||||
->get()
|
||||
->keyBy('code')
|
||||
->all();
|
||||
|
||||
$this->transaction(function () use ($rows, $existing) {
|
||||
foreach ($rows as $code => $row) {
|
||||
/** @var PaymentPlugin|null $current */
|
||||
$current = $existing[$code] ?? null;
|
||||
$payload = array_merge($row, [
|
||||
'status' => (int) ($current->status ?? 1),
|
||||
'remark' => (string) ($current->remark ?? ''),
|
||||
]);
|
||||
|
||||
if ($current) {
|
||||
$current->fill($payload);
|
||||
$current->save();
|
||||
unset($existing[$code]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->paymentPluginRepository->create($payload);
|
||||
}
|
||||
|
||||
foreach ($existing as $plugin) {
|
||||
$plugin->delete();
|
||||
}
|
||||
});
|
||||
|
||||
return [
|
||||
'count' => count($rows),
|
||||
'plugins' => $this->paymentPluginRepository->query()
|
||||
->orderBy('code')
|
||||
->get()
|
||||
->values()
|
||||
->all(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 实例化插件类并过滤非支付插件类。
|
||||
*/
|
||||
private function instantiatePlugin(string $className): null|(PaymentInterface & PayPluginInterface)
|
||||
{
|
||||
if (!class_exists($className)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$instance = container_make($className, []);
|
||||
if (!$instance instanceof PayPluginInterface || !$instance instanceof PaymentInterface) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $instance;
|
||||
}
|
||||
}
|
||||
162
app/service/payment/config/PaymentPollGroupBindService.php
Normal file
162
app/service/payment/config/PaymentPollGroupBindService.php
Normal file
@@ -0,0 +1,162 @@
|
||||
<?php
|
||||
|
||||
namespace app\service\payment\config;
|
||||
|
||||
use app\common\base\BaseService;
|
||||
use app\exception\PaymentException;
|
||||
use app\model\payment\PaymentPollGroupBind;
|
||||
use app\repository\merchant\base\MerchantGroupRepository;
|
||||
use app\repository\payment\config\PaymentPollGroupBindRepository;
|
||||
use app\repository\payment\config\PaymentPollGroupRepository;
|
||||
|
||||
/**
|
||||
* 商户分组路由绑定服务。
|
||||
*/
|
||||
class PaymentPollGroupBindService extends BaseService
|
||||
{
|
||||
public function __construct(
|
||||
protected PaymentPollGroupBindRepository $paymentPollGroupBindRepository,
|
||||
protected MerchantGroupRepository $merchantGroupRepository,
|
||||
protected PaymentPollGroupRepository $paymentPollGroupRepository
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询商户分组路由绑定。
|
||||
*/
|
||||
public function paginate(array $filters = [], int $page = 1, int $pageSize = 10)
|
||||
{
|
||||
$query = $this->paymentPollGroupBindRepository->query()
|
||||
->from('ma_payment_poll_group_bind as b')
|
||||
->leftJoin('ma_merchant_group as mg', 'mg.id', '=', 'b.merchant_group_id')
|
||||
->leftJoin('ma_payment_type as t', 't.id', '=', 'b.pay_type_id')
|
||||
->leftJoin('ma_payment_poll_group as pg', 'pg.id', '=', 'b.poll_group_id')
|
||||
->select([
|
||||
'b.id',
|
||||
'b.merchant_group_id',
|
||||
'b.pay_type_id',
|
||||
'b.poll_group_id',
|
||||
'b.status',
|
||||
'b.remark',
|
||||
'b.created_at',
|
||||
'b.updated_at',
|
||||
'mg.group_name as merchant_group_name',
|
||||
't.name as pay_type_name',
|
||||
'pg.group_name as poll_group_name',
|
||||
'pg.route_mode',
|
||||
]);
|
||||
|
||||
$keyword = trim((string) ($filters['keyword'] ?? ''));
|
||||
if ($keyword !== '') {
|
||||
$query->where(function ($builder) use ($keyword) {
|
||||
$builder->where('mg.group_name', 'like', '%' . $keyword . '%')
|
||||
->orWhere('t.name', 'like', '%' . $keyword . '%')
|
||||
->orWhere('pg.group_name', 'like', '%' . $keyword . '%');
|
||||
});
|
||||
}
|
||||
|
||||
if (($merchantGroupId = (int) ($filters['merchant_group_id'] ?? 0)) > 0) {
|
||||
$query->where('b.merchant_group_id', $merchantGroupId);
|
||||
}
|
||||
|
||||
if (($payTypeId = (int) ($filters['pay_type_id'] ?? 0)) > 0) {
|
||||
$query->where('b.pay_type_id', $payTypeId);
|
||||
}
|
||||
|
||||
if (($pollGroupId = (int) ($filters['poll_group_id'] ?? 0)) > 0) {
|
||||
$query->where('b.poll_group_id', $pollGroupId);
|
||||
}
|
||||
|
||||
if (array_key_exists('status', $filters) && $filters['status'] !== '') {
|
||||
$query->where('b.status', (int) $filters['status']);
|
||||
}
|
||||
|
||||
return $query
|
||||
->orderByDesc('b.id')
|
||||
->paginate(max(1, $pageSize), ['*'], 'page', max(1, $page));
|
||||
}
|
||||
|
||||
public function findById(int $id): ?PaymentPollGroupBind
|
||||
{
|
||||
return $this->paymentPollGroupBindRepository->find($id);
|
||||
}
|
||||
|
||||
public function create(array $data): PaymentPollGroupBind
|
||||
{
|
||||
$this->assertBindingUnique((int) $data['merchant_group_id'], (int) $data['pay_type_id']);
|
||||
$this->assertPollGroupMatchesPayType($data);
|
||||
|
||||
return $this->paymentPollGroupBindRepository->create($this->normalizePayload($data));
|
||||
}
|
||||
|
||||
public function update(int $id, array $data): ?PaymentPollGroupBind
|
||||
{
|
||||
$current = $this->paymentPollGroupBindRepository->find($id);
|
||||
if (!$current) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$merchantGroupId = (int) ($data['merchant_group_id'] ?? $current->merchant_group_id);
|
||||
$payTypeId = (int) ($data['pay_type_id'] ?? $current->pay_type_id);
|
||||
$this->assertBindingUnique($merchantGroupId, $payTypeId, $id);
|
||||
$this->assertPollGroupMatchesPayType(array_merge($current->toArray(), $data));
|
||||
|
||||
if (!$this->paymentPollGroupBindRepository->updateById($id, $this->normalizePayload($data))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->paymentPollGroupBindRepository->find($id);
|
||||
}
|
||||
|
||||
public function delete(int $id): bool
|
||||
{
|
||||
return $this->paymentPollGroupBindRepository->deleteById($id);
|
||||
}
|
||||
|
||||
private function normalizePayload(array $data): array
|
||||
{
|
||||
return [
|
||||
'merchant_group_id' => (int) $data['merchant_group_id'],
|
||||
'pay_type_id' => (int) $data['pay_type_id'],
|
||||
'poll_group_id' => (int) $data['poll_group_id'],
|
||||
'status' => (int) ($data['status'] ?? 1),
|
||||
'remark' => trim((string) ($data['remark'] ?? '')),
|
||||
];
|
||||
}
|
||||
|
||||
private function assertBindingUnique(int $merchantGroupId, int $payTypeId, int $ignoreId = 0): void
|
||||
{
|
||||
$query = $this->paymentPollGroupBindRepository->query()
|
||||
->where('merchant_group_id', $merchantGroupId)
|
||||
->where('pay_type_id', $payTypeId);
|
||||
|
||||
if ($ignoreId > 0) {
|
||||
$query->where('id', '<>', $ignoreId);
|
||||
}
|
||||
|
||||
if ($query->exists()) {
|
||||
throw new PaymentException('当前商户分组与支付方式已绑定轮询组', 40232, [
|
||||
'merchant_group_id' => $merchantGroupId,
|
||||
'pay_type_id' => $payTypeId,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
private function assertPollGroupMatchesPayType(array $data): void
|
||||
{
|
||||
$pollGroupId = (int) ($data['poll_group_id'] ?? 0);
|
||||
$payTypeId = (int) ($data['pay_type_id'] ?? 0);
|
||||
|
||||
$pollGroup = $this->paymentPollGroupRepository->find($pollGroupId);
|
||||
if (!$pollGroup) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((int) $pollGroup->pay_type_id !== $payTypeId) {
|
||||
throw new PaymentException('轮询组与支付方式不一致', 40233, [
|
||||
'poll_group_id' => $pollGroupId,
|
||||
'pay_type_id' => $payTypeId,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
184
app/service/payment/config/PaymentPollGroupChannelService.php
Normal file
184
app/service/payment/config/PaymentPollGroupChannelService.php
Normal file
@@ -0,0 +1,184 @@
|
||||
<?php
|
||||
|
||||
namespace app\service\payment\config;
|
||||
|
||||
use app\common\base\BaseService;
|
||||
use app\exception\PaymentException;
|
||||
use app\model\payment\PaymentPollGroupChannel;
|
||||
use app\repository\payment\config\PaymentChannelRepository;
|
||||
use app\repository\payment\config\PaymentPollGroupChannelRepository;
|
||||
use app\repository\payment\config\PaymentPollGroupRepository;
|
||||
|
||||
/**
|
||||
* 轮询组通道编排服务。
|
||||
*/
|
||||
class PaymentPollGroupChannelService extends BaseService
|
||||
{
|
||||
public function __construct(
|
||||
protected PaymentPollGroupChannelRepository $paymentPollGroupChannelRepository,
|
||||
protected PaymentPollGroupRepository $paymentPollGroupRepository,
|
||||
protected PaymentChannelRepository $paymentChannelRepository
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询轮询组通道编排。
|
||||
*/
|
||||
public function paginate(array $filters = [], int $page = 1, int $pageSize = 10)
|
||||
{
|
||||
$query = $this->paymentPollGroupChannelRepository->query()
|
||||
->from('ma_payment_poll_group_channel as pgc')
|
||||
->leftJoin('ma_payment_poll_group as pg', 'pg.id', '=', 'pgc.poll_group_id')
|
||||
->leftJoin('ma_payment_channel as c', 'c.id', '=', 'pgc.channel_id')
|
||||
->leftJoin('ma_payment_type as t', 't.id', '=', 'pg.pay_type_id')
|
||||
->select([
|
||||
'pgc.id',
|
||||
'pgc.poll_group_id',
|
||||
'pgc.channel_id',
|
||||
'pgc.sort_no',
|
||||
'pgc.weight',
|
||||
'pgc.is_default',
|
||||
'pgc.status',
|
||||
'pgc.remark',
|
||||
'pgc.created_at',
|
||||
'pgc.updated_at',
|
||||
'pg.group_name as poll_group_name',
|
||||
'pg.pay_type_id',
|
||||
'c.name as channel_name',
|
||||
'c.merchant_id',
|
||||
'c.channel_mode',
|
||||
'c.plugin_code',
|
||||
't.name as pay_type_name',
|
||||
]);
|
||||
|
||||
$keyword = trim((string) ($filters['keyword'] ?? ''));
|
||||
if ($keyword !== '') {
|
||||
$query->where(function ($builder) use ($keyword) {
|
||||
$builder->where('pg.group_name', 'like', '%' . $keyword . '%')
|
||||
->orWhere('c.name', 'like', '%' . $keyword . '%')
|
||||
->orWhere('c.plugin_code', 'like', '%' . $keyword . '%');
|
||||
});
|
||||
}
|
||||
|
||||
if (($pollGroupId = (int) ($filters['poll_group_id'] ?? 0)) > 0) {
|
||||
$query->where('pgc.poll_group_id', $pollGroupId);
|
||||
}
|
||||
|
||||
if (($channelId = (int) ($filters['channel_id'] ?? 0)) > 0) {
|
||||
$query->where('pgc.channel_id', $channelId);
|
||||
}
|
||||
|
||||
if (array_key_exists('status', $filters) && $filters['status'] !== '') {
|
||||
$query->where('pgc.status', (int) $filters['status']);
|
||||
}
|
||||
|
||||
return $query
|
||||
->orderBy('pgc.poll_group_id')
|
||||
->orderBy('pgc.sort_no')
|
||||
->orderByDesc('pgc.id')
|
||||
->paginate(max(1, $pageSize), ['*'], 'page', max(1, $page));
|
||||
}
|
||||
|
||||
public function findById(int $id): ?PaymentPollGroupChannel
|
||||
{
|
||||
return $this->paymentPollGroupChannelRepository->find($id);
|
||||
}
|
||||
|
||||
public function create(array $data): PaymentPollGroupChannel
|
||||
{
|
||||
$this->assertPairUnique((int) $data['poll_group_id'], (int) $data['channel_id']);
|
||||
$this->assertChannelMatchesPollGroup($data);
|
||||
$payload = $this->normalizePayload($data);
|
||||
|
||||
return $this->transaction(function () use ($payload) {
|
||||
if ((int) ($payload['is_default'] ?? 0) === 1) {
|
||||
$this->paymentPollGroupChannelRepository->clearDefaultExcept((int) $payload['poll_group_id']);
|
||||
}
|
||||
|
||||
return $this->paymentPollGroupChannelRepository->create($payload);
|
||||
});
|
||||
}
|
||||
|
||||
public function update(int $id, array $data): ?PaymentPollGroupChannel
|
||||
{
|
||||
$current = $this->paymentPollGroupChannelRepository->find($id);
|
||||
if (!$current) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$pollGroupId = (int) ($data['poll_group_id'] ?? $current->poll_group_id);
|
||||
$channelId = (int) ($data['channel_id'] ?? $current->channel_id);
|
||||
$this->assertPairUnique($pollGroupId, $channelId, $id);
|
||||
$this->assertChannelMatchesPollGroup(array_merge($current->toArray(), $data));
|
||||
|
||||
$payload = $this->normalizePayload($data);
|
||||
|
||||
return $this->transaction(function () use ($id, $payload) {
|
||||
if ((int) ($payload['is_default'] ?? 0) === 1) {
|
||||
$this->paymentPollGroupChannelRepository->clearDefaultExcept((int) $payload['poll_group_id'], $id);
|
||||
}
|
||||
|
||||
if (!$this->paymentPollGroupChannelRepository->updateById($id, $payload)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->paymentPollGroupChannelRepository->find($id);
|
||||
});
|
||||
}
|
||||
|
||||
public function delete(int $id): bool
|
||||
{
|
||||
return $this->paymentPollGroupChannelRepository->deleteById($id);
|
||||
}
|
||||
|
||||
private function normalizePayload(array $data): array
|
||||
{
|
||||
return [
|
||||
'poll_group_id' => (int) $data['poll_group_id'],
|
||||
'channel_id' => (int) $data['channel_id'],
|
||||
'sort_no' => (int) ($data['sort_no'] ?? 0),
|
||||
'weight' => max(1, (int) ($data['weight'] ?? 100)),
|
||||
'is_default' => (int) ($data['is_default'] ?? 0),
|
||||
'status' => (int) ($data['status'] ?? 1),
|
||||
'remark' => trim((string) ($data['remark'] ?? '')),
|
||||
];
|
||||
}
|
||||
|
||||
private function assertPairUnique(int $pollGroupId, int $channelId, int $ignoreId = 0): void
|
||||
{
|
||||
$query = $this->paymentPollGroupChannelRepository->query()
|
||||
->where('poll_group_id', $pollGroupId)
|
||||
->where('channel_id', $channelId);
|
||||
|
||||
if ($ignoreId > 0) {
|
||||
$query->where('id', '<>', $ignoreId);
|
||||
}
|
||||
|
||||
if ($query->exists()) {
|
||||
throw new PaymentException('该轮询组已添加当前支付通道', 40230, [
|
||||
'poll_group_id' => $pollGroupId,
|
||||
'channel_id' => $channelId,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
private function assertChannelMatchesPollGroup(array $data): void
|
||||
{
|
||||
$pollGroupId = (int) ($data['poll_group_id'] ?? 0);
|
||||
$channelId = (int) ($data['channel_id'] ?? 0);
|
||||
|
||||
$pollGroup = $this->paymentPollGroupRepository->find($pollGroupId);
|
||||
$channel = $this->paymentChannelRepository->find($channelId);
|
||||
|
||||
if (!$pollGroup || !$channel) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((int) $pollGroup->pay_type_id !== (int) $channel->pay_type_id) {
|
||||
throw new PaymentException('轮询组与支付通道的支付方式不一致', 40231, [
|
||||
'poll_group_id' => $pollGroupId,
|
||||
'channel_id' => $channelId,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace app\service\payment\config;
|
||||
|
||||
use app\common\base\BaseService;
|
||||
use app\exception\PaymentException;
|
||||
use app\model\payment\PaymentPollGroup;
|
||||
use app\repository\payment\config\PaymentPollGroupRepository;
|
||||
|
||||
/**
|
||||
* 支付轮询组命令服务。
|
||||
*/
|
||||
class PaymentPollGroupCommandService extends BaseService
|
||||
{
|
||||
public function __construct(
|
||||
protected PaymentPollGroupRepository $paymentPollGroupRepository
|
||||
) {
|
||||
}
|
||||
|
||||
public function create(array $data): PaymentPollGroup
|
||||
{
|
||||
$this->assertGroupNameUnique((string) ($data['group_name'] ?? ''));
|
||||
return $this->paymentPollGroupRepository->create($data);
|
||||
}
|
||||
|
||||
public function update(int $id, array $data): ?PaymentPollGroup
|
||||
{
|
||||
$this->assertGroupNameUnique((string) ($data['group_name'] ?? ''), $id);
|
||||
if (!$this->paymentPollGroupRepository->updateById($id, $data)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->paymentPollGroupRepository->find($id);
|
||||
}
|
||||
|
||||
public function delete(int $id): bool
|
||||
{
|
||||
return $this->paymentPollGroupRepository->deleteById($id);
|
||||
}
|
||||
|
||||
private function assertGroupNameUnique(string $groupName, int $ignoreId = 0): void
|
||||
{
|
||||
$groupName = trim($groupName);
|
||||
if ($groupName === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->paymentPollGroupRepository->existsByGroupName($groupName, $ignoreId)) {
|
||||
throw new PaymentException('轮询组名称已存在', 40234, [
|
||||
'group_name' => $groupName,
|
||||
'ignore_id' => $ignoreId,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
79
app/service/payment/config/PaymentPollGroupQueryService.php
Normal file
79
app/service/payment/config/PaymentPollGroupQueryService.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace app\service\payment\config;
|
||||
|
||||
use app\common\base\BaseService;
|
||||
use app\model\payment\PaymentPollGroup;
|
||||
use app\repository\payment\config\PaymentPollGroupRepository;
|
||||
|
||||
/**
|
||||
* 支付轮询组查询服务。
|
||||
*/
|
||||
class PaymentPollGroupQueryService extends BaseService
|
||||
{
|
||||
public function __construct(
|
||||
protected PaymentPollGroupRepository $paymentPollGroupRepository
|
||||
) {
|
||||
}
|
||||
|
||||
public function paginate(array $filters = [], int $page = 1, int $pageSize = 10)
|
||||
{
|
||||
$query = $this->paymentPollGroupRepository->query();
|
||||
|
||||
$keyword = trim((string) ($filters['keyword'] ?? ''));
|
||||
if ($keyword !== '') {
|
||||
$query->where('group_name', 'like', '%' . $keyword . '%');
|
||||
}
|
||||
|
||||
$groupName = trim((string) ($filters['group_name'] ?? ''));
|
||||
if ($groupName !== '') {
|
||||
$query->where('group_name', 'like', '%' . $groupName . '%');
|
||||
}
|
||||
|
||||
if (($payTypeId = (int) ($filters['pay_type_id'] ?? 0)) > 0) {
|
||||
$query->where('pay_type_id', $payTypeId);
|
||||
}
|
||||
|
||||
if (array_key_exists('route_mode', $filters) && $filters['route_mode'] !== '') {
|
||||
$query->where('route_mode', (int) $filters['route_mode']);
|
||||
}
|
||||
|
||||
if (array_key_exists('status', $filters) && $filters['status'] !== '') {
|
||||
$query->where('status', (int) $filters['status']);
|
||||
}
|
||||
|
||||
return $query
|
||||
->orderByDesc('id')
|
||||
->paginate(max(1, $pageSize), ['*'], 'page', max(1, $page));
|
||||
}
|
||||
|
||||
public function enabledOptions(array $filters = []): array
|
||||
{
|
||||
$query = $this->paymentPollGroupRepository->query()
|
||||
->where('status', 1);
|
||||
|
||||
if (($payTypeId = (int) ($filters['pay_type_id'] ?? 0)) > 0) {
|
||||
$query->where('pay_type_id', $payTypeId);
|
||||
}
|
||||
|
||||
return $query
|
||||
->orderBy('group_name')
|
||||
->orderByDesc('id')
|
||||
->get(['id', 'group_name', 'pay_type_id', 'route_mode'])
|
||||
->map(function (PaymentPollGroup $pollGroup): array {
|
||||
return [
|
||||
'label' => sprintf('%s(%d)', (string) $pollGroup->group_name, (int) $pollGroup->id),
|
||||
'value' => (int) $pollGroup->id,
|
||||
'pay_type_id' => (int) $pollGroup->pay_type_id,
|
||||
'route_mode' => (int) $pollGroup->route_mode,
|
||||
];
|
||||
})
|
||||
->values()
|
||||
->all();
|
||||
}
|
||||
|
||||
public function findById(int $id): ?PaymentPollGroup
|
||||
{
|
||||
return $this->paymentPollGroupRepository->find($id);
|
||||
}
|
||||
}
|
||||
48
app/service/payment/config/PaymentPollGroupService.php
Normal file
48
app/service/payment/config/PaymentPollGroupService.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace app\service\payment\config;
|
||||
|
||||
use app\common\base\BaseService;
|
||||
use app\model\payment\PaymentPollGroup;
|
||||
|
||||
/**
|
||||
* 支付轮询组门面服务。
|
||||
*/
|
||||
class PaymentPollGroupService extends BaseService
|
||||
{
|
||||
public function __construct(
|
||||
protected PaymentPollGroupQueryService $queryService,
|
||||
protected PaymentPollGroupCommandService $commandService
|
||||
) {
|
||||
}
|
||||
|
||||
public function paginate(array $filters = [], int $page = 1, int $pageSize = 10)
|
||||
{
|
||||
return $this->queryService->paginate($filters, $page, $pageSize);
|
||||
}
|
||||
|
||||
public function enabledOptions(array $filters = []): array
|
||||
{
|
||||
return $this->queryService->enabledOptions($filters);
|
||||
}
|
||||
|
||||
public function findById(int $id): ?PaymentPollGroup
|
||||
{
|
||||
return $this->queryService->findById($id);
|
||||
}
|
||||
|
||||
public function create(array $data): PaymentPollGroup
|
||||
{
|
||||
return $this->commandService->create($data);
|
||||
}
|
||||
|
||||
public function update(int $id, array $data): ?PaymentPollGroup
|
||||
{
|
||||
return $this->commandService->update($id, $data);
|
||||
}
|
||||
|
||||
public function delete(int $id): bool
|
||||
{
|
||||
return $this->commandService->delete($id);
|
||||
}
|
||||
}
|
||||
150
app/service/payment/config/PaymentTypeService.php
Normal file
150
app/service/payment/config/PaymentTypeService.php
Normal file
@@ -0,0 +1,150 @@
|
||||
<?php
|
||||
|
||||
namespace app\service\payment\config;
|
||||
|
||||
use app\common\base\BaseService;
|
||||
use app\exception\ValidationException;
|
||||
use app\model\payment\PaymentType;
|
||||
use app\repository\payment\config\PaymentTypeRepository;
|
||||
|
||||
/**
|
||||
* 支付方式字典服务。
|
||||
*
|
||||
* 负责支付方式的基础列表查询、新增、修改、删除和下拉选项输出。
|
||||
*/
|
||||
class PaymentTypeService extends BaseService
|
||||
{
|
||||
/**
|
||||
* 构造函数,注入支付方式仓库。
|
||||
*/
|
||||
public function __construct(
|
||||
protected PaymentTypeRepository $paymentTypeRepository
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询支付方式。
|
||||
*/
|
||||
public function paginate(array $filters = [], int $page = 1, int $pageSize = 10)
|
||||
{
|
||||
$query = $this->paymentTypeRepository->query();
|
||||
|
||||
$keyword = trim((string) ($filters['keyword'] ?? ''));
|
||||
if ($keyword !== '') {
|
||||
$query->where(function ($builder) use ($keyword) {
|
||||
$builder->where('code', 'like', '%' . $keyword . '%')
|
||||
->orWhere('name', 'like', '%' . $keyword . '%');
|
||||
});
|
||||
}
|
||||
|
||||
$code = trim((string) ($filters['code'] ?? ''));
|
||||
if ($code !== '') {
|
||||
$query->where('code', 'like', '%' . $code . '%');
|
||||
}
|
||||
|
||||
$name = trim((string) ($filters['name'] ?? ''));
|
||||
if ($name !== '') {
|
||||
$query->where('name', 'like', '%' . $name . '%');
|
||||
}
|
||||
|
||||
if (array_key_exists('status', $filters) && $filters['status'] !== '') {
|
||||
$query->where('status', (int) $filters['status']);
|
||||
}
|
||||
|
||||
return $query
|
||||
->orderBy('sort_no')
|
||||
->orderByDesc('id')
|
||||
->paginate(max(1, $pageSize), ['*'], 'page', max(1, $page));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询启用中的支付方式选项。
|
||||
*/
|
||||
public function enabledOptions(): array
|
||||
{
|
||||
return $this->paymentTypeRepository->enabledList(['id', 'code', 'name'])
|
||||
->map(function (PaymentType $paymentType): array {
|
||||
return [
|
||||
'label' => (string) $paymentType->name,
|
||||
'value' => (int) $paymentType->id,
|
||||
'code' => (string) $paymentType->code,
|
||||
];
|
||||
})
|
||||
->values()
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析启用中的支付方式,优先按编码匹配,未命中则取首个启用项。
|
||||
*/
|
||||
public function resolveEnabledType(string $code = ''): PaymentType
|
||||
{
|
||||
$code = trim($code);
|
||||
if ($code !== '') {
|
||||
$paymentType = $this->paymentTypeRepository->findByCode($code);
|
||||
if ($paymentType && (int) $paymentType->status === 1) {
|
||||
return $paymentType;
|
||||
}
|
||||
}
|
||||
|
||||
$paymentType = $this->paymentTypeRepository->enabledList()->first();
|
||||
if (!$paymentType) {
|
||||
throw new ValidationException('未配置可用支付方式');
|
||||
}
|
||||
|
||||
return $paymentType;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据支付方式编码查询字典。
|
||||
*/
|
||||
public function findByCode(string $code): ?PaymentType
|
||||
{
|
||||
return $this->paymentTypeRepository->findByCode(trim($code));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据支付方式 ID 解析支付方式编码。
|
||||
*/
|
||||
public function resolveCodeById(int $id): string
|
||||
{
|
||||
$paymentType = $this->paymentTypeRepository->find($id);
|
||||
return $paymentType ? (string) $paymentType->code : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 按 ID 查询支付方式。
|
||||
*/
|
||||
public function findById(int $id): ?PaymentType
|
||||
{
|
||||
return $this->paymentTypeRepository->find($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增支付方式。
|
||||
*/
|
||||
public function create(array $data): PaymentType
|
||||
{
|
||||
return $this->paymentTypeRepository->create($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新支付方式。
|
||||
*/
|
||||
public function update(int $id, array $data): ?PaymentType
|
||||
{
|
||||
if (!$this->paymentTypeRepository->updateById($id, $data)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->paymentTypeRepository->find($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除支付方式。
|
||||
*/
|
||||
public function delete(int $id): bool
|
||||
{
|
||||
return $this->paymentTypeRepository->deleteById($id);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user