test: improve checkout UX
This commit is contained in:
parent
b80a8ee240
commit
430e7f1dbe
|
|
@ -128,7 +128,7 @@
|
|||
</form>
|
||||
|
||||
<% } else { %>
|
||||
<!-- ─── Step 2: Payment display (SSR, auto-refreshes without JS) ─── -->
|
||||
<!-- ─── Step 2: Payment display ─── -->
|
||||
<%
|
||||
const inv = invoice;
|
||||
const isPending = inv.status === 'pending' || inv.status === 'underpaid' || inv.status === 'confirming';
|
||||
|
|
@ -140,110 +140,133 @@
|
|||
const remainingSec = Math.max(0, Math.floor((expiresAt.getTime() - now.getTime()) / 1000));
|
||||
const mins = Math.floor(remainingSec / 60);
|
||||
const secs = remainingSec % 60;
|
||||
const pct = Math.min(100, Math.round((received / total) * 100));
|
||||
const invPlanNames = { pro: 'Pro', pro2x: 'Pro 2x', pro4x: 'Pro 4x', lifetime: 'Lifetime' };
|
||||
const invPlanLabel = invPlanNames[inv.plan] || inv.plan;
|
||||
%>
|
||||
|
||||
<% if (isPending) { %>
|
||||
<!-- Auto-refresh every 5s for status updates (no-JS polling) -->
|
||||
<meta http-equiv="refresh" content="5">
|
||||
<% } %>
|
||||
|
||||
<div class="card-static px-6 py-10 text-center space-y-5">
|
||||
<div class="card-static overflow-hidden max-w-md mx-auto">
|
||||
|
||||
<% if (inv.status === 'confirming') { %>
|
||||
<!-- intentionally empty — confirming state is handled below -->
|
||||
<% } else if (inv.status !== 'paid') { %>
|
||||
<!-- QR -->
|
||||
<div>
|
||||
<a href="<%= inv.pay_uri %>" class="inline-block"><img src="<%= inv.qr_url %>" alt="QR Code" class="w-48 h-48 rounded-lg bg-white p-2" id="pay-qr"></a>
|
||||
<!-- Header bar -->
|
||||
<div class="px-5 py-3 border-b divider flex items-center justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-sm font-medium text-gray-200"><%= invPlanLabel %></span>
|
||||
<% if (inv.months) { %><span class="text-xs text-gray-500"><%= inv.months %>mo</span><% } %>
|
||||
</div>
|
||||
<div class="text-sm font-medium text-gray-300">$<%= Number(inv.amount_usd).toFixed(2) %></div>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<% if (inv.status === 'pending' || inv.status === 'underpaid') { %>
|
||||
<!-- Amount -->
|
||||
<div>
|
||||
<div class="text-2xl font-bold font-mono text-gray-100"><%= received > 0 && remaining > 0 ? remaining.toFixed(8) : inv.amount_crypto %></div>
|
||||
<div class="text-sm text-gray-500"><%= inv.coin_label %> (<%= inv.coin_ticker %>)</div>
|
||||
<div class="text-xs text-gray-600 mt-1">$<%= Number(inv.amount_usd).toFixed(2) %> USD</div>
|
||||
</div>
|
||||
<div class="px-5 pt-6 pb-5">
|
||||
|
||||
<% if (received > 0 && remaining > 0) { %>
|
||||
<!-- Received / Remaining -->
|
||||
<div class="flex justify-center gap-6 text-sm">
|
||||
<div>
|
||||
<span class="text-gray-500">Received:</span>
|
||||
<span class="text-green-400 font-mono"><%= received.toFixed(8) %></span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-gray-500">Remaining:</span>
|
||||
<span class="text-yellow-400 font-mono"><%= remaining.toFixed(8) %></span>
|
||||
<!-- QR + amount side by side on wider, stacked on narrow -->
|
||||
<div class="flex flex-col items-center gap-5">
|
||||
|
||||
<!-- QR -->
|
||||
<a href="<%= inv.pay_uri %>" class="inline-block">
|
||||
<img src="<%= inv.qr_url %>" alt="QR Code" class="w-44 h-44 rounded-xl bg-white p-2" id="pay-qr">
|
||||
</a>
|
||||
|
||||
<!-- Amount -->
|
||||
<div class="text-center">
|
||||
<div class="text-xs text-gray-500 uppercase tracking-wider mb-1">Send exactly</div>
|
||||
<div class="flex items-center justify-center gap-2">
|
||||
<span class="text-xl font-bold font-mono text-white"><%= received > 0 && remaining > 0 ? remaining.toFixed(8) : inv.amount_crypto %></span>
|
||||
<span class="text-sm font-medium text-gray-400"><%= inv.coin_ticker %></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% if (received > 0 && remaining > 0) { %>
|
||||
<!-- Progress bar -->
|
||||
<div class="w-full">
|
||||
<div class="w-full h-1.5 rounded-full bg-gray-800 overflow-hidden">
|
||||
<div class="h-full rounded-full bg-green-500 transition-all" style="width: <%= pct %>%"></div>
|
||||
</div>
|
||||
<div class="flex justify-between mt-1.5 text-xs">
|
||||
<span class="text-green-400 font-mono"><%= received.toFixed(8) %> received</span>
|
||||
<span class="text-gray-500 font-mono"><%= remaining.toFixed(8) %> left</span>
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<!-- Address -->
|
||||
<div class="w-full">
|
||||
<div class="flex items-center justify-between mb-1.5">
|
||||
<span class="text-xs text-gray-500">Address</span>
|
||||
<button onclick="copyAddr()" id="copy-btn" class="text-xs text-gray-500 hover:text-gray-300 transition-colors">Copy</button>
|
||||
</div>
|
||||
<code class="block bg-surface-solid border border-border-subtle rounded-lg px-3 py-2.5 text-blue-400 text-[11px] font-mono select-all break-all leading-relaxed" id="pay-address"><%= inv.address %></code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<!-- Address -->
|
||||
<div>
|
||||
<label class="block text-xs text-gray-500 mb-1">Send to</label>
|
||||
<code class="block bg-surface-solid border border-border-subtle rounded-lg px-3 py-2.5 text-blue-400 text-xs font-mono select-all break-all" id="pay-address"><%= inv.address %></code>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<!-- Status -->
|
||||
<% if (inv.status === 'pending') { %>
|
||||
<div class="flex items-center justify-center gap-2 text-sm">
|
||||
<span class="w-2 h-2 rounded-full bg-yellow-500 animate-pulse"></span>
|
||||
<span class="text-gray-400">Waiting for payment...</span>
|
||||
</div>
|
||||
<div class="text-xs text-gray-600 mt-2">
|
||||
Expires in <span class="font-mono"><%= mins %>:<%= String(secs).padStart(2, '0') %></span>
|
||||
</div>
|
||||
|
||||
<% } else if (inv.status === 'underpaid') { %>
|
||||
<div class="flex items-center justify-center gap-2 text-sm">
|
||||
<span class="w-2 h-2 rounded-full bg-yellow-500 animate-pulse"></span>
|
||||
<span class="text-yellow-400">Underpaid - please send the remaining amount</span>
|
||||
</div>
|
||||
<div class="text-xs text-gray-600 mt-2">
|
||||
Expires in <span class="font-mono"><%= mins %>:<%= String(secs).padStart(2, '0') %></span>
|
||||
<!-- Footer: status + timer -->
|
||||
<div class="px-5 py-3 border-t divider flex items-center justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="w-1.5 h-1.5 rounded-full <%= inv.status === 'underpaid' ? 'bg-yellow-500' : 'bg-blue-500' %> animate-pulse"></span>
|
||||
<span class="text-xs <%= inv.status === 'underpaid' ? 'text-yellow-400' : 'text-gray-400' %>"><%= inv.status === 'underpaid' ? 'Underpaid, send the rest' : 'Waiting for payment' %></span>
|
||||
</div>
|
||||
<span class="text-xs text-gray-500 font-mono"><%= mins %>:<%= String(secs).padStart(2, '0') %></span>
|
||||
</div>
|
||||
|
||||
<% } else if (inv.status === 'confirming') { %>
|
||||
<div class="w-16 h-16 mx-auto rounded-full bg-blue-500/10 border border-blue-500/20 flex items-center justify-center mb-4">
|
||||
<svg class="w-8 h-8 text-blue-500 animate-spin" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"/></svg>
|
||||
<div class="px-5 py-12 text-center">
|
||||
<div class="w-14 h-14 mx-auto rounded-full bg-blue-500/10 border border-blue-500/20 flex items-center justify-center mb-5">
|
||||
<svg class="w-7 h-7 text-blue-500 animate-spin" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"/></svg>
|
||||
</div>
|
||||
<p class="text-base font-semibold text-white mb-1">Payment received</p>
|
||||
<p class="text-sm text-gray-400">Waiting for network confirmation</p>
|
||||
<% if (inv.address) { %>
|
||||
<a href="https://transaction.st/address/<%= inv.address %>" target="_blank" rel="noopener" class="inline-block text-xs text-gray-500 hover:text-gray-300 transition-colors mt-4">View on explorer →</a>
|
||||
<% } %>
|
||||
</div>
|
||||
<p class="text-lg font-semibold text-white">Payment received</p>
|
||||
<p class="text-sm text-gray-400">Waiting for 1 network confirmation...</p>
|
||||
<% if (inv.address) { %>
|
||||
<a href="https://transaction.st/address/<%= inv.address %>" target="_blank" rel="noopener" class="block text-xs text-gray-500 hover:text-gray-400 transition-colors mt-1">View on block explorer →</a>
|
||||
<% } %>
|
||||
|
||||
<% } else if (inv.status === 'paid') { %>
|
||||
<div class="w-16 h-16 mx-auto rounded-full bg-green-500/10 border border-green-500/20 flex items-center justify-center mb-4">
|
||||
<svg class="w-8 h-8 text-green-400" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/></svg>
|
||||
<div class="px-5 py-12 text-center">
|
||||
<div class="w-14 h-14 mx-auto rounded-full bg-green-500/10 border border-green-500/20 flex items-center justify-center mb-5">
|
||||
<svg class="w-7 h-7 text-green-400" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/></svg>
|
||||
</div>
|
||||
<p class="text-base font-semibold text-white mb-1">Payment confirmed</p>
|
||||
<p class="text-sm text-gray-400 mb-5">Your <%= invPlanLabel %> plan is now active.</p>
|
||||
<div class="flex items-center justify-center gap-3">
|
||||
<a href="/dashboard/settings" class="btn-primary px-5 py-2 text-sm">Back to settings</a>
|
||||
<a href="/dashboard/checkout/<%= inv.id %>/receipt" target="_blank" class="btn-secondary px-5 py-2 text-sm">Receipt</a>
|
||||
</div>
|
||||
<% if (inv.address) { %>
|
||||
<a href="https://transaction.st/address/<%= inv.address %>" target="_blank" rel="noopener" class="inline-block text-xs text-gray-500 hover:text-gray-300 transition-colors mt-4">View on explorer →</a>
|
||||
<% } %>
|
||||
</div>
|
||||
<p class="text-lg font-semibold text-white">Payment confirmed</p>
|
||||
<p class="text-sm text-gray-400">Your plan has been activated.</p>
|
||||
<div class="flex items-center justify-center gap-3 mt-2">
|
||||
<a href="/dashboard/settings" class="btn-primary inline-block px-5 py-2 text-sm">Back to settings</a>
|
||||
<a href="/dashboard/checkout/<%= inv.id %>/receipt" target="_blank" class="btn-secondary inline-block px-5 py-2 text-sm">View Receipt</a>
|
||||
</div>
|
||||
<% if (inv.address) { %>
|
||||
<a href="https://transaction.st/address/<%= inv.address %>" target="_blank" rel="noopener" class="block text-xs text-gray-500 hover:text-gray-400 transition-colors mt-3">View on block explorer →</a>
|
||||
<% } %>
|
||||
|
||||
<% } else if (inv.status === 'expired') { %>
|
||||
<div class="flex items-center justify-center gap-2 text-red-400">
|
||||
<span class="font-medium">Payment expired</span>
|
||||
<div class="px-5 py-12 text-center">
|
||||
<div class="w-14 h-14 mx-auto rounded-full bg-red-500/10 border border-red-500/20 flex items-center justify-center mb-5">
|
||||
<svg class="w-7 h-7 text-red-400" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/></svg>
|
||||
</div>
|
||||
<p class="text-base font-semibold text-white mb-1">Invoice expired</p>
|
||||
<p class="text-sm text-gray-400 mb-5">This payment window has closed.</p>
|
||||
<a href="/dashboard/checkout" class="btn-primary inline-block px-5 py-2 text-sm">Try again</a>
|
||||
</div>
|
||||
<a href="/dashboard/checkout" class="mt-3 text-sm text-blue-400 hover:text-blue-300">Try again</a>
|
||||
<% } %>
|
||||
|
||||
</div>
|
||||
<p class="text-xs text-gray-600 text-center mt-4">You can leave this page. Payments are detected and confirmed automatically.</p>
|
||||
<p class="text-xs text-gray-600 text-center mt-4">You can close this page. Payments are detected automatically.</p>
|
||||
<% } %>
|
||||
</main>
|
||||
|
||||
|
||||
<script>
|
||||
// Copy address
|
||||
function copyAddr() {
|
||||
const addr = document.getElementById('pay-address');
|
||||
const btn = document.getElementById('copy-btn');
|
||||
if (addr) navigator.clipboard.writeText(addr.textContent);
|
||||
if (btn) { btn.textContent = 'Copied'; setTimeout(() => btn.textContent = 'Copy', 1500); }
|
||||
}
|
||||
|
||||
const multipliers = { pro: 1, pro2x: 2, pro4x: 4 };
|
||||
const planValue = document.getElementById('plan-value');
|
||||
let selectedTier = 'pro';
|
||||
|
|
|
|||
Loading…
Reference in New Issue