BILLING SYSTEM - nself-org/nchat GitHub Wiki
Complete billing system with Stripe subscriptions, crypto payments, and token gating.
The nself-chat billing system provides:
- Stripe Subscriptions - Traditional card-based recurring billing
- Plan-Based Restrictions - Feature gating and usage limits
- Crypto Payments - Support for cryptocurrency payments
- NFT Token Gating - Channel access based on NFT ownership
- Usage Tracking - Real-time monitoring of plan limits
- Admin Dashboard - Revenue analytics and subscription management
- Price: $0/month
-
Features:
- Up to 10 users
- 5 channels
- 10,000 messages/month
- 5GB storage
- Video calls (4 participants)
- Basic features only
- Price: $8/month or $80/year
-
Features:
- Up to 50 users
- 25 channels
- 100,000 messages/month
- 50GB storage
- Custom branding
- API access
- Video calls (10 participants)
- Price: $25/month or $250/year
-
Features:
- Up to 200 users
- 100 channels
- 500,000 messages/month
- 250GB storage
- Advanced analytics
- Priority support
- SSO integration
- Token gating
- Crypto payments
- Video calls (25 participants)
- Price: $75/month or $750/year
-
Features:
- Up to 1,000 users
- 500 channels
- 2,000,000 messages/month
- 1TB storage
- All Pro features
- SLA guarantee
- Video calls (100 participants)
- Price: Custom pricing
-
Features:
- Unlimited everything
- Dedicated support
- Custom integrations
- On-premise deployment option
interface UsageMetrics {
users: number
channels: number
messages: number
storageGB: number
integrations: number
bots: number
aiMinutes: number
aiQueries: number
callMinutes: number
recordingGB: number
}The system automatically tracks and enforces limits:
- Soft Limits (75%): Warning notification
- Hard Limits (90%): Upgrade prompt
- Exceeded (100%): Feature restriction
import { UsageTracker } from '@/lib/usage-tracker'
// Check if feature is allowed
const canUseAPI = UsageTracker.isFeatureAllowed('free', 'apiAccess') // false
const canUseAPI = UsageTracker.isFeatureAllowed('starter', 'apiAccess') // true
// Check usage limit
const { allowed, limit, percentage } = UsageTracker.checkLimit(
'free',
'maxUsers',
8 // current users
)
console.log(allowed) // true (8 < 10)
console.log(percentage) // 80- Install Stripe:
npm install stripe- Configure Environment Variables:
# .env.local
STRIPE_SECRET_KEY=sk_test_...
STRIPE_PUBLISHABLE_KEY=pk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
# Plan Price IDs
STRIPE_STARTER_MONTHLY_PRICE_ID=price_...
STRIPE_STARTER_YEARLY_PRICE_ID=price_...
STRIPE_PRO_MONTHLY_PRICE_ID=price_...
STRIPE_PRO_YEARLY_PRICE_ID=price_...// Client-side
const handleUpgrade = async () => {
const response = await fetch('/api/billing/checkout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
planId: 'pro',
interval: 'month',
userId: user.id,
userEmail: user.email,
}),
})
const { url } = await response.json()
window.location.href = url // Redirect to Stripe
}The system automatically handles these Stripe events:
customer.subscription.createdcustomer.subscription.updatedcustomer.subscription.deletedinvoice.paidinvoice.payment_failed
Webhook endpoint: /api/billing/webhook
import {
requireFeature,
requireMinimumPlan,
checkUsageLimit,
applyPlanRestrictions,
} from '@/middleware/plan-restrictions'
// In API route
export async function POST(request: NextRequest) {
// Check if user's plan has the feature
const restriction = await applyPlanRestrictions(request, requireFeature('customBranding'))
if (restriction) return restriction // 403 Forbidden
// Feature is allowed, continue...
}
// Check minimum plan tier
const restriction = await applyPlanRestrictions(request, requireMinimumPlan('pro'))
// Check usage limit
const restriction = await applyPlanRestrictions(
request,
checkUsageLimit('maxUsers', currentUserCount)
)import { PlanGate } from '@/middleware/plan-restrictions'
// Check if feature is available
const canUse = PlanGate.canUseFeature('free', 'customBranding') // false
// Get upgrade path
const upgradeTo = PlanGate.getUpgradePath('free', 'customBranding') // 'starter'
// In React component
{PlanGate.canUseFeature(userPlan, 'webhooks') && (
<WebhooksSettings />
)}- ETH (Ethereum)
- USDC (USD Coin)
- USDT (Tether)
- Ethereum (mainnet)
- Polygon
- Binance Smart Chain
- Arbitrum
import { WalletConnector } from '@/components/billing/WalletConnector'
<WalletConnector
onConnect={(info) => {
console.log('Connected:', info.address)
console.log('Network:', info.network)
console.log('Balance:', info.balance)
}}
onDisconnect={() => {
console.log('Disconnected')
}}
requiredNetwork="ethereum"
/>- User connects wallet (MetaMask, Coinbase Wallet)
- Select network and currency
- System displays payment address and amount
- User sends transaction
- System monitors blockchain for confirmation
- Subscription activated upon confirmation
import { CryptoPayment } from '@/components/billing/CryptoPayment'
<CryptoPayment
planId="pro"
interval="month"
onPaymentComplete={(txHash) => {
console.log('Payment completed:', txHash)
// Update subscription status
}}
/>Create access requirements based on:
- ERC-20 Tokens: Minimum balance required
- ERC-721 NFTs: Ownership of specific NFTs
- ERC-1155 NFTs: Ownership of specific token IDs
import type { TokenRequirement } from '@/types/billing'
const requirement: TokenRequirement = {
id: 'req-1',
channelId: 'channel-123',
tokenType: 'erc721',
contractAddress: '0x...',
network: 'ethereum',
minTokenCount: 1,
name: 'Bored Ape Yacht Club',
description: 'Must own at least 1 BAYC NFT',
enabled: true,
createdAt: new Date(),
}import { verifyTokenRequirement } from '@/lib/crypto/nft-verifier'
const result = await verifyTokenRequirement(requirement, walletAddress)
if (result.verified) {
console.log('Access granted!')
console.log('Balance:', result.balance)
} else {
console.log('Access denied:', result.error)
}import { TokenGatedChannel } from '@/components/billing/TokenGatedChannel'
<TokenGatedChannel
channelId="premium-channel"
channelName="VIP Lounge"
requirements={[requirement]}
onAccessGranted={() => {
// Grant access to channel
router.push('/channels/premium-channel')
}}
/>import { BillingDashboard } from '@/components/billing/BillingDashboard'
<BillingDashboard />Displays:
- Total revenue
- Monthly Recurring Revenue (MRR)
- Annual Recurring Revenue (ARR)
- Active subscriptions
- Churn rate
- Plan distribution
- Payment method breakdown
- Failed payments
- Upcoming renewals
import { UsageTracker } from '@/components/billing/UsageTracker'
<UsageTracker
limits={usageLimits}
onUpgrade={() => {
// Navigate to pricing page
router.push('/billing#pricing')
}}
/>-
POST /api/billing/checkout- Create Stripe checkout session -
POST /api/billing/portal- Access billing portal -
POST /api/billing/webhook- Stripe webhook handler -
GET /api/billing/subscription- Get current subscription -
PUT /api/billing/subscription- Update subscription -
DELETE /api/billing/subscription- Cancel subscription
-
POST /api/billing/crypto/payment- Create crypto payment -
GET /api/billing/crypto/status/:txHash- Check payment status -
POST /api/billing/crypto/verify- Verify wallet ownership
-
POST /api/tokens/verify- Verify token ownership -
GET /api/tokens/requirements/:channelId- Get channel requirements -
POST /api/tokens/requirements- Create token requirement (admin)
CREATE TABLE subscriptions (
id UUID PRIMARY KEY,
user_id UUID REFERENCES users(id),
plan_id TEXT NOT NULL,
status TEXT NOT NULL,
interval TEXT NOT NULL,
current_period_start TIMESTAMPTZ,
current_period_end TIMESTAMPTZ,
cancel_at_period_end BOOLEAN DEFAULT false,
stripe_subscription_id TEXT,
stripe_customer_id TEXT,
crypto_address TEXT,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);CREATE TABLE usage_metrics (
id UUID PRIMARY KEY,
user_id UUID REFERENCES users(id),
period TEXT NOT NULL, -- YYYY-MM
users INTEGER DEFAULT 0,
channels INTEGER DEFAULT 0,
messages INTEGER DEFAULT 0,
storage_gb DECIMAL(10,2) DEFAULT 0,
integrations INTEGER DEFAULT 0,
bots INTEGER DEFAULT 0,
ai_minutes INTEGER DEFAULT 0,
ai_queries INTEGER DEFAULT 0,
call_minutes INTEGER DEFAULT 0,
recording_gb DECIMAL(10,2) DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(user_id, period)
);CREATE TABLE token_requirements (
id UUID PRIMARY KEY,
channel_id UUID REFERENCES channels(id),
role_id UUID REFERENCES roles(id),
feature_id TEXT,
token_type TEXT NOT NULL,
contract_address TEXT NOT NULL,
network TEXT NOT NULL,
min_balance DECIMAL(36,18),
token_ids JSONB,
min_token_count INTEGER,
name TEXT NOT NULL,
description TEXT,
enabled BOOLEAN DEFAULT true,
created_at TIMESTAMPTZ DEFAULT NOW()
);Complete list of required environment variables:
# Stripe
STRIPE_SECRET_KEY=sk_test_...
STRIPE_PUBLISHABLE_KEY=pk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
# Plan Price IDs
STRIPE_STARTER_MONTHLY_PRICE_ID=price_...
STRIPE_STARTER_YEARLY_PRICE_ID=price_...
STRIPE_PRO_MONTHLY_PRICE_ID=price_...
STRIPE_PRO_YEARLY_PRICE_ID=price_...
STRIPE_BUSINESS_MONTHLY_PRICE_ID=price_...
STRIPE_BUSINESS_YEARLY_PRICE_ID=price_...
# Optional: Crypto
NEXT_PUBLIC_PAYMENT_WALLET_ADDRESS=0x...
INFURA_API_KEY=...
ALCHEMY_API_KEY=...
# Optional: NFT Verification
MORALIS_API_KEY=...Use Stripe test cards:
Success: 4242 4242 4242 4242
Decline: 4000 0000 0000 0002
3D Secure: 4000 0025 0000 3155
Use testnets:
- Ethereum Sepolia
- Polygon Mumbai
- BSC Testnet
Deploy test NFT contracts on testnets for verification.
- Configure production Stripe account
- Set up Stripe webhook endpoint
- Create production price IDs
- Set up crypto payment wallet
- Configure blockchain monitoring
- Set up usage tracking cron jobs
- Test all payment flows
- Configure email notifications
- Set up invoice generation
- Enable SCA compliance (Europe)
- Test subscription lifecycle
- Set up analytics tracking
- Configure tax calculation (if needed)
For issues or questions:
- Email: [email protected]
- Docs: /docs/billing
- API Reference: /api-docs/billing
-
v0.9.0 (2026-02-03): Initial billing system implementation
- Stripe subscriptions
- Plan-based restrictions
- Crypto payments
- NFT token gating
- Admin dashboard