Skip to main content

Processing Service

Overview

gateway-processing is the canonical payment engine. It owns transaction, customer, stored-payment-method, subscription, settlement, gift-card, routing, and reconciliation behavior. The service is internal-only: it has no public load-balancer route and accepts calls through Cloud Run IAM.

Management, online-txn, card-present, and MCP call processing through InternalServiceClient or their service-specific integration layer.

Responsibilities

AreaResponsibility
PaymentsSale, authorization, capture, increment, void, refund, and processor sync
RoutingResolve typed processor/platform configuration and select an adapter
IdempotencyPersist command intent and return stable outcomes for repeated keys
ReconciliationPreserve and resolve in-doubt external outcomes
CustomersCustomer records, balances, and stored payment methods
SubscriptionsSchedule, retry, pause, resume, cancel, and invoice recurring charges
SettlementBatch lifecycle, reconciliation, and evidence
Gift cardsLedger-backed issue, redeem, refund, and balance behavior
EventsPublish durable payment-domain events for downstream delivery

Settlement and Reconciliation Contract

Settlement is command/report driven:

  • (TSYS, TRANSIT) uses a fenced close command plus exact host settlement reports.
  • (EPX, SERVER_POST) uses a fenced batch-close command. Its SS02 report is aggregate-only, so command acknowledgement never creates transaction membership.
  • CardPointe and Elavon EPG are report-only and never receive settlement close commands. CardPointe exact rows may settle; Elavon remains pending until finality semantics are certified.
  • Other processor/platform pairs fail closed until an exact settlement capability is certified.

Settlement reads and manual close are separate surfaces. The Management BFF exposes read-only historical settlement and reconciliation views plus the location-scoped POST /api/v1/settlements/manual-close action. That public action is not a generic Processing write: Management authorizes REPORTS_READ, requires the portal's second confirmation, and forwards the request to Processing as the pinned Management service identity. The internal Processing command route is not public.

Active settlement statuses are SETTLED and REVIEW_REQUIRED. No public retry, force-close, or manual-adjudication operation exists. Platform-only repair write paths remain separate from manual close and are not a way to retry or adjudicate a processor close.

Manual close ordering and failure invariants

For a supported (processorType, processorPlatform) command/report account, the manual-close flow is fail-closed and ordered:

  1. Resolve exactly one active account for the authorized location and reject unsupported rails, conflicting bindings, missing settleable work, or an invalid cutoff.
  2. Poll the report and build a complete canonical pre-dispatch baseline.
  3. Persist the request audit record, scoped to the exact organization and location.
  4. In one atomic database transaction, lock the processor account, reject an unresolved command or a confirmed command that already covers the work, insert the command and immutable baseline rows, complete the baseline, and cross the dispatch fence. A failed post-insert CAS or fence check rolls back the transaction; it does not leave a partially admitted command.
  5. Dispatch exactly one processor close attempt after the durable fence, then persist CONFIRMED, REJECTED, or UNKNOWN by guarded transition and write the result audit.

The endpoint requires the same key in the Idempotency-Key header and request body. The controller enforces that equality. The IdempotencyFilter persists the request fingerprint and replays only the exact stored HTTP result; reusing the key with a different body is a conflict. Independently, the command store uses the exact processor account/cutoff identity for durable command replay and rejects conflicting identity state. A timeout, transport error, malformed response, or other ambiguous dispatch is UNKNOWN: it is never automatically redialed or retried. Only one exact immutable post-fence report, or an operator-supplied exact report identity, can resolve UNKNOWN; lack of exact evidence remains unresolved.

Settlement scheduler steady state

Production steady state uses the enabled processor-settlement-sweep Cloud Scheduler job at */5 * * * * UTC. The one-shot repair job is absent. Repair flags remain off; historical and unmatched paths are enabled only where their documented controlled rollout requires them. A successful build is not deployment evidence: for staging, verify the deployed revision; for production, verify the published release-tag deployment's actual immutable revision and traffic.

Fraud-safe transaction score

The transaction-detail fraud projection is the bounded fraud-safe-txn-v1 model. It is an integer from 0 through 100 and is displayable only when scoreStatus=AVAILABLE. Support/admin and merchant transaction-detail views may show only the score, status, and model version. They must not show raw fraud inputs, evidence, fingerprints, rule details, or shopper-facing fraud data.

The score is available only when the decision matches the exact authorized organization, location, and transaction. Missing, insufficient-input, unknown, malformed, out-of-range, or organization/location/transaction-mismatched data is rendered unavailable; it must never be coerced into an available score.

Processor Routing

Controllers and services call PaymentProcessorGateway. The router selects an adapter from the location's typed processor configuration:

Processor integrationClient library
TSYS TransITlibs/transit-client
Fiserv CardPointe Gatewaylibs/cardpointe-client
Sierralibs/sierra-client
Fiservlibs/fiserv-client
Elavonlibs/elavon-client

CardPointe Bolt terminal operations live at the card-present boundary and use libs/bolt-client.

Vendor-specific request and response models stay behind adapters. The shared payment contract uses typed processorType and processorPlatform fields. Unknown or unsupported combinations fail closed; the engine never falls back to another processor for a monetary operation.

Payment Transition Invariants

Every durable payment mutation follows this order:

  1. Authorize the caller and resolve the target organization/location.
  2. Validate amount, currency, current state, and processor capability.
  3. Persist idempotent intent before external I/O.
  4. Call the selected processor adapter.
  5. Persist the observed outcome and processor evidence.
  6. Publish the durable event.
  7. Return success only after the local outcome is durable.

A timeout or ambiguous vendor response becomes explicit in-doubt or reconciliation work. It is never treated as permission to replay the payment blindly.

API Boundary

The service exposes internal /api/v1/* and /internal/* routes used by the BFF and peer services. Do not publish its Cloud Run URL or add processing to Terraform's externally_routed_services map.

Request and response bodies are owned by controller/DTO code and the canonical OpenAPI artifacts. Do not copy payload schemas into this page. Use:

Credential Boundary

Merchant-onboarding owns credential_profiles and encrypted processor credentials. Processing reads only the routing metadata and decrypted material needed for the selected operation through the internal credential-profile contract. Public and generated DTOs must not expose processor secrets.

Credential-profile resolution is location-scoped. Organization defaults may influence assignment, but a payment always resolves to a concrete location and profile before processor I/O.

Data Ownership

Processing owns payment-domain tables and their state transitions. Important families include:

  • transactions, attempts, idempotency commands, and reconciliation records
  • customers, payment methods, balances, and balance transactions
  • subscriptions, invoices, billing events, and dunning state
  • settlement batches and settlement evidence
  • gift-card accounts and immutable ledger entries
  • processor routing rules and execution evidence owned by processing

The SQLDelight source under libs/schema/src/main/sqldelight/ is authoritative. Do not use copied DDL in support docs to plan schema work. Follow Schema Migrations.

Events

Processing persists state before publishing the corresponding domain event. Webhook delivery is downstream behavior; a webhook failure does not roll back a successful payment transition. Event types come from the shared webhook-event registry and remain string literals where validation contract tests require source visibility.

Reliability

  • Resilience4j policies are operation-aware and bounded.
  • Idempotency keys protect externally retried commands.
  • Unknown external outcomes enter reconciliation instead of automatic retry.
  • Circuit breakers may protect non-authoritative dependency calls but cannot convert an unknown monetary result into success.
  • Audit and processor evidence must survive failures after external I/O.

Runtime

ComponentTechnology
LanguageKotlin 2.2 on Java 25
FrameworkSpring Boot 4.0.5
DatabaseCloud Spanner PostgreSQL dialect through SQLDelight/PGAdapter
Processor HTTPVendor-specific OkHttp clients
MessagingCloud Pub/Sub
SecretsKMS-encrypted credential material owned by merchant-onboarding
ResilienceResilience4j

Health endpoint: GET /health on the internal service URL. Public operators should use the aggregated status service instead of attempting to expose or probe processing through the public load balancer.