Skip to main content

System Architecture

Overview

Peak Gateway is an eight-service Kotlin/Spring Boot payment platform on Google Cloud Run. Public clients enter through two global load-balancer hosts; internal payment and onboarding services are reachable only through authenticated service-to-service calls.

The same unsuffixed resource names exist in both environments:

EnvironmentGCP projectRegion
Stagingpeakgateway-stagingus-east1
Productionpinpoint-gatewayus-east1

Environment is determined by the GCP project, not a -staging service-name suffix.

Request and Service Topology

Portal users (Firebase) API clients (OAuth2) Checkout shoppers
| | |
+-------------------------------+----------------------------+
|
Global HTTPS load balancer
+-----------------+----------------+
| |
api.peakgateway.co pay.peakgateway.co
| |
+--------------+-------------+ +--------+---------+
| | | | |
auth management status online-txn card-present
| | | |
| +-------------+-------------+------------------+
| IAM
| +-----------+-----------+
| | |
| processing merchant-onboarding
| | |
| payment processors TransIT / XTMS
|
mcp --IAM--> processing / management

All containers listen on 8080. Local Docker mappings use 8181 through 8188; see Local Development.

Security Boundaries

Publicly routed services

  • auth
  • management
  • online-txn
  • card-present
  • status
  • mcp

Public traffic reaches these services only through the global load balancer and Cloud Armor. OAuth/JWT or Firebase authorization remains an application-level requirement except for explicitly public health and shopper-session routes.

Internal-only services

  • processing
  • merchant-onboarding

Neither service is present in Terraform's externally_routed_services map. Cloud Run IAM validates caller identity before the request reaches the application. Callers use InternalServiceClient, which obtains a Google IAM ID token from the metadata server. Unknown or unauthenticated callers fail closed.

See Internal-Only Services for the source and live verification checklist.

Service Catalog

ServiceResponsibilityDurable store
authOAuth2 client credentials, API-key exchange, JWK publication, device identitySpanner
processingPayment orchestration, subscriptions, settlement, customers, routing, processor adaptersSpanner
managementPortal BFF, organizations, locations, users, agents, audit, configurationSpanner
online-txnCheckout sessions, hosted payments, webhooks, API keys, storefront APIsSpanner
card-presentIn-person acceptance, terminal sync/refund, device orchestrationSpanner
merchant-onboardingCredential-profile ownership, processor activation, XTMS bindingSpanner
statusAggregate healthNone
mcpScoped MCP read, dunning, and refund operationsNone

management is both a domain owner and a BFF. It authorizes portal users, clamps every request to the caller's organization/location/agent scope, then forwards non-owned operations to the owning internal service.

Public Routing

Host and pathServiceRewrite
api.peakgateway.co/.well-known/*authnone
api.peakgateway.co/oauth2/*authnone
api.peakgateway.co/auth/*authremove /auth
api.peakgateway.co/management/*managementremove /management
api.peakgateway.co/status/*statusremove /status
api.peakgateway.co/mcp and /mcp/*mcpexact transport path retained; nested prefix removed
pay.peakgateway.co/online-txn/*online-txnremove /online-txn
pay.peakgateway.co/card-present/*card-presentremove /card-present

The API host defaults to management. The payment host defaults to online-txn. Staging uses staging-api.peakgateway.co and staging-pay.peakgateway.co with the same path rules.

Payment Processor Architecture

The processing service is the canonical payment engine. Requests flow through PaymentProcessorGateway and the processor router, which selects an adapter from the location's typed processor configuration. Current processor clients include:

  • TSYS TransIT (libs/transit-client)
  • Fiserv CardPointe Gateway (libs/cardpointe-client)
  • Sierra (libs/sierra-client)
  • Fiserv (libs/fiserv-client)
  • Elavon (libs/elavon-client)

Card-present CardPointe terminal operations use the Bolt client in libs/bolt-client. Vendor-specific request shaping remains behind adapters; controllers and caller services use the shared payment contract.

Unknown processor selection is an explicit error. The system never silently falls back to a different processor for a monetary operation.

Settlement uses explicit processor rails rather than a public batch-control API: (TSYS, TRANSIT) has an internal close command plus report verification, CardPointe is report-only, and every other processor/platform pair fails closed. Public settlement and reconciliation surfaces expose read-only historical evidence with active statuses SETTLED and REVIEW_REQUIRED; close, retry, force-close, and adjudication are not public operations.

Service-to-Service Calls

CallerCalleePurpose
managementprocessingTransactions, settlements, subscriptions, customers, gift cards, tax, reports
managementmerchant-onboardingCredential profiles, activation, provisioning
managementonline-txnCheckout and hosted-payment administration, webhook operations
managementauthOAuth clients, API keys, device identity
managementcard-presentTerminal and device administration
online-txnprocessingCard-not-present payment execution
card-presentprocessingShared payment execution for in-person requests
processingmerchant-onboardingAPI-safe credential-profile metadata for routing
mcpprocessing / managementScoped reads and approved mutations
statusall servicesHealth aggregation

Cloud Run IAM is the authorization boundary for every internal call. Processing and merchant-onboarding do not expose public load-balancer routes.

Data Ownership and Durable Transitions

Cloud Spanner uses the PostgreSQL dialect through SQLDelight. A table has one owning service unless it is explicitly designated as a shared append-only or multi-writer contract.

Payment-critical transitions follow these invariants:

  1. Validate tenant, authorization, amount, currency, and state before side effects.
  2. Persist idempotent intent before processor or other external I/O.
  3. Persist the observed outcome before returning success.
  4. Treat an unknown processor outcome as in-doubt reconciliation work, never as a blind retry.
  5. Preserve audit and processor evidence needed to explain the transition.

Schema changes are bounded, immutable, forward-only revisions under db/schema-revisions/. See Schema Migrations.

Infrastructure

LayerTechnology
ServicesKotlin 2.2, Java 25, Spring Boot 4.0.5, Spring Security 7.0
FrontendsReact 19, Vite 7, Tailwind 4, Docusaurus
DatabaseCloud Spanner PostgreSQL dialect, SQLDelight 2.2.1
MessagingCloud Pub/Sub
IdentitySpring Authorization Server, Firebase Auth, SAML SSO, Cloud Run IAM
EdgeGlobal external Application Load Balancer, Certificate Manager, Cloud Armor
BuildBazel with bzlmod and BuildBuddy remote execution/cache
InfrastructureTerraform managed through Terrakube
DeploymentGitHub Actions to Cloud Run and Cloudflare Pages

Repository Map

gateway/
├── services/ # Eight Spring Boot services
│ ├── auth/
│ ├── processing/
│ ├── management/
│ ├── online-txn/
│ ├── card-present/
│ ├── merchant-onboarding/
│ ├── status/
│ └── mcp/
├── libs/ # Shared security, schema, processor, messaging, and resilience libraries
├── websites/ # Admin, merchant, checkout, marketing, public docs, support docs
├── sdks/
│ ├── kotlin/
│ └── typescript/
├── infra/tf/gcp/ # Terraform root and modules
├── db/schema-revisions/ # Immutable forward-only schema revisions
├── artifacts/openapi/ # Canonical SDK-enabled OpenAPI artifacts
├── tools/
└── scripts/

The implementation and generated contracts are authoritative. See Repository Source of Truth before adding or relocating architecture documentation.