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
| Area | Responsibility |
|---|---|
| Payments | Sale, authorization, capture, increment, void, refund, and processor sync |
| Routing | Resolve typed processor/platform configuration and select an adapter |
| Idempotency | Persist command intent and return stable outcomes for repeated keys |
| Reconciliation | Preserve and resolve in-doubt external outcomes |
| Customers | Customer records, balances, and stored payment methods |
| Subscriptions | Schedule, retry, pause, resume, cancel, and invoice recurring charges |
| Settlement | Batch lifecycle, reconciliation, and evidence |
| Gift cards | Ledger-backed issue, redeem, refund, and balance behavior |
| Events | Publish 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:
- Resolve exactly one active account for the authorized location and reject unsupported rails, conflicting bindings, missing settleable work, or an invalid cutoff.
- Poll the report and build a complete canonical pre-dispatch baseline.
- Persist the request audit record, scoped to the exact organization and location.
- 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.
- Dispatch exactly one processor close attempt after the durable fence, then
persist
CONFIRMED,REJECTED, orUNKNOWNby 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 integration | Client library |
|---|---|
| TSYS TransIT | libs/transit-client |
| Fiserv CardPointe Gateway | libs/cardpointe-client |
| Sierra | libs/sierra-client |
| Fiserv | libs/fiserv-client |
| Elavon | libs/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:
- Authorize the caller and resolve the target organization/location.
- Validate amount, currency, current state, and processor capability.
- Persist idempotent intent before external I/O.
- Call the selected processor adapter.
- Persist the observed outcome and processor evidence.
- Publish the durable event.
- 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:
- Public API reference
- Internal operator API reference
services/processing/src/main/kotlin/.../controller/artifacts/openapi/for SDK-enabled contracts
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
| Component | Technology |
|---|---|
| Language | Kotlin 2.2 on Java 25 |
| Framework | Spring Boot 4.0.5 |
| Database | Cloud Spanner PostgreSQL dialect through SQLDelight/PGAdapter |
| Processor HTTP | Vendor-specific OkHttp clients |
| Messaging | Cloud Pub/Sub |
| Secrets | KMS-encrypted credential material owned by merchant-onboarding |
| Resilience | Resilience4j |
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.