회원탈퇴 완료

This commit is contained in:
sungro815 2026-02-02 18:35:56 +09:00
parent 97f0e70f4d
commit 2905c22f26
6 changed files with 362 additions and 23 deletions

View File

@ -667,6 +667,33 @@ class MemberAuthRepository
}
}
//회원 탈퇴 1차 비밀번호 검증
public function verifyLegacyPassword(int $memNo, string $pwPlain): bool
{
$pwPlain = (string) $pwPlain;
if ($memNo <= 0 || $pwPlain === '') {
return false;
}
// ✅ 1차 비번은 mem_st_ring.str_0
$stored = (string) (DB::table('mem_st_ring')
->where('mem_no', $memNo)
->value('str_0') ?? '');
if ($stored === '') {
return false;
}
// ✅ CI 방식(PASS_SET=0) - 기존 attemptLegacyLogin과 동일
$try = (string) CiPassword::make($pwPlain, 0);
if ($try === '') {
return false;
}
return hash_equals($stored, $try);
}
// 2차 비밀번호 검증
public function verifyPin2(int $memNo, string $pin2Plain): bool
{

View File

@ -572,7 +572,7 @@ class MemInfoService
// ✅ 최근 7일 이내 구매내역(stat_pay p/t) 있으면 불가
$from = Carbon::today()->subDays(7)->startOfDay();
$cnt = (int) DB::table('giftcard_order') // ⚠️ 테이블명이 다르면 여기만 바꾸면 됨
$cnt = (int) DB::table('pin_order')
->where('mem_no', $memNo)
->whereIn('stat_pay', ['p','t'])
->where('dt_stat_pay', '>=', $from->toDateTimeString())
@ -607,13 +607,12 @@ class MemInfoService
'name_first' => '',
'name_mid' => '',
'name_last' => '',
'birth' => '',
'birth' => '1900-01-01',
'gender' => '',
'native' => '',
'cell_corp' => '',
'cell_phone' => '',
'ci' => '',
'bank_code' => '',
'bank_name' => '',
'bank_act_num' => '',
@ -622,7 +621,6 @@ class MemInfoService
'dt_req_out' => $dtReqOut,
'dt_out' => $now->toDateTimeString(),
'stat_3' => '4',
'dt_mod' => $now->toDateTimeString(),
]);
@ -630,8 +628,6 @@ class MemInfoService
DB::table('mem_account')
->where('mem_no', $memNo)
->update([
'act_type' => '',
'act_state' => '',
'bank_code' => '',
'bank_name' => '',
'bank_act_name' => '',
@ -642,7 +638,6 @@ class MemInfoService
DB::table('mem_address')
->where('mem_no', $memNo)
->update([
'gubun' => '',
'shipping' => '',
'zipNo' => '',
'roadAddrPart1' => '',

View File

@ -161,3 +161,184 @@
font-size:13px;
}
}
/* =========================================
Withdraw Dialog (mypage style)
========================================= */
/* dim */
.ui-dialog.ui-dialog--mypage .ui-dialog__backdrop{
background: rgba(16,24,40,.45);
backdrop-filter: blur(2px);
}
/* panel */
.ui-dialog__panel--mypage{
width: min(640px, calc(100vw - 32px));
border-radius: 18px;
background: #ffffff;
border: 1px solid #e5e7eb;
box-shadow: 0 18px 50px rgba(16,24,40,.18);
overflow: hidden;
}
/* head */
.ui-dialog__head--mypage{
display:flex;
align-items:center;
justify-content:space-between;
padding: 16px 18px;
background: #ffffff;
border-bottom: 1px solid rgba(0,0,0,.06);
}
.ui-dialog__title--mypage{
font-size: 15px;
font-weight: 900;
color: #101828;
letter-spacing: .2px;
}
.ui-dialog__close--mypage{
width: 34px;
height: 34px;
border-radius: 12px;
border: 1px solid rgba(0,0,0,.06);
background: #f7f8fb;
color: #101828;
font-weight: 900;
cursor: pointer;
}
/* body */
.ui-dialog__body--mypage{
padding: 16px 18px 18px;
background: #ffffff;
}
/* card blocks */
.wd-card{
border-radius: 16px;
border: 1px solid rgba(0,0,0,.06);
background: #f7f8fb;
padding: 14px;
}
.wd-card + .wd-card{ margin-top: 12px; }
.wd-card--form{
background: #ffffff;
}
/* guide list */
.wd-guide{
margin: 0;
padding-left: 18px;
color: #475467;
font-size: 13px;
line-height: 1.65;
}
.wd-guide li + li{ margin-top: 6px; }
/* agree */
.wd-agree{
display:flex;
align-items:center;
gap:10px;
margin-top: 12px;
font-weight: 800;
color: #101828;
}
.wd-agree input{
width: 16px;
height: 16px;
accent-color: #2563eb;
}
/* fields */
.wd-field + .wd-field{ margin-top: 12px; }
.wd-label{
display:block;
font-size: 12px;
font-weight: 800;
color: #667085;
margin-bottom: 6px;
}
.wd-label small{
font-weight: 800;
color: #98a2b3;
}
.wd-input{
width: 100%;
height: 44px;
border-radius: 14px;
border: 1px solid rgba(0,0,0,.10);
background: #ffffff;
padding: 0 12px;
font-size: 14px;
font-weight: 800;
color: #101828;
outline: none;
}
.wd-input:focus{
border-color: rgba(37,99,235,.55);
box-shadow: 0 0 0 4px rgba(37,99,235,.12);
}
/* error msg */
.wd-msg{
margin-top: 12px;
padding: 10px 12px;
border-radius: 14px;
background: #fff1f2;
border: 1px solid rgba(225,29,72,.18);
color: #9f1239;
font-weight: 900;
font-size: 13px;
}
/* footer buttons */
.ui-dialog__foot--mypage{
display:flex;
gap:10px;
padding: 14px 18px 18px;
background: #ffffff;
border-top: 1px solid rgba(0,0,0,.06);
}
.wd-btn{
flex: 1;
height: 44px;
border-radius: 14px;
font-weight: 900;
font-size: 14px;
cursor: pointer;
}
.wd-btn--ghost{
background: #ffffff;
border: 1px solid rgba(0,0,0,.10);
color: #101828;
}
.wd-btn--danger{
background: #f97316; /* 사이트 메인 오렌지 쓰면 여기 바꿔도 됨 */
border: 1px solid rgba(0,0,0,.06);
color: #ffffff;
}
.wd-btn:disabled{
opacity: .6;
cursor: not-allowed;
}
/* mobile */
@media (max-width: 480px){
.ui-dialog__panel--mypage{ border-radius: 16px; }
.ui-dialog__body--mypage{ padding: 14px; }
.ui-dialog__foot--mypage{ padding: 12px 14px 14px; }
}

View File

@ -1202,26 +1202,104 @@
})();
(function () {
const dlg = document.getElementById('withdrawDialog');
if (!dlg) return;
const openBtn = document.getElementById('btnOpenWithdraw'); // 아래에서 버튼 추가할 거임
const closeEls = dlg.querySelectorAll('[data-close="1"]');
const submitBtn = document.getElementById('withdrawSubmitBtn');
const agreeEl = document.getElementById('withdrawAgree');
const pwEl = document.getElementById('withdrawPw');
const pin2El = document.getElementById('withdrawPin2');
const msgEl = document.getElementById('withdrawMsg');
function openDialog() {
dlg.classList.add('is-open');
dlg.setAttribute('aria-hidden', 'false');
msgEl.style.display = 'none';
msgEl.textContent = '';
// reset
agreeEl.checked = false;
pwEl.value = '';
pin2El.value = '';
setTimeout(() => pwEl.focus(), 50);
}
function closeDialog() {
dlg.classList.remove('is-open');
dlg.setAttribute('aria-hidden', 'true');
}
function showError(text) {
msgEl.style.display = 'block';
msgEl.textContent = text;
}
closeEls.forEach(el => el.addEventListener('click', closeDialog));
if (openBtn) openBtn.addEventListener('click', openDialog);
// 숫자만
pin2El.addEventListener('input', function () {
this.value = (this.value || '').replace(/[^\d]/g, '').slice(0, 4);
});
submitBtn.addEventListener('click', async function () {
if (!agreeEl.checked) return showError('안내사항 동의가 필요합니다.');
const password = (pwEl.value || '').trim();
const pin2 = (pin2El.value || '').trim();
if (!password) return showError('비밀번호를 입력해 주세요.');
if (!/^\d{4}$/.test(pin2)) return showError('2차 비밀번호는 숫자 4자리여야 합니다.');
submitBtn.disabled = true;
try {
const res = await fetch(dlg.dataset.action, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest',
'X-CSRF-TOKEN': dlg.dataset.csrf,
},
body: JSON.stringify({
agree: '1',
password,
pin2
})
});
const json = await res.json().catch(() => ({}));
if (!res.ok || !json.ok) {
showError(json.message || '회원탈퇴에 실패했습니다.');
submitBtn.disabled = false;
return;
}
// 성공
if (typeof showMsg === 'function') {
await showMsg(json.message || '회원탈퇴가 완료되었습니다.', { type: 'alert', title: '완료' });
} else {
alert(json.message || '회원탈퇴가 완료되었습니다.');
}
window.location.href = json.redirect || '/';
} catch (e) {
showError('처리 중 오류가 발생했습니다. 잠시 후 다시 시도해 주세요.');
submitBtn.disabled = false;
}
});
})();
// -------------------------------------------
// 5) 기타 버튼들(준비중)
// -------------------------------------------
(function others() {
$('[data-action="withdraw-member"]')?.addEventListener('click', async () => {
const ok = await showMsg(
`회원탈퇴를 진행하시겠습니까?
탈퇴 계정 복구가 어려울 있습니다.
진행 보유 내역/정산/환불 정책을 확인해 주세요.`,
{ type: 'confirm', title: '회원탈퇴' }
);
if (!ok) return;
await showMsg('준비중입니다.', { type: 'alert', title: '회원탈퇴' });
});
})();
})();

View File

@ -0,0 +1,48 @@
<div id="withdrawDialog" class="ui-dialog ui-dialog--mypage" aria-hidden="true">
<div class="ui-dialog__backdrop" data-close="1"></div>
<div class="ui-dialog__panel ui-dialog__panel--mypage" role="dialog" aria-modal="true" aria-labelledby="withdrawTitle">
<div class="ui-dialog__head ui-dialog__head--mypage">
<div class="ui-dialog__title ui-dialog__title--mypage" id="withdrawTitle">회원탈퇴안내</div>
<button type="button" class="ui-dialog__close ui-dialog__close--mypage" data-close="1" aria-label="닫기">
</button>
</div>
<div class="ui-dialog__body ui-dialog__body--mypage">
<div class="wd-card wd-card--guide">
<ul class="wd-guide">
<li>회원 탈퇴 회원님께서 보유하셨던 포인트, 구매상품권, 쿠폰은 모두 삭제 됩니다.</li>
<li>회원 탈퇴 재가입시에는 신규 회원으로 가입이 처리되며, 탈퇴 전의 회원정보, 구매정보, 포인트정보 모든 정보는 복구되지 않습니다.</li>
<li>회원님이 사용하신 아이디(이메일) 탈퇴완료 90(3개월) 이후 재가입이 가능합니다.</li>
<li>회원탈퇴 모든정보는 즉시 삭제됩니다.</li>
</ul>
<label class="wd-agree">
<input type="checkbox" id="withdrawAgree" value="1">
<span> 내용에 동의 합니다.</span>
</label>
</div>
<div class="wd-card wd-card--form">
<div class="wd-field">
<label class="wd-label" for="withdrawPw">비밀번호</label>
<input type="password" id="withdrawPw" class="wd-input" autocomplete="current-password" placeholder="비밀번호를 입력해 주세요" />
</div>
<div class="wd-field">
<label class="wd-label" for="withdrawPin2">2 비밀번호 <small>(숫자 4자리)</small></label>
<input type="password" id="withdrawPin2" class="wd-input" inputmode="numeric" maxlength="4" placeholder="숫자 4자리" />
</div>
<div class="wd-msg" id="withdrawMsg" style="display:none;"></div>
</div>
</div>
<div class="ui-dialog__foot ui-dialog__foot--mypage">
<button type="button" class="wd-btn wd-btn--ghost" data-close="1">취소</button>
<button type="button" class="wd-btn wd-btn--danger" id="withdrawSubmitBtn">탈퇴하기</button>
</div>
</div>
</div>

View File

@ -167,7 +167,7 @@
<div class="mypage-card__arrow"></div>
</button>
<button type="button" class="mypage-card mypage-card--btn mypage-card--danger" data-action="withdraw-member">
<button type="button" id="btnOpenWithdraw" class="mypage-card mypage-card--btn mypage-card--danger" data-action="withdraw-member">
<div class="mypage-card__icon">⚠️</div>
<div class="mypage-card__body">
<div class="mypage-card__title">회원탈퇴</div>
@ -176,6 +176,8 @@
</div>
<div class="mypage-card__arrow"></div>
</button>
@include('web.mypage.info._withdraw_dialog')
</div>
{{-- 안내/주의사항 --}}
@ -229,6 +231,14 @@
},
});
})();
// dialog에 action/csrf 주입
(function(){
var dlg = document.getElementById('withdrawDialog');
if(!dlg) return;
dlg.dataset.action = "{{ route('web.mypage.info.withdraw') }}";
dlg.dataset.csrf = "{{ csrf_token() }}";
})();
</script>
<script src="{{ asset('assets/js/mypage_renew.js') }}" defer></script>
@endpush