Architecture Overview

Multi-Market Platform

LocallyGrown.net is a multi-tenant platform where each farmers market operates independently with its own Stripe account.

  • Architecture: Each market has separate Stripe API keys (publishable and secret)
  • Subdomain Routing: Markets are accessed via subdomains (e.g., market1.locallygrown.net)
  • Key Storage: Stripe keys are stored in the MySQL database
  • Key Selection: Runtime determination based on subdomain/marketId

Stripe Configuration per Market

Each market has Stripe configuration stored securely:

  • Publishable Key - Client-side API key for secure tokenization
  • Secret Key - Server-side API key (encrypted at rest)
  • Approval Status - Whether market is approved for payment processing
  • Prepayment Fee - Optional fee percentage for prepayments
  • Prepayment Fee Label - Customer-facing fee description

Stripe Client Initialization

Server-Side Initialization

The server-side Stripe client is initialized through a secure service layer that manages API key retrieval and client instantiation.

1. API Key Retrieval

The system retrieves market-specific Stripe API keys from secure storage:

  • Development environments can use override configuration for testing
  • Production environments retrieve keys from encrypted database storage
  • System throws errors if market configuration is missing or invalid

2. Client Creation

Stripe client initialization includes multiple safety mechanisms:

  • Payment processing is disabled by default in non-production environments
  • Explicit configuration flag required to enable live charges
  • Prevents accidental charges during development and testing
  • Uses current Stripe API version for compatibility

Safety Mechanism:
Payment processing must be explicitly enabled via configuration. The system defaults to disabled state to prevent accidental charges in development/testing environments.

Client-Side Initialization

Cards are collected using Stripe Elements, a PCI-compliant client-side SDK that handles sensitive card data entirely within Stripe's infrastructure.

Integration Approach:

  • Stripe.js library loaded dynamically from official CDN
  • Initialized with market-specific publishable key
  • Elements created for secure card input collection
  • Card data never touches LocallyGrown servers

API Endpoints and Flows

1. SetupIntent Creation (Card Saving Initiation)

GET
/api/stripe/setup-intent

Flow:

  1. Authenticates user session
  2. Applies rate limiting to prevent abuse
  3. Verifies payment processing is enabled
  4. Creates Stripe SetupIntent via service layer
  5. Returns secure client token for card collection

Response Format:

Returns JSON with success status, client secret token, and configuration flags

2. Setup Intent Processing (Server-Side)

The server-side service layer handles SetupIntent creation through the Stripe API.

Implementation Details:

  • Creates SetupIntent associated with customer account
  • Configured for off-session (future) payment use
  • Accepts all major card brands (credit, debit, prepaid)
  • Includes market and transaction metadata for tracking

Card Types Supported: All major card brands including Visa, Mastercard, American Express, Discover, and international cards

3. Payment Method Attachment

POST
/api/user/save-card

Flow:

  1. Authenticates user session
  2. Applies rate limiting for card management operations
  3. Validates payment method token format
  4. Processes card attachment through service layer
  5. Returns card display details (brand, last 4 digits)

Service Implementation

The payment service handles the secure attachment of payment methods to customer accounts.

Processing Steps:

  1. Token Validation: Verifies payment method token has correct format before processing
  2. Existing Customer Handling:
    • Retrieves customer record from Stripe
    • Attaches new payment method
    • Sets as default for future charges
    • Removes outdated payment methods
  3. New Customer Creation:
    • Creates Stripe customer with user email
    • Attaches payment method
    • Configures as default
  4. Record Update: Updates user record with customer ID and card display information

4. Payment Intent Creation (Charging Cards)

A. Single Order Payment

The payment service processes individual order payments through Stripe's PaymentIntent API with full off-session charge support.

Key Parameters:

  • Market-specific configuration
  • Customer ID from previous card setup
  • Calculated amount in cents
  • Descriptive payment label
  • Transaction metadata for tracking

B. Grouped Order Payment (Multiple Orders, Single Charge)

For efficiency, the system can combine multiple orders into a single charge using idempotency keys and distributed locking.

Implementation Features:

  • Unique idempotency key prevents duplicate charges
  • Distributed lock prevents concurrent processing
  • Metadata tracks all included order IDs
  • Single customer charge for multiple orders

Critical Feature: Distributed locking mechanism prevents concurrent payment processing for the same user, ensuring charge integrity.

5. Core Payment Intent Function

The core payment processing function implements several critical safety features to ensure payment integrity.

Key Features:

1. Card Sync Verification

Before charging, the system verifies that the stored card information matches Stripe's records. If a mismatch is detected, the system attempts automatic correction to prevent charging the wrong card.

2. Default Payment Method Retrieval

Retrieves the customer's default payment method from Stripe. Throws an error if no payment method is configured, preventing failed charge attempts.

3. Payment Method Cleanup

Lists all payment methods attached to the customer and removes non-default cards. This ensures only one active card per customer, preventing confusion and wrong card charges.

4. Off-Session Charge

Creates and immediately confirms the PaymentIntent with off-session mode enabled. Uses optional idempotency key for grouped payments to prevent duplicate charges on retry.

Configuration:

  • Amount in cents (Stripe standard)
  • USD currency
  • Customer and payment method IDs
  • Off-session and immediate confirmation flags
  • Descriptive transaction metadata

Stripe API Endpoints Used:

  • customers.retrieve - Get customer data
  • paymentMethods.retrieve - Get payment method details
  • paymentMethods.list - List customer's cards
  • paymentMethods.detach - Remove old cards
  • paymentIntents.create - Create and confirm charge

Card Saving Locations

There are two distinct UI locations where users can save cards:

Location 1: Cart Checkout Modal

Access: Users updating payment method during checkout process

Implementation Features:

  • Modal Interface: Overlay dialog with progressive disclosure for card entry
  • Lazy Loading: Stripe.js loaded on-demand when modal opens to improve page performance
  • Data Security: User data snapshot captured at modal open to prevent information leaks
  • Error Handling: Validates setup completion and handles API mismatches gracefully

Unique Features: In-flow card updates during checkout, current card display, charge timing information

Location 2: User Account Panel

Access: Users managing payment methods in their account settings

Implementation Features:

  • State Management: Tracks Stripe Elements state, loading indicators, and user feedback messages
  • Smart Initialization: Automatically prompts for card entry if none on file

Unique Features: Auto-expands payment section for first-time setup

Common Patterns Across All Locations

Security:

  1. All use Stripe Elements (PCI-compliant, tokenized input)
  2. Card data never touches LocallyGrown servers
  3. Only paymentMethodId (token) sent to backend

Flow:

  1. Create SetupIntent server-side → get clientSecret
  2. Load Stripe.js from CDN
  3. Initialize Stripe with stripePubKey
  4. Create Elements with clientSecret
  5. Mount individual card field elements
  6. User enters card data (stays in Stripe iframe)
  7. Call stripe.confirmCardSetup()
  8. Receive paymentMethodId
  9. Send paymentMethodId to /api/user/save-card
  10. Server attaches to customer and updates database

Payment Processing

When Charges Occur

CRITICAL: Charges happen after pickup, not at order time or after packing.

Why After Pickup?

Orders are charged after pickup to allow for final adjustments based on what the customer actually receives:

  • Growers may be out of some items
  • Quantities might change (3 tomatoes available instead of 5)
  • Customers may add items from an "extras table" at pickup
  • Out-of-stock items can be removed before charging

Flow:

  1. Customer Places Order
    • Order created with paymentMethod: 'Pending Stripe'
    • No charge occurs
    • applyPrepayment: 1 flag set if payment should be processed
  2. Grower Packs Order
    • Grower marks items as packed
    • May adjust quantities (missing/undelivered items)
  3. Background Worker Processes Payments
    • Automated system detects packed orders flagged for payment
    • Processes individual or grouped order payments through payment service
  4. Charge Calculation
    • Spot Price: Actual cost based on packed quantities
    • Prepayment Fee: Percentage fee if configured (default 0%)
    • Total: spotPrice + (spotPrice * prepaymentFeePercent / 100)
    Example:
    • Order total: $50.00
    • Prepayment fee: 3%
    • Fee amount: $1.50
    • Total charged: $51.50
  5. Stripe Charge
    • Creates PaymentIntent with off_session: true, confirm: true
    • Uses customer's default payment method
    • Includes metadata (orderId, userId)
  6. Post-Charge Actions
    • Records prepayment in prepayments table
    • Updates order: paid: 1, prepaymentProcessed: 1, paymentMethod: 'Stripe'
    • Queues payment confirmation email
    • Clears applyPrepayment flag

Minimum Charge Amount

Stripe Requirement: $0.50 minimum charge amount

  • System validates minimum amount before processing
  • Stores validation error if amount below minimum
  • Order remains unpaid until valid amount available

Refund Processing

The system supports two refund methods: account credits (automated) and Stripe card refunds (manual).

Account Credits (Automated):

  • Processed through OrderService.processRefunds()
  • Credits customer's account balance instantly
  • Tracks refund amounts in order item records
  • Balance can be used for future orders

Stripe Card Refunds (Manual):

  • Must be processed manually via Stripe dashboard
  • Market managers initiate refunds directly in Stripe
  • Returns money to customer's original card (2-10 business days)
  • PaymentService.processRefund() exists but is not currently integrated into workflows

Important: The PaymentService.processRefund() method contains Stripe refund API code but is not called by any current system workflows. All Stripe card refunds are processed manually through the Stripe dashboard.

Webhook Integration

POST
/api/webhooks/stripe

Security

Webhook security is implemented using Stripe's signature verification mechanism to ensure authenticity.

Verification Process:

  • Validates Stripe signature header is present
  • Verifies market context through request headers
  • Uses webhook secret for signature validation
  • Constructs verified event object from payload

Requirements:

  • Valid Stripe signature header
  • Market identification
  • Active Stripe configuration

Webhook Events Handled

1. payment_intent.succeeded

Actions:

  • Updates order status to paid
  • Marks payment as processed
  • Retrieves payment method details from Stripe
  • Creates payment transaction record with amount, card details, and transaction ID

2. payment_intent.payment_failed

Actions:

  • Updates order with error message
  • Resets payment processing status
  • Preserves error details for customer support

3. charge.refunded

Actions:

  • Updates payment transaction record with refund amount
  • Updates refund status through payment service

4. payment_method.updated

Actions:

  • Updates user record with new card details
  • Synchronizes card brand and last 4 digits

Note: Requires customer ID to user mapping for updates

Rate Limiting

Webhook endpoint implements rate limiting to prevent flooding attacks and abuse.

Security Implementation

PCI Compliance

Approach: Stripe Elements (SAQ-A compliance)

1. No Card Data Touching Servers

  • Card input fields are Stripe-hosted iframes
  • Only tokenized paymentMethodId transmitted to backend

2. HTTPS Only

  • All Stripe communication over TLS
  • Local dev uses SSL certificates via anchor-pki

3. Tokenization

  • SetupIntent creates token
  • PaymentMethod ID (pm_xxx) used for all operations
  • Raw card data never stored

Authentication & Authorization

API Endpoints:

  • All endpoints protected by session-based authentication
  • User sessions validated before payment operations
  • Authorization guards prevent unauthorized access

Market Context:

  • All operations scoped to user's market
  • Stripe configuration retrieved per market
  • Cross-market operations prevented

Rate Limiting

Comprehensive rate limiting is applied across all payment-related operations to prevent abuse and ensure system stability.

Protected Operations:

  1. Setup Intent Creation: Prevents repeated card save attempts
  2. Card Management: Limits add/remove card operations
  3. Webhook Processing: Prevents webhook flooding

Card Sync Verification

Purpose: Ensures displayed card information matches the card that will be charged

Verification Process:

  1. Before charging, system verifies stored card data matches Stripe's records
  2. Queries Stripe for customer's current default payment method
  3. Compares local card information with Stripe data
  4. If mismatch detected:
    • Attempts automatic synchronization
    • Updates local records with current Stripe data
    • Throws error if synchronization fails

Protection: Prevents charging wrong card after data inconsistencies or race conditions

Environment-Based Safety

Payment Processing Control:
Payment processing is controlled by configuration flag that defaults to disabled state.

Modes:

  • Disabled (Default): Simulation mode with no real charges
  • Enabled: Production mode with real charges

Simulation Mode Features:

  • Logs payment intents for debugging
  • Returns mock transaction IDs
  • Updates database records for testing
  • Uses test card data

Database Schema

The database schema supports multi-market Stripe integration with secure storage of payment-related information.

User Payment Data

User records store minimal payment information required for processing:

  • Stripe Customer Identifier - Links user to Stripe customer account
  • Card Display Information - Card brand and last 4 digits for user interface

Market Payment Configuration

Market records store Stripe configuration and settings:

  • Publishable Key - Client-side API key (public)
  • Secret Key - Server-side API key (encrypted at rest)
  • Prepayment Fee - Optional percentage fee configuration
  • Fee Display Label - Customer-facing fee description
  • Approval Status - Enables/disables payment processing

Order Payment Tracking

Order records track payment status and processing state:

  • Payment Trigger Flag - Indicates payment should be processed
  • Processing Status - Whether payment completed successfully
  • Transaction Reference - Links to payment transaction record
  • Error Information - Stores failure details for troubleshooting
  • Payment Method Status - Tracks payment state transitions

Payment Transaction Records

Payment transactions are tracked with comprehensive information:

  • Amount - Stored in cents (Stripe standard)
  • Currency - Payment currency (default: USD)
  • Vendor Transaction ID - Stripe PaymentIntent ID for reconciliation
  • Payment Method Type - Identifies payment processor
  • Card Display Information - Brand and last 4 digits for receipts
  • Status Tracking - Paid, refunded, rejected flags
  • Refund Information - Refund amount and status
  • Audit Trail - Creation and modification timestamps

Error Handling and Retry Logic

Payment Intent Error Handling

The payment service implements comprehensive error handling to gracefully manage various failure scenarios.

Validation Errors

Invalid Amounts
  • Validates numeric values before processing
  • Enforces minimum amount requirements
  • Stores validation errors for user feedback
  • Prevents invalid API calls
Missing Configuration
  • Verifies market has payment processing enabled
  • Returns clear error if Stripe not configured
  • Stores error message for troubleshooting

Stripe API Errors

Card Declined
  • Stripe returns detailed error via PaymentIntent status
  • Webhook delivers failure notification
  • Error message stored for customer support
Missing Payment Method
  • Detects when customer has no card on file
  • Returns appropriate error response
  • User prompted to add payment method
Sync Verification Failures
  • Attempts automatic synchronization correction
  • Throws error if correction fails
  • Requires user to update payment method

Retry Logic

Idempotency Keys

Grouped payments use unique idempotency keys to prevent duplicate charges on retry.

Purpose:

  • Prevents duplicate charges if request retries
  • Stripe recognizes duplicate requests by key
  • Returns original result for retries within 24 hours

Distributed Locks

Payment processing implements distributed locking to prevent race conditions:

  • Prevents concurrent payment processing for same user
  • Lock timeout ensures eventual recovery from failures
  • Ensures only one payment process at a time per user

Double-Charge Prevention

Order Status Filtering

System filters orders by payment status to prevent reprocessing:

  • Only processes orders in pending payment state
  • Status transitions are atomic with payment

Status Transition

  • Successful payment triggers status change
  • Prevents reprocessing if worker runs again
  • Status change synchronized with payment confirmation

Testing and Simulation Mode

Simulation Mode

The system supports development and testing through a simulation mode that allows full integration testing without live payment processing.

Simulation Features:

1. Customer Management

Creates mock customer objects with realistic identifiers for testing user payment flows

2. Payment Intents

Returns simulated PaymentIntent objects with successful status, allowing complete order payment testing

3. SetupIntents

Generates mock SetupIntent objects with properly formatted client secrets for card saving flows

4. Database Integration

Full business logic execution in simulation mode:

  • All database operations execute normally
  • Test card data (Visa 4242) stored for verification
  • Complete payment workflow from initiation to completion

Test Environment

The system includes comprehensive automated testing infrastructure for payment operations.

Test Database:

  • Isolated test database separate from development and production
  • Transaction-based test isolation with automatic rollback
  • Real database operations ensuring production-like behavior

Test Coverage:

  • Unit tests for payment service logic
  • Integration tests for complete payment flows
  • Race condition prevention tests
  • Data validation and error handling tests
  • Multi-order grouped payment tests

Test Organization:

Tests are organized into focused suites covering payment creation, grouped payments, validation logic, and concurrency scenarios.

API Reference Summary

Stripe API Endpoints Used

Customers

  • customers.create - Create new customer
  • customers.retrieve - Get customer details
  • customers.update - Update default payment method

Payment Methods

  • paymentMethods.attach - Attach card to customer
  • paymentMethods.detach - Remove old cards
  • paymentMethods.retrieve - Get card details
  • paymentMethods.list - List customer's cards

SetupIntents

  • setupIntents.create - Initiate card save

PaymentIntents

  • paymentIntents.create - Create and charge (with confirm: true)

Refunds

  • refunds.create - Process refund

Webhooks

  • webhooks.constructEvent - Verify and parse webhook

LocallyGrown API Endpoints

Public APIs

  • GET /api/stripe/setup-intent - Create SetupIntent
  • POST /api/user/save-card - Save payment method
  • POST /api/webhooks/stripe - Stripe webhook handler

Internal Services

The payment service layer provides:

  • Single and grouped order payment processing
  • Prepayment transaction recording
  • Refund processing capabilities
  • Payment method management (save/remove cards)

System Architecture Overview

Service Layer Architecture

The payment system is organized into distinct service layers:

  • Core Stripe integration services for API communication
  • Payment processing services for transaction management
  • User services for payment method and customer management

API Endpoint Organization

Public API endpoints are organized by functionality:

  • SetupIntent creation endpoint for card saving flows
  • Payment method management endpoints
  • Webhook handlers for Stripe event processing

User Interface Components

Payment-related UI components include:

  • Customer-facing checkout and card management modals
  • Administrative panels for account and payment oversight

Data Layer

Payment data is managed through:

  • Secure database schema for payment information
  • Encrypted storage of sensitive payment data
  • Transaction-safe operations for payment processing

Background Processing

Asynchronous payment operations handled by:

  • Background task queues for order payment processing
  • Automated payment workflows for packed orders
  • Retry mechanisms for failed payment attempts

Contact Information

For questions about this integration, please contact: