(() => { const { EventManager, pageHelper, utils, constants } = ShopbySkin; const { withdrawClaimByOrderOptionNo, confirmOrder, cancelOrder } = pageHelper.orderDetailHelper(); const { initialize } = pageHelper.myOrdersHelper(); const searchParams = new URLSearchParams(location.search); const queryParams = Object.fromEntries(searchParams.entries()); initialize(queryParams); const openNaverPayAlertModal = (nextActionType, orderStatusType) => { const ACTION_TYPE_LABEL = { CANCEL: '취소신청', EXCHANGE: '교환신청', RETURN: '반품신청', CONFIRM_ORDER: '구매확정', }; if ( nextActionType === 'WRITE_REVIEW' && ['DELIVERY_ING', 'DELIVERY_DONE', 'BUY_CONFIRM'].includes(orderStatusType) ) { EventManager.fire('MODAL_ALERT_OPEN', { noticeType: 'WARNING', message: '네이버페이 주문은 네이버페이에서 구매확정 이후 후기작성이 가능합니다.', }); return; } EventManager.fire('MODAL_ALERT_OPEN', { noticeType: 'WARNING', message: `네이버페이지 주문은 네이버페이에서 ${ACTION_TYPE_LABEL[nextActionType]} 하실 수 있습니다.`, }); }; const openWithdrawClaimModal = (orderOptionNo, claimStatusLabel) => { EventManager.fire('MODAL_CONFIRM_OPEN', { noticeType: 'WARNING', message: `${claimStatusLabel} 신청을 철회하시겠습니까?`, onConfirm: async () => { const { isSuccess } = await withdrawClaimByOrderOptionNo(orderOptionNo); if (isSuccess) { EventManager.fire('MODAL_ALERT_OPEN', { noticeType: 'SUCCESS', message: `${claimStatusLabel}신청 철회가 완료되었습니다.`, onClose: () => location.reload(), }); } }, }); }; const validateWithdrawClaim = ({ claimNo, claimNos, claimStatusLabel, orderOptionNo }) => { const hasAfterClaim = claimNos.split(',').some((no) => Number(no) > Number(claimNo)); if (!hasAfterClaim) return true; EventManager.fire('MODAL_CONFIRM_OPEN', { noticeType: 'WARNING', message: '철회 시 후순위 클레임의 결제/환불 금액이 변동됩니다.
클레임을 철회하시겠습니까?
', onConfirm: () => openWithdrawClaimModal(orderOptionNo, claimStatusLabel), }); return false; }; // 모바일은 새 탭으로 열면 뒤로가기가 없어 주문목록으로 돌아오지 못한다(앱 웹뷰는 새 창 자체가 막힌다). // 브라우저·웹뷰 구분 없이 현재 창에서 이동시킨다. const openDeliveryTracking = (deliveryUri) => { if (!deliveryUri) { EventManager.fire('MODAL_ALERT_OPEN', { noticeType: 'WARNING', message: '배송 정보가 아직 등록되지 않았습니다.
잠시 후 다시 확인해주세요.
', }); return; } window.location.href = deliveryUri; }; // 배송중·배송완료 주문은 후기 작성이 곧 구매확정이라, 폼을 열기 전에 먼저 확인받는다. // 확인하고 들어온 폼은 등록할 때 다시 묻지 않도록 confirmedOrderReceipt 를 넘긴다. const confirmOrderReceipt = (orderStatusType, openForm) => { if (!['DELIVERY_ING', 'DELIVERY_DONE'].includes(orderStatusType)) { openForm(); return; } EventManager.fire('MODAL_CONFIRM_OPEN', { noticeType: 'SUCCESS', message: '상품을 받으셨나요?
후기 작성 시 구매확정 처리됩니다.
', onConfirm: openForm, }); }; const MODULE_ACTION_HANDLER = { CANCEL: ({ orderOptionNo, orderNo, orderOptionCount, payType, orderStatusType, orderNextActionType }) => { const canCancelAll = orderNextActionType?.split(',').includes('CANCEL_ALL'); if (payType === 'NAVER_PAY') return openNaverPayAlertModal('CANCEL'); const escrows = ['VIRTUAL_ACCOUNT', 'ESCROW_VIRTUAL_ACCOUNT', 'ESCROW_REALTIME_ACCOUNT_TRANSFER']; const payTypeLabel = { VIRTUAL_ACCOUNT: '가상계좌', ESCROW_VIRTUAL_ACCOUNT: '에스크로', ESCROW_REALTIME_ACCOUNT_TRANSFER: '에스크로', }; if (escrows.includes(payType)) { if (canCancelAll) { EventManager.fire('MODAL_CONFIRM_OPEN', { noticeType: 'WARNING', message: `${payTypeLabel[payType]} 결제 건은 전체 취소신청만 가능합니다.
모두 취소신청 하시겠습니까?
`, onConfirm: async () => { const { isSuccess } = await cancelOrder(orderNo); if (isSuccess) { EventManager.fire('MODAL_ALERT_OPEN', { noticeType: 'SUCCESS', message: '전체 주문 취소가 완료되었습니다.', onClose: () => location.reload(), }); } }, }); return; } if (orderStatusType !== 'DEPOSIT_WAIT') { return EventManager.fire('MODAL_ALERT_OPEN', { noticeType: 'WARNING', message: '에스크로 결제 건은 모든 상품이 결제완료 상태에서만 취소신청이 가능합니다.
취소신청은 1:1문의를 통해 문의해주세요.
', }); } return; } if (canCancelAll && orderOptionCount >= 2) { EventManager.fire('MODAL_CONFIRM_OPEN', { hasHeader: true, noticeType: 'WARNING', cancelLabel: '선택 부분 취소', confirmLabel: '즉시 전체 취소', message: '주문에 포함된 상품을 즉시 전체취소 하시겠습니까?
일부 상품만 부분취소 하시겠습니까?
', onCancel: () => utils.moveTo({ url: `${constants.PAGE.CLAIM_FORM}?orderOptionNo=${orderOptionNo}&claimType=CANCEL` }), onConfirm: async () => { const { isSuccess } = await cancelOrder(orderNo); if (isSuccess) { EventManager.fire('MODAL_ALERT_OPEN', { noticeType: 'SUCCESS', message: '전체 주문 취소가 완료되었습니다.', onClose: () => location.reload(), }); } }, }); } else { utils.moveTo({ url: `${constants.PAGE.CLAIM_FORM}?orderOptionNo=${orderOptionNo}&claimType=CANCEL` }); } }, EXCHANGE: ({ orderOptionNo, deliverable, payType }) => { if (payType === 'NAVER_PAY') return openNaverPayAlertModal('EXCHANGE'); if (['ESCROW_VIRTUAL_ACCOUNT', 'ESCROW_REALTIME_ACCOUNT_TRANSFER'].includes(payType)) { return EventManager.fire('MODAL_ALERT_OPEN', { noticeType: 'WARNING', message: '에스크로 결제 건은 교환 신청이 불가합니다.
취소/반품 후 재주문으로 처리해 주세요.
', }); } utils.moveTo({ url: `${constants.PAGE.CLAIM_FORM}?orderOptionNo=${orderOptionNo}&claimType=EXCHANGE&deliverable=${deliverable}` }); }, RETURN: ({ orderOptionNo, deliverable, payType }) => { if (payType === 'NAVER_PAY') return openNaverPayAlertModal('RETURN'); if (['ESCROW_VIRTUAL_ACCOUNT', 'ESCROW_REALTIME_ACCOUNT_TRANSFER'].includes(payType)) { return EventManager.fire('MODAL_ALERT_OPEN', { noticeType: 'WARNING', message: '에스크로 결제 건은 결제업체에서 발송한 이메일에서만 반품신청이 가능합니다.
이메일 수신함을 확인해주세요.
', }); } utils.moveTo({ url: `${constants.PAGE.CLAIM_FORM}?orderOptionNo=${orderOptionNo}&claimType=RETURN&deliverable=${deliverable}` }); }, WITHDRAW_CANCEL: ({ orderOptionNo, claimNo, claimNos }) => { const label = '취소'; if (!validateWithdrawClaim({ claimNo, claimNos, claimStatusLabel: label, orderOptionNo })) return; openWithdrawClaimModal(orderOptionNo, label); }, WITHDRAW_EXCHANGE: ({ orderOptionNo, claimNo, claimNos }) => { const label = '교환'; if (!validateWithdrawClaim({ claimNo, claimNos, claimStatusLabel: label, orderOptionNo })) return; openWithdrawClaimModal(orderOptionNo, label); }, WITHDRAW_RETURN: ({ orderOptionNo, claimNo, claimNos }) => { const label = '반품'; if (!validateWithdrawClaim({ claimNo, claimNos, claimStatusLabel: label, orderOptionNo })) return; openWithdrawClaimModal(orderOptionNo, label); }, VIEW_DELIVERY: ({ deliveryUri, deliveryType }) => { if (deliveryType === 'DIRECT_DELIVERY') { return EventManager.fire('MODAL_ALERT_OPEN', { noticeType: 'WARNING', message: '직접배송의 경우 배송조회를 지원하지 않습니다.
판매자에게 문의해주세요.
', }); } openDeliveryTracking(deliveryUri); }, CONFIRM_ORDER: ({ orderOptionNo, payType, orderStatusType }) => { if (payType === 'NAVER_PAY') return openNaverPayAlertModal('CONFIRM_ORDER', orderStatusType); EventManager.fire('MODAL_CONFIRM_OPEN', { noticeType: 'WARNING', message: '구매확정 하시겠습니까?
확정 후에는 교환·반품 신청이 어렵습니다.
', cancelLabel: '취소', confirmLabel: '확인', onConfirm: async () => { const { isSuccess } = await confirmOrder(orderOptionNo); if (isSuccess) { EventManager.fire('MODAL_ALERT_OPEN', { noticeType: 'SUCCESS', message: '구매확정 처리되었습니다.', onClose: () => location.reload(), }); } }, }); }, WRITE_REVIEW: ({ payType, productNo, orderStatusType }) => { if (payType === 'NAVER_PAY') return openNaverPayAlertModal('WRITE_REVIEW', orderStatusType); const openForm = () => EventManager.fire('OPEN_LAYER_MODAL', { name: 'product-review-form', title: '상품후기', isFull: true, data: { productNo, isModify: false, confirmedOrderReceipt: true }, onClose: ({ reason }) => { if (reason === 'DID_SUBMIT') { EventManager.fire('MODAL_ALERT_OPEN', { noticeType: 'success', message: '상품후기가 등록되었습니다.', onClose: () => location.reload(), }); } }, }); confirmOrderReceipt(orderStatusType, openForm); }, VIEW_CLAIM_DETAIL: ({ claimNo, modalTitlePrefix }) => { EventManager.fire('OPEN_LAYER_MODAL', { name: 'claim-detail', title: `${modalTitlePrefix ?? '클레임'} 내역 상세`, data: { claimNo }, }); }, }; const delegateClickHandler = (e) => { const target = e.target.closest('[shopby-action]'); if (!target) return; const actionTarget = target.getAttribute('shopby-action'); if (!actionTarget || !MODULE_ACTION_HANDLER[actionTarget]) return; const orderOptionCount = target.closest('[shopby-order-option-count]')?.getAttribute('shopby-order-option-count'); const orderOptionNo = target.closest('[shopby-order-option-no]')?.getAttribute('shopby-order-option-no'); const payType = target.closest('[shopby-pay-type]')?.getAttribute('shopby-pay-type'); const orderStatusType = target.closest('[shopby-order-status]')?.getAttribute('shopby-order-status'); const deliverable = target.getAttribute('shopby-deliverable'); const deliveryUri = target.getAttribute('shopby-delivery-uri'); const claimNo = target.getAttribute('shopby-claim-no'); const modalTitlePrefix = target.getAttribute('shopby-modal-title-prefix'); const productNo = target.getAttribute('shopby-product-no'); const orderNo = target.getAttribute('shopby-order-no'); const deliveryType = target.getAttribute('shopby-delivery-type'); const claimNos = target.closest('[shopby-claim-nos]')?.getAttribute('shopby-claim-nos'); const orderNextActionType = target.closest('[shopby-order-next-action-type]')?.getAttribute('shopby-order-next-action-type'); MODULE_ACTION_HANDLER[actionTarget]({ orderOptionCount, orderOptionNo, deliverable, deliveryUri, claimNo, modalTitlePrefix, productNo, orderNo, payType, orderStatusType, deliveryType, claimNos, orderNextActionType, }); }; // document 위임 하나만 사용한다. my-orders 요소에 같은 핸들러를 또 바인딩하면 // 클릭이 my-orders와 document 양쪽으로 버블링돼 OPEN_LAYER_MODAL이 두 번 발화하고, // 클레임 상세 모달이 두 번 열려 확인/X를 한 번 눌러도 닫히지 않는다. document.addEventListener('click', delegateClickHandler); // 주문 상품 개별 "장바구니 담기" — 주문 옵션(productNo/optionNo/orderCnt)을 그대로 장바구니에 추가 const addOrderOptionToCart = ({ productNo, optionNo, orderCnt }) => { const cartAction = ShopbySkin.actions.getCartAction(); return cartAction.addToCart([ { productNo: Number(productNo), optionNo: Number(optionNo), orderCnt: Number(orderCnt) || 1 }, ]); }; const orderCartAddClickListener = async ({ target }) => { const btn = target.closest('.gb-cart-add'); if (!btn || btn.disabled) { return; } const productNo = btn.getAttribute('data-product-no'); const optionNo = btn.getAttribute('data-option-no'); const orderCnt = btn.getAttribute('data-order-cnt'); if (!optionNo || Number.isNaN(Number(optionNo)) || Number(optionNo) <= 0) { location.href = `/pages/product/product-detail.html?productNo=${productNo}`; return; } btn.disabled = true; try { await addOrderOptionToCart({ productNo, optionNo, orderCnt }); EventManager.fire('MODAL_CONFIRM_OPEN', { message: '

상품이 장바구니에 담겼습니다. 바로 확인하시겠습니까?

', cancelLabel: '쇼핑계속하기', confirmLabel: '장바구니 가기', onConfirm: () => { location.href = '/pages/order/cart.html'; }, }); } catch (e) { EventManager.fire('MODAL_ALERT_OPEN', { noticeType: 'WARNING', message: '품절되었거나 담을 수 없는 상품입니다.', }); } finally { btn.disabled = false; } }; document.addEventListener('click', orderCartAddClickListener); // 주문 상품별 액션 정리 — 교환·반품을 한 버튼으로 묶고 // 노출 순서를 [교환·반품] [구매확정] [후기작성] 로 고정한다. const mergeClaimButtons = (actionBox) => { const claimButtons = ['EXCHANGE', 'RETURN'] .map((action) => actionBox.querySelector(`[shopby-action="${action}"]`)) .filter(Boolean); if (!claimButtons.length) { return; } claimButtons.forEach((btn) => btn.classList.add('gb-claim-src')); // 배송 전(결제완료·상품준비중·배송준비중)에는 서버가 EXCHANGE만 내려준다. // 아직 받지 않은 상품이라 교환이 성립하지 않으므로 취소 신청만 남기고 묶음 버튼을 만들지 않는다. if (!actionBox.querySelector('[shopby-action="RETURN"]')) { return; } const combo = document.createElement('button'); combo.type = 'button'; combo.className = 'btn btn--line-gray btn--md gb-claim-combo'; combo.textContent = '교환·반품'; actionBox.appendChild(combo); }; const fixActionOrder = (actionBox) => { ['.gb-claim-combo', '[shopby-action="CONFIRM_ORDER"]', '[shopby-action="WRITE_REVIEW"]'] .map((selector) => actionBox.querySelector(selector)) .filter(Boolean) .forEach((btn) => actionBox.appendChild(btn)); }; const arrangeOrderActions = () => { document.querySelectorAll('.next-action-btns').forEach((actionBox) => { if (actionBox.dataset.gbArranged === 'Y') { return; } mergeClaimButtons(actionBox); fixActionOrder(actionBox); actionBox.dataset.gbArranged = 'Y'; }); }; new MutationObserver(arrangeOrderActions).observe(document.body, { childList: true, subtree: true }); arrangeOrderActions(); // [교환·반품] 클릭 시 교환/반품을 고르는 하단 시트 const closeClaimSheet = () => document.querySelector('.gb-claim-sheet')?.remove(); const openClaimSheet = (comboBtn) => { const actionBox = comboBtn.closest('.next-action-btns'); const sources = ['EXCHANGE', 'RETURN'] .map((action) => actionBox.querySelector(`.gb-claim-src[shopby-action="${action}"]`)) .filter(Boolean); const sheet = document.createElement('div'); sheet.className = 'gb-claim-sheet'; sheet.innerHTML = `
`; sheet.addEventListener('click', ({ target }) => { if (target.closest('.gb-claim-sheet__dim, .gb-claim-sheet__cancel')) { closeClaimSheet(); return; } const choice = target.closest('[data-claim-action]'); if (!choice) { return; } const source = sources.find((btn) => btn.getAttribute('shopby-action') === choice.dataset.claimAction); closeClaimSheet(); source?.click(); }); document.body.appendChild(sheet); }; document.addEventListener('click', ({ target }) => { const combo = target.closest('.gb-claim-combo'); if (combo) { openClaimSheet(combo); } }); })();