Appearance
Stripe Integration
Overview
AcqMarketplace integrates with Stripe for subscription management, payment processing, and billing operations. The integration includes automatic subscription synchronization, real-time status monitoring, and comprehensive error handling.
Architecture
Core Components
Subscription Management
useSubscriptionhook - Main subscription state managementuseSubscriptionMonitorhook - Background monitoring and manual syncBillingTabcomponent - User-facing subscription interface
Edge Functions
Subscription & Billing:
check-subscription- Syncs subscription status with Stripestripe-sync- Comprehensive Stripe data synchronizationdaily-subscription-sync- Automated daily subscription checkscreate-checkout- Creates subscription checkout sessionscustomer-portal- Manages billing portal accessstripe-webhook- Handles Stripe webhook eventssync-invoices- Syncs invoice data from Stripe
Stripe Connect (Marketplace):
connect-dashboard-link- Generates Stripe Connect dashboard linksconnect-onboarding-link- Creates onboarding links for sellersrefresh-kyc-status- Updates KYC verification status
Payment Processing:
create-deal-checkout- Creates checkout for deal paymentscreate-deal-payment- Processes deal paymentspayment-status- Checks payment statusrefund-deal-payment- Handles payment refundsrelease-deal-payout- Releases funds to sellers
Utility Functions:
get-price-id- Retrieves Stripe price IDstest-stripe- Stripe integration testingauto-fix-subscription-dates- Fixes subscription date inconsistencies
Database Tables
subscriptions- Primary subscription data (Stripe source of truth)profiles- User profile with subscription fields (synced from subscriptions)
Subscription Sync System
Automatic Synchronization
On Authentication
When a user successfully logs in, the system automatically:
- Triggers
check-subscriptionEdge Function - Syncs current subscription status with Stripe
- Updates both
subscriptionsandprofilestables - Shows immediate notification if subscription has actually expired and there was a previous paid period (no deduplication; new users without a prior period do NOT see a toast)
typescript
// AuthContext.tsx - Automatic sync on login
if (session?.user && event === 'SIGNED_IN') {
window.requestIdleCallback(async () => {
queryClient.invalidateQueries({ queryKey: ['subscription'] });
const { data, error } = await supabase.functions.invoke('check-subscription');
// Handle sync result and notifications
}, { timeout: 1000 });
}Background Monitoring
The useSubscriptionMonitor hook provides:
- Periodic Checks: Every 60 minutes in background
- Manual Trigger: "Check Status" button for instant sync (no notification)
- Smart Throttling: Prevents excessive API calls
- Notification Deduplication: Shows notification only if 55+ minutes passed since last one
- Error Handling: Graceful failure with user feedback
typescript
// useSubscriptionMonitor.ts - Background monitoring
const SUBSCRIPTION_CHECK_INTERVAL = 60 * 60 * 1000; // 60 minutes
useEffect(() => {
const intervalId = setInterval(() => {
checkSubscriptionStatus();
}, SUBSCRIPTION_CHECK_INTERVAL);
return () => clearInterval(intervalId);
}, [user, checkSubscriptionStatus]);Data Consistency
Priority System
The system uses a clear data priority hierarchy:
- Primary Source:
subscriptionstable (Stripe data) - Fallback:
profilestable (synced data) - Default: Free plan if no data exists
typescript
// BillingTab.tsx - Data priority
const rawLevel = subscription?.subscription_level || 'free';
const rawStatus = subscription?.subscription_status || 'inactive';
const rawEnd = subscription?.current_period_end ||
profile?.subscription_current_period_end ||
profile?.subscription_end_date;Sync Process
The check-subscription function ensures data consistency:
- Fetch from Stripe: Gets latest subscription data
- Update subscriptions table: Primary data store
- Update profiles table: Sync all relevant fields
- Return consistent data: Unified response format
Duplicate Subscription Guards (New)
Problem: Stripe allows multiple active subscriptions for the same customer, which led to duplicates (e.g., Starter + Pro in parallel) and UI confusion.
Changes implemented:
UI guard in
PricingPlans:- If user already has an active subscription (
hasActiveSubscription), the purchase button NO LONGER opens Checkout. - Instead, redirects to
Account Settings → Billingfor management. - Code:
src/components/pricing/PricingPlans.tsx:133-142.
- If user already has an active subscription (
Server guard in
create-checkoutEdge Function:- Before creating a Checkout session, verifies in Stripe the existence of an ACTIVE subscription for the customer.
- If it exists, does NOT create a new checkout, but generates a Billing Portal session and returns the portal URL (
reason: 'already_active'). - Code:
supabase/functions/create-checkout/index.ts:127-151(block that listsstripe.subscriptions.list({ status: 'active' })and returnsbillingPortal.sessions.create).
Plan selection rule in UI (
useSubscription):- When multiple subscriptions exist in DB, we choose deterministically:
- Prefer ACTIVE ones
- Among active ones, prefer LOWER price (Starter before Pro)
- At equal price, choose the most recent
current_period_end - Fallback: most recently registered
- Code:
src/hooks/useSubscription.ts:70-92.
- When multiple subscriptions exist in DB, we choose deterministically:
Impact:
- Prevents subscription duplication on purchase
- UI displays "Current Plan" consistently (doesn't over-report Pro when Starter is active)
- Maintains control through portal for upgrade/downgrade/cancellation
Invoice Date Logic (New)
Context: all synchronized invoices appeared to be from the same day due to using finalized_at/created for created_at.
Change:
sync-invoiceswas adjusted to usepaid_at(payment date) as priority, thenfinalized_at, thencreated.- Code:
supabase/functions/sync-invoices/index.ts:75-91(calculation ofchosenSecfromstatus_transitions.paid_at || finalized_at || created).
Result: the "Created" column in UI correctly reflects actual payment moments, not aggregated on the same date.
Stripe Connect Integration
Marketplace Payments
AcqMarketplace uses Stripe Connect to facilitate payments between buyers and sellers:
Seller Onboarding
- Connect Onboarding: Sellers complete Stripe Connect onboarding
- KYC Verification: Required for payout eligibility (separate from platform verification)
- Dashboard Access: Sellers can manage their Stripe account
Dual Verification System
AcqMarketplace implements two complementary verification systems:
Platform Verification (
seller_verification_status):- Identity and legitimacy verification
- Required for listing visibility
- Managed by platform admins
Stripe KYC (
kyc_status):- Financial compliance verification
- Required for receiving payouts
- Managed by Stripe Connect
Both verifications are required for full platform access and payout eligibility.
Payment Flow
- Deal Creation: Buyer initiates deal with seller
- Payment Processing: Funds held in escrow via Stripe
- Due Diligence: Both parties complete verification
- Fund Release: Automatic payout to seller upon completion
KYC Management
- Status Tracking: Real-time KYC verification status
- Compliance: Ensures regulatory compliance
- Payout Control: KYC required for fund releases
Connect Functions
typescript
// Connect dashboard access
const { data } = await supabase.functions.invoke('connect-dashboard-link');
// KYC status refresh
const { data } = await supabase.functions.invoke('refresh-kyc-status');Payment Processing
Deal Payments
The system handles complex payment flows for marketplace transactions:
Escrow System
- Fund Holding: Payments held securely until deal completion
- Automatic Release: Funds released upon successful completion
- Refund Handling: Automatic refunds for failed deals
Payment Functions
typescript
// Create deal payment
const { data } = await supabase.functions.invoke('create-deal-payment', {
deal_id: 'deal-123',
amount: 50000, // $500.00 in cents
currency: 'usd'
});
// Release payout to seller
const { data } = await supabase.functions.invoke('release-deal-payout', {
deal_id: 'deal-123',
seller_id: 'seller-456'
});Notification System
Notification Logic
The system implements a sophisticated notification system to inform users about subscription status changes:
Authentication Notifications
- Trigger: When user successfully logs in
- Behavior: Immediate notification only if subscription is expired AND there is a previous period (
subscription_end) in the past (new users without previous period don't see toast) - Deduplication: None (displays on login if truly expired)
- Purpose: Ensures user notification when a previous subscription has expired
Periodic Notifications
- Trigger: Every 60 minutes via background monitoring
- Behavior: Notification only if subscription expired AND 55+ minutes since last notification
- Deduplication: Prevents spam by tracking last notification time
- Purpose: Periodic reminders without overwhelming the user
Manual Check Notifications
- Trigger: When user clicks "Check Status" button
- Behavior: No subscription notifications (only sync confirmation)
- Purpose: Allow users to sync without triggering notifications
Implementation Details
typescript
// Gating + deduplication logic (simplified)
const checkSubscriptionStatus = async (isPeriodicCheck = false) => {
// ... sync logic ...
const hadPreviousPeriod = Boolean(data?.subscription_end);
const expiredInPast = hadPreviousPeriod
? new Date(data.subscription_end).getTime() < Date.now()
: false;
// Periodic notification only when truly expired and not a new user
if (data && !data.subscribed && data.subscription_status === 'inactive' && isPeriodicCheck && expiredInPast) {
const now = Date.now();
const timeSinceLastNotification = now - lastNotificationRef.current;
if (timeSinceLastNotification >= 55 * 60 * 1000) {
toast({
title: 'Subscription Expired',
description: 'Your subscription has expired. You have been switched to the free plan.',
variant: 'destructive',
duration: 10000
});
lastNotificationRef.current = now;
}
}
};Notification Scenarios
| Scenario | Notification | Deduplication |
|---|---|---|
| User logs in with expired subscription (with previous period) | ✅ Immediate | No |
| User logs in without previous period (new user) | ❌ No toast | N/A |
| Background check finds expired subscription | ✅ If 55+ min since last | Yes |
| Manual "Check Status" click | ❌ No notification | N/A |
| Page reload with expired subscription | ✅ Immediate (via login) | No |
Subscription Status Management
Status Types
- active: Subscription is current and valid
- inactive: No active subscription (free plan)
- expired: Subscription has passed end date
- past_due: Payment failed, grace period active
- canceled: Subscription was canceled
Expiration Handling
When a subscription expires:
- Status automatically changes to "expired"
- User is switched to free plan
- Immediate notification on authentication (no deduplication)
- Periodic notifications every 60 minutes (with 55-minute deduplication)
- UI updates to reflect free tier limitations
typescript
// Expiration check logic
if (rawEnd) {
const endDate = new Date(rawEnd as string);
if (new Date() > endDate && rawStatus === 'active') {
subscriptionStatus = 'expired';
}
}User Interface
Billing Tab Features
Current Plan Display
- Shows subscription level (Free/Starter/Pro)
- Displays expiration date
- Color-coded status badges
Manual Sync
- "Check Status" button for instant sync
- Loading states during sync
- Success/error feedback via toasts
Subscription Management
- "Manage" button opens Stripe Customer Portal
- Direct access to billing history
- Invoice download capabilities
Real-time Updates
- Automatic UI refresh after sync
- Query invalidation for fresh data
- Optimistic updates for better UX
Error Handling
Sync Failures
- Graceful degradation on API failures
- User-friendly error messages
- Retry mechanisms for transient errors
Data Inconsistencies
- Automatic correction during sync
- Logging for debugging
- Fallback to safe defaults
Network Issues
- Offline state detection
- Queue sync operations when online
- Background retry with exponential backoff
Testing
E2E Test Coverage
- Complete subscription flow testing
- Sync functionality validation
- Error scenario handling
- UI state verification
Manual Testing
- Stripe webhook simulation
- Subscription status changes
- Expiration scenarios
- Payment failure handling
Monitoring & Observability
Logging
- Detailed sync operation logs
- Error tracking and alerting
- Performance metrics
- User action tracking
Metrics
- Sync success/failure rates
- Response times
- Error frequency
- User engagement with billing features
Security Considerations
Data Protection
- No sensitive data in frontend
- Secure API key management
- Row Level Security (RLS) enforcement
- Input validation and sanitization
Access Control
- User-specific data access
- Admin-only functions protected
- Audit trail for all operations
- Secure webhook verification
Future Enhancements
Planned Features
- Real-time subscription updates via webhooks
- Advanced billing analytics
- Subscription upgrade/downgrade flows
- Proration handling for plan changes
Technical Improvements
- Caching layer for subscription data
- Batch sync operations
- Advanced error recovery
- Performance optimizations
Related Documentation:
- Verification Systems - Complete verification overview
- Seller Verification - Platform verification details
- Architecture Overview - Overall system structure
- Edge Functions - Backend function details
- Performance Guide - Performance optimizations
- Testing Strategy - Testing approaches