diff --git a/.env.example b/.env.example
index 74fb9072..27d61180 100644
--- a/.env.example
+++ b/.env.example
@@ -3,7 +3,7 @@ APP_ENV=production
APP_KEY=
APP_DEBUG=false
APP_URL=http://localhost
-APP_VERSION=7.0.4
+APP_VERSION=7.0.5
APP_LOCALE=ko
APP_FALLBACK_LOCALE=ko
diff --git a/.env.testing.example b/.env.testing.example
index 94e4f911..bec41e3b 100644
--- a/.env.testing.example
+++ b/.env.testing.example
@@ -3,7 +3,7 @@ APP_ENV=testing
APP_KEY=
APP_DEBUG=false
APP_URL=http://localhost
-APP_VERSION=7.0.4
+APP_VERSION=7.0.5
APP_LOCALE=ko
APP_FALLBACK_LOCALE=ko
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 20eca2ea..79a05713 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,12 @@
형식은 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/)를 따르며,
[Semantic Versioning](https://semver.org/lang/ko/)을 준수합니다.
+## [7.0.5] - 2026-07-15
+
+### Added
+
+- 확장 기능이 화면을 그릴 때, 방문자의 접속 기기 종류처럼 요청마다 달라지는 정보를 화면 구성 데이터에 함께 실어 보낼 수 있는 통로를 마련했습니다. 쇼핑몰의 애플페이가 iOS 기기에서만 표시되도록 하는 데 이 통로가 쓰입니다.
+
## [7.0.4] - 2026-07-14
### Security
diff --git a/INSTALL.md b/INSTALL.md
index 573b2221..6c55f3cf 100644
--- a/INSTALL.md
+++ b/INSTALL.md
@@ -246,7 +246,7 @@ unzip g7-release.zip
# 압축 해제 결과 확인 — 루트 디렉토리가 g7이 아니면 이름 변경
ls -la
-# (필요 시) mv g7-7.0.4 g7
+# (필요 시) mv g7-7.0.5 g7
# ZIP 파일 정리 (선택)
rm g7-release.zip
diff --git a/README.md b/README.md
index 906de834..3e74f2b8 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@
-
+
diff --git a/app/Services/SettingsService.php b/app/Services/SettingsService.php
index 475a8f0d..8f57e0ff 100644
--- a/app/Services/SettingsService.php
+++ b/app/Services/SettingsService.php
@@ -198,7 +198,10 @@ class SettingsService
$result[$frontendKey] = $this->castValue($value, $fieldSchema);
}
- return $result;
+ // 확장이 요청 컨텍스트 기반 값(예: 기기 유형)을 appConfig 에 주입할 수 있도록 필터 훅 제공.
+ // config/frontend.php 는 정적 config 값만 담으므로, 요청별로 달라지는 값은 이 훅으로 확장이 채운다.
+ // 프론트는 window.G7Config.appConfig → _global.appConfig 로 그대로 받는다.
+ return HookManager::applyFilters('core.frontend.filter_app_config', $result);
}
/**
diff --git a/config/app.php b/config/app.php
index df842759..986558af 100644
--- a/config/app.php
+++ b/config/app.php
@@ -231,7 +231,7 @@ return [
|
*/
- 'version' => env('APP_VERSION', '7.0.4'),
+ 'version' => env('APP_VERSION', '7.0.5'),
/*
|--------------------------------------------------------------------------
diff --git a/modules/_bundled/sirsoft-ecommerce/CHANGELOG.md b/modules/_bundled/sirsoft-ecommerce/CHANGELOG.md
index 0580e9bd..02eadcd3 100644
--- a/modules/_bundled/sirsoft-ecommerce/CHANGELOG.md
+++ b/modules/_bundled/sirsoft-ecommerce/CHANGELOG.md
@@ -17,6 +17,9 @@
- 결제 플러그인이 제공하는 간편결제도 신용카드·가상계좌와 동일하게 정식 결제수단으로 취급됩니다. 주문 내역에 실제로 선택한 결제수단(예: 네이버페이)이 그대로 기록되어 관리자 주문 상세와 주문 목록에서 확인할 수 있습니다.
- 쇼핑몰 환경설정 > 주문설정의 결제수단 목록에서 간편결제가 "PG 불필요"가 아니라 "PG 고정"으로 표시되고 어느 결제대행사를 통해 처리되는지 함께 보여줍니다. 간편결제는 해당 결제대행사 전용이므로 다른 대행사로 바꿀 수 없습니다.
+- 주문서의 간편결제 버튼에 표시되는 브랜드 마크(네이버페이·카카오페이·삼성페이 등의 색상 배지와 로고)를 표준화했습니다. 이전에는 결제 플러그인이 화면이 그려진 뒤 버튼 모양을 덧입히는 방식이라 여러 결제사가 함께 켜져 있을 때 드물게 표시가 겹치거나 어긋날 수 있었는데, 이제 결제수단 정보에 담긴 브랜드 마크를 주문서가 처음부터 직접 그리도록 바꿔 표시가 안정적입니다.
+- 애플페이 간편결제 버튼이 iOS 기기(iPhone·iPad)에서만 주문서에 표시되도록 했습니다. 애플페이는 iOS에서만 결제할 수 있으므로 다른 기기에서는 버튼을 감춰 혼선을 줄입니다.
+- 위 표시 개선을 위해 코어 최소 요구 버전을 7.0.5로 상향했습니다. 쇼핑몰 모듈을 업데이트하기 전에 코어를 7.0.5 이상으로 먼저 업데이트해 주세요.
## [1.0.3] - 2026-07-14
diff --git a/modules/_bundled/sirsoft-ecommerce/module.json b/modules/_bundled/sirsoft-ecommerce/module.json
index 8229085f..59be2a50 100644
--- a/modules/_bundled/sirsoft-ecommerce/module.json
+++ b/modules/_bundled/sirsoft-ecommerce/module.json
@@ -11,7 +11,7 @@
"ko": "그누보드7 이커머스 모듈 - 상품, 주문, 결제 관리",
"en": "Gnuboard7 Ecommerce Module - Product, Order, Payment Management"
},
- "g7_version": ">=7.0.0",
+ "g7_version": ">=7.0.5",
"dependencies": {
"modules": {},
"plugins": {}
diff --git a/modules/_bundled/sirsoft-ecommerce/module.php b/modules/_bundled/sirsoft-ecommerce/module.php
index 89b77bd3..0d38a80f 100644
--- a/modules/_bundled/sirsoft-ecommerce/module.php
+++ b/modules/_bundled/sirsoft-ecommerce/module.php
@@ -19,6 +19,7 @@ use Modules\Sirsoft\Ecommerce\Listeners\CouponUseListener;
use Modules\Sirsoft\Ecommerce\Listeners\EcommerceAdminActivityLogListener;
use Modules\Sirsoft\Ecommerce\Listeners\EcommerceNotificationDataListener;
use Modules\Sirsoft\Ecommerce\Listeners\EcommerceUserActivityLogListener;
+use Modules\Sirsoft\Ecommerce\Listeners\InjectAppConfigDeviceListener;
use Modules\Sirsoft\Ecommerce\Listeners\MergeCartOnLoginListener;
use Modules\Sirsoft\Ecommerce\Listeners\MileageTransactionListener;
use Modules\Sirsoft\Ecommerce\Listeners\OrderActivityLogListener;
@@ -2007,6 +2008,7 @@ class Module extends AbstractModule
UserMileageCleanupListener::class,
EcommerceNotificationDataListener::class,
OrderStatusNotificationListener::class,
+ InjectAppConfigDeviceListener::class,
];
}
diff --git a/modules/_bundled/sirsoft-ecommerce/src/Helpers/DeviceDetector.php b/modules/_bundled/sirsoft-ecommerce/src/Helpers/DeviceDetector.php
index d3c63815..0f5ef01d 100644
--- a/modules/_bundled/sirsoft-ecommerce/src/Helpers/DeviceDetector.php
+++ b/modules/_bundled/sirsoft-ecommerce/src/Helpers/DeviceDetector.php
@@ -24,6 +24,13 @@ class DeviceDetector
*/
private const MOBILE_UA_KEYWORDS = ['Mobile', 'Android', 'iPhone', 'iPad', 'iPod'];
+ /**
+ * iOS User-Agent 키워드 (iPhone/iPad/iPod)
+ *
+ * @var array
+ */
+ private const IOS_UA_KEYWORDS = ['iPhone', 'iPad', 'iPod'];
+
/**
* Request 의 User-Agent 로 결제 디바이스 판별
*
@@ -51,4 +58,25 @@ class DeviceDetector
return 'pc';
}
+
+ /**
+ * User-Agent 문자열이 iOS 기기(iPhone/iPad/iPod)인지 판별
+ *
+ * 애플페이는 iOS 기기에서만 결제 가능하므로, 체크아웃 결제수단 목록에서
+ * 애플페이 노출 여부를 정하는 데 쓰인다. 데스크탑 UA 를 보내는 iPadOS 는
+ * 이 판정에서 false 가 될 수 있으며, 그 경우 클라이언트(maxTouchPoints)가 보정한다.
+ *
+ * @param string $userAgent User-Agent 헤더 값
+ * @return bool iOS 기기 여부
+ */
+ public static function isIosFromUserAgent(string $userAgent): bool
+ {
+ foreach (self::IOS_UA_KEYWORDS as $keyword) {
+ if (stripos($userAgent, $keyword) !== false) {
+ return true;
+ }
+ }
+
+ return false;
+ }
}
diff --git a/modules/_bundled/sirsoft-ecommerce/src/Http/Middleware/DetectDevice.php b/modules/_bundled/sirsoft-ecommerce/src/Http/Middleware/DetectDevice.php
new file mode 100644
index 00000000..31bbe800
--- /dev/null
+++ b/modules/_bundled/sirsoft-ecommerce/src/Http/Middleware/DetectDevice.php
@@ -0,0 +1,61 @@
+userAgent() ?? '')
+ );
+
+ return $next($request);
+ }
+
+ /**
+ * 현재 요청이 iOS 기기인지 반환합니다.
+ *
+ * 미들웨어가 실행되지 않은 컨텍스트에서는 false(애플페이 기본 숨김 — fail-safe)를 반환합니다.
+ *
+ * @return bool iOS 기기 여부
+ */
+ public static function isIos(): bool
+ {
+ if (App::bound(self::IS_IOS_KEY)) {
+ return (bool) App::make(self::IS_IOS_KEY);
+ }
+
+ return false;
+ }
+}
diff --git a/modules/_bundled/sirsoft-ecommerce/src/Listeners/InjectAppConfigDeviceListener.php b/modules/_bundled/sirsoft-ecommerce/src/Listeners/InjectAppConfigDeviceListener.php
new file mode 100644
index 00000000..f897ae72
--- /dev/null
+++ b/modules/_bundled/sirsoft-ecommerce/src/Listeners/InjectAppConfigDeviceListener.php
@@ -0,0 +1,58 @@
+>
+ */
+ public static function getSubscribedHooks(): array
+ {
+ return [
+ 'core.frontend.filter_app_config' => [
+ 'method' => 'injectDeviceFlags',
+ 'priority' => 20,
+ 'type' => 'filter',
+ ],
+ ];
+ }
+
+ /**
+ * 기본 핸들러 (미사용).
+ *
+ * @param mixed ...$args
+ */
+ public function handle(...$args): void {}
+
+ /**
+ * appConfig 에 요청 기기 iOS 여부를 주입합니다.
+ *
+ * @param array $appConfig 코어가 조립한 프론트엔드 appConfig
+ * @return array isIos 가 추가된 appConfig
+ */
+ public function injectDeviceFlags(array $appConfig): array
+ {
+ $appConfig['isIos'] = DetectDevice::isIos();
+
+ return $appConfig;
+ }
+}
diff --git a/modules/_bundled/sirsoft-ecommerce/src/Providers/EcommerceServiceProvider.php b/modules/_bundled/sirsoft-ecommerce/src/Providers/EcommerceServiceProvider.php
index 005095db..5742e1d0 100644
--- a/modules/_bundled/sirsoft-ecommerce/src/Providers/EcommerceServiceProvider.php
+++ b/modules/_bundled/sirsoft-ecommerce/src/Providers/EcommerceServiceProvider.php
@@ -4,6 +4,8 @@ namespace Modules\Sirsoft\Ecommerce\Providers;
use App\Extension\BaseModuleServiceProvider;
use App\Seo\SitemapGenerator;
+use Illuminate\Contracts\Http\Kernel as HttpKernelContract;
+use Illuminate\Foundation\Http\Kernel as HttpKernel;
use Modules\Sirsoft\Ecommerce\Console\Commands\AggregateEcommerceStatsCommand;
use Modules\Sirsoft\Ecommerce\Console\Commands\CancelPendingPaymentOrdersCommand;
use Modules\Sirsoft\Ecommerce\Console\Commands\EarnMileageCommand;
@@ -11,6 +13,7 @@ use Modules\Sirsoft\Ecommerce\Console\Commands\ExpireMileageCommand;
use Modules\Sirsoft\Ecommerce\Console\Commands\NotifyExpiringMileageCommand;
use Modules\Sirsoft\Ecommerce\Console\Commands\PruneExpiredCartsCommand;
use Modules\Sirsoft\Ecommerce\Console\Commands\ReconcileMileageBalanceCommand;
+use Modules\Sirsoft\Ecommerce\Http\Middleware\DetectDevice;
use Modules\Sirsoft\Ecommerce\Repositories\BrandRepository;
use Modules\Sirsoft\Ecommerce\Repositories\CartRepository;
use Modules\Sirsoft\Ecommerce\Repositories\CategoryImageRepository;
@@ -210,6 +213,20 @@ class EcommerceServiceProvider extends BaseModuleServiceProvider
$this->commands($this->commands);
}
+ // 요청 기기 유형(iOS) 감지 미들웨어를 web 그룹(SSR 셸 렌더 경로)에 등록.
+ // InjectAppConfigDeviceListener 가 core.frontend.filter_app_config 훅에서 이 값을 읽어
+ // appConfig.isIos 로 주입한다(체크아웃 애플페이 iOS 게이팅). DB/인증 비의존 순수 UA 판정이라
+ // 마이그레이션 이전에도 안전하다.
+ //
+ // Router::pushMiddlewareToGroup 은 Router 자체 레지스트리만 갱신해 실제 요청 파이프라인이
+ // 참조하는 HTTP Kernel 의 web 그룹에는 반영되지 않는다(미들웨어 미실행 → isIos 항상 false).
+ // sirsoft-gdpr 선례와 동일하게 HTTP Kernel::appendMiddlewareToGroup 을 사용한다
+ // (Kernel 은 중복 등록을 자체 방지하므로 매 부팅 호출돼도 1회만 등록).
+ $kernel = $this->app->make(HttpKernelContract::class);
+ if ($kernel instanceof HttpKernel) {
+ $kernel->appendMiddlewareToGroup('web', DetectDevice::class);
+ }
+
// Sitemap 기여자 등록
$this->app->booted(function () {
if ($this->app->bound(SitemapGenerator::class)) {
diff --git a/modules/_bundled/sirsoft-ecommerce/src/Services/EcommerceSettingsService.php b/modules/_bundled/sirsoft-ecommerce/src/Services/EcommerceSettingsService.php
index 656526ce..12680ee1 100644
--- a/modules/_bundled/sirsoft-ecommerce/src/Services/EcommerceSettingsService.php
+++ b/modules/_bundled/sirsoft-ecommerce/src/Services/EcommerceSettingsService.php
@@ -796,6 +796,12 @@ class EcommerceSettingsService implements ModuleSettingsInterface
'refund_method' => $refundMethod,
];
+ // iOS 전용 노출 플래그(애플페이 등) — 정의가 SSoT. 관리자가 편집하는 값이 아니며,
+ // 체크아웃 레이아웃이 비-iOS 기기에서 해당 수단을 렌더하지 않는 데 쓰인다.
+ if (! empty($definition['requires_ios'])) {
+ $capabilities['requires_ios'] = true;
+ }
+
if ($savedItem) {
$merged[] = array_merge([
'id' => $id,
@@ -810,6 +816,7 @@ class EcommerceSettingsService implements ModuleSettingsInterface
'_cached_name' => $definition['name'],
'_cached_description' => $definition['description'] ?? ['ko' => '', 'en' => ''],
'_cached_icon' => $definition['icon'] ?? 'circle-question',
+ '_cached_brand_mark' => $definition['brand_mark'] ?? null,
'_cached_source' => $definition['source'] ?? 'builtin',
], $capabilities);
} else {
@@ -825,6 +832,7 @@ class EcommerceSettingsService implements ModuleSettingsInterface
'_cached_name' => $definition['name'],
'_cached_description' => $definition['description'] ?? ['ko' => '', 'en' => ''],
'_cached_icon' => $definition['icon'] ?? 'circle-question',
+ '_cached_brand_mark' => $definition['brand_mark'] ?? null,
'_cached_source' => $definition['source'] ?? 'builtin',
], $capabilities);
}
@@ -874,6 +882,7 @@ class EcommerceSettingsService implements ModuleSettingsInterface
$savedMethods[$index]['_cached_name'] = $def['name'] ?? $method['_cached_name'] ?? null;
$savedMethods[$index]['_cached_description'] = $def['description'] ?? $method['_cached_description'] ?? null;
$savedMethods[$index]['_cached_icon'] = $def['icon'] ?? $method['_cached_icon'] ?? null;
+ $savedMethods[$index]['_cached_brand_mark'] = $def['brand_mark'] ?? $method['_cached_brand_mark'] ?? null;
$savedMethods[$index]['_cached_source'] = $def['source'] ?? $method['_cached_source'] ?? 'builtin';
}
// 고아 항목은 기존 _cached_* 유지
diff --git a/modules/_bundled/sirsoft-ecommerce/tests/Playwright/specs/shop/checkout-brand-mark.spec.ts b/modules/_bundled/sirsoft-ecommerce/tests/Playwright/specs/shop/checkout-brand-mark.spec.ts
new file mode 100644
index 00000000..41600b8a
--- /dev/null
+++ b/modules/_bundled/sirsoft-ecommerce/tests/Playwright/specs/shop/checkout-brand-mark.spec.ts
@@ -0,0 +1,76 @@
+/**
+ * 체크아웃 간편결제 브랜드 마크 데이터 기반 렌더 + 애플페이 iOS 게이팅.
+ *
+ * 3사(kginicis/nhnkcp/nicepayments) 동시 활성 체크아웃에서, 각 간편결제 버튼의 브랜드 마크
+ * (색 배지 / 브랜드 SVG 로고)가 카탈로그 _cached_brand_mark 데이터로 렌더되고(injector 후처리
+ * 없이), 애플페이는 iOS 기기에서만 노출되는지 검증. 템플릿 sirsoft-basic (유저 화면).
+ * (skeleton, placeholder)
+ *
+ * @effects badge_renders_text_and_color,
+ * svg_renders_inline_logo,
+ * svg_dangerous_content_sanitized,
+ * none_form_falls_back_to_icon,
+ * applepay_shown_on_ios,
+ * applepay_hidden_on_non_ios,
+ * non_ios_methods_unaffected,
+ * injectors_removed
+ *
+ * e2e:allow 브랜드 마크 렌더/게이팅 — 카탈로그→레이아웃 계약을 단위/렌더/리소스 테스트로 1차 차단하고,
+ * 브라우저 회귀는 본 placeholder(test.describe.skip)가 data-testid 보강 + 3-PG 동시활성 시드 후 활성화될 때 검증한다.
+ * 현재 커버리지:
+ * (1) 카탈로그 보강 — modules/_bundled/sirsoft-ecommerce/tests/Unit/Services/EcommerceSettingsOrderSettingsTest.php
+ * (brand_mark→_cached_brand_mark 병합, requires_ios 전달, 마크 없는 builtin=null) green.
+ * (2) 등록 리스너 — plugins/_bundled/sirsoft-pay_{nhnkcp,nicepayments,kginicis}/tests/Unit/Listeners/RegisterEasyPayMethodsListenerTest.php
+ * (배지 9종 text/class, SVG 6종 markup, 애플페이만 requires_ios) green.
+ * (3) BrandMark 컴포넌트 — templates/_bundled/sirsoft-basic/src/components/composite/__tests__/BrandMark.test.tsx
+ * (배지/SVG 렌더, SVG script/onload/foreignObject sanitize 제거, 마크 없음→null) green.
+ * (4) 레이아웃 렌더 + iOS 필터 — templates/_bundled/sirsoft-basic/__tests__/layouts/checkout-payment-brand-mark.test.tsx
+ * (BrandMark 노드 바인딩 + Icon 폴백, iteration source 필터가 iOS↔비iOS 로 애플페이 포함/제외) green.
+ * (5) 서버/클라 iOS 게이팅 — tests/Unit/Helpers/DeviceDetectorTest.php + tests/Unit/Middleware/DetectDeviceTest.php
+ * + tests/Unit/Services/SettingsServiceAppConfigTest.php + templates/_bundled/sirsoft-basic/src/handlers/__tests__/deviceCorrection.test.ts
+ * (UA→appConfig.isIos, iPadOS 데스크탑 UA 클라 보정) green.
+ * 라이브 검수는 Chrome MCP 실측(3사 브랜드 버튼 공존 + 마크 표시, iOS 에뮬레이션 애플페이 노출/숨김, 스크린샷)으로 기록 예정.
+ */
+// @scenario mark_form=svg, requires_ios=false, device=ios
+// @scenario mark_form=svg, requires_ios=false, device=non_ios
+// @scenario mark_form=none, requires_ios=false, device=non_ios
+// @scenario mark_form=badge, requires_ios=false, device=non_ios
+import { test, expect, authenticatePage } from '../../fixtures/ecommerce-auth';
+
+test.describe.skip('체크아웃 간편결제 브랜드 마크 + 애플페이 iOS 게이팅 (skeleton)', () => {
+ test('3사 간편결제 버튼이 브랜드 마크(배지/SVG)로 렌더된다', async ({ page, userToken }) => {
+ await authenticatePage(page, userToken);
+ await page.goto('/shop/checkout');
+ await page.waitForLoadState('domcontentloaded', { timeout: 30_000 });
+
+ // 활성화 시: 배지 형태(nhnkcp 네이버페이 'N')와 SVG 형태(kginicis 네이버페이 로고)가 함께 렌더
+ await expect(page.getByTestId('payment-method-nhnkcp_naverpay').getByText('N')).toBeVisible();
+ await expect(page.getByTestId('payment-method-kginicis_naverpay').locator('svg')).toBeVisible();
+ // 마크 없는 무통장은 아이콘 폴백
+ await expect(page.getByTestId('payment-method-dbank').locator('i')).toBeVisible();
+ });
+
+ test('비-iOS 기기에서 애플페이 버튼이 노출되지 않는다', async ({ page, userToken }) => {
+ await authenticatePage(page, userToken);
+ await page.goto('/shop/checkout');
+ await page.waitForLoadState('domcontentloaded', { timeout: 30_000 });
+
+ // 활성화 시: 데스크탑(비-iOS)에서는 애플페이 수단 카드가 렌더되지 않음
+ await expect(page.getByTestId('payment-method-nhnkcp_applepay')).toHaveCount(0);
+ // 비-애플페이 수단은 정상 노출
+ await expect(page.getByTestId('payment-method-nhnkcp_naverpay')).toBeVisible();
+ });
+
+ test('iOS 기기 에뮬레이션에서 애플페이 버튼이 노출된다', async ({ browser }) => {
+ // 활성화 시: iPhone UA 컨텍스트에서 애플페이 수단 카드가 렌더됨(서버 UA 게이팅)
+ const context = await browser.newContext({
+ userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15',
+ });
+ const page = await context.newPage();
+ await page.goto('/shop/checkout');
+ await page.waitForLoadState('domcontentloaded', { timeout: 30_000 });
+
+ await expect(page.getByTestId('payment-method-nhnkcp_applepay')).toBeVisible();
+ await context.close();
+ });
+});
diff --git a/modules/_bundled/sirsoft-ecommerce/tests/Unit/Helpers/DeviceDetectorTest.php b/modules/_bundled/sirsoft-ecommerce/tests/Unit/Helpers/DeviceDetectorTest.php
new file mode 100644
index 00000000..fc9a6562
--- /dev/null
+++ b/modules/_bundled/sirsoft-ecommerce/tests/Unit/Helpers/DeviceDetectorTest.php
@@ -0,0 +1,62 @@
+assertSame($expected, DeviceDetector::detectFromUserAgent($userAgent));
+ }
+
+ /**
+ * @return array
+ */
+ public static function deviceTypeProvider(): array
+ {
+ return [
+ 'iPhone' => ['Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X)', 'mobile'],
+ 'iPad' => ['Mozilla/5.0 (iPad; CPU OS 17_0 like Mac OS X)', 'mobile'],
+ 'Android' => ['Mozilla/5.0 (Linux; Android 14; Pixel 8)', 'mobile'],
+ 'desktop Chrome' => ['Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120', 'pc'],
+ 'macOS Safari' => ['Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) Safari/17', 'pc'],
+ 'empty' => ['', 'pc'],
+ ];
+ }
+
+ /**
+ * @scenario mark_form=badge, requires_ios=true, device=ios
+ *
+ * @effects server_ua_sets_is_ios, applepay_shown_on_ios
+ */
+ #[DataProvider('iosProvider')]
+ public function test_is_ios_from_user_agent(string $userAgent, bool $expected): void
+ {
+ $this->assertSame($expected, DeviceDetector::isIosFromUserAgent($userAgent));
+ }
+
+ /**
+ * @return array
+ */
+ public static function iosProvider(): array
+ {
+ return [
+ 'iPhone' => ['Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X)', true],
+ 'iPad' => ['Mozilla/5.0 (iPad; CPU OS 17_0 like Mac OS X)', true],
+ 'iPod' => ['Mozilla/5.0 (iPod touch; CPU iPhone OS 15_0 like Mac OS X)', true],
+ // Android 모바일은 iOS 가 아님(애플페이 미노출).
+ 'Android' => ['Mozilla/5.0 (Linux; Android 14; Pixel 8)', false],
+ // 데스크탑 UA 를 보내는 iPadOS 는 서버 UA 판정으로 놓친다(클라 보정 대상).
+ 'macOS desktop UA' => ['Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) Safari/17', false],
+ 'Windows' => ['Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120', false],
+ 'empty' => ['', false],
+ ];
+ }
+}
diff --git a/modules/_bundled/sirsoft-ecommerce/tests/Unit/Middleware/DetectDeviceTest.php b/modules/_bundled/sirsoft-ecommerce/tests/Unit/Middleware/DetectDeviceTest.php
new file mode 100644
index 00000000..f8c608c9
--- /dev/null
+++ b/modules/_bundled/sirsoft-ecommerce/tests/Unit/Middleware/DetectDeviceTest.php
@@ -0,0 +1,80 @@
+ 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X)',
+ ]);
+
+ (new DetectDevice)->handle($request, fn () => new Response);
+
+ $this->assertTrue(DetectDevice::isIos());
+ }
+
+ /**
+ * @scenario mark_form=badge, requires_ios=true, device=non_ios
+ *
+ * @effects server_ua_sets_is_ios, applepay_hidden_on_non_ios
+ */
+ public function test_desktop_user_agent_sets_is_ios_false_in_container(): void
+ {
+ $request = Request::create('/', 'GET', server: [
+ 'HTTP_USER_AGENT' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120',
+ ]);
+
+ (new DetectDevice)->handle($request, fn () => new Response);
+
+ $this->assertFalse(DetectDevice::isIos());
+ }
+
+ public function test_is_ios_defaults_false_when_middleware_not_run(): void
+ {
+ // 컨테이너 미바인딩(미들웨어 미실행) 시 fail-safe(애플페이 기본 숨김).
+ $this->assertFalse(DetectDevice::isIos());
+ }
+
+ /**
+ * @scenario mark_form=badge, requires_ios=true, device=ipados_desktop_ua
+ *
+ * @effects is_ios_flows_to_global_appconfig
+ */
+ public function test_listener_injects_is_ios_into_app_config_via_hook(): void
+ {
+ // iOS 미들웨어 통과 상태 재현.
+ $request = Request::create('/', 'GET', server: [
+ 'HTTP_USER_AGENT' => 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X)',
+ ]);
+ (new DetectDevice)->handle($request, fn () => new Response);
+
+ // 리스너를 훅에 등록(모듈 부팅이 하는 것과 동일).
+ HookManager::addFilter(
+ 'core.frontend.filter_app_config',
+ [new InjectAppConfigDeviceListener, 'injectDeviceFlags'],
+ 20
+ );
+
+ $appConfig = app(SettingsService::class)->getAppConfigForFrontend();
+
+ $this->assertArrayHasKey('isIos', $appConfig);
+ $this->assertTrue($appConfig['isIos']);
+ }
+}
diff --git a/modules/_bundled/sirsoft-ecommerce/tests/Unit/Providers/DetectDeviceMiddlewareRegistrationTest.php b/modules/_bundled/sirsoft-ecommerce/tests/Unit/Providers/DetectDeviceMiddlewareRegistrationTest.php
new file mode 100644
index 00000000..2f301d03
--- /dev/null
+++ b/modules/_bundled/sirsoft-ecommerce/tests/Unit/Providers/DetectDeviceMiddlewareRegistrationTest.php
@@ -0,0 +1,41 @@
+app->make(HttpKernelContract::class);
+ $this->assertInstanceOf(HttpKernel::class, $kernel);
+
+ // HTTP Kernel 이 실제 요청 처리 시 참조하는 web 그룹을 조회한다.
+ $webGroup = $kernel->getMiddlewareGroups()['web'] ?? [];
+
+ $this->assertContains(
+ DetectDevice::class,
+ $webGroup,
+ 'DetectDevice 미들웨어가 HTTP Kernel 의 web 그룹에 등록되어야 SSR 셸 요청에서 실행된다.'
+ );
+ }
+}
diff --git a/modules/_bundled/sirsoft-ecommerce/tests/Unit/Services/EcommerceSettingsOrderSettingsTest.php b/modules/_bundled/sirsoft-ecommerce/tests/Unit/Services/EcommerceSettingsOrderSettingsTest.php
index f7c567c9..f25c57ed 100644
--- a/modules/_bundled/sirsoft-ecommerce/tests/Unit/Services/EcommerceSettingsOrderSettingsTest.php
+++ b/modules/_bundled/sirsoft-ecommerce/tests/Unit/Services/EcommerceSettingsOrderSettingsTest.php
@@ -257,6 +257,55 @@ class EcommerceSettingsOrderSettingsTest extends TestCase
$this->assertFalse($toss['is_active']); // 기본값: 비활성
}
+ /**
+ * @scenario mark_form=svg, requires_ios=false, device=non_ios
+ *
+ * @effects brand_mark_flows_to_cached, requires_ios_flag_carried, none_form_cached_brand_mark_null
+ */
+ public function test_brand_mark_and_requires_ios_flow_from_definition_to_merged_catalog(): void
+ {
+ // 플러그인이 brand_mark(배지/SVG) + requires_ios 를 등록하면
+ // 병합 카탈로그가 _cached_brand_mark / requires_ios 로 노출해 레이아웃까지 전달한다.
+ $this->addPaymentMethodFilter(function (array $methods) {
+ $methods[] = [
+ 'id' => 'pgtest_badge',
+ 'name' => ['ko' => '배지결제', 'en' => 'Badge Pay'],
+ 'description' => ['ko' => '배지 결제', 'en' => 'Badge Payment'],
+ 'icon' => 'wallet',
+ 'source' => 'plugin:sirsoft-pgtest',
+ 'brand_mark' => ['text' => 'B', 'class' => 'bg-green-500 text-white'],
+ 'defaults' => ['is_active' => true, 'min_order_amount' => 0, 'stock_deduction_timing' => 'payment_complete'],
+ ];
+ $methods[] = [
+ 'id' => 'pgtest_svg_ios',
+ 'name' => ['ko' => 'SVG결제', 'en' => 'SVG Pay'],
+ 'description' => ['ko' => 'SVG 결제', 'en' => 'SVG Payment'],
+ 'icon' => 'smartphone',
+ 'source' => 'plugin:sirsoft-pgtest',
+ 'brand_mark' => ['svg' => ''],
+ 'requires_ios' => true,
+ 'defaults' => ['is_active' => true, 'min_order_amount' => 0, 'stock_deduction_timing' => 'payment_complete'],
+ ];
+
+ return $methods;
+ });
+
+ $this->service->clearCache();
+ $methods = collect($this->service->getSettings('order_settings')['payment_methods'])->keyBy('id');
+
+ $badge = $methods->get('pgtest_badge');
+ $this->assertSame(['text' => 'B', 'class' => 'bg-green-500 text-white'], $badge['_cached_brand_mark']);
+ $this->assertArrayNotHasKey('requires_ios', $badge);
+
+ $svg = $methods->get('pgtest_svg_ios');
+ $this->assertSame(['svg' => ''], $svg['_cached_brand_mark']);
+ $this->assertTrue($svg['requires_ios']);
+
+ // brand_mark 없는 builtin 은 _cached_brand_mark 가 null.
+ $dbank = $methods->get('dbank');
+ $this->assertNull($dbank['_cached_brand_mark']);
+ }
+
// ──────────────────────────────────────────────
// 결제수단 병합 (사용자 저장 설정 오버라이드)
// ──────────────────────────────────────────────
diff --git a/modules/_bundled/sirsoft-ecommerce/tests/scenarios/checkout-brand-mark-rendering.yaml b/modules/_bundled/sirsoft-ecommerce/tests/scenarios/checkout-brand-mark-rendering.yaml
new file mode 100644
index 00000000..08ae9ff8
--- /dev/null
+++ b/modules/_bundled/sirsoft-ecommerce/tests/scenarios/checkout-brand-mark-rendering.yaml
@@ -0,0 +1,76 @@
+feature: checkout_brand_mark_rendering
+description: >
+ 체크아웃 간편결제 버튼의 브랜드 시각정보(색 배지 / 브랜드 SVG 로고)를 결제수단 카탈로그
+ (`_cached_brand_mark`)로 편입해, 코어 체크아웃 레이아웃의 BrandMark 컴포넌트가 데이터 기반
+ 으로 직접 렌더한다. 과거에는 각 PG 플러그인의 체크아웃 DOM 인젝터(checkoutEasyPayInjector /
+ checkoutNaverpayBrandButton)가 렌더 후 DOM 을 후처리해 마크를 주입했고, injector 끼리 같은
+ 버튼을 두고 경합하며 reconcileKginicisPatchedDuplicates 로 중복을 방어했다. 시각정보를
+ 카탈로그로 옮기면서 세 injector + reconcile 을 제거했다.
+
+ 애플페이 iOS 게이팅도 injector 밖으로 이관했다: 이커머스 모듈의 DetectDevice 미들웨어가
+ UA 로 기기를 감지해 컨테이너에 보관하고, InjectAppConfigDeviceListener 가 코어
+ `core.frontend.filter_app_config` 훅에서 이를 appConfig.isIos 로 주입해 내려준다(1차).
+ 템플릿 부트스트랩이 iPadOS(데스크탑 UA)를 클라 신호(maxTouchPoints)로 보정한다(2차).
+ 체크아웃 레이아웃 iteration source 필터가 requires_ios 수단을 _global.appConfig.isIos 에 따라
+ 포함/제외한다. (미들웨어·감지 로직은 코어가 아닌 이커머스 모듈 소유 — 코어는 appConfig 필터 훅만 제공.)
+
+test_files:
+ - modules/_bundled/sirsoft-ecommerce/tests/Unit/Services/EcommerceSettingsOrderSettingsTest.php
+ - modules/_bundled/sirsoft-ecommerce/tests/Unit/Helpers/DeviceDetectorTest.php
+ - modules/_bundled/sirsoft-ecommerce/tests/Unit/Middleware/DetectDeviceTest.php
+ - modules/_bundled/sirsoft-ecommerce/tests/Unit/Providers/DetectDeviceMiddlewareRegistrationTest.php
+ - tests/Unit/Services/SettingsServiceAppConfigTest.php
+ - plugins/_bundled/sirsoft-pay_nhnkcp/tests/Unit/Listeners/RegisterEasyPayMethodsListenerTest.php
+ - plugins/_bundled/sirsoft-pay_nicepayments/tests/Unit/Listeners/RegisterEasyPayMethodsListenerTest.php
+ - plugins/_bundled/sirsoft-pay_kginicis/tests/Unit/Listeners/RegisterEasyPayMethodsListenerTest.php
+ - templates/_bundled/sirsoft-basic/src/components/composite/__tests__/BrandMark.test.tsx
+ - templates/_bundled/sirsoft-basic/src/handlers/__tests__/deviceCorrection.test.ts
+ - templates/_bundled/sirsoft-basic/__tests__/layouts/checkout-payment-brand-mark.test.tsx
+ - modules/_bundled/sirsoft-ecommerce/tests/Playwright/specs/shop/checkout-brand-mark.spec.ts
+
+# 입력 axis — 브랜드 마크 렌더 + iOS 게이팅에 영향을 주는 변수
+axes:
+ # 브랜드 마크 형태: 배지(nhnkcp/nicepay) / SVG 로고(kginicis) / 없음(무통장·카드 등 builtin)
+ mark_form: [badge, svg, none]
+ # iOS 전용 노출 수단 여부 (애플페이만 true)
+ requires_ios: [true, false]
+ # 요청 기기 (서버 UA 판정 + 클라 보정 결과)
+ device: [ios, non_ios, ipados_desktop_ua]
+
+exclusions:
+ - mark_form: none
+ requires_ios: true
+ reason: "브랜드 마크 없는 builtin(무통장/카드/가상계좌)에는 iOS 전용 수단이 없다"
+ - mark_form: svg
+ requires_ios: true
+ reason: "SVG 마크(kginicis)에는 애플페이 수단이 없다 — kginicis 는 애플페이 미제공"
+
+# 후속 효과 체인 — 시각정보 이관 + injector 제거가 낳는 관찰 가능 효과
+effects:
+ # 카탈로그 → 레이아웃 데이터 전달
+ - brand_mark_flows_to_cached # brand_mark 등록값이 _cached_brand_mark 로 병합 카탈로그에 노출
+ - requires_ios_flag_carried # requires_ios 가 method 최상위로 노출(레이아웃 접근 가능)
+ - none_form_cached_brand_mark_null # 마크 없는 수단은 _cached_brand_mark=null (아이콘 폴백)
+ # 렌더 (BrandMark 컴포넌트)
+ - badge_renders_text_and_color # 배지 형태는 text + Tailwind 색상 클래스로 렌더
+ - svg_renders_inline_logo # SVG 형태는 인라인 SVG 로고로 렌더
+ - svg_dangerous_content_sanitized # SVG 내부 script/onload/foreignObject 는 sanitize 제거(XSS 방어)
+ - none_form_falls_back_to_icon # 마크 없는 수단은 Icon 폴백 렌더
+ # iOS 게이팅
+ - server_ua_sets_is_ios # DetectDevice 미들웨어가 UA 로 appConfig.isIos 설정
+ - is_ios_flows_to_global_appconfig # appConfig.isIos 가 _global.appConfig 로 전달
+ - client_corrects_ipados_desktop_ua # 부트스트랩이 iPadOS 데스크탑 UA 를 클라 신호로 보정
+ - applepay_shown_on_ios # iOS 기기에서 애플페이 버튼 노출
+ - applepay_hidden_on_non_ios # 비-iOS 기기에서 애플페이 버튼 미노출(iteration 필터 제외)
+ - non_ios_methods_unaffected # iOS 게이팅이 비-애플페이 수단 노출에 영향 없음
+ # injector 제거 (기술부채 상환)
+ - injectors_removed # 세 injector + reconcile + install 호출 제거
+ - shared_helpers_preserved # applePayDevice(결제 시점 방어) / JPY restrictor / admin injector 잔존
+
+sub_flows:
+ - id: three_pg_coexist
+ description: 3사(kginicis/nhnkcp/nicepayments) 동시 활성 체크아웃에서 브랜드 버튼 공존
+ effects:
+ - badge_renders_text_and_color
+ - svg_renders_inline_logo
+ - none_form_falls_back_to_icon
diff --git a/plugins/_bundled/sirsoft-pay_kginicis/CHANGELOG.md b/plugins/_bundled/sirsoft-pay_kginicis/CHANGELOG.md
index 390666c9..aa93009e 100644
--- a/plugins/_bundled/sirsoft-pay_kginicis/CHANGELOG.md
+++ b/plugins/_bundled/sirsoft-pay_kginicis/CHANGELOG.md
@@ -17,6 +17,7 @@
- 간편결제가 정식 결제수단으로 취급되어 주문 내역에 실제로 선택한 결제수단(예: 네이버페이)이 그대로 기록됩니다.
- 쇼핑몰 환경설정 > 주문설정의 결제수단 목록에서 간편결제가 "PG 고정 · KG이니시스"로 표시됩니다. 간편결제는 KG 이니시스 전용이므로 다른 결제대행사로 바꿀 수 없습니다.
- 결제창을 띄우기 위해 브라우저 요청을 가로채 결제수단을 바꿔치기하던 내부 처리를 제거했습니다. 결제 진행 방식이 표준화되어 주문서 입력값이 초기화되는 등의 부작용이 사라집니다.
+- 주문서의 간편결제 버튼에 표시되는 브랜드 로고(네이버페이·삼성페이·카카오페이·L.pay 등)를 화면이 그려진 뒤 덧입히던 방식에서 결제수단 정보로 처음부터 표시하는 방식으로 바꿨습니다. 여러 결제사가 함께 켜져 있을 때 표시가 안정적입니다.
## [1.0.0] - 2026-07-01
diff --git a/plugins/_bundled/sirsoft-pay_kginicis/components.json b/plugins/_bundled/sirsoft-pay_kginicis/components.json
index 06021ad1..758dc0b8 100644
--- a/plugins/_bundled/sirsoft-pay_kginicis/components.json
+++ b/plugins/_bundled/sirsoft-pay_kginicis/components.json
@@ -1,7 +1,7 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"identifier": "sirsoft-pay_kginicis",
- "version": "1.0.0",
+ "version": "1.0.1",
"components": {
"basic": [],
"composite": [],
diff --git a/plugins/_bundled/sirsoft-pay_kginicis/dist/js/plugin.iife.js b/plugins/_bundled/sirsoft-pay_kginicis/dist/js/plugin.iife.js
index 5126f528..158395bf 100644
--- a/plugins/_bundled/sirsoft-pay_kginicis/dist/js/plugin.iife.js
+++ b/plugins/_bundled/sirsoft-pay_kginicis/dist/js/plugin.iife.js
@@ -1,14 +1,14 @@
-(function(){"use strict";const L="kginicis_pay_form_",M="__sirsoftKginicisReloadStandardPaySdk",w="__sirsoftKginicisMobilePaymentReturnPending";function de(){if(typeof document>"u")return 0;const e=document.querySelectorAll(`form[id^="${L}"]`);return e.forEach(t=>t.remove()),e.length}function at(){typeof window>"u"||(window[M]=!0)}function st(){if(typeof window>"u")return!1;const e=window,t=e[M]===!0;return delete e[M],t}function ct(e){typeof window>"u"||typeof document>"u"||(window.INIStdPay=void 0,document.querySelectorAll(`script[src="${e}"], script[src*="/INIStdPay_third-party.js"]`).forEach(t=>t.remove()))}function lt(e=Date.now()){if(!(typeof window>"u"))try{window.sessionStorage.setItem(w,String(e))}catch{window[w]=e}}function K(){if(!(typeof window>"u")){try{window.sessionStorage.removeItem(w)}catch{}delete window[w]}}function ue(e=Date.now()){if(typeof window>"u")return!1;let t=null;try{const n=window.sessionStorage.getItem(w);t=n?Number(n):null}catch{t=null}if(!Number.isFinite(t)){const n=window[w];t=typeof n=="number"?n:Number(n)}return!Number.isFinite(t)||e-t>18e5?(K(),!1):!0}const O="sirsoft-pay_kginicis",dt=O,ut="payment-window-closed",fe="__sirsoftKginicisPaymentCloseListenerInstalled",G="__sirsoftKginicisActiveStandardPaymentCloseContext",me=20,ft=100,$={info:(...e)=>console.info(`[${O}]`,...e),warn:(...e)=>console.warn(`[${O}]`,...e)};function mt(e){if(!e||typeof e!="object")return!1;const t=e;return t.source===dt&&t.type===ut}function D(){return/\/shop\/checkout\/?$/.test(window.location.pathname)}function E(){return window}function pt(){const e=E()[G];return!e||typeof e!="object"?null:e}function yt(e){return/^https?:\/\//i.test(e)||e.startsWith("/api/")?e:e.startsWith("/plugins/")?`/api${e}`:e.startsWith("plugins/")?`/api/${e}`:e}function ht(e){e.closeReportUrl&&(E()[G]={...e,reported:!1})}function pe(){delete E()[G]}function ye(){pe()}async function gt(e="payment-window-closed"){const t=pt();if(!t||t.reported||!D())return;t.reported=!0;const n={oid:t.oid,price:t.price,buyer_email:t.buyer_email??"",buyer_phone:t.buyer_phone??"",payment_method:t.payment_method??"",reason:e};try{const i=window.G7Core?.api;typeof i?.post=="function"?await i.post(t.closeReportUrl,n):await fetch(yt(t.closeReportUrl),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n),keepalive:!0})}catch(i){$.warn("failed to report KG payment window close",{reason:e,error:i})}finally{pe()}}function he(e="payment-window-closed",t=!0){if(!D())return!1;const i=window.G7Core?.state?.setLocal;return typeof i!="function"?(t&&$.warn("G7Core.state.setLocal not available while resetting payment submit state"),!1):(de(),at(),i({isSubmittingOrder:!1}),$.info("checkout submit state reset after KG payment close",{reason:e}),!0)}function _t(e,t=!1){let n=0;const i=()=>{if(n++,!(t&&!ue())){if(he(e,n>=me)){t&&K();return}!D()||n>=me||window.setTimeout(i,ft)}};i()}function v(e){ue()&&_t(e,!0)}function wt(){if(typeof window>"u")return;const e=E();e[fe]||(window.addEventListener("message",t=>{t.origin===window.location.origin&&mt(t.data)&&(gt(t.data.reason),he(t.data.reason))}),window.addEventListener("pagehide",()=>{ye()}),window.addEventListener("beforeunload",()=>{ye()}),window.addEventListener("pageshow",t=>{v(t.persisted?"mobile-payment-bfcache-return":"mobile-payment-page-show")}),window.addEventListener("focus",()=>{v("mobile-payment-window-focus")}),document.addEventListener("visibilitychange",()=>{document.visibilityState==="visible"&&v("mobile-payment-visibility-return")}),e[fe]=!0,v("mobile-payment-listener-installed"))}function ge(){if(typeof navigator>"u")return!1;const e=navigator;if(e.userAgentData?.mobile!==void 0)return e.userAgentData.mobile;const t=(e.userAgent||"").toLowerCase(),n=((e.userAgentData?.platform??e.platform)||"").toLowerCase();if(/android|iphone|ipad|ipod|windows phone|iemobile|blackberry|opera mini|mobile safari/.test(t)||/iphone|ipad|ipod|ios/.test(n))return!0;const i=e.maxTouchPoints??0;return/macintosh|mac os x/.test(t)&&i>1}function bt(e){return new Promise((t,n)=>{if(document.querySelector(`script[src="${e}"]`)){t();return}const i=document.createElement("script");i.src=e,i.async=!0,i.onload=()=>t(),i.onerror=()=>n(new Error(`Failed to load script: ${e}`)),document.head.appendChild(i)})}function _e(e,t){if(!t.startsWith("kginicis_"))return e;const n=new URL(e,window.location.origin);return n.searchParams.set("selectedPaymentMethod",t),n.toString()}function kt(e){const t=(e??"").trim().toUpperCase();return t===""||t==="WON"?"KRW":t}function we(e,t,n="utf-8",i){const r=document.createElement("form");i&&(r.id=i),r.method="POST",r.action=e,r.acceptCharset=n,r.style.display="none";for(const[o,a]of Object.entries(t)){const s=document.createElement("input");s.type="hidden",s.name=o,s.value=a,r.appendChild(s)}document.body.appendChild(r),r.submit()}const Pt={card:"Card",vbank:"VBank",bank:"DirectBank",phone:"HPP",kginicis_samsung_pay:"onlyssp",kginicis_naverpay:"onlynaverpay",kginicis_lpay:"onlylpay",kginicis_kakaopay:"onlykakaopay"},xt={card:"CARD",vbank:"VBANK",bank:"BANK",phone:"MOBILE"},Ct={kginicis_samsung_pay:"d_samsungpay=Y",kginicis_naverpay:"d_npay=Y",kginicis_lpay:"d_lpay=Y",kginicis_kakaopay:"d_kakaopay=Y"},Et=["CARD","CVS","PAYpay"],be={card:["CARD"],kginicis_japan_paypay:["PAYpay"],kginicis_japan_cvs:["CVS"]},vt=new Set(Object.keys(be)),St=new Set(["kginicis_samsung_pay","kginicis_naverpay","kginicis_lpay","kginicis_kakaopay"]);async function It(e,t,n,i){const r=String(Math.floor(Date.now())),o=await e.api.post(t.callback_urls.mobile_signature,{oid:n.order_number,price:n.amount,timestamp:r,buyer_email:n.customer_email??"",buyer_phone:n.customer_phone??""}),{chkfake:a,mobile_payment_url:s}=o.data,c=_e(window.location.origin+t.callback_urls.mobile_callback+"?orderId="+encodeURIComponent(n.order_number),i),u=Ct[i],l=u!==void 0,d=l?s.replace(/\/smart\/[^/]+\/?$/,"/smart/wcard/"):s,f=xt[i]??"CARD",m=t.use_escrow?"below1000=Y&vbank_receipt=Y&useescrow=Y¢erCd=Y&amt_hash=Y":"below1000=Y&vbank_receipt=Y¢erCd=Y&amt_hash=Y",p=l?m.replace("&useescrow=Y","")+"&"+u:m,y={P_MID:t.mid,P_OID:n.order_number,P_AMT:String(n.amount),P_GOODS:n.order_name,P_UNAME:n.customer_name??"",P_MOBILE:n.customer_phone??"",P_EMAIL:n.customer_email??"",P_NEXT_URL:c,P_CHARSET:"utf8",P_TIMESTAMP:r,P_CHKFAKE:a,P_RESERVED:p};l?y.P_SKIP_TERMS="Y":y.P_INI_PAYMENT=f,f==="MOBILE"&&!l&&(y.P_HPP_METHOD="2"),f==="VBANK"&&!l&&(y.P_NOTI_URL=window.location.origin+t.callback_urls.mobile_vbank_notify),lt(),we(d,y,"euc-kr",L+"mobile_"+Date.now())}async function At(e,t,n,i){const r=String(Math.floor(Date.now())),o=await e.api.post(t.callback_urls.signature,{oid:n.order_number,price:n.amount,timestamp:r,buyer_email:n.customer_email??"",buyer_phone:n.customer_phone??""}),{signature:a,verification:s,mKey:c}=o.data;if(st()&&ct(t.sdk_url),window.INIStdPay||await bt(t.sdk_url),window.INIStdPay||await new Promise(p=>setTimeout(p,100)),!window.INIStdPay)throw new Error("INIStdPay SDK not available");const u=_e(window.location.origin+t.callback_urls.callback,i),l=window.location.origin+t.callback_urls.close;de();const d=L+Date.now(),f=document.createElement("form");f.id=d,f.method="POST",f.acceptCharset="euc-kr";const m={version:"1.0",mid:t.mid,oid:n.order_number,goodname:n.order_name,price:String(n.amount),currency:"WON",buyername:n.customer_name??"",buyeremail:n.customer_email??"",buyertel:n.customer_phone??"",timestamp:r,signature:a,verification:s,mKey:c,returnUrl:u,closeUrl:l,gopaymethod:Pt[i]??"Card",acceptmethod:(()=>{const p=t.use_escrow?"useescrow:":"",y=t.use_credit_point?"CREDITCARD(Y):":"",g=i==="phone"?`HPP(1):${p}${y}centerCd(Y)`:`${p}${y}centerCd(Y)`;return i==="kginicis_samsung_pay"||i==="kginicis_naverpay"||i==="kginicis_lpay"||i==="kginicis_kakaopay"?g?`${g}:cardonly`:"cardonly":g})(),payViewType:"overlay",use_chkfake:"Y",charset:"UTF-8"};for(const[p,y]of Object.entries(m)){const g=document.createElement("input");g.type="hidden",g.name=p,g.value=y,f.appendChild(g)}document.body.appendChild(f),t.callback_urls.close_report&&ht({closeReportUrl:t.callback_urls.close_report,oid:n.order_number,price:Number(n.amount),buyer_email:n.customer_email??"",buyer_phone:n.customer_phone??"",payment_method:i}),window.INIStdPay.pay(d)}function Tt(e=new Date){const t=n=>String(n).padStart(2,"0");return e.getFullYear().toString()+t(e.getMonth()+1)+t(e.getDate())+t(e.getHours())+t(e.getMinutes())+t(e.getSeconds())}function Nt(e,t){const n=e.cbt_extra_data??{},r=(t?be[t]:void 0)??n.payment?.paymethod??Et,o=window.location.origin+e.callback_urls.cbt_cvs_notify;return{...n,paymentUI:{language:"JP",...n.paymentUI??{}},payment:{...n.payment??{},paymethod:r,isMobile:ge()?"true":"false",cvs:{...n.payment?.cvs??{},notiUrl:o}}}}async function Rt(e,t,n,i){const r=t.japan_mid,o=Tt(),a=n.customer_email??"",s=n.customer_phone??"",c=await e.api.post(t.callback_urls.cbt_checkout_token,{oid:n.order_number,price:n.amount,buyer_email:a,buyer_phone:s}),{checkout_token:u}=c.data,l=await e.api.post(t.callback_urls.cbt_hash_data,{oid:n.order_number,price:n.amount,timestamp:o,buyer_email:a,buyer_phone:s,checkout_token:u}),{hash_data:d}=l.data,f=window.location.origin+t.callback_urls.cbt_callback+`?oid=${encodeURIComponent(n.order_number)}&selectedPaymentMethod=${encodeURIComponent(i??"card")}`;we(t.callback_urls.cbt_auth_url,{cbtType:"JPPG",mid:r,timestamp:o,returnUrl:f,buyerName:n.customer_name??"",buyerTel:n.customer_phone??"",buyerEmail:n.customer_email??"",goodName:n.order_name,amount:String(n.amount),orderId:n.order_number,hashData:d,extraData:JSON.stringify(Nt(t,i))})}async function Lt(e,t){const{pgPaymentData:n,paymentMethod:i}=e.params||{};if(!n)return;const r=window.__templateApp?.globalState?._local,o=i??n.payment_method??r?.paymentMethod??"card",a=window.G7Core;try{const s=await a.api.get("/modules/sirsoft-ecommerce/payments/client-config/kginicis");if(!s.data)throw new Error("Failed to fetch KG Inicis client config");const c=s.data,u=kt(n.currency),l=u==="JPY",d=u==="KRW",f=o.startsWith("kginicis_japan_"),m=St.has(o),p=c.japan_enabled&&!!c.japan_mid&&c.japan_configured!==!1,y=c.japan_restrict_jpy_payment_methods===!0;if(f&&!l)throw new Error("KG Inicis Japan payment methods require a JPY order.");if(m&&Array.isArray(c.easy_pay_enabled_methods)&&!c.easy_pay_enabled_methods.includes(o))throw new Error("Selected KG Inicis easy pay method is disabled.");if(!l&&!d)throw new Error("KG Inicis supports only KRW standard payments or JPY Japan CBT payments.");if(l&&!p)throw new Error("KG Inicis Japan CBT payment is not configured.");if(l&&y&&!vt.has(o))throw new Error("JPY orders can only use KG Inicis Japan CBT payment methods.");const g=ge();if(d&&c.standard_configured===!1)throw new Error("KG Inicis live standard payment is not configured.");if(d&&g&&c.mobile_configured===!1)throw new Error("KG Inicis live mobile payment is not configured.");l?await Rt(a,c,n,o):g?await It(a,c,n,o):await At(a,c,n,o)}catch(s){const c=s instanceof Error?s.message:"Unknown error";a?.state?.setLocal?.({paymentErrorMessage:c,isSubmittingOrder:!1,paymentMethod:o}),a?.modal?.open?.("kginicis_payment_error_modal")}}const ke={requestPayment:Lt},Mt="sirsoft-pay_kginicis",Kt={info:(...e)=>console.info(`[${Mt}]`,...e)};function Ot(){Kt.info("order interceptor is a no-op — payment entry is dispatched via pg_payment_handler")}const Gt="sirsoft-pay_kginicis";function $t(){return localStorage.getItem("auth_token")}function Dt(){try{const t=sessionStorage.getItem("g7_guest_order_token");if(t)return t}catch{}const e=window.G7Core?.state?.get?.("_global")?.guestOrderToken;return typeof e=="string"&&e!==""?e:null}async function Pe(e,t){try{const n=await fetch(`/api/plugins/${Gt}/user/orders/${e}/receipt`,{headers:t,credentials:"same-origin"});return n.ok?{status:n.status,data:await n.json()}:{status:n.status,data:null}}catch{return{status:0,data:null}}}async function xe(e){const t=$t(),n=Dt(),i={Accept:"application/json"};t?i.Authorization=`Bearer ${t}`:n&&(i["X-Guest-Order-Token"]=n);const r=await Pe(e,i);if(r.data||r.status!==401||!t)return{status:r.status,info:r.data};const o={Accept:"application/json"};n&&(o["X-Guest-Order-Token"]=n);const a=await Pe(e,o);return{status:a.status,info:a.data}}async function S(e){return(await xe(e)).info}function P(e){return e?e.receipt_url?!0:e.receipt_type==="cbt_confirmation"&&Array.isArray(e.receipt_fields)&&e.receipt_fields.length>0:!1}function I(e){return e?.receipt_view_label||"영수증 조회"}function Bt(e){return e?.receipt_label||"영수증"}function Ce(e){if(e.receipt_url){window.open(e.receipt_url,"kginicis_receipt","width=800,height=600,scrollbars=yes,resizable=yes");return}e.receipt_type==="cbt_confirmation"&&jt(e)}function jt(e){const t=window.open("","kginicis_receipt","width=800,height=700,scrollbars=yes,resizable=yes");if(!t)return;const n=e.receipt_title||"KG 이니시스 CBT 결제확인서",i=e.receipt_notice||"",r=(e.receipt_fields??[]).map(o=>`
+(function(){"use strict";const x="kginicis_pay_form_",R="__sirsoftKginicisReloadStandardPaySdk",w="__sirsoftKginicisMobilePaymentReturnPending";function oe(){if(typeof document>"u")return 0;const e=document.querySelectorAll(`form[id^="${x}"]`);return e.forEach(t=>t.remove()),e.length}function qe(){typeof window>"u"||(window[R]=!0)}function We(){if(typeof window>"u")return!1;const e=window,t=e[R]===!0;return delete e[R],t}function Ve(e){typeof window>"u"||typeof document>"u"||(window.INIStdPay=void 0,document.querySelectorAll(`script[src="${e}"], script[src*="/INIStdPay_third-party.js"]`).forEach(t=>t.remove()))}function ze(e=Date.now()){if(!(typeof window>"u"))try{window.sessionStorage.setItem(w,String(e))}catch{window[w]=e}}function M(){if(!(typeof window>"u")){try{window.sessionStorage.removeItem(w)}catch{}delete window[w]}}function ie(e=Date.now()){if(typeof window>"u")return!1;let t=null;try{const n=window.sessionStorage.getItem(w);t=n?Number(n):null}catch{t=null}if(!Number.isFinite(t)){const n=window[w];t=typeof n=="number"?n:Number(n)}return!Number.isFinite(t)||e-t>18e5?(M(),!1):!0}const N="sirsoft-pay_kginicis",Xe=N,Ze="payment-window-closed",ae="__sirsoftKginicisPaymentCloseListenerInstalled",L="__sirsoftKginicisActiveStandardPaymentCloseContext",se=20,Qe=100,O={info:(...e)=>console.info(`[${N}]`,...e),warn:(...e)=>console.warn(`[${N}]`,...e)};function et(e){if(!e||typeof e!="object")return!1;const t=e;return t.source===Xe&&t.type===Ze}function $(){return/\/shop\/checkout\/?$/.test(window.location.pathname)}function P(){return window}function tt(){const e=P()[L];return!e||typeof e!="object"?null:e}function nt(e){return/^https?:\/\//i.test(e)||e.startsWith("/api/")?e:e.startsWith("/plugins/")?`/api${e}`:e.startsWith("plugins/")?`/api/${e}`:e}function rt(e){e.closeReportUrl&&(P()[L]={...e,reported:!1})}function ce(){delete P()[L]}function de(){ce()}async function ot(e="payment-window-closed"){const t=tt();if(!t||t.reported||!$())return;t.reported=!0;const n={oid:t.oid,price:t.price,buyer_email:t.buyer_email??"",buyer_phone:t.buyer_phone??"",payment_method:t.payment_method??"",reason:e};try{const r=window.G7Core?.api;typeof r?.post=="function"?await r.post(t.closeReportUrl,n):await fetch(nt(t.closeReportUrl),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n),keepalive:!0})}catch(r){O.warn("failed to report KG payment window close",{reason:e,error:r})}finally{ce()}}function le(e="payment-window-closed",t=!0){if(!$())return!1;const r=window.G7Core?.state?.setLocal;return typeof r!="function"?(t&&O.warn("G7Core.state.setLocal not available while resetting payment submit state"),!1):(oe(),qe(),r({isSubmittingOrder:!1}),O.info("checkout submit state reset after KG payment close",{reason:e}),!0)}function it(e,t=!1){let n=0;const r=()=>{if(n++,!(t&&!ie())){if(le(e,n>=se)){t&&M();return}!$()||n>=se||window.setTimeout(r,Qe)}};r()}function I(e){ie()&&it(e,!0)}function at(){if(typeof window>"u")return;const e=P();e[ae]||(window.addEventListener("message",t=>{t.origin===window.location.origin&&et(t.data)&&(ot(t.data.reason),le(t.data.reason))}),window.addEventListener("pagehide",()=>{de()}),window.addEventListener("beforeunload",()=>{de()}),window.addEventListener("pageshow",t=>{I(t.persisted?"mobile-payment-bfcache-return":"mobile-payment-page-show")}),window.addEventListener("focus",()=>{I("mobile-payment-window-focus")}),document.addEventListener("visibilitychange",()=>{document.visibilityState==="visible"&&I("mobile-payment-visibility-return")}),e[ae]=!0,I("mobile-payment-listener-installed"))}function ue(){if(typeof navigator>"u")return!1;const e=navigator;if(e.userAgentData?.mobile!==void 0)return e.userAgentData.mobile;const t=(e.userAgent||"").toLowerCase(),n=((e.userAgentData?.platform??e.platform)||"").toLowerCase();if(/android|iphone|ipad|ipod|windows phone|iemobile|blackberry|opera mini|mobile safari/.test(t)||/iphone|ipad|ipod|ios/.test(n))return!0;const r=e.maxTouchPoints??0;return/macintosh|mac os x/.test(t)&&r>1}function st(e){return new Promise((t,n)=>{if(document.querySelector(`script[src="${e}"]`)){t();return}const r=document.createElement("script");r.src=e,r.async=!0,r.onload=()=>t(),r.onerror=()=>n(new Error(`Failed to load script: ${e}`)),document.head.appendChild(r)})}function me(e,t){if(!t.startsWith("kginicis_"))return e;const n=new URL(e,window.location.origin);return n.searchParams.set("selectedPaymentMethod",t),n.toString()}function ct(e){const t=(e??"").trim().toUpperCase();return t===""||t==="WON"?"KRW":t}function fe(e,t,n="utf-8",r){const o=document.createElement("form");r&&(o.id=r),o.method="POST",o.action=e,o.acceptCharset=n,o.style.display="none";for(const[i,s]of Object.entries(t)){const a=document.createElement("input");a.type="hidden",a.name=i,a.value=s,o.appendChild(a)}document.body.appendChild(o),o.submit()}const dt={card:"Card",vbank:"VBank",bank:"DirectBank",phone:"HPP",kginicis_samsung_pay:"onlyssp",kginicis_naverpay:"onlynaverpay",kginicis_lpay:"onlylpay",kginicis_kakaopay:"onlykakaopay"},lt={card:"CARD",vbank:"VBANK",bank:"BANK",phone:"MOBILE"},ut={kginicis_samsung_pay:"d_samsungpay=Y",kginicis_naverpay:"d_npay=Y",kginicis_lpay:"d_lpay=Y",kginicis_kakaopay:"d_kakaopay=Y"},mt=["CARD","CVS","PAYpay"],pe={card:["CARD"],kginicis_japan_paypay:["PAYpay"],kginicis_japan_cvs:["CVS"]},ft=new Set(Object.keys(pe)),pt=new Set(["kginicis_samsung_pay","kginicis_naverpay","kginicis_lpay","kginicis_kakaopay"]);async function yt(e,t,n,r){const o=String(Math.floor(Date.now())),i=await e.api.post(t.callback_urls.mobile_signature,{oid:n.order_number,price:n.amount,timestamp:o,buyer_email:n.customer_email??"",buyer_phone:n.customer_phone??""}),{chkfake:s,mobile_payment_url:a}=i.data,c=me(window.location.origin+t.callback_urls.mobile_callback+"?orderId="+encodeURIComponent(n.order_number),r),u=ut[r],d=u!==void 0,l=d?a.replace(/\/smart\/[^/]+\/?$/,"/smart/wcard/"):a,m=lt[r]??"CARD",f=t.use_escrow?"below1000=Y&vbank_receipt=Y&useescrow=Y¢erCd=Y&amt_hash=Y":"below1000=Y&vbank_receipt=Y¢erCd=Y&amt_hash=Y",p=d?f.replace("&useescrow=Y","")+"&"+u:f,y={P_MID:t.mid,P_OID:n.order_number,P_AMT:String(n.amount),P_GOODS:n.order_name,P_UNAME:n.customer_name??"",P_MOBILE:n.customer_phone??"",P_EMAIL:n.customer_email??"",P_NEXT_URL:c,P_CHARSET:"utf8",P_TIMESTAMP:o,P_CHKFAKE:s,P_RESERVED:p};d?y.P_SKIP_TERMS="Y":y.P_INI_PAYMENT=m,m==="MOBILE"&&!d&&(y.P_HPP_METHOD="2"),m==="VBANK"&&!d&&(y.P_NOTI_URL=window.location.origin+t.callback_urls.mobile_vbank_notify),ze(),fe(l,y,"euc-kr",x+"mobile_"+Date.now())}async function _t(e,t,n,r){const o=String(Math.floor(Date.now())),i=await e.api.post(t.callback_urls.signature,{oid:n.order_number,price:n.amount,timestamp:o,buyer_email:n.customer_email??"",buyer_phone:n.customer_phone??""}),{signature:s,verification:a,mKey:c}=i.data;if(We()&&Ve(t.sdk_url),window.INIStdPay||await st(t.sdk_url),window.INIStdPay||await new Promise(p=>setTimeout(p,100)),!window.INIStdPay)throw new Error("INIStdPay SDK not available");const u=me(window.location.origin+t.callback_urls.callback,r),d=window.location.origin+t.callback_urls.close;oe();const l=x+Date.now(),m=document.createElement("form");m.id=l,m.method="POST",m.acceptCharset="euc-kr";const f={version:"1.0",mid:t.mid,oid:n.order_number,goodname:n.order_name,price:String(n.amount),currency:"WON",buyername:n.customer_name??"",buyeremail:n.customer_email??"",buyertel:n.customer_phone??"",timestamp:o,signature:s,verification:a,mKey:c,returnUrl:u,closeUrl:d,gopaymethod:dt[r]??"Card",acceptmethod:(()=>{const p=t.use_escrow?"useescrow:":"",y=t.use_credit_point?"CREDITCARD(Y):":"",h=r==="phone"?`HPP(1):${p}${y}centerCd(Y)`:`${p}${y}centerCd(Y)`;return r==="kginicis_samsung_pay"||r==="kginicis_naverpay"||r==="kginicis_lpay"||r==="kginicis_kakaopay"?h?`${h}:cardonly`:"cardonly":h})(),payViewType:"overlay",use_chkfake:"Y",charset:"UTF-8"};for(const[p,y]of Object.entries(f)){const h=document.createElement("input");h.type="hidden",h.name=p,h.value=y,m.appendChild(h)}document.body.appendChild(m),t.callback_urls.close_report&&rt({closeReportUrl:t.callback_urls.close_report,oid:n.order_number,price:Number(n.amount),buyer_email:n.customer_email??"",buyer_phone:n.customer_phone??"",payment_method:r}),window.INIStdPay.pay(l)}function ht(e=new Date){const t=n=>String(n).padStart(2,"0");return e.getFullYear().toString()+t(e.getMonth()+1)+t(e.getDate())+t(e.getHours())+t(e.getMinutes())+t(e.getSeconds())}function gt(e,t){const n=e.cbt_extra_data??{},o=(t?pe[t]:void 0)??n.payment?.paymethod??mt,i=window.location.origin+e.callback_urls.cbt_cvs_notify;return{...n,paymentUI:{language:"JP",...n.paymentUI??{}},payment:{...n.payment??{},paymethod:o,isMobile:ue()?"true":"false",cvs:{...n.payment?.cvs??{},notiUrl:i}}}}async function wt(e,t,n,r){const o=t.japan_mid,i=ht(),s=n.customer_email??"",a=n.customer_phone??"",c=await e.api.post(t.callback_urls.cbt_checkout_token,{oid:n.order_number,price:n.amount,buyer_email:s,buyer_phone:a}),{checkout_token:u}=c.data,d=await e.api.post(t.callback_urls.cbt_hash_data,{oid:n.order_number,price:n.amount,timestamp:i,buyer_email:s,buyer_phone:a,checkout_token:u}),{hash_data:l}=d.data,m=window.location.origin+t.callback_urls.cbt_callback+`?oid=${encodeURIComponent(n.order_number)}&selectedPaymentMethod=${encodeURIComponent(r??"card")}`;fe(t.callback_urls.cbt_auth_url,{cbtType:"JPPG",mid:o,timestamp:i,returnUrl:m,buyerName:n.customer_name??"",buyerTel:n.customer_phone??"",buyerEmail:n.customer_email??"",goodName:n.order_name,amount:String(n.amount),orderId:n.order_number,hashData:l,extraData:JSON.stringify(gt(t,r))})}async function bt(e,t){const{pgPaymentData:n,paymentMethod:r}=e.params||{};if(!n)return;const o=window.__templateApp?.globalState?._local,i=r??n.payment_method??o?.paymentMethod??"card",s=window.G7Core;try{const a=await s.api.get("/modules/sirsoft-ecommerce/payments/client-config/kginicis");if(!a.data)throw new Error("Failed to fetch KG Inicis client config");const c=a.data,u=ct(n.currency),d=u==="JPY",l=u==="KRW",m=i.startsWith("kginicis_japan_"),f=pt.has(i),p=c.japan_enabled&&!!c.japan_mid&&c.japan_configured!==!1,y=c.japan_restrict_jpy_payment_methods===!0;if(m&&!d)throw new Error("KG Inicis Japan payment methods require a JPY order.");if(f&&Array.isArray(c.easy_pay_enabled_methods)&&!c.easy_pay_enabled_methods.includes(i))throw new Error("Selected KG Inicis easy pay method is disabled.");if(!d&&!l)throw new Error("KG Inicis supports only KRW standard payments or JPY Japan CBT payments.");if(d&&!p)throw new Error("KG Inicis Japan CBT payment is not configured.");if(d&&y&&!ft.has(i))throw new Error("JPY orders can only use KG Inicis Japan CBT payment methods.");const h=ue();if(l&&c.standard_configured===!1)throw new Error("KG Inicis live standard payment is not configured.");if(l&&h&&c.mobile_configured===!1)throw new Error("KG Inicis live mobile payment is not configured.");d?await wt(s,c,n,i):h?await yt(s,c,n,i):await _t(s,c,n,i)}catch(a){const c=a instanceof Error?a.message:"Unknown error";s?.state?.setLocal?.({paymentErrorMessage:c,isSubmittingOrder:!1,paymentMethod:i}),s?.modal?.open?.("kginicis_payment_error_modal")}}const ye={requestPayment:bt},kt="sirsoft-pay_kginicis",Ct={info:(...e)=>console.info(`[${kt}]`,...e)};function Et(){Ct.info("order interceptor is a no-op — payment entry is dispatched via pg_payment_handler")}const St="sirsoft-pay_kginicis";function Pt(){return localStorage.getItem("auth_token")}function It(){try{const t=sessionStorage.getItem("g7_guest_order_token");if(t)return t}catch{}const e=window.G7Core?.state?.get?.("_global")?.guestOrderToken;return typeof e=="string"&&e!==""?e:null}async function _e(e,t){try{const n=await fetch(`/api/plugins/${St}/user/orders/${e}/receipt`,{headers:t,credentials:"same-origin"});return n.ok?{status:n.status,data:await n.json()}:{status:n.status,data:null}}catch{return{status:0,data:null}}}async function he(e){const t=Pt(),n=It(),r={Accept:"application/json"};t?r.Authorization=`Bearer ${t}`:n&&(r["X-Guest-Order-Token"]=n);const o=await _e(e,r);if(o.data||o.status!==401||!t)return{status:o.status,info:o.data};const i={Accept:"application/json"};n&&(i["X-Guest-Order-Token"]=n);const s=await _e(e,i);return{status:s.status,info:s.data}}async function v(e){return(await he(e)).info}function C(e){return e?e.receipt_url?!0:e.receipt_type==="cbt_confirmation"&&Array.isArray(e.receipt_fields)&&e.receipt_fields.length>0:!1}function A(e){return e?.receipt_view_label||"영수증 조회"}function vt(e){return e?.receipt_label||"영수증"}function ge(e){if(e.receipt_url){window.open(e.receipt_url,"kginicis_receipt","width=800,height=600,scrollbars=yes,resizable=yes");return}e.receipt_type==="cbt_confirmation"&&At(e)}function At(e){const t=window.open("","kginicis_receipt","width=800,height=700,scrollbars=yes,resizable=yes");if(!t)return;const n=e.receipt_title||"KG 이니시스 CBT 결제확인서",r=e.receipt_notice||"",o=(e.receipt_fields??[]).map(i=>`
-
${x(o.label)}
- ${x(o.value)}
+ ${E(i.label)}
+ ${E(i.value)}
`).join("");t.document.open(),t.document.write(`
- ${x(n)}
+ ${E(n)}