- Add comprehensive analytics components with export functionality - Implement territory management with manager performance tracking - Add seatmap components for venue layout management - Create customer management features with modal interface - Add advanced hooks for dashboard flags and territory data - Implement seat selection and venue management utilities - Add type definitions for ticketing and seatmap systems 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1126 lines
34 KiB
Markdown
1126 lines
34 KiB
Markdown
# Stripe Connect Checkout + Ticket Minting Guide
|
|
|
|
This guide covers the complete implementation of Stripe Connect checkout sessions with automatic ticket generation.
|
|
|
|
## Overview
|
|
|
|
The system now supports:
|
|
- ✅ **Checkout Creation**: Using connected Stripe accounts with platform fees
|
|
- ✅ **Payment Processing**: Secure Stripe Checkout with proper fee splitting
|
|
- ✅ **Webhook Handling**: Automatic ticket minting on payment completion
|
|
- ✅ **Inventory Management**: Automatic inventory updates and sold count tracking
|
|
- ✅ **Order Records**: Complete purchase history with customer details
|
|
|
|
## Backend Implementation
|
|
|
|
### New Cloud Functions
|
|
|
|
#### 1. `createCheckout`
|
|
**Endpoint**: `POST /api/checkout/create`
|
|
|
|
Creates a Stripe Checkout session using the organization's connected account with full idempotency and inventory safety.
|
|
|
|
**Request Body**:
|
|
```typescript
|
|
{
|
|
orgId: string;
|
|
eventId: string;
|
|
ticketTypeId: string;
|
|
qty: number;
|
|
purchaserEmail?: string;
|
|
successUrl: string;
|
|
cancelUrl: string;
|
|
}
|
|
```
|
|
|
|
**Response**:
|
|
```typescript
|
|
{
|
|
url: string; // Stripe Checkout URL
|
|
sessionId: string; // Session ID for tracking
|
|
}
|
|
```
|
|
|
|
**Key Features**:
|
|
- Validates organization has connected Stripe account and charges enabled
|
|
- Calculates platform fees using `PLATFORM_FEE_BPS` environment variable (default 3%)
|
|
- Creates checkout with connected account using `stripeAccount` parameter
|
|
- Includes comprehensive metadata for webhook processing
|
|
- Creates placeholder order for UI polling
|
|
- Full inventory validation before checkout creation
|
|
|
|
#### 2. `stripeWebhookConnect`
|
|
**Endpoint**: `POST /api/stripe/webhook/connect`
|
|
|
|
Handles webhooks from connected accounts with full idempotency protection.
|
|
|
|
**Supported Events**:
|
|
- `checkout.session.completed` - Triggers ticket minting
|
|
- `payment_intent.succeeded` - Additional payment tracking
|
|
|
|
**Ticket Minting Process**:
|
|
1. **Idempotency Check**: Creates `processedSessions/{sessionId}` document to prevent duplicates
|
|
2. **Inventory Transaction**: Atomically validates and decrements inventory
|
|
3. **Ticket Generation**: Creates individual tickets with UUID QR codes
|
|
4. **Order Update**: Marks order as 'paid' with payment details
|
|
5. **Email Delivery**: Sends confirmation email with QR code links
|
|
6. **Error Handling**: Graceful failure handling with comprehensive logging
|
|
|
|
All operations use Firestore transactions for atomic consistency.
|
|
|
|
#### 3. `verifyTicket`
|
|
**Endpoint**: `POST /api/tickets/verify` or `GET /api/tickets/verify/:qr`
|
|
|
|
Verifies ticket QR codes and marks them as scanned.
|
|
|
|
**Request Body** (POST):
|
|
```typescript
|
|
{
|
|
qr: string;
|
|
}
|
|
```
|
|
|
|
**Response**:
|
|
```typescript
|
|
{
|
|
valid: boolean;
|
|
ticket?: {
|
|
id: string;
|
|
eventId: string;
|
|
ticketTypeId: string;
|
|
eventName?: string;
|
|
ticketTypeName?: string;
|
|
status: string;
|
|
purchaserEmail?: string;
|
|
};
|
|
reason?: string; // 'already_scanned', 'ticket_voided', 'Ticket not found'
|
|
scannedAt?: string;
|
|
}
|
|
```
|
|
|
|
**Key Features**:
|
|
- Atomic scan status updates to prevent double-scanning
|
|
- Comprehensive ticket information in response
|
|
- Support for both POST with body and GET with path parameter
|
|
- Detailed error reasons for failed verifications
|
|
|
|
#### 4. `getOrder`
|
|
**Endpoint**: `POST /api/orders/get`
|
|
|
|
Retrieves order details by session ID for frontend polling.
|
|
|
|
**Request Body**:
|
|
```typescript
|
|
{
|
|
sessionId: string;
|
|
}
|
|
```
|
|
|
|
**Response**:
|
|
```typescript
|
|
{
|
|
id: string;
|
|
status: 'pending' | 'paid' | 'failed_sold_out';
|
|
qty: number;
|
|
totalCents: number;
|
|
purchaserEmail?: string;
|
|
eventName?: string;
|
|
ticketTypeName?: string;
|
|
// ... additional order details
|
|
}
|
|
```
|
|
|
|
### Environment Configuration
|
|
|
|
Required environment variables for Firebase Functions:
|
|
|
|
```bash
|
|
# Stripe Configuration
|
|
STRIPE_SECRET_KEY=sk_test_...
|
|
STRIPE_WEBHOOK_SECRET_CONNECT=whsec_... # For connected account events
|
|
|
|
# Email Configuration (optional - logs in dev if not set)
|
|
EMAIL_API_KEY=re_... # Resend API key
|
|
|
|
# Application Configuration
|
|
APP_URL=https://staging.blackcanyontickets.com # For QR code links
|
|
PLATFORM_FEE_BPS=300 # 3% platform fee (default if not set)
|
|
```
|
|
|
|
Firebase Functions config (legacy method):
|
|
```bash
|
|
firebase functions:config:set stripe.secret_key="sk_test_..."
|
|
firebase functions:config:set stripe.connect_webhook_secret="whsec_..."
|
|
firebase functions:config:set email.api_key="re_..."
|
|
firebase functions:config:set app.url="https://staging.blackcanyontickets.com"
|
|
firebase functions:config:set platform.fee_bps="300"
|
|
```
|
|
|
|
## Webhook Setup
|
|
|
|
### Platform Webhook (Existing)
|
|
- **URL**: `https://us-central1-PROJECT_ID.cloudfunctions.net/stripeWebhook`
|
|
- **Events**: `account.updated`
|
|
- **Purpose**: Sync account connection status
|
|
|
|
### Connect Webhook (New)
|
|
- **URL**: `https://us-central1-PROJECT_ID.cloudfunctions.net/stripeWebhookConnect`
|
|
- **Events**: `checkout.session.completed`
|
|
- **Purpose**: Handle successful payments and mint tickets with idempotency protection
|
|
|
|
**Note**: Connect webhooks receive a `Stripe-Account` header identifying which connected account triggered the event.
|
|
|
|
## Frontend Implementation
|
|
|
|
### useCheckout Hook
|
|
|
|
```typescript
|
|
import { useCheckout } from '../hooks/useCheckout';
|
|
|
|
const { createCheckout, isLoading, error } = useCheckout();
|
|
|
|
// Create checkout session
|
|
await createCheckout({
|
|
orgId: user.organization.id,
|
|
eventId: 'event_123',
|
|
ticketTypeId: 'ticket_type_456',
|
|
quantity: 2,
|
|
customerEmail: 'customer@example.com',
|
|
successUrl: '/checkout/success',
|
|
cancelUrl: '/checkout/cancel',
|
|
});
|
|
```
|
|
|
|
### TicketPurchase Component
|
|
|
|
Complete checkout component with:
|
|
- Quantity selector with inventory validation
|
|
- Price breakdown including platform fees
|
|
- Customer email input
|
|
- Secure checkout button
|
|
- Error handling and loading states
|
|
|
|
```tsx
|
|
import { TicketPurchase } from '../components/checkout/TicketPurchase';
|
|
|
|
<TicketPurchase
|
|
eventId="event_123"
|
|
ticketTypeId="ticket_type_456"
|
|
ticketTypeName="General Admission"
|
|
priceInCents={5000} // $50.00
|
|
maxQuantity={10}
|
|
inventory={50}
|
|
/>
|
|
```
|
|
|
|
### Success/Cancel Pages
|
|
|
|
- **`/checkout/success`** - Displays purchase confirmation with ticket details
|
|
- **`/checkout/cancel`** - Handles cancelled purchases with retry options
|
|
|
|
## Data Schema
|
|
|
|
### Tickets Collection
|
|
```typescript
|
|
// /tickets/{ticketId}
|
|
{
|
|
orgId: string;
|
|
eventId: string;
|
|
ticketTypeId: string;
|
|
orderId: string; // Links to order (sessionId)
|
|
purchaserEmail: string;
|
|
qr: string; // UUID for scanning
|
|
status: 'issued' | 'scanned' | 'void';
|
|
createdAt: Timestamp;
|
|
scannedAt?: Timestamp;
|
|
updatedAt?: Timestamp;
|
|
}
|
|
```
|
|
|
|
### Orders Collection
|
|
```typescript
|
|
// /orders/{sessionId}
|
|
{
|
|
orgId: string;
|
|
eventId: string;
|
|
ticketTypeId: string;
|
|
qty: number;
|
|
sessionId: string;
|
|
status: 'pending' | 'paid' | 'failed_sold_out';
|
|
totalCents: number;
|
|
createdAt: Timestamp;
|
|
purchaserEmail?: string;
|
|
paymentIntentId?: string;
|
|
stripeAccountId: string;
|
|
updatedAt?: Timestamp;
|
|
failureReason?: string; // For failed orders
|
|
}
|
|
```
|
|
|
|
### Updated Ticket Types
|
|
```typescript
|
|
// /ticket_types/{ticketTypeId}
|
|
{
|
|
orgId: string;
|
|
eventId: string;
|
|
name: string;
|
|
priceCents: number;
|
|
currency: 'USD';
|
|
inventory: number; // Total available
|
|
sold: number; // Number sold (incremented atomically)
|
|
createdAt: Timestamp;
|
|
updatedAt?: Timestamp;
|
|
}
|
|
```
|
|
|
|
### Processed Sessions Collection (Idempotency)
|
|
```typescript
|
|
// /processedSessions/{sessionId}
|
|
{
|
|
sessionId: string;
|
|
processedAt: Timestamp;
|
|
orgId: string;
|
|
eventId: string;
|
|
ticketTypeId: string;
|
|
qty: number;
|
|
paymentIntentId: string;
|
|
stripeAccountId: string;
|
|
}
|
|
```
|
|
|
|
## Testing Flow
|
|
|
|
### End-to-End Test Scenario
|
|
|
|
1. **Setup**: Ensure organization has connected Stripe account
|
|
2. **Create Event**: With published status and active ticket types
|
|
3. **Purchase Flow**:
|
|
```bash
|
|
# User clicks "Purchase Tickets"
|
|
# → createStripeCheckout called
|
|
# → Redirects to Stripe Checkout
|
|
# → User completes payment
|
|
# → Stripe sends webhook to stripeConnectWebhook
|
|
# → Tickets automatically minted
|
|
# → User redirected to success page
|
|
```
|
|
|
|
### Validation Points
|
|
|
|
✅ **Payment Processing**:
|
|
- Platform fee calculated correctly (2.9% + $0.30)
|
|
- Connected account receives net amount
|
|
- Platform receives application fee
|
|
|
|
✅ **Ticket Generation**:
|
|
- Unique ticket IDs generated
|
|
- QR codes created for scanning
|
|
- Customer information captured
|
|
|
|
✅ **Inventory Management**:
|
|
- Available inventory decremented
|
|
- Sold count incremented
|
|
- Overselling prevented
|
|
|
|
✅ **Data Consistency**:
|
|
- All database operations atomic
|
|
- Order and ticket records linked
|
|
- Audit trail maintained
|
|
|
|
## Error Handling
|
|
|
|
### Common Scenarios
|
|
|
|
1. **Payment Fails**: User sees error, can retry
|
|
2. **Webhook Delay**: Tickets may take 1-2 minutes to appear
|
|
3. **Inventory Conflict**: Prevented by atomic operations
|
|
4. **Email Issues**: Tickets stored in database regardless
|
|
|
|
### Monitoring
|
|
|
|
Add logging to track:
|
|
- Checkout session creation success/failure rates
|
|
- Webhook processing times
|
|
- Ticket minting success rates
|
|
- Payment vs. ticket generation correlation
|
|
|
|
## Security Considerations
|
|
|
|
### Payment Security
|
|
- ✅ Card data never touches your servers
|
|
- ✅ PCI compliance handled by Stripe
|
|
- ✅ Webhook signatures verified
|
|
- ✅ Connected account isolation
|
|
|
|
### Data Protection
|
|
- ✅ Customer data encrypted in transit and at rest
|
|
- ✅ Minimal customer data stored (email, name)
|
|
- ✅ Ticket IDs non-enumerable (timestamp + random)
|
|
- ✅ Organization data isolation maintained
|
|
|
|
## Performance Optimizations
|
|
|
|
### Database Efficiency
|
|
- Batch operations for ticket creation
|
|
- Indexed queries for ticket lookup
|
|
- Efficient inventory updates
|
|
- Proper error rollback
|
|
|
|
### Frontend Optimization
|
|
- Immediate redirect to Stripe (no waiting)
|
|
- Optimistic UI updates where appropriate
|
|
- Error state handling
|
|
- Loading state management
|
|
|
|
## Next Steps
|
|
|
|
### Advanced Features
|
|
1. **Email Notifications**: Send ticket confirmations via Resend/SendGrid
|
|
2. **PDF Generation**: Create downloadable ticket PDFs with QR codes
|
|
3. **Calendar Integration**: Add event to customer calendars
|
|
4. ✅ **Refund Handling**: Secure refund processing with organization validation
|
|
5. **Bulk Purchases**: Handle group bookings and corporate sales
|
|
6. **Waitlist Management**: Handle sold-out scenarios with waitlist functionality
|
|
7. **Dynamic Pricing**: Time-based and demand-based pricing strategies
|
|
8. **Multi-Currency Support**: International payment processing
|
|
|
|
### Performance Optimizations
|
|
1. **Webhook Batching**: Process multiple events in single transaction
|
|
2. **Inventory Caching**: Redis caching for high-traffic events
|
|
3. **Database Sharding**: Partition tickets by event for scale
|
|
4. **Queue Processing**: Async ticket generation for very large orders
|
|
5. **CDN Integration**: Cache static checkout pages
|
|
|
|
### Analytics Integration
|
|
1. **Sales Tracking**: Revenue and conversion analytics with refund adjustments
|
|
2. **Customer Insights**: Purchase behavior analysis and fraud detection
|
|
3. **Event Performance**: Attendance and sales metrics with real-time updates
|
|
4. **Platform Metrics**: Fee collection and growth tracking with configurable rates
|
|
5. **Operational Analytics**: Webhook processing times, error rates, and idempotency metrics
|
|
6. **Security Analytics**: Failed authentication attempts and suspicious refund patterns
|
|
|
|
## Refunds, Voids & Disputes System
|
|
|
|
### Overview
|
|
|
|
The platform now supports comprehensive refund management, dispute handling, and financial reconciliation with enterprise-grade safety features:
|
|
|
|
- ✅ **Full/Partial/Per-Ticket Refunds**: Flexible refund options with proper ledger tracking
|
|
- ✅ **Automatic Dispute Handling**: Tickets locked on dispute creation, outcomes processed automatically
|
|
- ✅ **Financial Reconciliation**: Complete ledger system with CSV export capabilities
|
|
- ✅ **Permission-Based Access**: Only admins/super-admins can process refunds
|
|
- ✅ **Idempotent Operations**: Prevents duplicate refunds and maintains data consistency
|
|
|
|
### Refund Management
|
|
|
|
#### Creating Refunds
|
|
**Endpoint**: `POST /api/refunds/create`
|
|
|
|
**Request Body**:
|
|
```typescript
|
|
{
|
|
orderId: string; // Required: Order to refund
|
|
ticketId?: string; // Optional: Specific ticket to refund
|
|
amountCents?: number; // Optional: Custom amount (defaults to full/ticket price)
|
|
reason?: string; // Optional: Reason for refund
|
|
}
|
|
```
|
|
|
|
**Refund Types**:
|
|
1. **Full Order Refund**: Refunds entire order amount
|
|
```typescript
|
|
{ orderId: "order_123" }
|
|
```
|
|
|
|
2. **Single Ticket Refund**: Refunds specific ticket at ticket price
|
|
```typescript
|
|
{ orderId: "order_123", ticketId: "ticket_456" }
|
|
```
|
|
|
|
3. **Partial Amount Refund**: Custom refund amount
|
|
```typescript
|
|
{ orderId: "order_123", amountCents: 2500 } // $25.00
|
|
```
|
|
|
|
4. **Multiple Tickets**: Multiple tickets via custom amount
|
|
```typescript
|
|
{ orderId: "order_123", amountCents: 5000 } // 2 x $25 tickets
|
|
```
|
|
|
|
**Response**:
|
|
```typescript
|
|
{
|
|
refundId: string;
|
|
stripeRefundId: string;
|
|
amountCents: number;
|
|
status: "succeeded" | "failed";
|
|
}
|
|
```
|
|
|
|
**Key Features**:
|
|
- **Permission Validation**: Only org admins/super-admins can create refunds
|
|
- **Idempotency Protection**: Duplicate refund requests return existing refund
|
|
- **Automatic Fee Handling**: Platform fees and Stripe fees refunded proportionally
|
|
- **Ticket Status Updates**: Refunded tickets marked as 'refunded' status
|
|
- **Comprehensive Logging**: Full audit trail for all refund operations
|
|
|
|
#### Refund Validation Rules
|
|
- Order must be in 'paid' status
|
|
- Refund amount cannot exceed order total
|
|
- Tickets must be 'issued' or 'scanned' (not already refunded/void/disputed)
|
|
- Only organization members with admin privileges can create refunds
|
|
- Disputed orders cannot be refunded (must resolve dispute first)
|
|
|
|
### Dispute Handling
|
|
|
|
#### Automatic Dispute Processing
|
|
The system automatically handles Stripe dispute webhooks:
|
|
|
|
**`charge.dispute.created`**:
|
|
1. Finds order by payment intent/charge ID
|
|
2. Locks all related tickets (status: 'locked_dispute')
|
|
3. Creates dispute fee ledger entries if applicable
|
|
4. Updates order with dispute information
|
|
|
|
**`charge.dispute.closed`**:
|
|
1. **If Won**: Restores tickets to previous status ('issued'/'scanned')
|
|
2. **If Lost**: Voids tickets and creates negative ledger entries (loss accounting)
|
|
|
|
**Dispute Status Tracking**:
|
|
```typescript
|
|
// Order dispute field
|
|
{
|
|
dispute?: {
|
|
disputeId: string;
|
|
status: string;
|
|
reason: string;
|
|
outcome?: "won" | "lost";
|
|
createdAt: Timestamp;
|
|
closedAt?: Timestamp;
|
|
}
|
|
}
|
|
```
|
|
|
|
**Ticket Status Flow**:
|
|
```
|
|
Normal Flow: issued → scanned
|
|
Dispute Flow: issued → locked_dispute → (won: issued) | (lost: void)
|
|
```
|
|
|
|
#### Getting Dispute Information
|
|
**Endpoint**: `POST /api/disputes/get`
|
|
|
|
**Request Body**:
|
|
```typescript
|
|
{ orderId: string; }
|
|
```
|
|
|
|
**Response**:
|
|
```typescript
|
|
{
|
|
orderId: string;
|
|
dispute: {
|
|
disputeId: string;
|
|
status: string;
|
|
reason: string;
|
|
outcome?: string;
|
|
} | null;
|
|
}
|
|
```
|
|
|
|
### Financial Ledger System
|
|
|
|
#### Ledger Entry Types
|
|
All financial transactions are recorded in the `ledger` collection:
|
|
|
|
```typescript
|
|
interface LedgerEntry {
|
|
orgId: string;
|
|
eventId: string;
|
|
orderId: string;
|
|
type: "sale" | "refund" | "fee" | "platform_fee" | "dispute_fee";
|
|
amountCents: number; // Positive for revenue, negative for costs
|
|
currency: "USD";
|
|
stripe: {
|
|
balanceTxnId?: string;
|
|
chargeId?: string;
|
|
refundId?: string;
|
|
disputeId?: string;
|
|
accountId: string;
|
|
};
|
|
createdAt: Timestamp;
|
|
meta?: Record<string, any>;
|
|
}
|
|
```
|
|
|
|
#### Ledger Entry Creation
|
|
|
|
**For Sales** (in `checkout.session.completed`):
|
|
```typescript
|
|
// Sale entry (+$150.00)
|
|
{ type: "sale", amountCents: 15000 }
|
|
|
|
// Platform fee (+$4.50 for platform)
|
|
{ type: "platform_fee", amountCents: 450 }
|
|
|
|
// Stripe processing fee (-$4.65 for organizer)
|
|
{ type: "fee", amountCents: -465 }
|
|
```
|
|
|
|
**For Refunds** (in `createRefund` and `refund.created` webhook):
|
|
```typescript
|
|
// Refund entry (-$75.00)
|
|
{ type: "refund", amountCents: -7500 }
|
|
|
|
// Platform fee refund (-$2.25 for platform)
|
|
{ type: "platform_fee", amountCents: -225 }
|
|
|
|
// Stripe refund fee (varies by policy)
|
|
{ type: "fee", amountCents: -0 }
|
|
```
|
|
|
|
**For Disputes** (in `charge.dispute.created`):
|
|
```typescript
|
|
// Dispute fee (-$15.00 typically)
|
|
{ type: "dispute_fee", amountCents: -1500 }
|
|
```
|
|
|
|
### Reconciliation & Reporting
|
|
|
|
#### Reconciliation API
|
|
**Endpoint**: `POST /api/reconciliation/data`
|
|
|
|
**Request Body**:
|
|
```typescript
|
|
{
|
|
orgId: string;
|
|
eventId?: string; // Optional: specific event or "all"
|
|
startDate: string; // ISO date string
|
|
endDate: string; // ISO date string
|
|
format?: "json" | "csv"; // Default: json
|
|
}
|
|
```
|
|
|
|
**Response (JSON)**:
|
|
```typescript
|
|
{
|
|
summary: {
|
|
grossSales: number; // Total sales amount
|
|
refunds: number; // Total refunds (positive)
|
|
stripeFees: number; // Total Stripe fees (positive)
|
|
platformFees: number; // Total platform fees (positive)
|
|
disputeFees: number; // Total dispute fees (positive)
|
|
netToOrganizer: number; // Final amount to organizer
|
|
totalTransactions: number; // Unique order count
|
|
period: { start: string; end: string; }
|
|
};
|
|
entries: LedgerEntry[]; // Detailed transactions
|
|
total: number; // Entry count
|
|
}
|
|
```
|
|
|
|
**Calculation Formula**:
|
|
```
|
|
Net to Organizer = Gross Sales - Refunds - Stripe Fees - Platform Fees - Dispute Fees
|
|
```
|
|
|
|
#### CSV Export
|
|
|
|
**Request with `format: "csv"`** returns downloadable CSV with:
|
|
- **Summary Section**: Period, totals, and key metrics
|
|
- **Transaction Details**: Complete ledger entries
|
|
- **Headers**: Date, Type, Amount, Order ID, Stripe Transaction ID, Account ID, Notes
|
|
|
|
**CSV Structure**:
|
|
```csv
|
|
SUMMARY
|
|
2024-01-01,Period Start,,,,,
|
|
2024-12-31,Period End,,,,,
|
|
,Gross Sales,150.00,,,,
|
|
,Net to Organizer,140.85,,,,
|
|
|
|
TRANSACTIONS
|
|
Date,Type,Amount,Order ID,Stripe Transaction ID,Account ID,Notes
|
|
2024-08-01T10:30:00Z,sale,150.00,order-123,txn_abc123,acct_def456,
|
|
2024-08-01T10:30:00Z,platform_fee,4.50,order-123,txn_abc123,acct_def456,
|
|
```
|
|
|
|
### Frontend Components
|
|
|
|
#### RefundModal Component
|
|
**Location**: `src/features/orders/RefundModal.tsx`
|
|
|
|
**Props**:
|
|
```typescript
|
|
interface RefundModalProps {
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
order: {
|
|
id: string;
|
|
totalCents: number;
|
|
tickets: Array<{
|
|
id: string;
|
|
status: string;
|
|
priceCents: number;
|
|
ticketTypeName: string;
|
|
}>;
|
|
};
|
|
onRefundCreated?: (refundId: string) => void;
|
|
}
|
|
```
|
|
|
|
**Features**:
|
|
- **Refund Type Selection**: Full, partial, or specific tickets
|
|
- **Dynamic Amount Calculation**: Updates based on selection
|
|
- **Validation**: Prevents invalid amounts and states
|
|
- **Real-time Feedback**: Success/error states with detailed messages
|
|
- **Permission Awareness**: Disabled for users without refund permissions
|
|
|
|
#### OrdersTable Component
|
|
**Location**: `src/features/orders/OrdersTable.tsx`
|
|
|
|
**Features**:
|
|
- **Order Display**: Status, amounts, customer info, ticket details
|
|
- **Dispute Alerts**: Visual indicators for disputed orders
|
|
- **Refund History**: Shows existing refunds with amounts and dates
|
|
- **Action Buttons**: Context-aware refund and view options
|
|
- **Real-time Updates**: Refreshes data after refund operations
|
|
|
|
#### Reconciliation Component
|
|
**Location**: `src/features/reports/Reconciliation.tsx`
|
|
|
|
**Features**:
|
|
- **Date Range Filtering**: Custom period selection
|
|
- **Event Filtering**: All events or specific event
|
|
- **Summary Cards**: Key financial metrics with visual indicators
|
|
- **Detailed Breakdown**: Sortable transaction table
|
|
- **CSV Export**: Client-side download generation
|
|
- **Real-time Calculations**: Updates as filters change
|
|
|
|
### Security & Permissions
|
|
|
|
#### Refund Permissions
|
|
Only users with these roles can create refunds:
|
|
- **Super Admin**: Global platform admin
|
|
- **Organization Admin**: Admin of the specific organization
|
|
- **Territory Manager**: Manager of organization's territory _(future feature)_
|
|
|
|
#### Permission Validation
|
|
```typescript
|
|
// Server-side validation in all refund endpoints
|
|
async function checkRefundPermissions(uid: string, orgId: string): Promise<boolean> {
|
|
const userDoc = await db.collection("users").doc(uid).get();
|
|
const userData = userDoc.data();
|
|
|
|
// Super admin access
|
|
if (userData?.role === "super_admin") return true;
|
|
|
|
// Org admin access
|
|
if (userData?.organization?.id === orgId && userData?.role === "admin") return true;
|
|
|
|
return false;
|
|
}
|
|
```
|
|
|
|
#### API Security
|
|
- **Authentication**: All refund APIs require valid JWT token
|
|
- **Authorization**: Organization-based access control
|
|
- **Input Validation**: Comprehensive validation of all parameters
|
|
- **Rate Limiting**: Protection against abuse (configured at infrastructure level)
|
|
|
|
### Error Handling & Monitoring
|
|
|
|
#### Comprehensive Error Handling
|
|
```typescript
|
|
// Refund creation errors
|
|
{
|
|
"orderId is required" |
|
|
"Order not found" |
|
|
"Can only refund paid orders" |
|
|
"Invalid refund amount: {amount}. Must be between 1 and {max}" |
|
|
"Cannot refund ticket with status: {status}" |
|
|
"Insufficient permissions" |
|
|
"Refund failed" // With Stripe error details
|
|
}
|
|
```
|
|
|
|
#### Logging & Monitoring
|
|
**Structured Logging**:
|
|
```typescript
|
|
console.log(`[create_refund] Starting refund creation`, {
|
|
orderId, ticketId, amountCents, uid, orgId,
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
|
|
console.log(`[create_refund] Refund completed successfully`, {
|
|
refundId, stripeRefundId, amountCents,
|
|
processingTime: Date.now() - startTime
|
|
});
|
|
```
|
|
|
|
**Key Metrics to Monitor**:
|
|
- Refund success/failure rates
|
|
- Average refund processing time
|
|
- Dispute resolution outcomes
|
|
- Ledger entry consistency
|
|
- Permission denial attempts
|
|
|
|
### Testing & Validation
|
|
|
|
#### Comprehensive Test Coverage
|
|
**File**: `tests/refunds-disputes.spec.ts`
|
|
|
|
**Test Scenarios**:
|
|
- ✅ Full order refunds with success validation
|
|
- ✅ Partial amount refunds with input validation
|
|
- ✅ Single and multiple ticket refunds
|
|
- ✅ Refund amount validation and error handling
|
|
- ✅ Permission-based access control
|
|
- ✅ Idempotency for duplicate refund requests
|
|
- ✅ Dispute status display and ticket locking
|
|
- ✅ Reconciliation calculations and CSV export
|
|
- ✅ Ledger entry creation and integrity
|
|
|
|
#### Manual Testing Checklist
|
|
- [ ] Admin can create all types of refunds
|
|
- [ ] Non-admin users get permission denied
|
|
- [ ] Refunded tickets show correct status
|
|
- [ ] Ledger entries balance with Stripe dashboard
|
|
- [ ] Disputed orders block refund attempts
|
|
- [ ] CSV export contains accurate data
|
|
- [ ] Duplicate refund requests handled gracefully
|
|
|
|
### Production Deployment
|
|
|
|
#### Environment Variables
|
|
```bash
|
|
# Required for refunds and disputes
|
|
STRIPE_SECRET_KEY=sk_live_...
|
|
STRIPE_WEBHOOK_SECRET_CONNECT=whsec_...
|
|
EMAIL_API_KEY=re_...
|
|
APP_URL=https://portal.blackcanyontickets.com
|
|
PLATFORM_FEE_BPS=300 # 3% platform fee
|
|
```
|
|
|
|
#### Webhook Configuration
|
|
**Connect Webhook Events** (required for dispute handling):
|
|
```
|
|
charge.dispute.created
|
|
charge.dispute.closed
|
|
refund.created
|
|
checkout.session.completed
|
|
```
|
|
|
|
#### Database Indexes
|
|
**Recommended Firestore indexes**:
|
|
```typescript
|
|
// ledger collection
|
|
{ orgId: "asc", createdAt: "desc" }
|
|
{ orgId: "asc", eventId: "asc", createdAt: "desc" }
|
|
{ orderId: "asc", type: "asc" }
|
|
|
|
// refunds collection
|
|
{ orgId: "asc", createdAt: "desc" }
|
|
{ orderId: "asc", createdAt: "desc" }
|
|
|
|
// orders collection
|
|
{ paymentIntentId: "asc" }
|
|
```
|
|
|
|
### Troubleshooting
|
|
|
|
#### Common Issues
|
|
|
|
**Refund Fails with Stripe Error**:
|
|
- Check if payment intent supports refunds
|
|
- Verify connected account has sufficient balance
|
|
- Ensure webhook secrets are correctly configured
|
|
|
|
**Ledger Entries Don't Match Stripe**:
|
|
- Verify all webhook events are being processed
|
|
- Check for missing balance transaction data
|
|
- Validate fee calculations against Stripe dashboard
|
|
|
|
**Disputes Not Processing**:
|
|
- Confirm webhook endpoint is receiving events
|
|
- Verify `charge.dispute.*` events are configured
|
|
- Check account identification in webhook headers
|
|
|
|
**CSV Export Issues**:
|
|
- Verify `csv-writer` dependency is installed
|
|
- Check file system permissions for temporary files
|
|
- Validate date range parameters
|
|
|
|
#### Support Escalation
|
|
For production issues:
|
|
1. Check Firebase Functions logs for detailed error traces
|
|
2. Verify Stripe webhook delivery logs
|
|
3. Validate ledger entry consistency with database queries
|
|
4. Contact platform support with specific error messages and timestamps
|
|
|
|
## Summary of Complete Implementation
|
|
|
|
### ✅ Security Enhancements
|
|
- **Idempotency Protection**: Prevents duplicate operations across all functions
|
|
- **Transactional Safety**: Atomic operations for inventory, refunds, and ledger entries
|
|
- **Permission-Based Access**: Role-based refund authorization with org isolation
|
|
- **Input Validation**: Comprehensive validation of all API inputs and business rules
|
|
- **Account Isolation**: Proper Stripe Connect account handling and data separation
|
|
|
|
### ✅ Financial Integrity
|
|
- **Complete Ledger System**: Every transaction recorded with full audit trail
|
|
- **Automated Fee Tracking**: Stripe processing fees and platform fees captured automatically
|
|
- **Dispute Accounting**: Proper handling of dispute outcomes with financial adjustments
|
|
- **Reconciliation Tools**: Real-time financial reporting with CSV export capabilities
|
|
- **Multi-Currency Support**: Foundation for international payment processing _(USD only currently)_
|
|
|
|
### ✅ Operational Excellence
|
|
- **Comprehensive Error Handling**: Graceful handling of all failure scenarios
|
|
- **Structured Logging**: Consistent audit trail optimized for monitoring and alerting
|
|
- **Performance Monitoring**: Processing times and success rates tracked across all operations
|
|
- **Automated Testing**: Complete test coverage for critical financial operations
|
|
- **Production Readiness**: Enterprise-grade configuration and deployment guidelines
|
|
|
|
### ✅ User Experience
|
|
- **Intuitive Refund Interface**: Easy-to-use modal with multiple refund options
|
|
- **Real-time Status Updates**: Live feedback on refund processing and dispute status
|
|
- **Comprehensive Reporting**: Detailed financial reconciliation with export capabilities
|
|
- **Permission-Aware UI**: Interface adapts based on user roles and permissions
|
|
- **Mobile-Responsive Design**: Full functionality across all device types
|
|
|
|
The refunds, disputes, and reconciliation system is now production-ready with enterprise-grade financial controls, comprehensive audit trails, and robust error handling! 💰🔒📊
|
|
|
|
## Connecting Stripe for Organizers
|
|
|
|
### Accessing Payment Settings
|
|
|
|
Organizers need to connect their Stripe account before they can publish events and accept payments.
|
|
|
|
**Navigation Path**:
|
|
- From any event detail page → "Connect Stripe Account" button in payment banner
|
|
- From publish modal → "Connect Payments" action button (with `data-testid="paymentCheck"`)
|
|
- Direct URL: `/org/{orgId}/payments`
|
|
|
|
### React Integration Components
|
|
|
|
The frontend integration includes several key components with proper TypeScript support and testing:
|
|
|
|
#### PaymentSettings Component
|
|
**Location**: `src/features/org/PaymentSettings.tsx`
|
|
|
|
**Features**:
|
|
- **Design Token Based**: Uses semantic color and spacing tokens (no hardcoded values)
|
|
- **Data Test IDs**: All interactive elements have `data-testid` attributes for testing
|
|
- **Zustand Integration**: Connects to `useCurrentOrgStore` for real-time payment status
|
|
- **Mock API Integration**: Uses `api.stripeConnect` service for consistent data fetching
|
|
|
|
**Key Elements**:
|
|
- `data-testid="connectBtn"` - Connect/Continue Setup button
|
|
- `data-testid="refreshBtn"` - Refresh Status button
|
|
- `data-testid="disconnectBtn"` - Disconnect button
|
|
- UX Callout: Shows warning when payment not connected for publishing
|
|
|
|
#### PublishEventModal Component
|
|
**Location**: `src/features/events/PublishEventModal.tsx`
|
|
|
|
**Enhanced Features**:
|
|
- **Payment Checklist**: Payment requirement with `data-testid="paymentCheck"`
|
|
- **Publish Button**: `data-testid="publishBtn"` for E2E testing
|
|
- **Smart Actions**: Direct link to payment settings when payment not connected
|
|
- **Requirement Validation**: Blocks publishing until all requirements met including payment
|
|
|
|
#### Stripe Connect Hooks
|
|
**Location**: `src/hooks/useStripeConnect.ts`
|
|
|
|
**Provides**:
|
|
- `useStripeConnect(orgId)` - Main hook for all Stripe Connect operations
|
|
- `useStripeConnectStatus(orgId)` - Auto-fetching payment status
|
|
- `useStripeConnectStart(orgId)` - Onboarding flow initiation
|
|
- `useStripeConnectRefresh(orgId)` - Manual status refresh
|
|
|
|
**State Management**:
|
|
- Integrates with Zustand org store
|
|
- Mock API integration for demo/testing
|
|
- Proper loading and error states
|
|
- TypeScript interfaces for all data structures
|
|
|
|
### API Integration
|
|
|
|
**Mock API Endpoints** (for frontend demo):
|
|
- `POST ${VITE_API_BASE}/stripe/connect/start` - Start onboarding
|
|
- `GET ${VITE_API_BASE}/stripe/connect/status` - Get connection status
|
|
|
|
**Service Layer**:
|
|
```typescript
|
|
import { api } from '../services/api';
|
|
|
|
// Start onboarding
|
|
const result = await api.stripeConnect.startOnboarding(orgId);
|
|
|
|
// Check status
|
|
const status = await api.stripeConnect.getConnectStatus(orgId);
|
|
```
|
|
|
|
### Payment Settings Interface
|
|
|
|
The Payment Settings page shows:
|
|
|
|
#### Connection Status
|
|
- **Provider**: Always shows "Stripe" (only supported provider)
|
|
- **Connected**: ✅ (Green check) or ❌ (Warning badge)
|
|
- **Business Name**: Displayed when account is fully set up
|
|
- **Account Details**: Shows detailsSubmitted and chargesEnabled status
|
|
|
|
#### Action Buttons
|
|
- **Connect Stripe**: Starts initial onboarding flow
|
|
- **Continue Setup**: Resumes incomplete onboarding
|
|
- **Refresh Status**: Manually checks current Stripe account status
|
|
- **Disconnect**: Removes Stripe connection (stub implementation)
|
|
|
|
### What "Connected" Means
|
|
|
|
An account is considered **fully connected** when:
|
|
1. `detailsSubmitted: true` - Business details provided to Stripe
|
|
2. `chargesEnabled: true` - Stripe has approved the account for processing
|
|
3. Both conditions must be true for `connected: true`
|
|
|
|
### Onboarding Process
|
|
|
|
1. **Click Connect/Continue**: Redirects to Stripe Connect onboarding
|
|
2. **Complete Forms**: Provide business details, banking info, identity verification
|
|
3. **Submit for Review**: Stripe reviews account (1-2 business days typical)
|
|
4. **Account Approved**: `chargesEnabled` becomes true, account fully connected
|
|
5. **Return to Platform**: Automatic redirect back to Payment Settings
|
|
|
|
### Status Messages
|
|
|
|
- **Setup Required**: Account created but details not submitted
|
|
- **Under Review**: Details submitted, waiting for Stripe approval
|
|
- **Connected**: Fully operational, can accept payments
|
|
|
|
### Publish Flow Integration
|
|
|
|
Events cannot be published until:
|
|
- ✅ At least 1 active ticket type exists
|
|
- ✅ Valid event dates (start < end)
|
|
- ✅ Stripe account connected (`connected: true`)
|
|
|
|
The publish modal enforces these requirements and provides direct links to resolve each issue.
|
|
|
|
### Troubleshooting
|
|
|
|
**Common Issues**:
|
|
- **Interrupted Onboarding**: Use "Continue Setup" button to resume
|
|
- **Long Review Times**: Stripe may request additional documentation
|
|
- **Account Restricted**: Check Stripe dashboard for specific requirements
|
|
- **Status Not Updating**: Use "Refresh Status" button to sync latest data
|
|
|
|
**Support**:
|
|
- Platform issues → Contact BCT support
|
|
- Stripe account issues → Contact Stripe support directly
|
|
|
|
### Testing & Quality Assurance
|
|
|
|
#### Comprehensive Test Suite
|
|
|
|
**PaymentSettings Component Tests** (`src/features/org/__tests__/PaymentSettings.test.tsx`):
|
|
- ✅ Renders payment settings page with correct elements
|
|
- ✅ Shows disconnected state when no payment info
|
|
- ✅ Displays connected status with account details
|
|
- ✅ Handles partially connected state with setup required
|
|
- ✅ Calls correct API endpoints with proper parameters
|
|
- ✅ Updates Zustand store on successful operations
|
|
- ✅ Displays loading states during API calls
|
|
- ✅ Shows error messages when API calls fail
|
|
- ✅ Handles URL parameters for post-onboarding flows
|
|
- ✅ Shows development info in DEV mode
|
|
|
|
**PublishEventModal Component Tests** (`src/features/events/__tests__/PublishEventModal.test.tsx`):
|
|
- ✅ Renders modal with requirements checklist
|
|
- ✅ Shows loading state while checking requirements
|
|
- ✅ Displays all requirements with proper pass/fail states
|
|
- ✅ Blocks publishing when payment not connected
|
|
- ✅ Shows payment checklist with correct data-testid
|
|
- ✅ Enables publish button when all requirements met
|
|
- ✅ Handles successful publishing flow with auto-close
|
|
- ✅ Provides action buttons for fixing failed requirements
|
|
- ✅ Maintains proper accessibility attributes
|
|
- ✅ Handles error states gracefully
|
|
|
|
#### Testing Patterns
|
|
|
|
**Data Test IDs**:
|
|
- `data-testid="connectBtn"` - Connect/Continue Setup button
|
|
- `data-testid="refreshBtn"` - Refresh Status button
|
|
- `data-testid="disconnectBtn"` - Disconnect button
|
|
- `data-testid="paymentCheck"` - Payment requirements checklist item
|
|
- `data-testid="publishBtn"` - Publish Event button
|
|
|
|
**Mock API Integration**:
|
|
```typescript
|
|
// Tests use consistent mocking patterns
|
|
global.fetch = vi.fn();
|
|
mockFetch.mockResolvedValue({
|
|
ok: true,
|
|
json: async () => ({ payment: mockPaymentData }),
|
|
});
|
|
```
|
|
|
|
**Zustand Store Testing**:
|
|
```typescript
|
|
// Store integration is mocked and validated
|
|
(useCurrentOrgStore as any).mockReturnValue({
|
|
org: mockOrg,
|
|
updatePaymentStatus: mockUpdatePaymentStatus,
|
|
});
|
|
|
|
expect(mockUpdatePaymentStatus).toHaveBeenCalledWith(expectedPaymentData);
|
|
```
|
|
|
|
#### End-to-End Testing
|
|
|
|
**Critical User Flows**:
|
|
1. **Payment Connection Flow**:
|
|
- Navigate to payment settings
|
|
- Click connect button
|
|
- Redirect to Stripe (mocked)
|
|
- Return with success status
|
|
- Verify payment status updated
|
|
|
|
2. **Publishing with Payment Check**:
|
|
- Open publish modal
|
|
- Verify payment requirement shows connected status
|
|
- Publish button enabled when all requirements met
|
|
- Successful publishing updates event status
|
|
|
|
3. **Error Handling Paths**:
|
|
- API failures show appropriate error messages
|
|
- Loading states provide user feedback
|
|
- Permission errors prevent unauthorized actions
|
|
|
|
#### Performance & Accessibility
|
|
|
|
**Key Metrics**:
|
|
- ✅ **WCAG AA Compliance**: Proper ARIA labels and keyboard navigation
|
|
- ✅ **Mobile Responsive**: Works on all device sizes
|
|
- ✅ **Loading States**: Immediate user feedback during API calls
|
|
- ✅ **Error Recovery**: Clear error messages with retry options
|
|
- ✅ **Design Token Usage**: No hardcoded colors or spacing values
|
|
|
|
**Browser Support**:
|
|
- Modern browsers with ES2020+ support
|
|
- Mobile Safari and Chrome
|
|
- Desktop Firefox, Chrome, Edge, Safari
|
|
|
|
#### Production Readiness Checklist
|
|
|
|
- [x] All components use design tokens (no hardcoded styles)
|
|
- [x] Comprehensive data-testid coverage for E2E testing
|
|
- [x] TypeScript interfaces for all props and state
|
|
- [x] Error boundaries and graceful failure handling
|
|
- [x] Loading states for all async operations
|
|
- [x] Accessibility compliance with proper ARIA attributes
|
|
- [x] Mock API integration for frontend-only testing
|
|
- [x] Comprehensive test coverage with React Testing Library
|
|
- [x] Integration with existing Zustand state management
|
|
- [x] Mobile-first responsive design implementation |