fix(core,ecommerce,board,basic,pay_kginicis,pay_nhnkcp,pay_nicepayments): 7.0.6 회귀 3건·잠복 결함 3건 해소 + 통화 하드코딩 전수 제거
7.0.6 사이클에서 유입된 회귀 3건을 출시 전에 닫는다. 추가옵션 저장소의 반환 타입 선언이 실제 반환(중첩 맵)과 어긋나 추가옵션 있는 상품의 담기· 바로구매가 죽었고, 검색 엔진이 결과 소비 시점에 SELECT 를 재작성해 정렬이 참조하던 스코어 별칭을 지웠으며, 미들웨어 targets 가 늘어난 라우트를 따라가지 못해 비회원 현금영수증이 전면 불능이었다. 셋 다 정적 검사의 사각지대였으므로 룰 2건과 계약 테스트를 함께 세워 재발을 막는다. 같은 유형의 잠복 결함 3건(빈 반환의 타입 불일치 2건, 없는 메서드 호출 1건)도 호출 경로가 생기는 순간 fatal 이므로 함께 닫았다. 브라우저 검수 중 드러난 선행 결함 5건을 이어서 수정한다. 엔진이 event 키로 선언한 액션을 React prop 으로 연결하지 않아 오류 없이 조용히 무반응이었고, 연결한 뒤에는 커스텀 컴포넌트의 이벤트 payload 가 유실됐다. 비회원 주문상세 응답에 현금영수증 키가 없어 발급 결과에 도달할 수 없었으며, 입금확인과 현금영수증 발급이 base 통화 금액을 써서 결제 통화가 다른 주문에서 각각 저장 실패와 잘못된 증빙을 냈다. 마지막으로 통화 단위를 코드·문구에서 걷어내 설정으로 이관한다. 다국어 문구의 원/円, 프론트의 KRW 고정표와 리터럴 폴백 173건, 서버 포맷의 통화 코드 리터럴이 대상이며, 기본 통화가 원이 아닌 상점에서 값은 맞고 단위만 틀린 금액이 예외 없이 나가던 문제였다. 통화 판정을 설정 단일 출처로 모으고 규칙 3건을 정적 검사로 고정했다.
This commit is contained in:
@@ -22,6 +22,9 @@
|
||||
|
||||
### Fixed
|
||||
|
||||
- 관리자가 추가한 통화(예: 파운드)로 볼 때 금액이 원화 기호와 자릿수로 표시되던 문제를 수정했습니다. 이제 환경설정에 등록한 통화 기호와 소수 자릿수를 그대로 따릅니다.
|
||||
- 표시 통화가 정해지지 않은 순간에 금액이 원화로 표시될 수 있던 문제를 수정했습니다. 이제 상점 기본 통화로 표시됩니다.
|
||||
- 기본 통화가 원(KRW)이 아닌 쇼핑몰을 다른 통화로 볼 때, 추가옵션 금액만 기준 통화로 표시되고 그 값이 그대로 총 금액에 더해지던 문제를 수정했습니다. 상품 상세의 추가옵션 선택지·추가옵션 금액·총 금액, 장바구니와 주문서의 추가옵션 줄이 모두 보고 있는 통화로 표시됩니다. 기본 통화가 원인 상점은 이전과 동일합니다.
|
||||
- 주문 상세의 결제 정보 영역에 결제 플러그인이 제공하는 공급가액·부가세·영수증 링크·에스크로 구매확정 버튼이 표시되지 않던 문제를 수정했습니다.
|
||||
- 총 건수를 끝까지 세지 못한 목록에서 페이지 이동 막대가 사라지거나 "1페이지뿐" 으로 잘못 표시되던 문제를 수정했습니다. 마이페이지의 주문·마일리지·위시리스트·알림함, 게시판 목록, 상품 리뷰·문의, 내가 쓴 글 목록이 해당합니다.
|
||||
- 검색 결과의 탭 배지가 끝까지 세지 못한 건수를 정확한 숫자처럼 보여주던 문제를 수정했습니다. 이제 그 경우 숫자 뒤에 "이상" 표시가 붙습니다.
|
||||
|
||||
+4
-4
@@ -30,7 +30,7 @@ describe('체크아웃 할인/쿠폰 선택 통화 환산 (_checkout_discount.js
|
||||
it('쿠폰 옵션 텍스트는 multi_currency_benefit_formatted 선택통화 폴백을 쓴다 (옵션 텍스트 KRW 고정 차단)', () => {
|
||||
// benefit_formatted 단독 사용 패턴이 없어야 한다(반드시 multi_currency 우선)
|
||||
const benefitMatches = raw.match(/coupon\.benefit_formatted/g) ?? [];
|
||||
const multiMatches = raw.match(/coupon\.multi_currency_benefit_formatted\?\.\[_global\.preferredCurrency \?\? 'KRW'\]/g) ?? [];
|
||||
const multiMatches = raw.match(/coupon\.multi_currency_benefit_formatted\?\.\[_global\.preferredCurrency \?\? _global\.defaultCurrency\]/g) ?? [];
|
||||
// 옵션 텍스트 2곳(주문쿠폰 + 배송비쿠폰) 모두 multi_currency 우선 폴백
|
||||
expect(multiMatches.length).toBeGreaterThanOrEqual(2);
|
||||
// benefit_formatted 가 등장하더라도 항상 multi_currency 폴백의 ?? 우변으로만
|
||||
@@ -39,19 +39,19 @@ describe('체크아웃 할인/쿠폰 선택 통화 환산 (_checkout_discount.js
|
||||
|
||||
it('적용 주문쿠폰 할인액이 summary.multi_currency 선택통화 폴백을 쓴다', () => {
|
||||
expect(raw).toContain(
|
||||
"multi_currency?.[_global.preferredCurrency ?? 'KRW']?.order_coupon_discount_formatted"
|
||||
"multi_currency?.[_global.preferredCurrency ?? _global.defaultCurrency]?.order_coupon_discount_formatted"
|
||||
);
|
||||
});
|
||||
|
||||
it('적용 배송비쿠폰 할인액이 summary.multi_currency 선택통화 폴백을 쓴다 (D5 핵심)', () => {
|
||||
expect(raw).toContain(
|
||||
"multi_currency?.[_global.preferredCurrency ?? 'KRW']?.shipping_discount_formatted"
|
||||
"multi_currency?.[_global.preferredCurrency ?? _global.defaultCurrency]?.shipping_discount_formatted"
|
||||
);
|
||||
});
|
||||
|
||||
it('적용 할인코드 할인액이 summary.multi_currency 선택통화 폴백을 쓴다', () => {
|
||||
expect(raw).toContain(
|
||||
"multi_currency?.[_global.preferredCurrency ?? 'KRW']?.code_discount_formatted"
|
||||
"multi_currency?.[_global.preferredCurrency ?? _global.defaultCurrency]?.code_discount_formatted"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -77,15 +77,23 @@ describe('U10 — 다운로드 모달 무한스크롤 (버튼식 페이지네이
|
||||
});
|
||||
});
|
||||
|
||||
describe('U10 — 더보기 액션(증분 첫 페이지 로드)', () => {
|
||||
describe('U10 — 더보기 액션(배지와 같은 목록을 연다)', () => {
|
||||
const text = serialize(infoSummary);
|
||||
|
||||
it('더보기 apiCall 이 per_page=8 + page=1 로 첫 페이지를 로드한다', () => {
|
||||
expect(text).toContain('per_page=8&page=1');
|
||||
/*
|
||||
* 「+N」 배지는 이 상품에 적용되는 쿠폰(productDownloadableCoupons)으로 계산된다.
|
||||
* 더보기가 전체 다운로드 쿠폰을 새로 조회하면 이 상품에 적용되지 않는 쿠폰이 섞여
|
||||
* 배지 수와 모달 건수가 어긋나므로, 더보기는 배지와 같은 데이터소스를 그대로 넘긴다.
|
||||
* (종전 테스트는 별도 조회 설계를 고정하고 있었다 — 그 설계가 배지 불일치의 원인이었다.)
|
||||
*/
|
||||
it('더보기가 별도 조회 없이 배지와 같은 데이터소스를 모달에 넘긴다', () => {
|
||||
expect(text).toContain('downloadableCoupons":"{{productDownloadableCoupons.data}}');
|
||||
expect(text).not.toContain('per_page=8&page=1');
|
||||
});
|
||||
|
||||
it('더보기 onSuccess 가 downloadingMore 를 초기화한다', () => {
|
||||
expect(text).toContain('downloadingMore');
|
||||
it('더보기가 모달 로딩 상태를 초기화한다', () => {
|
||||
expect(text).toContain('downloadableCouponsLoading":false');
|
||||
expect(text).toContain('downloadingMore":false');
|
||||
});
|
||||
|
||||
it('downloadableCouponsPage setState 가 제거되었다', () => {
|
||||
|
||||
@@ -29,7 +29,7 @@ const modalText = fs.readFileSync(
|
||||
describe('A1-④ — 쿠폰 모달 다통화 환산', () => {
|
||||
it('정액 할인이 multi_currency_discount_value 선택통화 환산을 표시한다', () => {
|
||||
expect(modalText).toContain('multi_currency_discount_value');
|
||||
expect(modalText).toContain("_global.preferredCurrency ?? 'KRW'");
|
||||
expect(modalText).toContain("_global.preferredCurrency ?? _global.defaultCurrency");
|
||||
});
|
||||
|
||||
it('정액 할인 폴백은 기본통화 숫자 + currency_unit 을 유지한다', () => {
|
||||
|
||||
@@ -67,7 +67,7 @@ describe.each([
|
||||
const strikethrough = nodes.find((n) =>
|
||||
typeof n.text === 'string' && n.text.includes('multi_currency_list_price'));
|
||||
expect(strikethrough).toBeDefined();
|
||||
expect(strikethrough!.text).toContain("_global.preferredCurrency ?? 'KRW'");
|
||||
expect(strikethrough!.text).toContain("_global.preferredCurrency ?? _global.defaultCurrency");
|
||||
expect(strikethrough!.text).toContain('list_price_formatted');
|
||||
expect(strikethrough!.props?.className).toContain('line-through');
|
||||
});
|
||||
@@ -83,7 +83,7 @@ describe.each([
|
||||
expect(secondaryBlock).toBeDefined();
|
||||
expect(secondaryBlock!.if).toContain('discount_rate');
|
||||
expect(secondaryBlock!.iteration?.source).toContain('multi_currency_list_price');
|
||||
expect(secondaryBlock!.iteration?.source).toContain("code !== (_global.preferredCurrency ?? 'KRW')");
|
||||
expect(secondaryBlock!.iteration?.source).toContain("code !== (_global.preferredCurrency ?? _global.defaultCurrency)");
|
||||
});
|
||||
|
||||
it('판매가 표시(multi_currency_selling_price)는 유지된다(비회귀)', () => {
|
||||
|
||||
+6
-6
@@ -30,12 +30,12 @@ describe('마이페이지 결제 정보 — base 통화 고정 + 결제통화
|
||||
|
||||
it('금액 primary 는 preferredCurrency 가 아니라 base(*_formatted)를 바인딩한다', () => {
|
||||
// 회귀: mc_total_amount[preferredCurrency].formatted 를 primary 로 쓰던 패턴 제거
|
||||
expect(s).not.toContain("mc_total_amount?.[_global.preferredCurrency ?? 'KRW']?.formatted");
|
||||
expect(s).not.toContain("mc_total_amount?.[_global.preferredCurrency ?? _global.defaultCurrency]?.formatted");
|
||||
expect(s).toContain('order.data.total_amount_formatted');
|
||||
});
|
||||
|
||||
it('보조 통화 iteration 은 주문 base_currency 를 제외한다', () => {
|
||||
expect(s).toContain("order.data.base_currency ?? _global.preferredCurrency ?? 'KRW'");
|
||||
expect(s).toContain("order.data.base_currency ?? _global.preferredCurrency ?? _global.defaultCurrency");
|
||||
});
|
||||
|
||||
it('base≠결제 통화일 때 결제 통화 실청구액을 명시한다', () => {
|
||||
@@ -69,7 +69,7 @@ describe('마이페이지 주문 목록 — base 통화 고정', () => {
|
||||
|
||||
it('합계 primary 는 base(*_formatted), 보조 통화는 base_currency 제외', () => {
|
||||
expect(s).toContain('order.total_amount_formatted');
|
||||
expect(s).toContain("order.base_currency ?? _global.preferredCurrency ?? 'KRW'");
|
||||
expect(s).toContain("order.base_currency ?? _global.preferredCurrency ?? _global.defaultCurrency");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -78,13 +78,13 @@ describe('마이페이지 주문 상세 상품 카드 — base 통화 고정', (
|
||||
|
||||
it('상품 단가/소계 primary 는 preferredCurrency 가 아니라 base(*_formatted)를 바인딩한다', () => {
|
||||
// 회귀: mc_subtotal_price[preferredCurrency].formatted 를 primary 로 쓰던 패턴 제거
|
||||
expect(s).not.toContain("mc_subtotal_price?.[_global.preferredCurrency ?? 'KRW']?.formatted");
|
||||
expect(s).not.toContain("mc_unit_price?.[_global.preferredCurrency ?? 'KRW']?.formatted");
|
||||
expect(s).not.toContain("mc_subtotal_price?.[_global.preferredCurrency ?? _global.defaultCurrency]?.formatted");
|
||||
expect(s).not.toContain("mc_unit_price?.[_global.preferredCurrency ?? _global.defaultCurrency]?.formatted");
|
||||
expect(s).toContain('item.unit_price_formatted');
|
||||
expect(s).toContain('item.subtotal_price_formatted');
|
||||
});
|
||||
|
||||
it('보조 통화 iteration 은 주문 base_currency 를 제외한다', () => {
|
||||
expect(s).toContain("order.data.base_currency ?? _global.preferredCurrency ?? 'KRW'");
|
||||
expect(s).toContain("order.data.base_currency ?? _global.preferredCurrency ?? _global.defaultCurrency");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -60,7 +60,7 @@ describe('상품상세 총 금액 선택 통화 환산 (_purchase_card.json)', (
|
||||
|
||||
it('옵션 선택형 총 금액은 selectedTotalMultiCurrency 선택통화 포맷 맵을 조회한다 (D2)', () => {
|
||||
expect(optionValueNode).toBeDefined();
|
||||
expect(optionValueNode!.text).toContain("selectedTotalMultiCurrency?.[_global.preferredCurrency ?? 'KRW']?.formatted");
|
||||
expect(optionValueNode!.text).toContain("selectedTotalMultiCurrency?.[_global.preferredCurrency ?? _global.defaultCurrency]?.formatted");
|
||||
});
|
||||
|
||||
it('옵션 선택형 총 금액은 handler() 표현식 호출과 원(KRW) 하드코딩을 쓰지 않는다 (금지패턴/D2 회귀 차단)', () => {
|
||||
@@ -71,7 +71,7 @@ describe('상품상세 총 금액 선택 통화 환산 (_purchase_card.json)', (
|
||||
|
||||
it('옵션 없는 상품 총 금액은 noOptionTotalMultiCurrency 또는 단가 formatted 폴백을 쓴다 (D3)', () => {
|
||||
expect(noOptionValueNode).toBeDefined();
|
||||
expect(noOptionValueNode!.text).toContain("noOptionTotalMultiCurrency?.[_global.preferredCurrency ?? 'KRW']?.formatted");
|
||||
expect(noOptionValueNode!.text).toContain("noOptionTotalMultiCurrency?.[_global.preferredCurrency ?? _global.defaultCurrency]?.formatted");
|
||||
// 단가 formatted 폴백(초기 수량 1)
|
||||
expect(noOptionValueNode!.text).toContain('multi_currency_selling_price');
|
||||
});
|
||||
@@ -85,7 +85,7 @@ describe('상품상세 총 금액 선택 통화 환산 (_purchase_card.json)', (
|
||||
it('개별 선택 항목 가격(multiCurrencyTotalPrice formatted)은 유지된다(비회귀)', () => {
|
||||
const text = JSON.stringify(layout);
|
||||
expect(text).toContain('multiCurrencyTotalPrice');
|
||||
expect(text).toContain("?.[_global.preferredCurrency ?? 'KRW']?.formatted");
|
||||
expect(text).toContain("?.[_global.preferredCurrency ?? _global.defaultCurrency]?.formatted");
|
||||
});
|
||||
|
||||
it('레이아웃 전체에 handler() 표현식 호출이 없다 (엔진 표현식 컨텍스트는 handler 미제공)', () => {
|
||||
|
||||
File diff suppressed because one or more lines are too long
+13
-14
File diff suppressed because one or more lines are too long
+1
-1
@@ -58,5 +58,5 @@ export interface AvatarUploaderProps {
|
||||
/** 레이아웃 편집기 주입 속성 (편집 모드 전용, 루트에 spread) */
|
||||
editorAttrs?: EditorAttrs;
|
||||
}
|
||||
export declare function AvatarUploader({ src, fallbackText, size, uploadEndpoint, deleteEndpoint, onUploadSuccess, onUploadError, onDeleteSuccess, onDeleteError, showDeleteButton, accept, maxSize, className, uploadButtonText, deleteButtonText, readOnly, confirmDelete, deleteConfirmMessage, confirmUpload, uploadConfirmMessage, uploadSuccessActions, uploadErrorActions, deleteSuccessActions, deleteErrorActions, id, editorAttrs, }: AvatarUploaderProps): import("react").JSX.Element;
|
||||
export declare function AvatarUploader({ src, fallbackText, size, uploadEndpoint, deleteEndpoint, onUploadSuccess, onUploadError, onDeleteSuccess, onDeleteError, showDeleteButton, accept, maxSize, className, uploadButtonText, deleteButtonText, readOnly, confirmDelete, deleteConfirmMessage, confirmUpload, uploadConfirmMessage, uploadSuccessActions, uploadErrorActions, deleteSuccessActions, deleteErrorActions, id, editorAttrs, }: AvatarUploaderProps): import("react/jsx-runtime").JSX.Element;
|
||||
export default AvatarUploader;
|
||||
|
||||
@@ -7,23 +7,25 @@ interface FormatCurrencyParams {
|
||||
/**
|
||||
* 숫자 값을 통화 형식으로 포맷팅합니다.
|
||||
*
|
||||
* 원화 계열(₩/원)은 금액 뒤에 "원", 그 외는 기호를 앞에 붙입니다
|
||||
* (백엔드 messages.currency.prefix/suffix 와 동일한 표기 규칙).
|
||||
*
|
||||
* @param params.value - 포맷팅할 숫자 값
|
||||
* @param params.currencyCode - 통화 코드 (KRW, USD, JPY 등)
|
||||
* @param params.locale - 로케일 (선택, 미지정 시 통화 기본 로케일 사용)
|
||||
* @param params.currencyCode - 통화 코드 (미지정 시 표시 통화 → 기본 통화 순)
|
||||
* @param params.locale - 로케일 (숫자 구분자용, 미지정 시 브라우저 로케일)
|
||||
* @param context - 핸들러 컨텍스트
|
||||
* @returns 포맷팅된 통화 문자열
|
||||
*
|
||||
* @example
|
||||
* formatCurrencyHandler({ value: 10000, currencyCode: 'KRW' }, context)
|
||||
* // => "₩10,000" 또는 "10,000원"
|
||||
*
|
||||
* formatCurrencyHandler({ value: 99.99, currencyCode: 'USD' }, context)
|
||||
* // => "$99.99"
|
||||
* formatCurrencyHandler({ value: 10000, currencyCode: 'KRW' }, context) // => "10,000원"
|
||||
* formatCurrencyHandler({ value: 99.99, currencyCode: 'USD' }, context) // => "$99.99"
|
||||
*/
|
||||
export declare function formatCurrencyHandler(params: FormatCurrencyParams, context: HandlerContext): string;
|
||||
/**
|
||||
* 통화 심볼만 반환합니다.
|
||||
*
|
||||
* 설정의 symbol 을 우선하고, 없으면 폴백 표, 그것도 없으면 통화 코드를 그대로 돌려줍니다.
|
||||
*
|
||||
* @param currencyCode - 통화 코드
|
||||
* @returns 통화 심볼
|
||||
*/
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"pattern": "/api/modules/sirsoft-ecommerce/*",
|
||||
"headers": {
|
||||
"X-Cart-Key": "{{_global.cartKey}}",
|
||||
"X-Currency": "{{_global.preferredCurrency ?? _global.defaultCurrency ?? 'KRW'}}",
|
||||
"X-Currency": "{{_global.preferredCurrency ?? _global.defaultCurrency}}",
|
||||
"X-Shipping-Country": "{{_global.preferredShippingCountry ?? _global.modules?.['sirsoft-ecommerce']?.shipping?.default_country ?? 'KR'}}"
|
||||
}
|
||||
},
|
||||
@@ -59,14 +59,14 @@
|
||||
"handler": "setState",
|
||||
"params": {
|
||||
"target": "global",
|
||||
"defaultCurrency": "{{_global.modules?.['sirsoft-ecommerce']?.language_currency?.default_currency ?? 'KRW'}}"
|
||||
"defaultCurrency": "{{_global.modules?.['sirsoft-ecommerce']?.language_currency?.default_currency}}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"_comment": "선호(표시) 통화를 _global 에 동기 주입. 우선순위 = 로그인 유저 계정 영속 통화(currentUser.ecommerce_preferred_currency, D-LOGIN-CUR) > localStorage 세션값(g7_preferred_currency, ISO형식) > 게스트 사이트 언어(g7_locale) 기반 통화 추정(currencies[].locales 매핑, 회원가입 통화 부여와 동일 입력·우선순위) > default_currency > KRW. 핸들러는 이커머스 모듈 소유(sirsoft-ecommerce.initPreferredCurrency) — 통화=커머스 책임. 모듈 미설치 시 미등록 핸들러로 무동작(통화 globals 자체가 빈 값).",
|
||||
"handler": "sirsoft-ecommerce.initPreferredCurrency",
|
||||
"params": {
|
||||
"defaultCurrency": "{{_global.defaultCurrency ?? 'KRW'}}"
|
||||
"defaultCurrency": "{{_global.defaultCurrency}}"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -132,7 +132,7 @@
|
||||
"handler": "sirsoft-ecommerce.initPreferredCurrency",
|
||||
"params": {
|
||||
"accountCurrency": "{{response.data?.data?.ecommerce_preferred_currency ?? response.data?.ecommerce_preferred_currency}}",
|
||||
"defaultCurrency": "{{_global.defaultCurrency ?? 'KRW'}}"
|
||||
"defaultCurrency": "{{_global.defaultCurrency}}"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -1889,7 +1889,7 @@
|
||||
"availableLocales": "{{$locales}}",
|
||||
"currentLocale": "{{$locale}}",
|
||||
"availableCurrencies": "{{_global.availableCurrencies ?? []}}",
|
||||
"preferredCurrency": "{{_global.preferredCurrency ?? _global.defaultCurrency ?? 'KRW'}}",
|
||||
"preferredCurrency": "{{_global.preferredCurrency ?? _global.defaultCurrency}}",
|
||||
"className": "hidden",
|
||||
"notifications": "{{(notifications?.data?.data ?? []).map(n => ({ id: n.id, title: n.subject ?? '', message: n.body ?? '', time: n.created_at ?? '', read: !!n.read_at, url: n.url ?? '', data: n.data ?? {} }))}}",
|
||||
"notificationHasMore": "{{_global.notificationsHasMore ?? notifications?.data?.pagination?.has_more_pages ?? false}}",
|
||||
|
||||
@@ -5,22 +5,47 @@
|
||||
},
|
||||
"type": "basic",
|
||||
"name": "Div",
|
||||
"props": { "className": "relative" },
|
||||
"props": {
|
||||
"className": "relative"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "basic",
|
||||
"name": "Button",
|
||||
"props": { "className": "flex items-center gap-1.5 px-3 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors" },
|
||||
"props": {
|
||||
"className": "flex items-center gap-1.5 px-3 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
|
||||
},
|
||||
"children": [
|
||||
{ "type": "basic", "name": "Icon", "props": { "name": "globe", "className": "w-4 h-4" } },
|
||||
{ "type": "basic", "name": "Span", "text": "{{_global.preferredCurrency ?? 'KRW'}}" },
|
||||
{ "type": "basic", "name": "Icon", "props": { "name": "chevron-down", "className": "w-3 h-3" } }
|
||||
{
|
||||
"type": "basic",
|
||||
"name": "Icon",
|
||||
"props": {
|
||||
"name": "globe",
|
||||
"className": "text-base"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "basic",
|
||||
"name": "Span",
|
||||
"text": "{{_global.preferredCurrency ?? _global.defaultCurrency}}"
|
||||
},
|
||||
{
|
||||
"type": "basic",
|
||||
"name": "Icon",
|
||||
"props": {
|
||||
"name": "chevron-down",
|
||||
"className": "text-xs"
|
||||
}
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"type": "click",
|
||||
"handler": "setState",
|
||||
"params": { "target": "local", "showCurrencyDropdown": "{{!_local.showCurrencyDropdown}}" }
|
||||
"params": {
|
||||
"target": "local",
|
||||
"showCurrencyDropdown": "{{!_local.showCurrencyDropdown}}"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -29,12 +54,17 @@
|
||||
"type": "basic",
|
||||
"name": "Div",
|
||||
"if": "{{_local.showCurrencyDropdown}}",
|
||||
"props": { "className": "absolute right-0 top-full mt-1 w-44 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg z-50 py-1 overflow-hidden" },
|
||||
"props": {
|
||||
"className": "absolute right-0 top-full mt-1 w-44 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg z-50 py-1 overflow-hidden"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "basic",
|
||||
"name": "Div",
|
||||
"iteration": { "source": "{{_global.availableCurrencies ?? []}}", "item_var": "currency" },
|
||||
"iteration": {
|
||||
"source": "{{_global.availableCurrencies ?? []}}",
|
||||
"item_var": "currency"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "basic",
|
||||
@@ -43,21 +73,47 @@
|
||||
"className": "w-full px-4 py-2.5 text-left text-sm flex items-center gap-3 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors {{_global.preferredCurrency === currency.code ? 'bg-blue-50 dark:bg-blue-900/20 text-blue-600 dark:text-blue-400' : 'text-gray-700 dark:text-gray-300'}}"
|
||||
},
|
||||
"children": [
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-base" }, "text": "{{currency.flag}}" },
|
||||
{
|
||||
"type": "basic",
|
||||
"name": "Span",
|
||||
"props": {
|
||||
"className": "text-base"
|
||||
},
|
||||
"text": "{{currency.flag}}"
|
||||
},
|
||||
{
|
||||
"type": "basic",
|
||||
"name": "Div",
|
||||
"props": { "className": "flex-1" },
|
||||
"props": {
|
||||
"className": "flex-1"
|
||||
},
|
||||
"children": [
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "font-medium" }, "text": "{{currency.code}}" },
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-gray-500 dark:text-gray-400 ml-1" }, "text": "({{currency.symbol}})" }
|
||||
{
|
||||
"type": "basic",
|
||||
"name": "Span",
|
||||
"props": {
|
||||
"className": "font-medium"
|
||||
},
|
||||
"text": "{{currency.code}}"
|
||||
},
|
||||
{
|
||||
"type": "basic",
|
||||
"name": "Span",
|
||||
"props": {
|
||||
"className": "text-gray-500 dark:text-gray-400 ml-1"
|
||||
},
|
||||
"text": "({{currency.symbol}})"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "basic",
|
||||
"name": "Icon",
|
||||
"if": "{{_global.preferredCurrency === currency.code}}",
|
||||
"props": { "name": "check", "className": "w-4 h-4" }
|
||||
"props": {
|
||||
"name": "check",
|
||||
"className": "text-base"
|
||||
}
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
@@ -65,9 +121,27 @@
|
||||
"type": "click",
|
||||
"handler": "sequence",
|
||||
"actions": [
|
||||
{ "handler": "setState", "params": { "target": "global", "preferredCurrency": "{{currency.code}}" } },
|
||||
{ "handler": "saveToLocalStorage", "params": { "key": "g7_preferred_currency", "value": "{{currency.code}}" } },
|
||||
{ "handler": "setState", "params": { "target": "local", "showCurrencyDropdown": false } }
|
||||
{
|
||||
"handler": "setState",
|
||||
"params": {
|
||||
"target": "global",
|
||||
"preferredCurrency": "{{currency.code}}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"handler": "saveToLocalStorage",
|
||||
"params": {
|
||||
"key": "g7_preferred_currency",
|
||||
"value": "{{currency.code}}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"handler": "setState",
|
||||
"params": {
|
||||
"target": "local",
|
||||
"showCurrencyDropdown": false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -81,12 +155,17 @@
|
||||
"type": "basic",
|
||||
"name": "Div",
|
||||
"if": "{{_local.showCurrencyDropdown}}",
|
||||
"props": { "className": "fixed inset-0 z-40" },
|
||||
"props": {
|
||||
"className": "fixed inset-0 z-40"
|
||||
},
|
||||
"actions": [
|
||||
{
|
||||
"type": "click",
|
||||
"handler": "setState",
|
||||
"params": { "target": "local", "showCurrencyDropdown": false }
|
||||
"params": {
|
||||
"target": "local",
|
||||
"showCurrencyDropdown": false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -569,7 +569,7 @@
|
||||
"type": "basic",
|
||||
"name": "Span",
|
||||
"props": { "className": "text-base font-bold text-gray-900 dark:text-white ml-1" },
|
||||
"text": "{{item.mc_final_amount?.[order.data.base_currency ?? _global.preferredCurrency ?? 'KRW']?.formatted ?? item.subtotal_price_formatted ?? ''}}",
|
||||
"text": "{{item.mc_final_amount?.[order.data.base_currency ?? _global.preferredCurrency ?? _global.defaultCurrency]?.formatted ?? item.subtotal_price_formatted ?? ''}}",
|
||||
"responsive": {
|
||||
"portable": {
|
||||
"props": { "className": "text-sm font-bold text-gray-900 dark:text-white ml-1" }
|
||||
@@ -597,7 +597,7 @@
|
||||
"name": "Div",
|
||||
"props": { "className": "flex flex-wrap gap-x-2 justify-end" },
|
||||
"iteration": {
|
||||
"source": "{{Object.entries(item.mc_final_amount ?? item.mc_subtotal_price ?? {}).filter(([code]) => code !== (order.data.base_currency ?? _global.preferredCurrency ?? 'KRW'))}}",
|
||||
"source": "{{Object.entries(item.mc_final_amount ?? item.mc_subtotal_price ?? {}).filter(([code]) => code !== (order.data.base_currency ?? _global.preferredCurrency ?? _global.defaultCurrency))}}",
|
||||
"item_var": "currency"
|
||||
},
|
||||
"children": [
|
||||
|
||||
@@ -824,7 +824,7 @@
|
||||
"name": "Div",
|
||||
"props": { "className": "flex flex-wrap gap-x-2 mt-0.5" },
|
||||
"iteration": {
|
||||
"source": "{{Object.entries(item.mc_subtotal_price ?? {}).filter(([code]) => code !== (order.base_currency ?? _global.preferredCurrency ?? 'KRW'))}}",
|
||||
"source": "{{Object.entries(item.mc_subtotal_price ?? {}).filter(([code]) => code !== (order.base_currency ?? _global.preferredCurrency ?? _global.defaultCurrency))}}",
|
||||
"item_var": "currency"
|
||||
},
|
||||
"children": [
|
||||
@@ -908,7 +908,7 @@
|
||||
"name": "Div",
|
||||
"props": { "className": "flex flex-wrap gap-x-2" },
|
||||
"iteration": {
|
||||
"source": "{{Object.entries(order.mc_total_amount ?? {}).filter(([code]) => code !== (order.base_currency ?? _global.preferredCurrency ?? 'KRW'))}}",
|
||||
"source": "{{Object.entries(order.mc_total_amount ?? {}).filter(([code]) => code !== (order.base_currency ?? _global.preferredCurrency ?? _global.defaultCurrency))}}",
|
||||
"item_var": "currency"
|
||||
},
|
||||
"children": [
|
||||
|
||||
@@ -399,7 +399,7 @@
|
||||
"name": "Span",
|
||||
"props": { "className": "text-xs text-gray-400 dark:text-gray-500 block" },
|
||||
"iteration": {
|
||||
"source": "{{Object.entries(_local.refundEstimate?.mc_original_snapshot?.mc_total_list_price_amount_formatted ?? {}).filter(([code]) => code !== (_local.refundEstimate?.original_snapshot?.base_currency ?? _global.preferredCurrency ?? 'KRW'))}}",
|
||||
"source": "{{Object.entries(_local.refundEstimate?.mc_original_snapshot?.mc_total_list_price_amount_formatted ?? {}).filter(([code]) => code !== (_local.refundEstimate?.original_snapshot?.base_currency ?? _global.preferredCurrency ?? _global.defaultCurrency))}}",
|
||||
"item_var": "mc"
|
||||
},
|
||||
"text": "{{mc[1]}}"
|
||||
@@ -423,7 +423,7 @@
|
||||
"name": "Span",
|
||||
"props": { "className": "text-xs text-gray-400 dark:text-gray-500 block" },
|
||||
"iteration": {
|
||||
"source": "{{Object.entries(_local.refundEstimate?.mc_recalculated_snapshot?.mc_total_list_price_amount_formatted ?? {}).filter(([code]) => code !== (_local.refundEstimate?.original_snapshot?.base_currency ?? _global.preferredCurrency ?? 'KRW'))}}",
|
||||
"source": "{{Object.entries(_local.refundEstimate?.mc_recalculated_snapshot?.mc_total_list_price_amount_formatted ?? {}).filter(([code]) => code !== (_local.refundEstimate?.original_snapshot?.base_currency ?? _global.preferredCurrency ?? _global.defaultCurrency))}}",
|
||||
"item_var": "mc"
|
||||
},
|
||||
"text": "{{mc[1]}}"
|
||||
@@ -456,7 +456,7 @@
|
||||
"name": "Span",
|
||||
"props": { "className": "text-xs text-gray-400 dark:text-gray-500 block" },
|
||||
"iteration": {
|
||||
"source": "{{Object.entries(_local.refundEstimate?.mc_original_snapshot?.mc_subtotal_amount_formatted ?? {}).filter(([code]) => code !== (_local.refundEstimate?.original_snapshot?.base_currency ?? _global.preferredCurrency ?? 'KRW'))}}",
|
||||
"source": "{{Object.entries(_local.refundEstimate?.mc_original_snapshot?.mc_subtotal_amount_formatted ?? {}).filter(([code]) => code !== (_local.refundEstimate?.original_snapshot?.base_currency ?? _global.preferredCurrency ?? _global.defaultCurrency))}}",
|
||||
"item_var": "mc"
|
||||
},
|
||||
"text": "{{mc[1]}}"
|
||||
@@ -480,7 +480,7 @@
|
||||
"name": "Span",
|
||||
"props": { "className": "text-xs text-gray-400 dark:text-gray-500 block" },
|
||||
"iteration": {
|
||||
"source": "{{Object.entries(_local.refundEstimate?.mc_recalculated_snapshot?.mc_subtotal_amount_formatted ?? {}).filter(([code]) => code !== (_local.refundEstimate?.original_snapshot?.base_currency ?? _global.preferredCurrency ?? 'KRW'))}}",
|
||||
"source": "{{Object.entries(_local.refundEstimate?.mc_recalculated_snapshot?.mc_subtotal_amount_formatted ?? {}).filter(([code]) => code !== (_local.refundEstimate?.original_snapshot?.base_currency ?? _global.preferredCurrency ?? _global.defaultCurrency))}}",
|
||||
"item_var": "mc"
|
||||
},
|
||||
"text": "{{mc[1]}}"
|
||||
@@ -771,7 +771,7 @@
|
||||
"name": "Span",
|
||||
"props": { "className": "text-xs text-gray-400 dark:text-gray-500 block font-normal" },
|
||||
"iteration": {
|
||||
"source": "{{Object.entries(_local.refundEstimate?.mc_original_snapshot?.mc_total_paid_amount_formatted ?? {}).filter(([code]) => code !== (_local.refundEstimate?.original_snapshot?.base_currency ?? _global.preferredCurrency ?? 'KRW'))}}",
|
||||
"source": "{{Object.entries(_local.refundEstimate?.mc_original_snapshot?.mc_total_paid_amount_formatted ?? {}).filter(([code]) => code !== (_local.refundEstimate?.original_snapshot?.base_currency ?? _global.preferredCurrency ?? _global.defaultCurrency))}}",
|
||||
"item_var": "mc"
|
||||
},
|
||||
"text": "{{mc[1]}}"
|
||||
@@ -795,7 +795,7 @@
|
||||
"name": "Span",
|
||||
"props": { "className": "text-xs text-gray-400 dark:text-gray-500 block font-normal" },
|
||||
"iteration": {
|
||||
"source": "{{Object.entries(_local.refundEstimate?.mc_recalculated_snapshot?.mc_total_paid_amount_formatted ?? {}).filter(([code]) => code !== (_local.refundEstimate?.original_snapshot?.base_currency ?? _global.preferredCurrency ?? 'KRW'))}}",
|
||||
"source": "{{Object.entries(_local.refundEstimate?.mc_recalculated_snapshot?.mc_total_paid_amount_formatted ?? {}).filter(([code]) => code !== (_local.refundEstimate?.original_snapshot?.base_currency ?? _global.preferredCurrency ?? _global.defaultCurrency))}}",
|
||||
"item_var": "mc"
|
||||
},
|
||||
"text": "{{mc[1]}}"
|
||||
|
||||
@@ -541,7 +541,7 @@
|
||||
"name": "Div",
|
||||
"props": { "className": "flex flex-wrap gap-x-2 justify-end mt-0.5" },
|
||||
"iteration": {
|
||||
"source": "{{Object.entries(order.data.mc_total_amount ?? {}).filter(([code]) => code !== (order.data.base_currency ?? _global.preferredCurrency ?? 'KRW'))}}",
|
||||
"source": "{{Object.entries(order.data.mc_total_amount ?? {}).filter(([code]) => code !== (order.data.base_currency ?? _global.preferredCurrency ?? _global.defaultCurrency))}}",
|
||||
"item_var": "currency"
|
||||
},
|
||||
"children": [
|
||||
|
||||
@@ -320,7 +320,7 @@
|
||||
"type": "basic",
|
||||
"name": "Span",
|
||||
"props": { "className": "text-sm font-medium {{Number(shipping.total_shipping_amount ?? 0) === 0 ? 'text-green-600 dark:text-green-400' : 'text-gray-900 dark:text-white'}}" },
|
||||
"text": "{{Number(shipping.total_shipping_amount ?? 0) === 0 ? '$t:mypage.order_detail.free_shipping' : (shipping.mc_total_shipping_amount?.[_global.preferredCurrency ?? 'KRW']?.formatted ?? shipping.total_shipping_amount_formatted ?? '')}}"
|
||||
"text": "{{Number(shipping.total_shipping_amount ?? 0) === 0 ? '$t:mypage.order_detail.free_shipping' : (shipping.mc_total_shipping_amount?.[_global.preferredCurrency ?? _global.defaultCurrency]?.formatted ?? shipping.total_shipping_amount_formatted ?? '')}}"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -1379,7 +1379,7 @@
|
||||
"name": "Select",
|
||||
"props": {
|
||||
"name": "ecommerce_preferred_currency",
|
||||
"value": "{{user.data?.ecommerce_preferred_currency ?? _global.modules?.['sirsoft-ecommerce']?.language_currency?.default_currency ?? 'KRW'}}",
|
||||
"value": "{{user.data?.ecommerce_preferred_currency ?? _global.modules?.['sirsoft-ecommerce']?.language_currency?.default_currency}}",
|
||||
"options": "{{(_global.modules?.['sirsoft-ecommerce']?.language_currency?.currencies ?? []).filter(c => c.is_default || (c.exchange_rate ?? 0) > 0).map(c => ({ value: c.code, label: c.code + ' (' + (c.symbol ?? c.code) + ')' }))}}",
|
||||
"className": "w-full sm:w-64"
|
||||
}
|
||||
|
||||
@@ -779,7 +779,7 @@
|
||||
"props": {
|
||||
"className": "text-base text-gray-900 dark:text-white font-medium"
|
||||
},
|
||||
"text": "{{user.data?.ecommerce_preferred_currency ?? _global.modules?.['sirsoft-ecommerce']?.language_currency?.default_currency ?? 'KRW'}}"
|
||||
"text": "{{user.data?.ecommerce_preferred_currency ?? _global.modules?.['sirsoft-ecommerce']?.language_currency?.default_currency}}"
|
||||
},
|
||||
{
|
||||
"type": "basic",
|
||||
|
||||
@@ -54,14 +54,14 @@
|
||||
"type": "basic",
|
||||
"name": "Span",
|
||||
"props": { "className": "font-bold text-gray-900 dark:text-white" },
|
||||
"text": "{{product.multi_currency_selling_price?.[_global.preferredCurrency ?? 'KRW']?.formatted ?? product.selling_price_formatted}}"
|
||||
"text": "{{product.multi_currency_selling_price?.[_global.preferredCurrency ?? _global.defaultCurrency]?.formatted ?? product.selling_price_formatted}}"
|
||||
},
|
||||
{
|
||||
"type": "basic",
|
||||
"name": "Span",
|
||||
"if": "{{product.discount_rate > 0}}",
|
||||
"props": { "className": "text-sm text-gray-500 dark:text-gray-400 line-through" },
|
||||
"text": "{{product.multi_currency_list_price?.[_global.preferredCurrency ?? 'KRW']?.formatted ?? product.list_price_formatted}}"
|
||||
"text": "{{product.multi_currency_list_price?.[_global.preferredCurrency ?? _global.defaultCurrency]?.formatted ?? product.list_price_formatted}}"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
"type": "basic",
|
||||
"name": "P",
|
||||
"props": { "className": "text-xs text-gray-500 dark:text-gray-400" },
|
||||
"text": "{{addOpt.group_name}}: {{addOpt.name}}{{(addOpt.custom_text ?? '') !== '' ? ' (' + addOpt.custom_text + ')' : ''}}{{(addOpt.price_adjustment ?? 0) > 0 ? ' (' + (addOpt.price_adjustment_formatted ?? ('+' + addOpt.price_adjustment.toLocaleString())) + ')' : ''}}"
|
||||
"text": "{{addOpt.group_name}}: {{addOpt.name}}{{(addOpt.custom_text ?? '') !== '' ? ' (' + addOpt.custom_text + ')' : ''}}{{(addOpt.price_adjustment ?? 0) > 0 ? ' (' + (addOpt.multi_currency_price_adjustment?.[_global.preferredCurrency ?? _global.defaultCurrency]?.formatted ?? addOpt.price_adjustment_formatted ?? ('+' + addOpt.price_adjustment.toLocaleString())) + ')' : ''}}"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -145,7 +145,7 @@
|
||||
"type": "basic",
|
||||
"name": "Span",
|
||||
"props": { "className": "text-xs text-gray-400 dark:text-gray-500 line-through" },
|
||||
"text": "{{item.product_option?.multi_currency_list_price?.[_global.preferredCurrency ?? 'KRW']?.formatted ?? item.product_option?.list_price_formatted ?? ''}}"
|
||||
"text": "{{item.product_option?.multi_currency_list_price?.[_global.preferredCurrency ?? _global.defaultCurrency]?.formatted ?? item.product_option?.list_price_formatted ?? ''}}"
|
||||
},
|
||||
{
|
||||
"comment": "할인율 칩",
|
||||
@@ -163,7 +163,7 @@
|
||||
"if": "{{(item.product_option?.discount_rate ?? 0) > 0}}",
|
||||
"props": { "className": "flex flex-wrap gap-x-2" },
|
||||
"iteration": {
|
||||
"source": "{{Object.entries(item.product_option?.multi_currency_list_price ?? {}).filter(([code]) => code !== (_global.preferredCurrency ?? 'KRW'))}}",
|
||||
"source": "{{Object.entries(item.product_option?.multi_currency_list_price ?? {}).filter(([code]) => code !== (_global.preferredCurrency ?? _global.defaultCurrency))}}",
|
||||
"item_var": "currency"
|
||||
},
|
||||
"children": [
|
||||
@@ -180,7 +180,7 @@
|
||||
"type": "basic",
|
||||
"name": "Div",
|
||||
"props": { "className": "text-sm font-medium text-gray-900 dark:text-white" },
|
||||
"text": "{{item.product_option?.multi_currency_selling_price?.[_global.preferredCurrency ?? 'KRW']?.formatted ?? item.product_option?.selling_price_formatted ?? ''}}"
|
||||
"text": "{{item.product_option?.multi_currency_selling_price?.[_global.preferredCurrency ?? _global.defaultCurrency]?.formatted ?? item.product_option?.selling_price_formatted ?? ''}}"
|
||||
},
|
||||
{
|
||||
"comment": "보조 통화들 (선택된 통화 제외)",
|
||||
@@ -188,7 +188,7 @@
|
||||
"name": "Div",
|
||||
"props": { "className": "flex flex-wrap gap-x-2" },
|
||||
"iteration": {
|
||||
"source": "{{Object.entries(item.product_option?.multi_currency_selling_price ?? {}).filter(([code]) => code !== (_global.preferredCurrency ?? 'KRW'))}}",
|
||||
"source": "{{Object.entries(item.product_option?.multi_currency_selling_price ?? {}).filter(([code]) => code !== (_global.preferredCurrency ?? _global.defaultCurrency))}}",
|
||||
"item_var": "currency"
|
||||
},
|
||||
"children": [
|
||||
@@ -400,7 +400,7 @@
|
||||
"type": "basic",
|
||||
"name": "Div",
|
||||
"props": { "className": "font-bold text-gray-900 dark:text-white" },
|
||||
"text": "{{item.multi_currency_subtotal?.[_global.preferredCurrency ?? 'KRW']?.formatted ?? item.subtotal_formatted}}"
|
||||
"text": "{{item.multi_currency_subtotal?.[_global.preferredCurrency ?? _global.defaultCurrency]?.formatted ?? item.subtotal_formatted}}"
|
||||
},
|
||||
{
|
||||
"comment": "보조 통화들",
|
||||
@@ -408,7 +408,7 @@
|
||||
"name": "Div",
|
||||
"props": { "className": "flex flex-wrap gap-x-2 justify-end" },
|
||||
"iteration": {
|
||||
"source": "{{Object.entries(item.multi_currency_subtotal ?? {}).filter(([code]) => code !== (_global.preferredCurrency ?? 'KRW'))}}",
|
||||
"source": "{{Object.entries(item.multi_currency_subtotal ?? {}).filter(([code]) => code !== (_global.preferredCurrency ?? _global.defaultCurrency))}}",
|
||||
"item_var": "currency"
|
||||
},
|
||||
"children": [
|
||||
|
||||
@@ -118,7 +118,7 @@
|
||||
"type": "basic",
|
||||
"name": "Span",
|
||||
"props": { "className": "font-medium text-gray-900 dark:text-white" },
|
||||
"text": "{{cartItems.data.calculation?.summary?.multi_currency[_global.preferredCurrency ?? 'KRW']?.subtotal_formatted ?? cartItems.data.calculation?.summary?.subtotal_formatted ?? '-'}}"
|
||||
"text": "{{cartItems.data.calculation?.summary?.multi_currency[_global.preferredCurrency ?? _global.defaultCurrency]?.subtotal_formatted ?? cartItems.data.calculation?.summary?.subtotal_formatted ?? '-'}}"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -136,7 +136,7 @@
|
||||
"props": { "className": "flex justify-between text-xs" },
|
||||
"children": [
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-gray-500 dark:text-gray-500" }, "text": "$t:shop.cart.taxable_amount" },
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-gray-600 dark:text-gray-400" }, "text": "{{cartItems.data.calculation?.summary?.multi_currency[_global.preferredCurrency ?? 'KRW']?.taxable_amount_formatted ?? '-'}}" }
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-gray-600 dark:text-gray-400" }, "text": "{{cartItems.data.calculation?.summary?.multi_currency[_global.preferredCurrency ?? _global.defaultCurrency]?.taxable_amount_formatted ?? '-'}}" }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -146,7 +146,7 @@
|
||||
"props": { "className": "flex justify-between text-xs" },
|
||||
"children": [
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-gray-500 dark:text-gray-500" }, "text": "$t:shop.cart.tax_free_amount" },
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-gray-600 dark:text-gray-400" }, "text": "{{cartItems.data.calculation?.summary?.multi_currency[_global.preferredCurrency ?? 'KRW']?.tax_free_amount_formatted ?? '-'}}" }
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-gray-600 dark:text-gray-400" }, "text": "{{cartItems.data.calculation?.summary?.multi_currency[_global.preferredCurrency ?? _global.defaultCurrency]?.tax_free_amount_formatted ?? '-'}}" }
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -165,7 +165,7 @@
|
||||
"props": { "className": "flex justify-between text-sm" },
|
||||
"children": [
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-gray-600 dark:text-gray-400" }, "text": "$t:shop.cart.discount_total" },
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "font-medium text-red-600 dark:text-red-400" }, "text": "-{{cartItems.data.calculation?.summary?.multi_currency[_global.preferredCurrency ?? 'KRW']?.total_discount_formatted ?? cartItems.data.calculation?.summary?.discount_formatted}}" }
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "font-medium text-red-600 dark:text-red-400" }, "text": "-{{cartItems.data.calculation?.summary?.multi_currency[_global.preferredCurrency ?? _global.defaultCurrency]?.total_discount_formatted ?? cartItems.data.calculation?.summary?.discount_formatted}}" }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -181,7 +181,7 @@
|
||||
"props": { "className": "flex justify-between text-xs" },
|
||||
"children": [
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-gray-500 dark:text-gray-500" }, "text": "$t:shop.cart.coupon_discount" },
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-red-500 dark:text-red-400" }, "text": "-{{cartItems.data.calculation?.summary?.multi_currency[_global.preferredCurrency ?? 'KRW']?.product_coupon_discount_formatted ?? '-'}}" }
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-red-500 dark:text-red-400" }, "text": "-{{cartItems.data.calculation?.summary?.multi_currency[_global.preferredCurrency ?? _global.defaultCurrency]?.product_coupon_discount_formatted ?? '-'}}" }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -191,7 +191,7 @@
|
||||
"props": { "className": "flex justify-between text-xs" },
|
||||
"children": [
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-gray-500 dark:text-gray-500" }, "text": "$t:shop.cart.code_discount" },
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-red-500 dark:text-red-400" }, "text": "-{{cartItems.data.calculation?.summary?.multi_currency[_global.preferredCurrency ?? 'KRW']?.code_discount_formatted ?? '-'}}" }
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-red-500 dark:text-red-400" }, "text": "-{{cartItems.data.calculation?.summary?.multi_currency[_global.preferredCurrency ?? _global.defaultCurrency]?.code_discount_formatted ?? '-'}}" }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -201,7 +201,7 @@
|
||||
"props": { "className": "flex justify-between text-xs" },
|
||||
"children": [
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-gray-500 dark:text-gray-500" }, "text": "$t:shop.cart.order_discount" },
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-red-500 dark:text-red-400" }, "text": "-{{cartItems.data.calculation?.summary?.multi_currency[_global.preferredCurrency ?? 'KRW']?.order_coupon_discount_formatted ?? '-'}}" }
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-red-500 dark:text-red-400" }, "text": "-{{cartItems.data.calculation?.summary?.multi_currency[_global.preferredCurrency ?? _global.defaultCurrency]?.order_coupon_discount_formatted ?? '-'}}" }
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -223,7 +223,7 @@
|
||||
"type": "basic",
|
||||
"name": "Span",
|
||||
"props": { "className": "font-medium {{(cartItems.data.calculation?.summary?.total_shipping ?? 0) === 0 ? 'text-green-600 dark:text-green-400' : 'text-gray-900 dark:text-white'}}" },
|
||||
"text": "{{(cartItems.data.calculation?.summary?.total_shipping ?? 0) === 0 ? '$t:shop.free_shipping' : (cartItems.data.calculation?.summary?.multi_currency[_global.preferredCurrency ?? 'KRW']?.total_shipping_formatted ?? cartItems.data.calculation?.summary?.shipping_fee_formatted)}}"
|
||||
"text": "{{(cartItems.data.calculation?.summary?.total_shipping ?? 0) === 0 ? '$t:shop.free_shipping' : (cartItems.data.calculation?.summary?.multi_currency[_global.preferredCurrency ?? _global.defaultCurrency]?.total_shipping_formatted ?? cartItems.data.calculation?.summary?.shipping_fee_formatted)}}"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -241,7 +241,7 @@
|
||||
"props": { "className": "flex justify-between text-xs" },
|
||||
"children": [
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-gray-500 dark:text-gray-500" }, "text": "$t:shop.cart.base_shipping" },
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-gray-600 dark:text-gray-400" }, "text": "{{cartItems.data.calculation?.summary?.multi_currency[_global.preferredCurrency ?? 'KRW']?.base_shipping_total_formatted ?? '-'}}" }
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-gray-600 dark:text-gray-400" }, "text": "{{cartItems.data.calculation?.summary?.multi_currency[_global.preferredCurrency ?? _global.defaultCurrency]?.base_shipping_total_formatted ?? '-'}}" }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -251,7 +251,7 @@
|
||||
"props": { "className": "flex justify-between text-xs" },
|
||||
"children": [
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-gray-500 dark:text-gray-500" }, "text": "$t:shop.cart.extra_shipping" },
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-gray-600 dark:text-gray-400" }, "text": "{{cartItems.data.calculation?.summary?.multi_currency[_global.preferredCurrency ?? 'KRW']?.extra_shipping_total_formatted ?? '-'}}" }
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-gray-600 dark:text-gray-400" }, "text": "{{cartItems.data.calculation?.summary?.multi_currency[_global.preferredCurrency ?? _global.defaultCurrency]?.extra_shipping_total_formatted ?? '-'}}" }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -261,7 +261,7 @@
|
||||
"props": { "className": "flex justify-between text-xs" },
|
||||
"children": [
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-gray-500 dark:text-gray-500" }, "text": "$t:shop.cart.shipping_discount" },
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-red-500 dark:text-red-400" }, "text": "-{{cartItems.data.calculation?.summary?.multi_currency[_global.preferredCurrency ?? 'KRW']?.shipping_discount_formatted ?? '-'}}" }
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-red-500 dark:text-red-400" }, "text": "-{{cartItems.data.calculation?.summary?.multi_currency[_global.preferredCurrency ?? _global.defaultCurrency]?.shipping_discount_formatted ?? '-'}}" }
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -275,7 +275,7 @@
|
||||
"props": { "className": "flex justify-between text-sm pt-2" },
|
||||
"children": [
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-gray-600 dark:text-gray-400" }, "text": "$t:shop.cart.payment_amount" },
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "font-medium text-gray-900 dark:text-white" }, "text": "{{cartItems.data.calculation?.summary?.multi_currency[_global.preferredCurrency ?? 'KRW']?.payment_amount_formatted ?? cartItems.data.calculation?.summary?.payment_amount_formatted ?? '-'}}" }
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "font-medium text-gray-900 dark:text-white" }, "text": "{{cartItems.data.calculation?.summary?.multi_currency[_global.preferredCurrency ?? _global.defaultCurrency]?.payment_amount_formatted ?? cartItems.data.calculation?.summary?.payment_amount_formatted ?? '-'}}" }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -303,7 +303,7 @@
|
||||
"props": { "className": "flex justify-between items-center" },
|
||||
"children": [
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-lg font-semibold text-gray-900 dark:text-white" }, "text": "$t:shop.cart.total" },
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-xl font-bold text-gray-900 dark:text-white" }, "text": "{{cartItems.data.calculation?.summary?.multi_currency[_global.preferredCurrency ?? 'KRW']?.final_amount_formatted ?? cartItems.data.calculation?.summary?.final_amount_formatted ?? '-'}}" }
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-xl font-bold text-gray-900 dark:text-white" }, "text": "{{cartItems.data.calculation?.summary?.multi_currency[_global.preferredCurrency ?? _global.defaultCurrency]?.final_amount_formatted ?? cartItems.data.calculation?.summary?.final_amount_formatted ?? '-'}}" }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -312,7 +312,7 @@
|
||||
"name": "Div",
|
||||
"props": { "className": "flex flex-wrap gap-x-2 justify-end mt-1" },
|
||||
"iteration": {
|
||||
"source": "{{Object.entries(cartItems.data.calculation?.summary?.multi_currency ?? {}).filter(([code]) => code !== (_global.preferredCurrency ?? 'KRW'))}}",
|
||||
"source": "{{Object.entries(cartItems.data.calculation?.summary?.multi_currency ?? {}).filter(([code]) => code !== (_global.preferredCurrency ?? _global.defaultCurrency))}}",
|
||||
"item_var": "currency"
|
||||
},
|
||||
"children": [
|
||||
|
||||
@@ -167,7 +167,7 @@
|
||||
"props": {
|
||||
"value": "{{coupon.id}}"
|
||||
},
|
||||
"text": "{{'[' + coupon.target_type_short_label + '] ' + coupon.localized_name + ' (' + (coupon.multi_currency_benefit_formatted?.[_global.preferredCurrency ?? 'KRW'] ?? coupon.benefit_formatted) + ')'}}"
|
||||
"text": "{{'[' + coupon.target_type_short_label + '] ' + coupon.localized_name + ' (' + (coupon.multi_currency_benefit_formatted?.[_global.preferredCurrency ?? _global.defaultCurrency] ?? coupon.benefit_formatted) + ')'}}"
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
@@ -225,7 +225,7 @@
|
||||
"props": { "className": "mt-2 text-sm text-green-600 dark:text-green-400 flex items-center gap-1" },
|
||||
"children": [
|
||||
{ "type": "basic", "name": "Icon", "props": { "name": "check-circle", "size": "sm" } },
|
||||
{ "type": "basic", "name": "Span", "text": "-{{checkoutData.data.calculation?.summary?.multi_currency?.[_global.preferredCurrency ?? 'KRW']?.order_coupon_discount_formatted ?? checkoutData.data.calculation?.summary?.order_coupon_discount_formatted}}" }
|
||||
{ "type": "basic", "name": "Span", "text": "-{{checkoutData.data.calculation?.summary?.multi_currency?.[_global.preferredCurrency ?? _global.defaultCurrency]?.order_coupon_discount_formatted ?? checkoutData.data.calculation?.summary?.order_coupon_discount_formatted}}" }
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -291,7 +291,7 @@
|
||||
"props": {
|
||||
"value": "{{coupon.id}}"
|
||||
},
|
||||
"text": "{{'[' + coupon.target_type_short_label + '] ' + coupon.localized_name + ' (' + (coupon.multi_currency_benefit_formatted?.[_global.preferredCurrency ?? 'KRW'] ?? coupon.benefit_formatted) + ')'}}"
|
||||
"text": "{{'[' + coupon.target_type_short_label + '] ' + coupon.localized_name + ' (' + (coupon.multi_currency_benefit_formatted?.[_global.preferredCurrency ?? _global.defaultCurrency] ?? coupon.benefit_formatted) + ')'}}"
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
@@ -349,7 +349,7 @@
|
||||
"props": { "className": "mt-2 text-sm text-green-600 dark:text-green-400 flex items-center gap-1" },
|
||||
"children": [
|
||||
{ "type": "basic", "name": "Icon", "props": { "name": "check-circle", "size": "sm" } },
|
||||
{ "type": "basic", "name": "Span", "text": "-{{checkoutData.data.calculation?.summary?.multi_currency?.[_global.preferredCurrency ?? 'KRW']?.shipping_discount_formatted ?? checkoutData.data.calculation?.summary?.shipping_discount_formatted ?? (checkoutData.data.calculation?.summary?.shipping_discount + '원')}}" }
|
||||
{ "type": "basic", "name": "Span", "text": "-{{checkoutData.data.calculation?.summary?.multi_currency?.[_global.preferredCurrency ?? _global.defaultCurrency]?.shipping_discount_formatted ?? checkoutData.data.calculation?.summary?.shipping_discount_formatted ?? (checkoutData.data.calculation?.summary?.shipping_discount + '원')}}" }
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -459,7 +459,7 @@
|
||||
"props": { "className": "mt-2 text-sm text-green-600 dark:text-green-400 flex items-center gap-1" },
|
||||
"children": [
|
||||
{ "type": "basic", "name": "Icon", "props": { "name": "check-circle", "size": "sm" } },
|
||||
{ "type": "basic", "name": "Span", "text": "-{{checkoutData.data.calculation?.summary?.multi_currency?.[_global.preferredCurrency ?? 'KRW']?.code_discount_formatted ?? checkoutData.data.calculation?.summary?.code_discount_formatted}}" }
|
||||
{ "type": "basic", "name": "Span", "text": "-{{checkoutData.data.calculation?.summary?.multi_currency?.[_global.preferredCurrency ?? _global.defaultCurrency]?.code_discount_formatted ?? checkoutData.data.calculation?.summary?.code_discount_formatted}}" }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -140,7 +140,7 @@
|
||||
"type": "basic",
|
||||
"name": "P",
|
||||
"props": { "className": "text-xs text-gray-500 dark:text-gray-400" },
|
||||
"text": "{{addOpt.group_name ? addOpt.group_name + ': ' : ''}}{{addOpt.name}}{{(addOpt.custom_text ?? '') !== '' ? ' (' + addOpt.custom_text + ')' : ''}}{{(addOpt.price_adjustment ?? 0) > 0 ? ' (' + (addOpt.price_adjustment_formatted ?? ('+' + addOpt.price_adjustment.toLocaleString())) + ')' : ''}}"
|
||||
"text": "{{addOpt.group_name ? addOpt.group_name + ': ' : ''}}{{addOpt.name}}{{(addOpt.custom_text ?? '') !== '' ? ' (' + addOpt.custom_text + ')' : ''}}{{(addOpt.price_adjustment ?? 0) > 0 ? ' (' + (addOpt.multi_currency_price_adjustment?.[_global.preferredCurrency ?? _global.defaultCurrency]?.formatted ?? addOpt.price_adjustment_formatted ?? ('+' + addOpt.price_adjustment.toLocaleString())) + ')' : ''}}"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -203,7 +203,7 @@
|
||||
"type": "basic",
|
||||
"name": "Span",
|
||||
"props": { "className": "text-xs text-gray-400 dark:text-gray-500 line-through" },
|
||||
"text": "{{item.product_option?.multi_currency_list_price?.[_global.preferredCurrency ?? 'KRW']?.formatted ?? item.product_option?.list_price_formatted ?? ''}}"
|
||||
"text": "{{item.product_option?.multi_currency_list_price?.[_global.preferredCurrency ?? _global.defaultCurrency]?.formatted ?? item.product_option?.list_price_formatted ?? ''}}"
|
||||
},
|
||||
{
|
||||
"comment": "할인율 칩",
|
||||
@@ -221,7 +221,7 @@
|
||||
"if": "{{(item.product_option?.discount_rate ?? 0) > 0}}",
|
||||
"props": { "className": "flex flex-wrap gap-x-2" },
|
||||
"iteration": {
|
||||
"source": "{{Object.entries(item.product_option?.multi_currency_list_price ?? {}).filter(([code]) => code !== (_global.preferredCurrency ?? 'KRW'))}}",
|
||||
"source": "{{Object.entries(item.product_option?.multi_currency_list_price ?? {}).filter(([code]) => code !== (_global.preferredCurrency ?? _global.defaultCurrency))}}",
|
||||
"item_var": "currency"
|
||||
},
|
||||
"children": [
|
||||
@@ -247,7 +247,7 @@
|
||||
{
|
||||
"type": "basic",
|
||||
"name": "Span",
|
||||
"text": "{{item.product_option?.multi_currency_selling_price?.[_global.preferredCurrency ?? 'KRW']?.formatted ?? item.product_option?.selling_price_formatted ?? ''}} × {{item.quantity}}"
|
||||
"text": "{{item.product_option?.multi_currency_selling_price?.[_global.preferredCurrency ?? _global.defaultCurrency]?.formatted ?? item.product_option?.selling_price_formatted ?? ''}} × {{item.quantity}}"
|
||||
},
|
||||
{
|
||||
"type": "basic",
|
||||
@@ -262,7 +262,7 @@
|
||||
"name": "Div",
|
||||
"props": { "className": "flex flex-wrap gap-x-2 mt-0.5" },
|
||||
"iteration": {
|
||||
"source": "{{Object.entries(item.product_option?.multi_currency_selling_price ?? {}).filter(([code]) => code !== (_global.preferredCurrency ?? 'KRW'))}}",
|
||||
"source": "{{Object.entries(item.product_option?.multi_currency_selling_price ?? {}).filter(([code]) => code !== (_global.preferredCurrency ?? _global.defaultCurrency))}}",
|
||||
"item_var": "currency"
|
||||
},
|
||||
"children": [
|
||||
@@ -555,7 +555,7 @@
|
||||
"name": "Div",
|
||||
"if": "{{(item.product_coupon_discount_amount ?? 0) === 0}}",
|
||||
"props": { "className": "font-bold text-gray-900 dark:text-white" },
|
||||
"text": "{{item.multi_currency_subtotal?.[_global.preferredCurrency ?? 'KRW']?.formatted ?? item.subtotal_formatted}}"
|
||||
"text": "{{item.multi_currency_subtotal?.[_global.preferredCurrency ?? _global.defaultCurrency]?.formatted ?? item.subtotal_formatted}}"
|
||||
},
|
||||
{
|
||||
"comment": "원 판매금액 (할인 있을 때 취소선) - 주 통화",
|
||||
@@ -563,7 +563,7 @@
|
||||
"name": "Div",
|
||||
"if": "{{(item.product_coupon_discount_amount ?? 0) > 0}}",
|
||||
"props": { "className": "text-sm text-gray-400 dark:text-gray-500 line-through" },
|
||||
"text": "{{item.multi_currency_subtotal?.[_global.preferredCurrency ?? 'KRW']?.formatted ?? item.subtotal_formatted}}"
|
||||
"text": "{{item.multi_currency_subtotal?.[_global.preferredCurrency ?? _global.defaultCurrency]?.formatted ?? item.subtotal_formatted}}"
|
||||
},
|
||||
{
|
||||
"comment": "할인 후 금액 - 주 통화",
|
||||
@@ -571,7 +571,7 @@
|
||||
"name": "Div",
|
||||
"if": "{{(item.product_coupon_discount_amount ?? 0) > 0}}",
|
||||
"props": { "className": "font-bold text-red-600 dark:text-red-400" },
|
||||
"text": "{{item.multi_currency_final_amount?.[_global.preferredCurrency ?? 'KRW']?.formatted ?? item.final_amount_formatted}}"
|
||||
"text": "{{item.multi_currency_final_amount?.[_global.preferredCurrency ?? _global.defaultCurrency]?.formatted ?? item.final_amount_formatted}}"
|
||||
},
|
||||
{
|
||||
"comment": "원 판매금액 - 보조 통화들 (할인 없을 때)",
|
||||
@@ -580,7 +580,7 @@
|
||||
"if": "{{(item.product_coupon_discount_amount ?? 0) === 0}}",
|
||||
"props": { "className": "flex flex-wrap gap-x-2 justify-end" },
|
||||
"iteration": {
|
||||
"source": "{{Object.entries(item.multi_currency_subtotal ?? {}).filter(([code]) => code !== (_global.preferredCurrency ?? 'KRW'))}}",
|
||||
"source": "{{Object.entries(item.multi_currency_subtotal ?? {}).filter(([code]) => code !== (_global.preferredCurrency ?? _global.defaultCurrency))}}",
|
||||
"item_var": "subtotalCurrency"
|
||||
},
|
||||
"children": [
|
||||
@@ -599,7 +599,7 @@
|
||||
"if": "{{(item.product_coupon_discount_amount ?? 0) > 0}}",
|
||||
"props": { "className": "flex flex-wrap gap-x-2 justify-end" },
|
||||
"iteration": {
|
||||
"source": "{{Object.entries(item.multi_currency_final_amount ?? {}).filter(([code]) => code !== (_global.preferredCurrency ?? 'KRW'))}}",
|
||||
"source": "{{Object.entries(item.multi_currency_final_amount ?? {}).filter(([code]) => code !== (_global.preferredCurrency ?? _global.defaultCurrency))}}",
|
||||
"item_var": "finalCurrency"
|
||||
},
|
||||
"children": [
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
"type": "basic",
|
||||
"name": "Span",
|
||||
"props": { "className": "font-medium text-gray-900 dark:text-white" },
|
||||
"text": "{{checkoutData.data.calculation?.summary?.multi_currency[_global.preferredCurrency ?? 'KRW']?.subtotal_formatted ?? checkoutData.data.calculation?.summary?.subtotal_formatted ?? '-'}}"
|
||||
"text": "{{checkoutData.data.calculation?.summary?.multi_currency[_global.preferredCurrency ?? _global.defaultCurrency]?.subtotal_formatted ?? checkoutData.data.calculation?.summary?.subtotal_formatted ?? '-'}}"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -100,7 +100,7 @@
|
||||
"props": { "className": "flex justify-between text-xs" },
|
||||
"children": [
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-gray-500 dark:text-gray-500" }, "text": "$t:shop.cart.taxable_amount" },
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-gray-600 dark:text-gray-400" }, "text": "{{checkoutData.data.calculation?.summary?.multi_currency[_global.preferredCurrency ?? 'KRW']?.taxable_amount_formatted ?? '-'}}" }
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-gray-600 dark:text-gray-400" }, "text": "{{checkoutData.data.calculation?.summary?.multi_currency[_global.preferredCurrency ?? _global.defaultCurrency]?.taxable_amount_formatted ?? '-'}}" }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -110,7 +110,7 @@
|
||||
"props": { "className": "flex justify-between text-xs" },
|
||||
"children": [
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-gray-500 dark:text-gray-500" }, "text": "$t:shop.cart.tax_free_amount" },
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-gray-600 dark:text-gray-400" }, "text": "{{checkoutData.data.calculation?.summary?.multi_currency[_global.preferredCurrency ?? 'KRW']?.tax_free_amount_formatted ?? '-'}}" }
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-gray-600 dark:text-gray-400" }, "text": "{{checkoutData.data.calculation?.summary?.multi_currency[_global.preferredCurrency ?? _global.defaultCurrency]?.tax_free_amount_formatted ?? '-'}}" }
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -151,7 +151,7 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "font-medium text-red-600 dark:text-red-400" }, "text": "-{{checkoutData.data.calculation?.summary?.multi_currency[_global.preferredCurrency ?? 'KRW']?.total_discount_formatted ?? checkoutData.data.calculation?.summary?.total_discount_formatted}}" }
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "font-medium text-red-600 dark:text-red-400" }, "text": "-{{checkoutData.data.calculation?.summary?.multi_currency[_global.preferredCurrency ?? _global.defaultCurrency]?.total_discount_formatted ?? checkoutData.data.calculation?.summary?.total_discount_formatted}}" }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -168,7 +168,7 @@
|
||||
"props": { "className": "flex justify-between text-xs" },
|
||||
"children": [
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-gray-500 dark:text-gray-500" }, "text": "$t:shop.cart.coupon_discount" },
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-red-500 dark:text-red-400" }, "text": "-{{checkoutData.data.calculation?.summary?.multi_currency[_global.preferredCurrency ?? 'KRW']?.product_coupon_discount_formatted ?? '-'}}" }
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-red-500 dark:text-red-400" }, "text": "-{{checkoutData.data.calculation?.summary?.multi_currency[_global.preferredCurrency ?? _global.defaultCurrency]?.product_coupon_discount_formatted ?? '-'}}" }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -200,7 +200,7 @@
|
||||
"props": { "className": "flex justify-between text-xs" },
|
||||
"children": [
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-gray-500 dark:text-gray-500" }, "text": "$t:shop.cart.code_discount" },
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-red-500 dark:text-red-400" }, "text": "-{{checkoutData.data.calculation?.summary?.multi_currency[_global.preferredCurrency ?? 'KRW']?.code_discount_formatted ?? '-'}}" }
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-red-500 dark:text-red-400" }, "text": "-{{checkoutData.data.calculation?.summary?.multi_currency[_global.preferredCurrency ?? _global.defaultCurrency]?.code_discount_formatted ?? '-'}}" }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -210,7 +210,7 @@
|
||||
"props": { "className": "flex justify-between text-xs" },
|
||||
"children": [
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-gray-500 dark:text-gray-500" }, "text": "$t:shop.cart.order_discount" },
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-red-500 dark:text-red-400" }, "text": "-{{checkoutData.data.calculation?.summary?.multi_currency[_global.preferredCurrency ?? 'KRW']?.order_coupon_discount_formatted ?? '-'}}" }
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-red-500 dark:text-red-400" }, "text": "-{{checkoutData.data.calculation?.summary?.multi_currency[_global.preferredCurrency ?? _global.defaultCurrency]?.order_coupon_discount_formatted ?? '-'}}" }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -254,7 +254,7 @@
|
||||
"type": "basic",
|
||||
"name": "Span",
|
||||
"props": { "className": "font-medium {{(checkoutData.data.calculation?.summary?.total_shipping ?? 0) === 0 ? 'text-green-600 dark:text-green-400' : 'text-gray-900 dark:text-white'}}" },
|
||||
"text": "{{(checkoutData.data.calculation?.summary?.total_shipping ?? 0) === 0 ? '$t:shop.checkout.free' : (checkoutData.data.calculation?.summary?.multi_currency[_global.preferredCurrency ?? 'KRW']?.total_shipping_formatted ?? checkoutData.data.calculation?.summary?.total_shipping_formatted)}}"
|
||||
"text": "{{(checkoutData.data.calculation?.summary?.total_shipping ?? 0) === 0 ? '$t:shop.checkout.free' : (checkoutData.data.calculation?.summary?.multi_currency[_global.preferredCurrency ?? _global.defaultCurrency]?.total_shipping_formatted ?? checkoutData.data.calculation?.summary?.total_shipping_formatted)}}"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -272,7 +272,7 @@
|
||||
"props": { "className": "flex justify-between text-xs" },
|
||||
"children": [
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-gray-500 dark:text-gray-500" }, "text": "$t:shop.cart.base_shipping" },
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-gray-600 dark:text-gray-400" }, "text": "{{checkoutData.data.calculation?.summary?.multi_currency[_global.preferredCurrency ?? 'KRW']?.base_shipping_total_formatted ?? '-'}}" }
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-gray-600 dark:text-gray-400" }, "text": "{{checkoutData.data.calculation?.summary?.multi_currency[_global.preferredCurrency ?? _global.defaultCurrency]?.base_shipping_total_formatted ?? '-'}}" }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -282,7 +282,7 @@
|
||||
"props": { "className": "flex justify-between text-xs" },
|
||||
"children": [
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-gray-500 dark:text-gray-500" }, "text": "$t:shop.cart.extra_shipping" },
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-gray-600 dark:text-gray-400" }, "text": "{{checkoutData.data.calculation?.summary?.multi_currency[_global.preferredCurrency ?? 'KRW']?.extra_shipping_total_formatted ?? '-'}}" }
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-gray-600 dark:text-gray-400" }, "text": "{{checkoutData.data.calculation?.summary?.multi_currency[_global.preferredCurrency ?? _global.defaultCurrency]?.extra_shipping_total_formatted ?? '-'}}" }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -292,7 +292,7 @@
|
||||
"props": { "className": "flex justify-between text-xs" },
|
||||
"children": [
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-gray-500 dark:text-gray-500" }, "text": "$t:shop.cart.shipping_discount" },
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-red-500 dark:text-red-400" }, "text": "-{{checkoutData.data.calculation?.summary?.multi_currency[_global.preferredCurrency ?? 'KRW']?.shipping_discount_formatted ?? '-'}}" }
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-red-500 dark:text-red-400" }, "text": "-{{checkoutData.data.calculation?.summary?.multi_currency[_global.preferredCurrency ?? _global.defaultCurrency]?.shipping_discount_formatted ?? '-'}}" }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -339,7 +339,7 @@
|
||||
"props": { "className": "flex justify-between text-sm pt-2" },
|
||||
"children": [
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-gray-600 dark:text-gray-400" }, "text": "$t:shop.cart.payment_amount" },
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "font-medium text-gray-900 dark:text-white" }, "text": "{{checkoutData.data.calculation?.summary?.multi_currency[_global.preferredCurrency ?? 'KRW']?.payment_amount_formatted ?? checkoutData.data.calculation?.summary?.payment_amount_formatted ?? '-'}}" }
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "font-medium text-gray-900 dark:text-white" }, "text": "{{checkoutData.data.calculation?.summary?.multi_currency[_global.preferredCurrency ?? _global.defaultCurrency]?.payment_amount_formatted ?? checkoutData.data.calculation?.summary?.payment_amount_formatted ?? '-'}}" }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -391,7 +391,7 @@
|
||||
"props": { "className": "text-lg font-bold text-gray-900 dark:text-white" }
|
||||
}
|
||||
},
|
||||
"text": "{{checkoutData.data.calculation?.summary?.multi_currency[_global.preferredCurrency ?? 'KRW']?.final_amount_formatted ?? checkoutData.data.calculation?.summary?.final_amount_formatted ?? '-'}}"
|
||||
"text": "{{checkoutData.data.calculation?.summary?.multi_currency[_global.preferredCurrency ?? _global.defaultCurrency]?.final_amount_formatted ?? checkoutData.data.calculation?.summary?.final_amount_formatted ?? '-'}}"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -401,7 +401,7 @@
|
||||
"name": "Div",
|
||||
"props": { "className": "flex flex-wrap gap-x-2 justify-end mt-1" },
|
||||
"iteration": {
|
||||
"source": "{{Object.entries(checkoutData.data.calculation?.summary?.multi_currency ?? {}).filter(([code]) => code !== (_global.preferredCurrency ?? 'KRW'))}}",
|
||||
"source": "{{Object.entries(checkoutData.data.calculation?.summary?.multi_currency ?? {}).filter(([code]) => code !== (_global.preferredCurrency ?? _global.defaultCurrency))}}",
|
||||
"item_var": "currency"
|
||||
},
|
||||
"children": [
|
||||
@@ -439,7 +439,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"text": "{{checkoutData.data.calculation?.summary?.multi_currency[_global.preferredCurrency ?? 'KRW']?.final_amount_formatted ?? checkoutData.data.calculation?.summary?.final_amount_formatted ?? '-'}} $t:shop.checkout.pay_button",
|
||||
"text": "{{checkoutData.data.calculation?.summary?.multi_currency[_global.preferredCurrency ?? _global.defaultCurrency]?.final_amount_formatted ?? checkoutData.data.calculation?.summary?.final_amount_formatted ?? '-'}} $t:shop.checkout.pay_button",
|
||||
"actions": [
|
||||
{
|
||||
"type": "click",
|
||||
|
||||
+6
-6
@@ -105,7 +105,7 @@
|
||||
"type": "basic",
|
||||
"name": "P",
|
||||
"props": { "className": "text-sm font-medium text-gray-900 dark:text-white" },
|
||||
"text": "{{$parent._local.optionModal.targetItem?.multi_currency_selling_price?.[_global.preferredCurrency ?? 'KRW']?.formatted ?? $parent._local.optionModal.targetItem?.unit_price_formatted}}"
|
||||
"text": "{{$parent._local.optionModal.targetItem?.multi_currency_selling_price?.[_global.preferredCurrency ?? _global.defaultCurrency]?.formatted ?? $parent._local.optionModal.targetItem?.unit_price_formatted}}"
|
||||
},
|
||||
{
|
||||
"comment": "보조 통화들",
|
||||
@@ -113,7 +113,7 @@
|
||||
"name": "Div",
|
||||
"props": { "className": "flex flex-wrap gap-x-2" },
|
||||
"iteration": {
|
||||
"source": "{{Object.entries($parent._local.optionModal.targetItem?.multi_currency_selling_price ?? {}).filter(([code]) => code !== (_global.preferredCurrency ?? 'KRW'))}}",
|
||||
"source": "{{Object.entries($parent._local.optionModal.targetItem?.multi_currency_selling_price ?? {}).filter(([code]) => code !== (_global.preferredCurrency ?? _global.defaultCurrency))}}",
|
||||
"item_var": "currency"
|
||||
},
|
||||
"children": [
|
||||
@@ -277,7 +277,7 @@
|
||||
"className": "w-full",
|
||||
"value": "{{$parent._local.additionalSelections?.[addGroup.id] ?? ''}}",
|
||||
"placeholder": "$t:shop.additional_option_placeholder",
|
||||
"options": "{{[{ value: '', label: $t('shop.additional_option_placeholder') }, ...(addGroup.values ?? []).map(v => ({ value: v.id, label: v.price_adjustment > 0 ? v.name + ' (' + (v.price_adjustment_formatted ?? ('+' + v.price_adjustment.toLocaleString())) + ')' : v.name }))]}}"
|
||||
"options": "{{[{ value: '', label: $t('shop.additional_option_placeholder') }, ...(addGroup.values ?? []).map(v => ({ value: v.id, label: v.price_adjustment > 0 ? v.name + ' (' + (v.multi_currency_price_adjustment?.[_global.preferredCurrency ?? _global.defaultCurrency]?.formatted ?? v.price_adjustment_formatted ?? ('+' + v.price_adjustment.toLocaleString())) + ')' : v.name }))]}}"
|
||||
},
|
||||
"actions": [
|
||||
{
|
||||
@@ -355,7 +355,7 @@
|
||||
"type": "basic",
|
||||
"name": "Div",
|
||||
"props": { "className": "text-sm font-bold text-gray-900 dark:text-white" },
|
||||
"text": "{{$parent._local.matchedOption.multi_currency_selling_price?.[_global.preferredCurrency ?? 'KRW']?.formatted ?? $parent._local.matchedOption.selling_price_formatted}}"
|
||||
"text": "{{$parent._local.matchedOption.multi_currency_selling_price?.[_global.preferredCurrency ?? _global.defaultCurrency]?.formatted ?? $parent._local.matchedOption.selling_price_formatted}}"
|
||||
},
|
||||
{
|
||||
"comment": "보조 통화들",
|
||||
@@ -363,7 +363,7 @@
|
||||
"name": "Div",
|
||||
"props": { "className": "flex flex-wrap gap-x-2 justify-end" },
|
||||
"iteration": {
|
||||
"source": "{{Object.entries($parent._local.matchedOption.multi_currency_selling_price ?? {}).filter(([code]) => code !== (_global.preferredCurrency ?? 'KRW'))}}",
|
||||
"source": "{{Object.entries($parent._local.matchedOption.multi_currency_selling_price ?? {}).filter(([code]) => code !== (_global.preferredCurrency ?? _global.defaultCurrency))}}",
|
||||
"item_var": "currency"
|
||||
},
|
||||
"children": [
|
||||
@@ -387,7 +387,7 @@
|
||||
"props": { "className": "flex justify-between items-center mt-2 pt-2 border-t border-blue-100 dark:border-blue-800 text-xs text-gray-600 dark:text-gray-300" },
|
||||
"children": [
|
||||
{ "type": "basic", "name": "Span", "text": "$t:shop.additional_options_amount" },
|
||||
{ "type": "basic", "name": "Span", "text": "{{(() => { const total = ($parent._local.optionProduct?.additional_options ?? []).reduce((sum, g) => sum + (((g.values ?? []).find(v => v.id === Number($parent._local.additionalSelections?.[g.id]))?.price_adjustment) ?? 0), 0); const c = (_global.availableCurrencies ?? []).find(x => x.is_default); const dp = c?.decimal_places ?? 0; const n = total.toLocaleString(undefined, { minimumFractionDigits: dp, maximumFractionDigits: dp }); const s = c?.symbol ?? '₩'; return '+' + ((c?.code === 'KRW' || s === '₩' || s === '원') ? (n + '원') : (s + n)); })()}}" }
|
||||
{ "comment": "추가금은 기본통화 기준 저장값이므로 서버가 준 표시통화 환산값을 우선 합산한다 (없으면 기본통화 값으로 폴백)", "type": "basic", "name": "Span", "text": "{{(() => { const code = _global.preferredCurrency ?? _global.defaultCurrency; const total = ($parent._local.optionProduct?.additional_options ?? []).reduce((sum, g) => { const v = (g.values ?? []).find(v => v.id === Number($parent._local.additionalSelections?.[g.id])); if (!v) return sum; return sum + (v.multi_currency_price_adjustment?.[code]?.price ?? v.price_adjustment ?? 0); }, 0); const cur = (_global.availableCurrencies ?? []).find(x => x.code === code) ?? (_global.availableCurrencies ?? []).find(x => x.is_default); const dp = cur?.decimal_places ?? 0; const n = total.toLocaleString(undefined, { minimumFractionDigits: dp, maximumFractionDigits: dp }); const s = cur?.symbol ?? '₩'; return '+' + ((cur?.code === 'KRW' || s === '₩' || s === '원') ? (n + '원') : (s + n)); })()}}" }
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -154,15 +154,15 @@
|
||||
"comment": "정액 할인 — 선택 통화 환산 (A1-④, formatted 가 기호 포함)",
|
||||
"type": "basic",
|
||||
"name": "Span",
|
||||
"if": "{{coupon.discount_type === 'fixed' && coupon.multi_currency_discount_value?.[_global.preferredCurrency ?? 'KRW']?.formatted}}",
|
||||
"if": "{{coupon.discount_type === 'fixed' && coupon.multi_currency_discount_value?.[_global.preferredCurrency ?? _global.defaultCurrency]?.formatted}}",
|
||||
"props": { "className": "text-xl font-bold text-blue-600 dark:text-blue-400" },
|
||||
"text": "{{coupon.multi_currency_discount_value?.[_global.preferredCurrency ?? 'KRW']?.formatted}}"
|
||||
"text": "{{coupon.multi_currency_discount_value?.[_global.preferredCurrency ?? _global.defaultCurrency]?.formatted}}"
|
||||
},
|
||||
{
|
||||
"comment": "정액 할인 — 폴백(환산 부재 시 기본통화 숫자 + 단위)",
|
||||
"type": "basic",
|
||||
"name": "Span",
|
||||
"if": "{{coupon.discount_type === 'fixed' && !coupon.multi_currency_discount_value?.[_global.preferredCurrency ?? 'KRW']?.formatted}}",
|
||||
"if": "{{coupon.discount_type === 'fixed' && !coupon.multi_currency_discount_value?.[_global.preferredCurrency ?? _global.defaultCurrency]?.formatted}}",
|
||||
"props": { "className": "text-xl font-bold text-blue-600 dark:text-blue-400" },
|
||||
"text": "{{Number(coupon.discount_value).toLocaleString()}}$t:shop.coupon_download.currency_unit"
|
||||
}
|
||||
@@ -179,15 +179,15 @@
|
||||
"comment": "최소 주문 금액 — 선택 통화 환산 (A1-④, formatted 가 기호 포함)",
|
||||
"type": "basic",
|
||||
"name": "P",
|
||||
"if": "{{coupon.min_order_amount > 0 && coupon.multi_currency_min_order_amount?.[_global.preferredCurrency ?? 'KRW']?.formatted}}",
|
||||
"if": "{{coupon.min_order_amount > 0 && coupon.multi_currency_min_order_amount?.[_global.preferredCurrency ?? _global.defaultCurrency]?.formatted}}",
|
||||
"props": { "className": "text-xs text-gray-500 dark:text-gray-400 mb-1" },
|
||||
"text": "$t:shop.coupon_download.min_order_amount {{coupon.multi_currency_min_order_amount?.[_global.preferredCurrency ?? 'KRW']?.formatted}}"
|
||||
"text": "$t:shop.coupon_download.min_order_amount {{coupon.multi_currency_min_order_amount?.[_global.preferredCurrency ?? _global.defaultCurrency]?.formatted}}"
|
||||
},
|
||||
{
|
||||
"comment": "최소 주문 금액 — 폴백(환산 부재 시)",
|
||||
"type": "basic",
|
||||
"name": "P",
|
||||
"if": "{{coupon.min_order_amount > 0 && !coupon.multi_currency_min_order_amount?.[_global.preferredCurrency ?? 'KRW']?.formatted}}",
|
||||
"if": "{{coupon.min_order_amount > 0 && !coupon.multi_currency_min_order_amount?.[_global.preferredCurrency ?? _global.defaultCurrency]?.formatted}}",
|
||||
"props": { "className": "text-xs text-gray-500 dark:text-gray-400 mb-1" },
|
||||
"text": "$t:shop.coupon_download.min_order_amount {{Number(coupon.min_order_amount).toLocaleString()}}$t:shop.coupon_download.currency_unit"
|
||||
},
|
||||
|
||||
@@ -15,13 +15,13 @@
|
||||
"name": "Span",
|
||||
"if": "{{product.data.discount_rate > 0}}",
|
||||
"props": { "className": "text-sm text-gray-500 dark:text-gray-400 line-through" },
|
||||
"text": "{{product.data.multi_currency_list_price?.[_global.preferredCurrency ?? 'KRW']?.formatted ?? product.data.list_price_formatted}}"
|
||||
"text": "{{product.data.multi_currency_list_price?.[_global.preferredCurrency ?? _global.defaultCurrency]?.formatted ?? product.data.list_price_formatted}}"
|
||||
},
|
||||
{
|
||||
"type": "basic",
|
||||
"name": "Div",
|
||||
"props": { "className": "text-2xl font-bold text-gray-900 dark:text-white" },
|
||||
"text": "{{product.data.multi_currency_selling_price?.[_global.preferredCurrency ?? 'KRW']?.formatted ?? product.data.selling_price_formatted}}"
|
||||
"text": "{{product.data.multi_currency_selling_price?.[_global.preferredCurrency ?? _global.defaultCurrency]?.formatted ?? product.data.selling_price_formatted}}"
|
||||
},
|
||||
{
|
||||
"type": "basic",
|
||||
@@ -81,7 +81,7 @@
|
||||
"value": "{{_local.currentSelection?.[group.id] ?? ''}}",
|
||||
"placeholder": "{{groupIndex === 0 || _local.currentSelection?.[product.data.option_groups[groupIndex - 1]?.id] ? '$t:shop.select' : '$t:shop.select_upper_option_first'}}",
|
||||
"disabled": "{{groupIndex > 0 && !_local.currentSelection?.[product.data.option_groups[groupIndex - 1]?.id]}}",
|
||||
"options": "{{group.values.map(v => ({ value: v.id, label: v.name + (v.additional_price > 0 ? ' (+' + (v.multi_currency_additional_price?.[_global.preferredCurrency ?? 'KRW']?.formatted ?? v.additional_price_formatted) + ')' : '') }))}}"
|
||||
"options": "{{group.values.map(v => ({ value: v.id, label: v.name + (v.additional_price > 0 ? ' (+' + (v.multi_currency_additional_price?.[_global.preferredCurrency ?? _global.defaultCurrency]?.formatted ?? v.additional_price_formatted) + ')' : '') }))}}"
|
||||
},
|
||||
"actions": [
|
||||
{
|
||||
@@ -105,7 +105,7 @@
|
||||
"optionGroups": "{{product.data.option_groups}}",
|
||||
"currentSelection": "{{_local.currentSelection}}",
|
||||
"selectedItems": "{{_local.selectedItems ?? []}}",
|
||||
"preferredCurrency": "{{_global.preferredCurrency ?? 'KRW'}}"
|
||||
"preferredCurrency": "{{_global.preferredCurrency ?? _global.defaultCurrency}}"
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -212,7 +212,7 @@
|
||||
"itemIndex": "{{itemIndex}}",
|
||||
"newQuantity": "{{$args[0]}}",
|
||||
"selectedItems": "{{_local.selectedItems}}",
|
||||
"preferredCurrency": "{{_global.preferredCurrency ?? 'KRW'}}"
|
||||
"preferredCurrency": "{{_global.preferredCurrency ?? _global.defaultCurrency}}"
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -221,7 +221,7 @@
|
||||
"type": "basic",
|
||||
"name": "Span",
|
||||
"props": { "className": "text-sm font-bold text-gray-900 dark:text-white" },
|
||||
"text": "{{item.multiCurrencyTotalPrice?.[_global.preferredCurrency ?? 'KRW']?.formatted ?? item.totalPriceFormatted}}"
|
||||
"text": "{{item.multiCurrencyTotalPrice?.[_global.preferredCurrency ?? _global.defaultCurrency]?.formatted ?? item.totalPriceFormatted}}"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -276,7 +276,7 @@
|
||||
"type": "basic",
|
||||
"name": "Span",
|
||||
"props": { "className": "text-xl font-bold text-gray-900 dark:text-white" },
|
||||
"text": "{{(function() { const items = _local.selectedItems ?? []; if (items.length === 0) return product.data.multi_currency_selling_price?.[_global.preferredCurrency ?? 'KRW']?.formatted ?? product.data.selling_price_formatted; const total = items.reduce((sum, item) => sum + (item.multiCurrencyTotalPrice?.[_global.preferredCurrency ?? 'KRW']?.value ?? item.totalPrice), 0); return _global.formatCurrency(total, _global.preferredCurrency ?? 'KRW'); })()}}"
|
||||
"text": "{{(function() { const items = _local.selectedItems ?? []; if (items.length === 0) return product.data.multi_currency_selling_price?.[_global.preferredCurrency ?? _global.defaultCurrency]?.formatted ?? product.data.selling_price_formatted; const total = items.reduce((sum, item) => sum + (item.multiCurrencyTotalPrice?.[_global.preferredCurrency ?? _global.defaultCurrency]?.value ?? item.totalPrice), 0); return _local.selectedTotalMultiCurrency?.[_global.preferredCurrency ?? _global.defaultCurrency]?.formatted ?? items[0]?.multiCurrencyTotalPrice?.[_global.preferredCurrency ?? _global.defaultCurrency]?.formatted ?? String(total); })()}}"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
"type": "basic",
|
||||
"name": "Span",
|
||||
"props": { "className": "text-sm text-gray-500 dark:text-gray-400 line-through" },
|
||||
"text": "{{product.data?.multi_currency_list_price?.[_global.preferredCurrency ?? 'KRW']?.formatted ?? product.data?.list_price_formatted}}"
|
||||
"text": "{{product.data?.multi_currency_list_price?.[_global.preferredCurrency ?? _global.defaultCurrency]?.formatted ?? product.data?.list_price_formatted}}"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -48,7 +48,7 @@
|
||||
"type": "basic",
|
||||
"name": "Span",
|
||||
"props": { "className": "text-2xl font-bold text-gray-900 dark:text-white" },
|
||||
"text": "{{product.data?.multi_currency_selling_price?.[_global.preferredCurrency ?? 'KRW']?.formatted ?? product.data?.selling_price_formatted}}"
|
||||
"text": "{{product.data?.multi_currency_selling_price?.[_global.preferredCurrency ?? _global.defaultCurrency]?.formatted ?? product.data?.selling_price_formatted}}"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -58,7 +58,7 @@
|
||||
"name": "Div",
|
||||
"if": "{{!product.data?.discount_rate || product.data?.discount_rate <= 0}}",
|
||||
"props": { "className": "text-2xl font-bold text-gray-900 dark:text-white" },
|
||||
"text": "{{product.data?.multi_currency_selling_price?.[_global.preferredCurrency ?? 'KRW']?.formatted ?? product.data?.selling_price_formatted}}"
|
||||
"text": "{{product.data?.multi_currency_selling_price?.[_global.preferredCurrency ?? _global.defaultCurrency]?.formatted ?? product.data?.selling_price_formatted}}"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -102,7 +102,7 @@
|
||||
"type": "basic",
|
||||
"name": "Span",
|
||||
"props": { "className": "whitespace-nowrap" },
|
||||
"text": "{{dlCoupon.multi_currency_benefit_formatted?.[_global.preferredCurrency ?? 'KRW'] ?? dlCoupon.benefit_formatted}}"
|
||||
"text": "{{dlCoupon.multi_currency_benefit_formatted?.[_global.preferredCurrency ?? _global.defaultCurrency] ?? dlCoupon.benefit_formatted}}"
|
||||
},
|
||||
{
|
||||
"type": "basic",
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
"type": "basic",
|
||||
"name": "Span",
|
||||
"props": { "className": "text-sm text-gray-500 dark:text-gray-400 line-through" },
|
||||
"text": "{{product.data?.multi_currency_list_price?.[_global.preferredCurrency ?? 'KRW']?.formatted ?? product.data?.list_price_formatted}}"
|
||||
"text": "{{product.data?.multi_currency_list_price?.[_global.preferredCurrency ?? _global.defaultCurrency]?.formatted ?? product.data?.list_price_formatted}}"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -38,7 +38,7 @@
|
||||
"type": "basic",
|
||||
"name": "Span",
|
||||
"props": { "className": "text-xl font-bold text-gray-900 dark:text-white" },
|
||||
"text": "{{product.data?.multi_currency_selling_price?.[_global.preferredCurrency ?? 'KRW']?.formatted ?? product.data?.selling_price_formatted}}"
|
||||
"text": "{{product.data?.multi_currency_selling_price?.[_global.preferredCurrency ?? _global.defaultCurrency]?.formatted ?? product.data?.selling_price_formatted}}"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -48,7 +48,7 @@
|
||||
"name": "Div",
|
||||
"if": "{{!product.data?.discount_rate || product.data?.discount_rate <= 0}}",
|
||||
"props": { "className": "text-xl font-bold text-gray-900 dark:text-white" },
|
||||
"text": "{{product.data?.multi_currency_selling_price?.[_global.preferredCurrency ?? 'KRW']?.formatted ?? product.data?.selling_price_formatted}}"
|
||||
"text": "{{product.data?.multi_currency_selling_price?.[_global.preferredCurrency ?? _global.defaultCurrency]?.formatted ?? product.data?.selling_price_formatted}}"
|
||||
},
|
||||
{
|
||||
"comment": "배송 불가 사전 경고 (B8 — 선택 배송국가로 배송 불가, 모바일 가격 영역)",
|
||||
|
||||
@@ -91,7 +91,7 @@
|
||||
"options": "{{product.data?.options}}",
|
||||
"currentSelection": "{{_local.currentSelection ?? {}}}",
|
||||
"selectedOptionItems": "{{_local.selectedOptionItems ?? []}}",
|
||||
"preferredCurrency": "{{_global.preferredCurrency ?? 'KRW'}}",
|
||||
"preferredCurrency": "{{_global.preferredCurrency ?? _global.defaultCurrency}}",
|
||||
"additionalOptionGroups": "{{product.data?.additional_options ?? []}}",
|
||||
"newGroupName": "{{group.name_localized}}",
|
||||
"newValue": "{{$event.target.value}}"
|
||||
@@ -264,7 +264,7 @@
|
||||
"className": "w-full",
|
||||
"value": "{{item.additionalOptionSelections?.[addGroup.id] ?? ''}}",
|
||||
"placeholder": "$t:shop.additional_option_placeholder",
|
||||
"options": "{{[{ value: '', label: $t('shop.additional_option_placeholder') }, ...(addGroup.values ?? []).map(v => ({ value: v.id, label: v.price_adjustment > 0 ? v.name + ' (' + (v.price_adjustment_formatted ?? ('+' + v.price_adjustment.toLocaleString())) + ')' : v.name }))]}}"
|
||||
"options": "{{[{ value: '', label: $t('shop.additional_option_placeholder') }, ...(addGroup.values ?? []).map(v => ({ value: v.id, label: v.price_adjustment > 0 ? v.name + ' (' + (v.multi_currency_price_adjustment?.[_global.preferredCurrency ?? _global.defaultCurrency]?.formatted ?? v.price_adjustment_formatted ?? ('+' + v.price_adjustment.toLocaleString())) + ')' : v.name }))]}}"
|
||||
},
|
||||
"actions": [
|
||||
{
|
||||
@@ -276,7 +276,7 @@
|
||||
"valueId": "{{$event.target.value}}",
|
||||
"selectedOptionItems": "{{_local.selectedOptionItems}}",
|
||||
"additionalOptionGroups": "{{product.data?.additional_options ?? []}}",
|
||||
"preferredCurrency": "{{_global.preferredCurrency ?? 'KRW'}}"
|
||||
"preferredCurrency": "{{_global.preferredCurrency ?? _global.defaultCurrency}}"
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -316,7 +316,7 @@
|
||||
"customText": "{{$event.target.value}}",
|
||||
"selectedOptionItems": "{{_local.selectedOptionItems}}",
|
||||
"additionalOptionGroups": "{{product.data?.additional_options ?? []}}",
|
||||
"preferredCurrency": "{{_global.preferredCurrency ?? 'KRW'}}"
|
||||
"preferredCurrency": "{{_global.preferredCurrency ?? _global.defaultCurrency}}"
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -335,7 +335,7 @@
|
||||
"props": { "className": "flex items-center justify-between text-xs text-gray-500 dark:text-gray-400 mb-2" },
|
||||
"children": [
|
||||
{ "type": "basic", "name": "Span", "text": "$t:shop.additional_options_amount" },
|
||||
{ "type": "basic", "name": "Span", "text": "{{(() => { const c = (_global.availableCurrencies ?? []).find(x => x.is_default); const dp = c?.decimal_places ?? 0; const n = (item.additionalOptionsTotal ?? 0).toLocaleString(undefined, { minimumFractionDigits: dp, maximumFractionDigits: dp }); const s = c?.symbol ?? '₩'; return '+' + ((c?.code === 'KRW' || s === '₩' || s === '원') ? (n + '원') : (s + n)); })()}}" }
|
||||
{ "comment": "추가금은 기본통화 기준으로 저장되므로 표시통화 환산값(핸들러가 서버 맵에서 계산)을 우선 사용한다. 없으면 기본통화 표기로 폴백", "type": "basic", "name": "Span", "text": "{{(() => { const code = _global.preferredCurrency ?? _global.defaultCurrency; const cur = (_global.availableCurrencies ?? []).find(x => x.code === code) ?? (_global.availableCurrencies ?? []).find(x => x.is_default); const converted = item.additionalOptionsMultiCurrencyTotal?.[code]?.value; const amount = converted ?? (item.additionalOptionsTotal ?? 0); const dp = cur?.decimal_places ?? 0; const n = amount.toLocaleString(undefined, { minimumFractionDigits: dp, maximumFractionDigits: dp }); const s = cur?.symbol ?? '₩'; return '+' + ((cur?.code === 'KRW' || s === '₩' || s === '원') ? (n + '원') : (s + n)); })()}}" }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -361,7 +361,7 @@
|
||||
"itemIndex": "{{itemIndex}}",
|
||||
"newQuantity": "{{$args[0]}}",
|
||||
"selectedOptionItems": "{{_local.selectedOptionItems}}",
|
||||
"preferredCurrency": "{{_global.preferredCurrency ?? 'KRW'}}"
|
||||
"preferredCurrency": "{{_global.preferredCurrency ?? _global.defaultCurrency}}"
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -370,7 +370,7 @@
|
||||
"type": "basic",
|
||||
"name": "Span",
|
||||
"props": { "className": "text-sm font-bold text-gray-900 dark:text-white" },
|
||||
"text": "{{item.multiCurrencyTotalPrice?.[_global.preferredCurrency ?? 'KRW']?.formatted ?? item.totalPriceFormatted}}"
|
||||
"text": "{{item.multiCurrencyTotalPrice?.[_global.preferredCurrency ?? _global.defaultCurrency]?.formatted ?? item.totalPriceFormatted}}"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -404,7 +404,7 @@
|
||||
"type": "basic",
|
||||
"name": "Span",
|
||||
"props": { "className": "text-xl font-bold text-gray-900 dark:text-white" },
|
||||
"text": "{{(_local.selectedOptionItems ?? []).length === 0 ? '-' : (_local.selectedTotalMultiCurrency?.[_global.preferredCurrency ?? 'KRW']?.formatted ?? _local.selectedOptionItems[0]?.multiCurrencyTotalPrice?.[_global.preferredCurrency ?? 'KRW']?.formatted ?? _local.selectedOptionItems[0]?.totalPriceFormatted ?? '')}}"
|
||||
"text": "{{(_local.selectedOptionItems ?? []).length === 0 ? '-' : (_local.selectedTotalMultiCurrency?.[_global.preferredCurrency ?? _global.defaultCurrency]?.formatted ?? _local.selectedOptionItems[0]?.multiCurrencyTotalPrice?.[_global.preferredCurrency ?? _global.defaultCurrency]?.formatted ?? _local.selectedOptionItems[0]?.totalPriceFormatted ?? '')}}"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -425,7 +425,7 @@
|
||||
"type": "basic",
|
||||
"name": "Span",
|
||||
"props": { "className": "text-xl font-bold text-gray-900 dark:text-white" },
|
||||
"text": "{{_local.noOptionTotalMultiCurrency?.[_global.preferredCurrency ?? 'KRW']?.formatted ?? product.data?.multi_currency_selling_price?.[_global.preferredCurrency ?? 'KRW']?.formatted ?? product.data?.selling_price_formatted ?? ''}}"
|
||||
"text": "{{_local.noOptionTotalMultiCurrency?.[_global.preferredCurrency ?? _global.defaultCurrency]?.formatted ?? product.data?.multi_currency_selling_price?.[_global.preferredCurrency ?? _global.defaultCurrency]?.formatted ?? product.data?.selling_price_formatted ?? ''}}"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -229,7 +229,7 @@
|
||||
"props": { "className": "flex justify-between" },
|
||||
"children": [
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-gray-600 dark:text-gray-400" }, "text": "$t:shop.order_complete.deposit_amount" },
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "font-bold text-blue-600 dark:text-blue-400 text-lg" }, "text": "{{orderData?.data?.mc_total_amount?.[_global.preferredCurrency ?? 'KRW']?.formatted ?? orderData?.data?.total_amount_formatted}}" }
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "font-bold text-blue-600 dark:text-blue-400 text-lg" }, "text": "{{orderData?.data?.mc_total_amount?.[_global.preferredCurrency ?? _global.defaultCurrency]?.formatted ?? orderData?.data?.total_amount_formatted}}" }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -472,7 +472,7 @@
|
||||
"comment": "단가 x 수량 (주 통화)",
|
||||
"type": "basic", "name": "P",
|
||||
"props": { "className": "text-sm text-gray-600 dark:text-gray-400" },
|
||||
"text": "{{item.mc_unit_price?.[_global.preferredCurrency ?? 'KRW']?.formatted ?? item.unit_price_formatted}} x {{item.quantity}}"
|
||||
"text": "{{item.mc_unit_price?.[_global.preferredCurrency ?? _global.defaultCurrency]?.formatted ?? item.unit_price_formatted}} x {{item.quantity}}"
|
||||
},
|
||||
{
|
||||
"comment": "단가 보조 통화들",
|
||||
@@ -481,7 +481,7 @@
|
||||
"if": "{{Object.keys(item.mc_unit_price ?? {}).length > 1}}",
|
||||
"props": { "className": "flex flex-wrap gap-x-2" },
|
||||
"iteration": {
|
||||
"source": "{{Object.entries(item.mc_unit_price ?? {}).filter(([code]) => code !== (_global.preferredCurrency ?? 'KRW'))}}",
|
||||
"source": "{{Object.entries(item.mc_unit_price ?? {}).filter(([code]) => code !== (_global.preferredCurrency ?? _global.defaultCurrency))}}",
|
||||
"item_var": "currency"
|
||||
},
|
||||
"children": [
|
||||
@@ -513,7 +513,7 @@
|
||||
"type": "basic",
|
||||
"name": "Span",
|
||||
"props": { "className": "font-medium text-gray-900 dark:text-white" },
|
||||
"text": "{{item.mc_subtotal_price?.[_global.preferredCurrency ?? 'KRW']?.formatted ?? item.subtotal_price_formatted}}"
|
||||
"text": "{{item.mc_subtotal_price?.[_global.preferredCurrency ?? _global.defaultCurrency]?.formatted ?? item.subtotal_price_formatted}}"
|
||||
},
|
||||
{
|
||||
"comment": "소계 보조 통화들",
|
||||
@@ -522,7 +522,7 @@
|
||||
"if": "{{Object.keys(item.mc_subtotal_price ?? {}).length > 1}}",
|
||||
"props": { "className": "flex flex-wrap gap-x-2 justify-end" },
|
||||
"iteration": {
|
||||
"source": "{{Object.entries(item.mc_subtotal_price ?? {}).filter(([code]) => code !== (_global.preferredCurrency ?? 'KRW'))}}",
|
||||
"source": "{{Object.entries(item.mc_subtotal_price ?? {}).filter(([code]) => code !== (_global.preferredCurrency ?? _global.defaultCurrency))}}",
|
||||
"item_var": "currency"
|
||||
},
|
||||
"children": [
|
||||
@@ -621,7 +621,7 @@
|
||||
"comment": "상품 금액",
|
||||
"type": "basic", "name": "Div", "props": { "className": "flex justify-between mb-2" }, "children": [
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-gray-600 dark:text-gray-400" }, "text": "$t:shop.order_complete.subtotal" },
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-gray-900 dark:text-white" }, "text": "{{orderData?.data?.mc_subtotal_amount?.[_global.preferredCurrency ?? 'KRW']?.formatted ?? orderData?.data?.subtotal_amount_formatted}}" }
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-gray-900 dark:text-white" }, "text": "{{orderData?.data?.mc_subtotal_amount?.[_global.preferredCurrency ?? _global.defaultCurrency]?.formatted ?? orderData?.data?.subtotal_amount_formatted}}" }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -632,7 +632,7 @@
|
||||
"comment": "Number() 캐스팅 — Order.total_shipping_amount 는 decimal:2 cast 로 '0.00' 문자열이 응답에 올 수 있어 === 0 비교가 false 회귀",
|
||||
"type": "basic", "name": "Span",
|
||||
"props": { "className": "{{Number(orderData?.data?.total_shipping_amount ?? 0) === 0 ? 'text-green-600 dark:text-green-400' : 'text-gray-900 dark:text-white'}}" },
|
||||
"text": "{{Number(orderData?.data?.total_shipping_amount ?? 0) === 0 ? '$t:shop.checkout.free' : (orderData?.data?.mc_total_shipping_amount?.[_global.preferredCurrency ?? 'KRW']?.formatted ?? orderData?.data?.total_shipping_amount_formatted)}}"
|
||||
"text": "{{Number(orderData?.data?.total_shipping_amount ?? 0) === 0 ? '$t:shop.checkout.free' : (orderData?.data?.mc_total_shipping_amount?.[_global.preferredCurrency ?? _global.defaultCurrency]?.formatted ?? orderData?.data?.total_shipping_amount_formatted)}}"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -642,7 +642,7 @@
|
||||
"if": "{{(orderData?.data?.total_discount_amount ?? 0) > 0}}",
|
||||
"props": { "className": "flex justify-between mb-2 text-red-600 dark:text-red-400" }, "children": [
|
||||
{ "type": "basic", "name": "Span", "text": "$t:shop.order_complete.discount" },
|
||||
{ "type": "basic", "name": "Span", "text": "-{{orderData?.data?.mc_total_discount_amount?.[_global.preferredCurrency ?? 'KRW']?.formatted ?? orderData?.data?.total_discount_amount_formatted}}" }
|
||||
{ "type": "basic", "name": "Span", "text": "-{{orderData?.data?.mc_total_discount_amount?.[_global.preferredCurrency ?? _global.defaultCurrency]?.formatted ?? orderData?.data?.total_discount_amount_formatted}}" }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -659,7 +659,7 @@
|
||||
"type": "basic", "name": "Div",
|
||||
"props": { "className": "flex justify-between items-center pt-2 border-t border-gray-200 dark:border-gray-700 font-bold text-lg" }, "children": [
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-gray-900 dark:text-white" }, "text": "$t:shop.order_complete.total" },
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-blue-600 dark:text-blue-400" }, "text": "{{orderData?.data?.mc_total_amount?.[_global.preferredCurrency ?? 'KRW']?.formatted ?? orderData?.data?.total_amount_formatted}}" }
|
||||
{ "type": "basic", "name": "Span", "props": { "className": "text-blue-600 dark:text-blue-400" }, "text": "{{orderData?.data?.mc_total_amount?.[_global.preferredCurrency ?? _global.defaultCurrency]?.formatted ?? orderData?.data?.total_amount_formatted}}" }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -669,7 +669,7 @@
|
||||
"if": "{{Object.keys(orderData?.data?.mc_total_amount ?? {}).length > 1}}",
|
||||
"props": { "className": "flex flex-wrap gap-x-2 justify-end mt-1" },
|
||||
"iteration": {
|
||||
"source": "{{Object.entries(orderData?.data?.mc_total_amount ?? {}).filter(([code]) => code !== (_global.preferredCurrency ?? 'KRW'))}}",
|
||||
"source": "{{Object.entries(orderData?.data?.mc_total_amount ?? {}).filter(([code]) => code !== (_global.preferredCurrency ?? _global.defaultCurrency))}}",
|
||||
"item_var": "currency"
|
||||
},
|
||||
"children": [
|
||||
|
||||
@@ -27,12 +27,15 @@ import type { EditorAttrs } from '../../types';
|
||||
* 통화를 바꿔도 카드가 리렌더되지 않았다(상품 리스트/검색/캐러셀 KRW 고정 결함).
|
||||
* → 전체 객체에서 `.preferredCurrency` 키로 접근 + state.subscribe 로 리렌더한다.
|
||||
*
|
||||
* @returns 현재 선호 통화 코드(미설정 시 'KRW')
|
||||
* 표시 통화 미설정 시에는 쇼핑몰 기본 통화로 폴백한다 — 특정 통화를 못 박으면
|
||||
* 그 통화를 쓰지 않는 상점에서 multi_currency 조회가 늘 빗나가 기준통화 폴백이 된다.
|
||||
*
|
||||
* @returns 현재 선호 통화 코드 (없으면 기본 통화, 그것도 없으면 빈 문자열)
|
||||
*/
|
||||
const usePreferredCurrency = (): string => {
|
||||
const read = (): string => {
|
||||
const state = (window as any).G7Core?.state?.get?.();
|
||||
return (state && state.preferredCurrency) || 'KRW';
|
||||
return (state && (state.preferredCurrency || state.defaultCurrency)) || '';
|
||||
};
|
||||
const [currency, setCurrency] = useState<string>(read);
|
||||
|
||||
@@ -42,7 +45,7 @@ const usePreferredCurrency = (): string => {
|
||||
setCurrency(read());
|
||||
if (typeof subscribe !== 'function') return;
|
||||
const unsubscribe = subscribe((state: Record<string, any>) => {
|
||||
setCurrency((state && state.preferredCurrency) || 'KRW');
|
||||
setCurrency((state && (state.preferredCurrency || state.defaultCurrency)) || '');
|
||||
});
|
||||
return typeof unsubscribe === 'function' ? unsubscribe : undefined;
|
||||
}, []);
|
||||
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* 통화 포맷 핸들러의 설정 기반 동작 회귀 테스트
|
||||
*
|
||||
* 종전 formatCurrencyHandler 는 KRW/USD/JPY/CNY/EUR 5종 고정 표를 두고
|
||||
* `CURRENCY_CONFIGS[currency] || CURRENCY_CONFIGS.KRW` 로 폴백했다. 운영자가 관리자에서
|
||||
* 통화를 추가할 수 있으므로(예: GBP), 표에 없는 통화는 값은 그 통화인데 기호·자릿수만
|
||||
* 원화로 표기되는 상태가 됐다.
|
||||
*
|
||||
* 통화 표기는 설정(`language_currency.currencies`)이 정한다는 것을 고정한다.
|
||||
*/
|
||||
|
||||
import { describe, it, expect, afterEach } from 'vitest';
|
||||
import { formatCurrencyHandler, getCurrencySymbol } from '../formatCurrency';
|
||||
|
||||
/**
|
||||
* 전역 통화 설정과 표시 통화를 심습니다.
|
||||
*
|
||||
* @param currencies 설정에 등록된 통화 목록
|
||||
* @param preferred 표시 통화 코드
|
||||
*/
|
||||
function installState(currencies: any[], preferred?: string): any {
|
||||
const state: Record<string, any> = {
|
||||
modules: { 'sirsoft-ecommerce': { language_currency: { currencies } } },
|
||||
};
|
||||
|
||||
if (preferred) {
|
||||
state.preferredCurrency = preferred;
|
||||
}
|
||||
|
||||
(window as any).G7Core = { state: { get: () => state } };
|
||||
|
||||
return {
|
||||
setState: () => {},
|
||||
getState: (path: string) => {
|
||||
if (path === '_global.preferredCurrency') return state.preferredCurrency;
|
||||
if (path === '_global.defaultCurrency') return state.defaultCurrency;
|
||||
|
||||
return undefined;
|
||||
},
|
||||
} as any;
|
||||
}
|
||||
|
||||
describe('formatCurrencyHandler — 설정 기반 통화 표기', () => {
|
||||
afterEach(() => {
|
||||
delete (window as any).G7Core;
|
||||
});
|
||||
|
||||
it('설정에만 있는 통화(GBP)를 원화로 폴백하지 않는다', () => {
|
||||
const ctx = installState(
|
||||
[{ code: 'GBP', symbol: '£', decimal_places: 2, is_default: true }],
|
||||
'GBP'
|
||||
);
|
||||
|
||||
const formatted = formatCurrencyHandler({ value: 1234.5 }, ctx);
|
||||
|
||||
expect(formatted).toContain('£');
|
||||
expect(formatted).not.toContain('₩');
|
||||
expect(formatted).not.toContain('원');
|
||||
});
|
||||
|
||||
it('설정의 decimal_places 를 따른다', () => {
|
||||
const ctx = installState(
|
||||
[{ code: 'GBP', symbol: '£', decimal_places: 2, is_default: true }],
|
||||
'GBP'
|
||||
);
|
||||
|
||||
expect(formatCurrencyHandler({ value: 1234.5 }, ctx)).toBe('£1,234.50');
|
||||
});
|
||||
|
||||
it('원화는 기존과 동일하게 금액 뒤에 원을 붙인다', () => {
|
||||
const ctx = installState(
|
||||
[{ code: 'KRW', symbol: '₩', decimal_places: 0, is_default: true }],
|
||||
'KRW'
|
||||
);
|
||||
|
||||
expect(formatCurrencyHandler({ value: 10000 }, ctx)).toBe('10,000원');
|
||||
});
|
||||
|
||||
it('표시 통화가 없으면 기본 통화로 표기한다', () => {
|
||||
const ctx = installState([
|
||||
{ code: 'JPY', symbol: '¥', decimal_places: 0, is_default: true },
|
||||
{ code: 'KRW', symbol: '₩', decimal_places: 0 },
|
||||
]);
|
||||
|
||||
const formatted = formatCurrencyHandler({ value: 5000 }, ctx);
|
||||
|
||||
expect(formatted).toContain('¥');
|
||||
expect(formatted).not.toContain('원');
|
||||
});
|
||||
|
||||
it('통화를 판정할 수 없으면 단위를 임의로 붙이지 않는다', () => {
|
||||
const ctx = installState([]);
|
||||
|
||||
const formatted = formatCurrencyHandler({ value: 3000 }, ctx);
|
||||
|
||||
expect(formatted).not.toContain('원');
|
||||
expect(formatted).not.toContain('₩');
|
||||
});
|
||||
|
||||
it('getCurrencySymbol 은 설정의 기호를 우선한다', () => {
|
||||
installState([{ code: 'CNY', symbol: '元', decimal_places: 2 }]);
|
||||
|
||||
expect(getCurrencySymbol('CNY')).toBe('元');
|
||||
});
|
||||
});
|
||||
+212
@@ -0,0 +1,212 @@
|
||||
/**
|
||||
* @file productOptionsAdditionalCurrency.test.ts
|
||||
* @description 추가옵션 추가금의 통화 환산 회귀 테스트
|
||||
*
|
||||
* 배경: 추가옵션 추가금은 쇼핑몰 **기본 통화** 기준으로 저장되는데, 총액 재계산이 그 값을
|
||||
* KRW 로 간주해 표시통화가 KRW 이면 환산 없이 그대로 더했다. 기본통화가 KRW 인 쇼핑몰에서만
|
||||
* 우연히 맞았고, 기본통화가 JPY 인 쇼핑몰을 KRW 로 보면
|
||||
* "환산된 상품가 + 환산 안 된 추가금" 이라는 통화가 섞인 합계가 표시됐다.
|
||||
*
|
||||
* 서버가 선택지마다 `multi_currency_price_adjustment` 를 내려주므로, 총액은 그 맵의
|
||||
* 표시통화 값을 써야 한다.
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import {
|
||||
updateSelectedItemQuantityHandler,
|
||||
addSelectedItemIfCompleteHandler,
|
||||
} from '../productOptions';
|
||||
|
||||
const mockG7Core = {
|
||||
state: { get: vi.fn(() => ({})), getLocal: vi.fn(() => ({})), setLocal: vi.fn() },
|
||||
toast: { show: vi.fn(), warning: vi.fn() },
|
||||
t: vi.fn((key: string) => key),
|
||||
};
|
||||
|
||||
/**
|
||||
* 기본통화 JPY / 표시통화 KRW 인 쇼핑몰의 옵션 단가 맵.
|
||||
* 상품가 10,000 JPY = 95,000 KRW (1,000 JPY = 9,500 KRW 가정).
|
||||
*/
|
||||
const unitPriceMap = {
|
||||
JPY: { value: 10000, formatted: '¥10,000' },
|
||||
KRW: { value: 95000, formatted: '95,000원' },
|
||||
};
|
||||
|
||||
/**
|
||||
* 추가금 5,000 JPY = 47,500 KRW 인 추가옵션 그룹.
|
||||
*/
|
||||
const additionalOptionGroups = [
|
||||
{
|
||||
id: 10,
|
||||
name: '포장 방식',
|
||||
is_required: false,
|
||||
values: [
|
||||
{
|
||||
id: 24,
|
||||
name: '프리미엄 포장',
|
||||
price_adjustment: 5000,
|
||||
is_default: false,
|
||||
multi_currency_price_adjustment: {
|
||||
JPY: { price: 5000, formatted: '+¥5,000', is_default: true },
|
||||
KRW: { price: 47500, formatted: '+47,500원', is_default: false },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const makeItem = (overrides: any = {}) => ({
|
||||
id: 'opt1',
|
||||
optionId: 1,
|
||||
options: {},
|
||||
optionValues: {},
|
||||
quantity: 1,
|
||||
stock: 99,
|
||||
unitPrice: 10000,
|
||||
unitPriceFormatted: '¥10,000',
|
||||
totalPrice: 15000,
|
||||
totalPriceFormatted: '¥15,000',
|
||||
multiCurrencyUnitPrice: { ...unitPriceMap },
|
||||
multiCurrencyTotalPrice: { ...unitPriceMap },
|
||||
additionalOptionSelections: { 10: 24 },
|
||||
additionalOptionsTotal: 5000,
|
||||
additionalOptionsMultiCurrencyTotal: {
|
||||
JPY: { value: 5000 },
|
||||
KRW: { value: 47500 },
|
||||
},
|
||||
...overrides,
|
||||
});
|
||||
|
||||
describe('추가옵션 추가금 통화 환산 (기본통화 ≠ 표시통화)', () => {
|
||||
beforeEach(() => {
|
||||
(window as any).G7Core = mockG7Core;
|
||||
});
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
delete (window as any).G7Core;
|
||||
});
|
||||
|
||||
it('표시통화 KRW 합계에 추가금이 환산되어 더해진다 (기준통화 값이 그대로 섞이지 않는다)', () => {
|
||||
const setState = vi.fn();
|
||||
|
||||
updateSelectedItemQuantityHandler(
|
||||
{
|
||||
handler: 'x',
|
||||
params: {
|
||||
itemIndex: 0,
|
||||
newQuantity: 1,
|
||||
selectedOptionItems: [makeItem()],
|
||||
preferredCurrency: 'KRW',
|
||||
additionalOptionGroups,
|
||||
},
|
||||
} as any,
|
||||
{ setState } as any
|
||||
);
|
||||
|
||||
const arg = setState.mock.calls[0][0];
|
||||
|
||||
// 기대: 95,000(상품가 환산) + 47,500(추가금 환산) = 142,500
|
||||
// 수정 전: 95,000 + 5,000(환산 안 된 기준통화 값) = 100,000
|
||||
expect(arg.selectedTotalMultiCurrency.KRW.value).toBe(142500);
|
||||
|
||||
// 기본통화(JPY)는 원값 그대로 10,000 + 5,000 = 15,000
|
||||
expect(arg.selectedTotalMultiCurrency.JPY.value).toBe(15000);
|
||||
});
|
||||
|
||||
it('수량이 늘면 환산된 추가금도 수량만큼 곱해진다', () => {
|
||||
const setState = vi.fn();
|
||||
|
||||
updateSelectedItemQuantityHandler(
|
||||
{
|
||||
handler: 'x',
|
||||
params: {
|
||||
itemIndex: 0,
|
||||
newQuantity: 3,
|
||||
selectedOptionItems: [makeItem()],
|
||||
preferredCurrency: 'KRW',
|
||||
additionalOptionGroups,
|
||||
},
|
||||
} as any,
|
||||
{ setState } as any
|
||||
);
|
||||
|
||||
const arg = setState.mock.calls[0][0];
|
||||
|
||||
// (95,000 + 47,500) × 3 = 427,500
|
||||
expect(arg.selectedTotalMultiCurrency.KRW.value).toBe(427500);
|
||||
});
|
||||
|
||||
it('추가옵션을 선택하지 않으면 상품가만 환산되어 합계가 된다', () => {
|
||||
const setState = vi.fn();
|
||||
|
||||
updateSelectedItemQuantityHandler(
|
||||
{
|
||||
handler: 'x',
|
||||
params: {
|
||||
itemIndex: 0,
|
||||
newQuantity: 2,
|
||||
selectedOptionItems: [
|
||||
makeItem({
|
||||
additionalOptionSelections: {},
|
||||
additionalOptionsTotal: 0,
|
||||
additionalOptionsMultiCurrencyTotal: undefined,
|
||||
}),
|
||||
],
|
||||
preferredCurrency: 'KRW',
|
||||
additionalOptionGroups,
|
||||
},
|
||||
} as any,
|
||||
{ setState } as any
|
||||
);
|
||||
|
||||
const arg = setState.mock.calls[0][0];
|
||||
expect(arg.selectedTotalMultiCurrency.KRW.value).toBe(190000);
|
||||
});
|
||||
|
||||
// 블럭 생성 시 자동 적용되는 기본 선택지는 **필수 그룹** 한정이므로 is_required: true 로 둔다
|
||||
it('선택 항목 생성 시 통화별 추가금 합계가 항목에 실린다', () => {
|
||||
const setState = vi.fn();
|
||||
|
||||
addSelectedItemIfCompleteHandler(
|
||||
{
|
||||
handler: 'x',
|
||||
params: {
|
||||
productId: 28,
|
||||
optionGroups: [{ name: '길이', values: ['1m'] }],
|
||||
options: [
|
||||
{
|
||||
id: 1,
|
||||
option_values: { 길이: '1m' },
|
||||
stock_quantity: 10,
|
||||
is_active: true,
|
||||
selling_price: 10000,
|
||||
selling_price_formatted: '¥10,000',
|
||||
multi_currency_selling_price: {
|
||||
JPY: { price: 10000, formatted: '¥10,000' },
|
||||
KRW: { price: 95000, formatted: '95,000원' },
|
||||
},
|
||||
},
|
||||
],
|
||||
currentSelection: { 길이: '1m' },
|
||||
selectedOptionItems: [],
|
||||
preferredCurrency: 'KRW',
|
||||
additionalOptionGroups: [
|
||||
{
|
||||
...additionalOptionGroups[0],
|
||||
is_required: true,
|
||||
values: [{ ...additionalOptionGroups[0].values[0], is_default: true }],
|
||||
},
|
||||
],
|
||||
},
|
||||
} as any,
|
||||
{ setState } as any
|
||||
);
|
||||
|
||||
const arg = setState.mock.calls[0][0];
|
||||
const item = arg.selectedOptionItems?.[0];
|
||||
|
||||
expect(item).toBeDefined();
|
||||
expect(item.additionalOptionsMultiCurrencyTotal?.KRW?.value).toBe(47500);
|
||||
expect(arg.selectedTotalMultiCurrency.KRW.value).toBe(142500);
|
||||
});
|
||||
});
|
||||
@@ -2,6 +2,11 @@
|
||||
* 통화 포맷팅 핸들러
|
||||
*
|
||||
* 숫자 값을 통화 형식 문자열로 변환합니다.
|
||||
*
|
||||
* 통화 목록은 운영자가 관리자에서 추가/삭제하므로, 이 핸들러는 통화표를 고정해 두지
|
||||
* 않고 쇼핑몰 설정(`language_currency.currencies`)의 기호(symbol)와 소수 자릿수
|
||||
* (decimal_places)를 따릅니다. 고정 통화표를 두면 설정에 없는 통화가 전부 특정 통화로
|
||||
* 폴백되어(예: GBP 를 ₩ + 0자리로) 값은 맞고 단위만 틀린 금액이 화면에 나갑니다.
|
||||
*/
|
||||
|
||||
import { HandlerContext } from '../types';
|
||||
@@ -14,71 +19,116 @@ interface FormatCurrencyParams {
|
||||
|
||||
interface CurrencyConfig {
|
||||
code: string;
|
||||
symbol: string;
|
||||
locale: string;
|
||||
decimals: number;
|
||||
symbol?: string;
|
||||
decimal_places?: number;
|
||||
is_default?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 통화별 기본 설정
|
||||
* 설정에 symbol 이 비어 있을 때만 쓰는 기호 폴백.
|
||||
* 위안화(CNY)는 엔화(¥)와 구분되도록 元 을 사용합니다 (이커머스 모듈과 동일 규칙).
|
||||
*/
|
||||
const CURRENCY_CONFIGS: Record<string, CurrencyConfig> = {
|
||||
KRW: { code: 'KRW', symbol: '₩', locale: 'ko-KR', decimals: 0 },
|
||||
USD: { code: 'USD', symbol: '$', locale: 'en-US', decimals: 2 },
|
||||
JPY: { code: 'JPY', symbol: '¥', locale: 'ja-JP', decimals: 0 },
|
||||
CNY: { code: 'CNY', symbol: '¥', locale: 'zh-CN', decimals: 2 },
|
||||
EUR: { code: 'EUR', symbol: '€', locale: 'de-DE', decimals: 2 },
|
||||
const SYMBOL_FALLBACK: Record<string, string> = {
|
||||
KRW: '₩',
|
||||
USD: '$',
|
||||
JPY: '¥',
|
||||
CNY: '元',
|
||||
EUR: '€',
|
||||
GBP: '£',
|
||||
};
|
||||
|
||||
/**
|
||||
* 설정에 decimal_places 가 없을 때만 쓰는 소수 자릿수 폴백.
|
||||
*/
|
||||
const DECIMAL_PLACES_FALLBACK: Record<string, number> = {
|
||||
KRW: 0,
|
||||
JPY: 0,
|
||||
};
|
||||
|
||||
/**
|
||||
* 쇼핑몰 설정에 등록된 통화 목록을 전역 상태에서 읽습니다.
|
||||
*
|
||||
* @returns currencies 배열 (읽지 못하면 빈 배열)
|
||||
*/
|
||||
function getConfiguredCurrencies(): CurrencyConfig[] {
|
||||
try {
|
||||
const state = (window as any).G7Core?.state?.get?.() || {};
|
||||
const lc = state?.modules?.['sirsoft-ecommerce']?.language_currency;
|
||||
|
||||
return Array.isArray(lc?.currencies) ? lc.currencies : [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 숫자 값을 통화 형식으로 포맷팅합니다.
|
||||
*
|
||||
* 원화 계열(₩/원)은 금액 뒤에 "원", 그 외는 기호를 앞에 붙입니다
|
||||
* (백엔드 messages.currency.prefix/suffix 와 동일한 표기 규칙).
|
||||
*
|
||||
* @param params.value - 포맷팅할 숫자 값
|
||||
* @param params.currencyCode - 통화 코드 (KRW, USD, JPY 등)
|
||||
* @param params.locale - 로케일 (선택, 미지정 시 통화 기본 로케일 사용)
|
||||
* @param params.currencyCode - 통화 코드 (미지정 시 표시 통화 → 기본 통화 순)
|
||||
* @param params.locale - 로케일 (숫자 구분자용, 미지정 시 브라우저 로케일)
|
||||
* @param context - 핸들러 컨텍스트
|
||||
* @returns 포맷팅된 통화 문자열
|
||||
*
|
||||
* @example
|
||||
* formatCurrencyHandler({ value: 10000, currencyCode: 'KRW' }, context)
|
||||
* // => "₩10,000" 또는 "10,000원"
|
||||
*
|
||||
* formatCurrencyHandler({ value: 99.99, currencyCode: 'USD' }, context)
|
||||
* // => "$99.99"
|
||||
* formatCurrencyHandler({ value: 10000, currencyCode: 'KRW' }, context) // => "10,000원"
|
||||
* formatCurrencyHandler({ value: 99.99, currencyCode: 'USD' }, context) // => "$99.99"
|
||||
*/
|
||||
export function formatCurrencyHandler(
|
||||
params: FormatCurrencyParams,
|
||||
context: HandlerContext
|
||||
): string {
|
||||
const { value, currencyCode, locale: customLocale } = params;
|
||||
const currency = currencyCode || context.getState('_global.preferredCurrency') || 'KRW';
|
||||
const currencies = getConfiguredCurrencies();
|
||||
|
||||
const config = CURRENCY_CONFIGS[currency] || CURRENCY_CONFIGS.KRW;
|
||||
const locale = customLocale || config.locale;
|
||||
const code = currencyCode
|
||||
|| context.getState('_global.preferredCurrency')
|
||||
|| context.getState('_global.defaultCurrency')
|
||||
|| currencies.find((c) => c.is_default)?.code;
|
||||
|
||||
try {
|
||||
return new Intl.NumberFormat(locale, {
|
||||
style: 'currency',
|
||||
currency: config.code,
|
||||
minimumFractionDigits: config.decimals,
|
||||
maximumFractionDigits: config.decimals,
|
||||
}).format(value);
|
||||
} catch {
|
||||
// Intl 지원 안 되는 환경 폴백
|
||||
const formatted = value.toLocaleString(undefined, {
|
||||
minimumFractionDigits: config.decimals,
|
||||
maximumFractionDigits: config.decimals,
|
||||
});
|
||||
return `${config.symbol}${formatted}`;
|
||||
if (!code) {
|
||||
// 통화를 판정할 수 없으면 단위를 임의로 붙이지 않는다 — 그것이 곧 하드코딩이다.
|
||||
return value.toLocaleString(customLocale);
|
||||
}
|
||||
|
||||
const config = currencies.find((c) => c.code === code);
|
||||
const symbol = (config?.symbol && config.symbol.length > 0)
|
||||
? config.symbol
|
||||
: (SYMBOL_FALLBACK[code] ?? '');
|
||||
const decimals = config?.decimal_places ?? DECIMAL_PLACES_FALLBACK[code] ?? 2;
|
||||
|
||||
const formatted = value.toLocaleString(customLocale, {
|
||||
minimumFractionDigits: decimals,
|
||||
maximumFractionDigits: decimals,
|
||||
});
|
||||
|
||||
// 기호를 모르면 코드 접미 (백엔드 동일 규칙)
|
||||
if (!symbol) {
|
||||
return `${formatted} ${code}`;
|
||||
}
|
||||
|
||||
if (code === 'KRW' || symbol === '₩' || symbol === '원') {
|
||||
return `${formatted}원`;
|
||||
}
|
||||
|
||||
return `${symbol}${formatted}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 통화 심볼만 반환합니다.
|
||||
*
|
||||
* 설정의 symbol 을 우선하고, 없으면 폴백 표, 그것도 없으면 통화 코드를 그대로 돌려줍니다.
|
||||
*
|
||||
* @param currencyCode - 통화 코드
|
||||
* @returns 통화 심볼
|
||||
*/
|
||||
export function getCurrencySymbol(currencyCode: string): string {
|
||||
return CURRENCY_CONFIGS[currencyCode]?.symbol || currencyCode;
|
||||
const config = getConfiguredCurrencies().find((c) => c.code === currencyCode);
|
||||
|
||||
return (config?.symbol && config.symbol.length > 0)
|
||||
? config.symbol
|
||||
: (SYMBOL_FALLBACK[currencyCode] || currencyCode);
|
||||
}
|
||||
|
||||
@@ -47,7 +47,11 @@ export function getDisplayPriceHandler(
|
||||
context: HandlerContext
|
||||
): string {
|
||||
const { product, priceField, currencyCode } = params;
|
||||
const preferredCurrency = currencyCode || context.getState('_global.preferredCurrency') || 'KRW';
|
||||
// 통화를 못 박지 않는다 — 판정 실패 시 아래 *_formatted(기준 통화 표기)로 폴백한다.
|
||||
const preferredCurrency = currencyCode
|
||||
|| context.getState('_global.preferredCurrency')
|
||||
|| context.getState('_global.defaultCurrency')
|
||||
|| '';
|
||||
|
||||
const multiCurrencyField = `multi_currency_${priceField}` as keyof Product;
|
||||
const multiCurrencyData = product[multiCurrencyField] as Record<string, CurrencyPrice> | undefined;
|
||||
|
||||
@@ -14,7 +14,25 @@ const logger = ((window as any).G7Core?.createLogger?.('Handler:Currency')) ?? {
|
||||
import { HandlerContext } from '../types';
|
||||
|
||||
const STORAGE_KEY = 'g7_preferred_currency';
|
||||
const DEFAULT_CURRENCY = 'KRW';
|
||||
|
||||
/**
|
||||
* 쇼핑몰 설정의 기본 통화를 전역 상태에서 읽습니다.
|
||||
*
|
||||
* 특정 통화를 상수로 못 박으면 그 통화를 쓰지 않는 상점에서 표시 통화가 잘못 초기화된다.
|
||||
*
|
||||
* @returns 기본 통화 코드 (읽지 못하면 빈 문자열)
|
||||
*/
|
||||
function resolveConfiguredDefaultCurrency(): string {
|
||||
try {
|
||||
const state = (window as any).G7Core?.state?.get?.() || {};
|
||||
|
||||
return state.defaultCurrency
|
||||
|| state?.modules?.['sirsoft-ecommerce']?.language_currency?.default_currency
|
||||
|| '';
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
interface LoadPreferredCurrencyParams {
|
||||
defaultCurrency?: string;
|
||||
@@ -42,7 +60,7 @@ export function loadPreferredCurrencyHandler(
|
||||
params: LoadPreferredCurrencyParams,
|
||||
context: HandlerContext
|
||||
): string {
|
||||
const { defaultCurrency = DEFAULT_CURRENCY } = params;
|
||||
const { defaultCurrency = resolveConfiguredDefaultCurrency() } = params;
|
||||
|
||||
let currency = defaultCurrency;
|
||||
|
||||
|
||||
@@ -61,7 +61,15 @@ interface ProductOptionRecord {
|
||||
interface AdditionalOptionValue {
|
||||
id: number;
|
||||
name: string;
|
||||
/** 추가금 (쇼핑몰 **기본 통화** 기준 — KRW 고정이 아니다) */
|
||||
price_adjustment: number;
|
||||
/**
|
||||
* 통화별 추가금 (서버 `multi_currency_price_adjustment`).
|
||||
*
|
||||
* 표시통화가 기본통화와 다르면 `price_adjustment` 를 그대로 쓰면 안 된다.
|
||||
* 상품가는 환산되어 오는데 추가금만 기준통화로 남으면 합계에서 통화가 섞인다.
|
||||
*/
|
||||
multi_currency_price_adjustment?: Record<string, { price?: number; value?: number; formatted: string }>;
|
||||
is_default: boolean;
|
||||
/** 직접입력 허용 — 이 선택지 선택 시 자유 텍스트 입력칸 노출(입력 필수) */
|
||||
allow_custom_text?: boolean;
|
||||
@@ -99,8 +107,10 @@ interface SelectedItem {
|
||||
additionalOptionSelections?: AdditionalOptionSelections;
|
||||
/** 블럭별 추가옵션 직접입력 텍스트 (그룹ID → custom_text). allow_custom_text 선택지 한정 */
|
||||
additionalOptionCustomTexts?: Record<number, string>;
|
||||
/** 블럭별 추가옵션 추가금 합계 (KRW 기준, 단위당) */
|
||||
/** 블럭별 추가옵션 추가금 합계 (기본 통화 기준, 단위당) */
|
||||
additionalOptionsTotal?: number;
|
||||
/** 블럭별 추가옵션 추가금 합계의 통화별 값 (단위당). 표시통화 합계 계산에 쓴다 */
|
||||
additionalOptionsMultiCurrencyTotal?: Record<string, { value: number }>;
|
||||
}
|
||||
|
||||
interface AddSelectedItemParams {
|
||||
@@ -157,68 +167,126 @@ interface ActionDefinition {
|
||||
}
|
||||
|
||||
/**
|
||||
* 통화별 포맷팅 설정
|
||||
* 통화 코드별 소수 자릿수 폴백 (설정에 decimal_places 가 없을 때만 사용).
|
||||
*/
|
||||
const CURRENCY_CONFIGS: Record<string, { locale: string; decimals: number }> = {
|
||||
KRW: { locale: 'ko-KR', decimals: 0 },
|
||||
USD: { locale: 'en-US', decimals: 2 },
|
||||
JPY: { locale: 'ja-JP', decimals: 0 },
|
||||
CNY: { locale: 'zh-CN', decimals: 2 },
|
||||
EUR: { locale: 'de-DE', decimals: 2 },
|
||||
const DECIMAL_PLACES_FALLBACK: Record<string, number> = {
|
||||
KRW: 0,
|
||||
JPY: 0,
|
||||
};
|
||||
|
||||
/**
|
||||
* 숫자를 통화 형식으로 포맷팅
|
||||
* 쇼핑몰 설정에 등록된 통화 목록을 전역 상태에서 읽습니다.
|
||||
*
|
||||
* @returns currencies 배열 (읽지 못하면 빈 배열)
|
||||
*/
|
||||
function formatPrice(amount: number, currencyCode: string = 'KRW'): string {
|
||||
if (!Number.isFinite(amount)) return '0';
|
||||
const config = CURRENCY_CONFIGS[currencyCode] || CURRENCY_CONFIGS.KRW;
|
||||
function getConfiguredCurrencies(): Array<{ code: string; decimal_places?: number; is_default?: boolean }> {
|
||||
try {
|
||||
return new Intl.NumberFormat(config.locale, {
|
||||
const state = (window as any).G7Core?.state?.get?.() || {};
|
||||
const lc = state?.modules?.['sirsoft-ecommerce']?.language_currency;
|
||||
|
||||
return Array.isArray(lc?.currencies) ? lc.currencies : [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 숫자를 통화 형식으로 포맷팅
|
||||
*
|
||||
* 통화 목록은 운영자가 관리자에서 추가/삭제하므로 고정 통화표를 두지 않는다. 설정에 없는
|
||||
* 통화를 특정 통화로 폴백시키면(예: GBP 를 원화 자릿수로) 값은 맞고 단위만 틀린 금액이 나간다.
|
||||
* 자릿수는 설정의 decimal_places 를 따르고, 기호·표기 규칙은 Intl 이 통화 코드로 판정한다.
|
||||
*
|
||||
* @param amount 금액
|
||||
* @param currencyCode 통화 코드 (미지정 시 설정의 기본 통화)
|
||||
* @returns 포맷된 금액 문자열
|
||||
*/
|
||||
function formatPrice(amount: number, currencyCode?: string): string {
|
||||
if (!Number.isFinite(amount)) return '0';
|
||||
|
||||
const currencies = getConfiguredCurrencies();
|
||||
const code = currencyCode || currencies.find((c) => c.is_default)?.code;
|
||||
|
||||
if (!code) {
|
||||
// 통화를 판정할 수 없으면 단위를 임의로 붙이지 않는다.
|
||||
return amount.toLocaleString();
|
||||
}
|
||||
|
||||
const decimals = currencies.find((c) => c.code === code)?.decimal_places
|
||||
?? DECIMAL_PLACES_FALLBACK[code]
|
||||
?? 2;
|
||||
|
||||
try {
|
||||
return new Intl.NumberFormat(undefined, {
|
||||
style: 'currency',
|
||||
currency: currencyCode,
|
||||
minimumFractionDigits: config.decimals,
|
||||
maximumFractionDigits: config.decimals,
|
||||
currency: code,
|
||||
minimumFractionDigits: decimals,
|
||||
maximumFractionDigits: decimals,
|
||||
}).format(amount);
|
||||
} catch {
|
||||
return amount.toLocaleString() + (currencyCode === 'KRW' ? '원' : '');
|
||||
return `${amount.toLocaleString(undefined, {
|
||||
minimumFractionDigits: decimals,
|
||||
maximumFractionDigits: decimals,
|
||||
})} ${code}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 다중 통화 총 가격 재계산
|
||||
*
|
||||
* 추가옵션 추가금(additionalUnitKrw)이 있으면 KRW 환율 기준으로 환산해 각 통화 단가에 가산한다.
|
||||
* 추가옵션은 공개 응답에 다통화 컬럼이 없으므로(plan §245) 기본통화(KRW) 단가 비율로 환산한다.
|
||||
* 추가옵션 추가금은 서버가 통화별로 환산해 내려준 맵(`additionalUnitByCurrency`)에서 읽는다.
|
||||
*
|
||||
* 종전에는 추가금을 KRW 로 간주해 표시통화가 KRW 이면 환산 없이 그대로 더했다. 추가금은
|
||||
* 쇼핑몰 **기본 통화** 기준이라 기본통화가 KRW 인 쇼핑몰에서만 우연히 맞았고, 기본통화가
|
||||
* 다른 쇼핑몰에서는 "환산된 상품가 + 환산 안 된 추가금" 이라는 통화가 섞인 합계가 나왔다.
|
||||
*
|
||||
* 서버 맵이 없는 응답(구버전)에서는 단가 비율로 환산한다. 이때 기준은 **기본 통화**다 —
|
||||
* 종전처럼 KRW 를 기준으로 잡으면 기본통화가 KRW 가 아닌 쇼핑몰에서 어긋난다. 기본 통화를
|
||||
* 판정할 수 없는 형태(내부 `{value, formatted}` 맵)면 마지막 수단으로 KRW 를 기준으로 둔다.
|
||||
*
|
||||
* @param unitPriceMap 옵션 단가 다통화 맵
|
||||
* @param quantity 수량
|
||||
* @param additionalUnitKrw 추가옵션 추가금 합계 (KRW 기준, 단위당). 기본 0.
|
||||
* @param additionalUnitByCurrency 통화별 추가옵션 추가금 합계 (단위당)
|
||||
* @param additionalUnitBase 기본 통화 기준 추가금 합계 (단위당, 폴백 환산용)
|
||||
*/
|
||||
function recalcMultiCurrencyTotal(
|
||||
unitPriceMap: Record<string, { value: number; formatted: string }> | undefined,
|
||||
quantity: number,
|
||||
additionalUnitKrw: number = 0
|
||||
additionalUnitByCurrency?: Record<string, { value: number }>,
|
||||
additionalUnitBase: number = 0
|
||||
): Record<string, { value: number; formatted: string }> | undefined {
|
||||
if (!unitPriceMap) return undefined;
|
||||
|
||||
// KRW 단가를 환산 기준으로 사용 (옵션의 KRW 단가 대비 타통화 비율)
|
||||
const krwUnit = (unitPriceMap.KRW as any)?.value ?? (unitPriceMap.KRW as any)?.price ?? 0;
|
||||
const readAmount = (entry: any): number => entry?.value ?? entry?.price ?? 0;
|
||||
|
||||
// 폴백 환산의 기준 통화: is_default 로 표시된 통화 → 없으면 KRW (기존 동작 보존)
|
||||
let pivotCode: string | undefined;
|
||||
for (const [code, data] of Object.entries(unitPriceMap)) {
|
||||
if ((data as any)?.is_default) {
|
||||
pivotCode = code;
|
||||
break;
|
||||
}
|
||||
}
|
||||
pivotCode = pivotCode ?? (unitPriceMap.KRW ? 'KRW' : undefined);
|
||||
const pivotUnit = pivotCode ? readAmount(unitPriceMap[pivotCode]) : 0;
|
||||
|
||||
const result: Record<string, { value: number; formatted: string }> = {};
|
||||
for (const [code, data] of Object.entries(unitPriceMap)) {
|
||||
// API는 { price, formatted } 형태, 내부는 { value, formatted } 형태 — 둘 다 지원
|
||||
const unitValue = (data as any)?.value ?? (data as any)?.price ?? 0;
|
||||
// 추가금 통화 환산: KRW 기준 추가금 × (해당통화 단가 / KRW 단가). KRW 자신은 그대로.
|
||||
let additionalConverted = 0;
|
||||
if (additionalUnitKrw > 0) {
|
||||
if (code === 'KRW') {
|
||||
additionalConverted = additionalUnitKrw;
|
||||
} else if (krwUnit > 0) {
|
||||
additionalConverted = (additionalUnitKrw * unitValue) / krwUnit;
|
||||
}
|
||||
const unitValue = readAmount(data);
|
||||
|
||||
let additionalValue: number;
|
||||
if (additionalUnitByCurrency) {
|
||||
// 서버가 통화별로 환산해 준 값이 있으면 그것이 정답이다
|
||||
additionalValue = additionalUnitByCurrency[code]?.value ?? 0;
|
||||
} else if (additionalUnitBase > 0 && code === pivotCode) {
|
||||
additionalValue = additionalUnitBase;
|
||||
} else if (additionalUnitBase > 0 && pivotUnit > 0) {
|
||||
additionalValue = (additionalUnitBase * unitValue) / pivotUnit;
|
||||
} else {
|
||||
additionalValue = 0;
|
||||
}
|
||||
const total = (unitValue + additionalConverted) * quantity;
|
||||
|
||||
const total = (unitValue + additionalValue) * quantity;
|
||||
result[code] = {
|
||||
value: total,
|
||||
formatted: formatPrice(total, code),
|
||||
@@ -279,6 +347,50 @@ function computeAdditionalOptionsTotal(
|
||||
return total;
|
||||
}
|
||||
|
||||
/**
|
||||
* 선택된 추가옵션 추가금의 **통화별** 합계를 계산합니다 (단위당).
|
||||
*
|
||||
* 서버가 선택지마다 내려주는 `multi_currency_price_adjustment` 를 통화코드별로 합산한다.
|
||||
* 그 맵이 없는 선택지(구버전 응답)는 기본통화 값만 있는 셈이므로 합산에서 제외하지 않고
|
||||
* 통화 구분 없이 더하면 통화가 섞이므로, 맵이 있는 선택지만 통화별 합계에 반영한다.
|
||||
*
|
||||
* @param selections 블럭별 추가옵션 선택 (그룹ID → 선택지ID)
|
||||
* @param groups 추가옵션 카탈로그
|
||||
* @returns 통화코드 => { value } 합계 맵 (선택 없음/맵 없음이면 undefined)
|
||||
*/
|
||||
function computeAdditionalOptionsMultiCurrencyTotal(
|
||||
selections: AdditionalOptionSelections | undefined,
|
||||
groups: AdditionalOptionGroup[] | undefined
|
||||
): Record<string, { value: number }> | undefined {
|
||||
if (!selections || !groups?.length) return undefined;
|
||||
|
||||
const totals: Record<string, number> = {};
|
||||
let found = false;
|
||||
|
||||
for (const group of groups) {
|
||||
const valueId = selections[group.id];
|
||||
if (valueId == null) continue;
|
||||
|
||||
const value = group.values?.find(v => v.id === Number(valueId));
|
||||
const map = value?.multi_currency_price_adjustment;
|
||||
if (!map) continue;
|
||||
|
||||
found = true;
|
||||
for (const [code, entry] of Object.entries(map)) {
|
||||
const amount = (entry as any)?.value ?? (entry as any)?.price ?? 0;
|
||||
totals[code] = (totals[code] ?? 0) + amount;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) return undefined;
|
||||
|
||||
const result: Record<string, { value: number }> = {};
|
||||
for (const [code, value] of Object.entries(totals)) {
|
||||
result[code] = { value };
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 추가옵션 선택을 백엔드 입력 형식으로 변환한다.
|
||||
* additional_option_selections: [{ additional_option_id, value_id }]
|
||||
@@ -486,6 +598,10 @@ export function addSelectedItemIfCompleteHandler(
|
||||
additionalOptionSelections,
|
||||
additionalOptionGroups
|
||||
);
|
||||
const additionalOptionsMultiCurrencyTotal = computeAdditionalOptionsMultiCurrencyTotal(
|
||||
additionalOptionSelections,
|
||||
additionalOptionGroups
|
||||
);
|
||||
|
||||
const blockUnitTotal = unitPrice + additionalOptionsTotal;
|
||||
|
||||
@@ -507,10 +623,12 @@ export function addSelectedItemIfCompleteHandler(
|
||||
multiCurrencyTotalPrice: recalcMultiCurrencyTotal(
|
||||
matchedOption.multi_currency_selling_price as any,
|
||||
1,
|
||||
additionalOptionsMultiCurrencyTotal,
|
||||
additionalOptionsTotal
|
||||
),
|
||||
additionalOptionSelections,
|
||||
additionalOptionsTotal,
|
||||
additionalOptionsMultiCurrencyTotal,
|
||||
};
|
||||
|
||||
const nextItems = [...items, newItem];
|
||||
@@ -553,6 +671,7 @@ export function updateSelectedItemQuantityHandler(
|
||||
multiCurrencyTotalPrice: recalcMultiCurrencyTotal(
|
||||
item?.multiCurrencyUnitPrice,
|
||||
newQuantity,
|
||||
item?.additionalOptionsMultiCurrencyTotal,
|
||||
additionalOptionsTotal
|
||||
),
|
||||
};
|
||||
@@ -616,6 +735,10 @@ export function setBlockAdditionalOptionHandler(
|
||||
}
|
||||
|
||||
const additionalOptionsTotal = computeAdditionalOptionsTotal(selections, additionalOptionGroups);
|
||||
const additionalOptionsMultiCurrencyTotal = computeAdditionalOptionsMultiCurrencyTotal(
|
||||
selections,
|
||||
additionalOptionGroups
|
||||
);
|
||||
const unitPrice = item?.unitPrice ?? 0;
|
||||
const quantity = item?.quantity ?? 1;
|
||||
const totalPrice = (unitPrice + additionalOptionsTotal) * quantity;
|
||||
@@ -625,11 +748,13 @@ export function setBlockAdditionalOptionHandler(
|
||||
additionalOptionSelections: selections,
|
||||
additionalOptionCustomTexts: customTexts,
|
||||
additionalOptionsTotal,
|
||||
additionalOptionsMultiCurrencyTotal,
|
||||
totalPrice,
|
||||
totalPriceFormatted: formatPrice(totalPrice, preferredCurrency),
|
||||
multiCurrencyTotalPrice: recalcMultiCurrencyTotal(
|
||||
item?.multiCurrencyUnitPrice,
|
||||
quantity,
|
||||
additionalOptionsMultiCurrencyTotal,
|
||||
additionalOptionsTotal
|
||||
),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user