Merchant Onboarding Service
Overview
gateway-merchant-onboarding owns processor credential profiles, activation,
and terminal onboarding workflows. It is internal-only in both
peakgateway-staging and pinpoint-gateway: no public load-balancer route
exists, and Cloud Run IAM authenticates every caller before the application.
The service is the sole writer of credential_profiles. Management exposes the
operator-facing BFF and forwards authorized requests through
InternalServiceClient.
Responsibilities
| Area | Responsibility |
|---|---|
| Credential profiles | Create, validate, submit, approve, assign, rotate, and retire processor profiles |
| Secret protection | Encrypt/decrypt credential material with envelope encryption and Cloud KMS |
| Processor activation | Run supported vendor onboarding, including TSYS TransIT MerchantActivation |
| Provisioning jobs | Persist asynchronous activation/device workflows and their evidence |
| XTMS | Bind NexGO devices and push approved parameters through libs/xtms-client |
| Audit | Append credential and provisioning lifecycle evidence without exposing secrets |
| Reconciliation | Resume incomplete local persistence after a successful external activation |
Service Boundary
Portal / operator
|
| Firebase + management permissions
v
management BFF
|
| Cloud Run IAM via InternalServiceClient
v
merchant-onboarding
| |
| HTTPS | KMS / Spanner
v v
TransIT / XTMS encrypted credential profiles
Processing reads API-safe credential-profile metadata needed for routing. It does not own onboarding state and must not write credential-profile rows. Management authorizes the human caller and tenant scope before forwarding any mutation.
API Boundary
Management exposes the support-relevant provisioning-job surface at
/management/api/v1/provisioning/jobs. The generated internal reference is the
operator contract:
- Internal operator API reference
websites/support-docs/openapi/internal-api.yaml
The owner service also has internal credential-profile and device-provisioning routes. Those are service-to-service contracts, not public APIs. Their controller/DTO source and focused tests are authoritative; do not copy secret or internal payload schemas into documentation.
The provisioning controller uses merchant:activate on direct OAuth-aware
calls. The management proxy instead enforces merchants:read or
devices:write, scopes the target merchant/location, and forwards the request
with Cloud Run IAM.
Credential Lifecycle
A credential profile moves through explicit lifecycle states. Exact state names and transitions are defined by the canonical enum/model and service tests, but the durable rules are stable:
- Create the profile for an organization and processor/platform.
- Store credential material encrypted; never return secret values in list or read responses.
- Validate the profile against the vendor without treating a transient failure as approval.
- Submit and approve only after required evidence exists.
- Assign the approved profile to concrete locations.
- Rotate by creating durable intent, completing external work, and persisting the result before reporting success.
- Retire or delete only when assignments and in-flight workflows permit it.
Unknown external outcomes become explicit reconciliation work. A successful
vendor activation followed by a failed local write is recoverable through the
durable PERSIST_ACTIVATION provisioning action; the issued credential is not
silently discarded or blindly reissued.
Provisioning Jobs
Provisioning actions currently include:
ACTIVATEROTATE_KEYPERSIST_ACTIVATION
Job states are QUEUED, IN_PROGRESS, COMPLETED, FAILED, and CANCELLED.
Create and retry return 202 Accepted; status reads and cancellation return the
persisted job representation.
A job records the target location/merchant, device serial, requested action, step evidence, timestamps, and any sanitized error. Secret values never belong in log messages, callback payloads, or operator-facing error text.
TransIT and XTMS
TransIT
MerchantActivation can issue a device ID and transaction key. Field ordering and wrapper shape are vendor-sensitive. Production DTOs, JSON ordering annotations, and certification fixtures are authoritative. Never patch required fields only in the certification runner.
XTMS
XTMS operations bind NexGO devices and push parameters. The service uses
HMAC-signed requests through libs/xtms-client. A successful TransIT activation
and a failed XTMS push are separate outcomes; preserve the activation and expose
the device step as retryable work.
Manual credential-package retrieval is an audited operator fallback when XTMS cannot push parameters. Treat its output as sensitive and never paste it into chat, tickets, PRs, or logs.
Security Controls
- Cloud Run IAM is the network/application entry gate.
- KMS envelope encryption protects stored credential material.
- Secret Manager supplies vendor application credentials.
- Credential reads return masked/API-safe metadata unless an explicitly audited sensitive operation is authorized.
- Tenant authorization is enforced at management before forwarding and checked again where the owner service has enough context.
- Audit rows are append-only evidence; they never contain plaintext secrets.
- Logs must not contain transaction keys, API keys, PAN, CVV, or raw vendor request/response bodies.
Data Ownership
Merchant-onboarding owns:
- credential profiles and location assignments
- provisioning jobs, steps, and audit evidence
- processor activation state owned by onboarding
- NexGO/XTMS binding state owned by onboarding
The SQLDelight files in libs/schema/src/main/sqldelight/ are authoritative.
Use Schema Migrations for changes; do not derive DDL
from copied examples.
Runtime
| Component | Technology |
|---|---|
| Language | Kotlin 2.2 on Java 25 |
| Framework | Spring Boot 4.0.5 |
| Database | Cloud Spanner PostgreSQL dialect through SQLDelight/PGAdapter |
| Vendor clients | OkHttp through transit-client and xtms-client |
| Encryption | AES-256-GCM envelope encryption with Cloud KMS |
| Resilience | Bounded Resilience4j policies |
| Ingress | Internal/load-balancer-only Cloud Run ingress plus IAM |
Health endpoint: GET /health on the internal service URL. Verify it through an
authorized internal caller or the aggregated status service; never add a public
route for convenience.