Skip to main content

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 auth
  • connected_app (String, Required) - One of: billing, review, funnel
  • contact_mapping (String, Optional) - Base64 encoded contact field mappings for billing
  • subaccountid (String, Optional) - Sub-account ID if connecting for child account

Returns: 302 Redirect

Business Logic Flow:

  1. Validate Required Parameters

    • Check forward_url exists
    • Validate connected_app is in allowed list: ['billing', 'review', 'funnel']
    • Return 400 if validation fails
  2. Handle Sub-Account

    • If subaccountid provided, validate it belongs to parent account
    • Switch accountId to sub-account ID
  3. Check Existing Token

    • Query stripe-key collection for account
    • If found:
      • Check if connected_app already in connected_apps array
      • If not, add app and trigger app-specific setup:
        • review: Register webhook for charge.succeeded
        • billing: Create queue jobs, save contact mappings
      • If token_invalidated: true, delete token and restart OAuth
      • Otherwise, redirect to success with token ID
  4. 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

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_apps array
  • โš ๏ธ 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 OAuth
  • state (String) - JWT token containing original request parameters

Returns: 302 Redirect to forward_url

Business Logic Flow:

  1. Verify State Token

    • Decode JWT state parameter
    • Extract account_id, forward_url, contact_mapping, connected_app
    • Return 401 if JWT verification fails
  2. Check Existing Connection

    • Query stripe-key by account_id
    • If exists, redirect to success immediately (prevents duplicate connections)
  3. 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"
      }
  4. Check Duplicate Connection

    • Query if stripe_user_id already connected to another account
    • If yes, reject with error: "Stripe account is already connected to another Dashboard"
  5. Prepare Data to Save

    • Parse contact_mapping from Base64
    • Build dataToSave object:
      {
      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"
      }
      }
  6. 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,
      });
  7. Save Token

    • Insert into stripe-key collection
    • Return saved document ID
  8. Redirect to Success

    • Redirect to: {forward_url}?status=success&integration=stripe&token={saved_id}

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_id exists 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, or all
  • subaccountid (String, Optional) - Sub-account ID if disconnecting child account

Returns: JSON { success: true, message: "SUCCESS" }

Business Logic Flow:

  1. Validate Parameters

    • Check connected_app is in allowed list: ['billing', 'review', 'funnel', 'all']
    • Return 400 if invalid
  2. Handle Sub-Account

    • If subaccountid provided, validate ownership
    • Switch to sub-account ID
  3. Find Token Document

    • Query stripe-key by account_id
    • Return 400 if not found
  4. 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)
  5. 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 });

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
  • 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_grant error
  • 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_id uniqueness 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_apps array
  • 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_url with error parameters
  • ๐Ÿ”— Account Uniqueness: One Stripe account can only connect to one DashClicks account
  • ๐ŸŽฏ Connected Apps: Multiple DashClicks apps can share same Stripe token

๐Ÿ’ฌ

Documentation Assistant

Ask me anything about the docs

Hi! I'm your documentation assistant. Ask me anything about the docs!

I can help you with:
- Code examples
- Configuration details
- Troubleshooting
- Best practices

Try asking: How do I configure the API?
09:31 AM