fix(kginicis): 주문설정 테스트결제 경고 표시 개선

This commit is contained in:
thisgun
2026-07-07 16:38:15 +09:00
parent ff45a19b4b
commit b9355f051c
10 changed files with 365 additions and 1 deletions
@@ -4,6 +4,12 @@
형식은 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/)를 따르며,
[Semantic Versioning](https://semver.org/lang/ko/)을 준수합니다.
## [Unreleased]
### Added
- 주문 설정 화면의 KG 이니시스 테스트모드 경고(`kginicis_test_mode_status`, `test_mode_settings_warning_*`) 일본어 번역 추가 — 테스트결제 상태 안내와 실결제 설정 이동 버튼이 일본어 로케일에서 자연스럽게 표시됩니다.
## [1.0.0] - 2026-07-01
### Added
@@ -4,6 +4,7 @@
"kginicis_cbt_reconciliation": "海外決済精算",
"kginicis_escrow_delivery": "エスクロー配送",
"kginicis_status": "決済ステータス",
"kginicis_test_mode_status": "テストモード状態",
"kginicis_test_map": "テスト決済マップ",
"settings": "決済設定",
"vbank_info": "仮想口座情報"
@@ -74,6 +75,10 @@
"test_mode_badge": "テスト決済",
"test_mode_warning_badge": "テスト決済注意事項",
"test_mode_order_notice": "この注文はテスト決済として処理されました。実際のカード承認・出金通知が発生する可能性がありますが、KGイニシスのテスト取引は毎日23:00~23:50の間に自動キャンセルされる可能性があります。絶対に配送しないでください。",
"test_mode_settings_warning_plugin": "KGイニシス",
"test_mode_settings_warning_title": "(注意)KGイニシス決済設定が現在テスト決済になっています。",
"test_mode_settings_warning_body": "テスト決済では実際の決済は発生しません。ショップ運営中の場合は必ず実決済に設定して運営してください。",
"test_mode_settings_warning_action": "実決済に設定",
"order_verify_title": "KGイニシス決済検証",
"order_verify_description": "KGイニシスサーバーからリアルタイムで決済ステータスを照会します。",
"order_verify_button": "決済照会",
@@ -4,6 +4,12 @@
형식은 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/)를 따르며,
[Semantic Versioning](https://semver.org/lang/ko/)을 준수합니다.
## [Unreleased]
### Fixed
- 이커머스 주문설정 화면에서 KG 이니시스 플러그인이 테스트 모드일 때 운영 전환 경고와 설정 이동 버튼을 표시하도록 보강했습니다.
## [1.0.0] - 2026-07-01
### Added
@@ -4,6 +4,7 @@
"kginicis_cbt_reconciliation": "Cross-border Reconciliation",
"kginicis_escrow_delivery": "Escrow Delivery",
"kginicis_status": "Payment Status",
"kginicis_test_mode_status": "Test Mode Status",
"kginicis_test_map": "Test Payment Map",
"settings": "Payment Settings",
"vbank_info": "Virtual Account Info"
@@ -74,6 +75,10 @@
"test_mode_badge": "Test Payment",
"test_mode_warning_badge": "Test Payment WARNING)",
"test_mode_order_notice": "This order was processed in test mode. Real card authorization/withdrawal notifications may occur, but KG Inicis test transactions may be automatically cancelled daily between 23:00 and 23:50. DO NOT ship this order.",
"test_mode_settings_warning_plugin": "KG Inicis",
"test_mode_settings_warning_title": "Warning: KG Inicis payments are currently set to test mode.",
"test_mode_settings_warning_body": "Test payments do not create real payments. If the shop is live, switch this plugin to live payment mode before operating.",
"test_mode_settings_warning_action": "Switch to live mode",
"order_verify_title": "KG Inicis Payment Verification",
"order_verify_description": "Query real-time payment status from KG Inicis servers.",
"order_verify_button": "Query Payment",
@@ -4,6 +4,7 @@
"kginicis_cbt_reconciliation": "해외결제 정산",
"kginicis_escrow_delivery": "에스크로 배송",
"kginicis_status": "결제 상태",
"kginicis_test_mode_status": "테스트 모드 상태",
"kginicis_test_map": "테스트 결제 맵",
"settings": "결제 설정",
"vbank_info": "가상계좌 정보"
@@ -74,6 +75,10 @@
"test_mode_badge": "테스트 결제",
"test_mode_warning_badge": "테스트 결제 주의)",
"test_mode_order_notice": "이 주문은 테스트 결제로 처리되었습니다. 실제 카드 승인/출금 알림이 발생할 수 있으나, KG 이니시스 테스트 거래는 매일 23:00~23:50 사이 자동 취소될 수 있습니다. 절대 배송하지 마세요.",
"test_mode_settings_warning_plugin": "KG 이니시스",
"test_mode_settings_warning_title": "(주의!) KG 이니시스 결제 설정이 현재 테스트결제로 되어 있습니다.",
"test_mode_settings_warning_body": "테스트결제 시 실제 결제가 되지 않으므로, 쇼핑몰 운영 중이면 반드시 실결제로 설정하여 운영하세요.",
"test_mode_settings_warning_action": "실결제로 설정",
"order_verify_title": "KG 이니시스 결제 검증",
"order_verify_description": "KG 이니시스 서버에서 실시간으로 결제 상태를 조회합니다.",
"order_verify_button": "결제 조회",
@@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace Plugins\Sirsoft\PayKginicis\Controllers;
use App\Helpers\ResponseHelper;
use App\Services\PluginSettingsService;
use Illuminate\Http\JsonResponse;
class AdminSettingsStatusController
{
private const PLUGIN_IDENTIFIER = 'sirsoft-pay_kginicis';
public function __construct(private readonly PluginSettingsService $settingsService) {}
/**
* 테스트 모드 활성 여부만 반환한다.
*
* @return JsonResponse
*/
public function testMode(): JsonResponse
{
return ResponseHelper::success('messages.success', [
'is_test_mode' => (bool) $this->settingsService->get(self::PLUGIN_IDENTIFIER, 'is_test_mode', true),
]);
}
}
@@ -13,10 +13,21 @@ class AdjustEcommercePaymentMethodsLayoutListener implements HookListenerInterfa
{
private const TARGET_LAYOUT = 'admin_ecommerce_settings';
private const TEST_MODE_DATA_SOURCE_ID = 'kginicis_test_mode_status';
private const TEST_MODE_NOTICE_ID = 'payment_test_mode_order_settings_notice';
private const TEST_MODE_NOTICE_ROW_ID = 'kginicis_test_mode_order_settings_notice';
private const TEST_MODE_CONDITION = 'kginicis_test_mode_status.data?.is_test_mode === true';
private const CORE_NO_PG_METHODS = "['point','deposit','free','dbank']";
private const KGINICIS_NO_PG_METHODS = "['point','deposit','free','dbank','kginicis_samsung_pay','kginicis_naverpay','kginicis_lpay','kginicis_kakaopay','kginicis_japan_paypay','kginicis_japan_cvs']";
/**
* @return array<string, array<string, mixed>>
*/
public static function getSubscribedHooks(): array
{
return [
@@ -37,6 +48,7 @@ class AdjustEcommercePaymentMethodsLayoutListener implements HookListenerInterfa
/**
* @param array<string, mixed> $layout
* @param int $templateId
* @return array<string, mixed>
*/
public function markEasyPayMethodsAsPgNotRequired(array $layout, int $templateId): array
@@ -45,7 +57,9 @@ class AdjustEcommercePaymentMethodsLayoutListener implements HookListenerInterfa
return $layout;
}
return $this->replaceNoPgMethodExpressions($layout);
$layout = $this->replaceNoPgMethodExpressions($layout);
return $this->ensureTestModeWarning($layout);
}
/**
@@ -71,4 +85,222 @@ class AdjustEcommercePaymentMethodsLayoutListener implements HookListenerInterfa
return $node;
}
/**
* @param array<string, mixed> $layout
* @return array<string, mixed>
*/
private function ensureTestModeWarning(array $layout): array
{
$layout = $this->ensureTestModeDataSource($layout);
return $this->insertTestModeNotice($layout);
}
/**
* @param array<string, mixed> $layout
* @return array<string, mixed>
*/
private function ensureTestModeDataSource(array $layout): array
{
$dataSources = is_array($layout['data_sources'] ?? null) ? $layout['data_sources'] : [];
foreach ($dataSources as $source) {
if (is_array($source) && ($source['id'] ?? null) === self::TEST_MODE_DATA_SOURCE_ID) {
$layout['data_sources'] = $dataSources;
return $layout;
}
}
$dataSources[] = [
'id' => self::TEST_MODE_DATA_SOURCE_ID,
'label_key' => '$t:sirsoft-pay_kginicis.editor.data_source.kginicis_test_mode_status',
'type' => 'api',
'endpoint' => '/api/plugins/sirsoft-pay_kginicis/admin/settings/test-mode-status',
'method' => 'GET',
'auto_fetch' => true,
'auth_required' => true,
];
$layout['data_sources'] = $dataSources;
return $layout;
}
/**
* @param array<string, mixed> $node
* @return array<string, mixed>
*/
private function insertTestModeNotice(array $node): array
{
if (($node['id'] ?? null) === 'tab_content_order_settings' && is_array($node['children'] ?? null)) {
$noticeIndex = $this->findChildIndexById($node['children'], self::TEST_MODE_NOTICE_ID);
if ($noticeIndex === null) {
$insertAt = $this->findChildIndexById($node['children'], 'payment_methods_card') ?? count($node['children']);
array_splice($node['children'], $insertAt, 0, [$this->testModeNoticeContainerNode()]);
$noticeIndex = $insertAt;
}
if (is_array($node['children'][$noticeIndex] ?? null)) {
$notice = $node['children'][$noticeIndex];
$notice['if'] = $this->mergeTestModeCondition((string) ($notice['if'] ?? ''));
$node['children'][$noticeIndex] = $this->appendTestModeNoticeRow($notice);
}
return $node;
}
foreach ($node as $key => $value) {
if (is_array($value)) {
$node[$key] = $this->insertTestModeNotice($value);
}
}
return $node;
}
/**
* @param array<int, mixed> $children
*/
private function findChildIndexById(array $children, string $id): ?int
{
foreach ($children as $index => $child) {
if (is_array($child) && ($child['id'] ?? null) === $id) {
return $index;
}
}
return null;
}
/**
* @param array<int, mixed> $children
*/
private function hasChildWithId(array $children, string $id): bool
{
return $this->findChildIndexById($children, $id) !== null;
}
private function mergeTestModeCondition(string $condition): string
{
if (str_contains($condition, self::TEST_MODE_CONDITION)) {
return $condition !== '' ? $condition : '{{' . self::TEST_MODE_CONDITION . '}}';
}
$inner = trim($condition);
if (str_starts_with($inner, '{{') && str_ends_with($inner, '}}')) {
$inner = trim(substr($inner, 2, -2));
}
if ($inner === '') {
return '{{' . self::TEST_MODE_CONDITION . '}}';
}
return '{{' . $inner . ' || ' . self::TEST_MODE_CONDITION . '}}';
}
/**
* @param array<string, mixed> $notice
* @return array<string, mixed>
*/
private function appendTestModeNoticeRow(array $notice): array
{
$children = is_array($notice['children'] ?? null) ? $notice['children'] : [];
if (! $this->hasChildWithId($children, self::TEST_MODE_NOTICE_ROW_ID)) {
$children[] = $this->testModeNoticeRowNode();
}
$notice['children'] = $children;
return $notice;
}
/**
* @return array<string, mixed>
*/
private function testModeNoticeContainerNode(): array
{
return [
'id' => self::TEST_MODE_NOTICE_ID,
'type' => 'basic',
'name' => 'Div',
'if' => '{{' . self::TEST_MODE_CONDITION . '}}',
'props' => [
'className' => 'mb-4 space-y-3 rounded-lg border border-orange-200 bg-orange-50 p-4 dark:border-orange-700 dark:bg-orange-900/20',
],
'children' => [
$this->testModeNoticeRowNode(),
],
];
}
/**
* @return array<string, mixed>
*/
private function testModeNoticeRowNode(): array
{
return [
'id' => self::TEST_MODE_NOTICE_ROW_ID,
'type' => 'basic',
'name' => 'Div',
'if' => '{{' . self::TEST_MODE_CONDITION . '}}',
'props' => [
'className' => 'flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between',
],
'children' => [
[
'type' => 'basic',
'name' => 'Div',
'props' => [
'className' => 'min-w-0',
],
'children' => [
[
'type' => 'basic',
'name' => 'Div',
'props' => [
'className' => 'text-sm font-medium text-orange-950 dark:text-orange-50',
],
'text' => '$t:sirsoft-pay_kginicis.admin.test_mode_settings_warning_plugin',
],
[
'type' => 'basic',
'name' => 'P',
'props' => [
'className' => 'mt-0.5 text-sm leading-6 text-orange-800 dark:text-orange-200',
],
'text' => '$t:sirsoft-pay_kginicis.admin.test_mode_settings_warning_body',
],
],
],
[
'type' => 'basic',
'name' => 'Button',
'props' => [
'type' => 'button',
'className' => 'btn btn-outline btn-sm w-full flex-shrink-0 border-orange-300 text-orange-900 hover:bg-orange-100 sm:w-auto dark:border-orange-600 dark:text-orange-100 dark:hover:bg-orange-900/40',
],
'actions' => [
[
'type' => 'click',
'handler' => 'navigate',
'params' => [
'path' => '/admin/plugins/sirsoft-pay_kginicis/settings',
],
],
],
'children' => [
[
'type' => 'basic',
'name' => 'Span',
'text' => '$t:sirsoft-pay_kginicis.admin.test_mode_settings_warning_action',
],
],
],
],
];
}
}
@@ -9,6 +9,7 @@ use Plugins\Sirsoft\PayKginicis\Controllers\AdminCbtTestProductController;
use Plugins\Sirsoft\PayKginicis\Controllers\AdminEscrowDeliveryController;
use Plugins\Sirsoft\PayKginicis\Controllers\AdminEscrowDenyConfirmController;
use Plugins\Sirsoft\PayKginicis\Controllers\AdminOrderListController;
use Plugins\Sirsoft\PayKginicis\Controllers\AdminSettingsStatusController;
use Plugins\Sirsoft\PayKginicis\Controllers\AdminTransactionController;
use Plugins\Sirsoft\PayKginicis\Controllers\CbtCheckoutTokenController;
use Plugins\Sirsoft\PayKginicis\Controllers\CbtHashDataController;
@@ -66,6 +67,10 @@ Route::prefix('admin')->name('admin.')->middleware(['auth:sanctum', 'admin'])->g
})->middleware('permission:admin,sirsoft-ecommerce.settings.read')
->name('vbank.notify.url');
Route::get('/settings/test-mode-status', [AdminSettingsStatusController::class, 'testMode'])
->middleware('permission:admin,sirsoft-ecommerce.settings.read')
->name('settings.test-mode-status');
// 어드민 주문 목록의 테스트 결제 배지 표시용 맵 (auto_fetch)
Route::get('/orders/test-mode-map', [AdminOrderListController::class, 'testModeMap'])
->middleware('permission:admin,sirsoft-ecommerce.orders.read')
@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Plugins\Sirsoft\PayKginicis\Tests\Feature\Controllers;
use App\Services\PluginSettingsService;
use Plugins\Sirsoft\PayKginicis\Tests\PluginTestCase;
class AdminSettingsStatusControllerTest extends PluginTestCase
{
public function test_test_mode_status_returns_current_setting(): void
{
$settings = $this->createMock(PluginSettingsService::class);
$settings->expects($this->once())
->method('get')
->with('sirsoft-pay_kginicis', 'is_test_mode', true)
->willReturn(true);
$this->app->instance(PluginSettingsService::class, $settings);
$admin = $this->createAdminUser(['sirsoft-ecommerce.settings.read']);
$response = $this->actingAs($admin)
->getJson('/api/plugins/sirsoft-pay_kginicis/admin/settings/test-mode-status');
$response->assertOk()
->assertJsonPath('data.is_test_mode', true);
$data = $response->json('data');
$this->assertArrayNotHasKey('test_mid', $data);
$this->assertArrayNotHasKey('live_mid', $data);
}
}
@@ -58,6 +58,45 @@ class AdjustEcommercePaymentMethodsLayoutListenerTest extends TestCase
$this->assertStringNotContainsString("['point','deposit','free','dbank'].includes", $json);
}
public function test_adds_test_mode_warning_to_order_settings_tab(): void
{
$listener = new AdjustEcommercePaymentMethodsLayoutListener();
$layout = [
'layout_name' => 'admin_ecommerce_settings',
'children' => [
[
'id' => 'tab_content_order_settings',
'children' => [
['id' => 'default_pg_card'],
['id' => 'payment_methods_card'],
],
],
],
];
$result = $listener->markEasyPayMethodsAsPgNotRequired($layout, 1);
$result = $listener->markEasyPayMethodsAsPgNotRequired($result, 1);
$json = json_encode($result, JSON_UNESCAPED_SLASHES);
$this->assertIsString($json);
$this->assertStringContainsString('kginicis_test_mode_status', $json);
$this->assertStringContainsString('/api/plugins/sirsoft-pay_kginicis/admin/settings/test-mode-status', $json);
$this->assertStringContainsString('payment_test_mode_order_settings_notice', $json);
$this->assertStringContainsString('kginicis_test_mode_order_settings_notice', $json);
$this->assertStringNotContainsString('sirsoft-pay_kginicis.admin.test_mode_settings_summary_title', $json);
$this->assertStringNotContainsString('sirsoft-pay_kginicis.admin.test_mode_settings_summary_body', $json);
$this->assertStringContainsString('sirsoft-pay_kginicis.admin.test_mode_settings_warning_plugin', $json);
$this->assertStringContainsString('sirsoft-pay_kginicis.admin.test_mode_settings_warning_body', $json);
$this->assertStringContainsString('/admin/plugins/sirsoft-pay_kginicis/settings', $json);
$this->assertSame(1, substr_count($json, 'payment_test_mode_order_settings_notice'));
$this->assertSame(1, substr_count($json, 'kginicis_test_mode_order_settings_notice'));
$this->assertLessThan(
strpos($json, 'payment_methods_card'),
strpos($json, 'payment_test_mode_order_settings_notice')
);
}
public function test_leaves_other_layouts_unchanged(): void
{
$listener = new AdjustEcommercePaymentMethodsLayoutListener();