1. 维护代码健壮

2. 更新项目结构文档
This commit is contained in:
技术老胡
2026-04-27 16:20:41 +08:00
parent 9a16a88640
commit 0e5de50337
198 changed files with 21038 additions and 702 deletions

View File

@@ -0,0 +1,388 @@
<?php
namespace app\service\merchant\portal;
use app\common\base\BaseService;
use app\common\constant\CommonConstant;
use app\common\constant\RouteConstant;
use app\exception\PaymentException;
use app\model\payment\PaymentChannel;
use app\model\payment\PaymentPlugin;
use app\model\payment\PaymentPluginConf;
use app\repository\payment\config\PaymentChannelRepository;
use app\repository\payment\config\PaymentPluginConfRepository;
use app\repository\payment\config\PaymentPluginRepository;
use app\repository\payment\config\PaymentTypeRepository;
/**
* 商户门户通道配置命令服务。
*
* 负责商户端插件配置、通道配置的新增修改删除,并集中校验商户归属与插件授权。
*/
class MerchantPortalChannelCommandService extends BaseService
{
public function __construct(
protected MerchantPortalSupportService $supportService,
protected PaymentPluginRepository $paymentPluginRepository,
protected PaymentPluginConfRepository $paymentPluginConfRepository,
protected PaymentChannelRepository $paymentChannelRepository,
protected PaymentTypeRepository $paymentTypeRepository
) {
}
/**
* 商户端允许使用的插件选项。
*
* @return array 插件选项和支付方式
*/
public function createMeta(): array
{
$plugins = $this->paymentPluginRepository->merchantEnabledList([
'code',
'name',
'config_schema',
'pay_types',
])->map(function (PaymentPlugin $plugin): array {
return $this->pluginOption($plugin);
})->values()->all();
return [
'plugins' => $plugins,
'pay_types' => $this->supportService->enabledPayTypeOptions(),
];
}
/**
* 查询商户插件配置列表。
*
* @param array $filters 筛选条件
* @param int $merchantId 商户ID
* @param int $page 页码
* @param int $pageSize 每页条数
* @return array 列表数据
*/
public function pluginConfigs(array $filters, int $merchantId, int $page, int $pageSize): 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.merchant_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")
->where('c.merchant_id', $merchantId);
$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 . '%');
});
}
$pluginCode = trim((string) ($filters['plugin_code'] ?? ''));
if ($pluginCode !== '') {
$query->where('c.plugin_code', $pluginCode);
}
$paginator = $query
->orderByDesc('c.id')
->paginate(max(1, $pageSize), ['*'], 'page', max(1, $page));
$paginator->getCollection()->transform(function ($row) {
$row->settlement_cycle_type_text = $this->textFromMap((int) $row->settlement_cycle_type, [
0 => 'D0',
1 => 'D1',
2 => 'D7',
3 => 'T1',
4 => 'OTHER',
]);
$row->created_at_text = $this->formatDateTime($row->created_at ?? null);
$row->updated_at_text = $this->formatDateTime($row->updated_at ?? null);
return $row;
});
return [
'merchant' => $this->supportService->merchantSummary($merchantId),
'plugins' => $this->createMeta()['plugins'],
'list' => $paginator->items(),
'total' => $paginator->total(),
'page' => $paginator->currentPage(),
'size' => $paginator->perPage(),
];
}
/**
* 新增商户插件配置。
*
* @param int $merchantId 商户ID
* @param array $data 写入数据
* @return PaymentPluginConf 配置
*/
public function createPluginConfig(int $merchantId, array $data): PaymentPluginConf
{
$payload = $this->normalizePluginConfigPayload($merchantId, $data);
$this->assertMerchantPluginAllowed((string) $payload['plugin_code']);
return $this->paymentPluginConfRepository->create($payload);
}
/**
* 修改商户插件配置。
*
* @param int $merchantId 商户ID
* @param int $id 配置ID
* @param array $data 写入数据
* @return PaymentPluginConf|null 配置
*/
public function updatePluginConfig(int $merchantId, int $id, array $data): ?PaymentPluginConf
{
$model = $this->paymentPluginConfRepository->findByMerchantAndId($merchantId, $id);
if (!$model) {
return null;
}
$payload = $this->normalizePluginConfigPayload($merchantId, $data);
$this->assertMerchantPluginAllowed((string) $payload['plugin_code']);
$model->fill($payload);
$model->save();
return $model->refresh();
}
/**
* 删除商户插件配置。
*
* @param int $merchantId 商户ID
* @param int $id 配置ID
* @return bool 是否删除
*/
public function deletePluginConfig(int $merchantId, int $id): bool
{
$model = $this->paymentPluginConfRepository->findByMerchantAndId($merchantId, $id);
if (!$model) {
return false;
}
if ($this->paymentChannelRepository->existsBy([
'merchant_id' => $merchantId,
'api_config_id' => $id,
])) {
throw new PaymentException('该配置已被通道使用,不能删除', 40241);
}
return (bool) $model->delete();
}
/**
* 商户插件配置下拉选项。
*
* @param int $merchantId 商户ID
* @param string $pluginCode 插件编码
* @return array 配置选项
*/
public function pluginConfigOptions(int $merchantId, string $pluginCode = ''): 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")
->where('c.merchant_id', $merchantId)
->orderByDesc('c.id');
$pluginCode = trim($pluginCode);
if ($pluginCode !== '') {
$query->where('c.plugin_code', $pluginCode);
}
return $query->get()->map(function ($row): array {
return [
'label' => sprintf('%s%d', (string) $row->plugin_name, (int) $row->id),
'value' => (int) $row->id,
'plugin_code' => (string) $row->plugin_code,
'plugin_name' => (string) $row->plugin_name,
];
})->values()->all();
}
/**
* 新增商户通道。
*
* @param int $merchantId 商户ID
* @param array $data 写入数据
* @return PaymentChannel 通道
*/
public function createChannel(int $merchantId, array $data): PaymentChannel
{
$payload = $this->normalizeChannelPayload($merchantId, $data);
$this->assertChannelWritable($merchantId, $payload);
$this->assertChannelNameUnique($merchantId, (string) $payload['name']);
return $this->paymentChannelRepository->create($payload);
}
/**
* 修改商户通道。
*
* @param int $merchantId 商户ID
* @param int $id 通道ID
* @param array $data 写入数据
* @return PaymentChannel|null 通道
*/
public function updateChannel(int $merchantId, int $id, array $data): ?PaymentChannel
{
$model = $this->paymentChannelRepository->findByMerchantAndId($merchantId, $id);
if (!$model) {
return null;
}
$payload = $this->normalizeChannelPayload($merchantId, $data);
$this->assertChannelWritable($merchantId, $payload);
$this->assertChannelNameUnique($merchantId, (string) $payload['name'], $id);
$model->fill($payload);
$model->save();
return $model->refresh();
}
/**
* 删除商户通道。
*
* @param int $merchantId 商户ID
* @param int $id 通道ID
* @return bool 是否删除
*/
public function deleteChannel(int $merchantId, int $id): bool
{
$model = $this->paymentChannelRepository->findByMerchantAndId($merchantId, $id);
if (!$model) {
return false;
}
return (bool) $model->delete();
}
/**
* 根据插件编码查询商户端可用插件结构。
*
* @param string $pluginCode 插件编码
* @return array 配置结构
*/
public function pluginSchema(string $pluginCode): array
{
$plugin = $this->assertMerchantPluginAllowed($pluginCode);
return [
'config_schema' => is_array($plugin->config_schema) ? array_values($plugin->config_schema) : [],
];
}
private function normalizePluginConfigPayload(int $merchantId, array $data): array
{
return [
'merchant_id' => $merchantId,
'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 normalizeChannelPayload(int $merchantId, array $data): array
{
return [
'merchant_id' => $merchantId,
'name' => trim((string) ($data['name'] ?? '')),
'split_rate_bp' => 10000,
'cost_rate_bp' => 0,
'channel_mode' => RouteConstant::CHANNEL_MODE_SELF,
'pay_type_id' => (int) ($data['pay_type_id'] ?? 0),
'plugin_code' => trim((string) ($data['plugin_code'] ?? '')),
'api_config_id' => (int) ($data['api_config_id'] ?? 0),
'daily_limit_amount' => max(0, (int) ($data['daily_limit_amount'] ?? 0)),
'daily_limit_count' => max(0, (int) ($data['daily_limit_count'] ?? 0)),
'min_amount' => max(0, (int) ($data['min_amount'] ?? 0)),
'max_amount' => max(0, (int) ($data['max_amount'] ?? 0)),
'remark' => trim((string) ($data['remark'] ?? '')),
'status' => (int) ($data['status'] ?? CommonConstant::STATUS_ENABLED),
'sort_no' => max(0, (int) ($data['sort_no'] ?? 0)),
];
}
private function assertChannelWritable(int $merchantId, array $payload): void
{
if ((string) $payload['name'] === '') {
throw new PaymentException('通道名称不能为空', 40242);
}
$plugin = $this->assertMerchantPluginAllowed((string) $payload['plugin_code']);
$config = $this->paymentPluginConfRepository->findByMerchantAndId($merchantId, (int) $payload['api_config_id']);
if (!$config || (string) $config->plugin_code !== (string) $payload['plugin_code']) {
throw new PaymentException('插件配置不存在或不属于当前插件', 40243);
}
$payType = $this->paymentTypeRepository->find((int) $payload['pay_type_id']);
if (!$payType) {
throw new PaymentException('支付方式不存在', 40244);
}
$payTypes = is_array($plugin->pay_types) ? $plugin->pay_types : [];
$payTypeCodes = array_values(array_filter(array_map(static fn ($item) => trim((string) $item), $payTypes)));
if (!in_array((string) $payType->code, $payTypeCodes, true)) {
throw new PaymentException('支付插件不支持当前支付方式', 40245);
}
if ((int) $payload['max_amount'] > 0 && (int) $payload['min_amount'] > (int) $payload['max_amount']) {
throw new PaymentException('单笔最小金额不能大于最大金额', 40246);
}
}
private function assertChannelNameUnique(int $merchantId, string $name, int $ignoreId = 0): void
{
if ($this->paymentChannelRepository->existsByMerchantName($merchantId, $name, $ignoreId)) {
throw new PaymentException('通道名称已存在', 40247);
}
if ($this->paymentChannelRepository->existsByName($name, $ignoreId)) {
throw new PaymentException('通道名称已被占用,请换一个名称', 40248);
}
}
private function assertMerchantPluginAllowed(string $pluginCode): PaymentPlugin
{
$plugin = $this->paymentPluginRepository->findMerchantAllowed($pluginCode);
if (!$plugin) {
throw new PaymentException('该支付插件未开放给商户端使用', 40240, [
'plugin_code' => $pluginCode,
]);
}
return $plugin;
}
private function pluginOption(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) : [],
'config_schema' => is_array($plugin->config_schema) ? array_values($plugin->config_schema) : [],
];
}
}

View File

@@ -8,6 +8,7 @@ use app\common\base\BaseService;
* 商户门户通道服务。
*
* @property MerchantPortalChannelQueryService $queryService 查询服务
* @property MerchantPortalChannelCommandService $commandService 命令服务
* @property MerchantPortalRoutePreviewService $routePreviewService 路由解析服务
*/
class MerchantPortalChannelService extends BaseService
@@ -16,10 +17,12 @@ class MerchantPortalChannelService extends BaseService
* 构造方法。
*
* @param MerchantPortalChannelQueryService $queryService 查询服务
* @param MerchantPortalChannelCommandService $commandService 命令服务
* @param MerchantPortalRoutePreviewService $routePreviewService 路由解析服务
*/
public function __construct(
protected MerchantPortalChannelQueryService $queryService,
protected MerchantPortalChannelCommandService $commandService,
protected MerchantPortalRoutePreviewService $routePreviewService
) {
}
@@ -51,5 +54,54 @@ class MerchantPortalChannelService extends BaseService
{
return $this->routePreviewService->routePreview($merchantId, $payTypeId, $payAmount, $statDate);
}
}
public function createMeta(): array
{
return $this->commandService->createMeta();
}
public function pluginConfigs(array $filters, int $merchantId, int $page, int $pageSize): array
{
return $this->commandService->pluginConfigs($filters, $merchantId, $page, $pageSize);
}
public function createPluginConfig(int $merchantId, array $data)
{
return $this->commandService->createPluginConfig($merchantId, $data);
}
public function updatePluginConfig(int $merchantId, int $id, array $data)
{
return $this->commandService->updatePluginConfig($merchantId, $id, $data);
}
public function deletePluginConfig(int $merchantId, int $id): bool
{
return $this->commandService->deletePluginConfig($merchantId, $id);
}
public function pluginConfigOptions(int $merchantId, string $pluginCode = ''): array
{
return $this->commandService->pluginConfigOptions($merchantId, $pluginCode);
}
public function createChannel(int $merchantId, array $data)
{
return $this->commandService->createChannel($merchantId, $data);
}
public function updateChannel(int $merchantId, int $id, array $data)
{
return $this->commandService->updateChannel($merchantId, $id, $data);
}
public function deleteChannel(int $merchantId, int $id): bool
{
return $this->commandService->deleteChannel($merchantId, $id);
}
public function pluginSchema(string $pluginCode): array
{
return $this->commandService->pluginSchema($pluginCode);
}
}

View File

@@ -33,22 +33,44 @@ class MerchantPortalCredentialCommandService extends BaseService
* 生成或重置商户 API 凭证。
*
* @param int $merchantId 商户ID
* @param array $options 生成选项
* @return array 凭证数据
*/
public function issueCredential(int $merchantId): array
public function issueCredential(int $merchantId, array $options = []): array
{
$merchant = $this->supportService->merchantSummary($merchantId);
$credentialValue = $this->merchantApiCredentialService->issueCredential($merchantId);
$result = $this->merchantApiCredentialService->issueCredentialBundle($merchantId, $options);
$credentialValue = (string) ($result['credential_value'] ?? '');
$merchantPrivateKey = (string) ($result['merchant_private_key'] ?? '');
$generated = (array) ($result['generated'] ?? []);
// 凭证明文只在发放当次返回一次,随后再查库只拿脱敏后的展示结构。
$credential = $this->merchantApiCredentialRepository->findByMerchantId($merchantId);
return [
'merchant' => $merchant,
'merchant' => $this->formatMerchant($merchant),
'integration' => $this->supportService->apiIntegrationInfo($merchant),
'credential_value' => $credentialValue,
'merchant_private_key' => $merchantPrivateKey,
'generated' => $generated,
'credential' => $credential ? $this->formatCredential($credential, $merchant) : null,
];
}
/**
* 格式化页面所需商户摘要。
*
* @param array $merchant 商户摘要
* @return array<string, mixed>
*/
private function formatMerchant(array $merchant): array
{
return [
'id' => (int) ($merchant['id'] ?? $merchant['merchant_id'] ?? 0),
'merchant_no' => (string) ($merchant['merchant_no'] ?? ''),
'merchant_name' => (string) ($merchant['merchant_name'] ?? ''),
];
}
/**
* 格式化接口凭证展示数据。
*
@@ -58,22 +80,25 @@ class MerchantPortalCredentialCommandService extends BaseService
*/
private function formatCredential(\app\model\merchant\MerchantApiCredential $credential, array $merchant): array
{
$signType = (int) $credential->sign_type;
$apiKey = trim((string) $credential->api_key);
$merchantPublicKey = trim((string) ($credential->merchant_public_key ?? ''));
$platformPublicKey = trim((string) config('epay.v2.platform_public_key', ''));
return [
'id' => (int) $credential->id,
'merchant_id' => (int) $credential->merchant_id,
'merchant_no' => (string) ($merchant['merchant_no'] ?? ''),
'merchant_name' => (string) ($merchant['merchant_name'] ?? ''),
'sign_type' => $signType,
'sign_type_text' => $this->supportService->signTypeText($signType),
// 展示页只保留脱敏后的 key 片段,避免明文凭证再次暴露。
'api_key_preview' => $this->maskCredentialValue((string) $credential->api_key),
'api_key_preview' => $this->maskCredentialValue($apiKey),
'api_key_full' => $apiKey,
'merchant_public_key_full' => $merchantPublicKey,
'merchant_public_key_preview' => $this->maskCredentialValue($merchantPublicKey),
'platform_public_key_full' => $platformPublicKey,
'platform_public_key_preview' => $this->maskCredentialValue($platformPublicKey),
'supports_v1' => $apiKey !== '',
'supports_v2' => $merchantPublicKey !== '' && $platformPublicKey !== '',
'v1_status_text' => $apiKey !== '' ? '已配置' : '未配置',
'v2_status_text' => $merchantPublicKey !== '' && $platformPublicKey !== '' ? '已配置' : '未配置',
'status' => (int) $credential->status,
'status_text' => (string) ($credential->status ? '启用' : '禁用'),
'last_used_at' => $this->formatDateTime($credential->last_used_at ?? null),
'created_at' => $this->formatDateTime($credential->created_at ?? null),
'updated_at' => $this->formatDateTime($credential->updated_at ?? null),
];
}
}

View File

@@ -3,7 +3,7 @@
namespace app\service\merchant\portal;
use app\common\base\BaseService;
use app\common\constant\CommonConstant;
use app\common\constant\AuthConstant;
use app\model\merchant\MerchantApiCredential;
use app\repository\merchant\credential\MerchantApiCredentialRepository;
@@ -39,12 +39,28 @@ class MerchantPortalCredentialQueryService extends BaseService
$credential = $this->merchantApiCredentialRepository->findByMerchantId($merchantId);
return [
'merchant' => $merchant,
'merchant' => $this->formatMerchant($merchant),
'has_credential' => $credential !== null,
'integration' => $this->supportService->apiIntegrationInfo($merchant),
'credential' => $credential ? $this->formatCredential($credential, $merchant) : null,
];
}
/**
* 格式化页面所需商户摘要。
*
* @param array $merchant 商户摘要
* @return array<string, mixed>
*/
private function formatMerchant(array $merchant): array
{
return [
'id' => (int) ($merchant['id'] ?? $merchant['merchant_id'] ?? 0),
'merchant_no' => (string) ($merchant['merchant_no'] ?? ''),
'merchant_name' => (string) ($merchant['merchant_name'] ?? ''),
];
}
/**
* 格式化接口凭证展示数据。
*
@@ -54,24 +70,26 @@ class MerchantPortalCredentialQueryService extends BaseService
*/
private function formatCredential(MerchantApiCredential $credential, array $merchant): array
{
$signType = (int) $credential->sign_type;
$status = (int) $credential->status;
$apiKey = trim((string) $credential->api_key);
$merchantPublicKey = trim((string) ($credential->merchant_public_key ?? ''));
$platformPublicKey = trim((string) config('epay.v2.platform_public_key', ''));
return [
'id' => (int) $credential->id,
'merchant_id' => (int) $credential->merchant_id,
'merchant_no' => (string) ($merchant['merchant_no'] ?? ''),
'merchant_name' => (string) ($merchant['merchant_name'] ?? ''),
'sign_type' => $signType,
'sign_type_text' => $this->supportService->signTypeText($signType),
'api_key_preview' => $this->maskCredentialValue((string) $credential->api_key),
'api_key_preview' => $this->maskCredentialValue($apiKey),
'api_key_full' => $apiKey,
'merchant_public_key_full' => $merchantPublicKey,
'merchant_public_key_preview' => $this->maskCredentialValue($merchantPublicKey),
'platform_public_key_full' => $platformPublicKey,
'platform_public_key_preview' => $this->maskCredentialValue($platformPublicKey),
'supports_v1' => $apiKey !== '',
'supports_v2' => $merchantPublicKey !== '' && $platformPublicKey !== '',
'v1_status_text' => $apiKey !== '' ? '已配置' : '未配置',
'v2_status_text' => $merchantPublicKey !== '' && $platformPublicKey !== '' ? '已配置' : '未配置',
'status' => $status,
'status_text' => (string) (CommonConstant::statusMap()[$status] ?? '未知'),
'last_used_at' => $this->formatDateTime($credential->last_used_at ?? null),
'created_at' => $this->formatDateTime($credential->created_at ?? null),
'updated_at' => $this->formatDateTime($credential->updated_at ?? null),
'status_text' => $this->textFromMap($status, AuthConstant::credentialStatusMap()),
];
}
}

View File

@@ -39,11 +39,11 @@ class MerchantPortalCredentialService extends BaseService
* 生成或重置商户 API 凭证。
*
* @param int $merchantId 商户ID
* @param array $options 生成选项
* @return array 凭证数据
*/
public function issueCredential(int $merchantId): array
public function issueCredential(int $merchantId, array $options = []): array
{
return $this->commandService->issueCredential($merchantId);
return $this->commandService->issueCredential($merchantId, $options);
}
}

View File

@@ -95,7 +95,7 @@ class MerchantPortalRoutePreviewService extends BaseService
));
} catch (Throwable $e) {
// 解析异常只影响路由结果,不影响基础信息展示,因此这里只回填失败原因。
$response['reason'] = $e->getMessage() !== '' ? $e->getMessage() : '路由解析失败';
$response['reason'] = $e->getMessage();
}
return $response;

View File

@@ -79,6 +79,56 @@ class MerchantPortalService extends BaseService
return $this->channelService->myChannels($filters, $merchantId, $page, $pageSize);
}
public function channelCreateMeta(): array
{
return $this->channelService->createMeta();
}
public function pluginConfigs(array $filters, int $merchantId, int $page, int $pageSize): array
{
return $this->channelService->pluginConfigs($filters, $merchantId, $page, $pageSize);
}
public function createPluginConfig(int $merchantId, array $data)
{
return $this->channelService->createPluginConfig($merchantId, $data);
}
public function updatePluginConfig(int $merchantId, int $id, array $data)
{
return $this->channelService->updatePluginConfig($merchantId, $id, $data);
}
public function deletePluginConfig(int $merchantId, int $id): bool
{
return $this->channelService->deletePluginConfig($merchantId, $id);
}
public function pluginConfigOptions(int $merchantId, string $pluginCode = ''): array
{
return $this->channelService->pluginConfigOptions($merchantId, $pluginCode);
}
public function createChannel(int $merchantId, array $data)
{
return $this->channelService->createChannel($merchantId, $data);
}
public function updateChannel(int $merchantId, int $id, array $data)
{
return $this->channelService->updateChannel($merchantId, $id, $data);
}
public function deleteChannel(int $merchantId, int $id): bool
{
return $this->channelService->deleteChannel($merchantId, $id);
}
public function pluginSchema(string $pluginCode): array
{
return $this->channelService->pluginSchema($pluginCode);
}
/**
* 获取商户路由解析结果。
*
@@ -108,11 +158,12 @@ class MerchantPortalService extends BaseService
* 生成或重置商户门户接口凭证。
*
* @param int $merchantId 商户ID
* @param array $options 生成选项
* @return array 凭证数据
*/
public function issueCredential(int $merchantId): array
public function issueCredential(int $merchantId, array $options = []): array
{
return $this->credentialService->issueCredential($merchantId);
return $this->credentialService->issueCredential($merchantId, $options);
}
/**

View File

@@ -127,7 +127,31 @@ class MerchantPortalSupportService extends BaseService
*/
public function enabledPayTypeOptions(): array
{
return $this->paymentTypeService->enabledOptions();
return array_values(array_filter(
$this->paymentTypeService->enabledOptions(),
static function (array $option): bool {
$label = trim((string) ($option['label'] ?? ''));
$value = trim((string) ($option['value'] ?? ''));
return $label !== '' && $label !== $value;
}
));
}
/**
* 商户开放接口对接信息。
*
* @param array $merchant 商户摘要
* @return array<string, mixed> 对接信息
*/
public function apiIntegrationInfo(array $merchant): array
{
$baseUrl = rtrim((string) sys_config('site_url', ''), '/');
return [
'base_url' => $baseUrl,
'merchant_id' => (int) ($merchant['merchant_id'] ?? $merchant['id'] ?? 0),
];
}
/**