Files
Gnuboard7/templates/_bundled/sirsoft-basic/layouts/shop/checkout.json
T
2026-07-01 10:30:32 +09:00

424 lines
20 KiB
JSON

{
"version": "1.0.0",
"layout_name": "shop/checkout",
"permissions": [],
"extends": "_user_base",
"meta": {
"title": "주문서 작성",
"description": "주문서 작성 페이지 - 상품 확인, 주문자/배송지 정보, 결제 방법 선택 (PC/모바일 반응형)"
},
"_comment_initLocal": "정적 기본값 설정 (데이터소스 initLocal 병합 전 기본값). country_code 는 init_actions 가 _global.preferredShippingCountry(헤더/계정/GeoIP/기본국가가 수렴한 값)로 시드한다 — 정적 KR 하드코딩 금지(헤더가 US 여도 KR 로 계산되는 회귀 방지).",
"initLocal": {
"checkout": {
"item_coupons": {},
"order_coupon_issue_id": null,
"shipping_coupon_issue_id": null,
"use_points": 0,
"discount_code": null,
"zipcode": null
},
"downloadableCoupons": null,
"downloadableCouponsLoading": false,
"downloadableCouponsPage": 1,
"isSubmittingOrder": false,
"errors": null,
"orderError": null,
"selectedDbank": null,
"addressModalMode": "list",
"selectedAddressId": null,
"selectedAddressObj": null,
"saveShippingAddress": false,
"editingAddress": null,
"unavailableModal": {
"visible": false,
"items": [],
"hasStockIssue": false
}
},
"init_actions": [
{
"comment": "배송국가 시드 — 주문서 진입 시 _local.checkout.country_code 를 _global.preferredShippingCountry(헤더/계정/GeoIP/기본국가 수렴값)로 채운다. 이 값이 checkoutData GET 쿼리(country_code)로 전달되어 배송비가 해당 국가로 계산된다. 유저가 주문서에서 배송국가 Select 를 바꾸면 그 시점 값이 SSoT 로 덮어쓴다(주문서 기준).",
"handler": "setState",
"params": {
"target": "local",
"checkout.country_code": "{{_global.preferredShippingCountry ?? _global.modules?.['sirsoft-ecommerce']?.shipping?.default_country ?? 'KR'}}"
}
},
{
"comment": "결제 콜백 에러 쿼리 파라미터 감지 — PG 결제 실패 후 리다이렉트 시 에러 코드 저장 + toast + URL 정리",
"handler": "conditions",
"conditions": [
{
"if": "{{!!query.error}}",
"then": {
"handler": "sequence",
"params": {
"actions": [
{
"handler": "setState",
"params": {
"target": "local",
"orderError": "{{query.error}}",
"orderErrorMessage": "{{query.message ?? ''}}"
}
},
{
"handler": "toast",
"params": {
"type": "error",
"message": "$t:shop.checkout.payment_generic_error"
}
},
{
"handler": "replaceUrl",
"params": {
"path": "/shop/checkout"
}
}
]
}
}
}
]
}
],
"computed": {
"_comment": "orderer 기본값 - _local.orderer가 없으면 _global.currentUser에서 가져옴 (새로고침 시 타이밍 문제 해결)",
"ordererDefaults": "{{ { name: _local.orderer?.name || _global.currentUser?.name || '', phone: _local.orderer?.phone || _global.currentUser?.phone || '', email: _local.orderer?.email || _global.currentUser?.email || '' } }}",
"_comment_payment": "선택된 결제수단 - _local에 없으면 첫 번째 활성 결제수단을 기본값으로 사용",
"selectedPaymentMethod": "{{_local.paymentMethod ?? (paymentSettings.data?.order_settings?.payment_methods ?? []).find(m => m.is_active)?.id ?? 'dbank'}}"
},
"data_sources": [
{
"id": "checkoutData",
"label_key": "$t:editor.data_source.checkoutData",
"type": "api",
"endpoint": "/api/modules/sirsoft-ecommerce/checkout",
"method": "GET",
"auto_fetch": true,
"auth_mode": "optional",
"loading_strategy": "progressive",
"comment": "장바구니에서 선택된 상품 또는 바로구매 상품 정보 + 저장된 promotions 데이터 복원. country_code 쿼리로 배송국가를 전달해 해당 국가 배송비로 미리보기 계산한다(init_actions 시드 _local.checkout.country_code 우선, 미설정 시 동일 fallback 체인). 우편번호는 도서산간/주별 세부 차등에만 관여하며 국가 결정과 무관.",
"params": {
"country_code": "{{_local.checkout?.country_code ?? _global.preferredShippingCountry ?? _global.modules?.['sirsoft-ecommerce']?.shipping?.default_country ?? 'KR'}}"
},
"initLocal": {
"_merge": "deep",
"checkout": {
"item_coupons": "promotions.item_coupons",
"order_coupon_issue_id": "promotions.order_coupon_issue_id",
"shipping_coupon_issue_id": "promotions.shipping_coupon_issue_id",
"use_points": "use_points"
},
"unavailableModal": {
"items": "unavailable_items",
"hasStockIssue": "has_stock_issue"
}
},
"onSuccess": {
"comment": "구매불가 상품이 있으면 setState로 데이터 설정 후 모달 표시 (initLocal 머지는 모달 표현식 캐시를 갱신하지 않으므로 setState 필수)",
"handler": "conditions",
"conditions": [
{
"if": "{{(response.data.data.unavailable_items?.length ?? 0) > 0}}",
"then": {
"handler": "sequence",
"params": {
"actions": [
{
"handler": "setState",
"target": "local",
"params": {
"unavailableModal": {
"visible": true,
"items": "{{response.data.data.unavailable_items ?? []}}",
"hasStockIssue": "{{response.data.data.has_stock_issue ?? false}}"
}
}
},
{
"handler": "openModal",
"target": "cart_unavailable_modal"
}
]
}
}
}
]
},
"errorHandling": {
"404": {
"handler": "openModal",
"target": "tempOrderNotFoundModal"
}
}
},
{
"id": "userAddresses",
"label_key": "$t:editor.data_source.userAddresses",
"type": "api",
"endpoint": "/api/modules/sirsoft-ecommerce/user/addresses",
"method": "GET",
"auto_fetch": true,
"auth_mode": "required",
"comment": "사용자 저장 배송지 목록 (회원 전용). 비회원/토큰만료 시 401 코어 DEFAULT 핸들러가 navigate({{auth.loginPath}}) 를 발동해 'Failed to execute action: navigate' 가 보고됨 — suppress 로 조용히 무시",
"fallback": { "data": { "addresses": { "data": [] } } },
"errorHandling": {
"401": { "handler": "suppress" },
"403": { "handler": "suppress" }
}
},
{
"id": "paymentSettings",
"label_key": "$t:editor.data_source.paymentSettings",
"type": "api",
"endpoint": "/api/modules/sirsoft-ecommerce/settings/payment",
"method": "GET",
"auto_fetch": true,
"auth_mode": "optional",
"comment": "결제 설정 (활성 결제 수단, 은행 목록, 입금 계좌)"
},
{
"id": "shippingSettings",
"label_key": "$t:editor.data_source.shippingSettings",
"type": "api",
"endpoint": "/api/modules/sirsoft-ecommerce/settings/shipping",
"method": "GET",
"auto_fetch": true,
"auth_mode": "optional",
"comment": "배송 설정 (국내/해외 배송 가능 여부 등)"
}
],
"slots": {
"content": [
{
"type": "layout",
"name": "Container",
"blur_until_loaded": "{{_local.isSubmittingOrder}}",
"props": {
"className": "py-8"
},
"responsive": {
"portable": {
"props": {
"className": "py-4"
}
}
},
"children": [
{
"comment": "에러 요약 (페이지 상단)",
"type": "basic",
"name": "Div",
"if": "{{_local.orderError}}",
"props": {
"className": "mb-6 p-4 rounded-lg bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800"
},
"responsive": {
"portable": {
"props": {
"className": "mb-4 p-3 rounded-lg bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800"
}
}
},
"children": [
{
"type": "basic",
"name": "Div",
"props": { "className": "flex items-start gap-3" },
"children": [
{
"type": "basic",
"name": "Icon",
"props": { "name": "exclamation-circle", "className": "w-5 h-5 text-red-500 flex-shrink-0 mt-0.5" }
},
{
"type": "basic",
"name": "Div",
"children": [
{
"type": "basic",
"name": "P",
"props": { "className": "text-sm font-medium text-red-800 dark:text-red-200" },
"text": "{{_local.orderError === 'confirm_failed' ? '$t:shop.checkout.payment_confirm_failed' : _local.orderError === 'amount_mismatch' ? '$t:shop.checkout.payment_amount_mismatch' : _local.orderError === 'order_not_found' ? '$t:shop.checkout.payment_order_not_found' : '$t:shop.checkout.payment_generic_error'}}"
},
{
"type": "basic",
"name": "P",
"if": "{{_local.orderErrorMessage}}",
"props": { "className": "text-xs text-red-600 dark:text-red-400 mt-1" },
"text": "{{_local.orderErrorMessage}}"
}
]
}
]
}
]
},
{
"comment": "뒤로가기 + 페이지 제목",
"type": "basic",
"name": "Div",
"props": {
"className": "mb-6"
},
"responsive": {
"portable": {
"props": {
"className": "mb-4"
}
}
},
"children": [
{
"type": "basic",
"name": "Button",
"props": {
"className": "inline-flex items-center gap-2 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white mb-4 text-sm cursor-pointer"
},
"responsive": {
"portable": {
"props": {
"className": "inline-flex items-center gap-2 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white mb-2 text-sm cursor-pointer"
}
}
},
"children": [
{
"type": "basic",
"name": "Icon",
"props": {
"name": "chevron-left",
"className": "w-4 h-4"
}
},
{
"type": "basic",
"name": "Span",
"text": "$t:shop.checkout.back"
}
],
"actions": [
{
"type": "click",
"handler": "navigate",
"params": {
"path": "{{_global.shopBase}}/cart"
}
}
]
},
{
"type": "basic",
"name": "H1",
"props": {
"className": "text-2xl font-bold text-gray-900 dark:text-white"
},
"responsive": {
"portable": {
"props": {
"className": "text-xl font-bold text-gray-900 dark:text-white"
}
}
},
"text": "$t:shop.checkout.title"
}
]
},
{
"comment": "PC: 3컬럼 그리드 (좌측 폼 2칸, 우측 결제금액 1칸)",
"type": "layout",
"name": "Grid",
"props": {
"cols": 3,
"gap": 6
},
"responsive": {
"portable": {
"props": {
"cols": 1,
"gap": 4
}
}
},
"children": [
{
"comment": "좌측 영역 - 폼 섹션들",
"type": "basic",
"name": "Div",
"props": {
"className": "col-span-2 space-y-6"
},
"responsive": {
"portable": {
"props": {
"className": "col-span-1 space-y-4"
}
}
},
"children": [
{
"partial": "partials/shop/_checkout_items.json"
},
{
"partial": "partials/shop/_checkout_discount.json"
},
{
"partial": "partials/shop/_checkout_mileage.json"
},
{
"partial": "partials/shop/_checkout_orderer.json"
},
{
"partial": "partials/shop/_checkout_shipping.json"
},
{
"partial": "partials/shop/_checkout_payment.json"
}
]
},
{
"comment": "우측 영역 - 결제 금액 (sticky)",
"type": "basic",
"name": "Div",
"props": {
"className": "col-span-1"
},
"children": [
{
"partial": "partials/shop/_checkout_summary.json"
}
]
}
]
},
{
"type": "extension_point",
"name": "shop_checkout_extensions",
"default": []
}
]
}
]
},
"modals": [
{ "partial": "partials/shop/_modal_temp_order_not_found.json" },
{ "partial": "partials/shop/_modal_exclusive_coupon_confirm.json" },
{ "partial": "partials/shop/_modal_address_manage.json" },
{ "partial": "partials/mypage/addresses/_modal_confirm_overwrite.json" },
{ "partial": "partials/shop/_modal_cart_unavailable.json" },
{ "partial": "partials/shop/_modal_coupon_download.json" }
],
"actions": [
{
"id": "selectDbank",
"comment": "무통장입금 계좌 선택",
"handler": "setState",
"params": {
"target": "_local.selectedDbank",
"value": "{{$params.bank}}"
}
}
]
}