937 lines
38 KiB
PHP
937 lines
38 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;
|
|
@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>
|
|
</div>
|
|
|
|
<div class="issue-picker" id="issuePicker">
|
|
{{-- 옵션 1 --}}
|
|
<div class="issue-option" 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>
|
|
|
|
{{-- 옵션 2 --}}
|
|
<div class="issue-option" 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>
|
|
|
|
{{-- 옵션 3 --}}
|
|
<div class="issue-option" 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>
|
|
</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
|