aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-09-05 19:40:59 -0700
committerAdam Soltys <[email protected]>2026-09-05 19:40:59 -0700
commitc99efea0ab7a724221d439bbc227058223dfe3c4 (patch)
treed8471552d806733bbdf861ddf22dc1482cd6e732
parentae85dc0afba70f40b333cf03c64abbbf4c8dfea0 (diff)
downloadcoinosv3-c99efea0ab7a724221d439bbc227058223dfe3c4.tar.gz
coinosv3-c99efea0ab7a724221d439bbc227058223dfe3c4.zip
Point of sale: show the terminal's tap-to-pay LNURL, link and QR
Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01VMi2Y1oKPZ3TeeFmLej47y
-rw-r--r--src/features/names.js20
-rw-r--r--src/i18n.js4
2 files changed, 22 insertions, 2 deletions
diff --git a/src/features/names.js b/src/features/names.js
index dc3426e..fa5bda1 100644
--- a/src/features/names.js
+++ b/src/features/names.js
@@ -15,6 +15,7 @@ import { resolveBip353, parsePaymentName, parseBip21 } from '../bip353.js';
import { decodeNoffer } from '../noffer.js';
import { nip98Header } from '../nip98.js';
import { qrSvg } from '../qr.js';
+import { bech32 } from '@scure/base';
import { isArkAddress } from './ark.js';
import { getNetwork } from '../api.js';
import { t } from '../i18n.js';
@@ -462,7 +463,14 @@ export function namesFeature(ctx) {
// token goes straight into the terminal's config at coinos.io/pos.
function posSection(st) {
const tok = ui.posToken;
- const body = () => ({ name: st.name, domain: st.domain || DOMAIN() });
+ const domain = st.domain || DOMAIN();
+ const body = () => ({ name: st.name, domain });
+ // The link the terminal broadcasts over NFC: the name's lnurlp on the
+ // registrar (it pins the sale just rung up), as a bech32 LNURL for
+ // wallet scanners and wrapped in coinos.io/ln/ for phones without one.
+ const payUrl = `${REGISTRAR}/lnurlp/${st.name}${domain === 'coinos.io' ? '' : `?domain=${encodeURIComponent(domain)}`}`;
+ const lnurl = bech32.encode('lnurl', bech32.toWords(new TextEncoder().encode(payUrl)), 2000).toUpperCase();
+ const link = `https://coinos.io/ln/${lnurl.toLowerCase()}`;
return h('div', { class: 'col', style: 'gap:6px' },
tok ? h('div', { class: 'addr-box break', style: 'font-size:10px' }, tok) : null,
h('div', { class: 'row gap6', style: 'flex-wrap:wrap' },
@@ -474,7 +482,15 @@ export function namesFeature(ctx) {
h('button', { class: 'btn-ghost btn-sm', onClick: async () => {
try { await post('/pos/token', 'DELETE', body()); ui.posToken = null; toast(t('namesPosRevoked')); render(); }
catch (e) { toast(e.message); }
- } }, t('namesPosRevoke'))));
+ } }, t('namesPosRevoke'))),
+ h('details', { class: 'small faint', style: 'margin-top:6px' },
+ h('summary', {}, t('namesPosLink')),
+ h('p', { style: 'margin:4px 0' }, t('namesPosLinkHow')),
+ h('div', { style: 'align-self:center;max-width:220px', html: qrSvg(lnurl, { ec: 'L', mode: 'Alphanumeric' }) }),
+ h('div', { class: 'addr-box break', style: 'font-size:10px' }, lnurl),
+ h('div', { class: 'row gap6', style: 'flex-wrap:wrap' },
+ copyBtn(lnurl, t('namesPosCopyLnurl')),
+ copyBtn(link, t('namesPosCopyLink')))));
}
// The Receive tab's default pane: your name, big and scannable.
diff --git a/src/i18n.js b/src/i18n.js
index 1b74c00..bede662 100644
--- a/src/i18n.js
+++ b/src/i18n.js
@@ -738,6 +738,10 @@ const STR = {
namesPosCopy: 'Copy token',
namesPosRevoke: 'Revoke tokens',
namesPosRevoked: 'POS tokens revoked',
+ namesPosLink: 'Tap-to-pay link',
+ namesPosLinkHow: 'What the terminal broadcasts over NFC. Any lightning wallet that scans it pays the sale the terminal just rang up, or asks for an amount. Print it as a sticker if you like.',
+ namesPosCopyLnurl: 'Copy LNURL',
+ namesPosCopyLink: 'Copy link',
namesDesc: 'Claim your payment address — like [email protected]. People can pay you just by typing it, no codes to copy or QR to scan.',
namesPlaceholder: 'username',
namesResolving: 'Looking up {name}…',