/* portal/commerce/public/order.js — 주문 흐름(§6.2): 상품·옵션 → 적용 대상 → 가격·청구 주기 → 약관·제한 → 결제 방식 → 제출. 멱등 키로 연타·새로 고침 중복을 막는다. */ (() => { const el = document.getElementById('order-new'); const esc = aw.esc; const money = (n) => Number(n).toLocaleString('ko-KR') + '원'; const idem = sessionStorage.getItem('order-idem') || (crypto.randomUUID ? crypto.randomUUID() : String(Date.now())); sessionStorage.setItem('order-idem', idem); let data; let pv; const sel = { includeBase: true, items: [] }; async function load() { try { data = await aw.api('GET', '/api/commerce/products'); pv = data.products.find((p) => p.id === el.dataset.product) || data.products[0]; if (!pv) { aw.state(el, 'empty', '판매 중인 상품이 없습니다'); return; } if (el.dataset.option) { sel.includeBase = false; sel.items = [{ optionId: el.dataset.option, qty: 1, targetId: el.dataset.target || null }]; } render(); } catch (e) { aw.state(el, 'failed', e.message, { retry: load }); } } async function render() { let quote; try { quote = await aw.api('POST', '/api/commerce/quote', { productVersionId: pv.id, includeBase: sel.includeBase, items: sel.items }); } catch (e) { aw.state(el, 'failed', e.message, { retry: render }); return; } const q = quote.quote; const pi = quote.paymentInstructions; el.innerHTML = `

1. 상품과 옵션

${pv.options.map((o) => { const it = sel.items.find((x) => x.optionId === o.id); return ``; }).join('')}
옵션가격수량적용 대상
${esc(o.name)}${money(o.price)}${o.per === 'site' ? ' / 사이트' : ''}${o.per === 'site' ? `` : '사용자 그룹'}

2. 가격과 청구 주기

${q.items.map((i) => ``).join('')}
${esc(i.name)} × ${i.qty}${money(i.amount)} + 세금 ${money(i.tax)}
합계(세금 포함)${money(q.total)} ${q.currency}

청구 주기: ${pv.billingCycle === 'monthly' ? '매월' : '매년'} · 갱신 전 청구 예정일과 금액을 알려 드립니다

3. 약관과 제한

${pv.deferred.length ? `

! 미확정 정책 ${pv.deferred.map((d) => esc(d.key)).join(', ')} — 확정 전에는 약속으로 보지 마세요

` : ''}

4. 결제 방식

${esc(pi.bank)} ${esc(pi.account)} (예금주 ${esc(pi.holder)}) · 입금 기한 ${pi.deadline_days}일 · ${esc(pi.note || '')}

운영자가 입금을 확인하고 승인한 뒤에만 사이트 수·용량·메일함·백업 권리를 쓸 수 있습니다.

취소

`; el.querySelector('#base').onchange = (e) => { sel.includeBase = e.target.checked; render(); }; el.querySelectorAll('[data-opt]').forEach((inp) => { inp.onchange = () => { const id = inp.dataset.opt; const qty = Number(inp.value) || 0; const t = el.querySelector(`[data-target="${id}"]`); sel.items = sel.items.filter((x) => x.optionId !== id); if (qty > 0) sel.items.push({ optionId: id, qty, targetId: t ? t.value || null : null }); render(); }; }); el.querySelectorAll('[data-target]').forEach((s) => { s.onchange = () => { const it = sel.items.find((x) => x.optionId === s.dataset.target); if (it) it.targetId = s.value || null; }; }); el.querySelector('#submit').onclick = async () => { const btn = el.querySelector('#submit'); btn.disabled = true; const err = el.querySelector('#err'); err.textContent = ''; try { const r = await aw.api('POST', '/api/commerce/orders', { productVersionId: pv.id, includeBase: sel.includeBase, items: sel.items, paymentMethod: 'manual_transfer', acceptTerms: el.querySelector('#terms').checked, idempotencyKey: idem }); sessionStorage.removeItem('order-idem'); location.href = `/orders/${r.order.id}`; } catch (e) { err.textContent = e.message + (e.detail?.action === 'verify_email' ? ' — 내 계정에서 이메일 확인을 완료하세요' : ''); btn.disabled = false; } }; } load(); })();