Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/monetize

crates/monetize-site-buy/assets/paid.js

// **Back from the vendor's checkout** (`/paid/{reference}`, `/cancel/{reference}`, A12, owner
// 2026-10-23; ONE copy for every product site, TASKS-4-v2 T16, A28 M11): gunnar-ui-web's
// PaidReturn and CancelledReturn panes and their FollowOrder (`pages/subscription.rs`,
// `purchase.rs`: the steps, `order_title` / `order_detail` by what the order does,
// `change_line`, `pay_area`, "Cancel order"), their words as Gunnar wrote them. It follows the
// order through the site's own buy doors (the shared monetize-site-buy relay):
// `GET /api/account/subscription/status?reference=` until it is over, and
// `POST /api/account/subscription/cancel` for "Cancel order" (A16), signed in as this tab's
// person (the site's cookie, or the bearer its login page keeps). The site's page draws the
// look and carries the elements this script fills (`monetize_site_buy::assets::PAID_IDS`).
const BUY = { product: '@@PRODUCT@@', bearerKey: '@@BEARER_KEY@@', again: '@@AGAIN@@', account: '@@ACCOUNT@@', login: '@@LOGIN@@' };
const SLOW_NOTICE = 'Usually within a minute. If Stripe\'s notice is slow, our check picks the payment up within 5 minutes — you can leave this page; the room is added either way.';
const CANCELLED_TITLE = 'Payment cancelled';
const CANCELLED_DETAIL = 'Nothing was charged. Your order is kept for a while: try again below, or cancel it.';
const STEPS = ['Choose', 'Pay at Stripe', 'Room added'];
const RESUME_LABEL = 'Resume payment';
const CREATING_LINK = 'Creating your payment link…';
const NO_LINK = 'The payment link could not be created. Nothing has been charged — cancel the order and choose again, or contact support if it happens again.';
// How many follow-ups a pending order may go without a link before the page says it failed.
const LINK_PATIENCE = 20;
const POLL_MS = 3000;

const $ = id => document.getElementById(id);
const headline = $('headline');
const detail = $('detail');
const progress = $('progress');
const foot = $('foot');

function bearer() {
  try { return sessionStorage.getItem(BUY.bearerKey) || ''; } catch (e) { return ''; }
}
function auth() { const t = bearer(); return t ? { Authorization: 'Bearer ' + t } : {}; }
function say(head, text, cls) { headline.textContent = head; detail.textContent = text; detail.className = cls || ''; detail.hidden = !text; }
function link(href, words) { const a = document.createElement('a'); a.href = href; a.textContent = words; return a; }
// gunnar-ui-web `fmt::bytes`: one decimal from KiB up.
function bytes(n) {
  const units = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB'];
  let v = Number(n || 0), i = 0;
  if (v < 1024) return v + ' B';
  while (v >= 1024 && i < units.length - 1) { v /= 1024; i++; }
  return v.toFixed(1) + ' ' + units[i];
}
// gunnar-ui-web `fmt::date`: the fact's day, YYYY-MM-DD (UTC).
function date(ms) { return new Date(Number(ms)).toISOString().slice(0, 10); }

// The reference: the rest of the path (monetize's may carry slashes), never empty segments.
const PATH = location.pathname;
const CANCELLED = PATH.startsWith('/cancel/');
const REFERENCE = (() => {
  const rest = PATH.replace(/^\/(paid|cancel)\//, '').replace(/\/+$/, '');
  return (rest && !rest.split('/').some(s => s === '')) ? decodeURIComponent(rest) : '';
})();

// gunnar-ui-web `Phase`, from the attempt's phase word and state.
function phaseOf(a) {
  const p = a.phase || '';
  if (p === 'waiting_for_payment') return 'waiting';
  if (p === 'paid_adding') return 'adding';
  if (p === 'done') return 'done';
  if (p === 'cancelled' || p === 'declined' || p === 'refunded') return 'cancelled';
  // gunnar-ui-web `phase`: monetize's ladder word, and whether the ledger holds a capture.
  if (a.state === 'pending') return a.captured ? 'adding' : 'waiting';
  if (a.state === 'paid') return 'done';
  if (a.state === 'declined' || a.state === 'released' || a.state === 'released_with_orphans') return 'cancelled';
  return 'unknown';
}
// `kind_of`: what the order does to the account, by monetize's verb.
function kindOf(action) {
  return { prolong: 'prolong', renew: 'prolong', upgrade: 'upgrade', bump: 'bump' }[action || ''] || 'room';
}
// `steps(kind)`.
function stepsOf(kind) {
  const last = { prolong: 'Prolonged', upgrade: 'Upgraded', bump: 'Raised' }[kind];
  return last ? [STEPS[0], STEPS[1], last] : STEPS;
}
// `step_of`.
function stepOf(phase) { return { waiting: 1, adding: 2, done: 2 }[phase] || 0; }
// `now_holds`: the room held after a granted order, from the server's `after`.
function nowHolds(a) {
  const after = a.after || {};
  const k = Object.keys(after)[0];
  return k === undefined ? undefined : after[k];
}
// `phase_title`.
function phaseTitle(phase, now) {
  return {
    waiting: 'Waiting for payment',
    adding: 'Paid – adding room',
    done: (now !== undefined) ? 'Done – you now have ' + bytes(now) + ' storage' : 'Done – the room is yours',
    cancelled: 'Cancelled or expired',
    unknown: 'We could not find this order',
  }[phase];
}
// `phase_detail`.
function phaseDetail(phase) {
  return {
    waiting: 'Nothing has been charged yet. Pay with Stripe to continue, or cancel the order.',
    adding: SLOW_NOTICE,
    done: 'The extra room is available now.',
    cancelled: 'Nothing was charged. You can choose again whenever you like.',
    unknown: 'If you have just paid, give it a minute and reload this page.',
  }[phase];
}
function periodWords(period) { return { year: '12 months', month: '1 month' }[period]; }
// `order_title`.
function orderTitle(phase, kind, now, until, period) {
  const d = until ? date(until) : undefined;
  if (kind === 'prolong' && phase === 'adding') return 'Paid – prolonging';
  if (phase === 'done' && kind === 'prolong') return d ? 'Prolonged until ' + d : 'Prolonged';
  if (phase === 'done' && kind === 'upgrade') {
    const w = periodWords(period);
    if (w && d) return 'Upgraded to ' + w + ' until ' + d;
    if (w) return 'Upgraded to ' + w;
    return d ? 'Upgraded until ' + d : 'Upgraded';
  }
  if (phase === 'done' && kind === 'bump') return now !== undefined ? 'Raised to ' + bytes(now) + ' storage' : 'Raised';
  return phaseTitle(phase, now);
}
// `order_detail`.
function orderDetail(phase, kind, now, until) {
  if (phase === 'done' && kind === 'prolong') return '';
  if (phase === 'done' && kind === 'upgrade') return now !== undefined ? 'You now have ' + bytes(now) + ' storage.' : '';
  if (phase === 'done' && kind === 'bump') return until ? 'Your plan runs until ' + date(until) + ', as before.' : '';
  return phaseDetail(phase);
}
// `change_line`: "You have X storage → Y after payment", only when both halves are sent.
function changeLine(a) {
  const after = a.after || {}, before = a.before || {};
  const k = Object.keys(after)[0];
  if (k === undefined) return '';
  return 'You have ' + bytes(before[k] || 0) + ' storage → ' + bytes(after[k]) + ' after payment';
}
// `pay_url`: a link the browser may be sent to pay at: `https://`, a host, no credentials,
// nothing but printable ASCII (the server's own rule, kept here too so the page never trusts a
// link it was handed: no `javascript:`, no plain http, no `//host`).
function payUrl(url) {
  if (typeof url !== 'string' || url.length > 4096 || !/^[\x21-\x7e]*$/.test(url)) return false;
  if (!url.startsWith('https://')) return false;
  const authority = url.slice('https://'.length).split(/[\/?#]/)[0];
  const colon = authority.lastIndexOf(':');
  const host = colon < 0 ? authority : authority.slice(0, colon);
  return host !== '' && !authority.includes('@');
}
// `pay_area`: never an order waiting for payment with no way to pay.
function payArea(phase, url, asks) {
  if (phase !== 'waiting') return 'nothing';
  if (payUrl(url)) return 'button';
  if (typeof url === 'string' && url.trim() !== '') return 'failed';
  return asks < LINK_PATIENCE ? 'creating' : 'failed';
}

let asks = 0;
let cancelling = false;
let over = false;

function steps(kind, phase) {
  const ol = $('steps');
  ol.textContent = '';
  const at = stepOf(phase);
  stepsOf(kind).forEach((name, i) => {
    const li = document.createElement('li');
    li.textContent = (i + 1) + '. ' + name;
    if (i < at || (i === at && phase === 'done')) li.className = 'done';
    else if (i === at) li.className = 'current';
    ol.append(li);
  });
}

// `FollowOrder`: one attempt drawn.
function draw(a) {
  const phase = phaseOf(a);
  const kind = kindOf(a.action);
  const now = nowHolds(a);
  steps(kind, phase);
  const cls = phase === 'done' ? 'ok' : '';
  say(orderTitle(phase, kind, now, a.paid_until_unix_ms, a.period), orderDetail(phase, kind, now, a.paid_until_unix_ms), cls);
  const change = (phase === 'waiting' || phase === 'adding') ? changeLine(a) : '';
  $('change').textContent = change;
  $('change').hidden = !change;
  const room = (kind !== 'prolong' && a.room && a.room.words) || '';
  $('room').textContent = room;
  $('room').hidden = !room;
  const pay = $('pay');
  pay.textContent = '';
  const area = payArea(phase, a.url, asks);
  if (area === 'button') {
    const b = document.createElement('button');
    b.type = 'button'; b.className = 'primary'; b.textContent = RESUME_LABEL;
    b.onclick = () => location.assign(a.url);
    pay.append(b);
  } else if (area === 'creating') {
    const p = document.createElement('p'); p.className = 'muted'; p.textContent = CREATING_LINK; pay.append(p);
  } else if (area === 'failed') {
    const p = document.createElement('p'); p.className = 'bad'; p.textContent = NO_LINK; pay.append(p);
  }
  if (a.cancellable) {
    const c = document.createElement('button');
    c.type = 'button'; c.textContent = 'Cancel order'; c.disabled = cancelling;
    c.onclick = () => cancel(a.reference || REFERENCE);
    pay.append(c);
  }
  pay.hidden = !pay.childElementCount;
  over = phase === 'done' || phase === 'cancelled' || phase === 'unknown';
  progress.hidden = over;
}

function back(tryAgain) {
  foot.textContent = '';
  if (tryAgain) foot.append(link(BUY.again, 'Try again'), ' ');
  foot.append(link(BUY.account, 'Back to your account'));
  foot.hidden = false;
}

// The customer's own "Cancel order" (gunnar-ui-web FollowOrder `cancel`): monetize decides.
async function cancel(reference) {
  cancelling = true;
  $('fault').hidden = true;
  let res, a = null;
  try {
    res = await fetch('/api/account/subscription/cancel', { method: 'POST', headers: Object.assign({ 'content-type': 'application/json' }, auth()), body: JSON.stringify({ reference }) });
    a = await res.json().catch(() => null);
  } catch (e) {
    res = null;
  }
  cancelling = false;
  if (res && res.ok && a) {
    draw(a);
    return;
  }
  $('fault').textContent = (a && a.error) || BUY.product + ' is not answering. Try again in a moment.';
  $('fault').hidden = false;
  const c = [...$('pay').querySelectorAll('button')].find(b => b.textContent === 'Cancel order');
  if (c) c.disabled = false;
}

async function tick(first) {
  let res, a = null;
  try {
    res = await fetch('/api/account/subscription/status?reference=' + encodeURIComponent(REFERENCE), { headers: auth() });
    a = await res.json().catch(() => null);
  } catch (e) {
    $('fault').textContent = BUY.product + ' is not answering. Try again in a moment.';
    $('fault').hidden = false;
    return setTimeout(() => tick(first), POLL_MS);
  }
  if (res.status === 401) {
    progress.hidden = true;
    say('Sign in to follow this order', 'This order is your account\'s: sign in, and come back to this address.', 'err');
    foot.textContent = '';
    foot.append(link(BUY.login + '?next=' + encodeURIComponent(location.pathname), 'Sign in at ' + location.hostname));
    foot.hidden = false;
    return;
  }
  if (!res.ok || !a) {
    if (first && CANCELLED) {
      // An order this reader cannot see still gets the sentence and a way back to buy again.
      progress.hidden = true;
      $('steps').hidden = true;
      headline.hidden = true;
      detail.hidden = true;
      return back(true);
    }
    $('fault').textContent = (a && a.error) || BUY.product + ' is not answering yet. Try again in a moment.';
    $('fault').hidden = false;
    return setTimeout(() => tick(first), POLL_MS);
  }
  $('fault').hidden = true;
  asks += 1;
  if (!cancelling) draw(a);
  back(false);
  if (!over) setTimeout(() => tick(false), POLL_MS);
}

if (!REFERENCE) {
  progress.hidden = true;
  say('We could not find this order', 'If you have just paid, give it a minute and reload this page.', 'err');
  back(true);
} else {
  if (CANCELLED) $('cancelled').hidden = false;
  tick(true);
}