Files
Gnuboard7/modules/_bundled/sirsoft-ecommerce/docs/api/settings.md
T
HeuJung 30ad72a3a1 fix(core,ecommerce,tosspayments): 간편결제 선택값 유실·기본통화 중복·고아 결제수단 노출 수정
엔진
- dot notation setState(target:"_local.xxx")가 canonical _global._local 을 동기화하지
 않아, 화면에는 선택 표시가 되는데 확장 커스텀 핸들러의 getLocal 에는 값이 보이지
 않던 비대칭을 해소했다(engine-v1.58.2). 예외도 경고도 없이 값만 비어 발견이 늦다.
 동기화 기준을 전체 스냅샷이 아니라 live _global._local + 변경 키로 잡아, 부분 상태가
 base 가 되어 다른 키를 잃거나 앞선 setLocal 을 되돌리는 기존 회귀 경로를 피했다.

토스페이먼츠
- 간편결제 자체창 직행 지정을 top-level easyPay 로 넘겨 SDK 가 통째로 무시하던 것을
 card.flowMode DIRECT + card.easyPay 정본 계약으로 교정했다.

이커머스
- defaults 에서 보충된 통화가 옛 is_default 를 들여와 기본 통화가 둘이 되면서 환율이
 1.0 으로 박제돼 PG 청구액이 1/100 로 나갔다. 병합 직후 재동기화하고, 표시 계층의
 기본 통화 해석을 default_currency 우선으로 통일해 환산 계층과 일치시켰다.
- 공급 확장이 더 이상 제공하지 않는 고아 결제수단이 주문서에 노출되던 것을 공개 API
 단일 지점에서 차단했다. 관리자 응답은 운영자가 확인·삭제해야 하므로 그대로 둔다.
- 관리자 주문설정에서 Select 에 준 폭 클래스가 컴포넌트 기본 w-full 에 밀려 죽는
 구조라, 폭 책임을 셀렉트에서 열 컨테이너로 옮겼다.
- 고아 행에서 마일리지 차감 시점 열만 가드가 빠져 노출되던 사전 결함을 함께 고쳤다.
 열을 열거하는 대신 조상 체인의 가드 유무를 스캔해, 이후 추가되는 열도 자동으로
 검사 대상이 되도록 회귀 테스트를 구성했다.

개발 도구
- dev-dashboard 인스톨러 초기화에 환경설정/첨부파일 삭제 토글을 추가했다. 컨테이너
 디렉토리를 보존해 재생성 시 소유권이 바뀌지 않게 하고, 두 옵션이 같은 경로를 다뤄도
 서로 간섭하지 않도록 분리했다.

규정
- 저장값과 확장 훅 카탈로그가 병합되는 설정에서 공개 응답이 카탈로그 소속까지 판정해야
 한다는 규칙을 문서화하고, 정적 판정이 불가한 근거와 함께 coverage 에 등록했다.
2026-08-10 17:17:51 +09:00

1909 lines
85 KiB
Markdown

# Settings API 레퍼런스
> **소유**: module `sirsoft-ecommerce` · **생성**: `php artisan api:docgen` (실측 기반). @generated 블록은 재생성 시 갱신되며, 사람이 작성한 설명은 보존됩니다.
---
## TL;DR (5초 요약)
```text
1. 이 문서는 실제 API 호출로 실측한 Settings 엔드포인트 레퍼런스입니다
2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(raw HTTP) + 실측 응답 필드 표 + 응답 예시(envelope)
3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다
4. 갱신: 코드 변경 후 php artisan api:docgen 재실행
5. 설명(TODO) 칸은 사람이 채웁니다
```
---
### GET /api/modules/sirsoft-ecommerce/admin/settings
<!-- @generated:start:api.modules.sirsoft-ecommerce.admin.settings.index -->
- **라우트명**: `api.modules.sirsoft-ecommerce.admin.settings.index`
- **컨트롤러**: `Modules\Sirsoft\Ecommerce\Http\Controllers\Admin\EcommerceSettingsController@index`
- **인증/권한**: `auth:sanctum` + `permission:sirsoft-ecommerce.settings.read`
**요청 파라미터**
_요청 파라미터 없음._
**요청 예시**
```http
GET /api/modules/sirsoft-ecommerce/admin/settings HTTP/1.1
Host: api.example.com
Accept: application/json
Authorization: Bearer {YOUR_TOKEN}
```
**응답 필드** (`data` 내부)
_단건 응답: `data` 객체의 필드._
| 필드 | 타입 | 실측 예시값 | 용도/설명 |
| --- | --- | --- | --- |
| basic_info | object | `{"shop_name":"","route_path":"shop","no_route":false,"com…` | 쇼핑몰 기본 정보 (쇼핑몰명·라우트 경로·상호·사업자번호·주소·연락처·이메일 등) |
| language_currency | object | `{"default_currency":"KRW","currencies":[{"code":"KRW","na…` | 통화 설정 (기본 통화 + 등록 통화 목록: 코드·다국어명·환율·기호·국기·반올림 규칙) |
| order_settings | object | `{"default_pg_provider":null,"cash_receipt_provider":"toss…` | 주문/결제 설정 (기본 PG·병합된 결제수단·은행/무통장 계좌·자동취소·장바구니 만료·현금영수증 발급 제공자·자진발급·배송비 과세 방식 등) |
| shipping | object | `{"default_country":"KR","available_countries":[{"code":"K…` | 배송 설정 (기본 국가·배송 가능 국가·무료배송·DB 관리 배송사(carriers)·배송유형(types)·계산 API 후보 필드 포함) |
| seo | object | `{"meta_category_title":"{commerce_name} - {category_name}…` | SEO 메타 설정 (카테고리·검색·상품·쇼핑몰 인덱스별 메타 타이틀/설명 및 SEO 활성 토글) |
| review_settings | object | `{"write_deadline_days":90,"max_images":5,"max_image_size_…` | 리뷰 정책 (작성 기한일·이미지 최대 개수·이미지 최대 용량 MB) |
| inquiry | object | `{"board_slug":"inquiry"}` | 문의 연동 설정 (문의 게시판 slug) |
| notifications | object | `{"channels":[{"id":"mail","is_active":true,"sort_order":1…` | 알림 채널 설정 (채널 ID·활성 여부·정렬 순서) |
| mileage | object | `{"enabled":false,"default_earn_rate":1,"earn_trigger":"co…` | 마일리지 설정 (사용 여부·기본 적립률·적립 트리거·통화별 규칙·소멸/소멸 알림·실제 활성 알림 채널 포함) |
| claim | object | `{"refund_reasons":[{"id":1,"type":"refund","code":"order_…` | 클레임 설정 (DB 관리 대상인 환불 사유 목록: 코드·다국어명·귀책 유형·노출/활성 여부) |
| available_pg_providers | array | `[{"id":"kginicis","name_key":"sirsoft-pay_kginicis::provi…` | 설치된 PG 플러그인이 훅으로 등록한 PG 제공자 목록 (id·name_key·지원 결제수단) |
| available_cash_receipt_providers | array | `[]` | 설치된 플러그인이 훅으로 등록한 현금영수증 발급 제공자 목록 (id·name_key — 미등록 시 빈 배열이며 신청 폼이 노출되지 않음) |
| abilities | object | `{"can_update":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) |
| _meta | object | `{"limits":{"auto_cancel_days_min":1,"auto_cancel_days_max…` | 설정 화면 전용 메타. `limits` 는 숫자 입력의 경계값 맵(`config('sirsoft-ecommerce.limits')`)으로, 저장 규칙(FormRequest)과 같은 출처다 — 화면이 리터럴 경계를 들면 규칙 변경을 따라가지 못해 "화면은 받는데 저장은 422" 가 되므로 입력 min/max 는 이 값을 바인딩한다 |
**응답 예시**
```http
HTTP/1.1 200
```
```json
{
"success": true,
"message": "설정을 조회했습니다.",
"data": {
"basic_info": {
"shop_name": "111",
"route_path": "shop",
"no_route": false,
"company_name": null,
"business_number": "",
"...": "(13개 키 생략, 총 18개)"
},
"language_currency": {
"default_currency": "JPY",
"currencies": [
{
"code": "KRW",
"name": {
"ko": "KRW (원)",
"en": "KRW (Won)"
},
"symbol": "₩",
"exchange_rate": null,
"base_unit": 1000,
"...": "(6개 키 생략, 총 11개)"
},
{
"code": "USD",
"name": {
"en": "Dollar"
},
"is_default": false,
"decimal_places": 2,
"base_unit": 1,
"...": "(3개 키 생략, 총 8개)"
},
{
"code": "JPY",
"name": {
"ja": "円"
},
"is_default": true,
"decimal_places": 0,
"base_unit": 100,
"...": "(3개 키 생략, 총 8개)"
},
{
"code": "CNY",
"name": {
"ko": "CNY (위안)",
"en": "CNY (Yuan)"
},
"symbol": "元",
"exchange_rate": 5.8,
"base_unit": 1,
"...": "(6개 키 생략, 총 11개)"
},
{
"code": "EUR",
"name": {
"ko": "EUR (유로)",
"en": "EUR (Euro)"
},
"symbol": "€",
"exchange_rate": 0.78,
"base_unit": 1,
"...": "(6개 키 생략, 총 11개)"
}
]
},
"order_settings": {
"default_pg_provider": null,
"payment_methods": [
{
"id": "card",
"pg_provider": null,
"sort_order": 1,
"is_active": false,
"min_order_amount": 0,
"...": "(10개 키 생략, 총 15개)"
},
{
"id": "dbank",
"pg_provider": null,
"sort_order": 1,
"is_active": true,
"min_order_amount": 0,
"...": "(10개 키 생략, 총 15개)"
},
{
"id": "vbank",
"pg_provider": null,
"sort_order": 2,
"is_active": false,
"min_order_amount": 0,
"...": "(10개 키 생략, 총 15개)"
},
{
"id": "point",
"pg_provider": null,
"sort_order": 2,
"is_active": true,
"min_order_amount": 0,
"...": "(10개 키 생략, 총 15개)"
},
{
"id": "deposit",
"pg_provider": null,
"sort_order": 3,
"is_active": true,
"min_order_amount": 0,
"...": "(10개 키 생략, 총 15개)"
},
"... (총 25건 중 5건 표시)"
],
"banks": [],
"bank_accounts": [
{
"bank_code": "004",
"account_number": "",
"account_holder": "",
"is_active": false,
"is_default": false
}
],
"auto_cancel_expired": true,
"...": "(5개 키 생략, 총 10개)"
},
"shipping": {
"default_country": "KR",
"available_countries": [
{
"code": "KR",
"name": {
"ko": "대한민국",
"en": "South Korea"
},
"is_active": true
},
{
"code": "US",
"name": {
"ko": "미국",
"en": "United States"
},
"is_active": true
}
],
"international_shipping_enabled": true,
"free_shipping_threshold": 50000,
"free_shipping_enabled": true,
"...": "(8개 키 생략, 총 13개)"
},
"seo": {
"meta_category_title": "{commerce_name} - {category_name}",
"meta_category_description": "",
"meta_search_title": "{commerce_name} - {keyword_name}",
"meta_search_description": "",
"meta_product_title": "{commerce_name} - {product_name}",
"...": "(7개 키 생략, 총 12개)"
},
"review_settings": {
"write_deadline_days": 90,
"max_images": 5,
"max_image_size_mb": 10
},
"inquiry": {
"board_slug": null
},
"notifications": {
"channels": [
{
"id": "mail",
"is_active": true,
"sort_order": 1
},
{
"id": "database",
"is_active": true,
"sort_order": 2
}
]
},
"mileage": {
"enabled": false,
"default_earn_rate": 1,
"earn_trigger": "confirmed",
"earn_delay_days": "0",
"currency_rules": [
{
"currency_code": "KRW",
"point_value": 1,
"min_use_amount": 1000,
"use_unit": 10,
"max_use_type": "fixed",
"max_use_percent": 30,
"max_use_value": 50000,
"earn_rounding_unit": "1",
"earn_rounding_method": "floor"
}
],
"expiry_enabled": true,
"expiry_days": 365,
"expiry_notification_enabled": true,
"expiry_notification_days_before": 7,
"notification_channels": [
"mail",
"database"
]
},
"claim": {
"refund_reasons": [
{
"id": 1,
"type": "refund",
"code": "order_mistake",
"name": {
"ko": "주문 실수",
"en": "Order Mistake"
},
"localized_name": "주문 실수",
"fault_type": "customer",
"is_user_selectable": true,
"is_active": true,
"sort_order": 0
},
{
"id": 8,
"type": "refund",
"code": "apidoc_sample",
"name": {
"ko": "API 문서 샘플 사유",
"en": "API Doc Sample Reason"
},
"localized_name": "API 문서 샘플 사유",
"fault_type": "customer",
"is_user_selectable": true,
"is_active": true,
"sort_order": 0
},
{
"id": 2,
"type": "refund",
"code": "changed_mind",
"name": {
"ko": "단순 변심",
"en": "Changed Mind"
},
"localized_name": "단순 변심",
"fault_type": "customer",
"is_user_selectable": true,
"is_active": true,
"sort_order": 1
},
{
"id": 3,
"type": "refund",
"code": "reorder_other",
"name": {
"ko": "다른 상품으로 재주문",
"en": "Reorder with Different Product"
},
"localized_name": "다른 상품으로 재주문",
"fault_type": "customer",
"is_user_selectable": true,
"is_active": true,
"sort_order": 2
},
{
"id": 4,
"type": "refund",
"code": "delayed_delivery",
"name": {
"ko": "배송 지연",
"en": "Delayed Delivery"
},
"localized_name": "배송 지연",
"fault_type": "seller",
"is_user_selectable": true,
"is_active": true,
"sort_order": 3
},
{
"id": 5,
"type": "refund",
"code": "product_info_different",
"name": {
"ko": "상품 정보 상이",
"en": "Product Info Different"
},
"localized_name": "상품 정보 상이",
"fault_type": "seller",
"is_user_selectable": true,
"is_active": true,
"sort_order": 4
},
{
"id": 6,
"type": "refund",
"code": "admin_cancel",
"name": {
"ko": "관리자 취소",
"en": "Admin Cancel"
},
"localized_name": "관리자 취소",
"fault_type": "seller",
"is_user_selectable": false,
"is_active": true,
"sort_order": 5
},
{
"id": 7,
"type": "refund",
"code": "etc",
"name": {
"ko": "기타",
"en": "Etc"
},
"localized_name": "기타",
"fault_type": "customer",
"is_user_selectable": true,
"is_active": true,
"sort_order": 6
}
]
},
"available_pg_providers": [],
"abilities": {
"can_update": true
}
}
}
```
**에러 응답**
| 상태코드 | 의미 | 발생 조건 |
| --- | --- | --- |
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
| 403 | Forbidden | 요구 권한(`sirsoft-ecommerce.settings.read`)이 없는 경우 |
<!-- @generated:end -->
**설명** 관리자가 이커머스 모듈의 전체 환경설정을 카테고리별로 묶어 한 번에 조회합니다. `permission:sirsoft-ecommerce.settings.read` 권한이 필요하며, `EcommerceSettingsService::getAllSettings()`로 JSON 설정을 읽은 뒤 DB 관리 대상(배송사·배송유형·클레임 사유·마일리지 알림 채널)과 등록된 PG 목록을 병합해 반환합니다. `basic_info`·`shipping`·`order_settings`·`claim`·`mileage` 등 관리자 설정 화면 전 탭의 초기 데이터를 이 한 응답으로 채웁니다. 응답의 `abilities.can_update` 로 수정 권한 보유 여부도 함께 내려갑니다.
### PUT /api/modules/sirsoft-ecommerce/admin/settings
<!-- @generated:start:api.modules.sirsoft-ecommerce.admin.settings.store -->
- **라우트명**: `api.modules.sirsoft-ecommerce.admin.settings.store`
- **컨트롤러**: `Modules\Sirsoft\Ecommerce\Http\Controllers\Admin\EcommerceSettingsController@store`
- **인증/권한**: `auth:sanctum` + `permission:sirsoft-ecommerce.settings.update`
**요청 파라미터**
| 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 |
| --- | --- | --- | --- | --- | --- |
| _tab | body | string | 아니오 | `basic_info`, `language_currency`, `seo`, `order_settings`, `claim`, `shipping`, `review_settings`, `notification_definitions`, `notifications`, `inquiry`, `mileage` | 저장할 설정 탭(카테고리) 지정 (탭별 부분 저장 식별용) |
| notifications | body | array | 아니오 | — | 알림 채널 설정 배열 (채널 ID·활성 여부·정렬 순서) |
| notifications.channels | body | array | 아니오 | — | 알림 채널 목록. 항목별 `id`(문자열, max 50, 필수), `is_active`(boolean, 필수), `sort_order`(integer, min 0) |
| basic_info | body | array | 아니오 | — | 쇼핑몰 기본 정보 섹션 (쇼핑몰명·라우트 경로·상호·사업자번호·주소·연락처 등) |
| basic_info.shop_name | body | string | 아니오 | max 255 | 쇼핑몰 이름 (`basic_info` 전달 시 필수) |
| basic_info.route_path | body | string | 아니오 | max 100 | 쇼핑몰 라우트 경로 (예: `shop`). `no_route: true` 이면 검증에서 제외 |
| basic_info.no_route | body | boolean | 아니오 | — | 라우트 경로를 사용하지 않음 (쇼핑몰만 단독 운영하는 경우) |
| basic_info.company_name | body | string | 아니오 | max 255 | 상호(회사명) |
| basic_info.business_number_1 | body | string | 아니오 | max 3 | 사업자등록번호 앞 3자리 |
| basic_info.business_number_2 | body | string | 아니오 | max 2 | 사업자등록번호 가운데 2자리 |
| basic_info.business_number_3 | body | string | 아니오 | max 5 | 사업자등록번호 뒤 5자리 |
| basic_info.ceo_name | body | string | 아니오 | max 100 | 대표자 이름 |
| basic_info.business_type | body | string | 아니오 | max 100 | 업태 (예: 도매, 소매) |
| basic_info.business_category | body | string | 아니오 | max 255 | 종목 (예: 전자상거래) |
| basic_info.zipcode | body | string | 아니오 | max 10 | 사업장 우편번호 |
| basic_info.base_address | body | string | 아니오 | max 500 | 사업장 기본주소 |
| basic_info.detail_address | body | string | 아니오 | max 255 | 사업장 상세주소 |
| basic_info.phone_1 | body | string | 아니오 | max 4 | 대표 전화번호 첫 번째 마디 |
| basic_info.phone_2 | body | string | 아니오 | max 4 | 대표 전화번호 두 번째 마디 |
| basic_info.phone_3 | body | string | 아니오 | max 4 | 대표 전화번호 세 번째 마디 |
| basic_info.fax_1 | body | string | 아니오 | max 4 | 대표 팩스번호 첫 번째 마디 |
| basic_info.fax_2 | body | string | 아니오 | max 4 | 대표 팩스번호 두 번째 마디 |
| basic_info.fax_3 | body | string | 아니오 | max 4 | 대표 팩스번호 세 번째 마디 |
| basic_info.email_id | body | string | 아니오 | max 100 | 대표 E-mail 의 아이디 부분 (`@` 앞) |
| basic_info.email_domain | body | string | 아니오 | max 100 | 대표 E-mail 의 도메인 부분 (`@` 뒤) |
| basic_info.privacy_officer | body | string | 아니오 | max 100 | 개인정보 보호책임자 이름 |
| basic_info.privacy_officer_email | body | email | 아니오 | max 255 | 개인정보 보호책임자 E-mail |
| basic_info.mail_order_number | body | string | 아니오 | max 100 | 통신판매업 신고번호 |
| basic_info.telecom_number | body | string | 아니오 | max 100 | 부가통신 사업자번호 |
| language_currency | body | array | 아니오 | — | 통화 설정 섹션 (기본 통화·통화 목록: 코드·다국어명·환율·반올림 규칙·통화별 로케일) |
| language_currency.default_currency | body | string | 아니오 | max 10 | 쇼핑몰 기본(base) 통화 코드. 상품/주문이 1건이라도 생성된 뒤에는 변경 불가 |
| language_currency.currencies | body | array | 아니오 | — | 등록 통화 목록. 항목별 `code`(ISO 4217 3자리 대문자, 필수)·`name`(다국어 배열, 필수)·`symbol`·`exchange_rate`·`base_unit`·`rounding_unit`·`rounding_method`(`floor`\|`round`\|`ceil`)·`decimal_places`·`is_default`·`locales` |
| seo | body | array | 아니오 | — | SEO 메타 설정 섹션 (페이지 유형별 메타 타이틀/설명·SEO 활성 토글) |
| seo.meta_category_title | body | string | 아니오 | max 500 | 카테고리 페이지 메타 Title (`{commerce_name}`·`{category_name}` 등 변수 사용 가능) |
| seo.meta_category_description | body | string | 아니오 | max 1000 | 카테고리 페이지 메타 Description |
| seo.meta_search_title | body | string | 아니오 | max 500 | 검색 결과 페이지 메타 Title (`{keyword_name}` 변수 사용 가능) |
| seo.meta_search_description | body | string | 아니오 | max 1000 | 검색 결과 페이지 메타 Description |
| seo.meta_product_title | body | string | 아니오 | max 500 | 상품 상세 페이지 메타 Title (`{product_name}` 변수 사용 가능) |
| seo.meta_product_description | body | string | 아니오 | max 1000 | 상품 상세 페이지 메타 Description |
| seo.meta_shop_index_title | body | string | 아니오 | max 500 | 쇼핑몰 메인 페이지 메타 Title |
| seo.meta_shop_index_description | body | string | 아니오 | max 1000 | 쇼핑몰 메인 페이지 메타 Description |
| seo.seo_category | body | boolean | 아니오 | — | 카테고리 페이지에 검색엔진 친화적 페이지 제공 여부 |
| seo.seo_search_result | body | boolean | 아니오 | — | 검색결과 페이지에 검색엔진 친화적 페이지 제공 여부 |
| seo.seo_product_detail | body | boolean | 아니오 | — | 상품 상세페이지에 검색엔진 친화적 페이지 제공 여부 |
| seo.seo_shop_index | body | boolean | 아니오 | — | 쇼핑몰 메인 페이지에 검색엔진 친화적 페이지 제공 여부 |
| inquiry | body | array | 아니오 | — | 문의 연동 설정 섹션 (문의 게시판 slug) |
| inquiry.board_slug | body | string | 아니오 | max 255 | 상품 1:1 문의를 연동할 게시판 slug (게시판 모듈 활성화 시에만 선택 가능) |
| order_settings | body | array | 아니오 | — | 주문/결제 설정 섹션 (기본 PG·결제수단·은행/무통장 계좌·자동취소·장바구니 만료 등) |
| order_settings.default_pg_provider | body | string | 아니오 | max 50 | 결제 처리에 사용할 기본 PG사 ID (결제수단별 `pg_provider` 미지정 시 적용) |
| order_settings.cash_receipt_provider | body | string | 아니오 | max 50 | 현금영수증 발급 프로바이더 ID. 결제 PG 와 독립 선택하며 빈 문자열이면 미사용 |
| order_settings.cash_receipt_self_issue | body | boolean | 아니오 | — | 현금영수증 자진발급 사용 (구매자가 신청하지 않은 무통장 입금 건도 자동 발급) |
| order_settings.shipping_fee_tax_policy | body | string | 아니오 | `proportional`, `taxable`, `follow_main_item` | 현금영수증의 배송비 과세 방식 (안분 / 전액 과세 / 주된 재화 기준) |
| order_settings.payment_methods | body | array | 아니오 | — | 결제수단 목록. 항목별 `id`(필수)·`pg_provider`·`sort_order`(min 1)·`is_active`·`min_order_amount`(min 0)·`stock_deduction_timing`(`order_placed`\|`payment_complete`\|`none`)·`mileage_deduction_timing`(`order_placed`\|`payment_complete`). 최소 1개는 활성이어야 하며, PG 가 필요한 결제수단을 활성화하려면 PG사 지정 필수 |
| order_settings.banks | body | array | 아니오 | — | 무통장입금용 은행 목록. 항목별 `code`(max 10, 필수)·`name`(다국어 배열, 현재 로케일 필수) |
| order_settings.bank_accounts | body | array | 아니오 | — | 무통장 입금 계좌 목록. 항목별 `bank_code`·`account_number`·`account_holder`(모두 필수)·`is_active`·`is_default`. 계좌가 있으면 최소 1건은 사용+기본 상태여야 함 |
| order_settings.auto_cancel_expired | body | boolean | 아니오 | — | 입금대기 상태 주문의 자동취소 사용 여부 |
| order_settings.auto_cancel_days | body | integer | 아니오 | min 0, max 30 | 자동취소 기한(일). 주문일 포함 이 일수 경과 시 입금대기 주문을 자동 취소 |
| order_settings.cart_expiry_days | body | integer | 아니오 | min 1, max 365 | 장바구니 보관기간(일). 경과 시 담긴 상품 자동 삭제 |
| order_settings.stock_restore_on_cancel | body | boolean | 아니오 | — | 주문 취소 시 차감된 재고 자동 복구 여부 (반품/교환에도 적용) |
| order_settings.confirmable_statuses | body | array | 아니오 | — | 사용자가 구매확정할 수 있는 주문 옵션 상태 목록 (`payment_complete`, `shipping_hold`, `preparing`, `shipping_ready`, `shipping`, `delivered` 중 선택) |
| claim | body | array | 아니오 | — | 클레임 설정 섹션 (환불 사유 목록, DB 동기화 대상으로 분리 저장) |
| claim.refund_reasons | body | array | 아니오 | — | 환불 사유 목록. 항목별 `id`·`code`(소문자·언더스코어, 필수)·`name`(다국어 배열, 현재 로케일 필수)·`fault_type`(`customer`\|`seller`\|`carrier`, 필수)·`is_user_selectable`·`is_active`·`sort_order`. 코드 중복 불가 |
| review_settings | body | array | 아니오 | — | 리뷰 정책 섹션 (작성 기한일·이미지 최대 개수·이미지 최대 용량 MB) |
| review_settings.write_deadline_days | body | integer | 아니오 | min 1, max 365 | 구매 확정 후 리뷰를 작성할 수 있는 기간(일) |
| review_settings.max_images | body | integer | 아니오 | min 0, max 20 | 리뷰 1건당 첨부 가능한 이미지 수 (0 = 이미지 첨부 불가) |
| review_settings.max_image_size_mb | body | integer | 아니오 | min 1, max 50 | 리뷰 이미지 파일 1개의 최대 용량(MB) |
| mileage | body | array | 아니오 | — | 마일리지 설정 섹션 (사용 여부·기본 적립률·적립 트리거·통화별 규칙·소멸/소멸 알림) |
| mileage.enabled | body | boolean | 아니오 | — | 마일리지 적립·사용 기능 사용 여부 (`mileage` 전달 시 필수) |
| mileage.default_earn_rate | body | number | 아니오 | min 0, max 100 | 기본 적립률(%). 상품 등록 시 기본값으로 사용되며, `enabled: true` 인 경우 0 초과여야 함 |
| mileage.earn_trigger | body | string | 아니오 | `delivered`, `confirmed` | 마일리지 적립 시점 (배송완료 / 구매확정) |
| mileage.earn_delay_days | body | integer | 아니오 | min 0, max 365 | 적립 시점 이후 실제 적립까지의 지연일 |
| mileage.currency_rules | body | array | 아니오 | — | 통화별 사용 규칙. 항목별 `currency_code`(ISO 4217, 필수)·`point_value`(1점당 금액, min 0.001)·`min_use_amount`·`use_unit`(min 1)·`max_use_type`(`percent`\|`fixed`)·`max_use_percent`·`max_use_value`. 첫 행은 기본 통화여야 하고, 등록 통화만 허용하며 중복 불가 |
| mileage.expiry_enabled | body | boolean | 아니오 | — | 마일리지 유효기간 사용 여부 (경과 시 미사용 마일리지 자동 소멸) |
| mileage.expiry_days | body | integer | 아니오 | min 1, max 3650 | 마일리지 유효기간(일) |
| mileage.expiry_notification_enabled | body | boolean | 아니오 | — | 소멸 예정 알림 발송 여부 |
| mileage.expiry_notification_days_before | body | integer | 아니오 | min 1, max 365 | 유효기간 만료 며칠 전에 소멸 예정 알림을 발송할지 |
| shipping | body | array | 아니오 | — | 배송 설정 섹션 (기본 국가·배송 가능 국가·무료배송·배송사(carriers)·배송유형(types) — carriers/types는 DB 동기화 대상으로 분리 저장) |
| shipping.default_country | body | string | 아니오 | max 10 | 기본 배송 국가 코드. 새 배송정책 등록 시 기본으로 추가되며, `available_countries` 에 존재해야 함 |
| shipping.available_countries | body | array | 아니오 | — | 배송 가능 국가 목록. 항목별 `code`(max 10, 필수)·`name`(다국어 배열, 필수)·`is_active`. 코드 중복 불가 |
| shipping.international_shipping_enabled | body | boolean | 아니오 | — | 해외배송 기능 사용 여부 |
| shipping.free_shipping_threshold | body | integer | 아니오 | min 0 | 무료배송 기준 금액 (이 금액 이상 주문 시 무료배송) |
| shipping.free_shipping_enabled | body | boolean | 아니오 | — | 무료배송 기준금액 적용 여부 |
| shipping.address_validation_enabled | body | boolean | 아니오 | — | 주소 검증(주소찾기 API) 사용 여부 |
| shipping.address_api_provider | body | string | 아니오 | max 50 | 주소찾기 API 제공자 식별자 (기본값 `kakao`) |
| shipping.carriers | body | array | 아니오 | — | 배송사 목록(DB 동기화 대상). 항목별 `id`·`code`(소문자/숫자/하이픈, 필수)·`name`(다국어 배열, 현재 로케일 필수)·`type`(`domestic`\|`international`, 필수)·`tracking_url`(`{tracking_number}` 치환)·`is_active`·`sort_order`. 코드 중복 불가 |
| shipping.types | body | array | 아니오 | — | 배송유형 목록(DB 동기화 대상). 항목별 `id`·`code`(소문자/숫자/하이픈, 필수)·`name`(다국어 배열, 현재 로케일 필수)·`category`(`domestic`\|`international`\|`other`, 필수)·`is_active`·`sort_order`. 코드 중복 불가 |
**요청 예시**
```http
PUT /api/modules/sirsoft-ecommerce/admin/settings HTTP/1.1
Host: api.example.com
Accept: application/json
Authorization: Bearer {YOUR_TOKEN}
Content-Type: application/json
{
"_tab": "basic_info",
"notifications": [
"예시값"
],
"notifications.channels": [
"예시값"
],
"basic_info": [
"예시값"
],
"basic_info.shop_name": "예시 이름",
"basic_info.route_path": "예시값",
"basic_info.no_route": true,
"basic_info.company_name": "예시 이름",
"basic_info.business_number_1": "예시값",
"basic_info.business_number_2": "예시값",
"basic_info.business_number_3": "예시값",
"basic_info.ceo_name": "예시 이름",
"basic_info.business_type": "예시값",
"basic_info.business_category": "예시값",
"basic_info.zipcode": "06234",
"basic_info.base_address": "서울특별시 강남구 테헤란로 1",
"basic_info.detail_address": "서울특별시 강남구 테헤란로 1",
"basic_info.phone_1": "010-1234-5678",
"basic_info.phone_2": "010-1234-5678",
"basic_info.phone_3": "010-1234-5678",
"basic_info.fax_1": "예시값",
"basic_info.fax_2": "예시값",
"basic_info.fax_3": "예시값",
"basic_info.email_id": "user@example.com",
"basic_info.email_domain": "user@example.com",
"basic_info.privacy_officer": "예시값",
"basic_info.privacy_officer_email": "user@example.com",
"basic_info.mail_order_number": "예시값",
"basic_info.telecom_number": "예시값",
"language_currency": [
"예시값"
],
"language_currency.default_currency": "예시값",
"language_currency.currencies": [
"예시값"
],
"seo": [
"예시값"
],
"seo.meta_category_title": "예시 제목",
"seo.meta_category_description": "예시 내용입니다.",
"seo.meta_search_title": "예시 제목",
"seo.meta_search_description": "예시 내용입니다.",
"seo.meta_product_title": "예시 제목",
"seo.meta_product_description": "예시 내용입니다.",
"seo.meta_shop_index_title": "예시 제목",
"seo.meta_shop_index_description": "예시 내용입니다.",
"seo.seo_category": true,
"seo.seo_search_result": true,
"seo.seo_product_detail": true,
"seo.seo_shop_index": true,
"inquiry": [
"예시값"
],
"inquiry.board_slug": "example-key",
"order_settings": [
"예시값"
],
"order_settings.default_pg_provider": "예시값",
"order_settings.cash_receipt_provider": "예시값",
"order_settings.cash_receipt_self_issue": true,
"order_settings.shipping_fee_tax_policy": "proportional",
"order_settings.payment_methods": [
"예시값"
],
"order_settings.banks": [
"예시값"
],
"order_settings.bank_accounts": [
"예시값"
],
"order_settings.auto_cancel_expired": true,
"order_settings.auto_cancel_days": 1,
"order_settings.cart_expiry_days": 1,
"order_settings.stock_restore_on_cancel": true,
"order_settings.confirmable_statuses": [
"예시값"
],
"claim": [
"예시값"
],
"claim.refund_reasons": [
"예시값"
],
"review_settings": [
"예시값"
],
"review_settings.write_deadline_days": 1,
"review_settings.max_images": 1,
"review_settings.max_image_size_mb": 1,
"mileage": [
"예시값"
],
"mileage.enabled": true,
"mileage.default_earn_rate": 1,
"mileage.earn_trigger": "delivered",
"mileage.earn_delay_days": 1,
"mileage.currency_rules": [
"예시값"
],
"mileage.expiry_enabled": true,
"mileage.expiry_days": 1,
"mileage.expiry_notification_enabled": true,
"mileage.expiry_notification_days_before": 1,
"shipping": [
"예시값"
],
"shipping.default_country": "KR",
"shipping.available_countries": [
"KR"
],
"shipping.international_shipping_enabled": true,
"shipping.free_shipping_threshold": 1,
"shipping.free_shipping_enabled": true,
"shipping.address_validation_enabled": true,
"shipping.address_api_provider": "서울특별시 강남구 테헤란로 1",
"shipping.carriers": [
"예시값"
],
"shipping.types": [
"예시값"
]
}
```
**응답 필드** (`data` 내부)
_단건 응답: `data` 객체의 필드._
| 필드 | 타입 | 실측 예시값 | 용도/설명 |
| --- | --- | --- | --- |
| basic_info | object | `{"shop_name":"","route_path":"shop","no_route":false,"com…` | 쇼핑몰 기본 정보 (쇼핑몰명·라우트 경로·상호·사업자번호·주소·연락처·이메일 등) |
| language_currency | object | `{"default_currency":"KRW","currencies":[{"code":"KRW","na…` | 통화 설정 (기본 통화 + 등록 통화 목록: 코드·다국어명·환율·기호·국기·반올림 규칙) |
| order_settings | object | `{"default_pg_provider":null,"cash_receipt_provider":"toss…` | 주문/결제 설정 (기본 PG·병합된 결제수단·은행/무통장 계좌·자동취소·장바구니 만료·현금영수증 발급 제공자·자진발급·배송비 과세 방식 등) |
| shipping | object | `{"default_country":"KR","available_countries":[{"code":"K…` | 배송 설정 (기본 국가·배송 가능 국가·무료배송·DB 관리 배송사(carriers)·배송유형(types)·계산 API 후보 필드 포함) |
| seo | object | `{"meta_category_title":"{commerce_name} - {category_name}…` | SEO 메타 설정 (카테고리·검색·상품·쇼핑몰 인덱스별 메타 타이틀/설명 및 SEO 활성 토글) |
| review_settings | object | `{"write_deadline_days":90,"max_images":5,"max_image_size_…` | 리뷰 정책 (작성 기한일·이미지 최대 개수·이미지 최대 용량 MB) |
| inquiry | object | `{"board_slug":"inquiry"}` | 문의 연동 설정 (문의 게시판 slug) |
| notifications | object | `{"channels":[{"id":"mail","is_active":true,"sort_order":1…` | 알림 채널 설정 (채널 ID·활성 여부·정렬 순서) |
| mileage | object | `{"enabled":false,"default_earn_rate":1,"earn_trigger":"co…` | 마일리지 설정 (사용 여부·기본 적립률·적립 트리거·통화별 규칙·소멸/소멸 알림·실제 활성 알림 채널 포함) |
| claim | object | `{"refund_reasons":[{"id":1,"type":"refund","code":"order_…` | 클레임 설정 (DB 관리 대상인 환불 사유 목록: 코드·다국어명·귀책 유형·노출/활성 여부) |
| available_pg_providers | array | `[{"id":"kginicis","name_key":"sirsoft-pay_kginicis::provi…` | 설치된 PG 플러그인이 훅으로 등록한 PG 제공자 목록 (id·name_key·지원 결제수단) |
| available_cash_receipt_providers | array | `[]` | 설치된 플러그인이 훅으로 등록한 현금영수증 발급 제공자 목록 (id·name_key — 미등록 시 빈 배열이며 신청 폼이 노출되지 않음) |
**응답 예시**
```http
HTTP/1.1 200
```
```json
{
"success": true,
"message": "설정이 저장되었습니다.",
"data": {
"basic_info": {
"shop_name": "111",
"route_path": "shop",
"no_route": false,
"company_name": null,
"business_number": "",
"...": "(13개 키 생략, 총 18개)"
},
"language_currency": {
"default_currency": "JPY",
"currencies": [
{
"code": "KRW",
"name": {
"ko": "KRW (원)",
"en": "KRW (Won)"
},
"symbol": "₩",
"exchange_rate": null,
"base_unit": 1000,
"...": "(6개 키 생략, 총 11개)"
},
{
"code": "USD",
"name": {
"en": "Dollar"
},
"is_default": false,
"decimal_places": 2,
"base_unit": 1,
"...": "(3개 키 생략, 총 8개)"
},
{
"code": "JPY",
"name": {
"ja": "円"
},
"is_default": true,
"decimal_places": 0,
"base_unit": 100,
"...": "(3개 키 생략, 총 8개)"
},
{
"code": "CNY",
"name": {
"ko": "CNY (위안)",
"en": "CNY (Yuan)"
},
"symbol": "元",
"exchange_rate": 5.8,
"base_unit": 1,
"...": "(6개 키 생략, 총 11개)"
},
{
"code": "EUR",
"name": {
"ko": "EUR (유로)",
"en": "EUR (Euro)"
},
"symbol": "€",
"exchange_rate": 0.78,
"base_unit": 1,
"...": "(6개 키 생략, 총 11개)"
}
]
},
"order_settings": {
"default_pg_provider": null,
"payment_methods": [
{
"id": "card",
"pg_provider": null,
"sort_order": 1,
"is_active": false,
"min_order_amount": 0,
"...": "(10개 키 생략, 총 15개)"
},
{
"id": "dbank",
"pg_provider": null,
"sort_order": 1,
"is_active": true,
"min_order_amount": 0,
"...": "(10개 키 생략, 총 15개)"
},
{
"id": "vbank",
"pg_provider": null,
"sort_order": 2,
"is_active": false,
"min_order_amount": 0,
"...": "(10개 키 생략, 총 15개)"
},
{
"id": "point",
"pg_provider": null,
"sort_order": 2,
"is_active": true,
"min_order_amount": 0,
"...": "(10개 키 생략, 총 15개)"
},
{
"id": "deposit",
"pg_provider": null,
"sort_order": 3,
"is_active": true,
"min_order_amount": 0,
"...": "(10개 키 생략, 총 15개)"
},
"... (총 25건 중 5건 표시)"
],
"banks": [],
"bank_accounts": [
{
"bank_code": "004",
"account_number": "",
"account_holder": "",
"is_active": false,
"is_default": false
}
],
"auto_cancel_expired": true,
"...": "(5개 키 생략, 총 10개)"
},
"shipping": {
"default_country": "KR",
"available_countries": [
{
"code": "KR",
"name": {
"ko": "대한민국",
"en": "South Korea"
},
"is_active": true
},
{
"code": "US",
"name": {
"ko": "미국",
"en": "United States"
},
"is_active": true
}
],
"international_shipping_enabled": true,
"free_shipping_threshold": 50000,
"free_shipping_enabled": true,
"...": "(8개 키 생략, 총 13개)"
},
"seo": {
"meta_category_title": "{commerce_name} - {category_name}",
"meta_category_description": "",
"meta_search_title": "{commerce_name} - {keyword_name}",
"meta_search_description": "",
"meta_product_title": "{commerce_name} - {product_name}",
"...": "(7개 키 생략, 총 12개)"
},
"review_settings": {
"write_deadline_days": 90,
"max_images": 5,
"max_image_size_mb": 10
},
"inquiry": {
"board_slug": null
},
"notifications": {
"channels": [
{
"id": "mail",
"is_active": true,
"sort_order": 1
},
{
"id": "database",
"is_active": true,
"sort_order": 2
}
]
},
"mileage": {
"enabled": false,
"default_earn_rate": 1,
"earn_trigger": "confirmed",
"earn_delay_days": "0",
"currency_rules": [
{
"currency_code": "KRW",
"point_value": 1,
"min_use_amount": 1000,
"use_unit": 10,
"max_use_type": "fixed",
"max_use_percent": 30,
"max_use_value": 50000,
"earn_rounding_unit": "1",
"earn_rounding_method": "floor"
}
],
"expiry_enabled": true,
"expiry_days": 365,
"expiry_notification_enabled": true,
"expiry_notification_days_before": 7,
"notification_channels": [
"mail",
"database"
]
},
"claim": {
"refund_reasons": [
{
"id": 1,
"type": "refund",
"code": "order_mistake",
"name": {
"ko": "주문 실수",
"en": "Order Mistake"
},
"localized_name": "주문 실수",
"fault_type": "customer",
"is_user_selectable": true,
"is_active": true,
"sort_order": 0
},
{
"id": 8,
"type": "refund",
"code": "apidoc_sample",
"name": {
"ko": "API 문서 샘플 사유",
"en": "API Doc Sample Reason"
},
"localized_name": "API 문서 샘플 사유",
"fault_type": "customer",
"is_user_selectable": true,
"is_active": true,
"sort_order": 0
},
{
"id": 2,
"type": "refund",
"code": "changed_mind",
"name": {
"ko": "단순 변심",
"en": "Changed Mind"
},
"localized_name": "단순 변심",
"fault_type": "customer",
"is_user_selectable": true,
"is_active": true,
"sort_order": 1
},
{
"id": 3,
"type": "refund",
"code": "reorder_other",
"name": {
"ko": "다른 상품으로 재주문",
"en": "Reorder with Different Product"
},
"localized_name": "다른 상품으로 재주문",
"fault_type": "customer",
"is_user_selectable": true,
"is_active": true,
"sort_order": 2
},
{
"id": 4,
"type": "refund",
"code": "delayed_delivery",
"name": {
"ko": "배송 지연",
"en": "Delayed Delivery"
},
"localized_name": "배송 지연",
"fault_type": "seller",
"is_user_selectable": true,
"is_active": true,
"sort_order": 3
},
{
"id": 5,
"type": "refund",
"code": "product_info_different",
"name": {
"ko": "상품 정보 상이",
"en": "Product Info Different"
},
"localized_name": "상품 정보 상이",
"fault_type": "seller",
"is_user_selectable": true,
"is_active": true,
"sort_order": 4
},
{
"id": 6,
"type": "refund",
"code": "admin_cancel",
"name": {
"ko": "관리자 취소",
"en": "Admin Cancel"
},
"localized_name": "관리자 취소",
"fault_type": "seller",
"is_user_selectable": false,
"is_active": true,
"sort_order": 5
},
{
"id": 7,
"type": "refund",
"code": "etc",
"name": {
"ko": "기타",
"en": "Etc"
},
"localized_name": "기타",
"fault_type": "customer",
"is_user_selectable": true,
"is_active": true,
"sort_order": 6
}
]
},
"available_pg_providers": []
}
}
```
**에러 응답**
| 상태코드 | 의미 | 발생 조건 |
| --- | --- | --- |
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
| 403 | Forbidden | 요구 권한(`sirsoft-ecommerce.settings.update`)이 없는 경우 |
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
<!-- @generated:end -->
**설명** 관리자가 이커머스 환경설정을 저장합니다. `permission:sirsoft-ecommerce.settings.update` 권한이 필요하며, `_tab` 으로 저장할 카테고리를 지정하고 각 섹션(`basic_info`·`shipping`·`claim` 등)을 배열로 전달합니다. `EcommerceSettingsService::saveSettings()`가 JSON 설정을 저장하되, DB 관리 대상인 `shipping.carriers`·`shipping.types`·`claim.refund_reasons` 는 분리해 각 Service 의 sync 메서드로 동기화합니다. 저장 성공 시 `sirsoft-ecommerce.settings.after_save` 훅을 발화하고, 관리자 UI 상태 갱신을 위해 병합된 전체 설정을 다시 반환합니다.
**숫자 필드 정규화** 검증 규칙에 `integer` / `numeric` 이 선언된 모든 필드는 검증 직전에 숫자 타입으로 캐스트되어 저장됩니다(중첩 배열의 와일드카드 경로 포함 — 예: `mileage.currency_rules.*.use_unit`). HTML `number` 입력의 값은 문자열(`"5"`)로 전송되고 Laravel 의 `integer` 규칙은 숫자 문자열을 통과시키되 캐스트하지 않으므로, 정규화가 없으면 문자열이 그대로 설정 파일에 영속되어 이후 날짜/수치 연산에서 타입 오류를 유발합니다.
- 정규화 대상: 정수 표기 문자열(`"5"`, `"05"`) → `int`, 소수 표기 문자열(`"1.5"`) → `float`(단, `numeric` 필드에 한함)
- 정규화 제외: 비숫자 문자열(`"abc"`), 빈 문자열, `null`, 불리언, 그리고 `integer` 필드에 전달된 소수 문자열(`"3.7"`) — 검증을 느슨하게 만들지 않기 위해 캐스트하지 않고 그대로 검증 실패시킵니다
- 조회(`GET`) 응답도 `defaults.json` 스키마의 숫자 타입으로 정규화되어 반환되므로, 저장 시점의 표현 형태와 무관하게 숫자로 수신됩니다
**마일리지 적립 절사 기준** `mileage.currency_rules.*` 의 두 필드가 적립 포인트 산출·안분의 절사 기준을 정합니다.
| 필드 | 타입 | 허용값 | 기본값 | 설명 |
| --- | --- | --- | --- | --- |
| `earn_rounding_unit` | string | `1` · `10` · `100` | `1` | 적립 포인트를 맞출 단위(점). 마일리지는 원장에 정수로 확정되므로 소수 단위는 허용하지 않습니다 |
| `earn_rounding_method` | string | `floor` · `round` · `ceil` | `floor` | 단위에 맞출 방식(버림 / 반올림 / 올림) |
- 적용 지점: 옵션 정액 적립 · 옵션 정률 적립 · 기본 적립률 세 갈래 전부, 그리고 부분취소로 주문옵션이 분할될 때의 적립액 안분
- 기준 통화 선택: 마일리지는 표시 통화가 아니라 기준 통화(`currency_snapshot.base_currency`)로 적립·정산되므로, 그 통화의 규칙을 사용하고 없으면 첫 규칙(기본 통화)으로 폴백합니다
- 주문 시점 고정: 주문 생성 시 `mileage_policy_snapshot.rule` 에 함께 기록되며, 부분취소·추가결제 재계산은 현재 설정이 아니라 이 스냅샷을 사용합니다. 그렇지 않으면 이후 설정 변경이 과거 주문에 소급돼, 취소하지 않은 잔여분의 적립액이 취소 처리만으로 달라집니다
- 값이 없는 경우(이 필드 도입 이전 설치본·주문): 기본값 `1` / `floor` 로 해석되며 이는 도입 이전 동작과 동일한 금액을 산출합니다
- 통화 환산 절사(`language_currency.currencies.*.rounding_unit` / `rounding_method`)와는 별개입니다 — 그쪽은 외화 표시 환산에만 적용되어 기본 통화에는 적용되지 않으므로 적립 규칙으로 쓸 수 없습니다
### PUT /api/modules/sirsoft-ecommerce/admin/settings/banks
<!-- @generated:start:api.modules.sirsoft-ecommerce.admin.settings.store-banks -->
- **라우트명**: `api.modules.sirsoft-ecommerce.admin.settings.store-banks`
- **컨트롤러**: `Modules\Sirsoft\Ecommerce\Http\Controllers\Admin\EcommerceSettingsController@storeBanks`
- **인증/권한**: `auth:sanctum` + `permission:sirsoft-ecommerce.settings.update`
**요청 파라미터**
| 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 |
| --- | --- | --- | --- | --- | --- |
| banks | body | array | 아니오 | — | 무통장입금용 은행 목록 (은행 코드·다국어 은행명) |
**요청 예시**
```http
PUT /api/modules/sirsoft-ecommerce/admin/settings/banks HTTP/1.1
Host: api.example.com
Accept: application/json
Authorization: Bearer {YOUR_TOKEN}
Content-Type: application/json
{
"banks": [
"예시값"
]
}
```
**응답 필드** (`data` 내부)
_단건 응답: `data` 객체의 필드._
| 필드 | 타입 | 실측 예시값 | 용도/설명 |
| --- | --- | --- | --- |
| basic_info | object | `{"shop_name":"","route_path":"shop","no_route":false,"com…` | 쇼핑몰 기본 정보 (쇼핑몰명·라우트 경로·상호·사업자번호·주소·연락처·이메일 등) |
| language_currency | object | `{"default_currency":"KRW","currencies":[{"code":"KRW","na…` | 통화 설정 (기본 통화 + 등록 통화 목록: 코드·다국어명·환율·기호·국기·반올림 규칙) |
| order_settings | object | `{"default_pg_provider":null,"cash_receipt_provider":"toss…` | 주문/결제 설정 (기본 PG·병합된 결제수단·은행/무통장 계좌·자동취소·장바구니 만료·현금영수증 발급 제공자·자진발급·배송비 과세 방식 등) |
| shipping | object | `{"default_country":"KR","available_countries":[{"code":"K…` | 배송 설정 (기본 국가·배송 가능 국가·무료배송·DB 관리 배송사(carriers)·배송유형(types)·계산 API 후보 필드 포함) |
| seo | object | `{"meta_category_title":"{commerce_name} - {category_name}…` | SEO 메타 설정 (카테고리·검색·상품·쇼핑몰 인덱스별 메타 타이틀/설명 및 SEO 활성 토글) |
| review_settings | object | `{"write_deadline_days":90,"max_images":5,"max_image_size_…` | 리뷰 정책 (작성 기한일·이미지 최대 개수·이미지 최대 용량 MB) |
| inquiry | object | `{"board_slug":"inquiry"}` | 문의 연동 설정 (문의 게시판 slug) |
| notifications | object | `{"channels":[{"id":"mail","is_active":true,"sort_order":1…` | 알림 채널 설정 (채널 ID·활성 여부·정렬 순서) |
| mileage | object | `{"enabled":false,"default_earn_rate":1,"earn_trigger":"co…` | 마일리지 설정 (사용 여부·기본 적립률·적립 트리거·통화별 규칙·소멸/소멸 알림·실제 활성 알림 채널 포함) |
**응답 예시**
```http
HTTP/1.1 200
```
```json
{
"success": true,
"message": "설정이 저장되었습니다.",
"data": {
"basic_info": {
"shop_name": "111",
"route_path": "shop",
"no_route": false,
"company_name": null,
"business_number": "",
"...": "(13개 키 생략, 총 18개)"
},
"language_currency": {
"default_currency": "JPY",
"currencies": [
{
"code": "KRW",
"name": {
"ko": "KRW (원)",
"en": "KRW (Won)"
},
"symbol": "₩",
"exchange_rate": null,
"base_unit": 1000,
"...": "(6개 키 생략, 총 11개)"
},
{
"code": "USD",
"name": {
"en": "Dollar"
},
"is_default": false,
"decimal_places": 2,
"base_unit": 1,
"...": "(3개 키 생략, 총 8개)"
},
{
"code": "JPY",
"name": {
"ja": "円"
},
"is_default": true,
"decimal_places": 0,
"base_unit": 100,
"...": "(3개 키 생략, 총 8개)"
},
{
"code": "CNY",
"name": {
"ko": "CNY (위안)",
"en": "CNY (Yuan)"
},
"symbol": "元",
"exchange_rate": 5.8,
"base_unit": 1,
"...": "(6개 키 생략, 총 11개)"
},
{
"code": "EUR",
"name": {
"ko": "EUR (유로)",
"en": "EUR (Euro)"
},
"symbol": "€",
"exchange_rate": 0.78,
"base_unit": 1,
"...": "(6개 키 생략, 총 11개)"
}
]
},
"order_settings": {
"default_pg_provider": null,
"payment_methods": [
{
"id": "card",
"pg_provider": null,
"sort_order": 1,
"is_active": false,
"min_order_amount": 0,
"...": "(10개 키 생략, 총 15개)"
},
{
"id": "dbank",
"pg_provider": null,
"sort_order": 1,
"is_active": true,
"min_order_amount": 0,
"...": "(10개 키 생략, 총 15개)"
},
{
"id": "vbank",
"pg_provider": null,
"sort_order": 2,
"is_active": false,
"min_order_amount": 0,
"...": "(10개 키 생략, 총 15개)"
},
{
"id": "point",
"pg_provider": null,
"sort_order": 2,
"is_active": true,
"min_order_amount": 0,
"...": "(10개 키 생략, 총 15개)"
},
{
"id": "deposit",
"pg_provider": null,
"sort_order": 3,
"is_active": true,
"min_order_amount": 0,
"...": "(10개 키 생략, 총 15개)"
},
"... (총 25건 중 5건 표시)"
],
"banks": [],
"bank_accounts": [
{
"bank_code": "004",
"account_number": "",
"account_holder": "",
"is_active": false,
"is_default": false
}
],
"auto_cancel_expired": true,
"...": "(5개 키 생략, 총 10개)"
},
"shipping": {
"default_country": "KR",
"available_countries": [
{
"code": "KR",
"name": {
"ko": "대한민국",
"en": "South Korea"
},
"is_active": true
},
{
"code": "US",
"name": {
"ko": "미국",
"en": "United States"
},
"is_active": true
}
],
"international_shipping_enabled": true,
"free_shipping_threshold": 50000,
"free_shipping_enabled": true,
"...": "(2개 키 생략, 총 7개)"
},
"seo": {
"meta_category_title": "{commerce_name} - {category_name}",
"meta_category_description": "",
"meta_search_title": "{commerce_name} - {keyword_name}",
"meta_search_description": "",
"meta_product_title": "{commerce_name} - {product_name}",
"...": "(7개 키 생략, 총 12개)"
},
"review_settings": {
"write_deadline_days": 90,
"max_images": 5,
"max_image_size_mb": 10
},
"inquiry": {
"board_slug": null
},
"notifications": {
"channels": [
{
"id": "mail",
"is_active": true,
"sort_order": 1
},
{
"id": "database",
"is_active": true,
"sort_order": 2
}
]
},
"mileage": {
"enabled": false,
"default_earn_rate": 1,
"earn_trigger": "confirmed",
"earn_delay_days": "0",
"currency_rules": [
{
"currency_code": "KRW",
"point_value": 1,
"min_use_amount": 1000,
"use_unit": 10,
"max_use_type": "fixed",
"max_use_percent": 30,
"max_use_value": 50000,
"earn_rounding_unit": "1",
"earn_rounding_method": "floor"
}
],
"expiry_enabled": true,
"expiry_days": 365,
"expiry_notification_enabled": true,
"expiry_notification_days_before": 7
}
}
}
```
**에러 응답**
| 상태코드 | 의미 | 발생 조건 |
| --- | --- | --- |
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
| 403 | Forbidden | 요구 권한(`sirsoft-ecommerce.settings.update`)이 없는 경우 |
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
<!-- @generated:end -->
**설명** 관리자가 무통장입금용 은행 목록만 별도로 저장합니다. `permission:sirsoft-ecommerce.settings.update` 권한이 필요하며, `banks` 배열을 받아 `EcommerceSettingsService::saveBanks()`가 저장합니다. 전체 설정 저장(`store`)과 분리된 전용 엔드포인트로, 결제 설정 화면에서 은행 목록만 관리할 때 사용합니다. 저장 성공 시 갱신된 전체 설정을 반환합니다.
### POST /api/modules/sirsoft-ecommerce/admin/settings/clear-cache
<!-- @generated:start:api.modules.sirsoft-ecommerce.admin.settings.clear-cache -->
- **라우트명**: `api.modules.sirsoft-ecommerce.admin.settings.clear-cache`
- **컨트롤러**: `Modules\Sirsoft\Ecommerce\Http\Controllers\Admin\EcommerceSettingsController@clearCache`
- **인증/권한**: `auth:sanctum` + `permission:sirsoft-ecommerce.settings.update`
**요청 파라미터**
_요청 파라미터 없음._
**요청 예시**
```http
POST /api/modules/sirsoft-ecommerce/admin/settings/clear-cache HTTP/1.1
Host: api.example.com
Accept: application/json
Authorization: Bearer {YOUR_TOKEN}
```
**응답 필드** (`data` 내부)
_단건 응답: `data` 객체의 필드._
| 필드 | 타입 | 실측 예시값 | 용도/설명 |
| --- | --- | --- | --- |
| cleared | boolean | `true` | 이커머스 설정 캐시 + SEO 렌더 캐시 초기화 성공 여부 (성공 시 항상 `true`) |
**응답 예시**
```http
HTTP/1.1 200
```
```json
{
"success": true,
"message": "sirsoft-ecommerce::messages.settings.cache_clear_success",
"data": {
"cleared": true
}
}
```
**에러 응답**
| 상태코드 | 의미 | 발생 조건 |
| --- | --- | --- |
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
| 403 | Forbidden | 요구 권한(`sirsoft-ecommerce.settings.update`)이 없는 경우 |
<!-- @generated:end -->
**설명** 관리자가 이커머스 설정 캐시와 SEO 렌더 캐시를 초기화합니다. `permission:sirsoft-ecommerce.settings.update` 권한이 필요하며, `EcommerceSettingsService::clearCache()`로 설정 캐시를 비우고 `SeoCacheManagerInterface::clearAll()`로 SEO 페이지 캐시까지 전부 삭제합니다. 설정 변경이 화면에 즉시 반영되지 않을 때 캐시를 강제로 비우는 용도로, 성공 시 `{cleared: true}` 를 반환합니다.
### GET /api/modules/sirsoft-ecommerce/admin/settings/seo-cache-info
<!-- @generated:start:api.modules.sirsoft-ecommerce.admin.settings.seo-cache-info -->
- **라우트명**: `api.modules.sirsoft-ecommerce.admin.settings.seo-cache-info`
- **컨트롤러**: `Modules\Sirsoft\Ecommerce\Http\Controllers\Admin\EcommerceSettingsController@seoCacheInfo`
- **인증/권한**: `auth:sanctum` + `permission:sirsoft-ecommerce.settings.read`
**요청 파라미터**
_요청 파라미터 없음._
**요청 예시**
```http
GET /api/modules/sirsoft-ecommerce/admin/settings/seo-cache-info HTTP/1.1
Host: api.example.com
Accept: application/json
Authorization: Bearer {YOUR_TOKEN}
```
**응답 필드** (`data` 내부)
_단건 응답: `data` 객체의 필드._
| 필드 | 타입 | 실측 예시값 | 용도/설명 |
| --- | --- | --- | --- |
| count | integer | `0` | 캐시된 SEO 페이지 URL 개수 |
| size_bytes | integer | `0` | 캐시된 SEO 페이지의 지원 로케일별 HTML 총 바이트 |
| size_formatted | string | `0 B` | `size` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) |
**응답 예시**
```http
HTTP/1.1 200
```
```json
{
"success": true,
"message": "설정을 조회했습니다.",
"data": {
"count": 0,
"size_bytes": 0,
"size_formatted": "0 B"
}
}
```
**에러 응답**
| 상태코드 | 의미 | 발생 조건 |
| --- | --- | --- |
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
| 403 | Forbidden | 요구 권한(`sirsoft-ecommerce.settings.read`)이 없는 경우 |
<!-- @generated:end -->
**설명** 관리자가 현재 캐시된 SEO 페이지의 개수와 총 용량을 조회합니다. `permission:sirsoft-ecommerce.settings.read` 권한이 필요하며, `SeoCacheManagerInterface::getCachedUrls()`로 캐시된 URL 을 열거하고 지원 로케일별 HTML 바이트를 합산합니다. 응답은 캐시 페이지 수(`count`)·총 바이트(`size_bytes`)·사람이 읽기 쉬운 크기(`size_formatted`, 예 `1.5 MB`)를 담습니다. 설정 화면에서 SEO 캐시 현황을 표시하고 캐시 초기화 여부를 판단하는 근거로 사용합니다.
### GET /api/modules/sirsoft-ecommerce/admin/settings/{category}
<!-- @generated:start:api.modules.sirsoft-ecommerce.admin.settings.show -->
- **라우트명**: `api.modules.sirsoft-ecommerce.admin.settings.show`
- **컨트롤러**: `Modules\Sirsoft\Ecommerce\Http\Controllers\Admin\EcommerceSettingsController@show`
- **인증/권한**: `auth:sanctum` + `permission:sirsoft-ecommerce.settings.read`
**요청 파라미터**
| 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 |
| --- | --- | --- | --- | --- | --- |
| category | path | string | 예 | — | 분류 필터 (해당 분류의 항목만 조회) |
**요청 예시**
```http
GET /api/modules/sirsoft-ecommerce/admin/settings/{category} HTTP/1.1
Host: api.example.com
Accept: application/json
Authorization: Bearer {YOUR_TOKEN}
```
**응답 필드** (`data` 내부)
_단건 응답: `data` 객체의 필드._
| 필드 | 타입 | 실측 예시값 | 용도/설명 |
| --- | --- | --- | --- |
| category | string | `"basic_info"` | 조회한 설정 카테고리 (path 파라미터를 그대로 반향) |
| settings | object | `{"shop_name":"","route_path":"shop","no_route":false}` | 해당 카테고리의 설정값. 구조는 카테고리마다 다르며 index 응답의 동명 최상위 키와 동일하다 (`order_settings` 조회 시 결제수단은 병합된 목록 — 공급 확장이 빠진 고아 항목 포함) |
| abilities | object | `{"can_update":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update — 권한 맵 기반) |
**응답 예시**
```http
HTTP/1.1 200
```
```json
{
"success": true,
"message": "설정을 조회했습니다.",
"data": {
"category": "basic_info",
"settings": {
"shop_name": "",
"route_path": "shop",
"no_route": false
},
"abilities": {
"can_update": true
}
}
}
```
**에러 응답**
| 상태코드 | 의미 | 발생 조건 |
| --- | --- | --- |
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
| 403 | Forbidden | 요구 권한(`sirsoft-ecommerce.settings.read`)이 없는 경우 |
| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 |
<!-- @generated:end -->
**설명** 관리자가 단일 설정 카테고리만 골라 조회합니다. `permission:sirsoft-ecommerce.settings.read` 권한이 필요하며, path 의 `category`(예 `basic_info`)로 `EcommerceSettingsService::getSettings()`를 호출해 해당 섹션만 반환합니다. 전체 설정을 내려받는 index 와 달리 특정 탭 데이터만 필요할 때 사용하며, 응답은 `category`·`settings`·`abilities.can_update` 를 포함합니다.
### GET /api/modules/sirsoft-ecommerce/settings/checkout
<!-- @generated:start:api.modules.sirsoft-ecommerce.settings.checkout -->
- **라우트명**: `api.modules.sirsoft-ecommerce.settings.checkout`
- **컨트롤러**: `Modules\Sirsoft\Ecommerce\Http\Controllers\Public\EcommerceSettingsController@checkout`
- **인증/권한**: 공개 (인증 불필요)
**요청 파라미터**
_요청 파라미터 없음._
**요청 예시**
```http
GET /api/modules/sirsoft-ecommerce/settings/checkout HTTP/1.1
Host: api.example.com
Accept: application/json
```
**응답 필드** (`data` 내부)
_단건 응답: `data` 객체의 필드._
| 필드 | 타입 | 실측 예시값 | 용도/설명 |
| --- | --- | --- | --- |
| shipping | object | `{"default_country":"KR","available_countries":[{"code":"K…` | 체크아웃용 배송 설정 (기본 국가·배송 가능 국가·무료배송·배송유형 등) |
| order_settings | object | `{"default_pg_provider":null,"cash_receipt_provider":"toss…` | 체크아웃용 주문/결제 설정 (기본 PG·활성 결제수단·무통장 계좌 등). `payment_methods` 는 현재 제공 가능한 결제수단만 포함하며, 공급 확장이 더 이상 제공하지 않는 결제수단(관리자 화면의 고아 항목)은 `is_active` 가 참이어도 제외된다 |
**응답 예시**
```http
HTTP/1.1 200
```
```json
{
"success": true,
"message": "설정을 조회했습니다.",
"data": {
"shipping": {
"default_country": "KR",
"available_countries": [
{
"code": "KR",
"name": {
"ko": "대한민국",
"en": "South Korea"
},
"is_active": true
},
{
"code": "US",
"name": {
"ko": "미국",
"en": "United States"
},
"is_active": true
}
],
"international_shipping_enabled": true,
"free_shipping_threshold": 50000,
"free_shipping_enabled": true,
"...": "(2개 키 생략, 총 7개)"
},
"order_settings": {
"default_pg_provider": null,
"payment_methods": [
{
"id": "card",
"pg_provider": null,
"sort_order": 1,
"is_active": false,
"min_order_amount": 0,
"...": "(10개 키 생략, 총 15개)"
},
{
"id": "dbank",
"pg_provider": null,
"sort_order": 1,
"is_active": true,
"min_order_amount": 0,
"...": "(10개 키 생략, 총 15개)"
},
{
"id": "vbank",
"pg_provider": null,
"sort_order": 2,
"is_active": false,
"min_order_amount": 0,
"...": "(10개 키 생략, 총 15개)"
},
{
"id": "point",
"pg_provider": null,
"sort_order": 2,
"is_active": true,
"min_order_amount": 0,
"...": "(10개 키 생략, 총 15개)"
},
{
"id": "deposit",
"pg_provider": null,
"sort_order": 3,
"is_active": true,
"min_order_amount": 0,
"...": "(10개 키 생략, 총 15개)"
},
"... (총 25건 중 5건 표시)"
],
"banks": [],
"bank_accounts": [
{
"bank_code": "004",
"account_number": "",
"account_holder": "",
"is_active": false,
"is_default": false
}
],
"auto_cancel_expired": true,
"...": "(5개 키 생략, 총 10개)"
}
}
}
```
**에러 응답**
| 상태코드 | 의미 | 발생 조건 |
| --- | --- | --- |
| 500 | Internal Server Error | 설정 조회 중 예외 발생 (`messages.settings.fetch_failed`) |
<!-- @generated:end -->
**설명** 인증 없이 접근 가능한 공개 엔드포인트로, 체크아웃 화면이 필요로 하는 배송·결제 설정을 한 번에 반환합니다. `EcommerceSettingsService::getSettings()`로 `shipping` 과 `order_settings` 두 섹션을 함께 조회하며, 개별 shipping/payment 엔드포인트를 두 번 호출하지 않도록 묶어줍니다. 비회원·회원 모두 접근하고, `logApiUsage('settings.checkout')`로 사용 로그를 남깁니다.
### GET /api/modules/sirsoft-ecommerce/settings/payment
<!-- @generated:start:api.modules.sirsoft-ecommerce.settings.payment -->
- **라우트명**: `api.modules.sirsoft-ecommerce.settings.payment`
- **컨트롤러**: `Modules\Sirsoft\Ecommerce\Http\Controllers\Public\EcommerceSettingsController@payment`
- **인증/권한**: 공개 (인증 불필요)
**요청 파라미터**
_요청 파라미터 없음._
**요청 예시**
```http
GET /api/modules/sirsoft-ecommerce/settings/payment HTTP/1.1
Host: api.example.com
Accept: application/json
```
**응답 필드** (`data` 내부)
_단건 응답: `data` 객체의 필드._
| 필드 | 타입 | 실측 예시값 | 용도/설명 |
| --- | --- | --- | --- |
| order_settings | object | `{"default_pg_provider":null,"cash_receipt_provider":"toss…` | 공개 가능한 결제 설정 (활성 결제수단·무통장 은행명 매핑 포함, 민감 정보 제외). `payment_methods` 는 현재 제공 가능한 결제수단만 포함하며, 공급 확장이 더 이상 제공하지 않는 결제수단(관리자 화면의 고아 항목)은 `is_active` 가 참이어도 제외된다 |
**응답 예시**
```http
HTTP/1.1 200
```
```json
{
"success": true,
"message": "설정을 조회했습니다.",
"data": {
"order_settings": {
"default_pg_provider": null,
"payment_methods": [
{
"id": "card",
"pg_provider": null,
"sort_order": 1,
"is_active": false,
"min_order_amount": 0,
"...": "(10개 키 생략, 총 15개)"
},
{
"id": "dbank",
"pg_provider": null,
"sort_order": 1,
"is_active": true,
"min_order_amount": 0,
"...": "(10개 키 생략, 총 15개)"
},
{
"id": "vbank",
"pg_provider": null,
"sort_order": 2,
"is_active": false,
"min_order_amount": 0,
"...": "(10개 키 생략, 총 15개)"
},
{
"id": "point",
"pg_provider": null,
"sort_order": 2,
"is_active": true,
"min_order_amount": 0,
"...": "(10개 키 생략, 총 15개)"
},
{
"id": "deposit",
"pg_provider": null,
"sort_order": 3,
"is_active": true,
"min_order_amount": 0,
"...": "(10개 키 생략, 총 15개)"
},
"... (총 25건 중 5건 표시)"
],
"banks": [],
"bank_accounts": [
{
"bank_code": "004",
"account_number": "",
"account_holder": "",
"is_active": false,
"is_default": false,
"...": "(1개 키 생략, 총 6개)"
}
],
"auto_cancel_expired": true,
"...": "(5개 키 생략, 총 10개)"
}
}
}
```
**에러 응답**
| 상태코드 | 의미 | 발생 조건 |
| --- | --- | --- |
| 500 | Internal Server Error | 설정 조회 중 예외 발생 (`messages.settings.fetch_failed`) |
<!-- @generated:end -->
**설명** 인증 없이 접근 가능한 공개 엔드포인트로, 체크아웃에서 필요한 결제 설정을 반환합니다. `EcommerceSettingsService::getPublicPaymentSettings()`가 활성화된 결제 수단과 무통장입금 설정 등 공개 가능한 항목만 추려 `order_settings` 로 내려줍니다. 관리자 전용 민감 정보는 제외되며, 비회원·회원 모두 접근하고 `logApiUsage('settings.payment')`로 사용 로그를 남깁니다.
### GET /api/modules/sirsoft-ecommerce/settings/review
<!-- @generated:start:api.modules.sirsoft-ecommerce.settings.review -->
- **라우트명**: `api.modules.sirsoft-ecommerce.settings.review`
- **컨트롤러**: `Modules\Sirsoft\Ecommerce\Http\Controllers\Public\EcommerceSettingsController@review`
- **인증/권한**: 공개 (인증 불필요)
**요청 파라미터**
_요청 파라미터 없음._
**요청 예시**
```http
GET /api/modules/sirsoft-ecommerce/settings/review HTTP/1.1
Host: api.example.com
Accept: application/json
```
**응답 필드** (`data` 내부)
_단건 응답: `data` 객체의 필드._
| 필드 | 타입 | 실측 예시값 | 용도/설명 |
| --- | --- | --- | --- |
| review_settings | object | `{"write_deadline_days":90,"max_images":5,"max_image_size_…` | 공개 리뷰 정책 (작성 기한일·이미지 최대 개수·이미지 최대 용량 MB) |
**응답 예시**
```http
HTTP/1.1 200
```
```json
{
"success": true,
"message": "설정을 조회했습니다.",
"data": {
"review_settings": {
"write_deadline_days": 90,
"max_images": 5,
"max_image_size_mb": 10
}
}
}
```
**에러 응답**
| 상태코드 | 의미 | 발생 조건 |
| --- | --- | --- |
| 500 | Internal Server Error | 설정 조회 중 예외 발생 (`messages.settings.fetch_failed`) |
<!-- @generated:end -->
**설명** 인증 없이 접근 가능한 공개 엔드포인트로, 리뷰 작성 화면이 필요로 하는 리뷰 정책을 반환합니다. `EcommerceSettingsService::getSettings('review_settings')`로 리뷰 이미지 최대 개수(`max_images`)·최대 용량(`max_image_size_mb`)·작성 기한(`write_deadline_days`) 등을 `review_settings` 로 내려줍니다. 프론트가 이미지 업로드 제한과 작성 가능 기간을 판단하는 데 사용하며, `logApiUsage('settings.review')`로 사용 로그를 남깁니다.
### GET /api/modules/sirsoft-ecommerce/settings/shipping
<!-- @generated:start:api.modules.sirsoft-ecommerce.settings.shipping -->
- **라우트명**: `api.modules.sirsoft-ecommerce.settings.shipping`
- **컨트롤러**: `Modules\Sirsoft\Ecommerce\Http\Controllers\Public\EcommerceSettingsController@shipping`
- **인증/권한**: 공개 (인증 불필요)
**요청 파라미터**
_요청 파라미터 없음._
**요청 예시**
```http
GET /api/modules/sirsoft-ecommerce/settings/shipping HTTP/1.1
Host: api.example.com
Accept: application/json
```
**응답 필드** (`data` 내부)
_단건 응답: `data` 객체의 필드._
| 필드 | 타입 | 실측 예시값 | 용도/설명 |
| --- | --- | --- | --- |
| shipping | object | `{"default_country":"KR","available_countries":[{"code":"K…` | 공개 배송 설정 (기본 국가·배송 가능 국가·국제배송 활성 여부·배송유형·무료배송 설정) |
**응답 예시**
```http
HTTP/1.1 200
```
```json
{
"success": true,
"message": "설정을 조회했습니다.",
"data": {
"shipping": {
"default_country": "KR",
"available_countries": [
{
"code": "KR",
"name": {
"ko": "대한민국",
"en": "South Korea"
},
"is_active": true
},
{
"code": "US",
"name": {
"ko": "미국",
"en": "United States"
},
"is_active": true
}
],
"international_shipping_enabled": true,
"free_shipping_threshold": 50000,
"free_shipping_enabled": true,
"address_validation_enabled": false,
"address_api_provider": "kakao"
}
}
}
```
**에러 응답**
| 상태코드 | 의미 | 발생 조건 |
| --- | --- | --- |
| 500 | Internal Server Error | 설정 조회 중 예외 발생 (`messages.settings.fetch_failed`) |
<!-- @generated:end -->
**설명** 인증 없이 접근 가능한 공개 엔드포인트로, 체크아웃에서 필요한 배송 설정을 반환합니다. `EcommerceSettingsService::getSettings('shipping')`로 기본 배송 국가·이용 가능한 국가 목록·국제 배송 활성화 여부·배송 타입·무료 배송 설정 등을 `shipping` 으로 내려줍니다. 프론트가 배송지 선택과 배송비 안내를 구성하는 데 사용하며, `logApiUsage('settings.shipping')`로 사용 로그를 남깁니다.
---
## 결제수단 카탈로그 (`order_settings.payment_methods[]`)
결제수단 카탈로그는 코어 8종(builtin)과 PG 플러그인이 훅으로 등록한 확장 결제수단을 함께 담는다.
설정 조회/저장 응답의 `order_settings.payment_methods` 배열 각 항목이 이 구조를 따른다.
아래 능력(capability) 필드는 **정의(builtin 은 코어, 확장은 플러그인 선언)가 SSoT** 이며 저장된 값으로
덮이지 않는다 — 관리자가 편집하는 값이 아니라 결제수단의 성격이기 때문이다.
| 이름 | 타입 | 용도 |
| --- | --- | --- |
| id | string | 결제수단 ID. builtin(`card` / `vbank` / `dbank` / `bank` / `phone` / `point` / `deposit` / `free`) 또는 확장 ID(예: `nhnkcp_naverpay`, `kginicis_lpay`) |
| needs_pg | boolean | PG 결제창이 필요한 수단인지. `false` 면 관리자 화면에 "PG 불필요" 로 표시되고 주문도 PG 를 거치지 않는다 (무통장·포인트·예치금·무료) |
| pg_locked | boolean | PG 가 특정 대행사로 고정된 수단인지. 간편결제처럼 특정 PG 전용인 수단은 `true` 이며, 관리자가 PG 를 바꿀 수 없고 화면에는 "PG 고정" 배지로 표시된다. `true` 일 때 `pg_provider` 는 저장값 대신 플러그인 선언값이 강제된다 |
| refund_method | string | 환불 수단 분류 (`pg` / `bank` / `points`). 주문 취소 시 PG 취소를 호출할지 결정한다 |
| pg_provider | string\|null | 이 결제수단을 처리할 PG. `pg_locked=true` 면 플러그인이 선언한 PG 로 고정되고, 아니면 관리자가 선택한다 (미선택 시 `default_pg_provider` 로 폴백) |
| is_active | boolean | 주문서에 노출할지 여부 |
| min_order_amount | number | 이 결제수단을 쓸 수 있는 최소 주문금액 |
| stock_deduction_timing | string | 재고 차감 시점 (`order_placed` / `payment_complete` / `none`) |
| mileage_deduction_timing | string | 마일리지 차감 시점 |
| sort_order | number | 주문서 노출 순서 |
| _cached_name / _cached_description | object | 다국어 표시명/설명 (locale => 문자열) |
| _cached_icon | string | 표시 아이콘 |
| _cached_source | string | 제공 주체 (`builtin` 또는 `plugin:{식별자}`) |
확장 결제수단(간편결제)은 코어의 `card` 로 치환되지 않고 자기 ID 그대로 저장·조회된다.
주문 생성 시 `payment_method` 로 확장 ID 를 그대로 보내면 되며, 서버는 이 카탈로그를 화이트리스트로
사용해 검증한다.