Stripe integration uses direct API queries to check subscription status, similar to how blockchain payments are verified via Alchemy API. This approach eliminates the need for webhooks, database storage, and complex synchronization logic.
User subscribes → Stripe manages subscription → App queries Stripe API → Verify active subscription
Key Benefits:
- ✅ No webhooks to configure or debug
- ✅ No database tables for payment storage
- ✅ Always up-to-date (Stripe is source of truth)
- ✅ Consistent with blockchain payment verification
- ✅ Simple and reliable
Add these to .env.local:
# Stripe API Keys
STRIPE_SECRET_KEY=sk_test_your_secret_key_here
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_your_publishable_key_here
# Stripe Product Configuration
STRIPE_PRICE_ID=price_your_recurring_price_id_here
# Note: STRIPE_WEBHOOK_SECRET is NOT needed for direct query approachWhen a user subscribes:
- User logs in with wallet (Dynamic.xyz)
- Frontend calls
/api/stripe/create-subscriptionwithwalletAddress - Backend creates Stripe customer with
walletAddressin metadata - Backend creates Stripe subscription with
walletAddressin metadata - Frontend shows Stripe Payment Element for user to complete payment
- Stripe handles recurring billing automatically
File: app/api/stripe/create-subscription/route.ts
When checking if user has access:
- User logs in with wallet
- Frontend calls
/api/check-subscriptionwithwalletAddress - Backend queries Stripe API: Search for customers with matching
walletAddressin metadata - Backend checks if customer has active subscription
- Returns subscription status (active/inactive, expiration date, days remaining)
Files:
lib/subscription-manager.ts-checkStripeSubscription()app/api/check-subscription/route.ts- API endpoint
The app supports both Stripe (card) and blockchain (crypto) payments:
// Queries both Stripe API and Alchemy API in parallel
const subscription = await checkCombinedSubscription(walletAddress);
// Returns whichever subscription expires latest
// User has access if EITHER subscription is activeFile: lib/subscription-manager.ts - checkCombinedSubscription()
POST /api/stripe/create-subscription
curl -X POST http://localhost:3000/api/stripe/create-subscription \
-H "Content-Type: application/json" \
-d '{"walletAddress": "0x1234..."}'Response:
{
"success": true,
"subscriptionId": "sub_...",
"clientSecret": "pi_..._secret_..."
}POST /api/check-subscription
curl -X POST http://localhost:3000/api/check-subscription \
-H "Content-Type: application/json" \
-d '{"walletAddress": "0x1234..."}'Response:
{
"success": true,
"subscription": {
"isActive": true,
"expiresAt": "2025-12-15T00:00:00.000Z",
"daysRemaining": 30,
"totalDaysPurchased": 30,
"totalPaid": 4.99,
"paymentCount": 1
}
}Payment Modal - app/components/PaymentModal.tsx
- Shows subscription options (crypto vs card)
- Handles Stripe subscription creation
- Displays Stripe Payment Element
Stripe Form - app/components/StripeSubscriptionForm.tsx
- Loads Stripe.js
- Renders Payment Element
- Handles payment confirmation
Auth Provider - app/components/AuthProvider.tsx
- Checks subscription status on login
- Caches subscription status for 1 hour
- Provides
hasActiveSubscriptionto components
Subscription Manager - lib/subscription-manager.ts
checkStripeSubscription()- Queries Stripe API directlycheckCombinedSubscription()- Merges Stripe + blockchain payments
API Routes:
app/api/stripe/create-subscription/route.ts- Creates subscriptionapp/api/check-subscription/route.ts- Verifies subscription status
-
Use Stripe test mode keys:
STRIPE_SECRET_KEY=sk_test_... NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...
-
Test card numbers:
- Success:
4242 4242 4242 4242 - Decline:
4000 0000 0000 0002 - Requires authentication:
4000 0025 0000 3155
- Success:
-
Any future expiry date and CVC will work
- Start dev server:
npm run dev - Log in with wallet
- Click "Subscribe to Premium"
- Choose "Pay with Card"
- Enter test card:
4242 4242 4242 4242 - Complete payment
- Verify subscription shows as active
# Check subscription status
curl -X POST http://localhost:3000/api/check-subscription \
-H "Content-Type: application/json" \
-d '{"walletAddress": "YOUR_WALLET_ADDRESS"}'- Go to https://dashboard.stripe.com/test/products
- Click "Add product"
- Name: "Premium Subscription"
- Price: $4.99
- Billing period: Monthly
- Click "Save product"
- Copy the Price ID (starts with
price_...) - Add to
.env.localasSTRIPE_PRICE_ID
- Go to https://dashboard.stripe.com/test/subscriptions
- Click a subscription to view details
- Check "Metadata" tab for
walletAddress
- Go to https://dashboard.stripe.com/test/customers
- Click a customer to view details
- Check "Metadata" tab for
walletAddress
Stripe API rate limits:
- Test mode: 100 requests/second
- Live mode: 100 requests/second
Subscription checks are cached for 1 hour in localStorage, so rate limits are rarely reached.
- Switch to live Stripe keys (not test keys)
- Update
STRIPE_SECRET_KEYin production environment - Update
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEYin production environment - Update
STRIPE_PRICE_IDto use live price (not test price) - Test subscription creation in production
- Test subscription verification in production
- Verify recurring billing works correctly
Production .env.local:
# Use LIVE keys
STRIPE_SECRET_KEY=sk_live_...
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_...
STRIPE_PRICE_ID=price_... # Must be from live mode- Wallet addresses are stored in Stripe customer and subscription metadata
- Addresses are normalized to lowercase for consistency
- No blockchain transactions required for card payments
- User proves wallet ownership via Dynamic.xyz authentication
STRIPE_SECRET_KEYmust NEVER be exposed to client- Only used in server-side API routes
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEYis safe to expose (by design)
- Subscription status queried directly from Stripe
- Cannot be spoofed or manipulated client-side
- User must prove wallet ownership to access subscription benefits
Possible causes:
- Subscription not created yet
- Subscription cancelled or expired
- Payment failed
- Wrong wallet address
Debug:
# Check Stripe for subscriptions with this wallet address
# Dashboard → Customers → Search metadata for walletAddressPossible causes:
STRIPE_SECRET_KEYnot configured- Stripe API error
- Network timeout
Debug: Check server console logs for detailed error message
This is supported! The system uses the subscription with the latest expiration date.
Try:
- Clear localStorage cache:
localStorage.removeItem('subscription_YOUR_ADDRESS') - Refresh the page
- Wait a few seconds for Stripe to update
| Feature | Stripe (Card) | Blockchain (Crypto) |
|---|---|---|
| Verification | Direct Stripe API | Alchemy API indexer |
| Storage | Stripe manages | No storage needed |
| Latency | ~200-500ms | ~500-1000ms |
| Rate limits | 100 req/s | 660 compute units/s |
| Caching | 1 hour | 1 hour |
| Recurring | Yes (automatic) | No (manual top-ups) |
| Refunds | Via Stripe Dashboard | Cannot refund blockchain tx |
If you previously used webhooks + database:
-
Remove old data:
DROP TABLE IF EXISTS stripe_payments;
-
Environment variables to remove:
STRIPE_WEBHOOK_SECRET(no longer needed)POSTGRES_DB(no longer required for Stripe payments)
-
Existing subscriptions will continue to work automatically via direct API queries
For Stripe integration issues:
- Check Stripe Dashboard logs
- Check server console for error messages
- Verify environment variables are set correctly
- Review Stripe API documentation: https://stripe.com/docs/api