landings: clickable blue payload links under every QR (webextension)

Show the exact encoded URI (taler:// / https / payto://) as a blue
monospace link under each QR so wallet webextensions and desktop can
open it. Stop stripping :443 in the bank withdraw QR path. Shop pay
and exchange withdraw-exchange included.
This commit is contained in:
Hernâni Marques 2026-07-10 23:05:00 +02:00
parent eb640da64e
commit 2fb060f79a
No known key found for this signature in database
6 changed files with 134 additions and 15 deletions

View file

@ -363,22 +363,27 @@
text-align: center; text-align: center;
max-width: 100%; max-width: 100%;
} }
/* Blue payload link under each QR (encoded content) */
a.meta-uri { a.meta-uri {
display: block; display: block;
font-size: 0.72rem; font-size: 0.68rem;
color: #5eead4; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
color: #7eb6ff;
word-break: break-all; word-break: break-all;
margin: 0.35rem 0 0; margin: 0.4rem auto 0;
text-align: center; text-align: center;
max-width: 22rem; max-width: 22rem;
text-decoration: none; text-decoration: none;
border-bottom: 1px solid rgba(94, 234, 212, 0.35); border-bottom: 1px solid rgba(126, 182, 255, 0.4);
line-height: 1.35; line-height: 1.35;
cursor: pointer; cursor: pointer;
} }
a.meta-uri:hover { a.meta-uri:hover {
color: #99f6e4; color: #b8d6ff;
border-bottom-color: #5eead4; border-bottom-color: #7eb6ff;
}
.qr-taler-wrap a.meta-uri {
margin-top: 0.45rem;
} }
a.meta-uri.disabled, a.meta-uri.disabled,
a.meta-uri[href="#"] { a.meta-uri[href="#"] {
@ -1250,11 +1255,11 @@ tw balance</pre>
<img class="goa-pay-taler-qr__logo" id="qr-withdraw-demo-logo" alt="" /> <img class="goa-pay-taler-qr__logo" id="qr-withdraw-demo-logo" alt="" />
</div> </div>
<div class="qr-label">Scan in wallet</div> <div class="qr-label">Scan in wallet</div>
<a class="meta-uri" id="withdraw-demo-meta" href="#">Preparing withdraw…</a>
</div> </div>
<a class="cta teal" id="open-withdraw-demo" href="#"> <a class="cta teal" id="open-withdraw-demo" href="#">
Open in wallet → Open in wallet →
</a> </a>
<a class="meta-uri" id="withdraw-demo-meta" href="#">Preparing withdraw…</a>
<button type="button" class="gen-own-acct" id="generate-own-account"> <button type="button" class="gen-own-acct" id="generate-own-account">
Generate own account Generate own account
</button> </button>
@ -1331,6 +1336,10 @@ tw balance</pre>
<dl class="own-acct-dl"> <dl class="own-acct-dl">
<div><dt>Username</dt><dd><code id="auto-account-user"></code></dd></div> <div><dt>Username</dt><dd><code id="auto-account-user"></code></dd></div>
<div><dt>Password</dt><dd><code id="auto-account-pass"></code></dd></div> <div><dt>Password</dt><dd><code id="auto-account-pass"></code></dd></div>
<div>
<dt>payto</dt>
<dd><a class="meta-uri" id="auto-account-payto" href="#" style="margin:0;text-align:left;max-width:100%"></a></dd>
</div>
</dl> </dl>
<p class="meta" id="auto-account-status" style="margin:0.5rem 0 0"></p> <p class="meta" id="auto-account-status" style="margin:0.5rem 0 0"></p>
<div class="more-qr" id="auto-account-qr-block" style="margin-top:0.85rem" hidden> <div class="more-qr" id="auto-account-qr-block" style="margin-top:0.85rem" hidden>
@ -1340,6 +1349,7 @@ tw balance</pre>
<img class="goa-pay-taler-qr__logo" id="qr-auto-acct-logo" alt="" /> <img class="goa-pay-taler-qr__logo" id="qr-auto-acct-logo" alt="" />
</div> </div>
<div class="qr-label" id="qr-auto-acct-label">Your account · bank UI login</div> <div class="qr-label" id="qr-auto-acct-label">Your account · bank UI login</div>
<a class="meta-uri" id="qr-auto-acct-payload" href="https://bank.hacktivism.ch/webui/" target="_blank" rel="noopener noreferrer">https://bank.hacktivism.ch/webui/</a>
</div> </div>
</div> </div>
</div> </div>
@ -1350,10 +1360,11 @@ tw balance</pre>
<img class="goa-pay-taler-qr__logo" id="qr-webui-logo" alt="" /> <img class="goa-pay-taler-qr__logo" id="qr-webui-logo" alt="" />
</div> </div>
<div class="qr-label">Bank UI · open site</div> <div class="qr-label">Bank UI · open site</div>
<a class="meta-uri" id="qr-webui-payload" href="https://bank.hacktivism.ch/webui/" target="_blank" rel="noopener noreferrer">https://bank.hacktivism.ch/webui/</a>
</div> </div>
</div> </div>
<div class="more-actions"> <div class="more-actions">
<a class="cta-sec" href="/webui/" id="auto-account-webui">Open bank UI →</a> <a class="cta-sec" href="https://bank.hacktivism.ch/webui/" id="auto-account-webui">Open bank UI →</a>
<button type="button" class="cta-sec" id="auto-account-again" hidden style="margin-top:0.45rem"> <button type="button" class="cta-sec" id="auto-account-again" hidden style="margin-top:0.45rem">
Generate another account Generate another account
</button> </button>
@ -1569,9 +1580,8 @@ tw balance</pre>
} }
function setDemoWithdraw(uri, amount) { function setDemoWithdraw(uri, amount) {
uri = String(uri || "") /* Keep host:port (…:443) — required for taler-integration withdraw */
.replace(/:443\//g, "/") uri = String(uri || "").trim();
.replace(/:443\?/g, "?");
var open = document.getElementById("open-withdraw-demo"); var open = document.getElementById("open-withdraw-demo");
var meta = document.getElementById("withdraw-demo-meta"); var meta = document.getElementById("withdraw-demo-meta");
if (open) { if (open) {
@ -1752,8 +1762,33 @@ tw balance</pre>
if (box) box.hidden = false; if (box) box.hidden = false;
var u = document.getElementById("auto-account-user"); var u = document.getElementById("auto-account-user");
var p = document.getElementById("auto-account-pass"); var p = document.getElementById("auto-account-pass");
var payA = document.getElementById("auto-account-payto");
var webuiA = document.getElementById("auto-account-webui");
if (u) u.textContent = d.username || d.name || d.display_name || "—"; if (u) u.textContent = d.username || d.name || d.display_name || "—";
if (p) p.textContent = d.password || "—"; if (p) p.textContent = d.password || "—";
/* Absolute HTTPS bank UI (never relative under <base href=/intro/>) */
var webui = d.webui || d.account_url || WEBUI;
if (!/^https?:\/\//i.test(webui)) {
webui = WEBUI;
}
if (webuiA) {
webuiA.href = webui;
webuiA.setAttribute("target", "_blank");
webuiA.setAttribute("rel", "noopener noreferrer");
}
/* payto must include host:port (e.g. bank.hacktivism.ch:443) */
var payto = d.payto_uri || "";
if (payA) {
if (payto && payto.indexOf("payto://") === 0) {
payA.href = payto;
payA.textContent = payto;
payA.classList.remove("disabled");
} else {
payA.href = "#";
payA.textContent = "—";
payA.classList.add("disabled");
}
}
if (st) { if (st) {
st.textContent = st.textContent =
"Account created for you · " + "Account created for you · " +
@ -1763,12 +1798,18 @@ tw balance</pre>
/* Fancy QR_Taler: encode bank UI URL (login with credentials above) */ /* Fancy QR_Taler: encode bank UI URL (login with credentials above) */
var qrBlock = document.getElementById("auto-account-qr-block"); var qrBlock = document.getElementById("auto-account-qr-block");
var qrLabel = document.getElementById("qr-auto-acct-label"); var qrLabel = document.getElementById("qr-auto-acct-label");
var webui = d.webui || WEBUI;
if (qrBlock) qrBlock.hidden = false; if (qrBlock) qrBlock.hidden = false;
if (qrLabel) { if (qrLabel) {
qrLabel.textContent = qrLabel.textContent =
"QR_Taler · open bank UI · login as " + (d.username || "you"); "QR_Taler · open bank UI · login as " + (d.username || "you");
} }
var autoPayload = document.getElementById("qr-auto-acct-payload");
if (autoPayload) {
autoPayload.href = webui;
autoPayload.textContent = webui;
autoPayload.setAttribute("target", "_blank");
autoPayload.setAttribute("rel", "noopener noreferrer");
}
makeQr( makeQr(
document.getElementById("qr-auto-acct"), document.getElementById("qr-auto-acct"),
webui, webui,

View file

@ -138,6 +138,25 @@ button.shop-item {
background: #fff; background: #fff;
border-radius: 4px; border-radius: 4px;
} }
/* Full encoded payload under QR — clickable (wallet / webextension) */
.goa-pay-payload {
display: block;
margin: 0.45rem 0 0.55rem;
padding: 0 0.25rem;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
font-size: 0.68rem;
line-height: 1.35;
color: #7eb6ff;
word-break: break-all;
text-align: center;
text-decoration: none;
border-bottom: 1px solid rgba(126, 182, 255, 0.4);
cursor: pointer;
}
.goa-pay-payload:hover {
color: #b8d6ff;
border-bottom-color: #7eb6ff;
}
/* Settlement: collapsed by default, expand on click */ /* Settlement: collapsed by default, expand on click */
.goa-pay-settle { .goa-pay-settle {

View file

@ -69,6 +69,7 @@
' <div class="goa-pay-taler-qr__inner" id="goa-pay-qr-pay"></div>' + ' <div class="goa-pay-taler-qr__inner" id="goa-pay-qr-pay"></div>' +
' <img class="goa-pay-taler-qr__logo" id="goa-pay-qr-pay-logo" alt="" />' + ' <img class="goa-pay-taler-qr__logo" id="goa-pay-qr-pay-logo" alt="" />' +
" </div>" + " </div>" +
' <a class="goa-pay-payload" id="goa-pay-qr-payload" href="#">…</a>' +
' <a class="goa-pay-cta" id="goa-pay-open" href="#">Open payment link →</a>' + ' <a class="goa-pay-cta" id="goa-pay-open" href="#">Open payment link →</a>' +
' <p class="goa-pay-line">' + ' <p class="goa-pay-line">' +
' <a class="goa-pay-link" id="goa-pay-uri" href="#">taler:// URI</a>' + ' <a class="goa-pay-link" id="goa-pay-uri" href="#">taler:// URI</a>' +
@ -90,6 +91,7 @@
' <div class="goa-pay-payto-qr__caption">Settlement only</div>' + ' <div class="goa-pay-payto-qr__caption">Settlement only</div>' +
' <div class="goa-pay-payto-qr__inner" id="goa-pay-qr-payto"></div>' + ' <div class="goa-pay-payto-qr__inner" id="goa-pay-qr-payto"></div>' +
" </div>" + " </div>" +
' <a class="goa-pay-payload" id="goa-pay-payto-payload" href="#">…</a>' +
' <p class="goa-pay-line">' + ' <p class="goa-pay-line">' +
' <a class="goa-pay-link" id="goa-pay-payto" href="#">payto:// URI</a>' + ' <a class="goa-pay-link" id="goa-pay-payto" href="#">payto:// URI</a>' +
' <span class="goa-pay-sep">·</span>' + ' <span class="goa-pay-sep">·</span>' +
@ -127,6 +129,11 @@
if (open) { if (open) {
// paint payto QR when first expanded // paint payto QR when first expanded
renderQr(document.getElementById("goa-pay-qr-payto"), SHOP_PAYTO, 140); renderQr(document.getElementById("goa-pay-qr-payto"), SHOP_PAYTO, 140);
var pp = document.getElementById("goa-pay-payto-payload");
if (pp) {
pp.href = SHOP_PAYTO;
pp.textContent = SHOP_PAYTO;
}
} }
}; };
document.addEventListener("keydown", function (e) { document.addEventListener("keydown", function (e) {
@ -335,11 +342,17 @@
// Open first so layout exists, then paint wallet QR only // Open first so layout exists, then paint wallet QR only
m.hidden = false; m.hidden = false;
m.classList.add("open"); m.classList.add("open");
var tpl = payTemplateUri(productId);
renderQr( renderQr(
document.getElementById("goa-pay-qr-pay"), document.getElementById("goa-pay-qr-pay"),
payTemplateUri(productId), tpl,
220 220
); );
var payload = document.getElementById("goa-pay-qr-payload");
if (payload) {
payload.href = tpl;
payload.textContent = tpl;
}
createPayUri(productId) createPayUri(productId)
.then(function (info) { .then(function (info) {
@ -355,6 +368,10 @@
uh.textContent = "HTTPS"; uh.textContent = "HTTPS";
// Live unpaid taler://pay (fresh order each open) // Live unpaid taler://pay (fresh order each open)
renderQr(document.getElementById("goa-pay-qr-pay"), pay, 220); renderQr(document.getElementById("goa-pay-qr-pay"), pay, 220);
if (payload) {
payload.href = pay;
payload.textContent = pay;
}
status.textContent = "Ready — scan in wallet"; status.textContent = "Ready — scan in wallet";
}) })
.catch(function (err) { .catch(function (err) {

View file

@ -416,6 +416,9 @@ sudo apt-get install -y taler-wallet-cli</pre>
<div class="qr-box"> <div class="qr-box">
<div id="qr-exchange"></div> <div id="qr-exchange"></div>
<div class="qr-label">Scan in wallet</div> <div class="qr-label">Scan in wallet</div>
<a class="meta-uri" id="qr-exchange-payload"
href="taler://withdraw-exchange/exchange.hacktivism.ch/"
style="display:block;margin:.45rem auto 0;max-width:22rem;font-size:.68rem;font-family:ui-monospace,Menlo,monospace;color:#0042b3;word-break:break-all;text-decoration:none;border-bottom:1px solid rgba(0,66,179,.35)">taler://withdraw-exchange/exchange.hacktivism.ch/</a>
</div> </div>
<a class="cta teal" href="taler://withdraw-exchange/exchange.hacktivism.ch/">Open in wallet →</a> <a class="cta teal" href="taler://withdraw-exchange/exchange.hacktivism.ch/">Open in wallet →</a>
<p class="meta">taler://withdraw-exchange/exchange.hacktivism.ch/</p> <p class="meta">taler://withdraw-exchange/exchange.hacktivism.ch/</p>
@ -565,9 +568,12 @@ sudo apt-get install -y taler-wallet-cli</pre>
if (foot) foot.innerHTML = 'Stats offline · <a href="https://bank.hacktivism.ch/intro/" style="color:#e8c878">bank.hacktivism.ch</a>'; if (foot) foot.innerHTML = 'Stats offline · <a href="https://bank.hacktivism.ch/intro/" style="color:#e8c878">bank.hacktivism.ch</a>';
}); });
var el = document.getElementById("qr-exchange"); var el = document.getElementById("qr-exchange");
var exUri = "taler://withdraw-exchange/exchange.hacktivism.ch/";
var exPay = document.getElementById("qr-exchange-payload");
if (exPay) { exPay.href = exUri; exPay.textContent = exUri; }
if (el && typeof QRCode !== "undefined") { if (el && typeof QRCode !== "undefined") {
new QRCode(el, { new QRCode(el, {
text: "taler://withdraw-exchange/exchange.hacktivism.ch/", text: exUri,
width: 220, height: 220, correctLevel: QRCode.CorrectLevel.M width: 220, height: 220, correctLevel: QRCode.CorrectLevel.M
}); });
} }

View file

@ -138,6 +138,25 @@ button.shop-item {
background: #fff; background: #fff;
border-radius: 4px; border-radius: 4px;
} }
/* Full encoded payload under QR — clickable (wallet / webextension) */
.goa-pay-payload {
display: block;
margin: 0.45rem 0 0.55rem;
padding: 0 0.25rem;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
font-size: 0.68rem;
line-height: 1.35;
color: #7eb6ff;
word-break: break-all;
text-align: center;
text-decoration: none;
border-bottom: 1px solid rgba(126, 182, 255, 0.4);
cursor: pointer;
}
.goa-pay-payload:hover {
color: #b8d6ff;
border-bottom-color: #7eb6ff;
}
/* Settlement: collapsed by default, expand on click */ /* Settlement: collapsed by default, expand on click */
.goa-pay-settle { .goa-pay-settle {

View file

@ -69,6 +69,7 @@
' <div class="goa-pay-taler-qr__inner" id="goa-pay-qr-pay"></div>' + ' <div class="goa-pay-taler-qr__inner" id="goa-pay-qr-pay"></div>' +
' <img class="goa-pay-taler-qr__logo" id="goa-pay-qr-pay-logo" alt="" />' + ' <img class="goa-pay-taler-qr__logo" id="goa-pay-qr-pay-logo" alt="" />' +
" </div>" + " </div>" +
' <a class="goa-pay-payload" id="goa-pay-qr-payload" href="#">…</a>' +
' <a class="goa-pay-cta" id="goa-pay-open" href="#">Open payment link →</a>' + ' <a class="goa-pay-cta" id="goa-pay-open" href="#">Open payment link →</a>' +
' <p class="goa-pay-line">' + ' <p class="goa-pay-line">' +
' <a class="goa-pay-link" id="goa-pay-uri" href="#">taler:// URI</a>' + ' <a class="goa-pay-link" id="goa-pay-uri" href="#">taler:// URI</a>' +
@ -90,6 +91,7 @@
' <div class="goa-pay-payto-qr__caption">Settlement only</div>' + ' <div class="goa-pay-payto-qr__caption">Settlement only</div>' +
' <div class="goa-pay-payto-qr__inner" id="goa-pay-qr-payto"></div>' + ' <div class="goa-pay-payto-qr__inner" id="goa-pay-qr-payto"></div>' +
" </div>" + " </div>" +
' <a class="goa-pay-payload" id="goa-pay-payto-payload" href="#">…</a>' +
' <p class="goa-pay-line">' + ' <p class="goa-pay-line">' +
' <a class="goa-pay-link" id="goa-pay-payto" href="#">payto:// URI</a>' + ' <a class="goa-pay-link" id="goa-pay-payto" href="#">payto:// URI</a>' +
' <span class="goa-pay-sep">·</span>' + ' <span class="goa-pay-sep">·</span>' +
@ -127,6 +129,11 @@
if (open) { if (open) {
// paint payto QR when first expanded // paint payto QR when first expanded
renderQr(document.getElementById("goa-pay-qr-payto"), SHOP_PAYTO, 140); renderQr(document.getElementById("goa-pay-qr-payto"), SHOP_PAYTO, 140);
var pp = document.getElementById("goa-pay-payto-payload");
if (pp) {
pp.href = SHOP_PAYTO;
pp.textContent = SHOP_PAYTO;
}
} }
}; };
document.addEventListener("keydown", function (e) { document.addEventListener("keydown", function (e) {
@ -335,11 +342,17 @@
// Open first so layout exists, then paint wallet QR only // Open first so layout exists, then paint wallet QR only
m.hidden = false; m.hidden = false;
m.classList.add("open"); m.classList.add("open");
var tpl = payTemplateUri(productId);
renderQr( renderQr(
document.getElementById("goa-pay-qr-pay"), document.getElementById("goa-pay-qr-pay"),
payTemplateUri(productId), tpl,
220 220
); );
var payload = document.getElementById("goa-pay-qr-payload");
if (payload) {
payload.href = tpl;
payload.textContent = tpl;
}
createPayUri(productId) createPayUri(productId)
.then(function (info) { .then(function (info) {
@ -355,6 +368,10 @@
uh.textContent = "HTTPS"; uh.textContent = "HTTPS";
// Live unpaid taler://pay (fresh order each open) // Live unpaid taler://pay (fresh order each open)
renderQr(document.getElementById("goa-pay-qr-pay"), pay, 220); renderQr(document.getElementById("goa-pay-qr-pay"), pay, 220);
if (payload) {
payload.href = pay;
payload.textContent = pay;
}
status.textContent = "Ready — scan in wallet"; status.textContent = "Ready — scan in wallet";
}) })
.catch(function (err) { .catch(function (err) {