feat: add receipts to the payment service
This commit is contained in:
@@ -305,6 +305,30 @@ export const dashboard = new Elysia()
|
||||
return html("checkout", { nav: "settings", account: acc, payApi, invoiceId: params.id, coins, invoice });
|
||||
})
|
||||
|
||||
// Receipt (proxy to pay service, serves HTML directly)
|
||||
.get("/dashboard/checkout/:id/receipt", async ({ cookie, headers, params, set }) => {
|
||||
const resolved = await getAccountId(cookie, headers);
|
||||
if (!resolved?.accountId) return redirect("/dashboard");
|
||||
|
||||
const payApi = process.env.PAY_API || "https://pay.pingql.com";
|
||||
const key = cookie?.pingql_key?.value;
|
||||
try {
|
||||
const res = await fetch(`${payApi}/checkout/${params.id}/receipt`, {
|
||||
headers: { "Authorization": `Bearer ${key}` },
|
||||
});
|
||||
if (!res.ok) {
|
||||
const data = await res.json().catch(() => ({}));
|
||||
set.status = res.status;
|
||||
return data.error || "Receipt not available";
|
||||
}
|
||||
set.headers["content-type"] = "text/html; charset=utf-8";
|
||||
return await res.text();
|
||||
} catch {
|
||||
set.status = 500;
|
||||
return "Could not load receipt";
|
||||
}
|
||||
})
|
||||
|
||||
// Create checkout via form POST (no-JS)
|
||||
.post("/dashboard/checkout", async ({ cookie, headers, body }) => {
|
||||
const resolved = await getAccountId(cookie, headers);
|
||||
|
||||
@@ -175,7 +175,10 @@
|
||||
</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>
|
||||
<a href="/dashboard/settings" class="btn-primary inline-block px-5 py-2 text-sm mt-2">Back to settings</a>
|
||||
<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>
|
||||
<% } %>
|
||||
|
||||
@@ -51,39 +51,6 @@
|
||||
<% } %>
|
||||
</section>
|
||||
|
||||
<!-- Invoices -->
|
||||
<% if (it.invoices && it.invoices.length > 0) { %>
|
||||
<section class="card-static p-6">
|
||||
<h2 class="text-sm font-semibold text-gray-300 mb-4">Invoices</h2>
|
||||
<div class="space-y-2">
|
||||
<% it.invoices.forEach(function(inv) {
|
||||
const statusColors = { paid: 'green', confirming: 'blue', pending: 'yellow', underpaid: 'orange' };
|
||||
const statusColor = statusColors[inv.status] || 'gray';
|
||||
const date = new Date(inv.created_at).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' });
|
||||
const planLabel = inv.plan === 'lifetime' ? 'Lifetime' : `Pro × ${inv.months}mo`;
|
||||
%>
|
||||
<div class="flex items-center justify-between p-3 rounded-lg bg-surface border border-border-subtle hover:border-border-strong transition-colors">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="w-2 h-2 rounded-full bg-<%= statusColor %>-500 <%= inv.status !== 'paid' ? 'animate-pulse' : '' %>"></span>
|
||||
<div>
|
||||
<span class="text-sm text-gray-200"><%= planLabel %></span>
|
||||
<span class="text-xs text-gray-600 ml-2">$<%= Number(inv.amount_usd).toFixed(2) %> · <%= inv.coin.toUpperCase() %></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="text-xs text-gray-500"><%= date %></span>
|
||||
<% if (inv.status === 'pending' || inv.status === 'underpaid' || inv.status === 'confirming') { %>
|
||||
<a href="/dashboard/checkout/<%= inv.id %>" class="text-xs text-blue-400 hover:text-blue-300">View</a>
|
||||
<% } else if (inv.status === 'paid' && inv.txid) { %>
|
||||
<span class="text-xs text-green-500/70">Paid</span>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
<% }) %>
|
||||
</div>
|
||||
</section>
|
||||
<% } %>
|
||||
|
||||
<!-- Account info -->
|
||||
<section class="card-static p-6">
|
||||
<h2 class="text-sm font-semibold text-gray-300 mb-4">Account</h2>
|
||||
@@ -184,6 +151,40 @@
|
||||
</section>
|
||||
<% } %>
|
||||
|
||||
<!-- Invoices -->
|
||||
<% if (it.invoices && it.invoices.length > 0) { %>
|
||||
<section class="card-static p-6">
|
||||
<h2 class="text-sm font-semibold text-gray-300 mb-4">Invoices</h2>
|
||||
<div class="space-y-2">
|
||||
<% it.invoices.forEach(function(inv) {
|
||||
const statusColors = { paid: 'green', confirming: 'blue', pending: 'yellow', underpaid: 'orange' };
|
||||
const statusColor = statusColors[inv.status] || 'gray';
|
||||
const date = new Date(inv.created_at).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' });
|
||||
const planLabel = inv.plan === 'lifetime' ? 'Lifetime' : `Pro × ${inv.months}mo`;
|
||||
%>
|
||||
<div class="flex items-center justify-between p-3 rounded-lg bg-surface border border-border-subtle hover:border-border-strong transition-colors">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="w-2 h-2 rounded-full bg-<%= statusColor %>-500 <%= inv.status !== 'paid' ? 'animate-pulse' : '' %>"></span>
|
||||
<div>
|
||||
<span class="text-sm text-gray-200"><%= planLabel %></span>
|
||||
<span class="text-xs text-gray-600 ml-2">$<%= Number(inv.amount_usd).toFixed(2) %> · <%= inv.coin.toUpperCase() %></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="text-xs text-gray-500"><%= date %></span>
|
||||
<% if (inv.status === 'pending' || inv.status === 'underpaid' || inv.status === 'confirming') { %>
|
||||
<a href="/dashboard/checkout/<%= inv.id %>" class="text-xs text-blue-400 hover:text-blue-300">View</a>
|
||||
<% } else if (inv.status === 'paid') { %>
|
||||
<a href="/dashboard/checkout/<%= inv.id %>/receipt" target="_blank" class="text-xs text-gray-500 hover:text-gray-400 transition-colors">Receipt</a>
|
||||
<span class="text-xs text-green-500/70">Paid</span>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
<% }) %>
|
||||
</div>
|
||||
</section>
|
||||
<% } %>
|
||||
|
||||
</main>
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user