CroaCard logo

C4 Architecture

System architecture diagrams plus native scanner view

Level 1 — System Context
Level 2 — Containers
Level 3 — Components
Native Scanner Architecture
Data Flows
Deployment

Level 1 — System Context

CroaCard and the people & external systems it interacts with.

100%

Level 2 — Container Diagram

Major deployable units, services, and data stores within the CroaCard platform.

100%

Level 3 — Component Diagram (Backend)

Detailed breakdown of backend services, routes, and their relationships.

100%

Native Scanner Architecture

Camera control, scan processing, callable functions, and Apple Wallet update path.

100%

Data Flows

Key end-to-end flows through the system.

Pass Creation Flow

  1. Merchant fills PassWizard (template, colours, assets, rewards)
  2. SPA POSTs to /api/customer-pass/create
  3. customerPassCoreService orchestrates creation
  4. passGenerationService loads template, substitutes fields, processes assets, signs .pkpass
  5. Uploads .pkpass to Firebase Storage
  6. Atomic transaction: writes customerPass doc + giftCardLedger issuance entry (if gift card) — both succeed or neither does
  7. Issuance ledger includes amountMinor, currency, ledgerVersion, stripePaymentIntentId (when Stripe-backed)
  8. Returns download URL to frontend

Scan / Visit Flow

  1. Merchant opens ScannerTab, camera reads QR
  2. checkPassType (callable) returns reward mode and payment/amount requirements
  3. If points: show TransactionAmountModal; if coupon requires payment: show PaymentModalAdapter; manual entry fallback allowed
  4. updateVisit (callable) Firestore transaction: increment visits/points/stamps
  5. Gift cards: deducts balance atomically, writes giftCardLedger entry with amountMinor, currency, ledgerVersion
  6. Inline call to evaluateRetentionForPass
  7. Returns updated pass data to SPA and updates local scan history
  8. onCustomerPassUpdated trigger sends APNs silent push (background)
  9. Apple Wallet refetches .pkpass via webServiceURL
  10. onPassScanned trigger logs scan event

Coupon Redemption Flow (Tap-to-Pay)

  1. Customer presents coupon QR code at POS
  2. Merchant scans QR via ScannerTab
  3. checkPassType (callable) returns reward mode + requiresPayment: true
  4. PaymentModalAdapter shown for card payment
  5. /v1/transactions/prepare-payment validates coupon via couponValidator, calculates discount (percentage / fixed / freeItem)
  6. /v1/transactions/create-payment-intent creates Stripe Terminal PaymentIntent via Connect account with TransactionMetadata (merchantId, couponSerial, customerPassId)
  7. /v1/transactions/terminal-connection-token returns Stripe Terminal connection token + location
  8. Stripe Terminal SDK collects card payment on reader
  9. payment_intent.succeeded webhook (or client fallback) triggers /v1/transactions/finalize-coupon-redemption
  10. redeemCoupon marks coupon as redeemed in Firestore
  11. Transaction doc logged to transactions collection

Gift Card Purchase Flow

  1. Frontend calls createPayment callable with paymentType: 'giftcard' + customerPassId
  2. stripeService.createPaymentIntent writes Stripe metadata: croacard_merchant_id, croacard_customer_pass_id, croacard_payment_type
  3. Writes giftCardPayments/{paymentIntentId} mapping doc (immutable, server-only)
  4. Returns clientSecret for Stripe Elements payment flow

Gift Card Refund Flow

  1. Stripe fires charge.refunded webhook (may contain multiple partial refunds)
  2. stripeWebhook reads croacard_customer_pass_id from charge metadata
  3. Iterates charge.refunds.data, skips already-processed by stripeRefundId
  4. Looks up giftCardPayments mapping for amountPaid cap
  5. Computes sumRefundedSoFar from ledger; caps each refund so total ≤ paid
  6. Each refund processed in its own Firestore transaction: updates balance + writes ledger
  7. Balance after refund cannot exceed originalBalance

Subscription Flow

  1. Merchant clicks subscribe in SettingsTab
  2. createCheckoutSession (callable) returns Stripe Checkout URL
  3. Merchant completes payment on Stripe
  4. subscriptionWebhook receives events:
  5. checkout.session.completed writes subscription to Firestore
  6. customer.subscription.updated syncs status
  7. invoice.payment_succeeded updates lastPaymentDate
  8. invoice.payment_failed flags merchant

Retention Flow

  1. runScheduledRetention fires every 24h
  2. Iterates merchants, passes, customerPasses
  3. evaluateRetentionForPass: builds signals, matches triggers
  4. Writes retentionEvent to Firestore
  5. runNoopDispatcher loads events + history
  6. CroaCore decideRetentionActions (pure policy) returns throttled decisions
  7. Persists delivery decisions
  8. Last-mile APNs delivery not yet wired

Bulk Distribution Flow

  1. Merchant uploads CSV via DistributeTab
  2. processCsvUpload (callable) parses CSV rows
  3. For each row: creates customer doc if needed
  4. Calls customerPassCoreService to generate pass
  5. Sends claim link via email/SMS (if configured)
  6. Writes batch results to Firestore

Analytics Flow

  1. AnalyticsTab queries Firestore collections
  2. Aggregates: issued/claimed passes, scan counts, revenue
  3. AnalyticsService provides enhanced insights
  4. Gift card sales tracking and statistics
  5. Charts rendered via frontend components

Gift Card Reconciliation Flow

  1. reconcileGiftCardBalances fires every 24h (scheduled)
  2. For each merchant, fetches all gift card customerPasses
  3. For each pass, sums all giftCardLedger entries
  4. expectedBalance = totalCredits - totalDebits (pure ledger-driven)
  5. Compares to currentBalance on the pass document
  6. If mismatch detected: writes reconciliationAlerts doc with delta and pass details
  7. Legacy gift cards (pre-issuance entries) handled gracefully

Gift Card CSV Export Flow

  1. Merchant navigates to /reports/gift-cards in the SPA
  2. Selects report type and optional date range
  3. SPA calls exportGiftCardReport (callable)
  4. Security: merchantId derived from auth.uid — client input ignored
  5. All monetary values exported as integer pence (amountMinor) + currency column + formatted convenience column
  6. CSV uploaded to Cloud Storage at exports/{merchantId}/
  7. Returns 1-hour signed download URL, auto-opens in browser
  8. Three report types: Liability Summary, Ledger Detail, Stripe Reconciliation

Deployment Topology

Where each component runs in production.

ComponentPlatformRegionRuntime
Web App (+ Reports UI)Firebase HostingGlobal CDNStatic (Vite)
API + Callables + TriggersCloud Functions (2nd Gen)europe-west2Node.js 20
exportGiftCardReportCloud Functions (2nd Gen) — callableeurope-west2Node.js 20, 512 MiB
createPaymentCloud Functions (2nd Gen) — callableeurope-west2Node.js 20
reconcileGiftCardBalancesCloud Functions (1st Gen) — scheduled 24heurope-west2Node.js 20
Scheduled FunctionsCloud Functions (1st Gen)europe-west2Node.js 20
FirestoreCloud Firestoreeurope-west2Managed
  ↳ giftCardLedgerSubcollection of merchantsImmutable, server-write
  ↳ giftCardPaymentsSubcollection of merchantsImmutable, server-write
  ↳ reconciliationAlertsSubcollection of merchantsServer-write, merchant-read
StorageFirebase StorageDefault bucketManaged
  ↳ exports/{merchantId}/CSV export filesSigned URLs, 1h expiry
Stripe WebhooksStripe to Cloud Functionseurope-west2HTTPS
  ↳ charge.refundedStripe Connect webhookPartial refund handler