255 lines
11 KiB
PHP
255 lines
11 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Admin\Product;
|
|
|
|
use App\Repositories\Admin\Product\AdminSaleCodeRepository;
|
|
use App\Services\Admin\AdminAuditService;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
final class AdminSaleCodeService
|
|
{
|
|
public function __construct(
|
|
private readonly AdminSaleCodeRepository $repo,
|
|
private readonly AdminAuditService $audit,
|
|
) {}
|
|
|
|
public function getGroupedTree(): array
|
|
{
|
|
$providers = $this->repo->getAllProviders();
|
|
$codes = $this->repo->getAllCodes();
|
|
|
|
$tree = [];
|
|
foreach ($providers as $p) {
|
|
$p['children'] = [];
|
|
$tree[$p['id']] = $p;
|
|
}
|
|
|
|
foreach ($codes as $c) {
|
|
if (isset($tree[$c['provider_id']])) {
|
|
$tree[$c['provider_id']]['children'][] = $c;
|
|
}
|
|
}
|
|
|
|
return array_values($tree);
|
|
}
|
|
|
|
public function storeProvider(array $input, int $actorAdminId, string $ip, string $ua): array
|
|
{
|
|
try {
|
|
return DB::transaction(function () use ($input, $actorAdminId, $ip, $ua) {
|
|
$data = $this->buildProviderPayload($input, true);
|
|
|
|
$newId = $this->repo->insertProvider($data);
|
|
$this->audit->log($actorAdminId, 'admin.sale_code.provider.create', 'api_provider', $newId, null, $data, $ip, $ua);
|
|
|
|
return ['ok' => true, 'message' => '연동사가 등록되었습니다.'];
|
|
});
|
|
} catch (\InvalidArgumentException $e) {
|
|
return ['ok' => false, 'message' => $e->getMessage()];
|
|
} catch (\Throwable $e) {
|
|
return ['ok' => false, 'message' => '연동사 코드가 중복되거나 저장 오류가 발생했습니다.'];
|
|
}
|
|
}
|
|
|
|
public function updateProvider(int $id, array $input, int $actorAdminId, string $ip, string $ua): array
|
|
{
|
|
try {
|
|
return DB::transaction(function () use ($id, $input, $actorAdminId, $ip, $ua) {
|
|
$before = $this->repo->findProvider($id);
|
|
if (!$before) {
|
|
return ['ok' => false, 'message' => '연동사를 찾을 수 없습니다.'];
|
|
}
|
|
|
|
$input['code'] = (string) $before->code; // 수정 시 code 고정
|
|
$data = $this->buildProviderPayload($input, false);
|
|
|
|
$this->repo->updateProvider($id, $data);
|
|
$this->audit->log(
|
|
$actorAdminId,
|
|
'admin.sale_code.provider.update',
|
|
'api_provider',
|
|
$id,
|
|
(array) $before,
|
|
array_merge((array) $before, $data),
|
|
$ip,
|
|
$ua
|
|
);
|
|
|
|
return ['ok' => true, 'message' => '연동사가 수정되었습니다.'];
|
|
});
|
|
} catch (\InvalidArgumentException $e) {
|
|
return ['ok' => false, 'message' => $e->getMessage()];
|
|
} catch (\Throwable $e) {
|
|
return ['ok' => false, 'message' => '수정 중 오류가 발생했습니다.'];
|
|
}
|
|
}
|
|
|
|
public function deleteProvider(int $id, int $actorAdminId, string $ip, string $ua): array
|
|
{
|
|
$before = $this->repo->findProvider($id);
|
|
if (!$before) return ['ok' => false, 'message' => '존재하지 않는 연동사입니다.'];
|
|
|
|
if ($this->repo->countCodesByProvider($id) > 0) {
|
|
return ['ok' => false, 'message' => '하위 상품 코드가 존재하여 삭제할 수 없습니다. 상품 코드를 먼저 삭제해주세요.'];
|
|
}
|
|
|
|
$this->repo->deleteProvider($id);
|
|
$this->audit->log($actorAdminId, 'admin.sale_code.provider.delete', 'api_provider', $id, (array) $before, null, $ip, $ua);
|
|
|
|
return ['ok' => true, 'message' => '삭제되었습니다.'];
|
|
}
|
|
|
|
public function storeCode(array $input, int $actorAdminId, string $ip, string $ua): array
|
|
{
|
|
try {
|
|
return DB::transaction(function () use ($input, $actorAdminId, $ip, $ua) {
|
|
$data = [
|
|
'provider_id' => (int)$input['provider_id'],
|
|
'api_code' => strtoupper(trim($input['api_code'])),
|
|
'name' => trim($input['name']),
|
|
'is_active' => (int)$input['is_active'],
|
|
'sort_order' => 0,
|
|
];
|
|
|
|
$newId = $this->repo->insertCode($data);
|
|
$this->audit->log($actorAdminId, 'admin.sale_code.code.create', 'api_code', $newId, null, $data, $ip, $ua);
|
|
|
|
return ['ok' => true, 'message' => '상품 코드가 등록되었습니다.'];
|
|
});
|
|
} catch (\Throwable $e) {
|
|
return ['ok' => false, 'message' => '해당 연동사에 중복된 코드가 있거나 저장 오류가 발생했습니다.'];
|
|
}
|
|
}
|
|
|
|
public function updateCode(int $id, array $input, int $actorAdminId, string $ip, string $ua): array
|
|
{
|
|
try {
|
|
return DB::transaction(function () use ($id, $input, $actorAdminId, $ip, $ua) {
|
|
$before = $this->repo->findCode($id);
|
|
if (!$before) return ['ok' => false, 'message' => '상품 코드를 찾을 수 없습니다.'];
|
|
|
|
$data = [
|
|
'provider_id' => (int)$input['provider_id'],
|
|
'api_code' => strtoupper(trim($input['api_code'])),
|
|
'name' => trim($input['name']),
|
|
'is_active' => (int)$input['is_active'],
|
|
];
|
|
|
|
$this->repo->updateCode($id, $data);
|
|
$this->audit->log($actorAdminId, 'admin.sale_code.code.update', 'api_code', $id, (array)$before, array_merge((array)$before, $data), $ip, $ua);
|
|
|
|
return ['ok' => true, 'message' => '상품 코드가 수정되었습니다.'];
|
|
});
|
|
} catch (\Throwable $e) {
|
|
return ['ok' => false, 'message' => '수정 중 중복 오류가 발생했습니다.'];
|
|
}
|
|
}
|
|
|
|
public function updateCodeSort(array $ids, int $actorAdminId): array
|
|
{
|
|
try {
|
|
DB::transaction(function () use ($ids) {
|
|
foreach ($ids as $index => $id) {
|
|
$this->repo->updateCodeSortOrder((int)$id, $index + 1);
|
|
}
|
|
});
|
|
return ['ok' => true];
|
|
} catch (\Throwable $e) {
|
|
return ['ok' => false, 'message' => '정렬 중 오류가 발생했습니다.'];
|
|
}
|
|
}
|
|
|
|
public function deleteCode(int $id, int $actorAdminId, string $ip, string $ua): array
|
|
{
|
|
$before = $this->repo->findCode($id);
|
|
if (!$before) return ['ok' => false, 'message' => '상품 코드를 찾을 수 없습니다.'];
|
|
|
|
$this->repo->deleteCode($id);
|
|
$this->audit->log($actorAdminId, 'admin.sale_code.code.delete', 'api_code', $id, (array)$before, null, $ip, $ua);
|
|
|
|
return ['ok' => true, 'message' => '상품 코드가 삭제되었습니다.'];
|
|
}
|
|
|
|
private function buildProviderPayload(array $input, bool $isCreate): array
|
|
{
|
|
$code = strtoupper(trim((string) ($input['code'] ?? '')));
|
|
$transportType = strtoupper(trim((string) ($input['transport_type'] ?? '')));
|
|
$baseUrl = $this->nullIfBlank($input['base_url'] ?? null);
|
|
$host = $this->nullIfBlank($input['host'] ?? null);
|
|
$port = isset($input['port']) && $input['port'] !== '' ? (int) $input['port'] : null;
|
|
|
|
$configJson = $this->normalizeConfigJson($input['config_json'] ?? null, $code);
|
|
|
|
if ($transportType === 'TCP_SOCKET') {
|
|
if ($host === null || $port === null) {
|
|
throw new \InvalidArgumentException('TCP_SOCKET 방식은 host, port를 반드시 입력해야 합니다.');
|
|
}
|
|
} else {
|
|
if ($baseUrl === null) {
|
|
throw new \InvalidArgumentException('HTTP 방식은 base_url을 반드시 입력해야 합니다.');
|
|
}
|
|
}
|
|
|
|
return array_merge(
|
|
$isCreate ? ['code' => $code] : [],
|
|
[
|
|
'name' => trim((string) $input['name']),
|
|
'transport_type' => $transportType,
|
|
'base_url' => $baseUrl,
|
|
'host' => $host,
|
|
'port' => $port,
|
|
'timeout_connect_sec' => (int) $input['timeout_connect_sec'],
|
|
'timeout_read_sec' => (int) $input['timeout_read_sec'],
|
|
'charset' => strtoupper(trim((string) $input['charset'])),
|
|
'response_format_default' => $this->nullIfBlank($input['response_format_default'] ?? null),
|
|
'is_test_mode' => (int) $input['is_test_mode'],
|
|
'supports_issue' => (int) $input['supports_issue'],
|
|
'supports_cancel' => (int) $input['supports_cancel'],
|
|
'supports_resend' => (int) $input['supports_resend'],
|
|
'supports_cancel_check' => (int) $input['supports_cancel_check'],
|
|
'supports_network_cancel' => (int) $input['supports_network_cancel'],
|
|
'config_json' => $configJson,
|
|
'is_active' => (int) $input['is_active'],
|
|
'sort_order' => 0,
|
|
]
|
|
);
|
|
}
|
|
|
|
private function normalizeConfigJson(?string $json, string $providerCode): ?string
|
|
{
|
|
$json = $this->nullIfBlank($json);
|
|
if ($json === null) {
|
|
return null;
|
|
}
|
|
|
|
$decoded = json_decode($json, true);
|
|
if (!is_array($decoded)) {
|
|
throw new \InvalidArgumentException('상세 설정 JSON 형식이 올바르지 않습니다.');
|
|
}
|
|
|
|
foreach ($this->requiredConfigKeys($providerCode) as $requiredKey) {
|
|
if (!array_key_exists($requiredKey, $decoded)) {
|
|
throw new \InvalidArgumentException("상세 설정 JSON에 필수 키가 없습니다: {$requiredKey}");
|
|
}
|
|
}
|
|
|
|
return json_encode($decoded, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
|
}
|
|
|
|
private function requiredConfigKeys(string $providerCode): array
|
|
{
|
|
return match ($providerCode) {
|
|
'DANAL' => ['cp_id', 'cp_pwd_enc', 'subcpid', 'crypto_key_enc', 'crypto_iv_enc'],
|
|
'KORCULTURE' => ['member_code', 'sub_member_code', 'issue_tr_code', 'cancel_tr_code'],
|
|
'KPREPAID' => ['chain_code', 'issue_path', 'cancel_path'],
|
|
default => [],
|
|
};
|
|
}
|
|
|
|
private function nullIfBlank(mixed $value): ?string
|
|
{
|
|
$value = is_string($value) ? trim($value) : $value;
|
|
return ($value === '' || $value === null) ? null : (string) $value;
|
|
}
|
|
}
|