Feature: Membership-Based Discount System with Lightning Integration #1

Open
opened 2026-01-02 18:59:55 +00:00 by padreug · 0 comments
Owner

Overview

This issue documents the implementation plan for a membership/loyalty system that allows users to receive tiered discounts AND automatic Lightning wallet integration by scanning a single membership ID (QR code or NFC) at the ATM.

Key Features:

  1. One scan, two benefits: Membership ID provides both discount tier AND Lightning wallet address
  2. Cash-in (buying BTC): No wallet QR needed - server already knows user's Lightning address
  3. Cash-out (selling BTC): User still presents Lightning invoice (but could be pre-filled)
  4. Tiered discounts: Bronze/Silver/Gold/Platinum with configurable percentages

Prerequisites

API Key Authentication

Before implementing this feature, we need API key authentication for programmatic access. See #2 for details.


User Flows

Cash-In Flow (User buys BTC with cash)

User starts cash-in transaction
    ↓
"Do you have a membership card?" [Yes] [No]
    ↓ (Yes)
Scan membership QR code
    ↓
Server validates: user_12345 → Gold tier (15% discount) + Lightning address
    ↓
"Welcome Gold Member! 15% discount applied"
"Bitcoin will be sent to your wallet automatically"
    ↓
User inserts cash
    ↓
Server sends BTC to user's Lightning address (no wallet scan needed!)

Cash-Out Flow (User sells BTC for cash)

User starts cash-out transaction
    ↓
"Do you have a membership card?" [Yes] [No]
    ↓ (Yes)
Scan membership QR code
    ↓
Server validates: Gold tier (15% discount applied)
    ↓
User scans Lightning invoice to receive payment
    ↓
User receives cash, pays Lightning invoice

Part 1: Database Schema Changes

New Tables

-- Discount tiers (e.g., Bronze, Silver, Gold, Platinum)
CREATE TABLE discount_tiers (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    name VARCHAR(50) NOT NULL UNIQUE,
    display_name VARCHAR(100) NOT NULL,
    discount_percentage INT NOT NULL,
    priority INT DEFAULT 0,
    created TIMESTAMPTZ DEFAULT NOW(),
    enabled BOOLEAN DEFAULT TRUE
);

-- External membership IDs linked to discount tiers AND Lightning wallets
CREATE TABLE memberships (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    external_user_id VARCHAR(255) NOT NULL UNIQUE,
    tier_id UUID REFERENCES discount_tiers(id),
    customer_id UUID REFERENCES customers(id),
    lightning_address VARCHAR(255),
    lightning_address_type VARCHAR(20),
    metadata JSONB,
    created TIMESTAMPTZ DEFAULT NOW(),
    last_used TIMESTAMPTZ,
    enabled BOOLEAN DEFAULT TRUE
);

-- Audit log for membership usage
CREATE TABLE membership_usage (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    membership_id UUID REFERENCES memberships(id),
    device_id VARCHAR(255),
    tx_id UUID,
    discount_applied INT,
    created TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX idx_memberships_external_id ON memberships(external_user_id);
CREATE INDEX idx_memberships_tier ON memberships(tier_id);

Seed Default Tiers

INSERT INTO discount_tiers (name, display_name, discount_percentage, priority) VALUES
    ('bronze', 'Bronze Member', 5, 1),
    ('silver', 'Silver Member', 10, 2),
    ('gold', 'Gold Member', 15, 3),
    ('platinum', 'Platinum Member', 20, 4);

Part 2: Server API Changes

New Files

  1. packages/server/lib/membership.js - Core membership validation service
  2. packages/server/lib/membership-lightning.js - Lightning Address resolution
  3. packages/server/lib/routes/membershipRoutes.js - REST endpoints for machine

REST Endpoints

  • POST /membership/validate - Validate membership ID, return tier + discount
  • POST /membership/invoice - Get Lightning invoice for a member (for cash-in)

GraphQL API (Admin)

type DiscountTier {
    id: ID!
    name: String!
    displayName: String!
    discountPercentage: Int!
    priority: Int!
    enabled: Boolean!
}

type Membership {
    id: ID!
    externalUserId: String!
    tier: DiscountTier!
    customer: Customer
    lightningAddress: String
    lightningAddressType: String
    metadata: JSONObject
    created: DateTimeISO!
    lastUsed: DateTimeISO
    enabled: Boolean!
}

type Mutation {
    upsertMembership(
        externalUserId: String!
        tierName: String!
        lightningAddress: String
        metadata: JSONObject
    ): Membership @auth
}

Part 3: Machine Changes (lamassu-machine)

Modified Cash-In Flow

For members, skip the wallet address scan entirely:

Normal flow (no membership):
    chooseCoin → scanAddress → insertBills → sendCoins

Member flow (with membership):
    chooseCoin → membershipScan → insertBills → sendCoins
                      ↓
              (server returns Lightning address)
                      ↓
              (no wallet scan needed!)

New States

  • membershipPrompt - "Do you have a membership card?"
  • membershipScan - Scan QR code
  • membershipResult - "Welcome Gold Member! 15% discount applied"
  • membershipInvalid - "Membership not recognized"

New Trader Methods

Trader.prototype.validateMembership = function(membershipId)
Trader.prototype.getMembershipInvoice = function(membershipId, amountSats)

Part 4: Lightning Address Support

Users can connect various Lightning wallets that support Lightning Addresses:

Custodial

  • Wallet of Satoshi: user@walletofsatoshi.com
  • Alby: user@getalby.com
  • Blink: user@blink.sv
  • Strike: user@strike.me

Self-Custodial

  • Phoenix Wallet (via LNURL-pay)
  • Breez (via LNURL-pay)
  • Zeus (connect to own node)

Implementation Checklist

Phase 1: Database & Core API

  • Create database migration for new tables
  • Implement membership.js service
  • Implement membership-lightning.js for Lightning Address resolution
  • Add /membership/validate REST endpoint
  • Add /membership/invoice REST endpoint
  • Add GraphQL types and resolvers for admin API

Phase 2: Machine Integration

  • Add new states to brain.js state machine
  • Implement membership scan flow
  • Modify cash-in flow to skip wallet scan for members
  • Create UI screens
  • Add trader methods

Phase 3: Lightning Integration

  • Test Lightning Address resolution
  • Test LNURL-pay flow
  • Handle edge cases (amount limits, offline wallets)

Phase 4: Web App Integration

  • API for syncing memberships
  • QR code generation for users

  • #2 - API Key Authentication (prerequisite)

References

  • Full implementation plan: membership-discount-plan.md in lamassu-stuff repo
  • API key auth plan: api-key-auth-plan.md in lamassu-stuff repo
## Overview This issue documents the implementation plan for a membership/loyalty system that allows users to receive tiered discounts AND automatic Lightning wallet integration by scanning a single membership ID (QR code or NFC) at the ATM. **Key Features:** 1. **One scan, two benefits**: Membership ID provides both discount tier AND Lightning wallet address 2. **Cash-in (buying BTC)**: No wallet QR needed - server already knows user's Lightning address 3. **Cash-out (selling BTC)**: User still presents Lightning invoice (but could be pre-filled) 4. **Tiered discounts**: Bronze/Silver/Gold/Platinum with configurable percentages --- ## Prerequisites ### API Key Authentication Before implementing this feature, we need API key authentication for programmatic access. See #2 for details. --- ## User Flows ### Cash-In Flow (User buys BTC with cash) ``` User starts cash-in transaction ↓ "Do you have a membership card?" [Yes] [No] ↓ (Yes) Scan membership QR code ↓ Server validates: user_12345 → Gold tier (15% discount) + Lightning address ↓ "Welcome Gold Member! 15% discount applied" "Bitcoin will be sent to your wallet automatically" ↓ User inserts cash ↓ Server sends BTC to user's Lightning address (no wallet scan needed!) ``` ### Cash-Out Flow (User sells BTC for cash) ``` User starts cash-out transaction ↓ "Do you have a membership card?" [Yes] [No] ↓ (Yes) Scan membership QR code ↓ Server validates: Gold tier (15% discount applied) ↓ User scans Lightning invoice to receive payment ↓ User receives cash, pays Lightning invoice ``` --- ## Part 1: Database Schema Changes ### New Tables ```sql -- Discount tiers (e.g., Bronze, Silver, Gold, Platinum) CREATE TABLE discount_tiers ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), name VARCHAR(50) NOT NULL UNIQUE, display_name VARCHAR(100) NOT NULL, discount_percentage INT NOT NULL, priority INT DEFAULT 0, created TIMESTAMPTZ DEFAULT NOW(), enabled BOOLEAN DEFAULT TRUE ); -- External membership IDs linked to discount tiers AND Lightning wallets CREATE TABLE memberships ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), external_user_id VARCHAR(255) NOT NULL UNIQUE, tier_id UUID REFERENCES discount_tiers(id), customer_id UUID REFERENCES customers(id), lightning_address VARCHAR(255), lightning_address_type VARCHAR(20), metadata JSONB, created TIMESTAMPTZ DEFAULT NOW(), last_used TIMESTAMPTZ, enabled BOOLEAN DEFAULT TRUE ); -- Audit log for membership usage CREATE TABLE membership_usage ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), membership_id UUID REFERENCES memberships(id), device_id VARCHAR(255), tx_id UUID, discount_applied INT, created TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX idx_memberships_external_id ON memberships(external_user_id); CREATE INDEX idx_memberships_tier ON memberships(tier_id); ``` ### Seed Default Tiers ```sql INSERT INTO discount_tiers (name, display_name, discount_percentage, priority) VALUES ('bronze', 'Bronze Member', 5, 1), ('silver', 'Silver Member', 10, 2), ('gold', 'Gold Member', 15, 3), ('platinum', 'Platinum Member', 20, 4); ``` --- ## Part 2: Server API Changes ### New Files 1. **`packages/server/lib/membership.js`** - Core membership validation service 2. **`packages/server/lib/membership-lightning.js`** - Lightning Address resolution 3. **`packages/server/lib/routes/membershipRoutes.js`** - REST endpoints for machine ### REST Endpoints - `POST /membership/validate` - Validate membership ID, return tier + discount - `POST /membership/invoice` - Get Lightning invoice for a member (for cash-in) ### GraphQL API (Admin) ```graphql type DiscountTier { id: ID! name: String! displayName: String! discountPercentage: Int! priority: Int! enabled: Boolean! } type Membership { id: ID! externalUserId: String! tier: DiscountTier! customer: Customer lightningAddress: String lightningAddressType: String metadata: JSONObject created: DateTimeISO! lastUsed: DateTimeISO enabled: Boolean! } type Mutation { upsertMembership( externalUserId: String! tierName: String! lightningAddress: String metadata: JSONObject ): Membership @auth } ``` --- ## Part 3: Machine Changes (lamassu-machine) ### Modified Cash-In Flow For members, skip the wallet address scan entirely: ``` Normal flow (no membership): chooseCoin → scanAddress → insertBills → sendCoins Member flow (with membership): chooseCoin → membershipScan → insertBills → sendCoins ↓ (server returns Lightning address) ↓ (no wallet scan needed!) ``` ### New States - `membershipPrompt` - "Do you have a membership card?" - `membershipScan` - Scan QR code - `membershipResult` - "Welcome Gold Member! 15% discount applied" - `membershipInvalid` - "Membership not recognized" ### New Trader Methods ```javascript Trader.prototype.validateMembership = function(membershipId) Trader.prototype.getMembershipInvoice = function(membershipId, amountSats) ``` --- ## Part 4: Lightning Address Support Users can connect various Lightning wallets that support Lightning Addresses: ### Custodial - Wallet of Satoshi: `user@walletofsatoshi.com` - Alby: `user@getalby.com` - Blink: `user@blink.sv` - Strike: `user@strike.me` ### Self-Custodial - Phoenix Wallet (via LNURL-pay) - Breez (via LNURL-pay) - Zeus (connect to own node) --- ## Implementation Checklist ### Phase 1: Database & Core API - [ ] Create database migration for new tables - [ ] Implement `membership.js` service - [ ] Implement `membership-lightning.js` for Lightning Address resolution - [ ] Add `/membership/validate` REST endpoint - [ ] Add `/membership/invoice` REST endpoint - [ ] Add GraphQL types and resolvers for admin API ### Phase 2: Machine Integration - [ ] Add new states to brain.js state machine - [ ] Implement membership scan flow - [ ] Modify cash-in flow to skip wallet scan for members - [ ] Create UI screens - [ ] Add trader methods ### Phase 3: Lightning Integration - [ ] Test Lightning Address resolution - [ ] Test LNURL-pay flow - [ ] Handle edge cases (amount limits, offline wallets) ### Phase 4: Web App Integration - [ ] API for syncing memberships - [ ] QR code generation for users --- ## Related Issues - #2 - API Key Authentication (prerequisite) ## References - Full implementation plan: `membership-discount-plan.md` in lamassu-stuff repo - API key auth plan: `api-key-auth-plan.md` in lamassu-stuff repo
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: aiolabs/lamassu-server#1
No description provided.