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¶
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
Option 2: Subscription Model (Recommended for Meister Bill)¶
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:
- 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 ```
- Backend API Routes
POST /stripe/connect- Start connection flowGET /stripe/status- Check connection statusDELETE /stripe/disconnect- Remove connectionPOST /stripe/payment-link/:invoiceId- Generate payment link-
POST /stripe/webhook- Handle Stripe events -
Frontend Pages
- Settings → Payments tab (connect/disconnect)
- Invoice detail page (generate/copy payment link)
What's NOT Implemented Yet¶
- Application Fees
- Taking a percentage of transactions
-
Would require adding
application_fee_percentorapplication_fee_amount -
Platform Subscriptions
- Charging service providers for using Meister Bill
-
Would need separate Stripe Billing integration
-
Payout Management
- Service providers manage payouts through their Stripe Dashboard
-
Could add Express Dashboard links in the future
-
Analytics Dashboard
- Transaction volume per service provider
- Total platform revenue from fees
- 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¶
- Go to Stripe Dashboard
- Login to Stripe Dashboard
-
Switch to Live mode (top right toggle)
-
Add Endpoint
- Click "Add endpoint" button
- Enter your endpoint URL:
https://api.meister-bill.com/stripe/webhook -
(Replace
api.meister-bill.comwith your actual API domain) -
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.
- Add Endpoint
- Click "Add endpoint" to save
-
You'll be taken to the endpoint details page
-
Copy Webhook Signing Secret
- On the endpoint details page, find "Signing secret"
- Click "Reveal" to show the secret (starts with
whsec_...) - Click "Copy" to copy it to clipboard
-
Important: Keep this secret secure!
-
Add to Environment Variables
In your production environment (e.g., Fly.io secrets), add:
bash
STRIPE_WEBHOOK_SECRET=whsec_your_production_webhook_secret_here
Option B: Development Webhooks (Stripe CLI - Recommended)¶
For local development, use Stripe CLI to forward webhooks to your local API:
- 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 ```
- 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
- 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. ```
- 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...
- 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:
- Install ngrok
```bash # macOS (using Homebrew) brew install ngrok
# Or download from https://ngrok.com/download ```
- Start Your API Server
bash
pnpm --filter @meisterbill/api dev
- Start ngrok
bash
ngrok http 3002
Output:
Forwarding https://abc123def456.ngrok.io -> http://localhost:3002
-
Configure Stripe Dashboard
- Click "Add endpoint"
- Enter:
https://abc123def456.ngrok.io/stripe/webhook - Select the three events (checkout.session.completed, account.updated, payment_intent.succeeded)
- Click "Add endpoint"
-
Copy the Signing secret
-
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¶
- 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 ```
- Connect a test Stripe account:
- Go to Settings → Payments
- Click "Connect Stripe Account"
-
Create a test invoice:
- Create an invoice in Meister Bill
- Click "Generate Payment Link"
-
Copy the payment link
-
Make a test payment:
- Open the payment link in a new browser tab
- Use test card:
4242 4242 4242 4242 - Expiry: Any future date (e.g.,
12/34) - CVC: Any 3 digits (e.g.,
123) -
Complete the payment
-
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
- Check the invoice:
- Refresh the invoice page in Meister Bill
- Status should now be "Paid"
- 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
-
Start fresh
stripe listensession:bash stripe listen --forward-to http://localhost:3001/stripe/webhook -
Copy the webhook signing secret from output: ```
Ready! Your webhook signing secret is whsec_abc123... ```
-
Update .env file with the EXACT secret:
bash # apps/api/.env STRIPE_WEBHOOK_SECRET=whsec_abc123def456... -
Restart API server:
bash # Ctrl+C to stop, then restart pnpm --filter @meisterbill/api dev -
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)¶
-
Add Application Fee Support
javascript // Optional: Take small fee per transaction application_fee_percent: 1.0, // 1% -
Express Dashboard Links
javascript // Let service providers access their Stripe Dashboard const loginLink = await stripe.accounts.createLoginLink(accountId); -
Payment Analytics
- Total payments processed
- Success rate
- Average invoice amount
Medium-term (Next Quarter)¶
- Subscription Billing for Platform
- Implement Stripe Billing
- Free tier with limitations
-
Paid tiers with features
-
Automatic Payouts Configuration
- Let service providers configure payout schedule
- Show next payout date
-
Display available balance
-
Refund Management
- Allow service providers to issue refunds
- Track refund history
- Update invoice status
Long-term (Roadmap)¶
- Multi-account Management
- Service providers with teams
-
Multiple connected accounts per organization
-
Advanced Fee Structures
- Volume-based discounts
- Custom fee agreements
-
Revenue sharing models
-
White-label Stripe Branding
- Customize Stripe Checkout appearance
- Service provider branding on payment pages
📚 Additional Resources¶
Stripe Documentation¶
Meister Bill Internal Docs¶
- Development Setup - Environment configuration
- Feature Catalogue - All planned features
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)