This guide walks through implementing actual payment processing for DriftOS pricing tiers using Stripe.
- Stripe Account: Sign up at https://stripe.com
- Stripe CLI (for webhook testing): https://stripe.com/docs/stripe-cli
Go to Stripe Dashboard → Products and create:
-
Pro Plan
- Name: "DriftOS Pro"
- Price: $99/month (recurring)
- Copy the Price ID (e.g.,
price_1ABC...)
-
Enterprise Plan
- Name: "DriftOS Enterprise"
- Price: $499/month (recurring)
- Copy the Price ID (e.g.,
price_2XYZ...)
cd /Users/scotty/development/driftos-gateway
npm install stripeAdd to /Users/scotty/development/driftos-gateway/.env:
# Stripe Keys (get from https://dashboard.stripe.com/apikeys)
STRIPE_SECRET_KEY=sk_test_...
STRIPE_PUBLISHABLE_KEY=pk_test_...
STRIPE_WEBHOOK_SECRET=whsec_... # Generate after setting up webhook
# Stripe Price IDs (from step 1)
STRIPE_PRO_PRICE_ID=price_1ABC...
STRIPE_ENTERPRISE_PRICE_ID=price_2XYZ...
# Frontend URL for redirects
FRONTEND_URL=http://localhost:3000Add to /Users/scotty/development/driftos-gateway/src/plugins/env.ts:
STRIPE_SECRET_KEY: Type.String(),
STRIPE_PUBLISHABLE_KEY: Type.String(),
STRIPE_WEBHOOK_SECRET: Type.String(),
STRIPE_PRO_PRICE_ID: Type.String(),
STRIPE_ENTERPRISE_PRICE_ID: Type.String(),
FRONTEND_URL: Type.String({ default: 'http://localhost:3000' }),Add to /Users/scotty/development/driftos-gateway/src/app.ts (after other routes):
import billingRoutes from './routes/billing/index.js';
// After existing routes...
await app.register(billingRoutes, { prefix: '/api/v1' });Update /Users/scotty/development/driftos-gateway/prisma/schema.prisma:
model User {
id String @id @default(cuid())
email String @unique
stripeCustomerId String? @map("stripe_customer_id")
createdAt DateTime @default(now()) @map("created_at")
@@map("users")
}Run migration:
npx prisma migrate dev --name add-stripe-customer-idcd /Users/scotty/development/driftos-website
npm install @stripe/stripe-jsAdd to /Users/scotty/development/driftos-website/.env.local:
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...Replace button logic in /Users/scotty/development/driftos-website/app/dashboard/pricing/page.tsx:
Find the button section (around line 208) and replace with:
import { UpgradeButton } from '@/components/pricing/upgrade-button';
// In the JSX:
{isCurrentTier ? (
<Button variant="outline" className="w-full" disabled>
Current Plan
</Button>
) : tier.name === 'enterprise' ? (
<Button variant="outline" className="w-full" asChild>
<a href="mailto:hello@driftos.dev">
Contact Sales
<ArrowRight className="ml-2 h-4 w-4" />
</a>
</Button>
) : tier.name === 'free' ? (
<Button variant="outline" className="w-full" disabled>
Current Plan
</Button>
) : (
<UpgradeButton
tier={tier.name}
tierDisplayName={tier.displayName}
isPro={isPro}
/>
)}-
Install Stripe CLI: https://stripe.com/docs/stripe-cli
-
Login to Stripe:
stripe login
-
Forward webhooks to local server:
stripe listen --forward-to localhost:3002/api/v1/billing/webhook
-
Copy the webhook signing secret (
whsec_...) and add to.envasSTRIPE_WEBHOOK_SECRET
-
Go to Stripe Dashboard → Developers → Webhooks
-
Add endpoint:
https://yourdomain.com/api/v1/billing/webhook -
Select events to listen to:
checkout.session.completedcustomer.subscription.updatedcustomer.subscription.deletedinvoice.payment_failed
-
Copy the webhook signing secret and add to production environment variables
Update /Users/scotty/development/driftos-gateway/src/plugins/auth.ts:
Add /api/v1/billing/webhook to PUBLIC_ROUTES array (webhooks need raw body access):
const PUBLIC_ROUTES = [
// ... existing routes
'/api/v1/billing/webhook', // Add this
];-
Start both servers:
# Terminal 1: Gateway cd /Users/scotty/development/driftos-gateway npm run dev # Terminal 2: Website cd /Users/scotty/development/driftos-website npm run dev # Terminal 3: Stripe webhook forwarding stripe listen --forward-to localhost:3002/api/v1/billing/webhook
-
Navigate to http://localhost:3000/dashboard/pricing
-
Click "Upgrade to Pro" button
-
Use Stripe test card:
4242 4242 4242 4242- Expiry: Any future date
- CVC: Any 3 digits
- ZIP: Any 5 digits
-
Complete checkout
-
Verify:
- User redirected to success page
- Webhook received and processed
- API key tier updated in database
- Check gateway logs for confirmation
Stripe provides various test cards:
- Success:
4242 4242 4242 4242 - Decline:
4000 0000 0000 0002 - Requires authentication:
4000 0025 0000 3155
Full list: https://stripe.com/docs/testing
Add a "Manage Subscription" button for users with active subscriptions:
'use client';
import { useAuth } from '@clerk/nextjs';
import { Button } from '@/components/ui/button';
import { createPortalSession } from '@/lib/api/billing';
import { toast } from 'sonner';
export function ManageSubscriptionButton() {
const { getToken } = useAuth();
const handleManage = async () => {
try {
const token = await getToken();
if (!token) return;
const portalUrl = await createPortalSession(token);
window.location.href = portalUrl;
} catch (error) {
toast.error('Failed to open billing portal');
}
};
return (
<Button onClick={handleManage} variant="outline">
Manage Subscription
</Button>
);
}Stripe automatically handles proration when users change plans mid-cycle.
Add trial period when creating checkout session:
const session = await stripe.checkout.sessions.create({
// ... existing config
subscription_data: {
trial_period_days: 14, // 14-day free trial
},
});For future token-based pricing, use Stripe metered billing:
// Report usage to Stripe
await stripe.subscriptionItems.createUsageRecord(
subscriptionItemId,
{
quantity: tokenCount,
timestamp: Math.floor(Date.now() / 1000),
}
);Before going live:
- Switch to live API keys (no
_test_prefix) - Set up production webhook endpoint
- Test full flow with real card (then refund)
- Set up Stripe Radar rules for fraud prevention
- Configure email receipts in Stripe Dashboard
- Set up invoice templates
- Add tax collection if required (Stripe Tax)
- Configure subscription renewal emails
- Set up failed payment dunning (automatic retries)
- Add monitoring/alerts for failed payments
- Test webhook reliability (Stripe retries failed webhooks)
- Never expose secret keys - Keep in backend only
- Verify webhook signatures - Already implemented in webhook handler
- Use HTTPS in production - Required for Stripe
- Validate tier changes - Check user permissions before upgrades
- Rate limit checkout API - Prevent abuse
- Log all billing events - For audit trail
- Handle webhook idempotency - Stripe may send duplicate events
Track key metrics:
- Conversion rate (free → paid)
- Churn rate (paid → free)
- Monthly Recurring Revenue (MRR)
- Failed payment rate
- Customer Lifetime Value (LTV)
Use Stripe Dashboard or integrate with analytics tools.
- Check webhook is running:
stripe listen - Verify signing secret matches
.env - Check gateway logs for errors
- Test webhook manually:
stripe trigger checkout.session.completed
- Verify price IDs are correct
- Check API key has correct permissions
- Ensure user is authenticated
- Check gateway logs for detailed error
- Verify webhook received event
- Check
userIdin session metadata - Verify database update query
- Check Prisma logs
- Stripe Documentation: https://stripe.com/docs
- Stripe API Reference: https://stripe.com/docs/api
- Stripe Support: https://support.stripe.com