-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.html
More file actions
101 lines (99 loc) · 5.12 KB
/
Copy pathindex.html
File metadata and controls
101 lines (99 loc) · 5.12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>CryptoPaywall - pay on-chain, download instantly</title>
<style>
:root { color-scheme: light dark; }
* { box-sizing: border-box; }
body { font-family: -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; margin: 0; line-height: 1.6; }
main { max-width: 780px; margin: 0 auto; padding: 2rem 1.25rem 4rem; }
h1 { margin-bottom: .3rem; }
.tag { color:#666; margin-top: 0; }
@media (prefers-color-scheme: dark) { .tag { color:#999; } }
.pay { border:2px solid #222; border-radius:14px; padding:1.5rem; margin:1.5rem 0; display:flex; gap:1.5rem; flex-wrap:wrap; }
@media (prefers-color-scheme: dark) { .pay { border-color:#888; } }
.pay img.qr { width:170px; height:170px; border-radius:8px; background:#fff; padding:6px; }
.addr { font-family: ui-monospace, Consolas, monospace; word-break: break-all; background:#f4f4f4; padding:.5rem; border-radius:6px; display:block; font-size:.85rem; }
@media (prefers-color-scheme: dark) { .addr { background:#1e1e1e; } }
button, a.btn { display:inline-block; background:#111; color:#fff; padding:.65rem 1.15rem; border-radius:8px; text-decoration:none; font-weight:600; cursor:pointer; border:none; font-size:1rem; }
button[disabled] { opacity:.5; cursor:wait; }
@media (prefers-color-scheme: dark) { button, a.btn { background:#eee; color:#111; } }
input[type=text] { width:100%; padding:.65rem .75rem; border-radius:8px; border:1px solid #bbb; font-family:ui-monospace,Consolas,monospace; }
.msg { min-height:1.4em; font-size:.92rem; margin:.5rem 0; }
.ok { color:#0a7d33; } .err { color:#b3261e; }
.hidden { display:none; }
.small { font-size:.85rem; color:#667; }
</style>
</head>
<body>
<main>
<h1 id="pname">Loading...</h1>
<p class="tag">Pay on-chain, get the file instantly. No account, no email, no middleman.</p>
<div class="pay">
<img class="qr" id="qr" alt="Payment QR code" loading="lazy">
<div style="flex:1;min-width:270px">
<strong id="priceline"></strong>
<p style="margin:.4rem 0">Send to:</p>
<span class="addr" id="addr"></span>
<button style="margin-top:.6rem" id="copy">Copy address</button>
<div style="margin-top:1.2rem">
<label for="tx"><strong>After paying:</strong> paste your transaction hash</label>
<input type="text" id="tx" placeholder="0x..." spellcheck="false" autocomplete="off">
<div style="display:flex;gap:.6rem;margin-top:.6rem;flex-wrap:wrap">
<button id="unlock">Unlock download</button>
<a class="btn hidden" id="dl" href="#" download>Download</a>
</div>
<div class="msg" id="msg"></div>
</div>
<p class="small">Payments are verified directly against the blockchain and go straight to the seller's wallet.</p>
</div>
</div>
<p class="small">Powered by <a href="https://github.com/ShavitR/cryptopaywall">CryptoPaywall</a> - self-hosted, open source, MIT.</p>
</main>
<script>
const $ = (id) => document.getElementById(id);
let cfg;
fetch("/api/config").then((r) => r.json()).then((c) => {
cfg = c;
document.title = c.productName + " - instant crypto checkout";
$("pname").textContent = c.productName;
$("priceline").innerHTML = c.priceLabel ? `Suggested price <strong>${c.priceLabel}</strong>.` : "Pay what you want.";
$("addr").textContent = c.merchantAddress;
$("qr").src = "https://api.qrserver.com/v1/create-qr-code/?size=340x340&margin=8&data=ethereum:" + c.merchantAddress;
});
$("copy").addEventListener("click", async () => {
try { await navigator.clipboard.writeText($("addr").textContent.trim()); $("copy").textContent = "Copied!"; setTimeout(() => ($("copy").textContent = "Copy address"), 1500); } catch {}
});
$("unlock").addEventListener("click", async () => {
const tx = $("tx").value.trim();
const msg = $("msg");
msg.className = "msg"; msg.textContent = "";
if (!/^0x[0-9a-fA-F]{64}$/.test(tx)) {
msg.className = "msg err"; msg.textContent = "That doesn't look like a transaction hash (0x + 66 hex chars).";
return;
}
$("unlock").disabled = true; $("unlock").textContent = "Checking chain...";
try {
const r = await fetch("/api/unlock", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ txHash: tx }) });
const j = await r.json();
if (r.ok && j.ok) {
const amount = j.amount.toFixed(6).replace(/\.?0+$/, "");
msg.className = "msg ok";
msg.textContent = `Payment verified on-chain (${amount} tokens). Download unlocked.`;
$("dl").href = "/api/download?t=" + encodeURIComponent(j.token);
$("dl").classList.remove("hidden");
$("unlock").classList.add("hidden");
} else {
msg.className = "msg err"; msg.textContent = j.error || "Verification failed.";
}
} catch (e) {
msg.className = "msg err"; msg.textContent = "Network error - try again.";
} finally {
$("unlock").disabled = false; if (!$("dl").classList.contains("hidden")) $("unlock").textContent = "Unlock download";
}
});
</script>
</body>
</html>