diff --git a/lang-packs/_bundled/g7-plugin-sirsoft-pay_nicepayments-ja/CHANGELOG.md b/lang-packs/_bundled/g7-plugin-sirsoft-pay_nicepayments-ja/CHANGELOG.md index d553c81c..a59ecfb2 100644 --- a/lang-packs/_bundled/g7-plugin-sirsoft-pay_nicepayments-ja/CHANGELOG.md +++ b/lang-packs/_bundled/g7-plugin-sirsoft-pay_nicepayments-ja/CHANGELOG.md @@ -9,3 +9,4 @@ ### Added - 가상계좌 환불 검증 안내(`errors.vbank_refund_*` — 환불 대상 주문 없음·환불 불가 결제·환불 금액 불일치·환불 처리 진행 중) 일본어 번역 추가 — 관리자 가상계좌 환불 요청 검증 실패 안내가 일본어 로케일에서 자연스럽게 표시됩니다. +- 주문 설정 화면의 나이스페이먼츠 테스트모드 경고(`nicepay_test_mode_status`, `test_mode_settings_warning_*`) 일본어 번역 추가 — 테스트결제 상태 안내와 실결제 설정 이동 버튼이 일본어 로케일에서 자연스럽게 표시됩니다. diff --git a/lang-packs/_bundled/g7-plugin-sirsoft-pay_nicepayments-ja/frontend/ja.json b/lang-packs/_bundled/g7-plugin-sirsoft-pay_nicepayments-ja/frontend/ja.json index a7a95827..50cb2574 100644 --- a/lang-packs/_bundled/g7-plugin-sirsoft-pay_nicepayments-ja/frontend/ja.json +++ b/lang-packs/_bundled/g7-plugin-sirsoft-pay_nicepayments-ja/frontend/ja.json @@ -4,6 +4,7 @@ "nicepay_easy_pay_config": "簡易決済設定", "nicepay_escrow": "エスクロー", "nicepay_status": "決済ステータス", + "nicepay_test_mode_status": "テストモード状態", "nicepay_test_map": "テスト決済マップ", "settings": "決済設定", "vbank_info": "仮想口座情報" @@ -109,6 +110,10 @@ "escrow_register_error": "エスクロー配送登録に失敗しました。", "test_mode_badge": "テスト決済", "test_mode_order_notice": "テスト決済として処理された注文です。実際のカード承認・出金通知が発生する可能性がありますが、NicePaymentsテストアカウント決済は当日23時30分頃一括自動キャンセルされます。配送前に運営決済の有無をご確認ください。", + "test_mode_settings_warning_plugin": "NicePayments", + "test_mode_settings_warning_title": "(注意)NicePayments決済設定が現在テスト決済になっています。", + "test_mode_settings_warning_body": "テスト決済では実際の決済は発生しません。ショップ運営中の場合は必ず実決済に設定して運営してください。", + "test_mode_settings_warning_action": "実決済に設定", "test_mode_orders_in_list": "テスト決済注文を含む" }, "easy_pay": { diff --git a/plugins/_bundled/sirsoft-pay_nicepayments/CHANGELOG.md b/plugins/_bundled/sirsoft-pay_nicepayments/CHANGELOG.md index 3f220366..296e25dc 100644 --- a/plugins/_bundled/sirsoft-pay_nicepayments/CHANGELOG.md +++ b/plugins/_bundled/sirsoft-pay_nicepayments/CHANGELOG.md @@ -27,6 +27,7 @@ - 환불 요청 시 현재 설정값이 아니라 결제 당시 MID와 테스트/운영 모드로 나이스페이먼츠 API 서명 컨텍스트를 복원하도록 수정했습니다. - 관리자 가상계좌 환불이 PG 취소만 호출하고 주문/결제/환불 장부를 갱신하지 않던 문제를 수정했습니다. - 결제완료 커밋 후 후속 처리 예외가 발생한 경우 이미 결제완료 상태인 거래에 망취소를 보내지 않도록 보상 처리 가드를 추가했습니다. +- 이커머스 주문설정 화면에서 나이스페이먼츠 플러그인이 테스트 모드일 때 운영 전환 경고와 설정 이동 버튼을 표시하도록 보강했습니다. ## [1.0.0-beta.4] - 2026-06-03 diff --git a/plugins/_bundled/sirsoft-pay_nicepayments/resources/lang/en.json b/plugins/_bundled/sirsoft-pay_nicepayments/resources/lang/en.json index f656cc9a..e644cfcc 100644 --- a/plugins/_bundled/sirsoft-pay_nicepayments/resources/lang/en.json +++ b/plugins/_bundled/sirsoft-pay_nicepayments/resources/lang/en.json @@ -4,6 +4,7 @@ "nicepay_easy_pay_config": "Easy Pay Config", "nicepay_escrow": "Escrow", "nicepay_status": "Payment Status", + "nicepay_test_mode_status": "Test Mode Status", "nicepay_test_map": "Test Payment Map", "settings": "Payment Settings", "vbank_info": "Virtual Account Info" @@ -109,6 +110,10 @@ "escrow_register_error": "Failed to register escrow delivery.", "test_mode_badge": "TEST", "test_mode_order_notice": "This order was processed in test mode. Real card authorization/withdrawal notifications may occur, but NicePayments test account payments are batch-cancelled around 23:30 on the same day. Verify live payment status before shipping.", + "test_mode_settings_warning_plugin": "NicePayments", + "test_mode_settings_warning_title": "Warning: NicePayments is 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", "test_mode_orders_in_list": "Test payment orders included" }, "easy_pay": { diff --git a/plugins/_bundled/sirsoft-pay_nicepayments/resources/lang/ko.json b/plugins/_bundled/sirsoft-pay_nicepayments/resources/lang/ko.json index f82123de..cb748fb1 100644 --- a/plugins/_bundled/sirsoft-pay_nicepayments/resources/lang/ko.json +++ b/plugins/_bundled/sirsoft-pay_nicepayments/resources/lang/ko.json @@ -4,6 +4,7 @@ "nicepay_easy_pay_config": "간편결제 설정", "nicepay_escrow": "에스크로", "nicepay_status": "결제 상태", + "nicepay_test_mode_status": "테스트 모드 상태", "nicepay_test_map": "테스트 결제 맵", "settings": "결제 설정", "vbank_info": "가상계좌 정보" @@ -109,6 +110,10 @@ "escrow_register_error": "에스크로 배송 등록에 실패했습니다.", "test_mode_badge": "테스트 결제", "test_mode_order_notice": "테스트 결제로 처리된 주문입니다. 실제 카드 승인/출금 알림이 발생할 수 있으나, 나이스페이먼츠 테스트 계정 결제는 당일 23:30경 일괄 자동 취소됩니다. 배송 전 운영 결제 여부를 확인하세요.", + "test_mode_settings_warning_plugin": "나이스페이먼츠", + "test_mode_settings_warning_title": "(주의!) 나이스페이먼츠 결제 설정이 현재 테스트결제로 되어 있습니다.", + "test_mode_settings_warning_body": "테스트결제 시 실제 결제가 되지 않으므로, 쇼핑몰 운영 중이면 반드시 실결제로 설정하여 운영하세요.", + "test_mode_settings_warning_action": "실결제로 설정", "test_mode_orders_in_list": "테스트 결제 주문 포함" }, "easy_pay": { diff --git a/plugins/_bundled/sirsoft-pay_nicepayments/src/Controllers/AdminSettingsStatusController.php b/plugins/_bundled/sirsoft-pay_nicepayments/src/Controllers/AdminSettingsStatusController.php new file mode 100644 index 00000000..2e854622 --- /dev/null +++ b/plugins/_bundled/sirsoft-pay_nicepayments/src/Controllers/AdminSettingsStatusController.php @@ -0,0 +1,28 @@ + (bool) $this->settingsService->get(self::PLUGIN_IDENTIFIER, 'is_test_mode', true), + ]); + } +} diff --git a/plugins/_bundled/sirsoft-pay_nicepayments/src/Listeners/AdjustEcommercePaymentMethodsLayoutListener.php b/plugins/_bundled/sirsoft-pay_nicepayments/src/Listeners/AdjustEcommercePaymentMethodsLayoutListener.php index 603cbf83..b1aabd02 100644 --- a/plugins/_bundled/sirsoft-pay_nicepayments/src/Listeners/AdjustEcommercePaymentMethodsLayoutListener.php +++ b/plugins/_bundled/sirsoft-pay_nicepayments/src/Listeners/AdjustEcommercePaymentMethodsLayoutListener.php @@ -13,6 +13,14 @@ class AdjustEcommercePaymentMethodsLayoutListener implements HookListenerInterfa { private const TARGET_LAYOUT = 'admin_ecommerce_settings'; + private const TEST_MODE_DATA_SOURCE_ID = 'nicepay_test_mode_status'; + + private const TEST_MODE_NOTICE_ID = 'payment_test_mode_order_settings_notice'; + + private const TEST_MODE_NOTICE_ROW_ID = 'nicepay_test_mode_order_settings_notice'; + + private const TEST_MODE_CONDITION = 'nicepay_test_mode_status.data?.is_test_mode === true'; + private const CORE_NO_PG_METHODS = ['point', 'deposit', 'free', 'dbank']; private const NICEPAY_NO_PG_METHODS = [ @@ -47,6 +55,7 @@ class AdjustEcommercePaymentMethodsLayoutListener implements HookListenerInterfa /** * @param array $layout + * @param int $templateId * @return array */ public function markEasyPayMethodsAsPgNotRequired(array $layout, int $templateId): array @@ -55,7 +64,9 @@ class AdjustEcommercePaymentMethodsLayoutListener implements HookListenerInterfa return $layout; } - return $this->replaceNoPgMethodExpressions($layout); + $layout = $this->replaceNoPgMethodExpressions($layout); + + return $this->ensureTestModeWarning($layout); } /** @@ -103,4 +114,222 @@ class AdjustEcommercePaymentMethodsLayoutListener implements HookListenerInterfa $expression ) ?? $expression; } + + /** + * @param array $layout + * @return array + */ + private function ensureTestModeWarning(array $layout): array + { + $layout = $this->ensureTestModeDataSource($layout); + + return $this->insertTestModeNotice($layout); + } + + /** + * @param array $layout + * @return array + */ + 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_nicepayments.editor.data_source.nicepay_test_mode_status', + 'type' => 'api', + 'endpoint' => '/api/plugins/sirsoft-pay_nicepayments/admin/settings/test-mode-status', + 'method' => 'GET', + 'auto_fetch' => true, + 'auth_required' => true, + ]; + + $layout['data_sources'] = $dataSources; + + return $layout; + } + + /** + * @param array $node + * @return array + */ + 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 $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 $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 $notice + * @return array + */ + 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 + */ + 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 + */ + 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_nicepayments.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_nicepayments.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_nicepayments/settings', + ], + ], + ], + 'children' => [ + [ + 'type' => 'basic', + 'name' => 'Span', + 'text' => '$t:sirsoft-pay_nicepayments.admin.test_mode_settings_warning_action', + ], + ], + ], + ], + ]; + } } diff --git a/plugins/_bundled/sirsoft-pay_nicepayments/src/routes/api.php b/plugins/_bundled/sirsoft-pay_nicepayments/src/routes/api.php index 8b5ab02e..43e6cf5b 100644 --- a/plugins/_bundled/sirsoft-pay_nicepayments/src/routes/api.php +++ b/plugins/_bundled/sirsoft-pay_nicepayments/src/routes/api.php @@ -3,6 +3,7 @@ use Illuminate\Support\Facades\Route; use Plugins\Sirsoft\PayNicepayments\Controllers\AdminEscrowController; use Plugins\Sirsoft\PayNicepayments\Controllers\AdminOrderListController; +use Plugins\Sirsoft\PayNicepayments\Controllers\AdminSettingsStatusController; use Plugins\Sirsoft\PayNicepayments\Controllers\AdminTransactionController; use Plugins\Sirsoft\PayNicepayments\Controllers\AdminVbankNotificationController; use Plugins\Sirsoft\PayNicepayments\Controllers\AdminVbankRefundController; @@ -39,6 +40,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'); + // TID 단건 거래 조회 Route::post('/transaction/query', [AdminTransactionController::class, 'query']) ->middleware('permission:admin,sirsoft-ecommerce.orders.read') diff --git a/plugins/_bundled/sirsoft-pay_nicepayments/tests/Feature/Controllers/AdminSettingsStatusControllerTest.php b/plugins/_bundled/sirsoft-pay_nicepayments/tests/Feature/Controllers/AdminSettingsStatusControllerTest.php new file mode 100644 index 00000000..f4cac8bd --- /dev/null +++ b/plugins/_bundled/sirsoft-pay_nicepayments/tests/Feature/Controllers/AdminSettingsStatusControllerTest.php @@ -0,0 +1,33 @@ +createMock(PluginSettingsService::class); + $settings->expects($this->once()) + ->method('get') + ->with('sirsoft-pay_nicepayments', '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_nicepayments/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); + } +} diff --git a/plugins/_bundled/sirsoft-pay_nicepayments/tests/Unit/Listeners/AdjustEcommercePaymentMethodsLayoutListenerTest.php b/plugins/_bundled/sirsoft-pay_nicepayments/tests/Unit/Listeners/AdjustEcommercePaymentMethodsLayoutListenerTest.php index 22e2b2a7..addb16c2 100644 --- a/plugins/_bundled/sirsoft-pay_nicepayments/tests/Unit/Listeners/AdjustEcommercePaymentMethodsLayoutListenerTest.php +++ b/plugins/_bundled/sirsoft-pay_nicepayments/tests/Unit/Listeners/AdjustEcommercePaymentMethodsLayoutListenerTest.php @@ -62,6 +62,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('nicepay_test_mode_status', $json); + $this->assertStringContainsString('/api/plugins/sirsoft-pay_nicepayments/admin/settings/test-mode-status', $json); + $this->assertStringContainsString('payment_test_mode_order_settings_notice', $json); + $this->assertStringContainsString('nicepay_test_mode_order_settings_notice', $json); + $this->assertStringNotContainsString('sirsoft-pay_nicepayments.admin.test_mode_settings_summary_title', $json); + $this->assertStringNotContainsString('sirsoft-pay_nicepayments.admin.test_mode_settings_summary_body', $json); + $this->assertStringContainsString('sirsoft-pay_nicepayments.admin.test_mode_settings_warning_plugin', $json); + $this->assertStringContainsString('sirsoft-pay_nicepayments.admin.test_mode_settings_warning_body', $json); + $this->assertStringContainsString('/admin/plugins/sirsoft-pay_nicepayments/settings', $json); + $this->assertSame(1, substr_count($json, 'payment_test_mode_order_settings_notice')); + $this->assertSame(1, substr_count($json, 'nicepay_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();