Stripe - Authentication
๐ Overviewโ
The Stripe authentication module handles the complete OAuth 2.0 Connect flow, token management, multi-app connection handling, and account disconnection. It supports connecting multiple DashClicks apps (billing, review, funnel) to a single Stripe account while managing tokens and webhooks appropriately.
Source Files:
- Controller:
external/Integrations/Stripe/Controllers/auth.js - Model:
external/Integrations/Stripe/Models/keys.js - Routes:
external/Integrations/Stripe/Routes/auth.js
External API: Stripe OAuth Connect + Stripe API
๐๏ธ Collections Usedโ
stripe-keyโ
- Operations: Create, Read, Update, Delete
- Model:
external/models/stripe-key.js - Usage Context: Store OAuth tokens, connected apps, and contact mapping configuration
queuesโ
- Operations: Create
- Model:
external/models/queues.js - Usage Context: Queue billing data sync and contact import jobs
accountโ
- Operations: Read
- Model:
external/models/account.js - Usage Context: Validate sub-account relationships
๐ Data Flowโ
Initial OAuth Connectionโ
sequenceDiagram
participant Client as DashClicks Frontend
participant API as Stripe Controller
participant JWT as JWT Service
participant Stripe as Stripe OAuth
participant DB as MongoDB
Client->>API: GET /auth/login?forward_url&connected_app&contact_mapping
API->>DB: Check existing token
alt Token Not Found
API->>JWT: Sign state token (account_id, params)
API->>Stripe: Redirect to OAuth authorize
Stripe-->>Client: Authorization prompt
Client->>Stripe: User authorizes
Stripe->>API: GET /auth/callback?code&state
API->>JWT: Verify state token
API->>Stripe: Exchange code for access_token
Stripe-->>API: OAuth response (access_token, stripe_user_id)
API->>DB: Check if stripe_user_id exists
alt Not Connected Elsewhere
API->>DB: Save token with connected_apps
alt Billing App
API->>Stripe: Register billing webhook
API->>DB: Create billing queue jobs
end
alt Review App
API->>Stripe: Register review webhook
end
API-->>Client: Redirect to forward_url?status=success&token={id}
else Already Connected
API-->>Client: Redirect to forward_url?status=error&reason=Already connected
end
else Token Exists & Valid
alt App Not Connected
API->>DB: Add app to connected_apps array
alt Billing App
API->>DB: Create queue jobs
end
end
API-->>Client: Redirect to forward_url?status=success&token={id}
else Token Invalidated
API->>DB: Delete old token
API->>JWT: Sign new state token
API->>Stripe: Redirect to OAuth (restart flow)
end
๐ง Business Logic & Functionsโ
loginAuth(req, res, next)โ
Purpose: Initiates OAuth Connect flow or returns existing token
Source: Controllers/auth.js
External API Endpoint: GET https://connect.stripe.com/oauth/authorize
Parameters:
forward_url(String, Required) - URL to redirect after authconnected_app(String, Required) - One of:billing,review,funnelcontact_mapping(String, Optional) - Base64 encoded contact field mappings for billingsubaccountid(String, Optional) - Sub-account ID if connecting for child account
Returns: 302 Redirect
Business Logic Flow:
-
Validate Required Parameters
- Check
forward_urlexists - Validate
connected_appis in allowed list:['billing', 'review', 'funnel'] - Return 400 if validation fails
- Check
-
Handle Sub-Account
- If
subaccountidprovided, validate it belongs to parent account - Switch
accountIdto sub-account ID
- If
-
Check Existing Token
- Query
stripe-keycollection for account - If found:
- Check if
connected_appalready inconnected_appsarray - If not, add app and trigger app-specific setup:
- review: Register webhook for
charge.succeeded - billing: Create queue jobs, save contact mappings
- review: Register webhook for
- If
token_invalidated: true, delete token and restart OAuth - Otherwise, redirect to success with token ID
- Check if
- Query
-
Start OAuth Flow (if no token)
- Create JWT state token containing:
{
data: 'account_id=...&forward_url=...&contact_mapping=...&connected_app=...';
} - Sign with
APP_SECRET - Build OAuth URL:
https://connect.stripe.com/oauth/authorize
?client_id={STRIPE_CLIENT_ID}
&state={jwt_token}
&response_type=code
&redirect_uri={STRIPE_REDIRECT_URL}
&scope=read_write - Redirect (301) to OAuth URL
- Create JWT state token containing:
OAuth Authorization URL:
GET https://connect.stripe.com/oauth/authorize
Query Parameters:
- client_id: STRIPE_CLIENT_ID
- state: JWT encoded parameters
- response_type: "code"
- redirect_uri: STRIPE_REDIRECT_URL
- scope: "read_write"
Error Handling:
- 400 Missing forward_url: Returns error response
- 400 Invalid connected_app: Returns error with allowed values
- 400 Invalid sub-account: Sub-account doesn't belong to parent
- 500 OAuth Error: Redirects to
forward_url?status=error&reason={error}
Example Usage:
// Initial connection for billing app
GET /v1/e/stripe/auth/login
?forward_url=https://app.dashclicks.com/billing/integrations
&connected_app=billing
&contact_mapping=eyJ0eXBlIjogImNvbnRhY3RzIiwgLi4ufQ==
// Re-connection to add review app
GET /v1/e/stripe/auth/login
?forward_url=https://app.dashclicks.com/reputation
&connected_app=review
Side Effects:
- โ ๏ธ Creates JWT state token
- โ ๏ธ May update
connected_appsarray - โ ๏ธ May trigger webhook registration
- โ ๏ธ May create queue jobs for billing sync
postAuth(req, res, next)โ
Purpose: OAuth callback handler - exchanges authorization code for access token
Source: Controllers/auth.js
External API Endpoint: POST https://connect.stripe.com/oauth/token (via Stripe SDK)
Parameters:
code(String) - Authorization code from Stripe OAuthstate(String) - JWT token containing original request parameters
Returns: 302 Redirect to forward_url
Business Logic Flow:
-
Verify State Token
- Decode JWT
stateparameter - Extract
account_id,forward_url,contact_mapping,connected_app - Return 401 if JWT verification fails
- Decode JWT
-
Check Existing Connection
- Query
stripe-keybyaccount_id - If exists, redirect to success immediately (prevents duplicate connections)
- Query
-
Exchange Authorization Code
- Call Stripe API to exchange code for tokens:
stripe.oauth.token({
grant_type: 'authorization_code',
code,
}); - Receive OAuth response:
{
access_token: String,
refresh_token: String,
stripe_user_id: String,
stripe_publishable_key: String,
scope: String,
livemode: Boolean,
token_type: "bearer"
}
- Call Stripe API to exchange code for tokens:
-
Check Duplicate Connection
- Query if
stripe_user_idalready connected to another account - If yes, reject with error: "Stripe account is already connected to another Dashboard"
- Query if
-
Prepare Data to Save
- Parse
contact_mappingfrom Base64 - Build
dataToSaveobject:{
account_id: ObjectId,
token: {oauth_response},
connected_apps: [connected_app],
type: "person" | "business", // from contact_mapping.type
new_contact: Boolean,
mappings: {
// Contact field mappings with dots replaced by dashes
"first_name": "billing_details-name",
"email": "email"
}
}
- Parse
-
App-Specific Setup
-
Review App: Register webhook for review automation
await reviewStripeWebhook({
stripe,
apiBaseUrl: process.env.API_BASE_URL,
}); -
Billing App: Create queue jobs
// Account sync queue
new Queue({
account_id,
user_id: accountId,
source: 'billing',
additional_info: oauth_response,
});
// Contact sync queue (if contact_mapping provided)
new Queue({
account_id,
user_id: accountId,
source: 'billing-contacts',
type: contact_mapping.type,
new_contact: contact_mapping.new_contact,
mappings: dataToSave.mappings,
});
-
-
Save Token
- Insert into
stripe-keycollection - Return saved document ID
- Insert into
-
Redirect to Success
- Redirect to:
{forward_url}?status=success&integration=stripe&token={saved_id}
- Redirect to:
API Request Example:
// Stripe OAuth token exchange (handled by SDK)
POST https://connect.stripe.com/oauth/token
Body: {
grant_type: "authorization_code",
code: "ac_..."
}
API Response Example:
{
access_token: "sk_test_...",
refresh_token: "rt_...",
stripe_user_id: "acct_...",
stripe_publishable_key: "pk_test_...",
scope: "read_write",
livemode: false,
token_type: "bearer"
}
Error Handling:
- 401 Invalid State: JWT verification failed
- 400 Missing Code: Authorization code not provided
- 409 Account Already Connected:
stripe_user_idexists for different account - 500 OAuth Exchange Error: Stripe API error during token exchange
Example Usage:
// OAuth callback from Stripe
GET /v1/e/stripe/auth/callback
?code=ac_1234567890
&state=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Provider API Rate Limits:
- Stripe OAuth endpoints: No published limits (reasonable use expected)
Side Effects:
- โ ๏ธ Creates Stripe OAuth token in database
- โ ๏ธ Registers webhooks with Stripe API
- โ ๏ธ Creates background queue jobs for data sync
- โ ๏ธ External API call to Stripe (no direct cost, but quota counted)
deleteDocument(req, res, next)โ
Purpose: Disconnect Stripe integration and clean up app-specific data
Source: Controllers/auth.js
External API Endpoint: DELETE https://api.stripe.com/v1/webhook_endpoints/:id (via SDK)
Parameters:
connected_app(String, Required) - App to disconnect:billing,review,funnel, orallsubaccountid(String, Optional) - Sub-account ID if disconnecting child account
Returns: JSON { success: true, message: "SUCCESS" }
Business Logic Flow:
-
Validate Parameters
- Check
connected_appis in allowed list:['billing', 'review', 'funnel', 'all'] - Return 400 if invalid
- Check
-
Handle Sub-Account
- If
subaccountidprovided, validate ownership - Switch to sub-account ID
- If
-
Find Token Document
- Query
stripe-keybyaccount_id - Return 400 if not found
- Query
-
App-Specific Cleanup
For Billing Disconnect:
- Delete all billing data:
Promise.all([
BillingCharge.deleteMany({ stripe_user_id }),
BillingDispute.deleteMany({ stripe_user_id }),
BillingRefund.deleteMany({ stripe_user_id }),
BillingCustomer.deleteMany({ stripe_user_id }),
BillingSubscription.deleteMany({ stripe_user_id }),
BillingNote.deleteMany({ stripe_user_id }),
BillingProduct.deleteMany({ stripe_user_id }),
]); - Delete billing webhook from Stripe:
// Find webhook with URL matching /v1/billing/webhooks
// Delete via stripe.webhookEndpoints.del(webhook.id)
For Review Disconnect:
- Check if any other accounts still use review app
- If not, delete review webhook from Stripe:
// Find webhook with URL matching /v1/reputation/webhook/stripe
// Delete via stripe.webhookEndpoints.del(webhook.id)
- Delete all billing data:
-
Update/Delete Token
- If multiple apps connected and removing one:
StripeModel.update({ _id: docId }, { $pull: { connected_apps: app_name } }); - If removing last app or disconnecting 'all':
StripeModel.deleteOne({ account_id });
- If multiple apps connected and removing one:
Error Handling:
- 400 Invalid connected_app: Not in allowed list
- 400 Invalid sub-account: Sub-account doesn't belong to parent
- 400 Document Not Found: No token found for account
- 500 Webhook Deletion Error: Logged but non-blocking
Example Usage:
// Disconnect billing app only
DELETE / v1 / e / stripe / auth;
Body: {
connected_app: 'billing';
}
// Disconnect all apps
DELETE / v1 / e / stripe / auth;
Body: {
connected_app: 'all';
}
Side Effects:
- โ ๏ธ Deletes OAuth token from database (if last app)
- โ ๏ธ Deletes all billing data collections (if billing app)
- โ ๏ธ Removes webhooks from Stripe API
- โ ๏ธ External API calls to Stripe (webhook deletion)
๐ Integration Pointsโ
Internal Servicesโ
- Billing Module: Uses tokens to sync charges, customers, subscriptions
- Review Module: Uses webhook events to trigger auto-review requests
- Funnel Module: Uses tokens for payment processing
- Queue Manager: Processes billing sync and contact import jobs
External API Dependenciesโ
- Provider: Stripe
- Endpoints:
- OAuth:
https://connect.stripe.com/oauth/* - API:
https://api.stripe.com/v1/* - Webhooks: Registered dynamically
- OAuth:
- Authentication: OAuth 2.0 Bearer tokens
- Rate Limits: Varies by endpoint (SDK handles retries)
Webhooksโ
Webhook: Billing Eventsโ
- URL:
{API_BASE_URL}/v1/billing/webhooks - Events: All billing-related events (charges, subscriptions, disputes, etc.)
- Handler: Billing module webhook controller
- Signature Verification: Stripe-Signature header validation
- Registration: Automatic on billing app connection
Webhook: Review Automationโ
- URL:
{API_BASE_URL}/v1/reputation/webhook/stripe - Events:
charge.succeeded - Handler: Reputation module webhook controller
- Purpose: Trigger automatic review requests after successful payments
- Registration: Automatic on review app connection
๐งช Edge Cases & Special Handlingโ
Token Invalidationโ
Issue: OAuth tokens can be revoked or expire
Detection:
- 401/403 responses from Stripe API
invalid_granterror- Error codes 401/403 in response
Handling:
// Set flag in all tokens for account
StripeModel.updateMany({ account_id }, { $set: { token_invalidated: true } });
User Experience: Next connection attempt will delete old token and restart OAuth
Duplicate Account Connectionโ
Issue: Same Stripe account cannot connect to multiple DashClicks accounts
Handling:
- Check
stripe_user_iduniqueness before saving - Reject with error: "Stripe account is already connected to another Dashboard"
Reason: Prevents billing data conflicts and webhook routing issues
Contact Mapping Formatโ
Issue: Mongoose doesn't allow dots in field names
Handling:
// Replace dots with dashes in mapping keys
for (const [key, value] of Object.entries(contact_mapping.mappings)) {
if (key.includes('.')) {
const replaceDotToDash = key.replace(/\./g, '-');
dataToSave.mappings[replaceDotToDash] = value;
}
}
Example: billing_details.name โ billing_details-name
Multi-App Connection Orderโ
Issue: Apps can be connected in any order
Handling:
- Each app independently checks if it's in
connected_appsarray - Missing apps trigger their setup without affecting existing apps
- Token is shared across all apps
Webhook Cleanup on Disconnectโ
Issue: Review webhooks should persist if other accounts use them
Handling:
// Before deleting review webhook
const isReviewConnected = await StripeModel.findOne({
connected_apps: 'review',
});
if (!isReviewConnected) {
// Safe to delete webhook
}
โ ๏ธ Important Notesโ
- ๐ OAuth Security: State parameter contains signed JWT to prevent CSRF
- ๐ฐ Billing Impact: Billing app connection triggers full data sync (quota impact)
- โฑ๏ธ Rate Limits: Stripe SDK handles automatic retry with exponential backoff
- ๐ Token Refresh: Stripe Connect tokens don't expire, but can be revoked
- ๐ Logging: All auth operations logged with initiator
external/Integrations/Stripe/ - ๐จ Error Propagation: OAuth errors redirect to
forward_urlwith error parameters - ๐ Account Uniqueness: One Stripe account can only connect to one DashClicks account
- ๐ฏ Connected Apps: Multiple DashClicks apps can share same Stripe token
๐ Related Documentationโ
- Integration Overview: Stripe Integration
- Stripe OAuth Docs: Stripe Connect OAuth
- Webhook Documentation: Webhooks
- Queue Processing: Queue Manager