/* public/assets/js/mypage_renew.js */ (function () { 'use strict'; const CFG = window.mypageRenew || {}; const URLS = (CFG.urls || {}); const $ = (sel, root = document) => root.querySelector(sel); function csrfToken() { return document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || ''; } function isMobileUA() { const ua = navigator.userAgent || ''; return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(ua); } // ----------------------------- // 1) 재인증 타이머 (항상 실행) // - view는 data-expire(unix ts), 기존은 data-until(ISO) // - 둘 다 지원하도록 수정 // ----------------------------- (function reauthCountdown() { const box = document.querySelector('.mypage-reauth--countdown'); const out = document.getElementById('reauthCountdown'); if (!box || !out) return; const untilStr = (box.getAttribute('data-until') || '').trim(); const expireTs = parseInt((box.getAttribute('data-expire') || '0').trim(), 10); const remainFallback = parseInt(box.getAttribute('data-remain') || '0', 10); function parseUntilMsFromISO(s) { if (!s) return null; const isoLike = s.includes('T') ? s : s.replace(' ', 'T'); const ms = Date.parse(isoLike); return Number.isFinite(ms) ? ms : null; } let untilMs = parseUntilMsFromISO(untilStr); // ✅ data-expire가 있으면 우선 사용 (unix seconds) if (!untilMs && Number.isFinite(expireTs) && expireTs > 0) { untilMs = expireTs * 1000; } let remain = Math.max(0, Number.isFinite(remainFallback) ? remainFallback : 0); let timer = null; let expiredOnce = false; function fmt(sec) { sec = Math.max(0, sec | 0); const m = Math.floor(sec / 60); const s = sec % 60; return String(m).padStart(2, '0') + ':' + String(s).padStart(2, '0'); } function getRemainSec() { if (untilMs !== null) { const diffMs = untilMs - Date.now(); return Math.max(0, Math.floor(diffMs / 1000)); } return Math.max(0, remain); } async function onExpiredOnce() { if (expiredOnce) return; expiredOnce = true; await showMsg( "인증 허용 시간이 만료되었습니다.\n\n보안을 위해 다시 재인증이 필요합니다.", { type: 'alert', title: '인증 만료' } ); window.location.href = URLS.gateReset || '/mypage/info'; } function tick() { const sec = getRemainSec(); out.textContent = fmt(sec); if (sec <= 0) { if (timer) clearInterval(timer); onExpiredOnce(); return; } if (untilMs === null) remain -= 1; } tick(); timer = setInterval(tick, 1000); })(); // ------------------------------------------- // 2) PASS 연락처 변경 (기존 로직 유지) // ------------------------------------------- (function phoneChange() { const btn = document.querySelector('[data-action="phone-change"]'); if (!btn) return; const readyUrl = btn.getAttribute('data-ready-url') || URLS.passReady; if (!readyUrl) return; function openIframeModal(popupName = 'danal_authtel_popup', w = 420, h = 750) { const old = document.getElementById(popupName); if (old) old.remove(); const wrap = document.createElement('div'); wrap.id = popupName; wrap.innerHTML = `
PASS 본인인증
`; document.body.appendChild(wrap); function removeModal() { const el = document.getElementById(popupName); if (el) el.remove(); } async function askCancelAndGo() { const ok = await showMsg( "인증을 중단하시겠습니까?\n\n닫으면 현재 변경 진행이 취소됩니다.", { type: "confirm", title: "인증취소", closeOnBackdrop: false, closeOnX: false, closeOnEsc: false, } ); if (!ok) return; const ifr = document.getElementById(popupName + "_iframe"); if (ifr) ifr.src = "about:blank"; removeModal(); } const closeBtn = wrap.querySelector('#' + popupName + '_close'); if (closeBtn) closeBtn.addEventListener('click', askCancelAndGo); window.closeIframe = function () { removeModal(); }; return popupName + '_iframe'; } function postToIframe(url, targetName, fieldsObj) { const temp = document.createElement('form'); temp.method = 'POST'; temp.action = url; temp.target = targetName; const csrf = document.createElement('input'); csrf.type = 'hidden'; csrf.name = '_token'; csrf.value = csrfToken(); temp.appendChild(csrf); const fields = document.createElement('input'); fields.type = 'hidden'; fields.name = 'fields'; fields.value = JSON.stringify(fieldsObj || {}); temp.appendChild(fields); const plat = document.createElement('input'); plat.type = 'hidden'; plat.name = 'platform'; plat.value = isMobileUA() ? 'mobile' : 'web'; temp.appendChild(plat); document.body.appendChild(temp); temp.submit(); temp.remove(); } window.addEventListener('message', async (ev) => { const d = ev.data || {}; if (d.type !== 'danal_result') return; if (typeof window.closeIframe === 'function') window.closeIframe(); await showMsg( d.message || (d.ok ? '본인인증이 완료되었습니다.' : '본인인증에 실패했습니다.'), { type: 'alert', title: d.ok ? '인증 완료' : '인증 실패' } ); if (d.redirect) window.location.href = d.redirect; }); btn.addEventListener('click', async () => { const ok = await showMsg( `연락처를 변경하시겠습니까? • PASS 본인인증은 가입자 본인 명의 휴대전화로만 가능합니다. • 인증 정보가 기존 회원정보와 일치하지 않으면 변경할 수 없습니다. 계속 진행할까요?`, { type: 'confirm', title: '연락처 변경' } ); if (!ok) return; btn.disabled = true; try { const res = await fetch(readyUrl, { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrfToken(), 'Accept': 'application/json', }, body: JSON.stringify({ purpose: 'mypage_phone_change' }), }); const data = await res.json().catch(() => ({})); if (!res.ok || data.ok === false) { await showMsg(data.message || '본인인증 준비에 실패했습니다.', { type: 'alert', title: '오류' }); return; } if (data.reason === 'danal_ready' && data.popup && data.popup.url) { const targetName = openIframeModal('danal_authtel_popup', 420, 750); postToIframe(data.popup.url, targetName, data.popup.fields || {}); return; } await showMsg('ready 응답이 올바르지 않습니다. 서버 응답 형식을 확인해 주세요.', { type: 'alert', title: '오류' }); } catch (e) { await showMsg('네트워크 오류가 발생했습니다. 잠시 후 다시 시도해 주세요.', { type: 'alert', title: '오류' }); } finally { btn.disabled = false; } }); })(); // ------------------------------------------- // 공통: 모달 스타일/유틸 // ------------------------------------------- function ensureCommonModalStyle(styleId, cssText) { if (document.getElementById(styleId)) return; const st = document.createElement('style'); st.id = styleId; st.textContent = cssText; document.head.appendChild(st); } function makeErrorSetter(wrap, errSel) { const errBox = $(errSel, wrap); return function setError(message) { if (!errBox) return; const msg = (message || '').trim(); if (msg === '') { errBox.style.display = 'none'; errBox.textContent = ''; return; } errBox.textContent = msg; errBox.style.display = 'block'; }; } // ------------------------------------------- // 3) 비밀번호 변경 레이어 팝업 (기존 유지 + 내부 에러) // ------------------------------------------- (function passwordChange() { const trigger = document.querySelector('[data-action="pw-change"]'); if (!trigger) return; const postUrl = URLS.passwordUpdate; if (!postUrl) { trigger.addEventListener('click', async () => { await showMsg('비밀번호 변경 URL이 설정되지 않았습니다.', { type: 'alert', title: '오류' }); }); return; } ensureCommonModalStyle('mypagePwModalStyle', ` .mypage-pwmodal{position:fixed;inset:0;z-index:220000;display:flex;align-items:center;justify-content:center} .mypage-pwmodal__dim{position:absolute;inset:0;background:#000;opacity:.55} .mypage-pwmodal__box{position:relative;width:min(420px,calc(100% - 28px));background:#fff;border-radius:14px;overflow:hidden;box-shadow:0 18px 60px rgba(0,0,0,.35)} .mypage-pwmodal__hd{display:flex;align-items:center;justify-content:space-between;padding:12px 14px;background:rgba(0,0,0,.04);border-bottom:1px solid rgba(0,0,0,.08)} .mypage-pwmodal__ttl{font-weight:900;font-size:14px;color:#111} .mypage-pwmodal__close{width:34px;height:34px;border-radius:10px;border:1px solid rgba(0,0,0,.12);background:#fff;cursor:pointer;font-size:18px;line-height:1;color:#111} .mypage-pwmodal__bd{padding:14px} .mypage-pwmodal__row{margin-top:10px} .mypage-pwmodal__label{display:block;font-size:12px;font-weight:800;color:#667085;margin-bottom:6px} .mypage-pwmodal__inp{width:100%;height:42px;border-radius:12px;border:1px solid #e5e7eb;padding:0 12px;font-size:14px;outline:none} .mypage-pwmodal__hint{margin-top:10px;font-size:12px;color:#667085;line-height:1.4} .mypage-pwmodal__error{margin-top:12px;padding:10px 12px;border-radius:12px;background:rgba(220,38,38,.08);border:1px solid rgba(220,38,38,.25);color:#b91c1c;font-weight:800;font-size:12px;display:none;white-space:pre-line} .mypage-pwmodal__ft{display:flex;gap:10px;padding:12px 14px;border-top:1px solid rgba(0,0,0,.08);background:#fff} .mypage-pwmodal__btn{flex:1;height:42px;border-radius:12px;border:1px solid rgba(0,0,0,.12);background:#fff;font-weight:900;cursor:pointer} .mypage-pwmodal__btn--primary{border:none;background:#111;color:#fff} .mypage-pwmodal__btn[disabled]{opacity:.6;cursor:not-allowed} `); function validateNewPw(pw) { if (!pw) return '비밀번호를 입력해 주세요.'; if (pw.length < 8) return '비밀번호는 8자리 이상이어야 합니다.'; if (pw.length > 20) return '비밀번호는 20자리를 초과할 수 없습니다.'; const re = /^(?=.*[A-Za-z])(?=.*\d)(?=.*[^A-Za-z0-9]).{8,20}$/; if (!re.test(pw)) return '비밀번호는 영문+숫자+특수문자를 포함해야 합니다.'; return ''; } function openModal() { const old = $('#mypagePwModal'); if (old) old.remove(); const wrap = document.createElement('div'); wrap.className = 'mypage-pwmodal'; wrap.id = 'mypagePwModal'; wrap.innerHTML = `
`; document.body.appendChild(wrap); const setError = makeErrorSetter(wrap, '#pw_error'); function close() { wrap.remove(); } // ✅ 닫기: X / 취소만 wrap.querySelector('.mypage-pwmodal__close')?.addEventListener('click', close); wrap.querySelector('[data-act="cancel"]')?.addEventListener('click', close); setTimeout(() => $('#pw_current', wrap)?.focus(), 10); async function submit() { setError(''); const cur = ($('#pw_current', wrap)?.value || '').trim(); const pw1 = ($('#pw_new', wrap)?.value || '').trim(); const pw2 = ($('#pw_new2', wrap)?.value || '').trim(); if (!cur) { setError('현재 비밀번호를 입력해 주세요.'); $('#pw_current', wrap)?.focus(); return; } const err = validateNewPw(pw1); if (err) { setError(err); $('#pw_new', wrap)?.focus(); return; } if (pw1 !== pw2) { setError('변경할 비밀번호 확인이 일치하지 않습니다.'); $('#pw_new2', wrap)?.focus(); return; } const ok = await showMsg('비밀번호를 변경하시겠습니까?', { type: 'confirm', title: '비밀번호 변경' }); if (!ok) return; const btn = wrap.querySelector('[data-act="submit"]'); if (btn) btn.disabled = true; try { const res = await fetch(postUrl, { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrfToken(), 'Accept': 'application/json', }, body: JSON.stringify({ current_password: cur, password: pw1, password_confirmation: pw2, }), }); const data = await res.json().catch(() => ({})); if (res.status === 401 && data.redirect) { await showMsg(data.message || '인증이 필요합니다.', { type: 'alert', title: '인증 필요' }); window.location.href = data.redirect; return; } if (!res.ok || data.ok === false) { const msg = (data && data.message) || (data && data.errors && (data.errors.password?.[0] || data.errors.current_password?.[0])) || '비밀번호 변경에 실패했습니다.'; setError(msg); return; } await showMsg(data.message || '비밀번호가 변경되었습니다.', { type: 'alert', title: '완료' }); close(); } catch (e) { setError('네트워크 오류가 발생했습니다. 잠시 후 다시 시도해 주세요.'); } finally { if (btn) btn.disabled = false; } } wrap.querySelector('[data-act="submit"]')?.addEventListener('click', submit); wrap.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); submit(); } }); } trigger.addEventListener('click', openModal); })(); // ------------------------------------------- // 4) 2차 비밀번호 변경 레이어 팝업 (신규) // - 로그인 비밀번호 + 현재 2차비번 둘 다 검증 후 변경 // - 에러는 내부 붉은 박스 // - 닫힘: 취소 / X만 // ------------------------------------------- (function pin2Change() { const trigger = document.querySelector('[data-action="pw2-change"]'); if (!trigger) return; const postUrl = URLS.pin2Update; // Blade에서 주입 필요 if (!postUrl) { trigger.addEventListener('click', async () => { await showMsg('2차 비밀번호 변경 URL이 설정되지 않았습니다.', { type: 'alert', title: '오류' }); }); return; } ensureCommonModalStyle('mypagePin2ModalStyle', ` .mypage-pin2modal{position:fixed;inset:0;z-index:220000;display:flex;align-items:center;justify-content:center} .mypage-pin2modal__dim{position:absolute;inset:0;background:#000;opacity:.55} .mypage-pin2modal__box{position:relative;width:min(420px,calc(100% - 28px));background:#fff;border-radius:14px;overflow:hidden;box-shadow:0 18px 60px rgba(0,0,0,.35)} .mypage-pin2modal__hd{display:flex;align-items:center;justify-content:space-between;padding:12px 14px;background:rgba(0,0,0,.04);border-bottom:1px solid rgba(0,0,0,.08)} .mypage-pin2modal__ttl{font-weight:900;font-size:14px;color:#111} .mypage-pin2modal__close{width:34px;height:34px;border-radius:10px;border:1px solid rgba(0,0,0,.12);background:#fff;cursor:pointer;font-size:18px;line-height:1;color:#111} .mypage-pin2modal__bd{padding:14px} .mypage-pin2modal__row{margin-top:10px} .mypage-pin2modal__label{display:block;font-size:12px;font-weight:800;color:#667085;margin-bottom:6px} .mypage-pin2modal__inp{width:100%;height:42px;border-radius:12px;border:1px solid #e5e7eb;padding:0 12px;font-size:14px;outline:none} .mypage-pin2modal__inp--pin{letter-spacing:6px;text-align:center;font-weight:900} .mypage-pin2modal__hint{margin-top:10px;font-size:12px;color:#667085;line-height:1.4} .mypage-pin2modal__error{margin-top:12px;padding:10px 12px;border-radius:12px;background:rgba(220,38,38,.08);border:1px solid rgba(220,38,38,.25);color:#b91c1c;font-weight:800;font-size:12px;display:none;white-space:pre-line} .mypage-pin2modal__ft{display:flex;gap:10px;padding:12px 14px;border-top:1px solid rgba(0,0,0,.08);background:#fff} .mypage-pin2modal__btn{flex:1;height:42px;border-radius:12px;border:1px solid rgba(0,0,0,.12);background:#fff;font-weight:900;cursor:pointer} .mypage-pin2modal__btn--primary{border:none;background:#111;color:#fff} .mypage-pin2modal__btn[disabled]{opacity:.6;cursor:not-allowed} `); function isPin4(v) { return /^\d{4}$/.test(v || ''); } function openModal() { const old = $('#mypagePin2Modal'); if (old) old.remove(); const wrap = document.createElement('div'); wrap.className = 'mypage-pin2modal'; wrap.id = 'mypagePin2Modal'; wrap.innerHTML = `
`; document.body.appendChild(wrap); const setError = makeErrorSetter(wrap, '#pin2_error'); function close() { wrap.remove(); } // ✅ 닫기: X / 취소만 wrap.querySelector('.mypage-pin2modal__close')?.addEventListener('click', close); wrap.querySelector('[data-act="cancel"]')?.addEventListener('click', close); setTimeout(() => $('#pin2_current_password', wrap)?.focus(), 10); // 숫자만 입력 보정(편의) — 필요 없으면 빼도 됨 function onlyDigitsMax4(el) { if (!el) return; el.addEventListener('input', () => { el.value = (el.value || '').replace(/[^\d]/g, '').slice(0, 4); }); } onlyDigitsMax4($('#pin2_current', wrap)); onlyDigitsMax4($('#pin2_new', wrap)); onlyDigitsMax4($('#pin2_new2', wrap)); async function submit() { setError(''); const curPw = ($('#pin2_current_password', wrap)?.value || '').trim(); const curPin2 = ($('#pin2_current', wrap)?.value || '').trim(); const pin2 = ($('#pin2_new', wrap)?.value || '').trim(); const pin2c = ($('#pin2_new2', wrap)?.value || '').trim(); if (!curPw) { setError('이전 비밀번호를 입력해 주세요.'); $('#pin2_current_password', wrap)?.focus(); return; } if (!isPin4(curPin2)) { setError('이전 2차 비밀번호는 숫자 4자리여야 합니다.'); $('#pin2_current', wrap)?.focus(); return; } if (!isPin4(pin2)) { setError('2차 비밀번호는 숫자 4자리여야 합니다.'); $('#pin2_new', wrap)?.focus(); return; } if (pin2 !== pin2c) { setError('2차 비밀번호 확인이 일치하지 않습니다.'); $('#pin2_new2', wrap)?.focus(); return; } const ok = await showMsg('2차 비밀번호를 변경하시겠습니까?', { type: 'confirm', title: '2차 비밀번호 변경' }); if (!ok) return; const btn = wrap.querySelector('[data-act="submit"]'); if (btn) btn.disabled = true; try { const res = await fetch(postUrl, { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrfToken(), 'Accept': 'application/json', }, body: JSON.stringify({ current_password: curPw, current_pin2: curPin2, pin2: pin2, pin2_confirmation: pin2c, }), }); const data = await res.json().catch(() => ({})); if (res.status === 401 && data.redirect) { await showMsg(data.message || '인증이 필요합니다.', { type: 'alert', title: '인증 필요' }); window.location.href = data.redirect; return; } if (!res.ok || data.ok === false) { // 422 validate or mismatch const msg = (data && data.message) || (data && data.errors && ( data.errors.current_password?.[0] || data.errors.current_pin2?.[0] || data.errors.pin2?.[0] || data.errors.pin2_confirmation?.[0] )) || '2차 비밀번호 변경에 실패했습니다.'; setError(msg); return; } await showMsg(data.message || '2차 비밀번호가 변경되었습니다.', { type: 'alert', title: '완료' }); close(); } catch (e) { setError('네트워크 오류가 발생했습니다. 잠시 후 다시 시도해 주세요.'); } finally { if (btn) btn.disabled = false; } } wrap.querySelector('[data-act="submit"]')?.addEventListener('click', submit); wrap.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); submit(); } }); } trigger.addEventListener('click', openModal); })(); // ------------------------------------------- // X) 출금계좌 등록/수정 (Dozn 성명인증 + 저장) // - 입력: 2차 비밀번호(4자리), 금융권, 은행, 계좌번호(숫자만), 예금주 // - showMsg confirm/alert 사용 // - 에러는 모달 내부 붉은 글씨 // - 닫기: X / 취소만 (dim 클릭/ESC 닫기 없음) // ------------------------------------------- (function withdrawAccount() { const trigger = document.querySelector('[data-action="withdraw-account"]'); if (!trigger) return; const postUrl = URLS.withdrawVerifyOut; const defaultDepositor = (CFG.memberName || '').trim(); // ✅ bankGroups는 config(bank_code.php)의 groups 구조(label/items)로 전달된다고 가정 const BANK_GROUPS = (CFG.bankGroups || {}); if (!postUrl) { trigger.addEventListener('click', async () => { await showMsg('출금계좌 인증 URL이 설정되지 않았습니다.', { type: 'alert', title: '오류' }); }); return; } ensureCommonModalStyle('mypageWithdrawModalStyle', ` .mypage-withmodal{position:fixed;inset:0;z-index:220000;display:flex;align-items:center;justify-content:center} .mypage-withmodal__dim{position:absolute;inset:0;background:#000;opacity:.55} .mypage-withmodal__box{position:relative;width:min(460px,calc(100% - 28px));background:#fff;border-radius:14px;overflow:hidden;box-shadow:0 18px 60px rgba(0,0,0,.35)} .mypage-withmodal__hd{display:flex;align-items:center;justify-content:space-between;padding:12px 14px;background:rgba(0,0,0,.04);border-bottom:1px solid rgba(0,0,0,.08)} .mypage-withmodal__ttl{font-weight:900;font-size:14px;color:#111} .mypage-withmodal__close{width:34px;height:34px;border-radius:10px;border:1px solid rgba(0,0,0,.12);background:#fff;cursor:pointer;font-size:18px;line-height:1;color:#111} .mypage-withmodal__bd{padding:14px} .mypage-withmodal__row{margin-top:10px} .mypage-withmodal__label{display:block;font-size:12px;font-weight:800;color:#667085;margin-bottom:6px} .mypage-withmodal__inp{width:100%;height:42px;border-radius:12px;border:1px solid #e5e7eb;padding:0 12px;font-size:14px;outline:none} .mypage-withmodal__inp--pin{letter-spacing:6px;text-align:center;font-weight:900} .mypage-withmodal__select{width:100%;height:42px;border-radius:12px;border:1px solid #e5e7eb;padding:0 10px;font-size:14px;outline:none;background:#fff} .mypage-withmodal__hint{margin-top:10px;font-size:12px;color:#667085;line-height:1.4} .mypage-withmodal__error{margin-top:12px;padding:10px 12px;border-radius:12px;background:rgba(220,38,38,.08);border:1px solid rgba(220,38,38,.25);color:#b91c1c;font-weight:800;font-size:12px;display:none;white-space:pre-line} .mypage-withmodal__ft{display:flex;gap:10px;padding:12px 14px;border-top:1px solid rgba(0,0,0,.08);background:#fff} .mypage-withmodal__btn{flex:1;height:42px;border-radius:12px;border:1px solid rgba(0,0,0,.12);background:#fff;font-weight:900;cursor:pointer} .mypage-withmodal__btn--primary{border:none;background:#111;color:#fff} .mypage-withmodal__btn[disabled]{opacity:.6;cursor:not-allowed} `); function isPin4(v){ return /^\d{4}$/.test(v || ''); } function isDigits(v){ return /^\d+$/.test(v || ''); } function isBankCode3(v){ return /^\d{3}$/.test(v || ''); } function escapeHtml(s) { return String(s ?? '').replace(/[&<>"']/g, m => ({ '&':'&','<':'<','>':'>','"':'"',"'":''' }[m])); } function maskAccount(v) { const s = String(v || ''); if (s.length <= 4) return s; return '****' + s.slice(-4); } function groupOptionsHtml() { const keys = Object.keys(BANK_GROUPS || {}); const opts = ['']; for (const k of keys) { const label = BANK_GROUPS[k]?.label || k; opts.push(``); } return opts.join(''); } function bankOptionsByGroupHtml(groupKey) { const group = BANK_GROUPS[groupKey]; const items = group?.items || {}; const codes = Object.keys(items); if (!groupKey || !group || codes.length === 0) { return ''; } // 코드 오름차순 codes.sort(); const opts = ['']; for (const code of codes) { const name = items[code]; opts.push(``); } return opts.join(''); } function getBankName(groupKey, bankCode) { return (BANK_GROUPS[groupKey]?.items && BANK_GROUPS[groupKey].items[bankCode]) ? BANK_GROUPS[groupKey].items[bankCode] : ''; } function openModal() { const old = document.getElementById('mypageWithdrawModal'); if (old) old.remove(); const isEdit = trigger.getAttribute('aria-label')?.includes('수정'); const wrap = document.createElement('div'); wrap.className = 'mypage-withmodal'; wrap.id = 'mypageWithdrawModal'; wrap.innerHTML = `
`; document.body.appendChild(wrap); const setError = makeErrorSetter(wrap, '#with_error'); const close = () => wrap.remove(); // ✅ 닫기: X / 취소만 wrap.querySelector('.mypage-withmodal__close')?.addEventListener('click', close); wrap.querySelector('[data-act="cancel"]')?.addEventListener('click', close); const pinEl = document.getElementById('with_pin2'); const groupEl = document.getElementById('with_group'); const bankEl = document.getElementById('with_bank'); const accEl = document.getElementById('with_account'); const depEl = document.getElementById('with_depositor'); // 숫자만 입력 보정 if (pinEl) pinEl.addEventListener('input', () => { pinEl.value = (pinEl.value || '').replace(/[^\d]/g, '').slice(0, 4); }); if (accEl) accEl.addEventListener('input', () => { accEl.value = (accEl.value || '').replace(/[^\d]/g, ''); }); // ✅ 금융권 선택 → 은행 목록 갱신 groupEl?.addEventListener('change', () => { const g = (groupEl.value || '').trim(); bankEl.innerHTML = bankOptionsByGroupHtml(g); bankEl.value = ''; }); setTimeout(() => pinEl?.focus(), 10); async function submit() { setError(''); const pin2 = (pinEl?.value || '').trim(); const groupKey = (groupEl?.value || '').trim(); const bankCode = (bankEl?.value || '').trim(); const account = (accEl?.value || '').trim(); const depositor = (depEl?.value || '').trim(); if (!isPin4(pin2)) { setError('2차 비밀번호는 숫자 4자리여야 합니다.'); pinEl?.focus(); return; } if (!groupKey || !BANK_GROUPS[groupKey]) { setError('금융권을 선택해 주세요.'); groupEl?.focus(); return; } if (!isBankCode3(bankCode)) { setError('은행을 선택해 주세요.'); bankEl?.focus(); return; } // ✅ 선택한 금융권 안에 실제로 존재하는 은행인지 (클라 방어) const bankName = getBankName(groupKey, bankCode); if (!bankName) { setError('선택한 은행 정보가 올바르지 않습니다. 다시 선택해 주세요.'); bankEl?.focus(); return; } if (!account || !isDigits(account)) { setError('계좌번호는 숫자만 입력해 주세요.'); accEl?.focus(); return; } if (!depositor) { setError('예금주(성명)를 입력해 주세요.'); depEl?.focus(); return; } const ok = await showMsg( `출금계좌를 인증 후 저장하시겠습니까?\n\n금융권: ${BANK_GROUPS[groupKey]?.label || groupKey}\n은행: ${bankName} (${bankCode})\n계좌: ${maskAccount(account)}\n예금주: ${depositor}`, { type: 'confirm', title: '출금계좌 인증/저장' } ); if (!ok) return; const btn = wrap.querySelector('[data-act="submit"]'); if (btn) btn.disabled = true; try { const res = await fetch(postUrl, { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrfToken(), 'Accept': 'application/json', }, body: JSON.stringify({ pin2: pin2, bank_code: bankCode, account: account, depositor: depositor, // groupKey는 서버 필수는 아니지만, 디버깅/검증 강화용으로 보내도 됨 // bank_group: groupKey, }), }); const data = await res.json().catch(() => ({})); if (res.status === 401 && data.redirect) { await showMsg(data.message || '인증이 필요합니다.', { type: 'alert', title: '인증 필요' }); window.location.href = data.redirect; return; } if (!res.ok || data.ok === false) { const msg = (data && data.message) || (data && data.errors && ( data.errors.pin2?.[0] || data.errors.bank_code?.[0] || data.errors.account?.[0] || data.errors.depositor?.[0] )) || '계좌 인증/저장에 실패했습니다.'; setError(msg); return; } await showMsg(data.message || '인증완료 및 계좌번호가 등록되었습니다.', { type: 'alert', title: '완료' }); close(); window.location.reload(); } catch (e) { setError('네트워크 오류가 발생했습니다. 잠시 후 다시 시도해 주세요.'); } finally { if (btn) btn.disabled = false; } } wrap.querySelector('[data-act="submit"]')?.addEventListener('click', submit); wrap.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); submit(); } }); } trigger.addEventListener('click', openModal); })(); // ------------------------------------------- // 5) 기타 버튼들(준비중) // ------------------------------------------- (function others() { $('[data-action="consent-edit"]')?.addEventListener('click', async () => { await showMsg('준비중입니다.', { type: 'alert', title: '수신 동의' }); }); $('[data-action="withdraw-member"]')?.addEventListener('click', async () => { const ok = await showMsg( `회원탈퇴를 진행하시겠습니까? • 탈퇴 시 계정 복구가 어려울 수 있습니다. • 진행 전 보유 내역/정산/환불 정책을 확인해 주세요.`, { type: 'confirm', title: '회원탈퇴' } ); if (!ok) return; await showMsg('준비중입니다.', { type: 'alert', title: '회원탈퇴' }); }); })(); })();