fix: Add Discord OAuth → Stripe checkout flow
THE BUG: Website redirected to /stripe/auth but route didn't exist. Checkout sessions were created WITHOUT client_reference_id (Discord ID), so webhook couldn't sync Discord roles after payment. THE FIX: - Added GET /stripe/auth - stores tier in session, redirects to Discord OAuth - Added GET /stripe/checkout - creates checkout WITH client_reference_id - Updated auth callback to redirect to /stripe/checkout after OAuth - Legacy POST /create-checkout-session kept for compatibility FLOW NOW: 1. User clicks Subscribe on website 2. → /stripe/auth?tier=X (stores tier, redirects to Discord) 3. → /auth/discord (Discord OAuth) 4. → /auth/discord/callback (user authenticated) 5. → /stripe/checkout?tier=X (creates Stripe session WITH Discord ID) 6. → Stripe Checkout (user pays) 7. → Webhook receives event with client_reference_id 8. → Discord role synced! Chronicler #75
This commit is contained in:
@@ -2,11 +2,24 @@ const express = require('express');
|
||||
const passport = require('passport');
|
||||
const router = express.Router();
|
||||
|
||||
/**
|
||||
* Standard Discord OAuth - redirects to admin after login
|
||||
*/
|
||||
router.get('/discord', passport.authenticate('discord'));
|
||||
|
||||
router.get('/discord/callback', passport.authenticate('discord', {
|
||||
failureRedirect: '/'
|
||||
}), (req, res) => {
|
||||
// Check if this was a checkout flow (tier stored in session)
|
||||
if (req.session.pendingCheckoutTier) {
|
||||
const tierLevel = req.session.pendingCheckoutTier;
|
||||
delete req.session.pendingCheckoutTier; // Clean up
|
||||
|
||||
// Redirect to checkout creation with Discord ID now available
|
||||
return res.redirect(`/stripe/checkout?tier=${tierLevel}`);
|
||||
}
|
||||
|
||||
// Standard admin redirect
|
||||
res.redirect('/admin');
|
||||
});
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* Handles checkout sessions, webhooks, and customer portal
|
||||
* Date: April 3, 2026
|
||||
* Updated: April 6, 2026 - Added Discord role sync (Task #87)
|
||||
* Updated: April 10, 2026 - Added /auth and /checkout routes for Discord OAuth flow
|
||||
*/
|
||||
|
||||
const express = require('express');
|
||||
@@ -27,9 +28,96 @@ const corsOptions = {
|
||||
router.options('/create-checkout-session', cors(corsOptions));
|
||||
|
||||
/**
|
||||
* CREATE CHECKOUT SESSION
|
||||
* STRIPE AUTH - Entry point from website
|
||||
* GET /stripe/auth?tier=X
|
||||
* Stores tier in session, redirects to Discord OAuth
|
||||
*/
|
||||
router.get('/auth', (req, res) => {
|
||||
const tierLevel = req.query.tier;
|
||||
|
||||
if (!tierLevel || isNaN(parseInt(tierLevel))) {
|
||||
return res.status(400).send('Invalid tier level. Please return to the subscribe page and try again.');
|
||||
}
|
||||
|
||||
// Store tier in session for after OAuth callback
|
||||
req.session.pendingCheckoutTier = parseInt(tierLevel);
|
||||
|
||||
// Redirect to Discord OAuth
|
||||
res.redirect('/auth/discord');
|
||||
});
|
||||
|
||||
/**
|
||||
* CHECKOUT - Creates Stripe session after Discord OAuth
|
||||
* GET /stripe/checkout?tier=X
|
||||
* Called from auth callback, user is now logged in
|
||||
*/
|
||||
router.get('/checkout', async (req, res) => {
|
||||
try {
|
||||
// User must be authenticated
|
||||
if (!req.user || !req.user.id) {
|
||||
return res.redirect('/stripe/auth?tier=' + (req.query.tier || '1'));
|
||||
}
|
||||
|
||||
const tierLevel = parseInt(req.query.tier);
|
||||
const discordId = req.user.id;
|
||||
|
||||
if (!tierLevel || isNaN(tierLevel)) {
|
||||
return res.status(400).send('Invalid tier level');
|
||||
}
|
||||
|
||||
// Get Stripe Price ID from database
|
||||
const productResult = await db.query(
|
||||
'SELECT stripe_price_id, tier_name, billing_type FROM stripe_products WHERE tier_level = $1',
|
||||
[tierLevel]
|
||||
);
|
||||
|
||||
if (productResult.rows.length === 0) {
|
||||
return res.status(404).send('Invalid tier level - product not found');
|
||||
}
|
||||
|
||||
const product = productResult.rows[0];
|
||||
const priceId = product.stripe_price_id;
|
||||
const billingMode = product.billing_type === 'one-time' ? 'payment' : 'subscription';
|
||||
|
||||
console.log('🔍 Creating checkout session (OAuth flow):', {
|
||||
discord_id: discordId,
|
||||
tier_level: tierLevel,
|
||||
tier_name: product.tier_name,
|
||||
priceId,
|
||||
billingMode
|
||||
});
|
||||
|
||||
// Create Stripe Checkout Session WITH client_reference_id
|
||||
const sessionConfig = {
|
||||
payment_method_types: ['card'],
|
||||
line_items: [{ price: priceId, quantity: 1 }],
|
||||
mode: billingMode,
|
||||
client_reference_id: discordId, // 🔑 THE KEY - Discord ID for webhook
|
||||
success_url: 'https://firefrostgaming.com/success',
|
||||
cancel_url: 'https://firefrostgaming.com/subscribe',
|
||||
metadata: {
|
||||
tier_level: tierLevel.toString(),
|
||||
tier_name: product.tier_name,
|
||||
discord_id: discordId
|
||||
}
|
||||
};
|
||||
|
||||
const session = await stripe.checkout.sessions.create(sessionConfig);
|
||||
|
||||
// Redirect directly to Stripe Checkout
|
||||
res.redirect(session.url);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Checkout creation error:', error);
|
||||
res.status(500).send('Failed to create checkout session. Please try again.');
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* CREATE CHECKOUT SESSION (Legacy API - kept for compatibility)
|
||||
* POST /stripe/create-checkout-session
|
||||
* Body: { tier_level }
|
||||
* NOTE: This doesn't have Discord ID - use /stripe/auth flow instead
|
||||
*/
|
||||
router.post('/create-checkout-session', cors(corsOptions), async (req, res) => {
|
||||
try {
|
||||
@@ -53,7 +141,7 @@ router.post('/create-checkout-session', cors(corsOptions), async (req, res) => {
|
||||
const priceId = product.stripe_price_id;
|
||||
const billingMode = product.billing_type === 'one-time' ? 'payment' : 'subscription';
|
||||
|
||||
console.log('🔍 Creating checkout session:', {
|
||||
console.log('🔍 Creating checkout session (legacy, no Discord ID):', {
|
||||
tier_level,
|
||||
tier_name: product.tier_name,
|
||||
priceId,
|
||||
|
||||
Reference in New Issue
Block a user