Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

10 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

CLINK Bitcoin Lightning Payments for OpenCart 4

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...).

Features

  • 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

Requirements

  • OpenCart 4.x (PHP 8.1+)
  • A CLINK-compatible merchant wallet/node to generate your noffer1... pointer (ShockWallet, ZEUS, Lightning.Pub)

Installation

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/.

Option A — OC4 Marketplace Installer (recommended)

  1. In OpenCart admin go to Extensions → Installer
  2. Click Upload, select clink.ocmod.zip
  3. Follow the install wizard (Upload → Install → Vendor) — 44 files, no errors expected
  4. Go to Extensions → Extensions, choose type Payments
  5. Find CLINK Payment, click Install (creates the oc_clink_transaction and oc_clink_subscription tables)
  6. Click Edit to configure settings

Option B — Manual copy (existing installs)

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) and upload/{admin,catalog}/.../extension/clink/ (app-path copies).

Configuration

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

Getting your nOffer pointer

  1. Install ShockWallet, ZEUS, or Lightning.Pub
  2. Generate a new noffer1... pointer
  3. Paste the pointer into Merchant nOffer Pointer
  4. 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.

How it works

Checkout flow (one-time)

  1. Customer picks Bitcoin Lightning (CLINK) at checkout
  2. OpenCart renders the payment form inside #checkout-payment (via checkout/confirm.confirm)
  3. clink-checkout.js calls getInvoiceData → server creates a pending transaction (oc_clink_transaction)
  4. 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
  5. 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
  6. recordBolt11 persists the invoice; the client polls confirmPayment every poll_interval
  7. 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

Recurring subscriptions (nDebit)

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.

Merchant webhook

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).

Troubleshooting

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 check document.readyState so they start even when injected after page load. If you see a stuck spinner, hard-refresh and confirm the module scripts are served with Content-Type: text/javascript (Apache/Nginx must not serve .mjs as application/octet-stream).
  • Relay responses ignored — relays may send ["EVENT", <sub>, <event>] (3 elements); the client uses data[2] with data[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-upload clink.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 [] (not false) on a miss, so the code short-circuited to a cached 0 → fallback; and CoinGecko rejects requests without a User-Agent (HTTP 403). Fixed in 1.0.2 — $cached is now checked truthily and the request sends User-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 to api.coingecko.com is allowed.

File structure

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

Compatible wallets

Wallet Offers Debits
ShockWallet
ZEUS
Amethyst
Lightning.Pub

Documentation

Full docs live in docs/: Installation, Configuration, Architecture, and Troubleshooting. See CHANGELOG.md for release history.

Related projects

License

GPL-3.0