2026-03-04 08:37:08 +09:00

978 lines
41 KiB
PHP

@extends('web.layouts.subpage')
@php
$mypageActive = $mypageActive ?? 'usage';
$attempt = $attempt ?? [];
$order = $order ?? [];
$items = $items ?? [];
$pins = $pins ?? [];
$pinsOpened = (bool)($pinsOpened ?? false);
$canCancel = (bool)($canCancel ?? false);
$backToListQuery = request()->only(['q', 'method', 'status', 'from', 'to', 'page']);
$backToListQuery = array_filter($backToListQuery, fn($v) => $v !== null && $v !== '');
// 결제수단 한글 매핑
$methodLabel = function ($m) {
$m = (string)$m;
return match ($m) {
'card' => '카드',
'phone' => '휴대폰',
'wire' => '계좌이체',
'vact' => '가상계좌',
default => $m ?: '-',
};
};
// 리스트와 동일한 상태 라벨
$statusLabel = function () use ($attempt, $order) {
$aCancel = (string)($attempt['cancel_status'] ?? 'none');
$oCancel = (string)($order['cancel_status'] ?? 'none');
// 결제 후 취소는 cancel_status=success로만 인식
if ($aCancel === 'success' || $oCancel === 'success') return '결제취소';
$aStatus = (string)($attempt['status'] ?? '');
$oPay = (string)($order['stat_pay'] ?? '');
if ($aStatus === 'paid' || $oPay === 'p') return '결제완료';
if ($aStatus === 'issued' || $oPay === 'w') return '입금대기';
// 화면 깨짐 방지용(원하면 숨겨도 됨)
if ($aStatus === 'failed' || $oPay === 'f') return '결제실패';
return '진행중';
};
$st = $statusLabel();
$isCancelledAfterPaid = ($st === '결제취소'); // 취소 완료면 전표만 남김
$statusClass = function ($label) {
return match ($label) {
'결제취소' => 'pill--danger',
'결제완료' => 'pill--ok',
'입금대기' => 'pill--wait',
'결제실패' => 'pill--danger',
default => 'pill--muted',
};
};
// 전표용 값
$attemptId = (int)($attempt['id'] ?? 0);
$oid = (string)($order['oid'] ?? '');
$method = (string)($order['pay_method'] ?? ($attempt['pay_method'] ?? ''));
$methodKor = $methodLabel($method);
$amounts = (array)($order['amounts'] ?? []);
$subtotal = (int)($amounts['subtotal'] ?? 0);
$fee = (int)($amounts['fee'] ?? 0);
$payMoney = (int)($amounts['pay_money'] ?? 0);
// 상품명: 현재 전달받는 변수 기준 유지 (사용자 확인 완료)
$productName = (string)($productname ?? '');
if ($productName === '') $productName = '-';
$itemName = (string)($items[0]['name'] ?? '');
if ($itemName === '') $itemName = '-';
// 수량 합계
$totalQty = 0;
foreach ($items as $it) $totalQty += (int)($it['qty'] ?? 0);
// 일시 (분까지)
$createdAt = (string)($order['created_at'] ?? ($attempt['created_at'] ?? ''));
$dateStr = $createdAt ? \Carbon\Carbon::parse($createdAt)->format('Y-m-d H:i') : '-';
// 핀 목록 "추후 조건" 대비: 지금은 보여줌
$showPinsNow = true;
// 핀발행 완료 여부 (우선 pins 존재 기준)
// 추후 서버에서 bool($pinsIssuedCompleted) 내려주면 그 값 우선 사용 권장
$isPinIssuedCompleted = (bool)($pinsIssuedCompleted ?? !empty($pins));
// 오른쪽 영역 배너 모드 조건
$useRightBannerMode = $isCancelledAfterPaid || $isPinIssuedCompleted;
// 핀 발행(확인) 방식: gc_products.pin_check_methods 기반 (서비스에서 내려줌)
$issueMethods = $issueMethods ?? ['PIN_INSTANT','SMS','BUYBACK'];
if (is_string($issueMethods)) $issueMethods = json_decode($issueMethods, true) ?: [];
if (!is_array($issueMethods) || empty($issueMethods)) $issueMethods = ['PIN_INSTANT','SMS','BUYBACK'];
$issueAllowed = array_fill_keys($issueMethods, true);
$issueMissing = $issueMissing ?? [];
if (is_string($issueMissing)) $issueMissing = json_decode($issueMissing, true) ?: [];
if (!is_array($issueMissing)) $issueMissing = [];
$issueMap = [
'PIN_INSTANT' => '핀번호 바로 확인',
'SMS' => 'SMS 발송',
'BUYBACK' => '구매상품권 판매',
];
$issueMissingLabels = [];
foreach ($issueMissing as $k) {
if (isset($issueMap[$k])) $issueMissingLabels[] = $issueMap[$k];
}
// 1개만 가능하면 그 옵션을 기본으로 열어둠
$issueOpenKey = (count($issueMethods) === 1) ? ($issueMethods[0] ?? null) : null;
@endphp
@section('title', '구매내역 상세')
@section('subcontent')
<div class="mypage-usage">
<div class="topbar">
@if(session('success')) <div class="flash ok">{{ session('success') }}</div> @endif
@if(session('error')) <div class="flash err">{{ session('error') }}</div> @endif
<div class="sp"></div>
<a class="btn btn--back" href="{{ route('web.mypage.usage.index', $backToListQuery) }}"> 목록</a>
</div>
{{-- 상단: 전표 + 우측 영역(핀발행/배너) --}}
<div class="detail-hero-grid">
{{-- 좌측: 영수증형 전표 --}}
<div class="receipt-card receipt-card--paper">
<div class="receipt-head">
<div>
<div class="receipt-title">결제 영수증</div>
<div class="receipt-sub">
{{ $dateStr }} · {{ $methodKor }}
</div>
</div>
<span class="pill {{ $statusClass($st) }}">{{ $st }}</span>
</div>
<div class="receipt-divider"></div>
<div class="receipt-main">
<div class="receipt-product">
{{ $productName }}
<span class="receipt-item">[ {{ $itemName }} ]</span>
</div>
<div class="receipt-grid">
<div class="row">
<span class="k">결제번호</span>
<span class="v">#{{ $attemptId ?: '-' }}</span>
</div>
<div class="row">
<span class="k">주문번호</span>
<span class="v mono">{{ $oid ?: '-' }}</span>
</div>
<div class="row">
<span class="k">결제수단</span>
<span class="v">{{ $methodKor }}</span>
</div>
<div class="row">
<span class="k">수량</span>
<span class="v">{{ $totalQty }}</span>
</div>
</div>
</div>
<div class="receipt-divider receipt-divider--dashed"></div>
<div class="receipt-amount">
<div class="amt-row">
<span class="k">상품금액</span>
<span class="v">{{ number_format($subtotal) }}</span>
</div>
<div class="amt-row">
<span class="k">고객수수료</span>
<span class="v">{{ number_format($fee) }}</span>
</div>
<div class="amt-row total">
<span class="k">결제금액</span>
<span class="v">{{ number_format($payMoney) }}</span>
</div>
</div>
@if(!empty($order['cancel_last_msg']))
<div class="notice-box notice-box--err">
<b>취소 처리 결과</b><br>
{{ $order['cancel_last_msg'] }}
</div>
@endif
</div>
{{-- 우측: 핀발행 인터랙션 또는 안내 배너 --}}
<aside class="right-panel {{ $useRightBannerMode ? 'right-panel--banner right-panel--mobile-hide' : '' }}">
@if($useRightBannerMode)
<div class="banner-stack">
@if($isCancelledAfterPaid)
<div class="promo-vertical-banner promo-vertical-banner--cancel">
<div class="promo-vertical-banner__inner">
<div class="promo-vertical-banner__badge">PROMO</div>
<div class="promo-vertical-banner__eyebrow">PIN FOR YOU</div>
<div class="promo-vertical-banner__title">
다음 구매는<br>
빠르고 간편하게
</div>
<div class="promo-vertical-banner__desc">
자주 찾는 상품권을 빠르게 확인하고,<br>
구매 내역/상태를 번에 관리해보세요.
</div>
<div class="promo-vertical-banner__chips">
<span class="chip">빠른 재구매</span>
<span class="chip">구매내역 관리</span>
<span class="chip">안전한 결제</span>
</div>
<div class="promo-vertical-banner__footer">
<div class="promo-vertical-banner__footer-title">추천 안내</div>
<div class="promo-vertical-banner__footer-desc">
진행 중인 이벤트 혜택은 메인/상품 페이지에서 확인할 있습니다.
</div>
</div>
</div>
</div>
@endif
@if($isPinIssuedCompleted && !$isCancelledAfterPaid)
<div class="info-banner info-banner--ok">
<div class="info-banner__title"> 발행이 완료되었습니다</div>
<div class="info-banner__desc">
발행이 완료된 주문은 우측 발행 선택 영역 대신 안내 배너를 표시합니다.
목록 영역에서 발행된 정보를 확인해 주세요.
</div>
</div>
<div class="info-banner info-banner--warn">
<div class="info-banner__title">취소 제한 안내</div>
<div class="info-banner__desc">
확인/발행 이후에는 결제 취소가 제한될 있습니다.
실제 취소 가능 여부는 하단 결제 취소 영역에서 확인됩니다.
</div>
</div>
@endif
</div>
@else
<div class="issue-panel">
<div class="issue-panel__head">
<h3 class="issue-panel__title"> 발행 선택</h3>
@if(!empty($issueMissingLabels))
<div class="muted" style="font-size:12px;margin-top:6px;">
상품은 {{ implode(', ', $issueMissingLabels) }} 기능을 지원하지 않습니다.
</div>
@endif
</div>
<div class="issue-picker" id="issuePicker">
{{-- 옵션 1 --}}
@if(isset($issueAllowed['PIN_INSTANT']))
<div class="issue-option {{ $issueOpenKey==='PIN_INSTANT' ? 'is-active' : '' }}" data-issue-card="view">
<button type="button" class="issue-option__toggle" data-issue-toggle>
<div class="issue-option__kicker">즉시 확인</div>
<div class="issue-option__title">핀번호 바로 확인</div>
<div class="issue-option__subtitle">안전하게 핀번호를 직접 확인합니다.</div>
<span class="issue-option__chev" aria-hidden="true"></span>
</button>
<div class="issue-option__detail">
<div class="issue-option__detail-inner">
<p class="issue-option__detail-text">
핀번호를 개인 암호화하여 발행합니다. 핀번호 유출에 주의하세요.
</p>
<button id="btnIssueView" type="button" class="issue-run issue-run--dark">
핀번호 바로 확인 실행
</button>
</div>
</div>
</div>
@endif
{{-- 옵션 2 --}}
@if(isset($issueAllowed['SMS']))
<div class="issue-option {{ $issueOpenKey==='SMS' ? 'is-active' : '' }}" data-issue-card="sms">
<button type="button" class="issue-option__toggle" data-issue-toggle>
<div class="issue-option__kicker">문자 발송</div>
<div class="issue-option__title">SMS 발송</div>
<div class="issue-option__subtitle">문자로 핀번호를 전송합니다.</div>
<span class="issue-option__chev" aria-hidden="true"></span>
</button>
<div class="issue-option__detail">
<div class="issue-option__detail-inner">
<p class="issue-option__detail-text">
SMS 발송 핀번호는 저장되지 않습니다. 문자 수신 즉시 확인하세요.
</p>
<button id="btnIssueSms" type="button" class="issue-run issue-run--sky">
SMS 발송 실행
</button>
</div>
</div>
</div>
@endif
{{-- 옵션 3 --}}
@if(isset($issueAllowed['BUYBACK']))
<div class="issue-option {{ $issueOpenKey==='BUYBACK' ? 'is-active' : '' }}" data-issue-card="sell">
<button type="button" class="issue-option__toggle" data-issue-toggle>
<div class="issue-option__kicker">재판매</div>
<div class="issue-option__title">구매상품권 판매</div>
<div class="issue-option__subtitle">구매하신 상품권을 판매 처리합니다.</div>
<span class="issue-option__chev" aria-hidden="true"></span>
</button>
<div class="issue-option__detail">
<div class="issue-option__detail-inner">
<p class="issue-option__detail-text">
구매하신 상품권을 판매합니다. 계좌번호가 등록되어 있어야 하며,
매입 처리 회원님 계좌로 입금됩니다.
</p>
<button id="btnIssueSell" type="button" class="issue-run issue-run--green">
구매상품권 판매 실행
</button>
</div>
</div>
</div>
@endif
@if(empty($issueAllowed))
<div class="muted" style="padding:12px 6px;">
현재 상품은 발행 방식이 설정되지 않았습니다. 고객센터로 문의해 주세요.
</div>
@endif
</div>
</div>
@endif
</aside>
</div>
@if(!$isCancelledAfterPaid)
{{-- 목록 --}}
@if($showPinsNow)
<div class="usage-card">
<div class="section-head">
<h3 class="card-title"> 목록</h3>
<div class="sub muted">
발행이 완료되면 영역에서 정보를 확인할 있습니다. (현재는 UI 확인을 위해 표시 )
</div>
</div>
@if(empty($pins))
<p class="muted">표시할 핀이 없습니다.</p>
@else
<ul class="pins">
@foreach($pins as $p)
@php
$id = (int)($p['id'] ?? 0);
$status = (string)($p['status'] ?? '');
$raw = (string)($p['pin'] ?? $p['pin_code'] ?? $p['pin_no'] ?? '');
$masked = (string)($p['pin_masked'] ?? $p['pin_mask'] ?? '');
if ($masked === '' && $raw !== '') {
$digits = preg_replace('/\s+/', '', $raw);
$masked = (mb_strlen($digits) >= 8)
? (mb_substr($digits,0,4).str_repeat('*',4).mb_substr($digits,-2))
: '****';
}
// 오픈 전에는 마스킹 우선
$display = $pinsOpened ? ($raw ?: $masked) : ($masked ?: '****');
@endphp
<li class="pin-row">
<span class="pill">#{{ $id }}</span>
@if($status !== '') <span class="pill pill--muted">{{ $status }}</span> @endif
<span class="mono pin-code">{{ $display }}</span>
</li>
@endforeach
</ul>
@endif
</div>
@endif
{{-- 취소 버튼은 아래, 작게 --}}
<div class="usage-card cancel-box">
<h3 class="card-title">결제 취소</h3>
<div class="muted">
핀을 확인/발행한 이후에는 취소가 제한될 있습니다.<br>
결제 취소는 처리 시간이 소요될 있으며, 취소 결과는 페이지에 반영됩니다.
</div>
@if($canCancel)
<form method="post" action="{{ route('web.mypage.usage.cancel', ['attemptId' => $attemptId]) }}" class="cancel-form">
@csrf
<input type="hidden" name="q" value="{{ request('q', '') }}">
<input type="hidden" name="method" value="{{ request('method', '') }}">
<input type="hidden" name="status" value="{{ request('status', '') }}">
<input type="hidden" name="from" value="{{ request('from', '') }}">
<input type="hidden" name="to" value="{{ request('to', '') }}">
<input type="hidden" name="page" value="{{ request('page', '') }}">
<input class="inp" name="reason" placeholder="취소 사유(선택)">
<button id="btnCancel" class="btn btn--danger btn--sm" type="submit">
결제 취소
</button>
</form>
@else
<div class="notice-box">
현재 상태에서는 결제 취소가 불가능합니다.
</div>
@endif
</div>
@endif
</div>
<style>
.mypage-usage { display:flex; flex-direction:column; gap:14px; }
.topbar{display:flex; align-items:center; gap:10px; flex-wrap:wrap;}
.topbar .sp{flex:1;}
.btn{
display:inline-flex;align-items:center;justify-content:center;
padding:10px 12px;border-radius:12px;border:1px solid rgba(0,0,0,.14);
cursor:pointer;text-decoration:none;font-size:13px; white-space:nowrap; background:#fff;
}
.btn--sm{padding:8px 10px;border-radius:10px;font-size:12px;}
.btn--danger{border-color: rgba(220,0,0,.35); color:rgb(180,0,0); font-weight:800;}
.flash{padding:8px 10px;border-radius:10px;font-size:13px;}
.flash.ok{background:rgba(0,160,60,.08);}
.flash.err{background:rgba(220,0,0,.08);}
.mono{font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;}
.muted { color:rgba(0,0,0,.55); }
.pill{
display:inline-flex; padding:4px 10px; border-radius:999px; font-size:12px;
border:1px solid rgba(0,0,0,.12); white-space:nowrap;
}
.pill--ok{background:rgba(0,160,60,.08); border-color:rgba(0,160,60,.18);}
.pill--danger{background:rgba(220,0,0,.08); border-color:rgba(220,0,0,.18); color:rgb(180,0,0);}
.pill--wait{background:rgba(255,190,0,.12); border-color:rgba(255,190,0,.25);}
.pill--muted{opacity:.75;}
.notice-box { padding:12px; border-radius:12px; background:rgba(0,0,0,.04); margin-top:10px; }
.notice-box--err { background:rgba(220,0,0,.06); }
/* ===== Hero grid (전표 + 오른쪽 영역) ===== */
.detail-hero-grid{
display:grid;
grid-template-columns:1fr;
gap:14px;
}
@media (min-width: 960px){
.detail-hero-grid{
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); /* 5:5 */
align-items: stretch; /* 좌우 높이 자연스럽게 맞춤 */
}
.detail-hero-grid > * {
min-width: 0;
}
}
.right-panel{
display:block;
height:100%;
}
.right-panel .issue-panel,
.right-panel .banner-stack,
.right-panel .promo-vertical-banner{
height:100%;
}
@media (max-width: 959px){
.right-panel--mobile-hide{display:none;}
}
/* ===== Receipt (카드 영수증 느낌) ===== */
.receipt-card{
border:1px solid rgba(0,0,0,.08);
border-radius:18px;
padding:16px;
background:#fff;
}
.receipt-card--paper{
box-shadow: 0 10px 24px rgba(0,0,0,.04);
}
.receipt-head{
display:flex; justify-content:space-between; align-items:flex-start; gap:10px;
}
.receipt-title{font-size:18px; font-weight:900; line-height:1.1;}
.receipt-sub{margin-top:4px; color:rgba(0,0,0,.55); font-size:12px;}
.receipt-divider{
margin:12px 0;
border-top:1px solid rgba(0,0,0,.08);
}
.receipt-divider--dashed{
border-top-style:dashed;
border-top-color:rgba(0,0,0,.16);
}
.receipt-main{display:flex; flex-direction:column; gap:10px;}
.receipt-product{
font-size:15px; font-weight:900; line-height:1.35;
}
.receipt-item{
font-size:13px; font-weight:700; color:rgba(0,0,0,.6);
}
.receipt-grid{
display:grid; grid-template-columns:1fr; gap:8px;
}
.receipt-grid .row{
display:flex; justify-content:space-between; gap:10px; font-size:13px;
}
.receipt-grid .k{color:rgba(0,0,0,.55);}
.receipt-grid .v{font-weight:800; text-align:right;}
.receipt-amount{
display:flex; flex-direction:column; gap:8px;
padding:12px;
border-radius:14px;
background:rgba(0,0,0,.02);
border:1px solid rgba(0,0,0,.05);
}
.amt-row{display:flex; justify-content:space-between; gap:10px; font-size:13px;}
.amt-row .k{color:rgba(0,0,0,.6);}
.amt-row .v{font-weight:800;}
.amt-row.total{
margin-top:2px;
padding-top:8px;
border-top:1px dashed rgba(0,0,0,.14);
font-size:15px; font-weight:900;
}
/* ===== Right panel: banner mode ===== */
.banner-stack{
display:flex;
flex-direction:column;
gap:10px;
}
.info-banner{
border-radius:18px;
padding:14px;
border:1px solid rgba(0,0,0,.08);
background:#fff;
box-shadow: 0 8px 20px rgba(0,0,0,.03);
}
.info-banner__title{
font-size:14px; font-weight:900;
}
.info-banner__desc{
margin-top:6px;
font-size:13px; line-height:1.45;
color:rgba(0,0,0,.62);
}
.info-banner--danger{
background:linear-gradient(180deg, rgba(220,0,0,.05), rgba(220,0,0,.02));
border-color:rgba(220,0,0,.14);
}
.info-banner--ok{
background:linear-gradient(180deg, rgba(0,160,60,.07), rgba(0,160,60,.03));
border-color:rgba(0,160,60,.16);
}
.info-banner--warn{
background:linear-gradient(180deg, rgba(255,190,0,.10), rgba(255,190,0,.04));
border-color:rgba(255,190,0,.20);
}
/* ===== Cancel 상태용 세로 광고 배너 ===== */
.promo-vertical-banner{
border-radius:20px;
position:relative;
overflow:hidden;
border:1px solid rgba(0,0,0,.08);
background:
radial-gradient(120% 80% at 0% 0%, rgba(255,120,120,.16), transparent 55%),
radial-gradient(90% 70% at 100% 100%, rgba(255,190,0,.16), transparent 55%),
linear-gradient(180deg, #fff 0%, #fbfbfd 100%);
box-shadow:
0 20px 35px rgba(0,0,0,.06),
0 8px 16px rgba(0,0,0,.03),
inset 0 1px 0 rgba(255,255,255,.9);
min-height:100%;
}
.promo-vertical-banner--cancel::before{
content:'';
position:absolute;
inset:0;
background:
linear-gradient(135deg, rgba(220,0,0,.04) 0%, transparent 38%),
linear-gradient(315deg, rgba(255,190,0,.05) 0%, transparent 42%);
pointer-events:none;
}
.promo-vertical-banner__inner{
position:relative;
z-index:1;
height:100%;
display:flex;
flex-direction:column;
padding:16px;
gap:10px;
}
.promo-vertical-banner__badge{
align-self:flex-start;
font-size:11px;
font-weight:900;
letter-spacing:.06em;
color:#7a1b1b;
background:rgba(220,0,0,.08);
border:1px solid rgba(220,0,0,.16);
border-radius:999px;
padding:4px 8px;
}
.promo-vertical-banner__eyebrow{
font-size:12px;
font-weight:800;
color:rgba(0,0,0,.45);
letter-spacing:.03em;
}
.promo-vertical-banner__title{
font-size:22px;
line-height:1.15;
font-weight:900;
letter-spacing:-0.02em;
color:#1d1d1f;
margin-top:2px;
}
.promo-vertical-banner__desc{
font-size:13px;
line-height:1.5;
color:rgba(0,0,0,.62);
}
.promo-vertical-banner__chips{
display:flex;
flex-wrap:wrap;
gap:8px;
margin-top:2px;
}
.promo-vertical-banner__chips .chip{
display:inline-flex;
align-items:center;
justify-content:center;
padding:6px 10px;
border-radius:999px;
font-size:12px;
font-weight:700;
background:rgba(255,255,255,.9);
border:1px solid rgba(0,0,0,.08);
box-shadow: 0 3px 6px rgba(0,0,0,.03);
}
.promo-vertical-banner__footer{
margin-top:auto; /* 아래 정렬 */
border-top:1px dashed rgba(0,0,0,.10);
padding-top:10px;
}
.promo-vertical-banner__footer-title{
font-size:12px;
font-weight:900;
color:rgba(0,0,0,.75);
}
.promo-vertical-banner__footer-desc{
margin-top:4px;
font-size:12px;
line-height:1.45;
color:rgba(0,0,0,.55);
}
/* ===== Right panel: issue picker (색감 + 입체감 강화) ===== */
.issue-panel{
border-radius:20px;
padding:14px;
background:
radial-gradient(1200px 320px at -10% -20%, rgba(0,130,255,.10), transparent 45%),
radial-gradient(1000px 280px at 110% 120%, rgba(0,160,60,.08), transparent 45%),
linear-gradient(180deg, rgba(255,255,255,.98), rgba(250,251,253,.98));
border:1px solid rgba(0,0,0,.08);
box-shadow:
0 20px 35px rgba(0,0,0,.06),
0 6px 14px rgba(0,0,0,.03),
inset 0 1px 0 rgba(255,255,255,.8);
}
.issue-panel__head{
display:flex; flex-direction:column; gap:6px;
margin-bottom:12px;
}
.issue-panel__title{
margin:0; font-size:17px; font-weight:900;
}
.issue-panel__desc{
font-size:13px; color:rgba(0,0,0,.62); line-height:1.4;
}
.issue-picker{
display:flex; flex-direction:column; gap:10px;
}
.issue-option{
border:1px solid rgba(0,0,0,.08);
border-radius:16px;
background:linear-gradient(180deg, rgba(255,255,255,1), rgba(248,249,251,1));
overflow:hidden;
transition:
border-color .25s ease,
box-shadow .25s ease,
transform .18s ease,
background .25s ease;
box-shadow:
0 6px 12px rgba(0,0,0,.02),
inset 0 1px 0 rgba(255,255,255,.85);
position:relative;
}
.issue-option::before{
content:'';
position:absolute;
left:0; top:0; bottom:0;
width:4px;
background:rgba(0,0,0,.08);
transition:opacity .25s ease, background .25s ease;
opacity:.7;
}
.issue-option:hover{
border-color:rgba(0,0,0,.14);
transform:translateY(-1px);
box-shadow:
0 12px 18px rgba(0,0,0,.04),
inset 0 1px 0 rgba(255,255,255,.9);
}
.issue-option.is-active{
border-color:rgba(0,0,0,.18);
box-shadow:
0 18px 24px rgba(0,0,0,.06),
0 8px 14px rgba(0,0,0,.03),
inset 0 1px 0 rgba(255,255,255,.95);
transform:translateY(-1px);
}
/* 카드별 컬러 포인트 */
.issue-option[data-issue-card="view"]::before{
background:linear-gradient(180deg, rgba(70,70,70,.85), rgba(20,20,20,.85));
}
.issue-option[data-issue-card="sms"]::before{
background:linear-gradient(180deg, rgba(0,130,255,.95), rgba(0,90,220,.85));
}
.issue-option[data-issue-card="sell"]::before{
background:linear-gradient(180deg, rgba(0,170,95,.95), rgba(0,130,70,.85));
}
.issue-option[data-issue-card="view"].is-active{
background:linear-gradient(180deg, rgba(0,0,0,.025), rgba(255,255,255,1));
}
.issue-option[data-issue-card="sms"].is-active{
background:linear-gradient(180deg, rgba(0,130,255,.06), rgba(255,255,255,1));
}
.issue-option[data-issue-card="sell"].is-active{
background:linear-gradient(180deg, rgba(0,160,60,.07), rgba(255,255,255,1));
}
.issue-option__toggle{
width:100%;
border:0;
background:transparent;
text-align:left;
cursor:pointer;
padding:12px 14px 12px 16px;
display:grid;
grid-template-columns:1fr auto;
gap:8px;
}
.issue-option__kicker{
grid-column:1 / 2;
font-size:11px; font-weight:800;
color:rgba(0,0,0,.45);
text-transform:uppercase;
letter-spacing:.03em;
}
.issue-option__title{
grid-column:1 / 2;
font-size:15px; font-weight:900; line-height:1.1;
}
.issue-option__subtitle{
grid-column:1 / 2;
font-size:12px; color:rgba(0,0,0,.58); line-height:1.35;
}
.issue-option__chev{
grid-column:2 / 3;
grid-row:1 / span 3;
align-self:center;
font-size:18px;
color:rgba(0,0,0,.45);
transition:transform .25s ease;
}
.issue-option.is-active .issue-option__chev{
transform:rotate(180deg);
}
.issue-option__detail{
max-height:0;
opacity:0;
overflow:hidden;
transition:max-height .32s ease, opacity .22s ease;
}
.issue-option.is-active .issue-option__detail{
max-height:220px;
opacity:1;
}
.issue-option__detail-inner{
padding:0 14px 14px 16px;
border-top:1px dashed rgba(0,0,0,.08);
}
.issue-option__detail-text{
margin:10px 0 12px;
font-size:13px; line-height:1.45;
color:rgba(0,0,0,.65);
}
.issue-run{
display:inline-flex; align-items:center; justify-content:center;
border:1px solid rgba(0,0,0,.12);
border-radius:12px;
background:#fff;
padding:10px 12px;
font-size:13px;
font-weight:800;
cursor:pointer;
box-shadow:
0 6px 12px rgba(0,0,0,.04),
inset 0 1px 0 rgba(255,255,255,.9);
}
.issue-run--dark{
background:linear-gradient(180deg, rgba(0,0,0,.06), rgba(0,0,0,.03));
}
.issue-run--sky{
background:linear-gradient(180deg, rgba(0,130,255,.10), rgba(0,130,255,.05));
border-color:rgba(0,130,255,.20);
}
.issue-run--green{
background:linear-gradient(180deg, rgba(0,160,60,.12), rgba(0,160,60,.06));
border-color:rgba(0,160,60,.18);
}
/* ===== Standard card / Pins / Cancel ===== */
.usage-card{border:1px solid rgba(0,0,0,.08); border-radius:16px; padding:16px; background:#fff;}
.section-head{display:flex; flex-direction:column; gap:6px; margin-bottom:10px;}
.card-title{font-size:16px; margin:0;}
.sub{font-size:13px;}
.pins{margin:0; padding-left:0; list-style:none; display:flex; flex-direction:column; gap:8px;}
.pin-row{display:flex; align-items:center; gap:8px; flex-wrap:wrap;}
.pin-code{font-weight:900; letter-spacing:0.3px;}
.cancel-box{padding:14px;}
.cancel-form{margin-top:10px; display:flex; flex-direction:column; gap:8px; align-items:flex-start;}
.inp{
padding:10px 10px; border-radius:12px; border:1px solid rgba(0,0,0,.14);
background:#fff; font-size:13px; width:100%; max-width:420px;
}
</style>
<script>
// ---- 핀 오픈(오픈 후 취소 제한 안내) ----
async function onOpenPinsOnce(e) {
e.preventDefault();
const ok = await showMsg(
"핀 확인(오픈) 후에는 취소가 불가능할 수 있습니다.\n\n진행할까요?",
{ type: 'confirm', title: '핀 확인' }
);
if (!ok) return;
const form = e.currentTarget.closest('form');
if (!form) return;
// requestSubmit이 있으면 native validation도 같이 탄다
if (form.requestSubmit) form.requestSubmit();
else form.submit();
}
// ---- 결제 취소(confirm) ----
async function onCancelOnce(e) {
e.preventDefault();
const ok = await showMsg(
"핀 확인 전에만 취소할 수 있습니다.\n\n결제를 취소할까요?",
{ type: 'confirm', title: '결제 취소' }
);
if (!ok) return;
const form = e.currentTarget.closest('form');
if (!form) return;
if (form.requestSubmit) form.requestSubmit();
else form.submit();
}
// ---- 준비중(3버튼 alert) ----
async function onIssueViewSoon() {
await showMsg(
"준비중입니다.\n\n핀번호를 개인 암호화하여 발행합니다. 핀번호 유출에 주의하세요.",
{ type: 'alert', title: '안내' }
);
}
async function onIssueSmsSoon() {
await showMsg(
"준비중입니다.\n\nSMS 발송 시 핀번호는 저장되지 않습니다. 문자 수신 후 즉시 확인하세요.",
{ type: 'alert', title: '안내' }
);
}
async function onIssueSellSoon() {
await showMsg(
"준비중입니다.\n\n구매하신 상품권을 판매합니다.\n계좌번호가 등록되어 있어야 합니다.\n매입 처리는 약간의 시간이 걸릴 수 있으며, 완료 후 회원님 계좌로 입금됩니다.",
{ type: 'alert', title: '안내' }
);
}
// ---- 바인딩 (최소 1회) ----
document.addEventListener('DOMContentLoaded', () => {
// 기존 로직 유지 (현재 버튼이 없을 수 있어도 그대로)
const btnOpen = document.getElementById('btnOpenPins');
if (btnOpen) btnOpen.addEventListener('click', onOpenPinsOnce);
const btnCancel = document.getElementById('btnCancel');
if (btnCancel) btnCancel.addEventListener('click', onCancelOnce);
const btn1 = document.getElementById('btnIssueView');
if (btn1) btn1.addEventListener('click', onIssueViewSoon);
const btn2 = document.getElementById('btnIssueSms');
if (btn2) btn2.addEventListener('click', onIssueSmsSoon);
const btn3 = document.getElementById('btnIssueSell');
if (btn3) btn3.addEventListener('click', onIssueSellSoon);
// ---- 핀 발행 선택형 배너 (한 번에 1개만 확장) ----
const issueCards = Array.from(document.querySelectorAll('[data-issue-card]'));
issueCards.forEach((card) => {
const toggle = card.querySelector('[data-issue-toggle]');
if (!toggle) return;
toggle.addEventListener('click', () => {
const isActive = card.classList.contains('is-active');
// 모두 닫기
issueCards.forEach(c => c.classList.remove('is-active'));
// 방금 클릭한 카드가 닫힌 상태였으면 열기
if (!isActive) {
card.classList.add('is-active');
}
});
});
});
</script>
@endsection