Customer checkout (clink.clink selected)
│
▼
checkout/confirm.confirm ── creates order, renders payment form
│ into #checkout-payment via jQuery .load()
▼
clink-checkout.js (ES module, loaded dynamically)
│
├─ GET getInvoiceData → creates pending row in oc_clink_transaction
│
├─ decode noffer → merchant pubkey / relay / offer_id
├─ generate ephemeral keypair
├─ kind 21001 event: { ["p", merchant], ["clink_version","1"],
│ content: NIP-44(payload) }
│ payload: { offer, amount_sats, description, expires_in_seconds }
├─ send EVENT + REQ to relay
│
▼
merchant node replies with kind 21001 (e-tag = request id, p-tag = payer)
│
▼
client matches by p/e tags → NIP-44 decrypt → bolt11 invoice
│
├─ POST recordBolt11 → persists bolt11 on the transaction
├─ render QR + lightning: link, countdown, poll confirmPayment
│
▼
payment arrives → merchant node → webhook (status=paid + preimage)
│
▼
client poll sees paid → order history updated → redirect to success
- Request event:
kind: 21001, encrypted with NIP-44 (v2 conversation key from the ephemeral secret key and the merchant's pubkey), signed with the ephemeral key. - Relay message format: strfry-style relays send
["EVENT", <sub_id>, <event>](3 elements). The client readsdata[2]with adata[1]fallback. - Response matching: the response event must carry
p= payer pubkey ande= request event id. - noffer decoding: bech32, charset
qpzry9x8gf2tvdw0s3jn54khce6mua7l; TLV 0 = pubkey hex, TLV 1 = relay utf8, TLV 2 = offer_id utf8.
- The catalog controller lives at
extension/clink/catalog/controller/payment/clink.php; all routes are underextension/clink/payment/.... - OC4 checkout reloads the confirm step with
$('#checkout-confirm').load('index.php?route=checkout/confirm.confirm...'). jQuery 3.7 strips<script type="module">tags from injected HTML, so the template loads the ES modules from an inline classic script (document.createElement('script')) and each module checksdocument.readyStatebefore registering aDOMContentLoadedlistener. - OC4 Twig has no globals — every URL/asset path is computed in the controller (
catalog_view_js_clink,payment_*_url) and passed as template data.
clink.ocmod.zip (zip root = extension/clink/ on disk)
├── install.json
├── admin/
│ ├── controller/payment/clink.php # settings, install/uninstall
│ ├── controller/event/clink_event.php # event hooks
│ ├── model/payment/clink.php # creates oc_clink_* tables
│ ├── language/en-gb/payment/
│ └── view/template/payment/
└── catalog/
├── controller/payment/clink.php # index, getInvoiceData, confirmPayment,
│ # recordBolt11, webhook, subscribe, renew
├── controller/payment/clink_renew.php
├── model/payment/clink.php
├── language/en-gb/payment/clink.php
├── view/template/payment/clink.twig
└── view/javascript/clink/
├── clink-checkout.js # main client
├── clink-price-converter.js # CoinGecko / fixed rate
├── clink-subscription.js # nDebit enrollment
└── lib/clink-nostr.mjs # vendored nostr-tools (58 kB)
Source trees in the repository:
upload/extension/clink/— native layout, used to build the zipupload/{admin,catalog}/.../extension/clink/— app-path copies for manual installsupload/catalog/view/javascript/clink/— shared JS sources
| Table | Purpose |
|---|---|
oc_clink_transaction |
One row per payment attempt: order, method (noffer/ndebit), bolt11, amounts, rate, status (pending/paid/expired/failed), preimage, nostr event id |
oc_clink_subscription |
nDebit enrollments for recurring payments |