2 * @file routes/stripe-connect.js
3 * @module routes/stripe-connect
4 * @description Stripe Connect onboarding and account management routes for tenant dashboard.
5 * Handles OAuth onboarding flow, account status retrieval, and configuration updates.
7 * **Dashboard Integration:**
8 * - GET /config - Fetch tenant's Stripe account status
9 * - POST /connect/start - Initiate Stripe Connect onboarding
10 * - GET /connect/refresh - Regenerate onboarding link (if expired)
11 * - GET /onboarding-complete - Return URL after successful onboarding
12 * - GET /onboarding-refresh - Return URL when link expires
14 * **Multi-Tenant Security:**
15 * - All routes require authentication (authenticateToken)
16 * - tenant_id extracted from JWT token
17 * - Row-level security enforced on stripe_config table
19 * **Onboarding Flow:**
20 * 1. Tenant clicks "Connect with Stripe" in dashboard
21 * 2. POST /connect/start creates Connected Account + account link
22 * 3. Redirect tenant to Stripe-hosted onboarding
23 * 4. Stripe redirects to /onboarding-complete or /onboarding-refresh
24 * 5. Dashboard polls GET /config to check charges_enabled status
27 * @requires ../services/stripeService
28 * @requires ../middleware/auth
29 * @see {@link https://stripe.com/docs/connect/enable-payment-acceptance-guide|Stripe Connect Onboarding}
32const express = require('express');
33const router = express.Router();
34const pool = require('../services/db');
35const StripeService = require('../services/stripeService');
36const authenticateToken = require('../middleware/auth');
39 * GET /api/stripe/config
41 * Retrieves tenant's Stripe Connect account configuration and status.
43 * Returns account details if configured, or null if not yet onboarded.
44 * Dashboard uses this to determine whether to show "Connect with Stripe" button
45 * or display account status (verified, pending, disabled).
47 * **Response Fields:**
48 * - stripe_account_id - Connected Account ID (acct_...)
49 * - account_status - pending, verified, disabled, suspended
50 * - charges_enabled - Can accept payments (true/false)
51 * - payouts_enabled - Can receive payouts (true/false)
52 * - verification_status - unverified, pending, verified, failed
53 * - requirements_currently_due - Array of missing required info
54 * - onboarding_completed_at - Timestamp of onboarding completion
56 * @route GET /api/stripe/config
57 * @param {express.Request} req - Express request (authenticated)
58 * @param {express.Response} res - Express response
59 * @returns {Promise<Object>} Stripe config or null if not configured
62 * // Response when not onboarded:
68 * // Response when onboarded and verified:
71 * "stripe_account_id": "acct_1234567890",
72 * "account_status": "verified",
73 * "charges_enabled": true,
74 * "payouts_enabled": true,
75 * "verification_status": "verified",
76 * "requirements_currently_due": [],
77 * "onboarding_completed_at": "2026-03-16T10:30:00.000Z",
79 * "default_currency": "usd"
82router.get('/config', authenticateToken, async (req, res) => {
84 const tenantId = req.user.tenant_id;
86 const result = await pool.query(
95 requirements_currently_due,
96 requirements_eventually_due,
97 requirements_disabled_reason,
101 onboarding_completed_at,
102 application_fee_percent,
103 application_fee_fixed,
107 WHERE tenant_id = $1`,
111 if (result.rows.length === 0) {
112 return res.json({ configured: false });
120 console.error('❌ Error fetching Stripe config:', error);
121 res.status(500).json({ error: 'Failed to fetch Stripe configuration' });
126 * POST /api/stripe/connect/start
128 * Initiates Stripe Connect onboarding flow for tenant.
130 * Creates Connected Account (if not exists) and generates onboarding link.
131 * Returns redirect URL for tenant to complete onboarding on Stripe.
134 * - email - Business email address (required)
135 * - country - ISO 3166-1 alpha-2 country code (default: US)
136 * - business_name - Business name (optional)
137 * - business_url - Business website URL (optional)
140 * - account_id - Stripe Connected Account ID
141 * - onboarding_url - URL to redirect tenant to
142 * - expires_at - Unix timestamp when link expires (5 minutes)
144 * @route POST /api/stripe/connect/start
145 * @param {express.Request} req - Express request with body
146 * @param {express.Response} res - Express response with onboarding URL
147 * @returns {Promise<Object>} Account ID and onboarding URL
151 * POST /api/stripe/connect/start
153 * "email": "billing@acme.com",
155 * "business_name": "Acme Corp",
156 * "business_url": "https://acme.example.com"
162 * "account_id": "acct_1234567890",
163 * "onboarding_url": "https://connect.stripe.com/setup/e/acct_1234567890/...",
164 * "expires_at": 1710630000
167router.post('/connect/start', authenticateToken, async (req, res) => {
169 const tenantId = req.user.tenant_id;
170 const { email, country = 'US', business_name, business_url } = req.body;
173 return res.status(400).json({ error: 'Email is required' });
176 // Check if tenant already has a Stripe account
177 const existingConfig = await pool.query(
178 'SELECT stripe_account_id FROM stripe_config WHERE tenant_id = $1',
184 if (existingConfig.rows.length > 0) {
185 // Use existing account
186 accountId = existingConfig.rows[0].stripe_account_id;
187 console.log(`🔄 Using existing Stripe account: ${accountId}`);
189 // Create new Connected Account
190 const businessProfile = {};
191 if (business_name) businessProfile.name = business_name;
192 if (business_url) businessProfile.url = business_url;
194 const account = await StripeService.createConnectAccount(
197 'express', // Use Express accounts for faster onboarding
201 accountId = account.id;
205 `INSERT INTO stripe_config
206 (tenant_id, stripe_account_id, account_status, account_type, country, created_by)
207 VALUES ($1, $2, 'pending', 'express', $3, $4)`,
208 [tenantId, accountId, country, req.user.user_id]
211 console.log(`✅ Created new Stripe account: ${accountId} for tenant ${tenantId}`);
214 // Generate onboarding link
215 const frontendUrl = process.env.FRONTEND_URL || 'https://rmm-psa-dashboard-5jyun.ondigitalocean.app';
216 const accountLink = await StripeService.createAccountLink(
218 `${frontendUrl}/integrations?stripe_onboarding=success`,
219 `${frontendUrl}/integrations?stripe_onboarding=refresh`
222 // Update account_link_created_at and expires_at
224 `UPDATE stripe_config
225 SET account_link_created_at = NOW(),
226 account_link_expires_at = to_timestamp($1),
228 WHERE stripe_account_id = $2`,
229 [accountLink.expires_at, accountId]
233 account_id: accountId,
234 onboarding_url: accountLink.url,
235 expires_at: accountLink.expires_at
238 console.error('❌ Error starting Stripe Connect onboarding:', error);
240 // Check if this is the "Connect not enabled" error
241 if (error.message && error.message.includes('signed up for Connect')) {
242 return res.status(400).json({
243 error: 'Stripe Connect not enabled',
244 message: 'Stripe Connect must be enabled on your Stripe account before creating connected accounts.',
246 '1. Go to https://dashboard.stripe.com/connect',
247 '2. Click "Get started" to enable Stripe Connect',
248 '3. Complete the platform setup',
249 '4. Return here and try connecting again'
251 setupUrl: 'https://dashboard.stripe.com/connect'
255 res.status(500).json({
256 error: 'Failed to start Stripe onboarding',
257 message: error.message
263 * POST /api/stripe/connect/refresh
265 * Regenerates onboarding link if previous link expired.
267 * Stripe account links expire after ~5 minutes. If tenant didn't complete
268 * onboarding in time, dashboard calls this to generate a fresh link.
270 * @route POST /api/stripe/connect/refresh
271 * @param {express.Request} req - Express request
272 * @param {express.Response} res - Express response with new onboarding URL
273 * @returns {Promise<Object>} New onboarding URL and expiration
278 * "onboarding_url": "https://connect.stripe.com/setup/e/acct_1234567890/...",
279 * "expires_at": 1710630300
282router.post('/connect/refresh', authenticateToken, async (req, res) => {
284 const tenantId = req.user.tenant_id;
286 // Get tenant's Stripe account ID
287 const configResult = await pool.query(
288 'SELECT stripe_account_id FROM stripe_config WHERE tenant_id = $1',
292 if (configResult.rows.length === 0) {
293 return res.status(404).json({ error: 'Stripe account not found. Please start onboarding first.' });
296 const accountId = configResult.rows[0].stripe_account_id;
298 // Generate new onboarding link
299 const frontendUrl = process.env.FRONTEND_URL || 'https://rmm-psa-dashboard-5jyun.ondigitalocean.app';
300 const accountLink = await StripeService.createAccountLink(
302 `${frontendUrl}/integrations?stripe_onboarding=success`,
303 `${frontendUrl}/integrations?stripe_onboarding=refresh`
308 `UPDATE stripe_config
309 SET account_link_created_at = NOW(),
310 account_link_expires_at = to_timestamp($1),
312 WHERE stripe_account_id = $2`,
313 [accountLink.expires_at, accountId]
317 onboarding_url: accountLink.url,
318 expires_at: accountLink.expires_at
321 console.error('❌ Error refreshing Stripe Connect link:', error);
322 res.status(500).json({
323 error: 'Failed to refresh onboarding link',
324 message: error.message
330 * POST /api/stripe/connect/sync
332 * Syncs tenant's Stripe account status from Stripe API to database.
334 * Dashboard calls this after onboarding redirect to fetch latest account status.
335 * Also useful for manually refreshing verification status if requirements change.
337 * @route POST /api/stripe/connect/sync
338 * @param {express.Request} req - Express request
339 * @param {express.Response} res - Express response with updated account status
340 * @returns {Promise<Object>} Updated account configuration
346 * "account_status": "verified",
347 * "charges_enabled": true,
348 * "payouts_enabled": true,
349 * "verification_status": "verified"
352router.post('/connect/sync', authenticateToken, async (req, res) => {
354 const tenantId = req.user.tenant_id;
356 // Get tenant's Stripe account ID
357 const configResult = await pool.query(
358 'SELECT stripe_account_id FROM stripe_config WHERE tenant_id = $1',
362 if (configResult.rows.length === 0) {
363 return res.status(404).json({ error: 'Stripe account not found' });
366 const accountId = configResult.rows[0].stripe_account_id;
368 // Fetch latest account details from Stripe
369 const account = await StripeService.retrieveAccount(accountId);
371 // Update database with latest status
372 await StripeService.updateAccountConfig(tenantId, account);
374 // Check if onboarding was just completed
375 if (account.charges_enabled && !configResult.rows[0].onboarding_completed_at) {
377 `UPDATE stripe_config
378 SET onboarding_completed_at = NOW()
379 WHERE tenant_id = $1`,
384 // Fetch updated config from database
385 const updatedConfig = await pool.query(
391 requirements_currently_due,
392 onboarding_completed_at
394 WHERE tenant_id = $1`,
400 ...updatedConfig.rows[0]
403 console.error('❌ Error syncing Stripe account status:', error);
404 res.status(500).json({
405 error: 'Failed to sync account status',
406 message: error.message
412 * DELETE /api/stripe/connect
414 * Disconnects tenant from Stripe (removes Connected Account configuration).
416 * **Warning:** This does NOT delete the Stripe account itself (must be done in Stripe Dashboard).
417 * Only removes the connection from this platform.
419 * @route DELETE /api/stripe/connect
420 * @param {express.Request} req - Express request
421 * @param {express.Response} res - Express response
422 * @returns {Promise<Object>} Deletion confirmation
424router.delete('/connect', authenticateToken, async (req, res) => {
426 const tenantId = req.user.tenant_id;
428 const result = await pool.query(
429 'DELETE FROM stripe_config WHERE tenant_id = $1 RETURNING stripe_account_id',
433 if (result.rows.length === 0) {
434 return res.status(404).json({ error: 'Stripe account not found' });
437 console.log(`🗑️ Disconnected Stripe account ${result.rows[0].stripe_account_id} for tenant ${tenantId}`);
441 message: 'Stripe account disconnected successfully'
444 console.error('❌ Error disconnecting Stripe account:', error);
445 res.status(500).json({ error: 'Failed to disconnect Stripe account' });
449module.exports = router;