Skip to content

Stripe Payment Workflow Architecture

This document explains how Stripe payments work in Meister Bill, including the relationship between the platform, service providers, and end customers.


🏗️ Architecture Overview

Meister Bill uses Stripe Connect to enable a multi-party payment system:

┌─────────────────────────────────────────────────────────────┐
│                    MEISTER BILL PLATFORM                     │
│                  (Your Main Stripe Account)                  │
└─────────────────────────────────────────────────────────────┘
                            │
                            │ Stripe Connect
                            │ (Express Accounts)
                            │
        ┌───────────────────┼───────────────────┐
        │                   │                   │
        ▼                   ▼                   ▼
┌───────────────┐   ┌───────────────┐   ┌───────────────┐
│ SERVICE       │   │ SERVICE       │   │ SERVICE       │
│ PROVIDER 1    │   │ PROVIDER 2    │   │ PROVIDER 3    │
│ (Freelancer)  │   │ (Coach)       │   │ (Designer)    │
└───────────────┘   └───────────────┘   └───────────────┘
        │                   │                   │
        │ Payment Links     │                   │
        │                   │                   │
        ▼                   ▼                   ▼
┌───────────────┐   ┌───────────────┐   ┌───────────────┐
│ END CUSTOMER  │   │ END CUSTOMER  │   │ END CUSTOMER  │
│ (Pays invoice)│   │ (Pays invoice)│   │ (Pays invoice)│
└───────────────┘   └───────────────┘   └───────────────┘

💰 Payment Flow

Level 1: Platform ↔ Service Providers

You (Meister Bill) charge your customers (service providers) for using the platform.

This is typically done through: - Subscription fees - Usage-based billing - Feature upgrades

Implementation Note: This part is NOT yet implemented in the current codebase. You would need to: 1. Create your own Stripe Checkout/Billing integration for subscriptions 2. Charge service providers directly from your main Stripe account 3. Track subscriptions in your database

Example flow:

// Not implemented yet - example only
const subscription = await stripe.subscriptions.create({
  customer: serviceProviderStripeCustomerId,
  items: [{ price: 'price_monthly_plan' }],
});

Level 2: Service Provider ↔ End Customers ✅ (Implemented)

Your customers (service providers) charge their customers (end customers) using Stripe Connect.

This IS what we just implemented:

1. Service Provider connects Stripe account
   └─> Settings → Payments → Connect Stripe
   └─> Completes Stripe onboarding
   └─> Status: connected

2. Service Provider generates payment link
   └─> Invoice detail page → Generate Payment Link
   └─> System creates Stripe Payment Link on their connected account

3. End Customer pays invoice
   └─> Clicks payment link
   └─> Stripe Checkout hosted page
   └─> Payment goes directly to Service Provider's Stripe account

4. Webhook updates invoice status
   └─> Stripe sends webhook to /stripe/webhook
   └─> System marks invoice as "paid"
   └─> Creates payment record

🔑 Key Concepts

Stripe Connect Express Accounts

Each service provider gets their own Stripe Express account, which: - ✅ Is separate from your platform account - ✅ Receives payments directly from end customers - ✅ Handles their own payouts to their bank - ✅ Manages their own tax/compliance - ✅ Is created and managed through your platform

Benefits: - Service providers don't need their own Stripe account - You (platform) maintain control over onboarding - Simplified compliance (Stripe handles most of it) - Easy integration with minimal code

Payment links are: - Hosted Stripe Checkout pages - Created on the service provider's connected account - Unique per invoice - Support multiple payment methods (card, Apple Pay, Google Pay, etc.) - Handle currency conversion automatically


💸 Revenue Models

Option 1: Platform Fees (Not Yet Implemented)

You can take a percentage of each transaction through application fees:

// Example: Take 3% platform fee
const paymentLink = await stripe.paymentLinks.create({
  line_items: [...],
  application_fee_percent: 3.0, // 3% goes to platform
}, {
  stripeAccount: serviceProviderStripeAccountId,
});

Revenue split:

End Customer pays: €100
├─> Service Provider receives: €97
└─> Platform (you) receives: €3

Charge service providers a monthly/annual subscription: - Basic Plan: €9.90/month - Pro Plan: €19.90/month - Enterprise: Custom pricing

This is separate from the Connect integration and would use standard Stripe Billing.

Option 3: Hybrid Model

Combine both: - Base subscription fee (e.g., €4.90/month) - Small transaction fee (e.g., 1% per invoice paid) - Free tier with limitations


🔐 Security & Compliance

Who Handles What?

Responsibility Platform (You) Stripe Service Provider
PCI Compliance ❌ No ✅ Yes ❌ No
Customer card data ❌ Never see it ✅ Stores securely ❌ Never see it
Payouts to bank ❌ Not involved ✅ Automated ✅ Configures
Tax compliance Partial* Partial* ✅ Yes
Fraud prevention ❌ Not needed ✅ Yes ❌ Not needed
Disputes/Chargebacks ❌ Not involved Helps mediate ✅ Handles

*Tax: Stripe helps with tax calculation, but ultimate responsibility varies by jurisdiction.


🛠️ Implementation Details

Current Implementation (Issue #28)

What we built:

  1. Database Schema ```sql -- service_providers table stripe_account_id TEXT -- Connected account ID stripe_account_status ENUM -- disconnected/pending/connected stripe_onboarding_complete BOOL -- Onboarding status

-- documents table stripe_payment_link TEXT -- Payment link URL ```

  1. Backend API Routes
  2. POST /stripe/connect - Start connection flow
  3. GET /stripe/status - Check connection status
  4. DELETE /stripe/disconnect - Remove connection
  5. POST /stripe/payment-link/:invoiceId - Generate payment link
  6. POST /stripe/webhook - Handle Stripe events

  7. Frontend Pages

  8. Settings → Payments tab (connect/disconnect)
  9. Invoice detail page (generate/copy payment link)

What's NOT Implemented Yet

  1. Application Fees
  2. Taking a percentage of transactions
  3. Would require adding application_fee_percent or application_fee_amount

  4. Platform Subscriptions

  5. Charging service providers for using Meister Bill
  6. Would need separate Stripe Billing integration

  7. Payout Management

  8. Service providers manage payouts through their Stripe Dashboard
  9. Could add Express Dashboard links in the future

  10. Analytics Dashboard

  11. Transaction volume per service provider
  12. Total platform revenue from fees
  13. Would query Stripe API for connected account data

📊 Example User Journey

For Service Provider (Your Customer)

Day 1: Sign up for Meister Bill
├─> Creates account
├─> Adds first customer
└─> Creates first invoice

Day 2: Connect Stripe
├─> Goes to Settings → Payments
├─> Clicks "Connect Stripe Account"
├─> Redirected to Stripe onboarding
│   ├─> Provides business details
│   ├─> Provides bank account
│   └─> Completes verification
└─> Returned to Meister Bill (Status: Connected)

Day 3: Send first invoice with payment link
├─> Opens invoice
├─> Clicks "Generate Payment Link"
├─> System creates payment link on their Stripe account
├─> Copies link and emails to customer
└─> Customer clicks link and pays
    ├─> Money goes to Service Provider's Stripe account
    ├─> Webhook updates invoice status to "paid"
    └─> Service Provider receives payout 2 days later

For End Customer (Service Provider's Customer)

1. Receives invoice email with payment link
2. Clicks link → Opens Stripe Checkout
3. Enters card details (or uses Apple/Google Pay)
4. Completes payment
5. Receives receipt email from Stripe
6. Invoice marked as paid in Meister Bill

🌍 Multi-Currency Support

Payment links support automatic currency conversion:

// Invoice in EUR
const paymentLink = await stripe.paymentLinks.create({
  line_items: [{
    price_data: {
      currency: 'eur',
      unit_amount: 10000, // €100.00
      product_data: {
        name: 'Invoice #INV-2024-001',
      },
    },
    quantity: 1,
  }],
}, {
  stripeAccount: serviceProviderStripeAccountId,
});

The end customer can pay in EUR, but Stripe can: - Show prices in customer's local currency - Handle conversion automatically - Service provider receives in their account currency


🔄 Webhook Setup & Configuration

What Are Webhooks?

Webhooks allow Stripe to notify your application when events happen in your Stripe account. This is critical for the payment workflow because:

  • ✅ Automatically updates invoice status when customers pay
  • ✅ Updates service provider connection status
  • ✅ Creates payment records without manual intervention
  • ✅ Enables real-time synchronization between Stripe and Meister Bill

Without webhooks configured, invoices will NOT automatically update to "paid" status!


🛠️ Webhook Endpoint Setup

Step 1: Choose Your Environment

For Development/Testing: You need to expose your local API to the internet so Stripe can send webhooks to it.

For Production: Use your live API URL (e.g., https://api.meister-bill.com/stripe/webhook)


Step 2: Configure Stripe Dashboard

Option A: Production Webhooks
  1. Go to Stripe Dashboard
  2. Login to Stripe Dashboard
  3. Switch to Live mode (top right toggle)

  4. Add Endpoint

  5. Click "Add endpoint" button
  6. Enter your endpoint URL: https://api.meister-bill.com/stripe/webhook
  7. (Replace api.meister-bill.com with your actual API domain)

  8. Select Events to Listen To

Click "Select events" and choose these events:

  • checkout.session.completed - Payment completed
  • account.updated - Connect account status changed
  • payment_intent.succeeded - Payment successful (backup)

Or select "Select all checkout events" and "Select all account events" for simplicity.

  1. Add Endpoint
  2. Click "Add endpoint" to save
  3. You'll be taken to the endpoint details page

  4. Copy Webhook Signing Secret

  5. On the endpoint details page, find "Signing secret"
  6. Click "Reveal" to show the secret (starts with whsec_...)
  7. Click "Copy" to copy it to clipboard
  8. Important: Keep this secret secure!

  9. Add to Environment Variables

In your production environment (e.g., Fly.io secrets), add: bash STRIPE_WEBHOOK_SECRET=whsec_your_production_webhook_secret_here

For local development, use Stripe CLI to forward webhooks to your local API:

  1. Install Stripe CLI

```bash # macOS (using Homebrew) brew install stripe/stripe-cli/stripe

# Linux wget https://github.com/stripe/stripe-cli/releases/download/vX.X.X/stripe_X.X.X_linux_x86_64.tar.gz tar -xvf stripe_*.tar.gz sudo mv stripe /usr/local/bin/

# Windows (using Scoop) scoop install stripe ```

  1. Login to Stripe

bash stripe login

This will: - Open your browser for authentication - Link the CLI to your Stripe account - Display your account name when successful

  1. Start Webhook Forwarding

```bash # Start your API server first pnpm --filter @meisterbill/api dev

# In another terminal, forward webhooks stripe listen --forward-to http://localhost:3002/stripe/webhook ```

Output will look like: ```

Ready! Your webhook signing secret is whsec_1234567890abcdef... You can now test your webhooks locally. ```

  1. Copy the Signing Secret

The CLI will output a webhook signing secret. Copy it and add to your .env file:

bash # apps/api/.env STRIPE_WEBHOOK_SECRET=whsec_1234567890abcdef...

  1. Restart Your API Server

bash # Restart to load the new environment variable pnpm --filter @meisterbill/api dev

Benefits of Stripe CLI: - ✅ No need to expose localhost to internet - ✅ See webhook events in terminal in real-time - ✅ Trigger test events manually - ✅ Faster development workflow

Option C: Development Webhooks (ngrok)

Alternative to Stripe CLI, use ngrok to expose your local server:

  1. Install ngrok

```bash # macOS (using Homebrew) brew install ngrok

# Or download from https://ngrok.com/download ```

  1. Start Your API Server

bash pnpm --filter @meisterbill/api dev

  1. Start ngrok

bash ngrok http 3002

Output: Forwarding https://abc123def456.ngrok.io -> http://localhost:3002

  1. Configure Stripe Dashboard

  2. Go to Stripe Dashboard > Webhooks

  3. Click "Add endpoint"
  4. Enter: https://abc123def456.ngrok.io/stripe/webhook
  5. Select the three events (checkout.session.completed, account.updated, payment_intent.succeeded)
  6. Click "Add endpoint"
  7. Copy the Signing secret

  8. Add to .env

bash STRIPE_WEBHOOK_SECRET=whsec_your_ngrok_webhook_secret_here

Note: ngrok URLs change every time you restart, so you'll need to update the Stripe webhook endpoint URL each time.


🔍 Webhook Event Details

The system handles these webhook events automatically:

checkout.session.completed

When: Triggered when an end customer successfully completes payment through a payment link.

What it does:

// apps/api/src/services/StripeService.ts:319-343
async handleCheckoutSessionCompleted(event) {
  const session = event.data.object;
  const invoiceId = session.metadata.invoice_id;

  // 1. Update invoice status to "paid"
  await db.update('documents')
    .set({ status: 'paid' })
    .where({ id: invoiceId });

  // 2. Create payment record
  await db.insert('payments').values({
    document_id: invoiceId,
    amount: session.amount_total / 100, // Convert cents to currency
    payment_date: new Date().toISOString().split('T')[0],
  });
}

Payload example:

{
  "id": "evt_1234567890",
  "type": "checkout.session.completed",
  "data": {
    "object": {
      "id": "cs_test_123...",
      "amount_total": 10000,
      "currency": "eur",
      "payment_status": "paid",
      "metadata": {
        "invoice_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
        "service_provider_id": "...",
        "document_number": "INV-2024-001"
      }
    }
  }
}

account.updated

When: Triggered when a service provider's Stripe Connect account status changes (e.g., completes onboarding, enables payouts).

What it does:

// apps/api/src/services/StripeService.ts:348-375
async handleAccountUpdated(event) {
  const account = event.data.object;

  // Find service provider with this Stripe account ID
  const serviceProvider = await db.query('service_providers')
    .where({ stripe_account_id: account.id })
    .first();

  // Check if account is fully set up
  const isComplete =
    account.charges_enabled &&
    account.payouts_enabled &&
    !account.requirements?.currently_due?.length;

  // Update service provider status
  await db.update('service_providers')
    .set({
      stripe_account_status: isComplete ? 'connected' : 'pending',
      stripe_onboarding_complete: isComplete,
    })
    .where({ id: serviceProvider.id });
}

Payload example:

{
  "id": "evt_9876543210",
  "type": "account.updated",
  "data": {
    "object": {
      "id": "acct_1234567890ABCDEF",
      "charges_enabled": true,
      "payouts_enabled": true,
      "requirements": {
        "currently_due": [],
        "eventually_due": []
      }
    }
  }
}

payment_intent.succeeded

When: Triggered when a payment is successfully processed (backup for checkout.session.completed).

What it does:

// apps/api/src/services/StripeService.ts:380-409
async handlePaymentSucceeded(event) {
  const paymentIntent = event.data.object;
  const invoiceId = paymentIntent.metadata.invoice_id;

  // Check if invoice is already marked as paid
  // (to avoid duplicate payment records)
  const invoice = await db.query('documents')
    .where({ id: invoiceId })
    .first();

  if (invoice.status !== 'paid') {
    // Update invoice and create payment record
    await db.update('documents')
      .set({ status: 'paid' })
      .where({ id: invoiceId });

    await db.insert('payments').values({
      document_id: invoiceId,
      amount: paymentIntent.amount / 100,
      payment_date: new Date().toISOString().split('T')[0],
    });
  }
}

Why this is needed: In rare cases, checkout.session.completed may not fire. This event acts as a safety net.


✅ Testing Webhooks

Manual Testing with Stripe CLI

You can trigger test webhook events manually:

# Trigger a test checkout session completed event
stripe trigger checkout.session.completed

# Trigger a test account updated event
stripe trigger account.updated

# Trigger a test payment intent succeeded event
stripe trigger payment_intent.succeeded

Watch your API logs to see the events being processed.


End-to-End Testing

  1. Start your development environment: ```bash # Terminal 1: Start API pnpm --filter @meisterbill/api dev

# Terminal 2: Start web frontend pnpm --filter @meisterbill/web dev

# Terminal 3: Forward webhooks stripe listen --forward-to http://localhost:3002/stripe/webhook ```

  1. Connect a test Stripe account:
  2. Go to Settings → Payments
  3. Click "Connect Stripe Account"
  4. Use Stripe's test account details

  5. Create a test invoice:

  6. Create an invoice in Meister Bill
  7. Click "Generate Payment Link"
  8. Copy the payment link

  9. Make a test payment:

  10. Open the payment link in a new browser tab
  11. Use test card: 4242 4242 4242 4242
  12. Expiry: Any future date (e.g., 12/34)
  13. CVC: Any 3 digits (e.g., 123)
  14. Complete the payment

  15. Verify webhook processing:

In your Stripe CLI terminal, you should see: 2025-01-11 14:23:45 --> checkout.session.completed [evt_test_123...] 2025-01-11 14:23:45 <-- [200] POST http://localhost:3002/stripe/webhook

In your API logs, you should see: Stripe webhook event: checkout.session.completed Payment completed for invoice: a1b2c3d4-e5f6-7890-abcd-ef1234567890

  1. Check the invoice:
  2. Refresh the invoice page in Meister Bill
  3. Status should now be "Paid"
  4. A payment record should be created

🔒 Security Best Practices

Webhook Signature Verification

The webhook handler automatically verifies the Stripe signature to prevent unauthorized requests:

// apps/api/src/services/StripeService.ts:311-321
async handleWebhook(payload, signature) {
  const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET;

  if (!webhookSecret) {
    throw new Error("STRIPE_WEBHOOK_SECRET not configured");
  }

  try {
    // Verify signature - throws error if invalid
    const event = await this.stripe.webhooks.constructEventAsync(
      payload,
      signature,
      webhookSecret
    );

    // Only processes events with valid signatures
    return event;
  } catch (error) {
    throw new Error("Webhook signature verification failed");
  }
}

Implementation Details: - Uses constructEventAsync() for async signature verification - Raw request body is obtained via c.req.text() in the route handler - Route handler does NOT use OpenAPI validation to preserve raw body - Signature is verified before any processing occurs

Why this matters: - ✅ Prevents malicious actors from sending fake webhook events - ✅ Ensures events genuinely come from Stripe - ✅ Protects against replay attacks - ✅ Raw body required - any modification breaks signature verification

The webhook endpoint is public (doesn't require authentication), but it verifies the Stripe signature, which is more secure than a bearer token.

Critical: Stripe webhook signature verification requires the exact raw request body as sent by Stripe. The implementation uses c.req.text() directly in the handler (not middleware) to get the unmodified body before any framework parsing occurs.


Environment Variable Management

Never commit webhook secrets to version control!

Good:

# apps/api/.env (gitignored)
STRIPE_WEBHOOK_SECRET=whsec_your_secret_here

Bad:

// apps/api/src/config.ts (committed to git)
const WEBHOOK_SECRET = 'whsec_1234567890'; // DON'T DO THIS!

Idempotency

The webhook handlers implement idempotency checks:

// Check if invoice is already paid before updating
if (invoice.status !== 'paid') {
  // Only update if not already paid
  await updateInvoice(invoiceId, { status: 'paid' });
}

This prevents duplicate payment records if Stripe retries the webhook.


🐛 Troubleshooting

Webhook Not Firing

Problem: Created a payment link and paid, but invoice status didn't update.

Checklist: 1. ✅ Is STRIPE_WEBHOOK_SECRET set in your .env file? 2. ✅ Did you restart the API server after adding the secret? 3. ✅ Is Stripe CLI running (stripe listen)? 4. ✅ Is your webhook endpoint accessible? 5. ✅ Check Stripe Dashboard → Webhooks → [Your endpoint] → "Recent events" - Look for failed deliveries (red) - Click on an event to see error details


Signature Verification Failed

Error: Webhook signature verification failed

Causes: - Using wrong webhook secret (production vs test) - Secret not set in environment variables - Stripe CLI not running in development - Multiple stripe listen processes with different secrets - Request not coming from Stripe (security working as intended!)

Fix: 1. Kill all existing stripe listen processes: ```bash # Find all stripe listen processes ps aux | grep "stripe listen"

# Kill them (replace PID with actual process ID) kill ```

  1. Start fresh stripe listen session: bash stripe listen --forward-to http://localhost:3001/stripe/webhook

  2. Copy the webhook signing secret from output: ```

    Ready! Your webhook signing secret is whsec_abc123... ```

  3. Update .env file with the EXACT secret: bash # apps/api/.env STRIPE_WEBHOOK_SECRET=whsec_abc123def456...

  4. Restart API server: bash # Ctrl+C to stop, then restart pnpm --filter @meisterbill/api dev

  5. Verify it works: ```bash # Trigger a test event stripe trigger payment_intent.succeeded

# Should see HTTP 200 in stripe listen output ```

Important: Each stripe listen session generates a unique webhook secret. You MUST update your .env file with the secret from your CURRENT running session.


Duplicate Payment Records

Problem: One payment created multiple payment records.

Causes: - Both checkout.session.completed and payment_intent.succeeded fired - Webhook retry after temporary failure

Fix: The code already handles this with idempotency checks (lines 398-409 in StripeService.ts). If you're still seeing duplicates, add unique constraints to your database:

-- Prevent duplicate payments for same invoice
ALTER TABLE payments
ADD CONSTRAINT unique_payment_per_invoice
UNIQUE (document_id, payment_date, amount);

No Webhook Events in Stripe CLI

Problem: stripe listen shows "Ready!" but no events appear.

Causes: - Using live mode credentials but testing in test mode - Events are firing on a different Stripe account - Payment link created before webhook listener started

Fix: 1. Make sure you're in test mode in Stripe Dashboard 2. Check STRIPE_SECRET_KEY starts with sk_test_ (not sk_live_) 3. Create a new payment link AFTER starting stripe listen 4. Complete a new test payment


📊 Monitoring Webhooks

Stripe Dashboard

Monitor webhook health in Stripe Dashboard > Webhooks:

  • Recent deliveries: See all webhook events sent
  • Success rate: Should be >99%
  • Response time: Should be <500ms
  • Failed deliveries: Investigate any red events

API Logs

Your API logs show webhook processing:

# Successful processing
Stripe webhook event: checkout.session.completed
Payment completed for invoice: a1b2c3d4-...

# Failed processing
Error handling webhook: Invoice not found
Webhook signature verification failed

Retry Behavior

Stripe automatically retries failed webhooks: - Initial retry: After 5 seconds - Additional retries: Up to 3 days - Backoff: Exponential (5s → 1m → 5m → 30m → 1h → etc.)

If a webhook fails repeatedly, Stripe marks it as failed and stops retrying.


📝 Webhook Event Log

For debugging, you can log all webhook events to your database:

// Optional: Add to apps/api/src/services/StripeService.ts
async handleWebhook(payload, signature) {
  const event = this.stripe.webhooks.constructEvent(...);

  // Log webhook event to database
  await SystemSupabaseClient.from('webhook_logs').insert({
    event_id: event.id,
    event_type: event.type,
    payload: event,
    received_at: new Date().toISOString(),
  });

  // Process event...
}

Then query webhook logs to troubleshoot issues:

SELECT * FROM webhook_logs
WHERE event_type = 'checkout.session.completed'
ORDER BY received_at DESC
LIMIT 10;

💡 Business Model Recommendations

Based on the Meister Bill feature catalog, here's what makes sense:

For Platform Revenue (You)

Recommended: Subscription-based with feature tiers

Free Tier (€0/month)
├─> Up to 5 invoices/month
├─> Basic features only
└─> Meister Bill branding on invoices

Starter (€9.90/month)
├─> Unlimited invoices
├─> Remove branding
├─> Payment links (Stripe Connect)
└─> Email support

Pro (€19.90/month)
├─> Everything in Starter
├─> Advanced features (time tracking, projects, etc.)
├─> Multi-currency
└─> Priority support

Enterprise (€49.90/month)
├─> Everything in Pro
├─> API access
├─> Webhook integrations
└─> Dedicated support

Why subscription over transaction fees? - ✅ Predictable revenue - ✅ Simpler for service providers (no surprise costs) - ✅ Better for high-volume users - ✅ Aligns with SaaS model

For Service Provider Revenue (Your Customers)

Service providers receive 100% of their invoice amounts (minus Stripe's standard processing fees of ~2.9% + €0.30).

This is competitive because: - No platform fees on top of Stripe fees - Direct payouts to their bank - Full control over pricing


🚀 Future Enhancements

Short-term (Next Sprint)

  1. Add Application Fee Support javascript // Optional: Take small fee per transaction application_fee_percent: 1.0, // 1%

  2. Express Dashboard Links javascript // Let service providers access their Stripe Dashboard const loginLink = await stripe.accounts.createLoginLink(accountId);

  3. Payment Analytics

  4. Total payments processed
  5. Success rate
  6. Average invoice amount

Medium-term (Next Quarter)

  1. Subscription Billing for Platform
  2. Implement Stripe Billing
  3. Free tier with limitations
  4. Paid tiers with features

  5. Automatic Payouts Configuration

  6. Let service providers configure payout schedule
  7. Show next payout date
  8. Display available balance

  9. Refund Management

  10. Allow service providers to issue refunds
  11. Track refund history
  12. Update invoice status

Long-term (Roadmap)

  1. Multi-account Management
  2. Service providers with teams
  3. Multiple connected accounts per organization

  4. Advanced Fee Structures

  5. Volume-based discounts
  6. Custom fee agreements
  7. Revenue sharing models

  8. White-label Stripe Branding

  9. Customize Stripe Checkout appearance
  10. Service provider branding on payment pages

📚 Additional Resources

Stripe Documentation

Meister Bill Internal Docs

Code References

  • StripeService: apps/api/src/services/StripeService.ts
  • Stripe Routes: apps/api/src/routes/stripe.ts
  • useStripe Composable: apps/web/composables/useStripe.ts
  • Migration: database/migrations/002_add_stripe_integration.sql

❓ FAQ

Q: Do I need to handle PCI compliance? A: No. Stripe handles all PCI compliance. You never touch card data.

Q: Can I take a fee from transactions? A: Yes, through Stripe application fees. Not currently implemented, but easy to add.

Q: What if a service provider disputes a charge? A: They handle it through their Stripe Dashboard. You're not involved in the dispute process.

Q: Can service providers use their existing Stripe account? A: No. Express accounts are created through your platform. This is by design for security and compliance.

Q: How do refunds work? A: Service providers handle refunds through their Stripe Dashboard or you can add refund API integration.

Q: What happens if I disconnect a service provider's Stripe account? A: Existing payment links stop working. Past payments remain in their Stripe account. They'd need to reconnect to generate new links.

Q: Can I see the service provider's transaction data? A: Only with their permission via OAuth scopes. Express accounts limit platform access by default for privacy.

Q: How long does onboarding take? A: Usually 5-10 minutes. Stripe may require additional verification for some businesses.

Q: What currencies are supported? A: All currencies supported by Stripe (135+ currencies). Payment links adapt to the invoice currency.


Last Updated: 2025-01-11 Issue: #28 Status: ✅ Implemented (Payment Links), 🔜 Pending (Platform Subscriptions)