Accept Bitcoin Lightning payments in your OpenCart 4 store via the CLINK protocol (Common Lightning Interface for Nostr Keys).
Supports one-time payments (CLINK Offers / noffer1...) and recurring subscriptions (CLINK Debits / ndebit1...).
- Announcement: https://stacker.news/items/1536984/r/06bc1a977d
- OpenCart Marketplace: https://www.opencart.com/index.php?route=marketplace/extension/info&member_token=e43e47714006c68af65cc6db7552b2a5&extension_id=49280
- One-time payments — customers pay a Lightning invoice generated from your
noffer1...pointer - Recurring subscriptions — auto-renewal via nDebit pull payments, no intermediaries
- QR-code checkout — scan-to-pay with any CLINK-compatible wallet (ShockWallet, ZEUS, Amethyst)
- Fiat → sats conversion — live CoinGecko rates with 5-minute cache, optional fixed-rate fallback
- NIP-44 encrypted invoice requests — signed Nostr kind-21001 events, no secrets on the relay
- Dual confirmation — merchant webhook receipt + AJAX polling fallback
- No API keys — CLINK uses Nostr cryptographic identity; no accounts or third-party payment processor
- OpenCart 4.x (PHP 8.1+)
- A CLINK-compatible merchant wallet/node to generate your
noffer1...pointer (ShockWallet, ZEUS, Lightning.Pub)
The clink.ocmod.zip uses the OC4 native extension layout — files at the zip root under admin/ and catalog/, matching what the marketplace installer copies into extension/clink/.
- In OpenCart admin go to Extensions → Installer
- Click Upload, select
clink.ocmod.zip - Follow the install wizard (Upload → Install → Vendor) — 44 files, no errors expected
- Go to Extensions → Extensions, choose type Payments
- Find CLINK Payment, click Install (creates the
oc_clink_transactionandoc_clink_subscriptiontables) - Click Edit to configure settings
Copy the app-path tree into your OpenCart root:
upload/admin/.../extension/clink/* → admin/.../extension/clink/
upload/catalog/.../extension/clink/* → catalog/.../extension/clink/
upload/catalog/view/javascript/clink/* → catalog/view/javascript/clink/
Then register the payment extension (Extensions → Extensions → Payments → Install) and configure it.
Note: keep the two source trees in sync when developing —
upload/extension/clink/(native layout, zipped) andupload/{admin,catalog}/.../extension/clink/(app-path copies).
| Setting | Description | Default |
|---|---|---|
| Status | Enable/disable the payment method | Disabled |
| Merchant nOffer Pointer | Your noffer1... bech32 string (encodes pubkey + relay + offer id) |
— |
| Merchant Public Key | Your Nostr hex public key (64 chars) | — |
| Relay URL | Nostr relay your CLINK node listens on | wss://relay.clinkme.dev |
| Fiat Currency | Currency for price conversion | Store default |
| Fixed BTC Rate | Fallback BTC price (empty = live CoinGecko) | — |
| Invoice Timeout | Seconds before the invoice expires | 600 |
| Poll Interval | Milliseconds between payment-status checks | 5000 |
| Subscriptions (nDebit) | Allow auto-renewal for subscription products | Disabled |
| Order Status | Status applied when an order is created | — |
| Geo Zone / Sort Order | Standard payment-extension options | — |
- Install ShockWallet, ZEUS, or Lightning.Pub
- Generate a new
noffer1...pointer - Paste the pointer into Merchant nOffer Pointer
- Copy the matching Nostr public key (hex) into Merchant Public Key
The pointer itself encodes the pubkey, relay, and offer id — the pubkey field is used as a sanity check.
- Customer picks Bitcoin Lightning (CLINK) at checkout
- OpenCart renders the payment form inside
#checkout-payment(viacheckout/confirm.confirm) clink-checkout.jscallsgetInvoiceData→ server creates a pending transaction (oc_clink_transaction)- The client decodes the
noffer, generates an ephemeral keypair, and sends a signed, NIP-44-encrypted kind-21001 event (["p", <merchant>]+["clink_version","1"]) to the relay - The merchant node replies with a kind-21001 event; the client matches it by
e-tag +p-tag, decrypts the bolt11 invoice, and displays it with a QR code recordBolt11persists the invoice; the client pollsconfirmPaymenteverypoll_interval- When the merchant node confirms payment (webhook →
status=paid+ preimage), the order is updated to the configured status and the customer is redirected to success
After the first payment the customer is offered auto-renewal; they generate an ndebit1... pointer which is stored against the subscription, and renewals issue CLINK debit requests.
Configure your CLINK node to POST {"bolt11": "...", "preimage": "..."} to:
https://YOUR-STORE/index.php?route=extension/clink/payment/clink.webhook
If a webhook_secret is set, the node must send it as X-Webhook-Signature (HMAC-SHA256 of the raw body).
The checkout form shows a "Processing your payment…" spinner until an invoice arrives. The usual causes and their fixes:
- Invoice never appears (stuck spinner) — the OpenCart 4 checkout injects the payment form into the DOM via
$('#checkout-confirm').load(...), and jQuery strips<script type="module">tags. The template therefore loads the CLINK modules from an inline classic script (document.createElement('script')), and the modules checkdocument.readyStateso they start even when injected after page load. If you see a stuck spinner, hard-refresh and confirm the module scripts are served withContent-Type: text/javascript(Apache/Nginx must not serve.mjsasapplication/octet-stream). - Relay responses ignored — relays may send
["EVENT", <sub>, <event>](3 elements); the client usesdata[2]withdata[1]fallback. - "Invoice amount mismatch" in the log — the bolt11 amount parser converts bolt11 units correctly (
p→/10000,n→/10,u→×100,m→×100000). This was fixed in 1.0.1. - Empty Payments list after install — the zip must be OC4-native (files at the zip root, not under
upload/). Re-uploadclink.ocmod.zip. - Fallback BTC rate always used (1.0.1 and earlier) — two bugs kept the live CoinGecko rate from ever being fetched: the OpenCart file cache returns
[](notfalse) on a miss, so the code short-circuited to a cached0→ fallback; and CoinGecko rejects requests without aUser-Agent(HTTP 403). Fixed in 1.0.2 —$cachedis now checked truthily and the request sendsUser-Agent: OpenCart-CLINK/1.0.1. If you still see "No rate available for USD, using fallback 65000" in the error log, clear the OC file cache (system/storage/cache/cache.clink_rate_*.json) and make sure outbound HTTPS toapi.coingecko.comis allowed.
clink.ocmod.zip # OC4-native install package (44 files)
├── install.json # extension metadata (name, code, version)
├── admin/ # → extension/clink/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/ # admin language strings
│ └── view/template/payment/ # settings + dashboard twig
└── catalog/ # → extension/clink/catalog/
├── controller/payment/clink.php # index, getInvoiceData, confirmPayment,
│ # recordBolt11, webhook, subscribe, renew
├── controller/payment/clink_renew.php # subscription renewal
├── model/payment/clink.php
├── language/en-gb/payment/clink.php
├── view/template/payment/clink.twig # checkout form + config + module loader
└── view/javascript/clink/
├── clink-checkout.js # main client (noffer decode, NIP-44, relay)
├── clink-price-converter.js # CoinGecko / fixed-rate
├── clink-subscription.js # nDebit enrollment
└── lib/clink-nostr.mjs # vendored nostr-tools bundle
Source trees:
upload/extension/clink/ # native layout used to build the zip
upload/{admin,catalog}/.../extension/clink/ # app-path copies (manual installs)
upload/catalog/view/javascript/clink/ # shared JS sources
| Wallet | Offers | Debits |
|---|---|---|
| ShockWallet | ✅ | ✅ |
| ZEUS | ✅ | — |
| Amethyst | ✅ | — |
| Lightning.Pub | ✅ | ✅ |
Full docs live in docs/: Installation, Configuration, Architecture, and Troubleshooting. See CHANGELOG.md for release history.
- Woo-CLINK — WooCommerce version
- CLINK protocol — protocol documentation
- CLINK SDK — TypeScript SDK
GPL-3.0