feat: payment in cabinet — banner + modal (card/SBP/cash), demo mode until YooKassa keys

This commit is contained in:
wasrusgen 2026-05-30 15:04:05 +03:00
parent e42d42f207
commit d378d47421

View File

@ -148,6 +148,20 @@ body{font-family:'Inter',sans-serif;background:var(--bg);color:var(--text);displ
.drop{border:2px dashed #CBD5E1;border-radius:14px;padding:32px;text-align:center;background:var(--subtle);max-width:520px;margin:8px auto}
.drop-ic{font-size:30px;margin-bottom:8px}
.spin{display:inline-block;animation:sp 1s linear infinite}@keyframes sp{to{transform:rotate(360deg)}}
.pay-banner{display:none;background:linear-gradient(135deg,var(--dark),var(--primary));color:#fff;padding:12px 26px;align-items:center;gap:14px;flex-shrink:0}
.pay-banner.show{display:flex}
.pay-banner b{font-size:16px;font-family:Montserrat}
.pay-btn{margin-left:auto;background:#fff;color:var(--primary);border:none;border-radius:9px;padding:9px 20px;font-weight:700;font-size:13px;cursor:pointer;font-family:Inter}
.modal-bg{display:none;position:fixed;inset:0;background:rgba(15,15,26,.6);z-index:100;align-items:center;justify-content:center}
.modal-bg.show{display:flex}
.modal{background:#fff;border-radius:16px;padding:26px;max-width:440px;width:90%}
.modal-h{font-family:Montserrat;font-weight:800;font-size:20px;margin-bottom:4px}
.modal-sub{font-size:14px;color:var(--muted);margin-bottom:20px}
.pay-opt{display:flex;align-items:center;gap:14px;border:1.5px solid var(--border);border-radius:12px;padding:15px;margin-bottom:10px;cursor:pointer;transition:all .15s}
.pay-opt:hover{border-color:var(--primary);background:var(--light)}
.pay-opt-ic{width:42px;height:42px;border-radius:10px;background:var(--light);display:flex;align-items:center;justify-content:center;font-size:20px;flex-shrink:0}
.pay-opt-t{font-size:15px;font-weight:700}.pay-opt-d{font-size:12px;color:var(--muted)}
.modal-close{margin-top:8px;width:100%;background:transparent;border:none;color:var(--muted);font-size:13px;cursor:pointer;padding:8px}
::-webkit-scrollbar{width:6px}::-webkit-scrollbar-thumb{background:rgba(0,0,0,.12);border-radius:4px}
</style>
</head>
@ -158,6 +172,17 @@ body{font-family:'Inter',sans-serif;background:var(--bg);color:var(--text);displ
<div class="hdr-client" id="hdrClient"></div>
<div class="hdr-r"><div class="elena-chip"><div class="elena-av">Е</div><div class="elena-nm">Елена</div><div class="elena-dot"></div></div></div>
</header>
<div class="pay-banner" id="payBanner"><span>💳 К оплате: <b id="payAmount"></b></span><button class="pay-btn" onclick="showPayModal()">Оплатить →</button></div>
<div class="modal-bg" id="payModal">
<div class="modal">
<div class="modal-h">Оплата консалтинга</div>
<div class="modal-sub">Сумма: <b id="modalAmount"></b> · выберите способ</div>
<div class="pay-opt" onclick="payVia('card')"><div class="pay-opt-ic">💳</div><div><div class="pay-opt-t">Банковская карта</div><div class="pay-opt-d">Visa, Mastercard, Мир · мгновенно</div></div></div>
<div class="pay-opt" onclick="payVia('sbp')"><div class="pay-opt-ic">📱</div><div><div class="pay-opt-t">СБП — по QR-коду</div><div class="pay-opt-d">Перевод через приложение банка</div></div></div>
<div class="pay-opt" onclick="payVia('cash')"><div class="pay-opt-ic">💵</div><div><div class="pay-opt-t">Наличными</div><div class="pay-opt-d">При встрече с консультантом</div></div></div>
<button class="modal-close" onclick="document.getElementById('payModal').classList.remove('show')">Отмена</button>
</div>
</div>
<div class="layout">
<aside class="sb">
<div class="sb-nav">
@ -333,6 +358,33 @@ function renderAll(){
chat.innerHTML="";
state.messages.forEach(m=>addMsg(m.role==="user"?"user":"elena",m.content));
unlockStages();
checkPayment();
}
function money(n){return (n||0).toLocaleString("ru-RU")+" ₽"}
function checkPayment(){
const crm=state.crm||{};const deal=crm.deal_amount||0;
const paid=(crm.payments||[]).reduce((s,p)=>s+(p.amount||0),0);
const left=deal-paid;
if(left>0){
document.getElementById("payAmount").textContent=money(left);
document.getElementById("modalAmount").textContent=money(left);
document.getElementById("payBanner").classList.add("show");
window.__payLeft=left;
}else document.getElementById("payBanner").classList.remove("show");
}
function showPayModal(){document.getElementById("payModal").classList.add("show")}
async function payVia(method){
const amount=window.__payLeft||0;
document.getElementById("payModal").classList.remove("show");
try{
const r=await fetch(`${API}/api/payment/create`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token,amount,method,return_url:location.href})});
const d=await r.json();
if(d.error){alert("Ошибка: "+d.error);return}
if(method==="cash"){alert("💵 "+d.instructions);return}
if(d.demo){alert("ДЕМО-режим: ЮKassa ещё не подключена.\n\n"+d.note);return}
if(d.qr){alert("СБП: отсканируйте QR в приложении банка\n\n"+(d.qr||""));return}
if(d.confirmation_url){location.href=d.confirmation_url;return}
}catch(e){alert("Ошибка: "+e.message)}
}
function unlockStages(){
// Этап 4 доступен если есть достаточно сообщений