vendure-plugin-bitcoin-lightning-via-clink

August 1, 2026 · View on GitHub

npm version Vendure TypeScript License: GPL v3 CLINK

Bitcoin Lightning payments for Vendure via the CLINK protocol — Nostr-native static payment codes.

Accept Bitcoin Lightning payments in your Vendure store using CLINK (noffer1...) static payment codes. Customers pay directly from any CLINK-compatible wallet — no invoices to generate, no payment pages to build.


Features

  • CLINK Protocol — Nostr-native static payment codes (noffer1... bech32 strings)
  • Nostr Relay Integration — Real-time payment monitoring via Kind 21001 events
  • NIP-44 Encryption — End-to-end encrypted payment communication
  • Per-Channel Config — Each Vendure channel gets its own Nostr keypair and relay config
  • HTTP Webhook FallbackPOST /payments/clink for wallets without Nostr support
  • GraphQL API — Shop API for storefronts, Admin API for management
  • Storefront React Component — Ready-to-use ClinkPayment component with QR display
  • Auto-Expiry — Configurable payment offer expiration
  • Zero Invoice Generation — Static offers eliminate per-payment invoice creation

Architecture

┌─────────────────────────────────────────────────────┐
│                    Vendure Server                     │
│                                                       │
│  ┌──────────────┐    ┌─────────────────────────────┐ │
│  │  Shop API     │    │  Admin API                  │ │
│  │  clinkOffer   │    │  clinkConfig                │ │
│  │  clinkPayment │    │  clinkOffers                │ │
│  │  Status       │    │  updateClinkConfig          │ │
│  └──────┬───────┘    └──────────┬──────────────────┘ │
│         │                       │                     │
│  ┌──────▼───────────────────────▼──────────────────┐ │
│  │              ClinkPlugin                        │ │
│  │  ┌─────────────┐  ┌──────────────────────────┐  │ │
│  │  │ ClinkService │  │ PaymentMonitorService    │  │ │
│  │  │ - createOffer│  │ - Nostr relay listener   │  │ │
│  │  │ - getOffer   │  │ - NIP-44 decrypt/encrypt │  │ │
│  │  │ - markPaid   │  │ - Receipt handler        │  │ │
│  │  └──────┬──────┘  └────────────┬─────────────┘  │ │
│  │         │                      │                  │ │
│  │  ┌──────▼──────────────────────▼──────────────┐  │ │
│  │  │        ClinkPaymentHandler                 │  │ │
│  │  │  code: "clink-lightning"                   │  │ │
│  │  │  createPayment → ClinkOffer (noffer1...)   │  │ │
│  │  │  settlePayment → verify Nostr receipt      │  │ │
│  │  │  cancelPayment → mark offer expired        │  │ │
│  │  └────────────────────────────────────────────┘  │ │
│  └─────────────────────────────────────────────────┘ │
│         │                                            │
│  ┌──────▼──────────────────────────────────────────┐ │
│  │            CLINK SDK (@shocknet/clink-sdk)      │ │
│  │  nofferEncode() → noffer1... bech32 string      │ │
│  │  ClinkSDK → Nostr relay communication           │ │
│  └─────────────────────────────────────────────────┘ │
└───────────────────────┬─────────────────────────────┘

         ┌──────────────▼──────────────┐
         │     Nostr Relay Network     │
         │  - Kind 21001 events        │
         │  - NIP-44 encrypted msgs    │
         │  - Payment confirmations    │
         └──────────────┬──────────────┘

         ┌──────────────▼──────────────┐
         │   CLINK-Compatible Wallet   │
         │   ShockWallet, ZEUS, etc.   │
         │   Scans noffer → Pays LN    │
         └─────────────────────────────┘

Quick Start

1. Install

npm install vendure-plugin-bitcoin-lightning-via-clink

2. Configure

// vendure-config.ts
import { ClinkPlugin } from 'vendure-plugin-bitcoin-lightning-via-clink';

export const config: VendureConfig = {
  plugins: [
    ClinkPlugin.init({
      relays: ['wss://relay.shocknetwork.com'],
      autoSettle: true,
      offerExpiryMinutes: 30,
      httpFallback: true,
      webhookSecret: process.env.CLINK_WEBHOOK_SECRET || '',
      network: 'mainnet',
    }),
  ],
  // ... rest of config
};

3. Add Payment Method

In the Vendure Admin UI, go to Settings > Payment Methods and add Bitcoin Lightning (CLINK).

4. Accept Payments

Customers can now select CLINK at checkout and pay from any compatible wallet.


Plugin Options

OptionTypeDefaultDescription
relaysstring[]['wss://relay.shocknetwork.com']Nostr relay URLs
autoSettlebooleantrueAuto-confirm payments when Nostr receipt received
offerExpiryMinutesnumber30Minutes before payment offer expires
httpFallbackbooleantrueEnable HTTP webhook at /payments/clink
networkstring'mainnet'Bitcoin network for invoice validation: 'mainnet', 'testnet', or 'regtest'
webhookSecretstring''HMAC secret for webhook auth. Required when httpFallback: true
nostrSecretKeystring''Global Nostr key (hex). Per-channel keys generated if empty

GraphQL API

Shop API (Customer-Facing)

# Get CLINK offer for an order
query {
  clinkOffer(orderCode: "ORDER_001") {
    offerId
    noffer          # noffer1... bech32 string for wallet
    amountSats
    status
    expiresAt
  }
}

# Check payment status
query {
  clinkPaymentStatus(orderCode: "ORDER_001") {
    offerId
    status          # pending | paid | expired
    preimage        # Payment proof (available after settlement)
    bolt11Invoice
  }
}

Admin API

# Get channel CLINK config
query {
  clinkConfig(channelId: "1") {
    nostrPubkey
    relayUrls
    autoSettle
    offerExpiryMinutes
  }
}

# List all offers
query {
  clinkOffers(status: "pending", limit: 20) {
    totalItems
    items {
      offerId
      noffer
      amountSats
      status
      paidAt
    }
  }
}

# Update channel config
mutation {
  updateClinkConfig(
    channelId: "1",
    input: {
      relayUrls: ["wss://relay.shocknetwork.com"]
      autoSettle: true
      offerExpiryMinutes: 60
    }
  ) {
    nostrPubkey
    relayUrls
  }
}

Storefront Integration

React Component

import { ClinkPayment } from './ClinkPayment';

function CheckoutPage({ orderCode }: { orderCode: string }) {
  return (
    <ClinkPayment
      orderCode={orderCode}
      apiEndpoint="https://your-store.com/shop-api"
      onPaymentConfirmed={(preimage) => {
        console.log('Payment confirmed!', preimage);
        // Redirect to order confirmation
      }}
      onPaymentExpired={() => {
        console.log('Payment expired');
        // Show retry option
      }}
    />
  );
}

Using Vendure's addPaymentToOrder Mutation

mutation AddClinkPayment {
  addPaymentToOrder(
    input: {
      method: "clink-lightning"
      metadata: {}
    }
  ) {
    ... on Order {
      id
      code
      total
      state
    }
    ... on ErrorResult {
      errorCode
      message
    }
  }
}

HTTP Webhook

For wallets without Nostr support, the HTTP fallback accepts:

POST /payments/clink
Content-Type: application/json

{
  "offerId": "vendure_ORDER_001_1234567890",
  "bolt11": "lnbc1...",
  "preimage": "hex_preimage"
}

Database Tables

The plugin creates two tables automatically:

  • clink_offer — Payment offers linked to Vendure orders
  • clink_channel_config — Per-channel Nostr keys and relay configuration

Development

Test App

# Build plugin and copy to test-app
npm run dev:test-app

# Start test Vendure server
cd test-app && npm run dev

Server runs at http://localhost:3000:

  • Shop API: http://localhost:3000/shop-api
  • Admin API: http://localhost:3000/admin-api

Build

npm run build          # Compile TypeScript
npm run typecheck      # Type-check without emitting
npm run dev            # Watch mode

  1. Merchant configures CLINK plugin with Nostr relay URLs
  2. Customer adds item to cart and proceeds to checkout
  3. Plugin generates a noffer1... static payment code linked to the order
  4. Customer scans/pastes the offer in a CLINK-compatible wallet
  5. Wallet resolves the offer via Nostr relay and pays the Lightning invoice
  6. Nostr relay forwards payment receipt (Kind 21001, NIP-44 encrypted)
  7. Plugin decrypts receipt, verifies payment, marks offer as paid
  8. Order advances to the next state

Key Concepts

  • noffer1...: Bech32-encoded static payment code (like a Lightning address, but for offers)
  • Nostr Kind 21001: CLINK protocol event type for payment communication
  • NIP-44: Nostr encryption standard for end-to-end encrypted payment messages
  • Per-channel keys: Each Vendure channel gets its own Nostr identity

License

GPL v3


Conversations