PageSourceSearch

https://shiftnex.ai/assets/10-credit-system-D9OQCdzN.js

js shiftnex.ai collected 2026-10-05 12:37:48 UTC 57,364 bytes, 1,354 lines download raw bytes

1const n=`---
2title: "Credit System PRD"
3author: "ShiftNex AI Team"
4status: "Approved"
5version: "2.0"
6last_updated: "2024-12-28"
7---
8
9# Credit System PRD
10
11## Table of Contents
121. [Overview](#overview)
132. [Database Schema](#database-schema)
143. [Edge Functions](#edge-functions)
154. [Frontend Architecture](#frontend-architecture)
165. [Stripe Integration](#stripe-integration)
176. [Business Logic](#business-logic)
187. [User Flows](#user-flows)
198. [Security & Permissions](#security--permissions)
209. [Testing Strategy](#testing-strategy)
2110. [Appendices](#appendices)
22
23---
24
25## 1. Overview
26
27### Purpose
28Credit facility management system for healthcare staffing operations, providing organizations with credit lines for payroll processing, invoice management, and payment method handling through Stripe integration.
29
30### Key Capabilities
31- **Credit Application Workflow**: 7-step application process with KYC/AML compliance
32- **Credit Limit Management**: Configurable limits with utilization tracking
33- **Transaction Tracking**: Real-time balance and transaction history
34- **Stripe Integration**: Payment methods, invoices, and payment processing
35- **Invoice Management**: Consolidated invoice creation and payment
36- **Bank Account Verification**: ACH/Plaid verification flows
37- **Multi-payment Method Support**: Credit cards and bank accounts
38
39### System Architecture
40
41\`\`\`
42┌─────────────────────────────────────────────────────────────────────┐
43│                    CREDIT SYSTEM ARCHITECTURE                        │
44├─────────────────────────────────────────────────────────────────────┤
45│                                                                      │
46│   ┌────────────────────────────────────────────────────────────┐   │
47│   │                    Frontend Layer                            │   │
48│   │  ┌─────────────────┐  ┌─────────────────┐                   │   │
49│   │  │ CreditApplication │  │ CreditAccountPage│                   │   │
50│   │  │ (7-step wizard)  │  │ (Dashboard)     │                   │   │
51│   │  └─────────────────┘  └─────────────────┘                   │   │
52│   │  ┌─────────────────┐  ┌─────────────────┐                   │   │
53│   │  │ PayInvoicesDialog│  │FinancePaymentMethods│               │   │
54│   │  └─────────────────┘  └─────────────────┘                   │   │
55│   └────────────────────────────────────────────────────────────┘   │
56│                              │                                       │
57│                              ▼                                       │
58│   ┌────────────────────────────────────────────────────────────┐   │
59│   │                    Context Layer                             │   │
60│   │  ┌─────────────────────────────────────────────────────┐   │   │
61│   │  │            StripeBillingContext                      │   │   │
62│   │  │  - Payment methods (cards, bank accounts)           │   │   │
63│   │  │  - Invoices with line items                         │   │   │
64│   │  │  - Current balance                                   │   │   │
65│   │  └─────────────────────────────────────────────────────┘   │   │
66│   └────────────────────────────────────────────────────────────┘   │
67│                              │                                       │
68│                              ▼                                       │
69│   ┌────────────────────────────────────────────────────────────┐   │
70│   │                    Edge Functions                            │   │
71│   │  ┌────────────────┐  ┌────────────────┐  ┌──────────────┐  │   │
72│   │  │create-stripe-  │  │ get-stripe-    │  │create-setup- │  │   │
73│   │  │   customer     │  │   billing      │  │   intent     │  │   │
74│   │  └────────────────┘  └────────────────┘  └──────────────┘  │   │
75│   │  ┌────────────────┐  ┌────────────────┐  ┌──────────────┐  │   │
76│   │  │process-payroll-│  │create-consol-  │  │get-invoice-  │  │   │
77│   │  │   payments     │  │idated-invoices │  │   amounts    │  │   │
78│   │  └────────────────┘  └────────────────┘  └──────────────┘  │   │
79│   └────────────────────────────────────────────────────────────┘   │
80│                              │                                       │
81│                              ▼                                       │
82│   ┌────────────────────────────────────────────────────────────┐   │
83│   │                    External Services                         │   │
84│   │  ┌─────────────────┐  ┌─────────────────┐                   │   │
85│   │  │   Stripe API    │  │   Plaid API     │                   │   │
86│   │  │  - Customers    │  │  - Bank linking │                   │   │
87│   │  │  - Invoices     │  │  - Verification │                   │   │
88│   │  │  - Payments     │  │                 │                   │   │
89│   │  └─────────────────┘  └─────────────────┘                   │   │
90│   └────────────────────────────────────────────────────────────┘   │
91│                                                                      │
92└─────────────────────────────────────────────────────────────────────┘
93\`\`\`
94
95---
96
97## 2. Database Schema
98
99### 2.1 org_billing Table
100
101Primary table mapping organizations to Stripe customers.
102
103\`\`\`sql
104CREATE TABLE public.org_billing (
105  id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
106  organization_id UUID REFERENCES organizations(id) UNIQUE NOT NULL,
107  stripe_customer_id TEXT NOT NULL,
108  created_at TIMESTAMPTZ DEFAULT now(),
109  updated_at TIMESTAMPTZ DEFAULT now()
110);
111
112-- Indexes
113CREATE UNIQUE INDEX idx_org_billing_organization_id ON org_billing(organization_id);
114CREATE INDEX idx_org_billing_stripe_customer_id ON org_billing(stripe_customer_id);
115
116-- RLS Policies
117ALTER TABLE org_billing ENABLE ROW LEVEL SECURITY;
118
119CREATE POLICY "Org admins can view their billing"
120ON org_billing FOR SELECT
121USING (
122  organization_id IN (
123    SELECT organization_id FROM org_associations
124    WHERE user_id = auth.uid() AND role = 'admin' AND status = 'active'
125  )
126);
127\`\`\`
128
129### 2.2 orders Table (Invoice Reference)
130
131Orders link to Stripe invoices after invoicing.
132
133\`\`\`sql
134-- Invoice linkage columns in orders table
135ALTER TABLE public.orders ADD COLUMN IF NOT EXISTS invoice_id TEXT;
136ALTER TABLE public.orders ADD COLUMN IF NOT EXISTS invoiced_at TIMESTAMPTZ;
137
138CREATE INDEX idx_orders_invoice_id ON orders(invoice_id) WHERE invoice_id IS NOT NULL;
139\`\`\`
140
141### 2.3 Credit Application Data Model (Frontend)
142
143\`\`\`typescript
144// src/pages/CreditApplication.tsx
145export interface CreditApplicationData {
146  // Step 1: Location & Authorization
147  applicationType?: 'organization' | 'location';
148  selectedLocationId?: string;
149  facilityManagerId?: string;
150  authorizedByName?: string;
151  authorizedByTitle?: string;
152  
153  // Step 2: Business Information & Compliance
154  legalBusinessName?: string;
155  dbaName?: string;
156  ein?: string;
157  businessAddress?: Address;
158  yearsInBusiness?: number;
159  naicsCode?: string;
160  industryType?: string;
161  businessType?: string;
162  annualRevenue?: number;
163  isShiftnexPrimary?: boolean;
164  shiftnexUsagePercentage?: number;
165  
166  // Step 3: Financial Information
167  weeklyPayrollSpend?: number;
168  monthlyPayrollSpend?: number;
169  existingCreditFacilities?: string[];
170  bankingRelationships?: string[];
171  financialDocuments?: string[];
172  cashFlowSeasonality?: boolean;
173  
174  // Step 4: Beneficial Ownership (KYC)
175  beneficialOwners?: BeneficialOwner[];
176  controllingPerson?: BeneficialOwner;
177  hasControllingPerson?: boolean;
178  
179  // Step 5: Signer Information
180  signerName?: string;
181  signerTitle?: string;
182  signerDob?: string;
183  signerSsn?: string;
184  signerAuthority?: string;
185  personalGuaranteeRequired?: boolean;
186  personalGuarantee?: boolean;
187  
188  // Step 6: Payment Method
189  paymentMethod?: PaymentMethodData;
190  interestRateStructure?: InterestRateTerms;
191  requestedCreditLimit?: number;
192  proposedCreditLimit?: number;
193  
194  // Step 7: Review & Compliance
195  regulatoryDisclosures?: DisclosureAcceptance[];
196  partnerBankName?: string;
197  electronicSignature?: string;
198  termsAccepted?: boolean;
199  
200  // Metadata
201  submittedAt?: string;
202  applicationId?: string;
203}
204
205interface BeneficialOwner {
206  id: string;
207  name: string;
208  address: Address;
209  dateOfBirth: string;
210  ssnLast4: string;
211  ownershipPercentage: number;
212  identityDocuments?: string[];
213}
214
215interface Address {
216  street: string;
217  city: string;
218  state: string;
219  zipCode: string;
220}
221\`\`\`
222
223### 2.4 Credit Account Data Model
224
225\`\`\`typescript
226// src/hooks/useCreditAccount.ts
227interface CreditAccount {
228  accountId: string;
229  creditLimit: number;
230  availableBalance: number;
231  currentBalance: number;
232  statementBalance: number;
233  utilization: number;           // Percentage
234  lastPaymentDate: string;
235  lastPaymentAmount: number;
236  nextPaymentDate: string;
237  nextPaymentAmount: number;
238  autoPayEnabled: boolean;
239  accountStatus: 'active' | 'suspended' | 'closed';
240  monthlySpend: number;
241  monthlyPayments: number;
242  averageBalance: number;
243  daysUntilDue: number;
244}
245
246interface CreditTransaction {
247  id: string;
248  date: string;
249  type: 'draw' | 'payment' | 'fee' | 'interest';
250  amount: number;
251  description: string;
252  balance: number;
253  reference?: string;
254}
255\`\`\`
256
257### 2.5 Application Status Model
258
259\`\`\`typescript
260// src/hooks/useCreditApplicationStatus.ts
261type CreditApplicationStatus = 
262  | 'no_application' 
263  | 'pending_approval' 
264  | 'approved' 
265  | 'rejected';
266
267interface CreditApplicationStatusData {
268  status: CreditApplicationStatus;
269  applicationId?: string;
270  submittedAt?: string;
271  reviewedAt?: string;
272  approvedAmount?: number;
273  rejectionReason?: string;
274  organizationId: string;
275}
276\`\`\`
277
278---
279
280## 3. Edge Functions
281
282### 3.1 create-stripe-customer
283
284Creates a Stripe customer during organization onboarding.
285
286\`\`\`
287POST /functions/v1/create-stripe-customer
288Authorization: Bearer <STATIC_AUTH_KEY>
289\`\`\`
290
291#### Request Interface
292\`\`\`typescript
293interface CreateStripeCustomerRequest {
294  organization_id: string;
295  organization_name: string;
296  contact_email: string;
297  address?: string;
298  address2?: string;
299  city?: string;
300  state?: string;
301  zip?: string;
302}
303\`\`\`
304
305#### Response Interface
306\`\`\`typescript
307interface CreateStripeCustomerResponse {
308  success: boolean;
309  stripe_customer_id: string;
310  message: string;
311}
312\`\`\`
313
314#### Implementation Flow
315\`\`\`
316┌─────────────────────────────────────────────────────────────────┐
317│                    create-stripe-customer                       │
318├─────────────────────────────────────────────────────────────────┤
319│                                                                  │
320│  ┌─────────────┐    ┌───────────────────┐                       │
321│  │ Verify Auth │───>│ Validate Required │                       │
322│  │    Key      │    │   Fields          │                       │
323│  └─────────────┘    └───────────────────┘                       │
324│                              │                                   │
325│                              ▼                                   │
326│  ┌───────────────────────────────────────────────────────────┐  │
327│  │              Stripe API: Create Customer                   │  │
328│  │  - name: organization_name                                 │  │
329│  │  - email: contact_email                                    │  │
330│  │  - metadata: { organization_id, source }                   │  │
331│  │  - address: { line1, city, state, postal_code, country }  │  │
332│  └───────────────────────────────────────────────────────────┘  │
333│                              │                                   │
334│                              ▼                                   │
335│  ┌───────────────────────────────────────────────────────────┐  │
336│  │           Wait 25 seconds (timing requirement)             │  │
337│  └───────────────────────────────────────────────────────────┘  │
338│                              │                                   │
339│                              ▼                                   │
340│  ┌───────────────────────────────────────────────────────────┐  │
341│  │     Upsert org_billing record with stripe_customer_id     │  │
342│  └───────────────────────────────────────────────────────────┘  │
343│                                                                  │
344└─────────────────────────────────────────────────────────────────┘
345\`\`\`
346
347---
348
349### 3.2 get-stripe-billing
350
351Fetches comprehensive billing data from Stripe.
352
353\`\`\`
354POST /functions/v1/get-stripe-billing
355Authorization: Bearer <JWT_TOKEN>
356\`\`\`
357
358#### Request Interface
359\`\`\`typescript
360interface GetStripeBillingRequest {
361  organization_id: string;
362}
363\`\`\`
364
365#### Response Interface
366\`\`\`typescript
367interface GetStripeBillingResponse {
368  paymentMethods: PaymentMethod[];
369  invoices: Invoice[];
370  currentBalance: number;
371  customerId?: string;
372  message?: string;
373}
374
375interface PaymentMethod {
376  id: string;
377  type: 'card' | 'us_bank_account';
378  last4: string;
379  brand: string;
380  expMonth?: number;
381  expYear?: number;
382  bankName?: string;
383  accountType?: string;
384  isDefault: boolean;
385  status?: string;
386}
387
388interface Invoice {
389  id: string;
390  number: string;
391  date: string;
392  amount: number;
393  amount_due: number;
394  amount_remaining: number;
395  status: 'paid' | 'pending' | 'overdue' | 'unpaid';
396  description: string;
397  downloadUrl?: string;
398  lineItems: LineItem[];
399  orderDetails?: OrderDetails;
400  orderId?: string;
401  provider_amount?: number;
402  network_fee_amount?: number;
403  network_fee_percentage?: number;
404  regular_hours?: number;
405  regular_amount?: number;
406  overtime_hours?: number;
407  overtime_amount?: number;
408}
409
410interface LineItem {
411  id: string;
412  description: string;
413  amount: number;
414  quantity: number;
415  order_id?: string;
416  type?: 'provider_payment' | 'network_fee' | 'other';
417}
418\`\`\`
419
420#### Implementation Details
421\`\`\`typescript
422// Parallel Stripe API calls for performance
423const [customerResponse, paymentMethodsResponse, bankAccountsResponse, setupIntentsResponse] = 
424  await Promise.all([
425    fetch(\`https://api.stripe.com/v1/customers/\${customerId}\`, options),
426    fetch(\`https://api.stripe.com/v1/payment_methods?customer=\${customerId}&type=card\`, options),
427    fetch(\`https://api.stripe.com/v1/payment_methods?customer=\${customerId}&type=us_bank_account\`, options),
428    fetch(\`https://api.stripe.com/v1/setup_intents?customer=\${customerId}\`, options)
429  ]);
430
431// Paginated invoice fetching (up to 1000 invoices)
432const fetchAllInvoices = async () => {
433  const allInvoices = [];
434  let hasMore = true;
435  let startingAfter = null;
436  
437  while (hasMore && allInvoices.length < 1000) {
438    const response = await fetch(
439      \`https://api.stripe.com/v1/invoices?customer=\${customerId}&limit=100\${
440        startingAfter ? \`&starting_after=\${startingAfter}\` : ''
441      }&expand[]=data.lines.data&expand[]=data.payment_intent\`,
442      options
443    );
444    const data = await response.json();
445    allInvoices.push(...data.data);
446    hasMore = data.has_more;
447    startingAfter = data.data[data.data.length - 1]?.id;
448  }
449  return allInvoices;
450};
451\`\`\`
452
453---
454
455### 3.3 create-setup-intent
456
457Creates Stripe SetupIntent for adding payment methods.
458
459\`\`\`
460POST /functions/v1/create-setup-intent
461Authorization: Bearer <JWT_TOKEN>
462\`\`\`
463
464#### Request Interface
465\`\`\`typescript
466interface CreateSetupIntentRequest {
467  organization_id: string;
468  payment_method_type?: 'card' | 'us_bank_account'; // Default: 'card'
469}
470\`\`\`
471
472#### Response Interface
473\`\`\`typescript
474interface CreateSetupIntentResponse {
475  client_secret: string;
476  setup_intent_id: string;
477}
478\`\`\`
479
480#### Error Codes
481| Code | Description |
482|------|-------------|
483| 401 | Unauthorized - Invalid token |
484| 400 | Bad Request - Missing org ID or no Stripe customer |
485| 403 | Forbidden - Not admin for organization |
486| 500 | Internal Server Error - Stripe API failure |
487
488---
489
490### 3.4 process-payroll-payments
491
492Processes payments for multiple invoices.
493
494\`\`\`
495POST /functions/v1/process-payroll-payments
496Authorization: Bearer <JWT_TOKEN>
497\`\`\`
498
499#### Request Interface
500\`\`\`typescript
501interface ProcessPaymentRequest {
502  organization_id: string;
503  invoice_ids: string[];
504  payment_method_id: string;
505}
506\`\`\`
507
508#### Response Interface
509\`\`\`typescript
510interface ProcessPaymentResponse {
511  success: boolean;
512  results: PaymentResult[];
513  summary: {
514    total: number;
515    successful: number;
516    failed: number;
517  };
518}
519
520interface PaymentResult {
521  invoice_id: string;
522  success: boolean;
523  amount?: number;
524  status?: string;
525  processing?: boolean;
526  message?: string;
527  error?: string;
528}
529\`\`\`
530
531#### Payment Flow
532\`\`\`
533┌─────────────────────────────────────────────────────────────────┐
534│                    process-payroll-payments                     │
535├─────────────────────────────────────────────────────────────────┤
536│                                                                  │
537│   For each invoice_id in invoice_ids:                           │
538│   ┌─────────────────────────────────────────────────────────┐   │
539│   │  1. GET /v1/invoices/{invoice_id}                       │   │
540│   │     └─> Check status, get amount_due                    │   │
541│   │                                                          │   │
542│   │  2. Skip if status === 'paid'                           │   │
543│   │                                                          │   │
544│   │  3. POST /v1/invoices/{invoice_id}/pay                  │   │
545│   │     body: { payment_method: payment_method_id }         │   │
546│   │                                                          │   │
547│   │  4. Handle response:                                     │   │
548│   │     - 'paid': Success, payment complete                 │   │
549│   │     - 'open': Processing (3-5 days for bank transfers)  │   │
550│   │     - other: Failed, capture error message              │   │
551│   └─────────────────────────────────────────────────────────┘   │
552│                                                                  │
553│   Return: { success, results, summary }                         │
554│                                                                  │
555└─────────────────────────────────────────────────────────────────┘
556\`\`\`
557
558---
559
560### 3.5 create-consolidated-invoices
561
562Creates Stripe invoices for approved shifts (system cron job).
563
564\`\`\`
565POST /functions/v1/create-consolidated-invoices
566x-system-auth: <SYSTEM_AUTH_KEY>
567\`\`\`
568
569#### Request Interface
570\`\`\`typescript
571interface CreateConsolidatedInvoicesRequest {
572  olderThanHours?: number; // Default: 48
573}
574\`\`\`
575
576#### Response Interface
577\`\`\`typescript
578interface CreateConsolidatedInvoicesResponse {
579  success: boolean;
580  invoices_created: number;
581  orders_processed: number;
582  results: LocationResult[];
583}
584
585interface LocationResult {
586  locationId: string;
587  locationName: string;
588  invoiceId?: string;
589  ordersCount: number;
590  error?: string;
591}
592\`\`\`
593
594#### Business Logic
595\`\`\`typescript
596// Groups approved shifts by location
597// Calculates network fees based on order type:
598// - 'shared': 10% markup
599// - 'managed': 30% markup
600// - 'private': No fee (excluded from invoicing)
601
602// Week boundary calculation (Sunday-Saturday)
603const currentWeekStart = new Date();
604currentWeekStart.setDate(now.getDate() - now.getDay());
605currentWeekStart.setHours(0, 0, 0, 0);
606
607// Only process shifts from completed weekly periods
608// AND older than 48 hours
609const effectiveCutoff = Math.min(cutoff48Hours, currentWeekStart);
610\`\`\`
611
612---
613
614### 3.6 get-invoice-amounts
615
616Fetches current invoice amounts and statuses.
617
618\`\`\`
619POST /functions/v1/get-invoice-amounts
620Authorization: Bearer <JWT_TOKEN>
621\`\`\`
622
623#### Request Interface
624\`\`\`typescript
625interface GetInvoiceAmountsRequest {
626  invoice_ids: string[];
627}
628\`\`\`
629
630#### Response Interface
631\`\`\`typescript
632interface GetInvoiceAmountsResponse {
633  invoices: Record<string, {
634    amount_due: number;
635    status: string;
636  }>;
637}
638\`\`\`
639
640---
641
642## 4. Frontend Architecture
643
644### 4.1 Component Tree
645
646\`\`\`
647src/
648├── pages/
649│   ├── CreditApplication.tsx      # 7-step application wizard
650│   ├── CreditAccountPage.tsx      # Credit account dashboard
651│   └── Finance.tsx                # Finance overview (invoices)
652│
653├── components/credit/
654│   ├── Application Steps/
655│   │   ├── CreditWelcome.tsx           # Start page
656│   │   ├── LocationAuthorization.tsx   # Step 1
657│   │   ├── BusinessInformation.tsx     # Step 2
658│   │   ├── FinancialInformation.tsx    # Step 3
659│   │   ├── BeneficialOwnership.tsx     # Step 4 (KYC)
660│   │   ├── SignerInformation.tsx       # Step 5
661│   │   ├── PaymentMethodSelection.tsx  # Step 6
662│   │   ├── ReviewSubmit.tsx            # Step 7
663│   │   └── SuccessPage.tsx             # Confirmation
664│   │
665│   ├── UI Components/
666│   │   ├── ProgressBar.tsx             # Step progress indicator
667│   │   ├── StepContainer.tsx           # Step wrapper
668│   │   ├── EligibilityCheck.tsx        # Pre-qualification
669│   │   └── PaymentMethodSchema.ts      # Validation schema
670│   │
671│   ├── Status Views/
672│   │   ├── NoApplicationView.tsx       # No application state
673│   │   ├── PendingApprovalView.tsx     # Awaiting review
674│   │   ├── RejectedApplicationView.tsx # Rejected state
675│   │   └── ApplicationStatusDemo.tsx   # Demo state switcher
676│   │
677│   ├── Account Management/
678│   │   ├── PaymentMethodSummary.tsx    # Default payment display
679│   │   ├── PaymentDialog.tsx           # Make payment modal
680│   │   ├── TransactionsList.tsx        # Transaction history (mobile)
681│   │   ├── TransactionsTable.tsx       # Transaction history (desktop)
682│   │   └── TransactionFilters.tsx      # Filter controls
683│   │
684├── components/finance/
685│   ├── FinanceDues.tsx                 # Outstanding amounts
686│   ├── FinanceBalance.tsx              # Account balance
687│   ├── FinancePaymentMethods.tsx       # Payment method list
688│   ├── FacilityInvoices.tsx            # Invoice list by facility
689│   ├── InvoiceDetail.tsx               # Single invoice view
690│   ├── PayInvoicesDialog.tsx           # Pay invoices modal
691│   ├── FinancePeriodCost.tsx           # Cost by period
692│   ├── FinanceSharedServices.tsx       # Shared service costs
693│   ├── FinanceManagedServices.tsx      # Managed service costs
694│   └── FinanceSubscriptions.tsx        # Subscription management
695│
696├── contexts/
697│   └── StripeBillingContext.tsx        # Billing data provider
698│
699├── hooks/
700│   ├── useCreditAccount.ts             # Credit account data
701│   └── useCreditApplicationStatus.ts   # Application status
702│
703└── components/
704    └── BankAccountVerification.tsx     # ACH verification flow
705\`\`\`
706
707### 4.2 StripeBillingContext
708
709Central context for all billing data.
710
711\`\`\`typescript
712// src/contexts/StripeBillingContext.tsx
713interface StripeBillingData {
714  paymentMethods: PaymentMethod[];
715  invoices: Invoice[];
716  payrollInvoices: Invoice[];  // Subset with order details
717  currentBalance: number;
718  customerId?: string;
719}
720
721interface StripeBillingContextType {
722  data: StripeBillingData;
723  loading: boolean;
724  error: string | null;
725  refetch: () => void;
726}
727
728export const StripeBillingProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
729  const { selectedOrgId } = useOrganization();
730  
731  const fetchWithRetry = async (retries = 5, delay = 3000) => {
732    for (let attempt = 1; attempt <= retries; attempt++) {
733      try {
734        const { data, error } = await supabase.functions.invoke('get-stripe-billing', {
735          body: { organization_id: selectedOrgId }
736        });
737        if (error) throw new Error(error.message);
738        
739        // Filter payroll invoices (those with order details)
740        const payrollInvoices = data.invoices.filter(inv => 
741          inv.orderDetails || inv.orderId || 
742          inv.lineItems?.some(item => item.order_id)
743        );
744        
745        return { ...data, payrollInvoices };
746      } catch (err) {
747        if (attempt === retries) throw err;
748        await new Promise(r => setTimeout(r, delay));
749      }
750    }
751  };
752  
753  // Auto-fetch on org change
754  useEffect(() => {
755    if (selectedOrgId && userProfile) {
756      fetchBillingData();
757    }
758  }, [selectedOrgId, userProfile]);
759  
760  return (
761    <StripeBillingContext.Provider value={{ data, loading, error, refetch }}>
762      {children}
763    </StripeBillingContext.Provider>
764  );
765};
766
767export const useStripeBilling = () => {
768  const context = useContext(StripeBillingContext);
769  if (!context) throw new Error('useStripeBilling must be used within StripeBillingProvider');
770  return context;
771};
772\`\`\`
773
774### 4.3 useCreditApplicationStatus Hook
775
776\`\`\`typescript
777// src/hooks/useCreditApplicationStatus.ts
778export const useCreditApplicationStatus = () => {
779  const { selectedOrgId } = useOrganization();
780  const [statusData, setStatusData] = useState<CreditApplicationStatusData | null>(null);
781  
782  // Storage key per organization
783  const getStorageKey = () => \`credit-application-status-\${selectedOrgId}\`;
784  
785  useEffect(() => {
786    const loadStatus = () => {
787      const stored = localStorage.getItem(getStorageKey());
788      if (stored) {
789        const data = JSON.parse(stored);
790        setStatusData(data);
791        
792        // Demo: Auto-approve after 5 minutes
793        if (data.status === 'pending_approval' && data.submittedAt) {
794          const timeDiff = Date.now() - new Date(data.submittedAt).getTime();
795          if (timeDiff > 300000) {
796            approveApplication(20000);
797          }
798        }
799      } else {
800        setStatusData({ status: 'no_application', organizationId: selectedOrgId });
801      }
802    };
803    
804    loadStatus();
805    const interval = setInterval(loadStatus, 30000); // Check every 30s
806    return () => clearInterval(interval);
807  }, [selectedOrgId]);
808  
809  const submitApplication = (applicationId: string) => {
810    updateStatus({ status: 'pending_approval', applicationId, submittedAt: new Date().toISOString() });
811  };
812  
813  const approveApplication = (approvedAmount: number) => {
814    updateStatus({ status: 'approved', reviewedAt: new Date().toISOString(), approvedAmount });
815  };
816  
817  const rejectApplication = (rejectionReason: string) => {
818    updateStatus({ status: 'rejected', reviewedAt: new Date().toISOString(), rejectionReason });
819  };
820  
821  return { statusData, loading, submitApplication, approveApplication, rejectApplication, resetApplication };
822};
823\`\`\`
824
825### 4.4 useCreditAccount Hook
826
827\`\`\`typescript
828// src/hooks/useCreditAccount.ts
829export const useCreditAccount = () => {
830  const { selectedOrgId } = useOrganization();
831  const { statusData } = useCreditApplicationStatus();
832  const [creditAccount, setCreditAccount] = useState<CreditAccount | null>(null);
833  const [transactions, setTransactions] = useState<CreditTransaction[]>([]);
834  
835  useEffect(() => {
836    if (!selectedOrgId || !statusData) return;
837    
838    const fetchCreditAccount = async () => {
839      if (statusData.status === 'approved') {
840        // TODO: Replace with actual API call
841        setCreditAccount(mockCreditAccount);
842        setTransactions(mockTransactions);
843      } else {
844        setCreditAccount(null);
845        setTransactions([]);
846      }
847    };
848    
849    fetchCreditAccount();
850  }, [selectedOrgId, statusData]);
851  
852  return {
853    creditAccount,
854    transactions,
855    loading,
856    error,
857    refetch,
858    applicationStatus: statusData?.status || 'no_application',
859    statusData
860  };
861};
862\`\`\`
863
864---
865
866## 5. Stripe Integration
867
868### 5.1 Customer Lifecycle
869
870\`\`\`
871┌─────────────────────────────────────────────────────────────────┐
872│                    STRIPE CUSTOMER LIFECYCLE                     │
873├─────────────────────────────────────────────────────────────────┤
874│                                                                  │
875│   1. Organization Created (via create-organization)             │
876│      │                                                          │
877│      ▼                                                          │
878│   2. create-stripe-customer triggered                           │
879│      - Creates Stripe Customer                                  │
880│      - Waits 25 seconds                                         │
881│      - Creates org_billing record                               │
882│      │                                                          │
883│      ▼                                                          │
884│   3. User adds payment method                                   │
885│      - create-setup-intent called                               │
886│      - Returns client_secret for Stripe.js                      │
887│      │                                                          │
888│      ▼                                                          │
889│   4. Stripe.js confirms SetupIntent                             │
890│      - Payment method attached to customer                      │
891│      │                                                          │
892│      ▼                                                          │
893│   5. Shifts approved → Invoices created                         │
894│      - create-consolidated-invoices (cron)                      │
895│      - Groups by location, calculates fees                      │
896│      │                                                          │
897│      ▼                                                          │
898│   6. User pays invoices                                         │
899│      - process-payroll-payments                                 │
900│      - Stripe charges payment method                            │
901│                                                                  │
902└─────────────────────────────────────────────────────────────────┘
903\`\`\`
904
905### 5.2 Payment Method Types
906
907| Type | Status Values | Verification |
908|------|--------------|--------------|
909| card | \`active\` | Immediate |
910| us_bank_account | \`new\`, \`verified\`, \`pending_verification\` | Plaid or micro-deposits |
911
912### 5.3 Invoice Statuses
913
914| Status | Description |
915|--------|-------------|
916| \`draft\` | Being prepared, not finalized |
917| \`open\` | Finalized, awaiting payment |
918| \`paid\` | Payment successful |
919| \`void\` | Cancelled |
920| \`uncollectible\` | Marked as bad debt |
921
922---
923
924## 6. Business Logic
925
926### 6.1 Network Fee Calculation
927
928\`\`\`typescript
929// Order type fee structure
930const NETWORK_FEES = {
931  shared: 0.10,   // 10% markup
932  managed: 0.30,  // 30% markup
933  private: 0.00   // No fee (not invoiced through platform)
934};
935
936const calculateNetworkFee = (order: Order, basePayrollAmount: number): number => {
937  const feeRate = NETWORK_FEES[order.type] || 0;
938  return basePayrollAmount * feeRate;
939};
940
941// Invoice line item creation
942const createInvoiceLineItems = (order: Order) => {
943  const regularPay = order.regular_hours * order.payrate;
944  const overtimePay = order.overtime_hours * order.payrate * 1.5;
945  const basePayroll = regularPay + overtimePay;
946  const networkFee = calculateNetworkFee(order, basePayroll);
947  
948  return [
949    { description: \`Regular Hours (\${order.regular_hours}h)\`, amount: regularPay },
950    { description: \`Overtime Hours (\${order.overtime_hours}h)\`, amount: overtimePay },
951    { description: \`Network Fee (\${NETWORK_FEES[order.type] * 100}%)\`, amount: networkFee }
952  ];
953};
954\`\`\`
955
956### 6.2 Invoice Consolidation Rules
957
958\`\`\`typescript
959// Invoices are created when:
960// 1. Order status = 'filled'
961// 2. order_time_summary.approved = true
962// 3. invoice_id IS NULL
963// 4. type != 'private'
964// 5. shift_start < effectiveCutoff (completed week + 48 hours old)
965
966// Grouped by:
967// - organization_id
968// - location_id
969
970// Week boundaries: Sunday 00:00 to Saturday 23:59
971\`\`\`
972
973### 6.3 Application Status State Machine
974
975\`\`\`
976┌─────────────────────────────────────────────────────────────────┐
977│                 APPLICATION STATUS STATE MACHINE                 │
978├─────────────────────────────────────────────────────────────────┤
979│                                                                  │
980│   ┌──────────────────┐                                          │
981│   │  no_application  │──── Start Application ────┐              │
982│   └──────────────────┘                           │              │
983│           ▲                                      │              │
984│           │                                      ▼              │
985│       Reset                            ┌─────────────────────┐  │
986│           │                            │ pending_approval    │  │
987│   ┌───────┴──────┐                     │ (Under Review)      │  │
988│   │              │                     └─────────────────────┘  │
989│   │              │                        │            │        │
990│   │              │                  Approve          Reject     │
991│   │              │                        │            │        │
992│   │              ▼                        ▼            ▼        │
993│   │   ┌──────────────────┐      ┌─────────────────────────┐    │
994│   └───│    rejected      │      │       approved          │    │
995│       │ (Can Reapply)    │      │  (Account Active)       │    │
996│       └──────────────────┘      └─────────────────────────┘    │
997│                                                                  │
998└─────────────────────────────────────────────────────────────────┘
999\`\`\`
1000
1001---
1002
1003## 7. User Flows
1004
1005### 7.1 Credit Application Flow
1006
1007\`\`\`
1008┌─────────────────────────────────────────────────────────────────┐
1009│                    CREDIT APPLICATION FLOW                       │
1010├─────────────────────────────────────────────────────────────────┤
1011│                                                                  │
1012│  User                    Frontend                    Backend     │
1013│   │                         │                           │        │
1014│   │──Navigate to /credit────>│                           │        │
1015│   │                         │                           │        │
1016│   │<──Show CreditWelcome────│                           │        │
1017│   │                         │                           │        │
1018│   │──Click "Get Started"────>│                           │        │
1019│   │                         │                           │        │
1020│   │     ┌─────────────────────────────────────────┐    │        │
1021│   │     │  Step 1: Location Authorization         │    │        │
1022│   │     │  - Select org or location scope         │    │        │
1023│   │     │  - Authorize representative             │    │        │
1024│   │     └─────────────────────────────────────────┘    │        │
1025│   │     │  Step 2: Business Information           │    │        │
1026│   │     │  - Legal name, EIN, address             │    │        │
1027│   │     │  - Industry, revenue                    │    │        │
1028│   │     └─────────────────────────────────────────┘    │        │
1029│   │     │  Step 3: Financial Information          │    │        │
1030│   │     │  - Payroll spend, banking              │    │        │
1031│   │     └─────────────────────────────────────────┘    │        │
1032│   │     │  Step 4: Beneficial Ownership (KYC)     │    │        │
1033│   │     │  - 25%+ owners, controlling person      │    │        │
1034│   │     └─────────────────────────────────────────┘    │        │
1035│   │     │  Step 5: Signer Information             │    │        │
1036│   │     │  - Authorized signer details            │    │        │
1037│   │     └─────────────────────────────────────────┘    │        │
1038│   │     │  Step 6: Payment Method                 │    │        │
1039│   │     │  - Add bank account or card             │───>│        │
1040│   │     │                                         │    │        │
1041│   │     │                                         │<───│        │
1042│   │     └─────────────────────────────────────────┘    │        │
1043│   │     │  Step 7: Review & Submit                │    │        │
1044│   │     │  - E-signature, disclosures             │    │        │
1045│   │     └─────────────────────────────────────────┘    │        │
1046│   │                         │                           │        │
1047│   │──Submit Application─────>│                           │        │
1048│   │                         │──Save to localStorage──────│        │
1049│   │                         │──Set status: pending───────│        │
1050│   │<──Show SuccessPage──────│                           │        │
1051│   │                         │                           │        │
1052└─────────────────────────────────────────────────────────────────┘
1053\`\`\`
1054
1055### 7.2 Pay Invoices Flow
1056
1057\`\`\`
1058┌─────────────────────────────────────────────────────────────────┐
1059│                    PAY INVOICES FLOW                            │
1060├─────────────────────────────────────────────────────────────────┤
1061│                                                                  │
1062│  User                    Frontend                    Backend     │
1063│   │                         │                           │        │
1064│   │──Select invoices────────>│                           │        │
1065│   │──Click "Pay"────────────>│                           │        │
1066│   │                         │                           │        │
1067│   │<──PayInvoicesDialog─────│                           │        │
1068│   │   - Invoice summary      │                           │        │
1069│   │   - Payment methods      │                           │        │
1070│   │                         │                           │        │
1071│   │──Select payment method──>│                           │        │
1072│   │──Confirm payment────────>│                           │        │
1073│   │                         │                           │        │
1074│   │                         │──process-payroll-payments─>│        │
1075│   │                         │  { organization_id,        │        │
1076│   │                         │    invoice_ids,            │        │
1077│   │                         │    payment_method_id }     │        │
1078│   │                         │                           │        │
1079│   │                         │       For each invoice:   │        │
1080│   │                         │       │                   │        │
1081│   │                         │       ├─> GET invoice     │        │
1082│   │                         │       ├─> POST pay        │        │
1083│   │                         │       └─> Record result   │        │
1084│   │                         │                           │        │
1085│   │                         │<──{ results, summary }────│        │
1086│   │                         │                           │        │
1087│   │<──Show results──────────│                           │        │
1088│   │   - Success count        │                           │        │
1089│   │   - Failure details      │                           │        │
1090│   │                         │                           │        │
1091│   │──Close dialog───────────>│                           │        │
1092│   │                         │──refetch billing data─────>│        │
1093│   │                         │                           │        │
1094└
1094─────────────────────────────────────────────────────────────────┘
1095\`\`\`
1096
1097### 7.3 Add Payment Method Flow
1098
1099\`\`\`
1100┌─────────────────────────────────────────────────────────────────┐
1101│                    ADD PAYMENT METHOD FLOW                      │
1102├─────────────────────────────────────────────────────────────────┤
1103│                                                                  │
1104│  User                Frontend           Edge Function    Stripe  │
1105│   │                    │                     │             │     │
1106│   │──Click "Add"───────>│                     │             │     │
1107│   │                    │                     │             │     │
1108│   │                    │──create-setup-intent->│             │     │
1109│   │                    │                     │             │     │
1110│   │                    │                     │──Create SI──>│     │
1111│   │                    │                     │<──SI data────│     │
1112│   │                    │                     │             │     │
1113│   │                    │<──client_secret─────│             │     │
1114│   │                    │                     │             │     │
1115│   │<──Show Stripe Form─│                     │             │     │
1116│   │                    │                     │             │     │
1117│   │──Enter card/bank──>│                     │             │     │
1118│   │                    │                     │             │     │
1119│   │                    │──Stripe.js confirm──────────────>│     │
1120│   │                    │<──Confirmation──────────────────│     │
1121│   │                    │                     │             │     │
1122│   │<──Success──────────│                     │             │     │
1123│   │                    │                     │             │     │
1124│   │                    │──refetch billing────>│             │     │
1125│   │                    │                     │             │     │
1126└─────────────────────────────────────────────────────────────────┘
1127\`\`\`
1128
1129---
1130
1131## 8. Security & Permissions
1132
1133### 8.1 Permission Model
1134
1135\`\`\`typescript
1136// Credit/Billing operations require admin role
1137const checkBillingPermission = async (userId: string, orgId: string): Promise<boolean> => {
1138  const { data } = await supabase
1139    .from('org_associations')
1140    .select('role')
1141    .eq('organization_id', orgId)
1142    .eq('user_id', userId)
1143    .eq('status', 'active')
1144    .eq('role', 'admin')
1145    .single();
1146  
1147  return !!data;
1148};
1149\`\`\`
1150
1151### 8.2 Edge Function Security
1152
1153| Function | Auth Type | Access |
1154|----------|-----------|--------|
1155| create-stripe-customer | Static Key | System triggers only |
1156| get-stripe-billing | JWT | Authenticated users |
1157| create-setup-intent | JWT | Org admins only |
1158| process-payroll-payments | JWT | Org admins only |
1159| create-consolidated-invoices | System Key | Cron jobs only |
1160| get-invoice-amounts | JWT | Authenticated users |
1161
1162### 8.3 Data Security
1163
1164\`\`\`sql
1165-- RLS on org_billing
1166CREATE POLICY "Admins can view org billing"
1167ON org_billing FOR SELECT
1168USING (
1169  EXISTS (
1170    SELECT 1 FROM org_associations
1171    WHERE org_associations.organization_id = org_billing.organization_id
1172    AND org_associations.user_id = auth.uid()
1173    AND org_associations.role = 'admin'
1174    AND org_associations.status = 'active'
1175  )
1176);
1177
1178-- No direct INSERT/UPDATE/DELETE from client
1179-- All modifications via edge functions with service role
1180\`\`\`
1181
1182---
1183
1184## 9. Testing Strategy
1185
1186### 9.1 Unit Tests
1187
1188\`\`\`typescript
1189// Network fee calculation tests
1190describe('calculateNetworkFee', () => {
1191  it('should calculate 10% for shared orders', () => {
1192    const fee = calculateNetworkFee({ type: 'shared' }, 1000);
1193    expect(fee).toBe(100);
1194  });
1195  
1196  it('should calculate 30% for managed orders', () => {
1197    const fee = calculateNetworkFee({ type: 'managed' }, 1000);
1198    expect(fee).toBe(300);
1199  });
1200  
1201  it('should return 0 for private orders', () => {
1202    const fee = calculateNetworkFee({ type: 'private' }, 1000);
1203    expect(fee).toBe(0);
1204  });
1205});
1206\`\`\`
1207
1208### 9.2 Integration Tests
1209
1210\`\`\`typescript
1211describe('StripeBillingContext', () => {
1212  it('should fetch billing data on mount', async () => {
1213    const { result, waitFor } = renderHook(() => useStripeBilling(), {
1214      wrapper: createTestWrapper({ orgId: 'test-org' })
1215    });
1216    
1217    await waitFor(() => !result.current.loading);
1218    
1219    expect(result.current.data.paymentMethods).toBeDefined();
1220    expect(result.current.data.invoices).toBeDefined();
1221  });
1222  
1223  it('should filter payroll invoices', async () => {
1224    const { result, waitFor } = renderHook(() => useStripeBilling(), {
1225      wrapper: createTestWrapper()
1226    });
1227    
1228    await waitFor(() => !result.current.loading);
1229    
1230    result.current.data.payrollInvoices.forEach(inv => {
1231      expect(inv.orderDetails || inv.orderId || inv.lineItems.some(li => li.order_id)).toBe(true);
1232    });
1233  });
1234});
1235\`\`\`
1236
1237### 9.3 E2E Tests
1238
1239\`\`\`typescript
1240describe('Credit Application', () => {
1241  test('should complete 7-step application', async ({ page }) => {
1242    await page.goto('/credit');
1243    
1244    // Welcome page
1245    await page.click('text=Get Started');
1246    
1247    // Step 1: Location Authorization
1248    await page.fill('[name="authorizedByName"]', 'John Doe');
1249    await page.click('text=Continue');
1250    
1251    // ... continue through steps
1252    
1253    // Step 7: Review & Submit
1254    await page.check('[name="termsAccepted"]');
1255    await page.click('text=Submit Application');
1256    
1257    // Verify success
1258    await expect(page.locator('text=Application Submitted')).toBeVisible();
1259  });
1260  
1261  test('should pay invoice', async ({ page }) => {
1262    await page.goto('/finance');
1263    
1264    // Select invoice
1265    await page.click('[data-testid="invoice-checkbox"]');
1266    await page.click('text=Pay Selected');
1267    
1268    // Select payment method
1269    await page.click('[data-testid="payment-method-option"]');
1270    await page.click('text=Confirm Payment');
1271    
1272    // Verify success
1273    await expect(page.locator('text=Payment Successful')).toBeVisible();
1274  });
1275});
1276\`\`\`
1277
1278---
1279
1280## 10. Appendices
1281
1282### A. Glossary
1283
1284| Term | Definition |
1285|------|------------|
1286| SetupIntent | Stripe object for saving payment methods without immediate charge |
1287| Network Fee | Platform markup on staffing orders (10-30%) |
1288| Consolidated Invoice | Single invoice grouping multiple shifts per location |
1289| KYC | Know Your Customer - identity verification requirements |
1290| ACH | Automated Clearing House - US bank transfer system |
1291
1292### B. Related Documents
1293
1294- [03-payroll-processing.md](./03-payroll-processing.md) - Payroll and invoicing
1295- [04-finance-management.md](./04-finance-management.md) - Finance overview
1296- [08-team-management.md](./08-team-management.md) - Organization roles
1297
1298### C. Environment Variables
1299
1300| Variable | Description | Required |
1301|----------|-------------|----------|
1302| stripeKey | Stripe secret key | Yes |
1303| SYSTEM_AUTH_KEY | System-level auth for cron jobs | Yes |
1304| SUPABASE_URL | Supabase project URL | Yes |
1305| SUPABASE_ANON_KEY | Supabase anonymous key | Yes |
1306| SUPABASE_SERVICE_ROLE_KEY | Supabase service role key | Yes |
1307
1308### D. Stripe API Endpoints Used
1309
1310| Endpoint | Method | Purpose |
1311|----------|--------|---------|
1312| /v1/customers | POST | Create customer |
1313| /v1/customers/{id} | GET | Get customer details |
1314| /v1/payment_methods | GET | List payment methods |
1315| /v1/setup_intents | POST | Create setup intent |
1316| /v1/setup_intents | GET | List setup intents |
1317| /v1/invoices | GET | List invoices |
1318| /v1/invoices | POST | Create invoice |
1319| /v1/invoices/{id}/pay | POST | Pay invoice |
1320| /v1/invoices/{id}/finalize | POST | Finalize draft invoice |
1321| /v1/invoiceitems | POST | Create invoice line item |
1322
1323### E. Component Props Reference
1324
1325\`\`\`typescript
1326// PayInvoicesDialog
1327interface PayInvoicesDialogProps {
1328  open: boolean;
1329  onOpenChange: (open: boolean) => void;
1330  invoices: Invoice[];
1331  onSuccess: () => void;
1332}
1333
1334// PaymentMethodSummary (no props - uses context)
1335
1336// TransactionsList
1337interface TransactionsListProps {
1338  transactions: CreditTransaction[];
1339  loading?: boolean;
1340}
1341
1342// TransactionsTable
1343interface TransactionsTableProps {
1344  transactions: CreditTransaction[];
1345  loading?: boolean;
1346}
1347\`\`\`
1348
1349---
1350
1351*Last Updated: December 28, 2024*
1352*Version: 2.0*
1353*Status: Approved*
1354`;export{n as default};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.