docs: new root as prior history lost (orphan + GC); ~215 commits not recoverable
250
configs/bank-landing/README.md
Normal file
|
|
@ -0,0 +1,250 @@
|
|||
# Bank landing (`bank.hacktivism.ch`)
|
||||
|
||||
English intro for **GOA exploration** (badge: **Intergalactic · GOA · no IBAN**):
|
||||
put coins in a GNU Taler wallet, optionally open your own bank account, and
|
||||
point merchants at `taler.hacktivism.ch`.
|
||||
|
||||
| Public URL | What |
|
||||
|------------|------|
|
||||
| `https://bank.hacktivism.ch/` · `/intro/` | Landing (nginx in bank container → host **9013**, Caddy) |
|
||||
| `https://bank.hacktivism.ch/intro/stats.json` | **Live stats** (written *inside* bank container) |
|
||||
| `https://bank.hacktivism.ch/intro/demo-withdraw.json` | Mint **shared-pool** (`explorer`) withdraw URI |
|
||||
| `https://bank.hacktivism.ch/intro/auto-account.json` | Auto-create **personal** bank user (credentials once; balance **GOA:0**) |
|
||||
| `https://bank.hacktivism.ch/webui/` | libeufin-bank SPA (register · login · withdraw) when libeufin is up on **9012** |
|
||||
| `https://exchange.hacktivism.ch/` | Exchange (ToS `/terms`, keys `/keys`) |
|
||||
| `https://taler.hacktivism.ch/` | Merchant backend |
|
||||
|
||||
## Page layout (top → bottom)
|
||||
|
||||
1. Hero (badge, short shared-account line)
|
||||
2. **Get the wallet** — Play / F-Droid / iOS / web hub + **Debian/Ubuntu · Command line** (apt) + **Unix / POSIX · from source** (build monorepo or run `.mjs`)
|
||||
3. **Withdraw GOA** — compact QR_Taler, open CTA, live pool balance note, generate-own-account link
|
||||
4. **GOA shop · samples** — always visible (2 products + merchant link)
|
||||
5. **How the shared account works** — collapsed fold (click to expand)
|
||||
6. **Optional · own bank account** — collapsed fold: auto-create account + credentials + QR_Taler for bank UI
|
||||
7. **GOA flow** stats
|
||||
8. **Performance**
|
||||
9. Footer
|
||||
|
||||
## User flow (documented on the page)
|
||||
|
||||
### Primary path (works without your own bank account)
|
||||
|
||||
1. **Install wallet** — Android (Play / F-Droid), iOS, Ubuntu Touch (via Waydroid + Android wallet), web (Chrome/Firefox/Opera on taler.net).
|
||||
2. **One QR / link** — live `taler://withdraw/…` from the **community-shared** bank pool (`explorer`).
|
||||
Minted via `GET /intro/demo-withdraw.json` (or fallback `withdraw.uri`).
|
||||
Auto-confirm finishes the bank side so coins reach the wallet.
|
||||
3. **Surprising balance note** — shows live `balance_explorer` from `stats.json`
|
||||
(“pool holds GOA:… (shared, but also yours)”).
|
||||
|
||||
QR encoding matches merchant shop popup (**QR_Taler**): animated `#0042B3` conic
|
||||
ring, off-DOM PNG, center `qr-logo.svg` (`shop-pay.css` classes). Label text:
|
||||
**Scan in wallet**.
|
||||
|
||||
### Optional: personal bank account (auto-created)
|
||||
|
||||
In the optional fold:
|
||||
|
||||
1. User clicks **Create my bank account**.
|
||||
2. Browser calls `GET /intro/auto-account.json` (no form fields).
|
||||
3. Server registers a new libeufin user via public `POST /accounts`.
|
||||
4. UI shows **username / password** once (“created for you”;
|
||||
not stored for recovery).
|
||||
5. Username shape: **`goa-account-<random>`** (shown to the user).
|
||||
Password embeds **`pleasechangeme`** with random chars around it.
|
||||
6. QR_Taler + link open `/webui/` so the user can log in with those credentials.
|
||||
|
||||
Balance **starts at zero** — not the shared `explorer` pool.
|
||||
|
||||
#### Deploy status — own bank account (**missing** until verified)
|
||||
|
||||
The **own bank account** path is **not complete** unless all of the following
|
||||
are live on `taler-hacktivism-bank`. Treat this as **missing** until checked:
|
||||
|
||||
| Check | Expected |
|
||||
|-------|----------|
|
||||
| Landing HTML | Optional fold has **Create my bank account** |
|
||||
| API process | `python3 /usr/local/bin/demo-withdraw-api.py` on **127.0.0.1:19096** |
|
||||
| Nginx | `location = /intro/auto-account.json` → `:19096` |
|
||||
| Public | `curl -sS https://bank.hacktivism.ch/intro/auto-account.json` returns JSON with `username` / `password` / `balance: "GOA:0"` |
|
||||
| WebUI login | Credentials work at `https://bank.hacktivism.ch/webui/` |
|
||||
|
||||
Install helper: `scripts/taler-bank/install-demo-withdraw-api.sh`
|
||||
(or manual copy of `demo-withdraw-api.py` + `nginx-landing.conf` + restart API).
|
||||
|
||||
### How the shared pool works
|
||||
|
||||
1. Shared account `explorer` is pre-funded with GOA.
|
||||
2. Each demo withdraw is a **one-shot** op from that pool (not a personal login).
|
||||
3. Wallet accepts the URI; **auto-confirm only for `explorer`** (see
|
||||
`auto-confirm-withdrawals.sh`).
|
||||
4. Pool balance is communal; amounts you withdraw are yours in the wallet.
|
||||
|
||||
### Helpers
|
||||
|
||||
| Script | Role |
|
||||
|--------|------|
|
||||
| `scripts/taler-bank/demo-withdraw-api.py` | Loopback HTTP **:19096** — `/demo-withdraw.json` + `/auto-account.json` |
|
||||
| `scripts/taler-bank/install-demo-withdraw-api.sh` | Copy into bank container, nginx locations, start API + auto-confirm loop |
|
||||
| `scripts/taler-bank/auto-confirm-withdrawals.sh` | Confirm **explorer-only** withdrawals when status=`selected` |
|
||||
| `scripts/taler-bank/refresh-demo-withdraw.sh` | Refresh static `withdraw.uri` (no python) |
|
||||
| `configs/bank-landing/nginx-landing.conf` | `/intro/demo-withdraw.json`, `/intro/auto-account.json` proxies |
|
||||
|
||||
Needs **python3** in the bank container for `demo-withdraw-api.py`.
|
||||
|
||||
### CLI sketch (fresh wallet DB)
|
||||
|
||||
```bash
|
||||
# equivalent one-step in CLI terms:
|
||||
taler-wallet-cli exchanges add https://exchange.hacktivism.ch/
|
||||
taler-wallet-cli exchanges accept-tos https://exchange.hacktivism.ch/
|
||||
# then withdraw from the exchange (amount chosen in wallet UI)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Live stats (in bank container)
|
||||
|
||||
Stats are **not** computed on the laptop or host browser. A small script runs
|
||||
**inside the bank container**, queries libeufin for the demo funding account
|
||||
(`explorer`), and writes a public JSON file next to the landing assets.
|
||||
|
||||
| Piece | Path / role |
|
||||
|-------|-------------|
|
||||
| Generator (in-container) | `/usr/local/bin/landing-stats.sh` |
|
||||
| Source in admin-log | `scripts/taler-bank/landing-stats.sh` |
|
||||
| Host installer | `scripts/taler-bank/landing-stats-install.sh` |
|
||||
| Output | `/var/www/bank-landing/stats.json` → `https://bank.hacktivism.ch/intro/stats.json` |
|
||||
| Landing UI | `index.html` section **GOA flow · live** |
|
||||
|
||||
### What the page shows
|
||||
|
||||
- **Bank accounts** — registered accounts (libeufin `GET /accounts`, admin)
|
||||
- **Wallets involved** — unique reserve pubs from Taler withdrawal debits
|
||||
- **Withdraws / bank credits** — flow amounts (exchange mirror skipped where noted)
|
||||
- **Last withdraws** — recent list with amount, time (**CEST** / `Europe/Zurich`)
|
||||
- **Last 24h / 7d** — amount
|
||||
- **`balance_explorer`** — live shared-pool balance (shown on withdraw step)
|
||||
- Performance probes + container RSS (bottom of page)
|
||||
|
||||
### Scan depth (must stay deep enough)
|
||||
|
||||
| Env | Default | Meaning |
|
||||
|-----|---------|---------|
|
||||
| `TX_DELTA` | `-50000` | per-account ledger window (`GET …/transactions?delta=`) |
|
||||
| `ACCOUNTS_DELTA` | `-500` | account-list window (`GET /accounts?delta=`) |
|
||||
| `MAX_SCAN_ACCOUNTS` | `500` | max usernames to scan |
|
||||
|
||||
Older defaults (`TX_DELTA=-100`, `MAX_SCAN_ACCOUNTS=80`) **undercounted** credits/withdraws
|
||||
and account totals on this stack. Empty accounts often return **HTTP 204** (no body) —
|
||||
that is normal (e.g. fresh auto-accounts), not a stats failure.
|
||||
|
||||
### `stats.json` shape (abridged)
|
||||
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"currency": "GOA",
|
||||
"generated_at": "2026-07-10T20:35+02:00",
|
||||
"balance_explorer": "GOA:960",
|
||||
"bank_accounts": { "total": 56, "users": 55 },
|
||||
"wallets": { "unique_reserves": 51 },
|
||||
"withdraws": {
|
||||
"count": 51,
|
||||
"last_24h": { "amount": "GOA:…" },
|
||||
"last_7d": { "amount": "GOA:…" }
|
||||
},
|
||||
"flow": {
|
||||
"incoming": { "amount": "GOA:…", "count": 68 },
|
||||
"withdraw": { "amount": "GOA:…", "count": 51 }
|
||||
},
|
||||
"source": "in-container landing-stats.sh"
|
||||
}
|
||||
```
|
||||
|
||||
### Install + refresh (on koopa host)
|
||||
|
||||
```bash
|
||||
# from laptop: copy scripts, then on koopa:
|
||||
cd /path/to/koopa-admin-log/scripts/taler-bank
|
||||
|
||||
# copy into container + one-shot run
|
||||
sudo ./landing-stats-install.sh
|
||||
|
||||
# every minute inside the container (* * * * *)
|
||||
sudo ./landing-stats-install.sh --cron
|
||||
# optional: LANDING_STATS_CRON='*/5 * * * *' ./landing-stats-install.sh --cron
|
||||
|
||||
# later: only re-run
|
||||
sudo ./landing-stats-install.sh --run-only
|
||||
```
|
||||
|
||||
Requirements **inside** the container:
|
||||
|
||||
- `curl`, `awk` (mawk OK), `sed`, `date` — **no python**
|
||||
- libeufin-bank listening on loopback (script auto-detects `PORT` / 9012 / 8080)
|
||||
- `/root/bank-explorer-password.txt` (or `BANK_PASS=…` on the `podman exec`)
|
||||
- writable `/var/www/bank-landing/` (same tree nginx uses for `/intro/`)
|
||||
|
||||
`make-demo-withdraw-qr.sh` also calls `landing-stats.sh` after refreshing the demo URI,
|
||||
when the binary is already installed at `/usr/local/bin/landing-stats.sh`.
|
||||
|
||||
### Manual one-liner (debug)
|
||||
|
||||
```bash
|
||||
podman exec -e LANDING_DIR=/var/www/bank-landing taler-hacktivism-bank \
|
||||
/usr/local/bin/landing-stats.sh
|
||||
curl -sS https://bank.hacktivism.ch/intro/stats.json | python3 -m json.tool
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Host / container files
|
||||
|
||||
Landing root (nginx in bank container), typically under something like
|
||||
`/var/www/bank-landing/` or the container path mapped for **9013**:
|
||||
|
||||
| File | Role |
|
||||
|------|------|
|
||||
| `index.html` | landing (wallet, shared withdraw, folds, stats) + Open Graph meta |
|
||||
| `og-goa-shop.png` | **link preview** image (1200×630) for chats / social (OG + Twitter) |
|
||||
| `shop-pay.css` / `shop-pay.js` | GOA shop popup + **QR_Taler** frame styles |
|
||||
| `qr-logo.svg` | center logo on QR_Taler frames |
|
||||
| `qrcode.min.js` | QR encode helper |
|
||||
| `stats.json` | **live stats** (from in-container script) |
|
||||
| `nginx-landing.conf` | bank container nginx (**9013**) |
|
||||
| `withdraw.uri` | current demo `taler://withdraw/…` (optional) |
|
||||
| `withdraw.amount` | e.g. `GOA:10` (optional) |
|
||||
| `withdraw-watch.ids` | ids for auto-confirm helper (optional) |
|
||||
|
||||
## Deploy landing HTML + helper API
|
||||
|
||||
```bash
|
||||
# landing assets
|
||||
podman cp configs/bank-landing/index.html \
|
||||
taler-hacktivism-bank:/var/www/bank-landing/index.html
|
||||
podman cp configs/bank-landing/og-goa-shop.png \
|
||||
taler-hacktivism-bank:/var/www/bank-landing/og-goa-shop.png
|
||||
podman cp configs/bank-landing/shop-pay.js \
|
||||
taler-hacktivism-bank:/var/www/bank-landing/shop-pay.js
|
||||
# … shop-pay.css, qr-logo.svg, qrcode.min.js as needed
|
||||
|
||||
# demo-withdraw + auto-account API (python3 in container)
|
||||
./scripts/taler-bank/install-demo-withdraw-api.sh
|
||||
# or manually:
|
||||
podman cp scripts/taler-bank/demo-withdraw-api.py \
|
||||
taler-hacktivism-bank:/usr/local/bin/demo-withdraw-api.py
|
||||
podman cp configs/bank-landing/nginx-landing.conf \
|
||||
taler-hacktivism-bank:/etc/nginx/sites-available/bank-landing
|
||||
# restart: python3 /usr/local/bin/demo-withdraw-api.py on 127.0.0.1:19096
|
||||
# nginx -s reload; auto-confirm-withdrawals.sh --loop
|
||||
```
|
||||
|
||||
### Demo QR refresh (in container, no python)
|
||||
|
||||
```bash
|
||||
podman exec taler-hacktivism-bank /usr/local/bin/refresh-demo-withdraw.sh
|
||||
# source: scripts/taler-bank/refresh-demo-withdraw.sh
|
||||
```
|
||||
|
||||
Older host script (needs python3): `scripts/taler-bank/make-demo-withdraw-qr.sh`
|
||||
2055
configs/bank-landing/index.html
Normal file
6
configs/bank-landing/nginx-cors-stats.conf
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
# Snippet: CORS so exchange/merchant landings can fetch stats.json
|
||||
# Include inside bank landing server { } or merge into bank-landing site.
|
||||
# location for stats only:
|
||||
# include /etc/nginx/snippets/goa-stats-cors.conf; (if placed as snippet)
|
||||
|
||||
# Applied as extra location on :9013 bank-landing:
|
||||
71
configs/bank-landing/nginx-landing.conf
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
# Bank landing — :9013 (behind Caddy :443 — never put 9013 in redirects)
|
||||
server {
|
||||
listen 9013;
|
||||
listen [::]:9013;
|
||||
server_name bank.hacktivism.ch _;
|
||||
root /var/www/bank-landing;
|
||||
index index.html;
|
||||
|
||||
# Critical behind reverse_proxy: no :9013 in Location headers
|
||||
absolute_redirect off;
|
||||
port_in_redirect off;
|
||||
|
||||
location = / {
|
||||
return 302 /intro/;
|
||||
}
|
||||
location = /intro {
|
||||
return 302 /intro/;
|
||||
}
|
||||
# Terms (same dark palette as merchant/exchange terms pages)
|
||||
location = /terms {
|
||||
alias /var/www/bank-landing/terms.html;
|
||||
default_type text/html;
|
||||
add_header Cache-Control "no-store" always;
|
||||
}
|
||||
location = /terms/ {
|
||||
return 302 /terms;
|
||||
}
|
||||
location = /privacy {
|
||||
alias /var/www/bank-landing/privacy.html;
|
||||
default_type text/html;
|
||||
add_header Cache-Control "no-store" always;
|
||||
}
|
||||
location = /privacy/ {
|
||||
return 302 /privacy;
|
||||
}
|
||||
# Fresh community-pool withdraw for one-click wallet funding
|
||||
location = /intro/demo-withdraw.json {
|
||||
proxy_pass http://127.0.0.1:19096/demo-withdraw.json;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
add_header Cache-Control "no-store" always;
|
||||
add_header Access-Control-Allow-Origin * always;
|
||||
}
|
||||
# Auto-create personal bank account (credentials returned once; balance GOA:0)
|
||||
location = /intro/auto-account.json {
|
||||
proxy_pass http://127.0.0.1:19096/auto-account.json;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
add_header Cache-Control "no-store" always;
|
||||
add_header Access-Control-Allow-Origin * always;
|
||||
}
|
||||
location /intro/ {
|
||||
alias /var/www/bank-landing/;
|
||||
}
|
||||
location = /intro/stats.json {
|
||||
alias /var/www/bank-landing/stats.json;
|
||||
default_type application/json;
|
||||
add_header Access-Control-Allow-Origin * always;
|
||||
add_header Access-Control-Allow-Methods "GET, OPTIONS" always;
|
||||
add_header Cache-Control "no-store" always;
|
||||
}
|
||||
location = /stats.json {
|
||||
alias /var/www/bank-landing/stats.json;
|
||||
default_type application/json;
|
||||
add_header Access-Control-Allow-Origin * always;
|
||||
add_header Cache-Control "no-store" always;
|
||||
}
|
||||
location / {
|
||||
try_files $uri $uri/ =404;
|
||||
}
|
||||
}
|
||||
BIN
configs/bank-landing/og-goa-shop.png
Normal file
|
After Width: | Height: | Size: 544 KiB |
137
configs/bank-landing/privacy.html
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<title>Privacy · GOA Bank · Swiss FADP</title>
|
||||
<style>
|
||||
:root { color-scheme: dark light; }
|
||||
body {
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
max-width: 42rem; margin: 2rem auto; padding: 0 1.1rem 3rem;
|
||||
line-height: 1.5; color: #e8e6e3; background: #1a1520;
|
||||
}
|
||||
h1 { font-size: 1.35rem; font-weight: 800; margin: 0 0 1rem; color: #f5f0ea; }
|
||||
h2 { font-size: 1.05rem; margin: 1.5rem 0 0.5rem; color: #e8c878; }
|
||||
p, li, td, th { font-size: 0.95rem; }
|
||||
ul { padding-left: 1.2rem; }
|
||||
code, a { color: #5eead4; }
|
||||
a { text-decoration: none; }
|
||||
a:hover { text-decoration: underline; }
|
||||
.badge {
|
||||
display: inline-block; font-size: 0.72rem; font-weight: 700;
|
||||
letter-spacing: 0.06em; text-transform: uppercase;
|
||||
color: #c4b5fd; border: 1px solid rgba(196,181,253,0.35);
|
||||
border-radius: 999px; padding: 0.2rem 0.65rem; margin-bottom: 0.85rem;
|
||||
}
|
||||
.note {
|
||||
border-radius: 12px; padding: 0.75rem 0.9rem; margin: 0.85rem 0 1rem;
|
||||
border: 1px solid rgba(255,255,255,0.1); background: rgba(0,0,0,0.25);
|
||||
font-size: 0.9rem; color: #c8c4bf;
|
||||
}
|
||||
table { width: 100%; border-collapse: collapse; margin: 0.6rem 0 1rem; font-size: 0.88rem; }
|
||||
th, td { border: 1px solid rgba(255,255,255,0.12); padding: 0.45rem 0.55rem; text-align: left; vertical-align: top; }
|
||||
th { background: rgba(0,0,0,0.35); color: #e8c878; font-weight: 700; }
|
||||
.muted { color: #a39e98; font-size: 0.88rem; }
|
||||
footer { margin-top: 2rem; font-size: 0.85rem; color: #a39e98; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="badge">bank.hacktivism.ch · privacy · CH</div>
|
||||
<h1>Privacy notice · GOA Exploration Bank</h1>
|
||||
<p class="note">
|
||||
This notice describes personal data processing for the self-hosted bank at
|
||||
<code>bank.hacktivism.ch</code> under the Swiss Federal Act on Data Protection
|
||||
(<strong>FADP / revDSG</strong>, in force since 1 Sep 2023). It is an
|
||||
explorational service, not a licensed Swiss bank.
|
||||
</p>
|
||||
|
||||
<h2>1. Controller</h2>
|
||||
<p>Operators of the hacktivism.ch GNU Taler stack (GOA exploration deployment).
|
||||
Contact via the operational channels published for this host. No separate DPO
|
||||
is appointed for this experimental service.</p>
|
||||
|
||||
<h2>2. Categories of data retained</h2>
|
||||
<table>
|
||||
<thead><tr><th>Data</th><th>Examples</th><th>Typical retention</th></tr></thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Account identifiers</td>
|
||||
<td>Username, account serial, registration timestamp</td>
|
||||
<td>For account lifetime + up to 12 months after deletion or archive wipe</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Authentication secrets</td>
|
||||
<td>Password hashes / tokens (not plaintext passwords)</td>
|
||||
<td>While account exists; tokens until expiry or revoke</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Ledger / transactions</td>
|
||||
<td>Credits, debits, amounts (GOA), subjects, counterparty account names, timestamps</td>
|
||||
<td>Operational retention for the service lifetime; may be wiped on stack reset</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Balances & limits</td>
|
||||
<td>Account balance, debt limit, conversion flags</td>
|
||||
<td>While account exists</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Withdrawal operations</td>
|
||||
<td>Withdrawal IDs, amounts, status (pending/selected/confirmed), reserve pub when known</td>
|
||||
<td>Until completed/aborted + short operational logs (days–weeks)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Technical logs</td>
|
||||
<td>HTTP access logs (IP, User-Agent, path, status), application logs</td>
|
||||
<td>Typically days to a few weeks (rotation); not used for marketing</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Landing / demo artefacts</td>
|
||||
<td>Public <code>stats.json</code> aggregates, demo withdraw URI state</td>
|
||||
<td>Stats overwritten continuously; demo URI until used or rotated</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p><strong>Not retained as payment content:</strong> wallet coin private keys (never sent to the bank).
|
||||
Full browser history or device contacts are not collected by this bank service.</p>
|
||||
|
||||
<h2>3. Purposes</h2>
|
||||
<ul>
|
||||
<li>Operating bank accounts and GOA transfers for exploration of GNU Taler</li>
|
||||
<li>Authenticating users and preventing abuse</li>
|
||||
<li>Enabling withdrawals to the GOA exchange</li>
|
||||
<li>Security, debugging, and capacity monitoring</li>
|
||||
</ul>
|
||||
|
||||
<h2>4. Legal basis (Swiss FADP)</h2>
|
||||
<p>Processing is necessary to provide the service requested by the user (account / withdraw)
|
||||
and for overriding private interests of secure operation of an experimental public stack
|
||||
(Art. 6 and 31 FADP principles: lawfulness, proportionality, purpose limitation).</p>
|
||||
|
||||
<h2>5. Recipients & transfers</h2>
|
||||
<ul>
|
||||
<li><strong>GOA exchange</strong> (<code>exchange.hacktivism.ch</code>): reserve/wire-related data required by the protocol</li>
|
||||
<li><strong>Infrastructure operators</strong> of this host (hosting, backups) under operational control</li>
|
||||
<li>No sale of personal data. No intentional transfer outside Switzerland/EEA for this service;
|
||||
infrastructure may use standard CDN/DNS resolvers</li>
|
||||
</ul>
|
||||
|
||||
<h2>6. Your rights (FADP)</h2>
|
||||
<p>Subject to legal limits: right to information/access, rectification, deletion, and to object
|
||||
to processing. Contact the operators. You may lodge a complaint with the Swiss Federal Data
|
||||
Protection and Information Commissioner (<strong>FDPIC / EDÖB</strong>).</p>
|
||||
|
||||
<h2>7. Security</h2>
|
||||
<p>TLS in transit; access control on bank API; experimental service — no certified ISMS.
|
||||
Do not store sensitive personal data in transaction subjects.</p>
|
||||
|
||||
<h2>Related</h2>
|
||||
<ul>
|
||||
<li><a href="https://bank.hacktivism.ch/terms">Bank terms</a></li>
|
||||
<li><a href="https://exchange.hacktivism.ch/privacy">Exchange privacy</a></li>
|
||||
<li><a href="https://taler.hacktivism.ch/privacy">Merchant privacy</a></li>
|
||||
<li><a href="https://bank.hacktivism.ch/intro/">Bank intro</a></li>
|
||||
</ul>
|
||||
<footer class="muted">bank-pp-swiss-v0 · Swiss FADP (revDSG)</footer>
|
||||
</body>
|
||||
</html>
|
||||
BIN
configs/bank-landing/qr-logo.png
Normal file
|
After Width: | Height: | Size: 8.4 KiB |
1
configs/bank-landing/qr-logo.svg
Normal file
|
After Width: | Height: | Size: 9.6 KiB |
BIN
configs/bank-landing/qr-payto.png
Normal file
|
After Width: | Height: | Size: 602 B |
BIN
configs/bank-landing/qr-product-beacon-badge.png
Normal file
|
After Width: | Height: | Size: 608 B |
BIN
configs/bank-landing/qr-product-blue-or-red-pill.png
Normal file
|
After Width: | Height: | Size: 617 B |
BIN
configs/bank-landing/qr-product-comet-cap.png
Normal file
|
After Width: | Height: | Size: 524 B |
BIN
configs/bank-landing/qr-product-eclipse-shades.png
Normal file
|
After Width: | Height: | Size: 609 B |
BIN
configs/bank-landing/qr-product-nebula-coffee.png
Normal file
|
After Width: | Height: | Size: 604 B |
BIN
configs/bank-landing/qr-product-orbit-sticker.png
Normal file
|
After Width: | Height: | Size: 597 B |
BIN
configs/bank-landing/qr-product-rainbow-pill.png
Normal file
|
After Width: | Height: | Size: 599 B |
BIN
configs/bank-landing/qr-product-relay-pin.png
Normal file
|
After Width: | Height: | Size: 528 B |
BIN
configs/bank-landing/qr-product-shuttle-pass.png
Normal file
|
After Width: | Height: | Size: 609 B |
BIN
configs/bank-landing/qr-product-star-chart.png
Normal file
|
After Width: | Height: | Size: 527 B |
BIN
configs/bank-landing/qr-product-voidwave-playlist.png
Normal file
|
After Width: | Height: | Size: 602 B |
1
configs/bank-landing/qrcode.min.js
vendored
Normal file
348
configs/bank-landing/shop-pay.css
Normal file
|
|
@ -0,0 +1,348 @@
|
|||
/* GOA shop pay popup — QR_Taler style from taler-merchant-webui / @gnu-taler/web-util */
|
||||
|
||||
/* Animated blue ring (same as webui QR.tsx) */
|
||||
@property --angle {
|
||||
syntax: "<angle>";
|
||||
initial-value: 0deg;
|
||||
inherits: false;
|
||||
}
|
||||
@keyframes goa-pay-qr-rotate {
|
||||
to {
|
||||
--angle: 360deg;
|
||||
}
|
||||
}
|
||||
|
||||
.shop-item[data-product] {
|
||||
cursor: pointer;
|
||||
transition: border-color 0.15s, transform 0.12s, background 0.15s;
|
||||
}
|
||||
.shop-item[data-product]:hover,
|
||||
.shop-item[data-product]:focus-visible {
|
||||
border-color: rgba(196, 181, 253, 0.75);
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
transform: translateY(-1px);
|
||||
outline: none;
|
||||
}
|
||||
button.shop-item {
|
||||
font: inherit;
|
||||
color: inherit;
|
||||
width: 100%;
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
.goa-pay-modal {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 80;
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 1rem;
|
||||
background: rgba(8, 6, 14, 0.75);
|
||||
backdrop-filter: blur(8px);
|
||||
-webkit-backdrop-filter: blur(8px);
|
||||
}
|
||||
.goa-pay-modal.open {
|
||||
display: flex;
|
||||
}
|
||||
.goa-pay-card {
|
||||
position: relative;
|
||||
background: rgba(36, 28, 48, 0.98);
|
||||
border: 1px solid rgba(167, 139, 250, 0.45);
|
||||
border-radius: 16px;
|
||||
padding: 1.25rem 1.2rem 1.2rem;
|
||||
max-width: 24rem;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.5);
|
||||
max-height: 92vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.goa-pay-x {
|
||||
position: absolute;
|
||||
top: 0.45rem;
|
||||
right: 0.55rem;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: #c4b5fd;
|
||||
font-size: 1.4rem;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
padding: 0.2rem 0.45rem;
|
||||
}
|
||||
.goa-pay-card h3 {
|
||||
margin: 0 0 0.2rem;
|
||||
font-size: 1.08rem;
|
||||
color: #faf5ff;
|
||||
padding-right: 1.5rem;
|
||||
}
|
||||
.goa-pay-amount {
|
||||
margin: 0 0 0.85rem;
|
||||
font-weight: 750;
|
||||
color: #c4b5fd;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
/* === QR_Taler frame (webui QR.tsx) === */
|
||||
.goa-pay-taler-qr {
|
||||
width: 100%;
|
||||
max-width: 280px;
|
||||
margin: 0 auto 0.75rem;
|
||||
padding: 10px;
|
||||
border-radius: 20px;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
background: conic-gradient(
|
||||
from var(--angle),
|
||||
#0042b3 0deg,
|
||||
#f1f1f4 20deg,
|
||||
#f1f1f4 150deg,
|
||||
#f1f1f4 160deg,
|
||||
#0042b3 180deg,
|
||||
#f1f1f4 200deg,
|
||||
#f1f1f4 330deg,
|
||||
#f1f1f4 340deg,
|
||||
#0042b3
|
||||
);
|
||||
animation: goa-pay-qr-rotate 10s linear infinite;
|
||||
}
|
||||
.goa-pay-taler-qr__inner {
|
||||
padding: 10px;
|
||||
border-radius: 20px;
|
||||
background: #fff;
|
||||
line-height: 0;
|
||||
min-height: 180px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
/* Always an <img data-url> with explicit pixel size from JS */
|
||||
.goa-pay-taler-qr__inner img {
|
||||
display: block !important;
|
||||
margin: 0 auto !important;
|
||||
border: 0 !important;
|
||||
background: #fff;
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
/* Center logo plate — official qr-logo.png (webui ~100×50; PNG for Android) */
|
||||
.goa-pay-taler-qr__logo {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 28%;
|
||||
height: auto;
|
||||
max-width: 100px;
|
||||
aspect-ratio: 200 / 95;
|
||||
object-fit: contain;
|
||||
pointer-events: none;
|
||||
background: #fff;
|
||||
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 */
|
||||
.goa-pay-settle {
|
||||
margin-top: 0.85rem;
|
||||
border: 1px solid rgba(148, 163, 184, 0.35);
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
background: rgba(0, 0, 0, 0.22);
|
||||
}
|
||||
.goa-pay-settle-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.5rem;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0.65rem 0.75rem;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: #cbd5e1;
|
||||
font: inherit;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 650;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
line-height: 1.3;
|
||||
}
|
||||
.goa-pay-settle-toggle:hover {
|
||||
background: rgba(148, 163, 184, 0.1);
|
||||
color: #e2e8f0;
|
||||
}
|
||||
.goa-pay-settle-toggle-text {
|
||||
flex: 1;
|
||||
}
|
||||
.goa-pay-settle-chevron {
|
||||
flex-shrink: 0;
|
||||
transition: transform 0.15s ease;
|
||||
color: #94a3b8;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
.goa-pay-modal.settle-open .goa-pay-settle-chevron {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
.goa-pay-settle-body {
|
||||
padding: 0 0.75rem 0.75rem;
|
||||
border-top: 1px solid rgba(148, 163, 184, 0.25);
|
||||
}
|
||||
.goa-pay-settle-note {
|
||||
margin-top: 0.55rem;
|
||||
}
|
||||
/* Settlement payto QR — plain box (not wallet pay / no logo / no blue ring) */
|
||||
.goa-pay-payto-qr {
|
||||
width: fit-content;
|
||||
max-width: 160px;
|
||||
margin: 0.35rem auto 0.55rem;
|
||||
padding: 0.45rem 0.5rem 0.5rem;
|
||||
border-radius: 10px;
|
||||
background: #fff;
|
||||
border: 1px dashed rgba(100, 116, 139, 0.75);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.goa-pay-payto-qr__caption {
|
||||
font-size: 0.62rem;
|
||||
font-weight: 750;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
color: #64748b;
|
||||
text-align: center;
|
||||
margin: 0 0 0.35rem;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.goa-pay-payto-qr__inner {
|
||||
line-height: 0;
|
||||
min-height: 0;
|
||||
}
|
||||
.goa-pay-payto-qr__inner img {
|
||||
display: block !important;
|
||||
margin: 0 auto !important;
|
||||
background: #fff;
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
.goa-pay-sub {
|
||||
margin: 0 0 0.55rem;
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.35;
|
||||
color: #b8a8c9;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.goa-pay-hint {
|
||||
margin: 0 0 0.75rem;
|
||||
font-size: 0.82rem;
|
||||
color: #b8a8c9;
|
||||
}
|
||||
.goa-pay-hint.err {
|
||||
color: #fca5a5;
|
||||
}
|
||||
.goa-pay-cta {
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
font-weight: 750;
|
||||
padding: 0.65rem 1rem;
|
||||
border-radius: 11px;
|
||||
background: linear-gradient(135deg, #a78bfa, #6366f1);
|
||||
color: #0f0a1a;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
.goa-pay-cta:hover {
|
||||
filter: brightness(1.06);
|
||||
}
|
||||
.goa-pay-cta.disabled {
|
||||
opacity: 0.45;
|
||||
pointer-events: none;
|
||||
}
|
||||
.goa-pay-label {
|
||||
margin: 0.55rem 0 0.2rem;
|
||||
font-size: 0.68rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.06em;
|
||||
text-transform: uppercase;
|
||||
color: #b8a8c9;
|
||||
}
|
||||
.goa-pay-line {
|
||||
margin: 0 0 0.35rem;
|
||||
text-align: center;
|
||||
font-size: 0.88rem;
|
||||
font-weight: 650;
|
||||
}
|
||||
.goa-pay-link {
|
||||
color: #93c5fd;
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.goa-pay-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.goa-pay-link.disabled {
|
||||
opacity: 0.45;
|
||||
pointer-events: none;
|
||||
}
|
||||
.goa-pay-sep {
|
||||
margin: 0 0.4rem;
|
||||
color: #b8a8c9;
|
||||
font-weight: 500;
|
||||
}
|
||||
.goa-pay-close {
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin-top: 0.85rem;
|
||||
border: 1px solid rgba(167, 139, 250, 0.45);
|
||||
background: transparent;
|
||||
color: #c4b5fd;
|
||||
font-weight: 650;
|
||||
padding: 0.5rem;
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Bank (gold/teal) overrides */
|
||||
.goa-pay-modal.bank-theme .goa-pay-card {
|
||||
background: rgba(42, 32, 24, 0.98);
|
||||
border-color: rgba(232, 168, 56, 0.4);
|
||||
}
|
||||
.goa-pay-modal.bank-theme .goa-pay-amount {
|
||||
color: #5eead4;
|
||||
}
|
||||
.goa-pay-modal.bank-theme .goa-pay-cta {
|
||||
background: linear-gradient(135deg, #5eead4, #0d9488);
|
||||
color: #042f2e;
|
||||
}
|
||||
.goa-pay-modal.bank-theme .goa-pay-link {
|
||||
color: #5eead4;
|
||||
}
|
||||
.goa-pay-modal.bank-theme .goa-pay-x,
|
||||
.goa-pay-modal.bank-theme .goa-pay-close {
|
||||
color: #e8c878;
|
||||
border-color: rgba(232, 168, 56, 0.35);
|
||||
}
|
||||
.goa-pay-modal.bank-theme .goa-pay-settle {
|
||||
border-color: rgba(232, 168, 56, 0.3);
|
||||
background: rgba(0, 0, 0, 0.28);
|
||||
}
|
||||
.goa-pay-modal.bank-theme .goa-pay-settle-toggle {
|
||||
color: #e8c878;
|
||||
}
|
||||
.goa-pay-modal.bank-theme .goa-pay-settle-body {
|
||||
border-top-color: rgba(232, 168, 56, 0.25);
|
||||
}
|
||||
410
configs/bank-landing/shop-pay.js
Normal file
|
|
@ -0,0 +1,410 @@
|
|||
/**
|
||||
* GOA shop pay popup — public only (no merchant secrets).
|
||||
* Flow: POST templates/{id} → taler://pay/… + payto links.
|
||||
*
|
||||
* QR display matches taler-merchant-webui QR_Taler
|
||||
* (@gnu-taler/web-util QR.tsx): animated #0042B3 conic ring + qr-logo.png.
|
||||
* Uses qrcode-generator (same lib as webui) via global QRCode if present,
|
||||
* else falls back to canvas from qrcode.min.js (davidshimjs).
|
||||
*/
|
||||
(function () {
|
||||
var MERCHANT_HOST = "taler.hacktivism.ch";
|
||||
/* goa-shop: dedicated instance with fixed-order product templates */
|
||||
var INSTANCE = "goa-shop";
|
||||
var SHOP_PAYTO =
|
||||
"payto://x-taler-bank/bank.hacktivism.ch/goa-shop?receiver-name=GOA%20Shop";
|
||||
var SHOP_PAYTO_HTTPS = "https://bank.hacktivism.ch/webui/";
|
||||
|
||||
function introBase() {
|
||||
var b = document.querySelector("base");
|
||||
return b && b.href ? b.href : "/intro/";
|
||||
}
|
||||
|
||||
function logoSrc() {
|
||||
/* PNG: Android often drops SVG wordmark fill inheritance (missing "taler") */
|
||||
return introBase() + "qr-logo.png";
|
||||
}
|
||||
|
||||
function templateHttps(productId) {
|
||||
return (
|
||||
"https://" +
|
||||
MERCHANT_HOST +
|
||||
"/instances/" +
|
||||
INSTANCE +
|
||||
"/templates/" +
|
||||
encodeURIComponent(productId)
|
||||
);
|
||||
}
|
||||
|
||||
function payTemplateUri(productId) {
|
||||
return (
|
||||
"taler://pay-template/" +
|
||||
MERCHANT_HOST +
|
||||
"/instances/" +
|
||||
INSTANCE +
|
||||
"/" +
|
||||
encodeURIComponent(productId)
|
||||
);
|
||||
}
|
||||
|
||||
function ensureModal() {
|
||||
var m = document.getElementById("goa-pay-modal");
|
||||
if (m) return m;
|
||||
m = document.createElement("div");
|
||||
m.id = "goa-pay-modal";
|
||||
m.className = "goa-pay-modal";
|
||||
m.setAttribute("role", "dialog");
|
||||
m.setAttribute("aria-modal", "true");
|
||||
m.setAttribute("aria-labelledby", "goa-pay-title");
|
||||
m.hidden = true;
|
||||
m.innerHTML =
|
||||
'<div class="goa-pay-card">' +
|
||||
' <button type="button" class="goa-pay-x" id="goa-pay-x" aria-label="Close">×</button>' +
|
||||
' <h3 id="goa-pay-title">Pay with Taler</h3>' +
|
||||
' <p class="goa-pay-amount" id="goa-pay-amount"></p>' +
|
||||
' <p class="goa-pay-hint" id="goa-pay-status">Preparing payment…</p>' +
|
||||
' <p class="goa-pay-label">Wallet payment</p>' +
|
||||
' <p class="goa-pay-sub">Scan with the GNU Taler Wallet to pay this product</p>' +
|
||||
' <div class="goa-pay-taler-qr" id="goa-pay-qr-pay-wrap">' +
|
||||
' <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="" />' +
|
||||
" </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>' +
|
||||
' <p class="goa-pay-line">' +
|
||||
' <a class="goa-pay-link" id="goa-pay-uri" href="#">taler:// URI</a>' +
|
||||
' <span class="goa-pay-sep">·</span>' +
|
||||
' <a class="goa-pay-link" id="goa-pay-uri-https" href="#" target="_blank" rel="noopener">HTTPS</a>' +
|
||||
" </p>" +
|
||||
' <div class="goa-pay-settle" id="goa-pay-settle">' +
|
||||
' <button type="button" class="goa-pay-settle-toggle" id="goa-pay-settle-toggle" aria-expanded="false" aria-controls="goa-pay-settle-body">' +
|
||||
' <span class="goa-pay-settle-toggle-text">Settlement account (payto) · not a wallet pay QR</span>' +
|
||||
' <span class="goa-pay-settle-chevron" aria-hidden="true">▸</span>' +
|
||||
" </button>" +
|
||||
' <div class="goa-pay-settle-body" id="goa-pay-settle-body" hidden>' +
|
||||
' <p class="goa-pay-sub goa-pay-settle-note">' +
|
||||
" This is the merchant’s bank account address (payto://). " +
|
||||
" The exchange wires funds here after a wallet payment. " +
|
||||
" Do not scan this to buy the product — use the wallet QR above." +
|
||||
" </p>" +
|
||||
' <div class="goa-pay-payto-qr" id="goa-pay-qr-payto-wrap">' +
|
||||
' <div class="goa-pay-payto-qr__caption">Settlement only</div>' +
|
||||
' <div class="goa-pay-payto-qr__inner" id="goa-pay-qr-payto"></div>' +
|
||||
" </div>" +
|
||||
' <a class="goa-pay-payload" id="goa-pay-payto-payload" href="#">…</a>' +
|
||||
' <p class="goa-pay-line">' +
|
||||
' <a class="goa-pay-link" id="goa-pay-payto" href="#">payto:// URI</a>' +
|
||||
' <span class="goa-pay-sep">·</span>' +
|
||||
' <a class="goa-pay-link" id="goa-pay-payto-https" href="#" target="_blank" rel="noopener">HTTPS</a>' +
|
||||
" </p>" +
|
||||
" </div>" +
|
||||
" </div>" +
|
||||
' <button type="button" class="goa-pay-close" id="goa-pay-close">Close</button>' +
|
||||
"</div>";
|
||||
document.body.appendChild(m);
|
||||
function close() {
|
||||
m.classList.remove("open");
|
||||
m.hidden = true;
|
||||
// collapse settlement on close
|
||||
var body = document.getElementById("goa-pay-settle-body");
|
||||
var btn = document.getElementById("goa-pay-settle-toggle");
|
||||
if (body) body.hidden = true;
|
||||
if (btn) {
|
||||
btn.setAttribute("aria-expanded", "false");
|
||||
m.classList.remove("settle-open");
|
||||
}
|
||||
}
|
||||
m.addEventListener("click", function (e) {
|
||||
if (e.target === m) close();
|
||||
});
|
||||
document.getElementById("goa-pay-x").onclick = close;
|
||||
document.getElementById("goa-pay-close").onclick = close;
|
||||
document.getElementById("goa-pay-settle-toggle").onclick = function () {
|
||||
var body = document.getElementById("goa-pay-settle-body");
|
||||
var btn = document.getElementById("goa-pay-settle-toggle");
|
||||
var open = body.hidden;
|
||||
body.hidden = !open;
|
||||
btn.setAttribute("aria-expanded", open ? "true" : "false");
|
||||
m.classList.toggle("settle-open", open);
|
||||
if (open) {
|
||||
// paint payto QR when first expanded
|
||||
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) {
|
||||
if (e.key === "Escape" && m.classList.contains("open")) close();
|
||||
});
|
||||
return m;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render URI as a real <img> PNG (not a live canvas).
|
||||
* davidshimjs paints canvas then often hides it for an img; if the modal is
|
||||
* still display:none, layout collapses and you only see the blue ring.
|
||||
* We always encode off-DOM and inject a fixed-size image.
|
||||
*/
|
||||
function renderQr(hostEl, text, size) {
|
||||
if (!hostEl) return;
|
||||
hostEl.innerHTML = "";
|
||||
size = size || 220;
|
||||
if (!text) {
|
||||
hostEl.innerHTML = '<p class="goa-pay-hint">…</p>';
|
||||
return;
|
||||
}
|
||||
if (typeof QRCode === "undefined") {
|
||||
hostEl.innerHTML =
|
||||
'<p class="goa-pay-hint err">QR library missing (qrcode.min.js)</p>';
|
||||
return;
|
||||
}
|
||||
var level =
|
||||
QRCode.CorrectLevel && QRCode.CorrectLevel.M != null
|
||||
? QRCode.CorrectLevel.M
|
||||
: QRCode.CorrectLevel && QRCode.CorrectLevel.L != null
|
||||
? QRCode.CorrectLevel.L
|
||||
: 1;
|
||||
var scratch = document.createElement("div");
|
||||
scratch.setAttribute("aria-hidden", "true");
|
||||
scratch.style.cssText =
|
||||
"position:fixed;left:-9999px;top:0;width:" +
|
||||
size +
|
||||
"px;height:" +
|
||||
size +
|
||||
"px;overflow:hidden;opacity:0;pointer-events:none";
|
||||
document.body.appendChild(scratch);
|
||||
var dataUrl = "";
|
||||
try {
|
||||
new QRCode(scratch, {
|
||||
text: String(text),
|
||||
width: size,
|
||||
height: size,
|
||||
colorDark: "#000000",
|
||||
colorLight: "#ffffff",
|
||||
correctLevel: level,
|
||||
});
|
||||
var canvas = scratch.querySelector("canvas");
|
||||
var libImg = scratch.querySelector("img");
|
||||
if (canvas && canvas.width > 0) {
|
||||
try {
|
||||
dataUrl = canvas.toDataURL("image/png");
|
||||
} catch (e1) {}
|
||||
}
|
||||
if (!dataUrl && libImg && libImg.src && libImg.src.indexOf("data:") === 0) {
|
||||
dataUrl = libImg.src;
|
||||
}
|
||||
} catch (err) {
|
||||
dataUrl = "";
|
||||
}
|
||||
if (scratch.parentNode) scratch.parentNode.removeChild(scratch);
|
||||
|
||||
if (!dataUrl) {
|
||||
hostEl.innerHTML =
|
||||
'<p class="goa-pay-hint err">QR encode failed</p>';
|
||||
return;
|
||||
}
|
||||
var img = document.createElement("img");
|
||||
img.alt = "QR code";
|
||||
img.width = size;
|
||||
img.height = size;
|
||||
img.src = dataUrl;
|
||||
img.style.display = "block";
|
||||
img.style.width = size + "px";
|
||||
img.style.height = size + "px";
|
||||
img.style.maxWidth = "100%";
|
||||
img.style.margin = "0 auto";
|
||||
img.style.background = "#fff";
|
||||
hostEl.appendChild(img);
|
||||
}
|
||||
|
||||
function setLogo(imgEl) {
|
||||
if (!imgEl) return;
|
||||
imgEl.alt = "Taler";
|
||||
imgEl.width = 100;
|
||||
imgEl.height = 50;
|
||||
imgEl.decoding = "async";
|
||||
imgEl.src = logoSrc();
|
||||
imgEl.onerror = function () {
|
||||
imgEl.src = introBase() + "qr-logo.svg";
|
||||
};
|
||||
imgEl.style.display = "";
|
||||
}
|
||||
|
||||
function normalizePayUri(uri) {
|
||||
if (!uri) return "";
|
||||
return String(uri)
|
||||
.replace(/taler\.hacktivism\.ch:443/g, "taler.hacktivism.ch")
|
||||
.replace(/:443\//g, "/")
|
||||
.replace(/:443\?/g, "?");
|
||||
}
|
||||
|
||||
/** Public: template → order → taler_pay_uri (no secrets). */
|
||||
function createPayUri(productId) {
|
||||
var url = templateHttps(productId);
|
||||
return fetch(url, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: "{}",
|
||||
cache: "no-store",
|
||||
})
|
||||
.then(function (r) {
|
||||
if (!r.ok) throw new Error("template POST HTTP " + r.status);
|
||||
return r.json();
|
||||
})
|
||||
.then(function (created) {
|
||||
var oid = created.order_id;
|
||||
var tok = created.token;
|
||||
if (!oid || !tok) throw new Error("no order_id/token");
|
||||
var statusUrl =
|
||||
"https://" +
|
||||
MERCHANT_HOST +
|
||||
"/instances/" +
|
||||
INSTANCE +
|
||||
"/orders/" +
|
||||
encodeURIComponent(oid) +
|
||||
"?token=" +
|
||||
encodeURIComponent(tok);
|
||||
return fetch(statusUrl, { cache: "no-store" }).then(function (r) {
|
||||
return r.text().then(function (t) {
|
||||
var pay = "";
|
||||
try {
|
||||
var d = JSON.parse(t);
|
||||
pay = d.taler_pay_uri || "";
|
||||
} catch (e) {}
|
||||
if (!pay) {
|
||||
pay =
|
||||
"taler://pay/" +
|
||||
MERCHANT_HOST +
|
||||
"/instances/" +
|
||||
INSTANCE +
|
||||
"/" +
|
||||
oid +
|
||||
"/?c=" +
|
||||
tok;
|
||||
}
|
||||
return {
|
||||
taler_pay_uri: normalizePayUri(pay),
|
||||
order_id: oid,
|
||||
token: tok,
|
||||
status_https: statusUrl.replace(
|
||||
"taler.hacktivism.ch:443",
|
||||
"taler.hacktivism.ch"
|
||||
),
|
||||
template_https: url,
|
||||
};
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function showPay(productId, name, amount) {
|
||||
var m = ensureModal();
|
||||
if (document.body && document.body.getAttribute("data-shop-theme") === "bank") {
|
||||
m.classList.add("bank-theme");
|
||||
} else {
|
||||
m.classList.remove("bank-theme");
|
||||
}
|
||||
document.getElementById("goa-pay-title").textContent = name || productId;
|
||||
document.getElementById("goa-pay-amount").textContent = amount || "";
|
||||
var status = document.getElementById("goa-pay-status");
|
||||
status.textContent = "Preparing payment…";
|
||||
status.className = "goa-pay-hint";
|
||||
var open = document.getElementById("goa-pay-open");
|
||||
open.removeAttribute("href");
|
||||
open.classList.add("disabled");
|
||||
open.onclick = null;
|
||||
var payLink = document.getElementById("goa-pay-uri");
|
||||
payLink.removeAttribute("href");
|
||||
payLink.classList.add("disabled");
|
||||
payLink.textContent = "taler:// URI";
|
||||
document.getElementById("goa-pay-uri-https").href = templateHttps(productId);
|
||||
document.getElementById("goa-pay-uri-https").textContent = "HTTPS";
|
||||
var paytoLink = document.getElementById("goa-pay-payto");
|
||||
paytoLink.href = SHOP_PAYTO;
|
||||
paytoLink.textContent = "payto:// URI";
|
||||
document.getElementById("goa-pay-payto-https").href = SHOP_PAYTO_HTTPS;
|
||||
document.getElementById("goa-pay-payto-https").textContent = "HTTPS";
|
||||
|
||||
setLogo(document.getElementById("goa-pay-qr-pay-logo"));
|
||||
|
||||
// Reset settlement panel (collapsed until user expands)
|
||||
var settleBody = document.getElementById("goa-pay-settle-body");
|
||||
var settleBtn = document.getElementById("goa-pay-settle-toggle");
|
||||
if (settleBody) settleBody.hidden = true;
|
||||
if (settleBtn) settleBtn.setAttribute("aria-expanded", "false");
|
||||
m.classList.remove("settle-open");
|
||||
var paytoHost = document.getElementById("goa-pay-qr-payto");
|
||||
if (paytoHost) paytoHost.innerHTML = "";
|
||||
|
||||
// Open first so layout exists, then paint wallet QR only
|
||||
m.hidden = false;
|
||||
m.classList.add("open");
|
||||
var tpl = payTemplateUri(productId);
|
||||
renderQr(
|
||||
document.getElementById("goa-pay-qr-pay"),
|
||||
tpl,
|
||||
220
|
||||
);
|
||||
var payload = document.getElementById("goa-pay-qr-payload");
|
||||
if (payload) {
|
||||
payload.href = tpl;
|
||||
payload.textContent = tpl;
|
||||
}
|
||||
|
||||
createPayUri(productId)
|
||||
.then(function (info) {
|
||||
var pay = info.taler_pay_uri;
|
||||
open.href = pay;
|
||||
open.classList.remove("disabled");
|
||||
open.textContent = "Open payment link →";
|
||||
payLink.href = pay;
|
||||
payLink.classList.remove("disabled");
|
||||
payLink.textContent = "taler:// URI";
|
||||
var uh = document.getElementById("goa-pay-uri-https");
|
||||
uh.href = info.status_https || info.template_https;
|
||||
uh.textContent = "HTTPS";
|
||||
// Live unpaid taler://pay (fresh order each open)
|
||||
renderQr(document.getElementById("goa-pay-qr-pay"), pay, 220);
|
||||
if (payload) {
|
||||
payload.href = pay;
|
||||
payload.textContent = pay;
|
||||
}
|
||||
status.textContent = "Ready — scan in wallet";
|
||||
})
|
||||
.catch(function (err) {
|
||||
status.textContent =
|
||||
"Payment setup failed: " + (err && err.message ? err.message : err);
|
||||
status.className = "goa-pay-hint err";
|
||||
open.textContent = "Retry";
|
||||
open.onclick = function (e) {
|
||||
e.preventDefault();
|
||||
showPay(productId, name, amount);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function bind() {
|
||||
document.querySelectorAll("[data-product]").forEach(function (el) {
|
||||
el.addEventListener("click", function (e) {
|
||||
e.preventDefault();
|
||||
var id = el.getAttribute("data-product");
|
||||
var nameEl = el.querySelector(".name");
|
||||
var priceEl = el.querySelector(".price");
|
||||
showPay(
|
||||
id,
|
||||
nameEl ? nameEl.textContent.trim() : id,
|
||||
priceEl ? priceEl.textContent.trim() : ""
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", bind);
|
||||
} else {
|
||||
bind();
|
||||
}
|
||||
})();
|
||||
75
configs/bank-landing/terms.html
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<title>No Formal Terms · GOA Bank</title>
|
||||
<style>
|
||||
:root { color-scheme: dark light; }
|
||||
body {
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
max-width: 40rem; margin: 2rem auto; padding: 0 1.1rem 3rem;
|
||||
line-height: 1.5; color: #e8e6e3; background: #1a1520;
|
||||
}
|
||||
h1 { font-size: 1.35rem; font-weight: 800; margin: 0 0 1rem; color: #f5f0ea; }
|
||||
h2 { font-size: 1.05rem; margin: 1.4rem 0 0.5rem; color: #e8c878; }
|
||||
p, li { font-size: 0.98rem; }
|
||||
ul { padding-left: 1.2rem; }
|
||||
code, a { color: #5eead4; }
|
||||
a { text-decoration: none; }
|
||||
a:hover { text-decoration: underline; }
|
||||
.badge {
|
||||
display: inline-block; font-size: 0.72rem; font-weight: 700;
|
||||
letter-spacing: 0.06em; text-transform: uppercase;
|
||||
color: #c4b5fd; border: 1px solid rgba(196,181,253,0.35);
|
||||
border-radius: 999px; padding: 0.2rem 0.65rem; margin-bottom: 0.85rem;
|
||||
}
|
||||
.cur {
|
||||
border-radius: 12px; padding: 0.75rem 0.9rem; margin: 0.85rem 0 1rem;
|
||||
border: 1px solid rgba(255,255,255,0.1); background: rgba(0,0,0,0.25);
|
||||
}
|
||||
.cur strong { display: block; font-size: 1.05rem; margin-bottom: 0.25rem; color: #5eead4; }
|
||||
.muted { color: #a39e98; font-size: 0.88rem; }
|
||||
footer { margin-top: 2rem; font-size: 0.85rem; color: #a39e98; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="badge">bank.hacktivism.ch · GOA</div>
|
||||
<h1>No Formal Terms · GOA Exploration Bank</h1>
|
||||
<p>This is a <strong>self-hosted GNU Taler regional bank</strong> at
|
||||
<code>bank.hacktivism.ch</code> (hacktivism.ch) for the explorational currency
|
||||
<strong>GOA</strong>.</p>
|
||||
<p><strong>No formal terms of service</strong> apply beyond this short notice.</p>
|
||||
|
||||
<div class="cur">
|
||||
<strong>GOA · explorational</strong>
|
||||
Not legal tender. No guaranteed real-world value, redemption, or convertibility.
|
||||
Accounts and demo withdraws are for exploration of the GNU Taler stack only.
|
||||
</div>
|
||||
|
||||
<h2>By using this bank you acknowledge</h2>
|
||||
<ul>
|
||||
<li>GOA is for exploration and testing only.</li>
|
||||
<li>There is no guaranteed availability, support, or uptime.</li>
|
||||
<li>Operators may reset accounts, balances, or configuration without notice.</li>
|
||||
<li>Registration data and wire-style transfers may identify account holders to operators.</li>
|
||||
<li>Software is provided as-is, without warranty.</li>
|
||||
</ul>
|
||||
<p>If you do not agree, do not use this bank.</p>
|
||||
|
||||
<h2>Related</h2>
|
||||
<ul>
|
||||
<li><a href="https://bank.hacktivism.ch/intro/">Bank intro</a></li>
|
||||
<li><a href="https://exchange.hacktivism.ch/terms">Exchange terms (GOA)</a></li>
|
||||
<li><a href="https://taler.hacktivism.ch/terms">Merchant terms</a></li>
|
||||
<li><a href="https://bank.hacktivism.ch/privacy">Bank privacy</a></li>
|
||||
<li><a href="https://bank.hacktivism.ch/webui/">Bank UI</a></li>
|
||||
</ul>
|
||||
|
||||
<h2>Privacy</h2>
|
||||
<p class="muted">Processing under Swiss FADP (revDSG). What data is retained
|
||||
(accounts, ledger, logs, …) is listed on
|
||||
<a href="https://bank.hacktivism.ch/privacy">/privacy</a>.</p>
|
||||
<footer class="muted">bank.hacktivism.ch · landing terms</footer>
|
||||
</body>
|
||||
</html>
|
||||