Reference Model & Architecture Specification
- Executive Summary
- Problem Statement
- User Personas
- Feature Prioritization & Roadmap
- Core Features & Use Cases
- Third-Party Integrations
- Critical Technical Patterns
- Architectural Patterns
- Data Model
- Scalability & Capacity Planning
- Accessibility & Internationalization
- Edge Cases & Error Scenarios
- Testing Strategy
- Deployment & Infrastructure
- Compliance & Security
- Success Metrics
- Visual Diagrams
- Purpose: A platform-agnostic reference model for building e-commerce payment processing applications
- Domain: E-commerce and digital payment infrastructure
- Scope: Multi-tenant merchant dashboard, transaction management, payment processing, and real-time reporting
- Target: Web, mobile, and desktop implementations sharing a common architectural foundation
Merchants need unified platforms to manage online payments across multiple channels while maintaining compliance, real-time visibility, and seamless user experiences.
- Authentication & Authorization: Multi-role access control with fine-grained permissions
- Data Consistency: Managing concurrent updates to transactions and balances
- Real-time Synchronization: Multiple users viewing and updating the same data simultaneously
- Network Resilience: Offline-first capability with conflict-free sync
- Complex Forms: Multi-step workflows with validation, error recovery, and async operations
- Advanced Filtering: Complex queries across large transaction datasets
- Error Handling: Graceful degradation and retry strategies for external API failures
| Persona | Goals & Responsibilities | Access Level |
|---|---|---|
| Merchant Owner | View dashboards, manage payout settings, analyze revenue trends, invite team members, configure webhooks | Full Access |
| Account Manager | Process transactions, manage payment methods, handle refunds, update transaction metadata | Transaction Management |
| Finance Officer | View reconciliation reports, audit logs, export transaction data, review dispute documentation | Financial Reporting |
| Support Agent | View transaction history (read-only), search customers, access logs, no data modification | Read-Only |
- P0 (Critical): Must-have for MVP; blocks core functionality
- P1 (High): Important for production readiness; enhances core value
- P2 (Medium): Nice-to-have; improves UX or efficiency
- P3 (Low): Future enhancements; can be deferred
| Feature ID | Feature Name | Priority | Domain | Dependencies |
|---|---|---|---|---|
| AUTH-001 | User Authentication (OAuth/JWT) | P0 | Authentication | - |
| AUTH-002 | Role-Based Access Control (RBAC) | P0 | Authentication | AUTH-001 |
| AUTH-003 | Multi-Factor Authentication (MFA) | P1 | Authentication | AUTH-001 |
| TXN-001 | Transaction List & Filtering | P0 | Transaction Management | AUTH-002 |
| TXN-002 | Transaction Detail View | P0 | Transaction Management | TXN-001 |
| TXN-003 | Real-time Transaction Updates | P1 | Transaction Management | TXN-001 |
| TXN-004 | Transaction Search | P1 | Transaction Management | TXN-001 |
| TXN-005 | Refund Initiation | P1 | Transaction Management | TXN-002, PM-001 |
| PM-001 | Payment Method Management | P0 | Payment Methods | AUTH-002 |
| PM-002 | Payment Gateway Integration | P0 | Payment Methods | PM-001 |
| PO-001 | Payout Request Creation | P1 | Payouts | AUTH-002 |
| PO-002 | Payout Approval Workflow | P1 | Payouts | PO-001 |
| SYNC-001 | WebSocket Real-time Updates | P1 | Synchronization | - |
| SYNC-002 | Conflict Resolution | P1 | Synchronization | SYNC-001 |
| OFFLINE-001 | Offline Data Caching | P2 | Offline-First | TXN-001, PM-001 |
| OFFLINE-002 | Sync Queue Management | P2 | Offline-First | OFFLINE-001 |
| FORM-001 | Multi-step Forms | P1 | Forms & Validation | - |
| FORM-002 | Field Validation | P0 | Forms & Validation | FORM-001 |
| FORM-003 | Async Validation | P1 | Forms & Validation | FORM-002 |
| ERROR-001 | Error Detection & User Feedback | P0 | Error Handling | - |
| ERROR-002 | Automatic Retry with Backoff | P1 | Error Handling | ERROR-001 |
| ERROR-003 | Circuit Breaker Pattern | P1 | Error Handling | ERROR-001 |
| REPORT-001 | Transaction Export (CSV) | P1 | Reporting | TXN-001 |
| REPORT-002 | PDF Report Generation | P2 | Reporting | TXN-001 |
| REPORT-003 | Scheduled Reports | P2 | Reporting | REPORT-002 |
| FILE-001 | File Upload & Management | P1 | File Management | - |
| NAV-001 | Deep Linking & URL State | P1 | Navigation | - |
| NOTIF-001 | In-app Notifications | P1 | Notifications | - |
| NOTIF-002 | Email Notifications | P2 | Notifications | NOTIF-001 |
| NOTIF-003 | Webhook Integration | P1 | Notifications | - |
- OAuth 2.0 / JWT-based login
- Multi-factor authentication (MFA)
- Role-based access control (RBAC): Owner, Manager, Finance, Support
- Row-level security (RLS): Users see only their merchant data
- Token refresh and automatic re-authentication
- Session timeout and forced logout
- Password reset flow
UC-AUTH-001: User Login
- Given: A registered user with valid credentials
- When: User submits login form with email and password
- Then: System validates credentials, returns JWT token with 24h expiry
- And: User is redirected to dashboard
- And: Session is stored securely (httpOnly cookie or secure storage)
UC-AUTH-002: Token Refresh
- Given: User has an active session with expired access token
- When: API returns 401 Unauthorized
- Then: Client automatically uses refresh token to obtain new access token
- And: Original request is retried transparently
- And: If refresh token is expired, user is redirected to login
UC-AUTH-003: Role-Based Access Control
- Given: User has "Finance Officer" role
- When: User attempts to access "Payout Approval" endpoint
- Then: Request is allowed based on role permissions
- And: User with "Support Agent" role receives 403 Forbidden for same endpoint
UC-AUTH-004: Row-Level Security
- Given: User belongs to Merchant A (ID: merchant-001)
- When: User queries transactions
- Then: System automatically filters to return only transactions where merchant_id = merchant-001
- And: No transactions from Merchant B are returned
UC-AUTH-005: MFA Setup
- Given: User is required to enable MFA (policy setting)
- When: User accesses MFA setup flow
- Then: System generates QR code for TOTP app
- And: User enters code to verify setup
- And: Backup codes are generated for account recovery
- Transaction list with status (pending, completed, failed, disputed)
- Advanced filtering: by date range, amount, status, customer, payment method
- Search across transaction IDs, customer names, order references
- Pagination and sorting (default: 50 items per page, max: 200)
- Real-time transaction status updates via WebSocket
- Transaction detail view with full audit trail
- Refund initiation (with validation and approval workflow)
- Export to CSV/PDF
- Bulk actions (bulk refund, bulk export)
UC-TXN-001: Load Transaction List
- Given: User is logged in with appropriate permissions
- When: User navigates to Transactions page
- Then: System returns first 50 transactions sorted by created_at DESC
- And: Response includes pagination metadata (total, page, per_page, total_pages)
- And: Loading state is shown while fetching data
- Performance: Initial load completes within 2 seconds
UC-TXN-002: Apply Filters
- Given: User is on Transactions page with 500 total transactions
- When: User filters by date range (last 30 days) and status = "completed"
- Then: System returns filtered results
- And: Filter state is preserved in URL for sharing
- And: Zero-state message shown if no matches found
UC-TXN-003: Search Transactions
- Given: User has 10,000+ transactions
- When: User searches for "john@example.com" in customer email field
- Then: System returns all transactions matching the email across all time periods
- And: Search is case-insensitive
- And: Results highlight matched term
- Performance: Search completes within 500ms for up to 100,000 transactions
UC-TXN-004: Real-time Update
- Given: Two managers (User A and User B) have transaction list open
- When: User A initiates a refund on transaction TX-12345
- Then: User B's list updates within 1 second to show new status
- And: Visual indicator (toast or highlight) shows which transaction changed
- And: WebSocket connection auto-reconnects if dropped
UC-TXN-005: View Transaction Details
- Given: User clicks on transaction TX-12345 from list
- When: Transaction detail page loads
- Then: All transaction fields are displayed (amount, currency, status, customer, metadata)
- And: Complete audit trail is visible (created_at, updated_at, completed_at, settled_at, who changed what)
- And: Related transactions (refunds, chargebacks) are linked
- And: Payment method details are shown (masked card number, expiry)
- And: If transaction has refunds, "Refund History" section is displayed
UC-TXN-005.1: View Refund History
- Given: Transaction TX-12345 has multiple partial refunds
- When: User views transaction detail page
- Then: "Refund History" section lists all refunds in chronological order
- And: Each refund shows:
- Refund ID (clickable for details)
- Refund amount (formatted with currency)
- Refund date (completed_at timestamp)
- Refund status (pending, processing, completed, failed)
- Requested by (user name)
- Reason (if provided)
- And: Summary section shows:
- Original transaction amount: $100.00
- Total refunded: $75.00 (3 refunds)
- Remaining refundable: $25.00
- And: Visual progress bar shows refund percentage (75% refunded)
- And: If fully refunded, transaction status shows "refunded" with link to refund history
UC-TXN-006: Initiate Refund
- Given: User views a completed transaction with refundable amount
- When: User clicks "Refund" and confirms amount
- Then: System validates:
- Refund amount ≤ remaining refundable amount
- Transaction is in refundable state (completed, not already fully refunded)
- User has refund permissions
- And: If validation passes, refund is created in "pending" status
- And: Approval workflow is triggered if configured
- And: User receives confirmation with refund ID
UC-TXN-007: Export Transactions
- Given: User has filtered transactions (last 30 days, status: completed)
- When: User clicks "Export to CSV"
- Then: System generates CSV with columns: id, amount, currency, status, customer_email, created_at, payment_method
- And: File is downloaded to user's device
- And: For large exports (>10,000 rows), system queues job and sends email with download link
- Add new payment method (card, bank account, wallet)
- Edit payment method details (nickname, expiry)
- Set default payment method
- Delete / archive payment method
- Tokenization via payment gateway
- Validation (card expiry, BIN checks, Luhn algorithm)
- 3D Secure authentication support
UC-PM-001: Add Card Payment Method
- Given: User is on Payment Methods page
- When: User clicks "Add Card" and enters card details (number, expiry, cvv, name)
- Then: Client-side validation checks:
- Card number passes Luhn algorithm
- Expiry date is in future
- CVV is 3-4 digits
- And: Card number is sent to payment gateway for tokenization
- And: System stores only the token, never raw card data
- And: Token is linked to merchant account
- Success: Payment method appears in list with masked number (**** 4242)
UC-PM-002: Validation Errors
- Given: User enters card details
- When: Card number is invalid (fails Luhn check)
- Then: Inline error shown: "Please enter a valid card number"
- And: Submit button remains disabled
- And: Field is highlighted in red
UC-PM-003: Gateway Failure
- Given: User submits valid card details
- When: Payment gateway is temporarily unavailable
- Then: User-friendly error shown: "Unable to process card. Please try again."
- And: Retry button is displayed
- And: Error is logged with correlation ID
- And: Circuit breaker tracks failure count
UC-PM-004: Set Default Payment Method
- Given: User has multiple payment methods
- When: User clicks "Set Default" on card ending in 4242
- Then: Card ending in 4242 is marked as is_default = true
- And: All other payment methods have is_default = false
- And: Future transactions use this method by default
UC-PM-005: Delete Payment Method
- Given: User wants to remove a payment method
- When: User clicks "Delete" on non-default method
- Then: Confirmation dialog shown: "Are you sure? This cannot be undone."
- And: If confirmed, method is soft-deleted (deleted_at timestamp set)
- And: If method is default, user must first set another as default
- Request payout (single or batch)
- Payout workflow: draft → submitted → approved → processing → completed / failed
- Approval by authorized users (configurable)
- Retry failed payouts
- Payout history and reconciliation
- Settlement fee calculation
- Multi-currency payout support
UC-PO-001: Create Payout Request
- Given: User has available balance > $0
- When: User clicks "Request Payout"
- Then: Form requires:
- Payout amount (≤ available balance)
- Bank account or payment method
- Payout date (today or future)
- And: System validates:
- Minimum payout amount ($10)
- Bank account belongs to merchant
- And: User can save as draft or submit immediately
UC-PO-002: Draft Payout
- Given: User creates payout request
- When: User saves as draft
- Then: Payout status = "draft"
- And: User can edit draft later
- And: Draft is visible in "My Drafts" section
- And: Drafts older than 30 days are auto-deleted
UC-PO-003: Submit for Approval
- Given: User has drafted payout request
- When: User clicks "Submit"
- Then: Payout status changes to "pending_approval"
- And: Notification is sent to authorized approvers
- And: Request becomes read-only for submitter
UC-PO-004: Approve Payout
- Given: User is authorized approver
- When: User views pending payout and clicks "Approve"
- Then: System checks:
- User has approval permission
- Payout is in "pending_approval" status
- And: If approved, status changes to "processing"
- And: Bank transfer is initiated via payment gateway
- And: Audit log records approval (user_id, timestamp, action)
UC-PO-005: Payout Failure & Retry
- Given: Payout is in "processing" status
- When: Bank transfer fails (insufficient funds, invalid account)
- Then: Status changes to "failed"
- And: Failure reason is stored (e.g., "Invalid account number")
- And: Notification sent to payout requester
- And: User can update bank details and click "Retry"
UC-PO-006: Batch Payout
- Given: User has multiple pending balances
- When: User selects multiple payouts and clicks "Batch Process"
- Then: All selected payouts move to "pending_approval"
- And: Single approval request covers all selected payouts
- And: Individual payouts can still fail independently
- WebSocket subscriptions for transaction updates
- Real-time balance updates across all connected clients
- Conflict resolution for concurrent edits (last-write-wins or operational transforms)
- Change logs and audit trails
- Graceful fallback to polling when WebSocket unavailable
- Connection status indicator
UC-SYNC-001: WebSocket Subscription
- Given: User opens transaction list
- When: Page loads, WebSocket connection is established
- Then: Client subscribes to merchant's transaction channel
- And: Connection status shown as "Connected" (green dot)
- And: Connection auto-reconnects if dropped
UC-SYNC-002: Receive Update
- Given: User A and User B have transaction list open
- When: Transaction TX-12345 status changes on server
- Then: Both clients receive update within 1 second
- And: Transaction row is updated in UI
- And: Visual highlight (flash or border) draws attention
- And: Toast notification: "Transaction TX-12345 updated"
UC-SYNC-003: Conflict Resolution
- Given: User A and User B edit transaction metadata simultaneously
- When: Both submit changes
- Then: Last-write-wins strategy applies (server timestamp comparison)
- And: User whose change was overridden receives notification
- And: Audit log records both changes with timestamps
UC-SYNC-004: Fallback to Polling
- Given: WebSocket connection fails (network issue)
- When: Reconnection attempts fail after 3 retries
- Then: Client switches to polling mode (every 30 seconds)
- And: Status indicator changes to "Polling" (yellow dot)
- And: System attempts WebSocket reconnection every 60 seconds
- And: Returns to WebSocket when connection restored
- Local storage of critical data (transactions, balances, user profile)
- Sync queue for pending operations
- Offline indicator in UI
- Conflict-free sync when reconnected
- Limited functionality when offline (read-only mode or queued writes)
- IndexedDB for large datasets
UC-OFFLINE-001: Detect Offline State
- Given: User is viewing transaction list
- When: Network connection is lost
- Then: UI shows offline indicator (banner or icon)
- And: Current data remains visible (cached)
- And: User can navigate previously loaded pages
- And: New data fetches fail gracefully with "Offline - showing cached data"
UC-OFFLINE-002: View Cached Data
- Given: User is offline
- When: User navigates to previously viewed transaction list
- Then: Cached data is displayed
- And: "Cached - last updated: 5 minutes ago" indicator shown
- And: User can view transaction details for cached items
UC-OFFLINE-003: Queue Operation
- Given: User is offline
- When: User initiates refund on cached transaction
- Then: Operation is added to sync queue
- And: UI shows "Queued for sync" status
- And: User can view queue status
- And: Operation executes when connection restored
UC-OFFLINE-004: Sync on Reconnect
- Given: User has 3 queued operations while offline
- When: Network connection is restored
- Then: Sync queue processes operations in order
- And: Each operation shows progress (uploading, success, failed)
- And: Failed operations can be retried manually
- And: Conflicts are flagged for user resolution
UC-OFFLINE-005: Conflict Resolution
- Given: User edited transaction metadata offline
- When: User reconnects and server shows different data
- Then: System detects conflict (server version differs from local)
- And: User is presented with both versions
- And: User chooses which version to keep
- And: Conflict is resolved and sync continues
- Multi-step forms (e.g., payment method addition: type → details → verification)
- Real-time field-level validation
- Async validation (check email uniqueness, verify card with gateway)
- Form state management (pristine, touched, dirty, submitted)
- Error messaging with inline and summary-level feedback
- Submit button disabled until valid
- Draft save (save-as-you-go)
- Undo / reset form
UC-FORM-001: Field Validation
- Given: User is filling payout form
- When: User enters invalid email format in email field
- Then: On blur, field shows inline error: "Please enter a valid email"
- And: Field is highlighted in red
- And: Submit button remains disabled
UC-FORM-002: Async Validation
- Given: User enters email in "Invite Team Member" form
- When: User stops typing for 500ms (debounce)
- Then: API call checks if email already exists
- And: If duplicate, shows error: "This email is already registered"
- And: Loading spinner shown while checking
- And: Field is not validated again unless user changes value
UC-FORM-003: Multi-step Form
- Given: User is adding new payment method
- When: User completes Step 1 (select type: Card)
- Then: User is navigated to Step 2 (enter card details)
- And: Progress indicator shows "Step 2 of 3"
- And: User can click "Back" to return to Step 1
- And: User can click "Next" only when current step is valid
UC-FORM-004: Form Submission
- Given: User has completed all form fields with valid data
- When: User clicks "Submit"
- Then: Submit button shows loading state
- And: Form data is sent to server
- If successful: Success message shown, form resets, user redirected
- If failed: Error message shown, form retains user input, submit button re-enabled
UC-FORM-005: Draft Auto-save
- Given: User is filling complex form (5+ fields)
- When: User types in any field
- Then: After 2 seconds of inactivity, form is auto-saved to local storage
- And: "Draft saved" indicator shown
- And: If user refreshes page, draft is restored
- And: Draft is cleared after successful submission
- Network error detection and user feedback
- Automatic retry with exponential backoff
- Circuit breaker pattern for external APIs
- User-friendly error messages (no stack traces)
- Graceful degradation (show cached data, disable features)
- Detailed error logs for debugging
- Recovery recommendations in UI
UC-ERROR-001: Network Timeout
- Given: User is loading transaction list
- When: API request times out after 30 seconds
- Then: User sees error: "Unable to load data. Please check your connection."
- And: Retry button is displayed
- And: If cached data exists, it's shown with "Last updated: 10 minutes ago"
- And: Error is logged with correlation ID
UC-ERROR-002: Automatic Retry
- Given: User submits refund request
- When: Server returns 503 Service Unavailable
- Then: Client automatically retries with exponential backoff (1s, 2s, 4s, 8s)
- And: If all retries fail, user sees error message
- And: User can manually retry
- And: Request is idempotent (duplicate requests don't cause double refund)
UC-ERROR-003: Circuit Breaker
- Given: Payment gateway has 5 consecutive failures
- When: 6th request is made to gateway
- Then: Circuit breaker is "open" - request fails immediately
- And: No actual API call is made to gateway
- After 60 seconds: Circuit breaker enters "half-open" state
- And: Test request is sent - if successful, breaker closes; if failed, re-opens
UC-ERROR-004: Validation Error
- Given: User submits form with invalid data
- When: Server returns 400 Bad Request with validation errors
- Then: Errors are displayed inline on each affected field
- And: Error summary is shown at top of form: "Please fix 2 errors"
- And: Form scrolls to first error field
- And: Submit button remains enabled for retry
UC-ERROR-005: Authentication Error
- Given: User has active session
- When: API returns 401 Unauthorized
- Then: Client attempts to refresh token automatically
- And: If refresh succeeds, original request is retried
- If refresh fails: User is logged out and redirected to login
- And: User message: "Your session has expired. Please log in again."
- Export transaction list to CSV
- Generate PDF reports (daily/weekly/monthly summaries)
- Schedule recurring reports
- Reconciliation reports
- Custom date ranges
- Audit logs
- Report templates and customization
UC-REPORT-001: CSV Export
- Given: User has filtered transactions (last 30 days, 500 results)
- When: User clicks "Export to CSV"
- Then: CSV file is generated with columns:
- Transaction ID, Date, Amount, Currency, Status, Customer Email, Payment Method
- And: File is downloaded with filename: transactions_2024-04-10.csv
- And: Date format: ISO 8601 (2024-04-10T14:30:00Z)
- And: Amount format: 2 decimal places, no currency symbol (123.45)
UC-REPORT-002: Large Export Queue
- Given: User requests export of 1,000,000 transactions
- When: User clicks "Export"
- Then: User sees message: "Your export is being prepared. We'll email you when ready."
- And: Export job is queued in background
- And: CSV is generated and uploaded to temporary storage
- And: User receives email with download link (valid for 24 hours)
- And: Download link expires after 24 hours
UC-REPORT-003: PDF Report
- Given: User generates monthly revenue report
- When: Report is generated
- Then: PDF includes:
- Merchant name and logo
- Report period (e.g., "March 2024")
- Summary table (total transactions, total revenue, average order value)
- Chart showing revenue trend
- Breakdown by payment method
- Footer with generated timestamp
- And: PDF is formatted for A4 paper
UC-REPORT-004: Scheduled Report
- Given: User wants weekly summary every Monday
- When: User configures scheduled report
- Then: User specifies:
- Report type (e.g., "Weekly Revenue Summary")
- Recurrence (weekly)
- Day of week (Monday)
- Time (9:00 AM)
- Recipients (email addresses)
- And: System sends PDF to all recipients at scheduled time
- And: User can pause, modify, or cancel schedule
UC-REPORT-005: Reconciliation Report
- Given: Finance officer needs to verify settlement
- When: User generates reconciliation report for date range
- Then: Report includes:
- Total transactions per status
- Total amount settled vs. expected
- Discrepancies with explanations
- Payout details (amount, bank account, status)
- Fees and adjustments
- And: Report can be exported to CSV for accounting system
- Upload dispute documents (receipts, screenshots)
- File upload progress tracking
- File validation (format, size)
- Download/preview uploaded files
- Delete files with audit log
- Virus scanning
- Secure storage (encrypted at rest)
UC-FILE-001: Upload Document
- Given: User is managing a dispute
- When: User clicks "Upload Evidence" and selects PDF file
- Then: Client validates:
- File type (allowed: PDF, JPG, PNG, maximum 10MB)
- File size (≤ 10MB)
- And: Progress bar shows upload percentage
- And: File is scanned for viruses
- And: If clean, file is stored securely
- And: File is linked to dispute record
UC-FILE-002: Upload Failure
- Given: User is uploading file
- When: Network connection fails during upload
- Then: Upload pauses and shows "Upload paused"
- And: User can click "Resume" when connection restored
- And: If resume not supported, user can retry upload
- And: Partial uploads are cleaned up after 24 hours
UC-FILE-003: Preview File
- Given: User has uploaded image file (JPG, PNG)
- When: User clicks "Preview"
- Then: File opens in modal/preview window
- And: User can zoom and pan image
- And: Download button is available
- And: For PDF files, first page is shown as thumbnail
UC-FILE-004: Delete File
- Given: User wants to remove uploaded document
- When: User clicks "Delete"
- Then: Confirmation dialog: "This will permanently delete the file. Continue?"
- If confirmed: File is soft-deleted (marked for deletion)
- And: File is permanently deleted after 30-day retention period
- And: Audit log records deletion (user_id, file_id, timestamp)
- URL-based navigation (deep links shareable)
- Preserve state in URL (filters, pagination, tabs)
- Back/forward browser support
- Breadcrumb navigation
- Mobile bottom tab navigation
- Navigation guards (unsaved changes warning)
UC-NAV-001: Shareable Filtered List
- Given: User has filtered transactions (status: completed, date: last 7 days)
- When: User copies URL and sends to colleague
- Then: Colleague opens URL and sees identical filters applied
- And: URL encodes all filter parameters:
/transactions?status=completed&date_from=2024-04-03&date_to=2024-04-10 - And: Filters can be cleared with "Clear All" button
UC-NAV-002: Browser Navigation
- Given: User navigates: Dashboard → Transactions → Transaction Detail
- When: User clicks browser back button
- Then: User returns to Transactions page
- And: Previous filter state is preserved
- And: User can click forward to return to Transaction Detail
- And: Navigation history is accurate for browser history API
UC-NAV-003: Breadcrumb Navigation
- Given: User is viewing transaction detail page
- When: Breadcrumb is displayed at top of page
- Then: Breadcrumb shows: Home > Transactions > TX-12345
- And: Each breadcrumb segment is clickable
- And: Current page is not clickable
- And: Breadcrumb updates on navigation
UC-NAV-004: Unsaved Changes Warning
- Given: User has edited form but not submitted
- When: User attempts to navigate away (click link, back button)
- Then: Browser dialog: "You have unsaved changes. Leave this page?"
- And: If user clicks "Stay", navigation is cancelled
- **If user clicks "Leave", navigation continues and changes are lost
- In-app toast notifications
- Push notifications (mobile)
- Email notifications for critical events
- Webhook for external integrations
- Notification preferences per user
- Notification history and read status
UC-NOTIF-001: In-App Toast
- Given: User is viewing dashboard
- When: Transaction status changes (e.g., refund completed)
- Then: Toast notification appears in corner:
- Title: "Refund Completed"
- Message: "Refund for $50.00 has been processed"
- Action button: "View Transaction"
- And: Toast auto-dismisses after 5 seconds
- And: User can manually dismiss
- And: Toast queue shows multiple notifications
UC-NOTIF-002: Push Notification
- Given: User has mobile app installed and notifications enabled
- When: Large dispute is filed (> $1,000)
- Then: Push notification sent to user's device
- And: Notification shows:
- Title: "High-Value Dispute Filed"
- Message: "Dispute for $1,500.00 requires attention"
- And: Tapping notification opens app to dispute detail
- And: Notification is logged in notification history
UC-NOTIF-003: Email Notification
- Given: User has enabled email notifications for payout approvals
- When: Payout requires approval
- Then: Email is sent to approver:
- Subject: "Payout Approval Required: $5,000.00"
- Body: Includes payout details, approve/reject buttons
- Buttons link directly to approval page
- And: Email is sent in user's preferred language
- And: Unsubscribe link is included
UC-NOTIF-004: Webhook
- Given: Merchant has configured webhook URL
- When: Transaction is completed
- Then: POST request is sent to webhook URL
- And: Payload includes:
- event: "transaction.completed"
- data: transaction object (id, amount, status, etc.)
- timestamp
- signature (HMAC-SHA256)
- And: If webhook fails, system retries with exponential backoff
- And: After 5 failures, webhook is disabled and user is notified
UC-NOTIF-005: Notification Preferences
- Given: User wants to manage notification settings
- When: User accesses notification preferences
- Then: User can configure per notification type:
- In-app toast (on/off)
- Push notification (on/off)
- Email (immediate, daily digest, weekly digest, off)
- Webhook (enabled/disabled)
- And: User can set quiet hours (e.g., no notifications 10 PM - 8 AM)
Supported Gateways
- Stripe (Primary)
- Features: Cards, Apple Pay, Google Pay, SEPA
- APIs: Payment Intents, Charges, Refunds, Payouts
- Webhooks: Payment success/failure, dispute creation
- Adyen (Secondary)
- Features: Global payment methods, 3D Secure, recurring payments
- APIs: Payments, Modifications, Payouts
- PayPal (Optional)
- Features: PayPal balance, cards, bank accounts
- APIs: Orders, Payments, Refunds
Integration Requirements
- Tokenization - never store raw card data
- 3D Secure authentication for card payments
- Webhook signature verification
- Idempotency for all API calls
- Fallback between gateways (if primary fails)
- SendGrid (Primary)
- Transactional emails, templates, analytics
- Rate limit: 300 emails/minute
- AWS SES (Backup)
- High volume, cost-effective
- SPF/DKIM/DMARC authentication
Push Notifications
- Firebase Cloud Messaging (Android)
- Apple Push Notification Service (iOS)
- OneSignal (Cross-platform alternative)
SMS (Optional)
- Twilio
- Critical alerts (e.g., large disputes)
- Verification codes for MFA
File Storage
- AWS S3 (Primary)
- Encrypted at rest (AES-256)
- Lifecycle policies for old files
- Multi-region replication
- Cloudflare R2 (Alternative)
- No egress fees
- S3-compatible API
CDN
- Cloudflare
- Global edge caching
- DDoS protection
- Image optimization
- AWS CloudFront (Alternative)
Application Monitoring
- Datadog (Primary)
- APM, log management, error tracking
- Real-time metrics and alerts
- Sentry (Error Tracking)
- JavaScript errors, stack traces
- User context and breadcrumbs
Business Analytics
- Google Analytics 4
- User behavior, conversion funnels
- Mixpanel or Amplitude
- Event tracking, user cohorts
Transaction Search
- Elasticsearch
- Full-text search across transactions
- Faceted filtering (date ranges, amounts, status)
- Aggregations and analytics queries
Alternative: Opensearch (AWS-compatible)
Live Chat
- Intercom or Zendesk
- In-app chat widget
- Ticket management
Help Center
- Zendesk Guide or GitBook
- Documentation, FAQs
- Searchable knowledge base
Overview Idempotency is critical in payment systems to prevent double-charging, double-refunds, or duplicate operations during network retries and client retries. An idempotent request can be called multiple times with the same result without causing side effects.
Implementation Requirements
- Generate Idempotency Key: Client MUST generate a unique UUID v4 for each state-changing operation
- Include in Headers: Every POST/PUT/PATCH request that mutates state MUST include
X-Idempotency-Keyheader - Key Reuse: If a request fails, client MUST retry with the same idempotency key
- Key Expiration: Client should NOT reuse idempotency keys after 48 hours
- Store Keys: Server MUST store idempotency keys with associated responses for 48 hours
- Check Before Processing: For each request, check if idempotency key exists in cache (Redis)
- Return Cached Response: If key exists, return the stored response without re-processing
- Key Storage Format: Store as
idempotency:{key}→{response, status_code, headers, timestamp}
sequenceDiagram
participant Client
participant Server
participant Redis
participant Gateway
Client->>Server: POST /refunds<br/>X-Idempotency-Key: uuid-123
Server->>Redis: GET idempotency:uuid-123
Redis-->>Server: null (not found)
Server->>Gateway: Process refund
Gateway-->>Server: Success response
Server->>Redis: SET idempotency:uuid-123<br/>{response, timestamp}
Server-->>Client: 200 OK + response
Note over Client,Redis: Client retries due to timeout
Client->>Server: POST /refunds<br/>X-Idempotency-Key: uuid-123
Server->>Redis: GET idempotency:uuid-123
Redis-->>Server: Cached response found
Server-->>Client: 200 OK + Cached response<br/>(No duplicate refund processed)
The following endpoints MUST require idempotency keys:
| Endpoint | Method | Reason |
|---|---|---|
/transactions |
POST | Create new transaction |
/transactions/:id/refund |
POST | Create refund |
/payouts |
POST | Create payout request |
/payment-methods |
POST | Add payment method |
/disputes/:id/evidence |
POST | Submit dispute evidence |
- TTL: 48 hours (172,800 seconds)
- Cleanup: Redis automatically expires keys after TTL
- Storage Backend: Redis recommended for performance
- Backup: Consider periodic backup to persistent storage for audit
- Invalid Key Format: Return
400 Bad Requestwith message "Invalid idempotency key format" - Key Already Used with Different Request: Return
409 Conflictwith message "Idempotency key already used for different request body" - Key Expired: Treat as new request, generate new response
Overview Transactions progress through well-defined states. Invalid state transitions must be prevented at the application level.
| State | Type | Description |
|---|---|---|
pending |
Transient | Transaction created, awaiting processing |
processing |
Transient | Being processed by payment gateway |
completed |
Transient | Successfully captured, not yet settled |
settled |
Transient | Funds settled by gateway (available for payout) |
failed |
Final | Transaction could not be processed |
refunded |
Final | Full or partial refund processed |
disputed |
Transient | Chargeback or dispute filed |
dispute_won |
Final | Dispute resolved in merchant's favor |
dispute_lost |
Final | Dispute resolved against merchant |
cancelled |
Final | Transaction cancelled before processing |
stateDiagram-v2
[*] --> pending: Create Transaction
pending --> processing: Submit to Gateway
processing --> completed: Capture Success
processing --> failed: Capture Failed
processing --> disputed: Chargeback Filed
completed --> settled: Settlement Complete
completed --> processing: Re-process (retry)
completed --> refunded: Refund Initiated
completed --> disputed: Chargeback Filed
settled --> refunded: Refund Initiated
settled --> disputed: Chargeback Filed
refunded --> [*]
failed --> [*]
failed --> processing: Retry (optional)
disputed --> dispute_won: Resolution: Won
disputed --> dispute_lost: Resolution: Lost
dispute_won --> settled: Settlement Reinstated
dispute_lost --> refunded: Funds Reversed
cancelled --> [*]
pending --> cancelled: Cancel Before Submit
| From State | To State | Pre-conditions |
|---|---|---|
pending |
processing |
Valid payment method, amount > 0 |
pending |
cancelled |
Before gateway submission |
processing |
completed |
Gateway capture successful |
processing |
failed |
Gateway capture failed |
processing |
disputed |
Chargeback received during processing |
completed |
settled |
Gateway settlement webhook received |
completed |
refunded |
Refund processed successfully |
completed |
disputed |
Chargeback received |
completed |
processing |
Manual retry by authorized user |
settled |
refunded |
Refund processed successfully |
settled |
disputed |
Chargeback received |
disputed |
dispute_won |
Evidence accepted, dispute resolved |
disputed |
dispute_lost |
Evidence rejected, funds reversed |
dispute_won |
settled |
Settlement reinstated by gateway |
dispute_lost |
refunded |
Funds automatically reversed |
| Attempted Transition | Reason |
|---|---|
failed → completed |
Failed transactions cannot auto-complete |
failed → refunded |
Cannot refund failed transactions |
pending → settled |
Must complete before settlement |
pending → refunded |
Cannot refund unprocessed transactions |
disputed → settled |
Must resolve dispute first |
dispute_lost → dispute_won |
Cannot reverse dispute outcome |
refunded → completed |
Cannot un-refund |
Database Constraints
-- Ensure state transition is valid using trigger or application logic
CREATE TRIGGER validate_transaction_state_transition
BEFORE UPDATE ON transactions
FOR EACH ROW
EXECUTE FUNCTION check_valid_state_transition(OLD.status, NEW.status);Application-Level Validation
const VALID_TRANSITIONS = {
pending: ['processing', 'cancelled'],
processing: ['completed', 'failed', 'disputed'],
completed: ['settled', 'refunded', 'disputed', 'processing'],
settled: ['refunded', 'disputed'],
failed: ['processing'], // Optional retry
disputed: ['dispute_won', 'dispute_lost'],
dispute_won: ['settled'],
dispute_lost: ['refunded'],
refunded: [], // Final state
cancelled: [] // Final state
};
function canTransition(from: string, to: string): boolean {
return VALID_TRANSITIONS[from]?.includes(to) ?? false;
}Settlement Timing
- Settlement typically occurs 1-3 business days after
completed settled_attimestamp marks when funds are available for payoutcompleted_atmarks when capture was successful- Finance officers use
settled_atfor reconciliation
Dispute Timing
- Disputes can only be filed on
completedorsettledtransactions - Dispute response deadline is typically 7-14 days from filing
- Loss of dispute triggers automatic refund
Refund Rules
- Can only refund
completedorsettledtransactions - Partial refunds supported (amount < original transaction)
- Multiple partial refunds allowed until fully refunded
refunded_amounttracks total refundedrefundable_amount=amount-refunded_amount
Problem Statement
JavaScript/TypeScript number type uses 64-bit floating-point representation, which can lead to precision errors in financial calculations:
// Example of floating-point errors
0.1 + 0.2 !== 0.3 // true: 0.30000000000000004
0.3 - 0.1 !== 0.2 // true: 0.19999999999999998In payment systems, even minor precision errors can cause:
- Balance discrepancies
- Reconciliation failures
- Regulatory compliance issues
Solution: String-Based Representation
All monetary values MUST use string type in API requests and responses:
// ❌ BAD: Using number type
interface Transaction {
amount: number; // Prone to precision errors
fee: number;
}
// ✅ GOOD: Using string type
interface Transaction {
amount: string; // Always represents cents as string
fee: string;
}- Currency: ISO 4217 code (e.g., "USD", "EUR")
- Amount Format: Integer string representing the smallest currency unit (cents)
- USD: "100" = $1.00, "1050" = $10.50
- JPY: "100" = ¥100 (no decimal places)
- BHD: "100" = 100 fils (3 decimal places for some currencies)
Use locale-aware formatting for display:
function formatCurrency(amount: string, currency: string): string {
const cents = BigInt(amount);
const dollars = Number(cents) / getCurrencyDivisor(currency);
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: currency
}).format(dollars);
}
function getCurrencyDivisor(currency: string): number {
const decimalPlaces = {
'USD': 2, 'EUR': 2, 'GBP': 2,
'JPY': 0, 'KRW': 0,
'BHD': 3, 'KWD': 3, 'OMR': 3
};
return Math.pow(10, decimalPlaces[currency] || 2);
}Use decimal math libraries for calculations:
Recommended Libraries
- Node.js:
decimal.js,big.js,bignumber.js - Database: Use DECIMAL/NUMERIC types with sufficient precision
import Decimal from 'decimal.js';
class FinancialCalculator {
static calculateSubtotal(items: {price: string, quantity: number}[]): string {
return items.reduce((sum, item) => {
const price = new Decimal(item.price);
const qty = new Decimal(item.quantity);
return sum.plus(price.times(qty));
}, new Decimal(0)).toString();
}
static calculateFee(amount: string, feeRate: string): string {
const amountDec = new Decimal(amount);
const rateDec = new Decimal(feeRate);
return amountDec.times(rateDec).toString();
}
static calculateNetAmount(amount: string, fee: string): string {
return new Decimal(amount).minus(new Decimal(fee)).toString();
}
}
// Example usage
const amount = "10000"; // $100.00
const feeRate = "0.029"; // 2.9%
const fee = FinancialCalculator.calculateFee(amount, feeRate); // "290"
const net = FinancialCalculator.calculateNetAmount(amount, fee); // "9710"-- ✅ GOOD: Using DECIMAL type
CREATE TABLE transactions (
id UUID PRIMARY KEY,
amount DECIMAL(19, 4) NOT NULL, -- 19 digits total, 4 decimal places
currency VARCHAR(3) NOT NULL,
fee_amount DECIMAL(19, 4) NOT NULL,
net_amount DECIMAL(19, 4) NOT NULL,
-- ...
);
-- ❌ BAD: Using FLOAT or DOUBLE
CREATE TABLE transactions (
amount FLOAT NOT NULL, -- Prone to precision errors
-- ...
);| Operation | Rule | Example |
|---|---|---|
| Addition | Decimal(A) + Decimal(B) | "100" + "50" = "150" |
| Subtraction | Decimal(A) - Decimal(B) | "100" - "30" = "70" |
| Multiplication | Decimal(A) × Decimal(B) | "100" × "0.029" = "2.9" |
| Division | Decimal(A) ÷ Decimal(B) | "100" ÷ "3" = "33.33..." (round to 2 decimals) |
| Rounding | Always round half-up | 2.345 → 2.35, 2.344 → 2.34 |
| Comparison | Exact string comparison | "100" === "100" |
| Currency | Code | Decimal Places | Example (100) |
|---|---|---|---|
| US Dollar | USD | 2 | $1.00 |
| Euro | EUR | 2 | €1.00 |
| Japanese Yen | JPY | 0 | ¥100 |
| Bahraini Dinar | BHD | 3 | BD 0.100 |
| Kuwaiti Dinar | KWD | 3 | KWD 0.100 |
| Omani Rial | OMR | 3 | RO 0.100 |
function validateMonetaryAmount(amount: string, currency: string): boolean {
// Must be numeric string
if (!/^\d+$/.test(amount)) {
return false;
}
// Must be non-negative
if (parseInt(amount, 10) < 0) {
return false;
}
// Check precision based on currency
const divisor = getCurrencyDivisor(currency);
const amountNum = BigInt(amount);
// For currencies with decimals, ensure valid precision
if (divisor > 1) {
// Amount should be divisible by the smallest unit
// This check depends on your specific requirements
}
return true;
}- All API monetary fields use
stringtype - Database uses DECIMAL/NUMERIC types
- Server-side calculations use decimal.js or equivalent
- Client-side display uses Intl.NumberFormat
- Currency decimal places configured correctly
- Rounding rules documented and consistent
- All monetary calculations use Big/Decimal types
Overview The platform uses a multi-tenant architecture where multiple merchants share the same infrastructure while maintaining complete data isolation.
Architecture Decision
┌─────────────────────────────────────────────────────────────┐
│ Single Database Instance │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ Shared Schema (public schema) │ │
│ │ ┌─────────────┬─────────────┬─────────────┬────────┐ │ │
│ │ │ merchants │ users │ transactions│ payouts │ │ │
│ │ └─────────────┴─────────────┴─────────────┴────────┘ │ │
│ │ All tables include merchant_id column │ │
│ └───────────────────────────────────────────────────────┘ │
│ │
│ Row-Level Security (RLS) Policies enforce isolation │
└─────────────────────────────────────────────────────────────┘
Advantages
- Cost Efficiency: Shared infrastructure reduces operational costs
- Rapid Onboarding: New merchants provisioned instantly
- Resource Utilization: Better resource pooling and utilization
- Maintenance: Single schema to upgrade and maintain
- Scalability Path: Can migrate high-volume merchants to isolated schema later
Trade-offs
- Performance Impact: Requires merchant_id indexing on all queries
- Noisy Neighbor: One merchant's load can affect others (mitigated with rate limiting)
- Security Complexity: RLS policies must be thoroughly tested
Database Schema Pattern
-- All tenant tables follow this pattern
CREATE TABLE transactions (
id UUID PRIMARY KEY,
merchant_id UUID NOT NULL,
-- ... other columns
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
-- Composite index for efficient tenant queries
INDEX idx_transactions_merchant_created (merchant_id, created_at)
);
-- Foreign keys enforce referential integrity
ALTER TABLE transactions
ADD CONSTRAINT fk_transactions_merchant
FOREIGN KEY (merchant_id) REFERENCES merchants(id);Row-Level Security (RLS) Policies
-- Enable RLS on all tenant tables
ALTER TABLE transactions ENABLE ROW LEVEL SECURITY;
-- Policy: Users can only see their own merchant's data
CREATE POLICY tenant_isolation_transactions ON transactions
FOR ALL
USING (merchant_id = current_setting('app.current_merchant_id')::UUID);
-- Similar policies for all tables
ALTER TABLE payouts ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation_payouts ON payouts
FOR ALL
USING (merchant_id = current_setting('app.current_merchant_id')::UUID);Application Context
// Middleware to set merchant context
async function setMerchantContext(req: Request, res: Response, next: NextFunction) {
const user = await getUserFromToken(req.headers.authorization);
const merchantId = user.merchant_id;
// Set merchant context for database session
await db.query('SET app.current_merchant_id = $1', [merchantId]);
req.merchantId = merchantId;
next();
}| Guarantee | Implementation | Verification |
|---|---|---|
| Data Isolation | RLS policies on all tables | Test: User A cannot query User B's data |
| Session Isolation | Merchant context per request | Test: Concurrent requests don't leak context |
| Resource Isolation | Rate limiting per merchant | Test: One merchant cannot exhaust resources |
| Backup Isolation | Backup/restore by merchant_id | Test: Can restore single merchant's data |
For high-volume merchants (>1M transactions/month):
Migration Strategy
graph LR
A[Shared Schema] -->|Volume Threshold Reached| B{Assess Migration}
B -->|Complex Queries| C[Create Dedicated Schema]
B -->|Simple Queries| D[Add Read Replica]
C --> E[Replicate Data]
E --> F[Update Routing Logic]
F --> G[Point-of-Switch]
G --> H[Isolated Schema Active]
Migration Steps
- Assessment: Evaluate query complexity and performance
- Provision: Create isolated schema/database for merchant
- Replicate: Copy data from shared to isolated schema
- Route: Update application routing based on merchant_id
- Cutover: Perform zero-downtime cutover
- Cleanup: Remove data from shared schema after verification
Routing Logic
function getDatabaseConnection(merchantId: string): Database {
const isolatedMerchants = new Set(['high-volume-merchant-1', 'high-volume-merchant-2']);
if (isolatedMerchants.has(merchantId)) {
return isolatedDatabases[merchantId];
}
return sharedDatabase;
}For large shared tables, use partitioning by merchant_id:
-- Partition transactions by merchant_id range
CREATE TABLE transactions (
id UUID,
merchant_id UUID,
-- ... other columns
) PARTITION BY HASH (merchant_id);
-- Create partitions
CREATE TABLE transactions_p0 PARTITION OF transactions
FOR VALUES WITH (MODULUS 4, REMAINDER 0);
CREATE TABLE transactions_p1 PARTITION OF transactions
FOR VALUES WITH (MODULUS 4, REMAINDER 1);
CREATE TABLE transactions_p2 PARTITION OF transactions
FOR VALUES WITH (MODULUS 4, REMAINDER 2);
CREATE TABLE transactions_p3 PARTITION OF transactions
FOR VALUES WITH (MODULUS 4, REMAINDER 3);Tenant Enumeration Prevention
// Prevent brute-force enumeration of merchant IDs
app.use(rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // Limit requests per IP
keyGenerator: (req) => req.ip
}));Data Exfiltration Prevention
- Audit logs track all queries with merchant_id
- Alert on unusual query patterns
- Limit bulk export operations
Per-Merchant Metrics
- Query latency
- Storage usage
- API request rate
- Error rate
Alerting
- High query latency for specific merchant
- Unusual storage growth
- Rate limit violations
Overview Webhooks enable real-time notifications to external systems. Given their critical role in payment processing, robust security and seamless secret rotation are essential.
sequenceDiagram
participant Platform as Payment Platform
participant Redis as Key Store
participant Merchant as Merchant Webhook Endpoint
participant DB as Database
Note over Platform,DB: Webhook Configuration
Platform->>DB: Store webhook URL + secrets
Platform->>Redis: Cache active secrets
Note over Platform,Merchant: Webhook Delivery
Platform->>Platform: Generate signature<br/>HMAC-SHA256(secret, payload)
Platform->>Merchant: POST webhook<br/>+ X-Webhook-Signature-256
Merchant->>Merchant: Verify signature<br/>HMAC-SHA256(secret, payload)
alt Signature Valid
Merchant-->>Platform: 200 OK
else Signature Invalid
Merchant-->>Platform: 401 Unauthorized
Platform->>Platform: Log security event
end
Rolling Secrets Approach
To avoid webhook downtime during secret rotation, support two active secrets simultaneously:
stateDiagram-v2
[*] --> Active_v1: Initial Secret
Active_v1 --> Transition: Rotation Initiated
Transition --> Active_v1_v2: Add New Secret
Active_v1_v2 --> Active_v2: Remove Old Secret
Active_v2 --> Transition: Next Rotation
Secret Rotation Workflow
- Generate New Secret: Create new UUID or cryptographically random string
- Add to Active Secrets: Add new secret alongside existing secret
- Notify Merchant: Send alert about secret rotation
- Update Merchant Systems: Merchant updates their verification logic
- Verification Period: Wait for configured period (default: 24 hours)
- Remove Old Secret: Remove old secret from active secrets
- Audit Log: Log rotation event
Database Schema
CREATE TABLE webhook_configs (
id UUID PRIMARY KEY,
merchant_id UUID NOT NULL,
url VARCHAR(2048) NOT NULL,
secret_current VARCHAR(255) NOT NULL,
secret_previous VARCHAR(255), -- During rotation
secret_rotation_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT fk_webhook_merchant
FOREIGN KEY (merchant_id) REFERENCES merchants(id)
);
-- Index for lookups
CREATE INDEX idx_webhook_merchant
ON webhook_configs(merchant_id);Webhook Delivery with Signature Verification
interface WebhookPayload {
event: string;
data: any;
timestamp: string;
}
class WebhookService {
private secrets: Map<string, {current: string, previous?: string}> = new Map();
async loadSecrets(merchantId: string): Promise<void> {
const config = await db.query(
'SELECT secret_current, secret_previous FROM webhook_configs WHERE merchant_id = $1',
[merchantId]
);
this.secrets.set(merchantId, {
current: config.secret_current,
previous: config.secret_previous
});
}
async sendWebhook(merchantId: string, payload: WebhookPayload): Promise<boolean> {
const secrets = this.secrets.get(merchantId);
if (!secrets) throw new Error('Webhook not configured');
const config = await db.query('SELECT url FROM webhook_configs WHERE merchant_id = $1', [merchantId]);
const url = config.url;
// Try with current secret first
const signature = this.generateSignature(secrets.current, payload);
const success = await this.deliver(url, payload, signature);
if (!success && secrets.previous) {
// Fallback to previous secret (during rotation)
const oldSignature = this.generateSignature(secrets.previous, payload);
return await this.deliver(url, payload, oldSignature);
}
return success;
}
private generateSignature(secret: string, payload: WebhookPayload): string {
const payloadString = JSON.stringify(payload);
return crypto
.createHmac('sha256', secret)
.update(payloadString)
.digest('hex');
}
private async deliver(url: string, payload: WebhookPayload, signature: string): Promise<boolean> {
try {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Webhook-Signature-256': signature,
'X-Webhook-Timestamp': payload.timestamp,
'X-Webhook-Id': crypto.randomUUID()
},
body: JSON.stringify(payload)
});
return response.ok;
} catch (error) {
// Log and return false for retry
logger.error('Webhook delivery failed', { url, error });
return false;
}
}
}Merchant-Side Verification (Example)
// Merchant's webhook handler
function verifyWebhookSignature(
payload: string,
signature: string,
secret: string
): boolean {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
// Use constant-time comparison to prevent timing attacks
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
// Handle both secrets during rotation
app.post('/webhook', (req, res) => {
const payload = JSON.stringify(req.body);
const signature = req.headers['x-webhook-signature-256'];
// Try current secret first
if (verifyWebhookSignature(payload, signature, CURRENT_SECRET)) {
// Process webhook
return res.status(200).send('OK');
}
// Try previous secret (during rotation)
if (PREVIOUS_SECRET && verifyWebhookSignature(payload, signature, PREVIOUS_SECRET)) {
// Process webhook
return res.status(200).send('OK');
}
// Invalid signature
return res.status(401).send('Invalid signature');
});Initiate Rotation
POST /api/v1/webhooks/rotate-secret
Authorization: Bearer {token}
Content-Type: application/json
{
"webhook_id": "webhook_123",
"rotation_delay_hours": 24
}Response
{
"webhook_id": "webhook_123",
"new_secret": "whsec_abc123...",
"previous_secret": "whsec_xyz789...",
"rotation_complete_at": "2024-04-11T14:30:00Z",
"message": "New secret generated. Update your verification code before rotation completes."
}Check Rotation Status
GET /api/v1/webhooks/{webhook_id}/rotation-status
Authorization: Bearer {token}Response
{
"webhook_id": "webhook_123",
"status": "in_progress",
"secrets": {
"current": "whsec_abc123...",
"previous": "whsec_xyz789..."
},
"rotation_started_at": "2024-04-10T14:30:00Z",
"rotation_complete_at": "2024-04-11T14:30:00Z",
"message": "Old secret will be removed in 12 hours"
}Secret Generation
function generateWebhookSecret(): string {
// Generate 32-byte cryptographically secure random string
const bytes = crypto.randomBytes(32);
return `whsec_${bytes.toString('hex')}`;
}Secret Storage
- Store secrets hashed in database (bcrypt/scrypt) if needed for audit
- Store secrets encrypted at rest (AWS KMS, vault)
- Never log secrets or include in error messages
Timestamp Protection
// Prevent replay attacks
function validateTimestamp(timestamp: string): boolean {
const webhookTime = new Date(timestamp).getTime();
const currentTime = Date.now();
const maxAge = 5 * 60 * 1000; // 5 minutes
return Math.abs(currentTime - webhookTime) < maxAge;
}Rate Limiting
// Prevent webhook abuse
const webhookRateLimit = rateLimit({
windowMs: 60 * 1000, // 1 minute
max: 100, // 100 webhooks per minute per merchant
keyGenerator: (req) => req.merchantId
});Metrics to Track
- Webhook delivery success rate
- Signature verification failures
- Rotation completion rate
- Secret age (alert if > 90 days)
Alerts
-
5% signature failure rate → Possible secret compromise
- Failed webhook delivery → Retry or manual intervention
- Secret not rotated in 90 days → Security advisory
Global State
- Authentication state (user, token, permissions)
- Tenant context (merchant_id, settings)
- Application settings (theme, language, notifications)
Local State
- Form data (pristine, touched, dirty, submitted)
- UI flags (loading, errors, modals open)
- Pagination and sort state
Cache State
- Transaction lists (keyed by filters)
- Payment methods (refreshed on mutation)
- Balances (stale-while-revalidate)
Sync State
- Pending operations queue
- Connection status (online/offline/syncing)
- Conflict flags
Unidirectional Flow
User Action → Dispatch Action → Reducer → New State → UI Re-render
Optimistic Updates
- User initiates action
- UI updates immediately (assumes success)
- API call made in background
- If success: confirmation shown
- If failure: rollback state, show error
Rollback Strategy
- Store previous state before optimistic update
- On server error, restore previous state
- Show error message with retry option
Time-Travel Debugging
- All state changes logged with action type and payload
- DevTools allows stepping through state history
- Useful for debugging complex state transitions
RESTful Endpoints
- Standard HTTP methods (GET, POST, PUT, DELETE)
- Resource-based URLs (e.g., /transactions, /transactions/:id)
- Consistent error response format:
{ "error": { "code": "VALIDATION_ERROR", "message": "Invalid email address", "details": { "field": "email", "value": "invalid" } }, "request_id": "req_abc123" }
Versioning Strategy
- URL-based versioning:
/api/v1/transactions - Breaking changes increment version
- Non-breaking changes don't require version bump
Rate Limiting
- Per-user rate limits (e.g., 100 requests/minute)
- Per-endpoint limits (e.g., 10 exports/hour)
- Rate limit headers:
X-RateLimit-Limit,X-RateLimit-Remaining,X-RateLimit-Reset
Pagination
- Offset-based:
?page=1&per_page=50 - Cursor-based for large datasets:
?cursor=abc123&limit=50 - Response includes pagination metadata:
{ "data": [...], "meta": { "total": 500, "page": 1, "per_page": 50, "total_pages": 10, "cursor": "next_cursor_value" } }
WebSocket
- Endpoint:
wss://api.example.com/ws - Authentication via query param:
?token=jwt_token - Message format:
{ "event": "transaction.updated", "data": { "id": "tx_123", "status": "completed" }, "timestamp": "2024-04-10T14:30:00Z" }
Cache-First Strategy
- Check cache for data
- If cache hit, return cached data
- Fetch from server in background
- Update cache with fresh data
- Re-render if data changed
Stale-While-Revalidate
- Return cached data immediately (even if stale)
- Fetch fresh data in background
- Update cache when fresh data arrives
- Re-render with fresh data
Cache Invalidation
- Time-based: Invalidate after X minutes
- Event-based: Invalidate on mutation (e.g., create/update/delete)
- Tag-based: Cache tagged by entity, invalidate by tag
Local Storage
- User preferences (theme, language)
- Form drafts (auto-save)
- Offline queue (pending operations)
- Browser localStorage for small data (<5MB)
- IndexedDB for large datasets
Typed Error Hierarchy
class AppError extends Error {
code: string;
statusCode: number;
isRetryable: boolean;
}
class NetworkError extends AppError { /* ... */ }
class ValidationError extends AppError { /* ... */ }
class AuthenticationError extends AppError { /* ... */ }
class AuthorizationError extends AppError { /* ... */ }
class NotFoundError extends AppError { /* ... */ }Result Pattern (Either Monad)
type Result<T, E> = { success: true; data: T } | { success: false; error: E };
async function processPayment(amount: number): Promise<Result<Payment, Error>> {
try {
const payment = await createPayment(amount);
return { success: true, data: payment };
} catch (error) {
return { success: false, error };
}
}Structured Logging
- Log all errors with context:
{ "level": "error", "message": "Payment gateway timeout", "error": { "code": "GATEWAY_TIMEOUT", "stack": "..." }, "context": { "user_id": "user_123", "transaction_id": "tx_456", "request_id": "req_abc123" }, "timestamp": "2024-04-10T14:30:00Z" }
Error Boundaries
- UI components catch errors and show fallback
- Prevent entire app from crashing
- Allow user to report error
Merchant (1) ----< (N) User
Merchant (1) ----< (N) Transaction
Merchant (1) ----< (N) PaymentMethod
Merchant (1) ----< (N) Payout
Merchant (1) ----< (N) AuditLog
Merchant (1) ----< (N) File
User (1) ----< (N) AuditLog
Transaction (1) ----< (N) Refund
Transaction (1) ----< (N) Dispute
Dispute (1) ----< (N) File
Payout (1) ----< (1) PaymentMethod
interface Merchant {
id: string; // UUID
name: string; // Business name
slug: string; // URL-friendly identifier
email: string; // Primary contact email
phone?: string; // Contact phone
status: 'active' | 'suspended' | 'pending';
kyc_status: 'verified' | 'pending' | 'rejected';
default_currency: string; // ISO 4217 (e.g., 'USD')
timezone: string; // IANA timezone (e.g., 'America/New_York')
settings: MerchantSettings; // JSON object
metadata: Record<string, any>; // Flexible key-value store
created_at: ISO8601Timestamp;
updated_at: ISO8601Timestamp;
deleted_at?: ISO8601Timestamp; // Soft delete
}
interface MerchantSettings {
require_mfa: boolean;
payout_approval_required: boolean;
minimum_payout_amount: string; // String representing cents (e.g., "1000" = $10.00)
payout_currency: string;
notification_preferences: NotificationPreferences;
}interface User {
id: string; // UUID
merchant_id: string; // FK to Merchant
email: string; // Unique
name: string; // Full name
role: 'owner' | 'manager' | 'finance' | 'support';
status: 'active' | 'inactive' | 'pending';
mfa_enabled: boolean;
last_login_at?: ISO8601Timestamp;
preferences: UserPreferences;
created_at: ISO8601Timestamp;
updated_at: ISO8601Timestamp;
deleted_at?: ISO8601Timestamp;
}
interface UserPreferences {
language: string; // 'en', 'es', 'fr', etc.
timezone: string;
theme: 'light' | 'dark' | 'system';
notifications: NotificationPreferences;
}interface Transaction {
id: string; // UUID
merchant_id: string; // FK to Merchant
amount: string; // String representing cents (e.g., "10000" = $100.00)
currency: string; // ISO 4217
status: 'pending' | 'processing' | 'completed' | 'settled' | 'failed' | 'refunded' | 'disputed' | 'dispute_won' | 'dispute_lost' | 'cancelled';
payment_method_id: string; // FK to PaymentMethod
customer_email: string;
customer_name?: string;
description?: string;
order_reference?: string; // Merchant's order ID
metadata: Record<string, any>; // Merchant custom data
gateway_transaction_id?: string; // External gateway ID
gateway_response?: any; // Full gateway response
refundable_amount: string; // Calculated field: amount - refunded_amount
refunded_amount: string; // Calculated field: total of all refunds
created_at: ISO8601Timestamp;
updated_at: ISO8601Timestamp;
completed_at?: ISO8601Timestamp; // When transaction was captured
settled_at?: ISO8601Timestamp; // When funds settled (available for payout)
deleted_at?: ISO8601Timestamp;
}interface PaymentMethod {
id: string; // UUID
merchant_id: string; // FK to Merchant
type: 'card' | 'bank_account' | 'wallet';
token: string; // Gateway token (never store raw data)
display_name?: string; // User-friendly name
is_default: boolean;
details: PaymentMethodDetails;
status: 'active' | 'expired' | 'failed';
last_used_at?: ISO8601Timestamp;
expires_at?: ISO8601Timestamp; // For cards
created_at: ISO8601Timestamp;
updated_at: ISO8601Timestamp;
deleted_at?: ISO8601Timestamp;
}
interface PaymentMethodDetails {
card?: {
brand: 'visa' | 'mastercard' | 'amex' | 'discover';
last4: string; // "4242"
expiry_month: number;
expiry_year: number;
};
bank_account?: {
bank_name: string;
last4: string;
account_type: 'checking' | 'savings';
};
wallet?: {
provider: 'paypal' | 'apple_pay' | 'google_pay';
email: string;
};
}interface Payout {
id: string; // UUID
merchant_id: string; // FK to Merchant
payment_method_id: string; // FK to PaymentMethod
amount: string; // String representing cents (e.g., "10000" = $100.00)
currency: string; // ISO 4217
status: 'draft' | 'pending_approval' | 'approved' | 'processing' | 'completed' | 'failed' | 'cancelled';
requested_by: string; // FK to User
approved_by?: string; // FK to User
approved_at?: ISO8601Timestamp;
scheduled_for?: ISO8601Timestamp;
processed_at?: ISO8601Timestamp;
fee_amount: string; // String representing processing fee (e.g., "290" = $2.90)
net_amount: string; // amount - fee_amount (e.g., "9710" = $97.10)
failure_reason?: string;
gateway_payout_id?: string; // External gateway ID
metadata: Record<string, any>;
created_at: ISO8601Timestamp;
updated_at: ISO8601Timestamp;
deleted_at?: ISO8601Timestamp;
}interface Refund {
id: string; // UUID
transaction_id: string; // FK to Transaction
amount: string; // String representing cents (e.g., "5000" = $50.00)
currency: string; // ISO 4217
status: 'pending' | 'processing' | 'completed' | 'failed';
reason?: string;
requested_by: string; // FK to User
gateway_refund_id?: string; // External gateway ID
failure_reason?: string;
is_partial: boolean; // True if amount < original transaction amount
created_at: ISO8601Timestamp;
updated_at: ISO8601Timestamp;
completed_at?: ISO8601Timestamp;
}interface Dispute {
id: string; // UUID
transaction_id: string; // FK to Transaction
amount: string; // String representing cents (e.g., "15000" = $150.00)
currency: string; // ISO 4217
status: 'open' | 'under_review' | 'won' | 'lost' | 'cancelled';
reason: string; // Chargeback reason code
deadline: ISO8601Timestamp; // Response deadline
evidence_submitted: boolean;
evidence_deadline: ISO8601Timestamp;
outcome?: 'won' | 'lost';
loss_reason?: string;
created_at: ISO8601Timestamp;
updated_at: ISO8601Timestamp;
resolved_at?: ISO8601Timestamp;
}interface File {
id: string; // UUID
merchant_id: string; // FK to Merchant
entity_type: 'transaction' | 'dispute' | 'user' | 'payout';
entity_id: string; // ID of associated entity
filename: string;
mime_type: string;
size_bytes: number;
storage_path: string; // S3 key or CDN URL
uploaded_by: string; // FK to User
virus_scan_status: 'pending' | 'clean' | 'infected';
created_at: ISO8601Timestamp;
deleted_at?: ISO8601Timestamp;
}interface AuditLog {
id: string; // UUID
merchant_id: string; // FK to Merchant
user_id: string; // FK to User (or 'system')
action: string; // 'create', 'update', 'delete', 'approve', etc.
entity_type: string; // 'transaction', 'payout', etc.
entity_id: string; // ID of affected entity
changes: {
before?: Record<string, any>;
after?: Record<string, any>;
};
ip_address?: string;
user_agent?: string;
created_at: ISO8601Timestamp;
}Critical Indexes
-- Transactions
CREATE INDEX idx_transactions_merchant_status ON transactions(merchant_id, status);
CREATE INDEX idx_transactions_created_at ON transactions(created_at DESC);
CREATE INDEX idx_transactions_customer_email ON transactions(customer_email);
-- Users
CREATE UNIQUE INDEX idx_users_email ON users(email);
CREATE INDEX idx_users_merchant_role ON users(merchant_id, role);
-- Payouts
CREATE INDEX idx_payouts_merchant_status ON payouts(merchant_id, status);
CREATE INDEX idx_payouts_scheduled_for ON payouts(scheduled_for) WHERE status = 'approved';
-- Audit Logs
CREATE INDEX idx_audit_logs_merchant ON audit_logs(merchant_id, created_at DESC);
CREATE INDEX idx_audit_logs_entity ON audit_logs(entity_type, entity_id);Data Retention
- Transactions: 7 years (compliance)
- Audit logs: 7 years (compliance)
- Files: 3 years after associated entity deletion
- Export jobs: 30 days
- Notification history: 90 days
Transaction Volume
- Small merchants: 1,000 - 10,000 transactions/month
- Medium merchants: 10,000 - 100,000 transactions/month
- Large merchants: 100,000 - 1,000,000+ transactions/month
- Peak traffic: 10x normal volume during sale events
User Volume
- Average merchant: 5 - 20 users
- Large merchants: 50 - 200 users
- Concurrent users: 20% of total users
Data Growth
- Transaction: ~1 KB per record
- Audit log: ~500 bytes per record
- With 1M transactions/month: ~1 GB/month for transactions + audit logs
API Response Times
- Transaction list (50 items): < 500ms (p95)
- Transaction detail: < 200ms (p95)
- Search query: < 1s (p95)
- Export generation: < 30s for 10K rows
- WebSocket message delivery: < 100ms (p99)
Page Load Times
- Initial load: < 2s (p95)
- Route transition: < 500ms (p95)
- First Contentful Paint (FCP): < 1.5s
- Time to Interactive (TTI): < 3.5s
Database Queries
- Simple lookups (by ID): < 10ms
- Filtered queries (with indexes): < 100ms
- Complex aggregations: < 500ms
- Full-text search: < 1s
Horizontal Scaling
- Stateless API servers behind load balancer
- Database read replicas for read-heavy workloads
- Connection pooling (e.g., PgBouncer for PostgreSQL)
- Microservices architecture for future growth
Vertical Scaling
- Database server with sufficient RAM for cache
- SSD storage for fast I/O
- CPU optimization for encryption/decryption
Caching Strategy
- Redis cache for:
- Session data
- Frequent query results
- Rate limit counters
- CDN for static assets
- Browser caching headers (1 hour for API responses)
Database Partitioning
- Partition transactions by date (monthly)
- Partition audit logs by date
- Archive old partitions to cold storage
Keyboard Navigation
- All interactive elements keyboard-accessible
- Visible focus indicators (2px solid outline, high contrast)
- Skip navigation link
- Logical tab order
Screen Reader Support
- ARIA labels for icons and buttons without text
- Semantic HTML (nav, main, article, etc.)
- Alt text for all images
- Live regions for dynamic content updates
Color & Contrast
- Minimum contrast ratio 4.5:1 for normal text
- Minimum contrast ratio 3:1 for large text (18pt+)
- Color not only way to convey information (icons, patterns)
- Support for high contrast mode
Form Accessibility
- Labels associated with inputs (for/id attributes)
- Error messages announced to screen readers
- Instructions provided before form
- Required fields clearly marked
Responsive Design
- Works with screen magnification (up to 400%)
- Touch targets minimum 44x44 pixels
- Content reflows without horizontal scrolling at 400% zoom
- Orientation changes handled gracefully
Language Support
- Default: English (en)
- Supported languages: Spanish (es), French (fr), German (de), Portuguese (pt), Japanese (ja)
- Language selection in user preferences
- Browser language detection for new users
Number & Currency Formatting
- Locale-specific number formatting (e.g., 1,234.56 vs 1.234,56)
- Currency symbols and placement (
$1,000 vs 1.000$ ) - Decimal precision based on currency (JPY: 0 decimals, USD: 2)
Date & Time Formatting
- Locale-specific date formats (MM/DD/YYYY vs DD/MM/YYYY)
- Timezone-aware display (user's local timezone)
- Relative time (2 hours ago, yesterday)
Text Direction
- LTR (Left-to-Right) for most languages
- RTL (Right-to-Left) for Arabic, Hebrew, etc.
- CSS logical properties (margin-inline-start vs margin-left)
Character Encoding
- UTF-8 for all data
- Support for Unicode characters (emojis, special characters)
Pluralization
- Language-specific plural rules (English: 1 item, 2 items; Arabic has 6 plural forms)
Slow Connection
- Action: Show loading indicators
- Timeout: 30 seconds for API calls
- Fallback: Show cached data if available
- UX: Progressive loading (skeleton screens)
Intermittent Connection
- Action: Queue requests, retry with backoff
- Detection: Navigator.onLine API, failed pings
- UX: Show "Offline - queued" status
- Sync: Process queue when reconnected
Complete Outage
- Action: Show friendly offline page
- Fallback: Serve cached critical pages (service worker)
- UX: Clear message with estimated recovery time
- Recovery: Auto-refresh when connection returns
Simultaneous Updates
- Scenario: Two users edit same transaction
- Resolution: Last-write-wins with timestamp comparison
- UX: Notify user whose change was overridden
- Audit: Log both changes with timestamps
Duplicate Requests
- Scenario: User double-clicks submit button
- Prevention: Disable submit button on first click
- Server: Idempotency keys to prevent duplicates
- UX: Clear feedback when request is processing
Race Conditions
- Scenario: Refund initiated while transaction status changing
- Prevention: Database row locking or optimistic concurrency
- Resolution: Return conflict error, retry logic
- UX: Inform user to try again
Partial Failures
- Scenario: Batch payout - some succeed, some fail
- Handling: Mark each payout individually (completed/failed)
- UX: Show which succeeded, which failed, why
- Retry: Allow retry of failed items only
Orphaned Records
- Scenario: Transaction without merchant (merchant deleted)
- Prevention: Soft deletes, cascade checks
- Handling: Flag orphaned records for review
- Cleanup: Job to resolve or archive orphaned data
Data Corruption
- Scenario: Invalid data in database
- Prevention: Strict validation on writes
- Detection: Data integrity checks (checksums)
- Recovery: Restore from backup, fix corrupted records
Token Theft
- Scenario: JWT token stolen via XSS
- Prevention: httpOnly cookies, short-lived tokens
- Detection: Anomaly detection (unusual IP, location)
- Response: Revoke token, force re-authentication
Rate Limit Bypass
- Scenario: Attacker uses multiple IPs to bypass rate limits
- Prevention: Rate limit by user_id, not just IP
- Detection: Suspicious pattern recognition
- Response: CAPTCHA, temporary account lock
SQL Injection
- Scenario: Malicious input in search field
- Prevention: Parameterized queries, input sanitization
- Detection: SQL query analysis
- Response: Block request, alert security team
Gateway Timeout
- Scenario: Payment gateway doesn't respond within 30 seconds
- Action: Mark transaction as "pending"
- Retry: Check status via webhook or periodic polling
- UX: Show "Processing - check back later" message
Gateway Downtime
- Scenario: Payment gateway completely unavailable
- Action: Circuit breaker opens
- Fallback: Use secondary gateway if configured
- UX: Show "Payment processing temporarily unavailable"
Webhook Failure
- Scenario: Webhook notification not delivered
- Retry: Exponential backoff (5 min, 15 min, 1 hour, 4 hours)
- Max retries: 5 attempts
- Fallback: Manual retry option in admin panel
Double Payment
- Scenario: Gateway processes payment twice
- Prevention: Idempotency keys
- Detection: Duplicate transaction_id check
- Resolution: Refund duplicate, alert merchant
Virus Detection
- Scenario: Uploaded file contains malware
- Action: Quarantine file, mark as "infected"
- UX: Show "File blocked: security threat"
- Notification: Alert security team
Storage Quota Exceeded
- Scenario: Merchant exceeds file storage limit
- Prevention: Show storage usage meter
- UX: Block uploads, prompt to delete old files
- Resolution: Upgrade plan or archive old files
Corrupted Upload
- Scenario: File upload completes but file is corrupted
- Detection: Checksum validation after upload
- Action: Delete corrupted file, show error
- UX: Prompt user to re-upload
Sync Conflict
- Scenario: Offline changes conflict with server changes
- Detection: Version/timestamp comparison
- UX: Show both versions, let user choose
- Resolution: Manual merge or override
Lost Sync
- Scenario: Sync queue data lost (device failure)
- Prevention: Persist queue to durable storage
- Detection: Queue完整性检查
- Recovery: Re-sync from server, flag lost changes
Clock Skew
- Scenario: Client clock differs from server
- Impact: Timestamps, expirations, sorting
- Prevention: Use server time for critical operations
- Detection: NTP sync, timestamp validation
Email Service Down
- Scenario: Transactional email provider unavailable
- Fallback: Queue emails, retry when service returns
- Max queue: 10,000 emails
- Alert: Notify admin if queue grows > 5,000
Push Notification Fail
- Scenario: FCM/APNS tokens invalid
- Detection: Unregistration feedback
- Action: Remove invalid tokens, prompt user to re-enable
- Fallback: In-app notification
CDN Failure
- Scenario: Static assets not loading from CDN
- Fallback: Serve from origin server
- Detection: Health checks every 30 seconds
- Alert: Notify admin of CDN issues
Unit Tests
- Coverage Goal: 80%+ code coverage
- What to Test:
- State reducers and actions
- Form validators
- Utility functions (formatters, parsers)
- Business logic (calculations, validations)
- Tools: Vitest, Jest, Mocha
Integration Tests
- Coverage Goal: All critical user flows
- What to Test:
- API calls with mocked responses
- Form submission flow
- Authentication flow
- Cache invalidation
- Error handling
- Tools: Supertest, Playwright API, MSW (Mock Service Worker)
End-to-End (E2E) Tests
- Coverage Goal: Core user journeys
- What to Test:
- Login → Dashboard → Transaction List → Transaction Detail → Logout
- Create Refund → Verify Status → View in History
- Upload File → Verify Upload → Delete File
- Apply Filters → Verify Results → Export CSV
- Tools: Playwright, Cypress, Puppeteer
- Environments: Staging environment (not production)
Performance Tests
- What to Test:
- API response times under load
- Database query performance
- Frontend rendering performance
- WebSocket connection handling
- Concurrent user simulation (100, 500, 1000 users)
- Tools: k6, Locust, JMeter, Lighthouse
Offline/Sync Tests
- What to Test:
- Offline mode detection
- Data caching
- Sync queue management
- Conflict resolution
- Reconnection handling
- Tools: Chrome DevTools Network throttling, Playwright offline mode
Security Tests
- What to Test:
- Authentication/authorization (RBAC, RLS)
- Input validation (SQL injection, XSS)
- CSRF protection
- Rate limiting
- Token expiration/refresh
- File upload security
- Tools: OWASP ZAP, Burp Suite, custom security tests
Accessibility Tests
- What to Test:
- Keyboard navigation
- Screen reader compatibility
- Color contrast
- ARIA labels
- Form accessibility
- Tools: axe DevTools, WAVE, Lighthouse
Test Data Strategy
- Use fixtures for consistent test data
- Generate random data for uniqueness tests
- Mock external dependencies (gateways, email services)
- Clean up test data after each test run
Environments
- Local: Unit tests, integration tests with mocks
- Dev: Integration tests with real services
- Staging: E2E tests, performance tests
- Production: Smoke tests (read-only, non-destructive)
CI/CD Integration
- Run tests on every pull request
- Block merge if tests fail
- Parallel test execution for speed
- Generate coverage reports
- Notify team of failures
Test Reporting
- Test results dashboard (e.g., GitHub Actions, CircleCI)
- Coverage trends over time
- Flaky test detection
- Performance regression alerts
Frontend
- Framework: React, Vue, or Angular (implementation choice)
- Hosting: Static hosting on CDN (Cloudflare, AWS CloudFront, Vercel)
- Build process: TypeScript → JavaScript (ES2022), minification, code splitting
- Deployment: Git push triggers automatic build and deploy
Backend
- Framework: Node.js (Express, Fastify) or Encore.ts
- Containerization: Docker containers
- Orchestration: Kubernetes, AWS ECS, or equivalent
- API Gateway: NGINX, AWS API Gateway, or Kong
- Scaling: Horizontal scaling with auto-scaling policies
Database
- Primary: PostgreSQL (recommended) or MySQL
- Replication: Read replicas for read-heavy workloads
- Backup: Daily full backups + continuous WAL archiving
- High Availability: Multi-region replication
- Connection Pooling: PgBouncer (PostgreSQL) or ProxySQL (MySQL)
Cache
- Redis or Memcached
- Use cases: Session storage, query cache, rate limiting
- Persistence: RDB snapshots every 5 minutes
- High Availability: Redis Cluster or AWS ElastiCache
Message Queue
- RabbitMQ, AWS SQS, or Kafka
- Use cases: Async tasks (payouts, reports, notifications)
- Dead Letter Queue: For failed messages
- Monitoring: Queue depth, processing time
Search
- Elasticsearch or OpenSearch
- Use cases: Transaction search, analytics
- Indexing: Real-time sync from primary database
- Scaling: Horizontal scaling with sharding
File Storage
- Object Storage: AWS S3, Cloudflare R2, or equivalent
- Encryption: Server-side encryption (AES-256)
- Lifecycle: Transition old files to Glacier after 90 days
- CDN: Cloudflare or CloudFront for fast delivery
Tools
- Terraform or AWS CDK
- Version control for infrastructure
- Reproducible deployments
- Multi-environment support (dev, staging, prod)
Components
- VPC and networking
- Load balancers
- Database clusters
- Redis clusters
- S3 buckets
- IAM roles and policies
Application Monitoring
- APM: Datadog, New Relic, or AppDynamics
- Metrics: Request rate, error rate, latency
- Distributed tracing: Request across services
- Error tracking: Sentry or Bugsnag
Infrastructure Monitoring
- CPU, memory, disk usage
- Network traffic
- Database connections, query performance
- Cache hit/miss ratio
- Queue depth
Logging
- Centralized logging: ELK Stack, CloudWatch Logs, or Datadog Logs
- Structured logging (JSON)
- Log levels: ERROR, WARN, INFO, DEBUG
- Sensitive data redaction (no credit card numbers, tokens)
Alerting
- Critical alerts (pager duty): Service down, database failure, security breach
- Warning alerts (email): High error rate, slow response times, disk space low
- Recovery: Automated rollback, scale up
Backup Strategy
- Database: Daily full backups + continuous WAL archiving
- Point-in-time recovery (PITR) capability
- Backups stored in multiple regions
- Weekly restore test
High Availability
- Multi-AZ deployment
- Auto-scaling for web servers
- Database failover (primary → replica)
- DNS failover for multi-region
Incident Response
- Runbooks for common scenarios
- On-call rotation
- Incident severity levels (SEV1-SEV4)
- Post-incident reviews (blameless)
Requirements
- Never store raw card data (PAN)
- Store only tokens from payment gateway
- Encrypt data in transit (TLS 1.2+)
- Encrypt data at rest (AES-256)
- Restrict access to cardholder data
- Regular vulnerability scans
- Annual penetration testing
Implementation
- Use PCI-compliant payment gateway (Stripe, Adyen)
- Server-side tokenization (never client-side card data to server)
- Mask card numbers in UI (**** 4242)
- Audit trail for all card-related operations
Requirements
- Data consent: Explicit consent for data collection
- Right to access: Users can request all their data
- Right to deletion: Users can request account deletion
- Right to portability: Export data in machine-readable format
- Data minimization: Collect only necessary data
- Privacy by design: Privacy built into system
Implementation
- Cookie consent banner
- Data export endpoint
- Account deletion flow (soft delete with 30-day grace period)
- Data retention policies
- Privacy policy and terms of service
Requirements
- Log all mutations (create, update, delete)
- Include: user_id, timestamp, action, entity_type, entity_id, changes
- Immutable logs (cannot be deleted or modified)
- Retention: 7 years (compliance)
- Exportable for audits
Implementation
- AuditLog entity (see Data Model)
- Automatic logging via middleware or triggers
- Queryable by merchant, user, entity, date range
- Regular backups of audit logs
In Transit
- TLS 1.2 or 1.3 for all API calls
- HSTS enabled
- Certificate management (auto-renewal)
- Secure cipher suites
At Rest
- Database encryption (TDE or application-level)
- File storage encryption (S3 server-side encryption)
- Backup encryption
- Key management: AWS KMS or equivalent
Application
- Sensitive fields encrypted in database (if not using TDE)
- Environment variables for secrets
- No secrets in code or logs
Authentication
- Strong password policy (12+ chars, mixed case, numbers, symbols)
- MFA for all users (configurable requirement)
- Session timeout (configurable, default 24 hours)
- Password reset with secure token
Authorization
- RBAC (Role-Based Access Control)
- Row-Level Security (RLS) for multi-tenancy
- Principle of least privilege
- Regular access reviews
Network
- IP whitelisting for admin access (optional)
- API key rotation for external integrations
- Rate limiting per user
- DDoS protection (Cloudflare, AWS Shield)
Input Validation
- Validate all user input on client and server
- Sanitize data to prevent XSS
- Use parameterized queries to prevent SQL injection
- Content Security Policy (CSP) headers
Dependencies
- Regular dependency updates
- Security scanning (npm audit, Snyk)
- Use vetted, maintained libraries
Code Security
- Code review process
- Static analysis (ESLint security plugins)
- Secret scanning (detect committed secrets)
- Secure coding training
Incident Response
- Security incident response plan
- Regular security drills
- Bug bounty program (optional)
- Disclosure policy
Response Times
- Page load: < 2s (p95)
- API response: < 500ms (p95)
- Database query: < 100ms (p95) for indexed queries
- WebSocket message delivery: < 100ms (p99)
Availability
- Uptime: 99.9% (8.76 hours downtime/year)
- Error rate: < 0.1% of requests
- Successful transactions: > 99.5%
Scalability
- Handle 10,000 concurrent users
- Process 1,000 transactions/second
- Support 1M+ transactions/month per merchant
Usability
- < 3 clicks to refund a transaction
- < 5 seconds to find a transaction
- < 10 seconds to export 10,000 transactions
- Offline-first: All cached data accessible offline
Satisfaction
- NPS score: > 50
- CSAT score: > 4.5/5
- Task completion rate: > 95%
Onboarding
- < 10 minutes to complete first transaction
- < 5 minutes to set up payment method
- < 2 minutes to invite team member
Accuracy
- Real-time balance sync: 100% accuracy
- Zero transaction loss (all transactions recorded)
- Audit trail completeness: 100%
Consistency
- No orphaned records
- No data inconsistencies across replicas
- Reconciliation matches 100%
Setup
- < 10 minutes to local setup
- Clear error messages for common issues
- Comprehensive documentation
Code Quality
- Test coverage: > 80%
- Code review turnaround: < 24 hours
- Bug fix time: < 48 hours for P0 bugs
Vulnerabilities
- Zero critical vulnerabilities
- Zero high-severity vulnerabilities in production
- < 5 medium-severity vulnerabilities
- All vulnerabilities patched within SLA
Compliance
- 100% of mutations audited
- 100% of sensitive data encrypted
- 100% PCI-DSS compliance
- 100% GDPR compliance
Incidents
- Security incidents: < 1 per year
- Data breaches: 0
- Mean time to detect (MTTD): < 1 hour
- Mean time to respond (MTTR): < 4 hours
sequenceDiagram
participant Customer
participant Merchant as Merchant App
participant Platform as Payment Platform
participant Gateway as Payment Gateway
participant Bank as Customer Bank
Note over Customer,Bank: Transaction Flow
Customer->>Merchant: Initiates payment
Merchant->>Platform: POST /transactions<br/>{amount, payment_method}
Platform->>Platform: Validate request<br/>Check idempotency key
Platform->>Gateway: Create payment intent
Gateway-->>Platform: Payment intent created
Platform-->>Merchant: Return client_secret
Merchant->>Gateway: Confirm payment<br/>(client-side)
Gateway->>Bank: Process charge
Bank-->>Gateway: Charge response
alt Charge Successful
Gateway-->>Platform: Webhook: payment.succeeded
Platform->>Platform: Update transaction: completed
Platform->>Merchant: WebSocket: transaction.updated
Merchant->>Customer: Show success
Note over Platform,Gateway: Settlement (1-3 business days)
Gateway-->>Platform: Webhook: payment.succeeded.settled
Platform->>Platform: Update transaction: settled
Platform->>Merchant: WebSocket: balance.updated
else Charge Failed
Gateway-->>Platform: Webhook: payment.failed
Platform->>Platform: Update transaction: failed
Platform->>Merchant: WebSocket: transaction.updated
Merchant->>Customer: Show error
end
Note over Customer,Bank: Refund Flow (if requested)
Merchant->>Platform: POST /transactions/:id/refund
Platform->>Platform: Validate refundable amount<br/>Check idempotency key
Platform->>Gateway: Create refund
Gateway-->>Platform: Refund created
Platform->>Platform: Update transaction: refunded_amount += amount
Platform->>Merchant: WebSocket: refund.created
Gateway->>Bank: Process refund
Bank-->>Gateway: Refund processed
Gateway-->>Platform: Webhook: refund.succeeded
Platform->>Platform: Update refund: completed
Platform->>Merchant: WebSocket: refund.completed
stateDiagram-v2
[*] --> Pending: Customer initiates payment
Pending --> Processing: Merchant submits to gateway
Processing --> Completed: Payment captured
Processing --> Failed: Payment declined
Processing --> Disputed: Chargeback filed
Completed --> Settled: Gateway settlement
Completed --> Refunded: Refund processed
Completed --> Disputed: Chargeback filed
Settled --> Refunded: Refund processed
Settled --> Disputed: Chargeback filed
Failed --> [*]
Refunded --> [*]
Disputed --> DisputeWon: Merchant wins
Disputed --> DisputeLost: Merchant loses
DisputeWon --> Settled: Funds reinstated
DisputeLost --> Refunded: Funds reversed
note right of Completed
Transaction captured
Funds not yet available
for payout
end note
note right of Settled
Funds settled by gateway
Available for payout
end note
stateDiagram-v2
[*] --> Online: App starts
Online --> Offline: Network lost
Offline --> Syncing: Network restored
Syncing --> Online: Sync complete
Online --> [*]: App closed
note right of Online
- Real-time updates via WebSocket
- Direct API calls
- Cache invalidation on mutations
end note
note right of Offline
- Show cached data
- Queue mutations in IndexedDB
- Disable features requiring live data
- Show offline indicator
end note
note right of Syncing
- Process queue in order
- Resolve conflicts
- Update cache
- Reconnect WebSocket
end note
graph TB
subgraph "Shared Database"
subgraph "Merchant A"
M1[Merchant: merchant_a]
U1[User: user_a1, user_a2]
T1[Transaction: tx_a1, tx_a2, tx_a3]
P1[Payout: payout_a1]
end
subgraph "Merchant B"
M2[Merchant: merchant_b]
U2[User: user_b1, user_b2]
T2[Transaction: tx_b1, tx_b2]
P2[Payout: payout_b1, payout_b2]
end
subgraph "Shared Tables"
Merchants[Merchants table]
Users[Users table]
Transactions[Transactions table]
Payouts[Payouts table]
end
end
M1 -.->|merchant_id| Merchants
U1 -.->|merchant_id| Users
T1 -.->|merchant_id| Transactions
P1 -.->|merchant_id| Payouts
M2 -.->|merchant_id| Merchants
U2 -.->|merchant_id| Users
T2 -.->|merchant_id| Transactions
P2 -.->|merchant_id| Payouts
style M1 fill:#e1f5ff
style U1 fill:#e1f5ff
style T1 fill:#e1f5ff
style P1 fill:#e1f5ff
style M2 fill:#fff4e1
style U2 fill:#fff4e1
style T2 fill:#fff4e1
style P2 fill:#fff4e1
sequenceDiagram
participant Client
participant API as API Server
participant Redis as Cache
participant Gateway as Payment Gateway
Client->>API: POST /refunds<br/>X-Idempotency-Key: uuid-123
API->>Redis: GET idempotency:uuid-123
Redis-->>API: null (not found)
API->>Gateway: Process refund
Gateway-->>API: Success: refund_id=R456
API->>Redis: SET idempotency:uuid-123<br/>value={response}<br/>TTL=48h
API-->>Client: 200 OK<br/>{id: R456, ...}
Note over Client,Redis: Network timeout - client retries
Client->>API: POST /refunds<br/>X-Idempotency-Key: uuid-123
API->>Redis: GET idempotency:uuid-123
Redis-->>API: Found: cached response
API-->>Client: 200 OK<br/>{id: R456, ...}<br/>(Cached response, no duplicate)
stateDiagram-v2
[*] --> Active_v1: Initial setup
Active_v1 --> Rotation: Merchant initiates
Rotation --> Dual_Secret: Generate new secret
Dual_Secret --> Active_v2: Remove old secret<br/>(after 24h)
Active_v2 --> Rotation: Next rotation
Active_v2 --> [*]: Merchant disabled
note right of Active_v1
Only secret_v1 active
All webhooks use secret_v1
end note
note right of Dual_Secret
Both secret_v1 and secret_v2 active
Verify with either secret
Grace period for updates
end note
note right of Active_v2
Only secret_v2 active
All webhooks use secret_v2
secret_v1 removed
end note
graph TB
subgraph "Client Layer"
Web[Web App]
Mobile[Mobile App]
Desktop[Desktop App]
end
subgraph "CDN Layer"
CDN[Static Assets & CDN]
end
subgraph "API Gateway"
Gateway[API Gateway<br/>Load Balancer]
end
subgraph "Application Layer"
API1[API Server 1]
API2[API Server 2]
API3[API Server N]
end
subgraph "Data Layer"
Redis[(Redis Cache)]
DB[(Primary Database<br/>PostgreSQL)]
Replica[(Read Replica)]
end
subgraph "Message Queue"
Queue[Message Queue<br/>RabbitMQ/SQS]
end
subgraph "External Services"
Stripe[Stripe Gateway]
SendGrid[Email Service]
S3[File Storage<br/>AWS S3]
ES[Elasticsearch]
end
subgraph "Monitoring"
Monitor[Monitoring<br/>Datadog/Sentry]
end
Web -->|HTTPS| CDN
Mobile -->|HTTPS| Gateway
Desktop -->|HTTPS| Gateway
CDN -->|Static Assets| Web
Gateway -->|Load Balance| API1
Gateway -->|Load Balance| API2
Gateway -->|Load Balance| API3
API1 -->|Read/Write| DB
API2 -->|Read/Write| DB
API3 -->|Read/Write| DB
API1 -->|Read| Replica
API2 -->|Read| Replica
API3 -->|Read| Replica
API1 -->|Cache| Redis
API2 -->|Cache| Redis
API3 -->|Cache| Redis
API1 -->|Async Tasks| Queue
API2 -->|Async Tasks| Queue
API3 -->|Async Tasks| Queue
API1 -->|Search| ES
API2 -->|Search| ES
API3 -->|Search| ES
API1 -->|Payment API| Stripe
API2 -->|Payment API| Stripe
API3 -->|Payment API| Stripe
API1 -->|Send Email| SendGrid
API2 -->|Send Email| SendGrid
API3 -->|Send Email| SendGrid
API1 -->|Upload/Download| S3
API2 -->|Upload/Download| S3
API3 -->|Upload/Download| S3
API1 -->|Logs/Metrics| Monitor
API2 -->|Logs/Metrics| Monitor
API3 -->|Logs/Metrics| Monitor
Queue -->|Process| API1
Queue -->|Process| API2
Queue -->|Process| API3
| Term | Definition |
|---|---|
| API | Application Programming Interface |
| JWT | JSON Web Token - for authentication |
| MFA | Multi-Factor Authentication |
| RBAC | Role-Based Access Control |
| RLS | Row-Level Security |
| PCI-DSS | Payment Card Industry Data Security Standard |
| GDPR | General Data Protection Regulation |
| WCAG | Web Content Accessibility Guidelines |
| TLS | Transport Layer Security - for encrypted connections |
| CDN | Content Delivery Network |
| SSO | Single Sign-On |
| TOTP | Time-based One-Time Password (for MFA) |
| PAN | Primary Account Number (card number) |
| WAL | Write-Ahead Log (database transaction log) |
| APM | Application Performance Monitoring |
| E2E | End-to-End (testing) |
| PITR | Point-In-Time Recovery |
| Version | Date | Changes |
|---|---|---|
| 1.1 | 2024-04-10 | Critical Technical Gaps Addressed - Added Section 7: Critical Technical Patterns - Idempotency Strategy with client/server responsibilities, TTL, and lifecycle - Transaction State Machine with transitions diagram and validation rules - Financial Precision: Mandated string types for amounts, decimal.js for calculations - Multi-Tenancy Strategy: Shared Schema with Merchant Partitioning - Webhook Security: Rolling Secrets strategy for seamless rotation - Updated Section 10 (Data Model): Changed amount fields from number to string- Added settled_at timestamp to Transaction entity- Added is_partial field to Refund entity- Added Section 18: Visual Diagrams with Mermaid diagrams - Payment Life Cycle - Transaction State Transitions - Offline Sync Logic - Multi-Tenancy Data Isolation - Idempotency Key Lifecycle - Webhook Secret Rotation - System Architecture - Enhanced UC-TXN-005.1: Refund History sub-view with partial refund support - Updated all monetary amounts in API contracts to use string representation - Added reconciliation grace period with settled_at timestamp- Added detailed validation rules for transaction state transitions |
| 1.0 | 2024-04-10 | Initial version - comprehensive PRD with feature prioritization, third-party integrations, scalability, accessibility, edge cases, and expanded use cases |
End of Document