diff --git a/docs/testing/e2e-testing.md b/docs/testing/e2e-testing.md index 8bd2dd88..392a24ce 100644 --- a/docs/testing/e2e-testing.md +++ b/docs/testing/e2e-testing.md @@ -74,14 +74,27 @@ test('@smoke 홈페이지 마운트', async ({ page }) => { 기존 PHPUnit testsuite (`Unit`/`Feature`/`Module`/`Plugin`) 및 Vitest 의 코어/확장 분리 원칙과 동일. -확장이 자체 config 를 가지므로 **확장 디렉토리에서 직접 실행**: +확장은 `tests/Playwright/playwright.config.ts` 에 자체 config 를 가지므로 **확장 디렉토리에서 직접 실행**한다. +config 가 확장 루트가 아니라 `tests/Playwright/` 아래에 있으므로 `npx playwright test` 를 인자 없이 부르면 +config 를 찾지 못한다 — `npm run test:e2e` (config 경로가 이미 묶여 있음) 를 쓴다. ```powershell cd modules/_bundled/sirsoft-ecommerce -$env:PLAYWRIGHT_BASE_URL='https://g7.dev' -npx playwright test +npm run test:e2e # 확장 전체 +npm run test:e2e -- specs/admin/some.spec.ts # 단일 spec +npm run test:e2e:ui # UI 디버깅 모드 ``` +base URL 은 코어 `.env` 의 `APP_URL` 에서 자동 해석된다 (`PLAYWRIGHT_BASE_URL` 로 덮어쓸 수 있다). + +### 실행 시 유의 (경험칙) + +| 항목 | 내용 | +| --- | --- | +| 산출물 위치 | 리포트·trace 는 **코어 루트**의 `test-results/{type}/{id}/`, `playwright-report/{type}/{id}/` 에 쌓인다. 확장 디렉토리 안에 쌓으면 Windows 에서 `{module\|template}:update` 의 디렉토리 이동이 열린 핸들에 걸려 실패한다 | +| 레이아웃 수정 후 | 레이아웃 JSON 을 고쳤으면 `{type}:update {id} --force` **+ `template:cache-clear {템플릿}`** 까지 해야 브라우저에 반영된다. 레이아웃은 템플릿 캐시(`/api/layouts/{template}/...`)로 서빙되므로 `cache:clear` 만으로는 갱신되지 않는다 | +| 공유 상태 | 같은 관리자 설정 화면을 건드리는 spec 이 병렬로 돌면 서로의 저장 상태를 덮어써 실패할 수 있다. 설정 저장을 수반하는 spec 은 `--workers=1` 로 확인한다 | + ## §4. 데이터 생성 위치 — 책임 분리 매트릭스 | 데이터 종류 | 책임 영역 | 위치 | 호출 | diff --git a/lang-packs/_bundled/g7-module-sirsoft-ecommerce-ja/CHANGELOG.md b/lang-packs/_bundled/g7-module-sirsoft-ecommerce-ja/CHANGELOG.md index 1ef02dee..35ef55bb 100644 --- a/lang-packs/_bundled/g7-module-sirsoft-ecommerce-ja/CHANGELOG.md +++ b/lang-packs/_bundled/g7-module-sirsoft-ecommerce-ja/CHANGELOG.md @@ -4,6 +4,12 @@ 형식은 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/)를 따르며, [Semantic Versioning](https://semver.org/lang/ko/)을 준수합니다. +## [1.0.4] - 2026-07-14 + +### Added + +- 주문설정 결제수단 목록의 "PG 고정" 표시에 대한 일본어 번역을 추가했습니다. + ## [1.0.3] - 2026-07-14 ### Added diff --git a/lang-packs/_bundled/g7-module-sirsoft-ecommerce-ja/frontend/partial/admin/settings.json b/lang-packs/_bundled/g7-module-sirsoft-ecommerce-ja/frontend/partial/admin/settings.json index fc0635e2..16f3a899 100644 --- a/lang-packs/_bundled/g7-module-sirsoft-ecommerce-ja/frontend/partial/admin/settings.json +++ b/lang-packs/_bundled/g7-module-sirsoft-ecommerce-ja/frontend/partial/admin/settings.json @@ -151,7 +151,8 @@ "pg_not_installed": "PG未インストール", "pg_select_first": "PG社をまず選択してください", "mileage_deduction_timing": "マイレージ差し引き時点", - "mileage_deduction_timing_disabled_hint": "マイレージ使用がオフになっているため、差し引き時点が適用されません。" + "mileage_deduction_timing_disabled_hint": "マイレージ使用がオフになっているため、差し引き時点が適用されません。", + "pg_fixed": "PG 固定" }, "bank_accounts": { "title": "無通帳口座番号設定", diff --git a/lang-packs/_bundled/g7-module-sirsoft-ecommerce-ja/language-pack.json b/lang-packs/_bundled/g7-module-sirsoft-ecommerce-ja/language-pack.json index 0cd77c21..180054e3 100644 --- a/lang-packs/_bundled/g7-module-sirsoft-ecommerce-ja/language-pack.json +++ b/lang-packs/_bundled/g7-module-sirsoft-ecommerce-ja/language-pack.json @@ -12,7 +12,7 @@ "en": "G7 module (sirsoft-ecommerce) Japanese language pack (bundled)", "ja": "G7 モジュール (sirsoft-ecommerce) 日本語 言語パック(バンドル)" }, - "version": "1.0.3", + "version": "1.0.4", "license": "MIT", "scope": "module", "target_identifier": "sirsoft-ecommerce", diff --git a/modules/_bundled/sirsoft-board/package.json b/modules/_bundled/sirsoft-board/package.json index e063df2d..9106b48a 100644 --- a/modules/_bundled/sirsoft-board/package.json +++ b/modules/_bundled/sirsoft-board/package.json @@ -9,7 +9,9 @@ "build": "vite build", "preview": "vite preview", "test": "vitest", - "test:run": "vitest run" + "test:run": "vitest run", + "test:e2e": "playwright test --config=tests/Playwright/playwright.config.ts", + "test:e2e:ui": "playwright test --config=tests/Playwright/playwright.config.ts --ui" }, "devDependencies": { "jsdom": "^27.4.0", diff --git a/modules/_bundled/sirsoft-board/tests/Playwright/playwright.config.ts b/modules/_bundled/sirsoft-board/tests/Playwright/playwright.config.ts index b3392758..6d809fb5 100644 --- a/modules/_bundled/sirsoft-board/tests/Playwright/playwright.config.ts +++ b/modules/_bundled/sirsoft-board/tests/Playwright/playwright.config.ts @@ -14,6 +14,18 @@ import { fileURLToPath } from 'node:url'; const __dirname = dirname(fileURLToPath(import.meta.url)); +/** + * 코어 루트 (artisan / .env / Playwright 산출물의 기준 경로). + * + * 확장 config 는 확장 디렉토리에서 실행되지만, 산출물을 그 안에 쓰면 Windows 에서 + * `{module|template}:update` 의 디렉토리 이동이 열린 핸들에 걸려 실패한다. + * 산출물은 코어 루트 아래로 모아 update 경로와 분리한다 (.gitignore 가 이미 덮는 위치). + */ +const CORE_ROOT = process.env.G7_ROOT || resolve(__dirname, '../../../../../'); + +/** 확장별 산출물 격리 — 확장끼리 리포트를 덮어쓰지 않도록 slug 로 네임스페이스. */ +const ARTIFACT_SLUG = 'modules/sirsoft-board'; + function readEnvFile(filePath: string, key: string): string | null { if (!existsSync(filePath)) return null; const content = readFileSync(filePath, { encoding: 'utf-8' }); @@ -31,8 +43,7 @@ function resolveBaseUrl(): string { if (process.env.PLAYWRIGHT_BASE_URL) { return process.env.PLAYWRIGHT_BASE_URL; } - const coreRoot = process.env.G7_ROOT || resolve(__dirname, '../../../../../'); - const appUrl = readEnvFile(resolve(coreRoot, '.env'), 'APP_URL'); + const appUrl = readEnvFile(resolve(CORE_ROOT, '.env'), 'APP_URL'); if (appUrl && !/^https?:\/\/localhost(:\d+)?\/?$/i.test(appUrl)) { return appUrl; } @@ -43,12 +54,13 @@ function resolveBaseUrl(): string { export default defineConfig({ testDir: './specs', + outputDir: resolve(CORE_ROOT, 'test-results', ARTIFACT_SLUG), fullyParallel: true, forbidOnly: !!process.env.CI, retries: process.env.CI ? 2 : 0, workers: process.env.CI ? 1 : undefined, reporter: [ - ['html', { outputFolder: 'playwright-report', open: 'never' }], + ['html', { outputFolder: resolve(CORE_ROOT, 'playwright-report', ARTIFACT_SLUG), open: 'never' }], ['list'], ], use: { diff --git a/modules/_bundled/sirsoft-ecommerce/CHANGELOG.md b/modules/_bundled/sirsoft-ecommerce/CHANGELOG.md index 597ee66c..0580e9bd 100644 --- a/modules/_bundled/sirsoft-ecommerce/CHANGELOG.md +++ b/modules/_bundled/sirsoft-ecommerce/CHANGELOG.md @@ -4,6 +4,20 @@ 형식은 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/)를 따르며, [Semantic Versioning](https://semver.org/lang/ko/)을 준수합니다. +## [1.0.4] - 2026-07-14 + +### Fixed + +- 네이버페이·카카오페이 등 결제 플러그인이 제공하는 간편결제로 주문할 때, 결제가 실패했는데도 관리자에게 "신규 주문 접수" 알림이 발송되던 문제를 수정했습니다. 이제 다른 카드 결제와 마찬가지로 결제가 실제로 완료된 시점에만 알림이 발송됩니다. +- 간편결제로 결제하다 실패한 뒤 다시 결제하려 하면 "임시주문을 찾을 수 없습니다" 오류가 뜨며 재결제가 불가능하던 문제를 수정했습니다. 이제 결제 실패 후에도 주문서가 유지되어 결제수단을 바꾸거나 다시 시도할 수 있습니다. +- 간편결제 주문을 취소할 때 카드 취소(PG 취소)가 함께 이뤄지지 않고 주문 상태만 취소로 바뀔 수 있던 문제를 수정했습니다. +- 주문 목록의 결제수단 필터와 주문서의 결제수단 검증이 간편결제를 인식하지 못하던 문제를 수정했습니다. + +### Changed + +- 결제 플러그인이 제공하는 간편결제도 신용카드·가상계좌와 동일하게 정식 결제수단으로 취급됩니다. 주문 내역에 실제로 선택한 결제수단(예: 네이버페이)이 그대로 기록되어 관리자 주문 상세와 주문 목록에서 확인할 수 있습니다. +- 쇼핑몰 환경설정 > 주문설정의 결제수단 목록에서 간편결제가 "PG 불필요"가 아니라 "PG 고정"으로 표시되고 어느 결제대행사를 통해 처리되는지 함께 보여줍니다. 간편결제는 해당 결제대행사 전용이므로 다른 대행사로 바꿀 수 없습니다. + ## [1.0.3] - 2026-07-14 ### Security diff --git a/modules/_bundled/sirsoft-ecommerce/composer.json b/modules/_bundled/sirsoft-ecommerce/composer.json index cc0f1bb7..3eda744e 100644 --- a/modules/_bundled/sirsoft-ecommerce/composer.json +++ b/modules/_bundled/sirsoft-ecommerce/composer.json @@ -2,7 +2,7 @@ "name": "modules/sirsoft-ecommerce", "description": "Ecommerce module for Gnuboard7", "type": "library", - "version": "1.0.3", + "version": "1.0.4", "license": "MIT", "autoload": { "psr-4": { diff --git a/modules/_bundled/sirsoft-ecommerce/database/factories/OrderPaymentFactory.php b/modules/_bundled/sirsoft-ecommerce/database/factories/OrderPaymentFactory.php index 47b08edd..9af55eff 100644 --- a/modules/_bundled/sirsoft-ecommerce/database/factories/OrderPaymentFactory.php +++ b/modules/_bundled/sirsoft-ecommerce/database/factories/OrderPaymentFactory.php @@ -31,7 +31,7 @@ class OrderPaymentFactory extends Factory 'embedded_pg_provider' => null, 'transaction_id' => $faker->uuid(), 'merchant_order_id' => 'MO-'.strtoupper($faker->bothify('????-####-????')), - 'payment_method' => PaymentMethodEnum::CARD, + 'payment_method' => PaymentMethodEnum::CARD->value, 'payment_device' => $faker->randomElement(['pc', 'mobile']), 'paid_amount_local' => $amount, 'paid_amount_base' => $amount, @@ -130,7 +130,7 @@ class OrderPaymentFactory extends Factory $faker = \fake(); return $this->state(fn (array $attributes) => [ - 'payment_method' => PaymentMethodEnum::CARD, + 'payment_method' => PaymentMethodEnum::CARD->value, 'card_name' => $faker->randomElement(['신한카드', '삼성카드', 'KB국민카드', '현대카드']), 'card_number_masked' => $faker->numerify('####-****-****-####'), ]); @@ -144,7 +144,7 @@ class OrderPaymentFactory extends Factory $faker = \fake(); return $this->state(fn (array $attributes) => [ - 'payment_method' => PaymentMethodEnum::DBANK, + 'payment_method' => PaymentMethodEnum::DBANK->value, 'payment_status' => PaymentStatusEnum::WAITING_DEPOSIT, 'pg_provider' => '', 'embedded_pg_provider' => null, @@ -169,7 +169,7 @@ class OrderPaymentFactory extends Factory $faker = \fake(); return $this->state(fn (array $attributes) => [ - 'payment_method' => PaymentMethodEnum::VBANK, + 'payment_method' => PaymentMethodEnum::VBANK->value, 'payment_status' => PaymentStatusEnum::WAITING_DEPOSIT, 'vbank_code' => '088', 'vbank_name' => $faker->randomElement(['신한은행', '국민은행', '우리은행']), diff --git a/modules/_bundled/sirsoft-ecommerce/docs/api/orders.md b/modules/_bundled/sirsoft-ecommerce/docs/api/orders.md index bb6a447b..1109b561 100644 --- a/modules/_bundled/sirsoft-ecommerce/docs/api/orders.md +++ b/modules/_bundled/sirsoft-ecommerce/docs/api/orders.md @@ -35,7 +35,7 @@ | order_status | query | array | 아니오 | — | 주문상태 다중 선택 필터 (OrderStatusEnum 값 배열, 해당 상태의 주문만 조회) | | option_status | query | array | 아니오 | — | 주문옵션 상태 다중 선택 필터 (OrderStatusEnum 값 배열, 해당 옵션 상태를 가진 주문만 조회) | | shipping_type | query | array | 아니오 | — | 배송유형 다중 선택 필터 (ShippingType 코드 배열) | -| payment_method | query | array | 아니오 | — | 결제수단 다중 선택 필터 (PaymentMethodEnum 값 배열) | +| payment_method | query | array | 아니오 | 결제수단 카탈로그에 등록된 ID | 결제수단 다중 선택 필터. 코어 8종과 PG 플러그인이 등록한 확장 결제수단 ID(예: `nhnkcp_naverpay`)를 모두 허용 | | category_id | query | integer | 아니오 | — | category 식별자 | | min_amount | query | integer | 아니오 | min 0 | 주문금액 범위 필터 하한 (이 금액 이상 주문만 조회) | | max_amount | query | integer | 아니오 | min 0 | 주문금액 범위 필터 상한 (이 금액 이하 주문만 조회) | @@ -1330,7 +1330,7 @@ HTTP/1.1 200 | 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | | --- | --- | --- | --- | --- | --- | -| payment_method | body | string | 예 | — | 결제수단 (PaymentMethodEnum 값 — card/vbank/dbank 등) | +| payment_method | body | string | 예 | 결제수단 카탈로그에 등록된 ID | 결제수단. 코어 8종(`card`/`vbank`/`dbank`/`bank`/`phone`/`point`/`deposit`/`free`) 과 PG 플러그인이 등록한 확장 결제수단 ID(예: `nhnkcp_naverpay`, `kginicis_lpay`)를 모두 허용한다. 확장 결제수단도 1급 시민으로 그대로 저장된다. 카탈로그에 없는 값은 422 | | expected_total_amount | body | number | 예 | min 0 | 프론트가 계산한 예상 결제금액 (서버 재계산값과 대조해 금액 위변조 검증) | | shipping_memo | body | string | 아니오 | max 500 | 배송 요청사항 메모 | | depositor_name | body | string | 아니오 | max 50 | depositor 이름 (식별자) | @@ -1362,18 +1362,33 @@ Content-Type: application/json **응답 필드** (`data` 내부) - +| 이름 | 타입 | 조건 | 용도 | +| --- | --- | --- | --- | +| order | object | 항상 | 생성된 주문 리소스 | +| redirect_url | string | 항상 | 결제가 필요 없는 주문의 이동 대상 (주문완료 페이지) | +| requires_pg_payment | boolean | 항상 | PG 결제창을 거쳐야 하는 주문인지. 결제수단이 PG 를 요구하고(`needs_pg`), 해석된 PG 가 `manual`/`internal`/`none` 이 아니며, 잔여 결제액이 0보다 클 때 `true` | +| pg_provider | string | `requires_pg_payment=true` | 결제를 처리할 PG (`sirsoft-` 접두사 부착 — 예: `sirsoft-nhnkcp`) | +| pg_payment_data | object | `requires_pg_payment=true` | PG 결제창에 전달할 결제 정보 (아래 표) | +| pg_payment_handler | string | `requires_pg_payment=true` **이고** PG 가 결제 진입 핸들러를 선언한 경우 | 프론트가 그대로 dispatch 할 결제 진입 핸들러 풀네임 (예: `sirsoft-pay_nhnkcp.requestPayment`). PG 가 미선언이면 이 키가 없으며, 템플릿은 PG 분기를 발화하지 않고 비-PG 흐름으로 안전 강하한다 | -**응답 예시** +`pg_payment_data` 필드: - +| 이름 | 타입 | 용도 | +| --- | --- | --- | +| order_number | string | 주문번호 | +| order_name | string | 주문명 (첫 상품명 + 외 N건) | +| amount | number | PG 청구액 (결제 통화의 최소 화폐단위 정수) | +| currency | string | 결제 통화 코드 | +| customer_name / customer_email / customer_phone | string\|null | 주문자 정보 | +| customer_key | string\|null | 회원 주문의 고객 식별자 (`user_{id}`), 비회원은 null | +| payment_method | string | 선택된 결제수단 ID. 확장 결제수단이면 확장 ID 그대로(예: `nhnkcp_naverpay`) — PG 플러그인이 이 값으로 결제창의 결제수단을 결정한다 | **에러 응답** | 상태코드 | 의미 | 발생 조건 | | --- | --- | --- | | 403 | Forbidden | 요구 권한(`sirsoft-ecommerce.user-orders.create`)이 없는 경우 | -| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) | +| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지). `payment_method` 가 결제수단 카탈로그에 없는 값이면 여기서 차단된다 | diff --git a/modules/_bundled/sirsoft-ecommerce/docs/api/settings.md b/modules/_bundled/sirsoft-ecommerce/docs/api/settings.md index 2d8668c0..a2293b6e 100644 --- a/modules/_bundled/sirsoft-ecommerce/docs/api/settings.md +++ b/modules/_bundled/sirsoft-ecommerce/docs/api/settings.md @@ -3570,3 +3570,33 @@ _대표 에러 없음 (공개 조회).