fix(nhnkcp): 주문설정 테스트결제 경고 표시 개선
This commit is contained in:
@@ -9,3 +9,4 @@
|
||||
### Added
|
||||
|
||||
- 환불 진행 중 안내(`refund.in_progress`)와 KCP CLI 바이너리 누락·실행 권한 부족 안내(`errors.cli_binary_missing`, `errors.cli_binary_not_executable`) 일본어 번역 추가 — 환불 중복 요청·CLI 실행 환경 오류 안내가 일본어 로케일에서 자연스럽게 표시됩니다.
|
||||
- 주문 설정 화면의 NHN KCP 테스트모드 경고(`nhnkcp_test_mode_status`, `test_mode_settings_warning_*`) 일본어 번역 추가 — 테스트결제 상태 안내와 실결제 설정 이동 버튼이 일본어 로케일에서 자연스럽게 표시됩니다.
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"kcp_escrow_delivery": "エスクロー配送",
|
||||
"kcp_status": "決済状態",
|
||||
"nhnkcp_easy_pay_config": "簡便決済設定",
|
||||
"nhnkcp_test_mode_status": "テストモード状態",
|
||||
"nhnkcp_test_map": "テスト決済マップ",
|
||||
"nicepay_test_map": "テスト決済マップ",
|
||||
"settings": "決済設定",
|
||||
@@ -48,6 +49,10 @@
|
||||
"admin": {
|
||||
"test_mode_badge": "テスト決済",
|
||||
"test_mode_order_notice": "この注文はテスト決済として処理されました。",
|
||||
"test_mode_settings_warning_plugin": "NHN KCP",
|
||||
"test_mode_settings_warning_title": "(注意)NHN KCP決済設定が現在テスト決済になっています。",
|
||||
"test_mode_settings_warning_body": "テスト決済では実際の決済は発生しません。ショップ運営中の場合は必ず実決済に設定して運営してください。",
|
||||
"test_mode_settings_warning_action": "実決済に設定",
|
||||
"order_info_title": "NHN KCP決済情報",
|
||||
"order_info_description": "DBに保存されたKCP決済情報を表示します。",
|
||||
"pay_status_approved": "承認",
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
- 관리자 주문상세의 NHN KCP 결제 정보 패널이 원승인 정보와 현재 취소/환불 상태를 함께 표시하도록 보강했습니다.
|
||||
- PC 결제창 close-report와 승인 콜백이 경쟁할 때 결제 성공 주문이 실패 처리로 덮이지 않도록 주문번호 단위 콜백 락과 최신 결제 상태 가드를 적용했습니다.
|
||||
- NHN KCP 부분취소 요청에서 `rem_mny`를 취소 후 잔액이 아닌 현재 부분취소 가능 금액으로 전달해 큰 금액 부분취소가 거절될 수 있던 문제를 수정했습니다.
|
||||
- 이커머스 주문설정 화면에서 NHN KCP 플러그인이 테스트 모드일 때 운영 전환 경고와 설정 이동 버튼을 표시하도록 보강했습니다.
|
||||
|
||||
## [1.0.0-beta.5] - 2026-06-23
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"health": "Health",
|
||||
"kcp_escrow_delivery": "Escrow Delivery",
|
||||
"kcp_status": "Payment Status",
|
||||
"nhnkcp_test_mode_status": "Test Mode Status",
|
||||
"nhnkcp_easy_pay_config": "Easy Pay Config",
|
||||
"nhnkcp_easy_pay_display_map": "Easy Pay Display Map",
|
||||
"nhnkcp_test_map": "Test Payment Map",
|
||||
@@ -49,6 +50,10 @@
|
||||
"admin": {
|
||||
"test_mode_badge": "Test Payment",
|
||||
"test_mode_order_notice": "This order was processed as a test payment.",
|
||||
"test_mode_settings_warning_plugin": "NHN KCP",
|
||||
"test_mode_settings_warning_title": "Warning: NHN KCP 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_info_title": "NHN KCP Payment Info",
|
||||
"order_info_description": "Displays stored KCP original approval and current payment status.",
|
||||
"pay_status_approved": "Approved",
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"health": "연동 상태",
|
||||
"kcp_escrow_delivery": "에스크로 배송",
|
||||
"kcp_status": "결제 상태",
|
||||
"nhnkcp_test_mode_status": "테스트 모드 상태",
|
||||
"nhnkcp_easy_pay_config": "간편결제 설정",
|
||||
"nhnkcp_easy_pay_display_map": "간편결제 표시 맵",
|
||||
"nhnkcp_test_map": "테스트 결제 맵",
|
||||
@@ -49,6 +50,10 @@
|
||||
"admin": {
|
||||
"test_mode_badge": "테스트 결제",
|
||||
"test_mode_order_notice": "이 주문은 테스트 결제로 처리되었습니다.",
|
||||
"test_mode_settings_warning_plugin": "NHN KCP",
|
||||
"test_mode_settings_warning_title": "(주의!) NHN KCP 결제 설정이 현재 테스트결제로 되어 있습니다.",
|
||||
"test_mode_settings_warning_body": "테스트결제 시 실제 결제가 되지 않으므로, 쇼핑몰 운영 중이면 반드시 실결제로 설정하여 운영하세요.",
|
||||
"test_mode_settings_warning_action": "실결제로 설정",
|
||||
"order_info_title": "NHN KCP 결제 정보",
|
||||
"order_info_description": "DB에 저장된 KCP 원승인 및 현재 결제 상태를 표시합니다.",
|
||||
"pay_status_approved": "승인",
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Plugins\Sirsoft\PayNhnkcp\Controllers;
|
||||
|
||||
use App\Helpers\ResponseHelper;
|
||||
use App\Services\PluginSettingsService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class AdminSettingsStatusController
|
||||
{
|
||||
private const PLUGIN_IDENTIFIER = 'sirsoft-pay_nhnkcp';
|
||||
|
||||
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),
|
||||
]);
|
||||
}
|
||||
}
|
||||
+230
-1
@@ -13,6 +13,14 @@ class AdjustEcommercePaymentMethodsLayoutListener implements HookListenerInterfa
|
||||
{
|
||||
private const TARGET_LAYOUT = 'admin_ecommerce_settings';
|
||||
|
||||
private const TEST_MODE_DATA_SOURCE_ID = 'nhnkcp_test_mode_status';
|
||||
|
||||
private const TEST_MODE_NOTICE_ID = 'payment_test_mode_order_settings_notice';
|
||||
|
||||
private const TEST_MODE_NOTICE_ROW_ID = 'nhnkcp_test_mode_order_settings_notice';
|
||||
|
||||
private const TEST_MODE_CONDITION = 'nhnkcp_test_mode_status.data?.is_test_mode === true';
|
||||
|
||||
private const CORE_NO_PG_METHODS = ['point', 'deposit', 'free', 'dbank'];
|
||||
|
||||
private const NHNKCP_NO_PG_METHODS = [
|
||||
@@ -44,6 +52,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
|
||||
@@ -52,7 +61,9 @@ class AdjustEcommercePaymentMethodsLayoutListener implements HookListenerInterfa
|
||||
return $layout;
|
||||
}
|
||||
|
||||
return $this->replaceNoPgMethodExpressions($layout);
|
||||
$layout = $this->replaceNoPgMethodExpressions($layout);
|
||||
|
||||
return $this->ensureTestModeWarning($layout);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -100,4 +111,222 @@ class AdjustEcommercePaymentMethodsLayoutListener implements HookListenerInterfa
|
||||
$expression
|
||||
) ?? $expression;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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_nhnkcp.editor.data_source.nhnkcp_test_mode_status',
|
||||
'type' => 'api',
|
||||
'endpoint' => '/api/plugins/sirsoft-pay_nhnkcp/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_nhnkcp.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_nhnkcp.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_nhnkcp/settings',
|
||||
],
|
||||
],
|
||||
],
|
||||
'children' => [
|
||||
[
|
||||
'type' => 'basic',
|
||||
'name' => 'Span',
|
||||
'text' => '$t:sirsoft-pay_nhnkcp.admin.test_mode_settings_warning_action',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Plugins\Sirsoft\PayNhnkcp\Controllers\AdminEscrowDeliveryController;
|
||||
use Plugins\Sirsoft\PayNhnkcp\Controllers\AdminOrderListController;
|
||||
use Plugins\Sirsoft\PayNhnkcp\Controllers\AdminSettingsStatusController;
|
||||
use Plugins\Sirsoft\PayNhnkcp\Controllers\AdminTransactionController;
|
||||
use Plugins\Sirsoft\PayNhnkcp\Controllers\HealthCheckController;
|
||||
use Plugins\Sirsoft\PayNhnkcp\Controllers\MobileApprovalController;
|
||||
@@ -53,6 +54,10 @@ Route::prefix('admin')->name('admin.')->middleware(['auth:sanctum', 'admin'])->g
|
||||
],
|
||||
]))->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');
|
||||
|
||||
// 테스트 모드 주문 맵 (관리자 주문목록 배지 표시용)
|
||||
Route::get('/orders/test-mode-map', [AdminOrderListController::class, 'testModeMap'])
|
||||
->name('orders.test-mode-map');
|
||||
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Plugins\Sirsoft\PayNhnkcp\Tests\Feature\Controllers;
|
||||
|
||||
use App\Services\PluginSettingsService;
|
||||
use Plugins\Sirsoft\PayNhnkcp\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_nhnkcp', '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_nhnkcp/admin/settings/test-mode-status');
|
||||
|
||||
$response->assertOk()
|
||||
->assertJsonPath('data.is_test_mode', true);
|
||||
|
||||
$data = $response->json('data');
|
||||
$this->assertArrayNotHasKey('test_site_cd', $data);
|
||||
$this->assertArrayNotHasKey('live_site_cd', $data);
|
||||
}
|
||||
}
|
||||
+39
@@ -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('nhnkcp_test_mode_status', $json);
|
||||
$this->assertStringContainsString('/api/plugins/sirsoft-pay_nhnkcp/admin/settings/test-mode-status', $json);
|
||||
$this->assertStringContainsString('payment_test_mode_order_settings_notice', $json);
|
||||
$this->assertStringContainsString('nhnkcp_test_mode_order_settings_notice', $json);
|
||||
$this->assertStringNotContainsString('sirsoft-pay_nhnkcp.admin.test_mode_settings_summary_title', $json);
|
||||
$this->assertStringNotContainsString('sirsoft-pay_nhnkcp.admin.test_mode_settings_summary_body', $json);
|
||||
$this->assertStringContainsString('sirsoft-pay_nhnkcp.admin.test_mode_settings_warning_plugin', $json);
|
||||
$this->assertStringContainsString('sirsoft-pay_nhnkcp.admin.test_mode_settings_warning_body', $json);
|
||||
$this->assertStringContainsString('/admin/plugins/sirsoft-pay_nhnkcp/settings', $json);
|
||||
$this->assertSame(1, substr_count($json, 'payment_test_mode_order_settings_notice'));
|
||||
$this->assertSame(1, substr_count($json, 'nhnkcp_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();
|
||||
|
||||
Reference in New Issue
Block a user