Processing Service
1. Overview
The Processing Microservice is the core payment engine. It handles all transaction-related communication with the TSYS TransIT Multipass API. All payment operations (sale, auth, capture, void, refund) flow through this service. It also handles settlement reconciliation and transaction lifecycle management. Note: the Merchant Onboarding Service also communicates with TransIT for MerchantActivation (credential generation) — see the Merchant Onboarding Service spec.
Rename note: the current tenant model is Organization -> Location. This page still shows legacy
merchantIdrequest/response fields where they match the shipped wire contract; in those cases, read the value as the concrete location record, not as a separate top-level tenant type.
1.1 Rename guardrails
Safe statements:
- the processing runtime is location-scoped for concrete transaction execution
- legacy
merchantIdfields in requests and responses identify the concrete location on the wire - organization-level defaults such as credentials or tax configuration can still influence runtime behavior upstream of the payment call
Unsafe statements:
- that
merchantIdhere implies a separate tenant model from Location - that every processing API field has already been renamed to
locationId - that support should describe the processing surface as merchant-first for new integrations
2. Responsibilities
| Responsibility | Description |
|---|---|
| Transaction processing | Execute sale, auth, capture, void, refund against TransIT |
| Transaction routing | Route requests to correct TransIT credentials per merchant |
| Idempotency | Prevent duplicate transactions via Spanner-backed idempotency keys |
| Settlement reconciliation | Reconcile daily TransIT settlement batches against our records |
| Transaction logging | Persist all transaction attempts, responses, and state transitions |
| Retry handling | Retry failed TransIT calls with exponential backoff |
| Tip adjustment | Adjust tip amounts on authorized transactions before settlement |
| L2/L3 enrichment | Auto-enrich transactions with Level 2/3 data post-auth to reduce interchange fees |
| Pin debit detection | Identify pin debit transactions to skip adjustment operations |
| Pricing calculation | Apply cash discount, surcharge amounts at transaction time |
| Batch settlement config | Manage timed and scheduled batch settlement (default 10:30 PM) |
| Recurring billing | Execute scheduled recurring charges using stored Guardian tokens |
| Subscription lifecycle | Manage subscription creation, billing cycles, retries, pausing, and cancellation |
| Event publishing | Publish transaction events to Pub/Sub for async consumers |
3. API Specification
Base URL
- Internal:
https://processing-service.internal:8080 - Not exposed publicly — only called by Management and Online Txn services
Endpoints
Process Sale
POST /api/v1/transactions/sale
Request:
{
"merchantId": "loc_abc123",
"amount": 2500, // cents
"currency": "USD",
"cardDataSource": "MANUAL", // MANUAL | SWIPE | EMV | CONTACTLESS | TOKEN | INTERNET
"cardData": {
"cardNumber": "4111111111111111", // or tokenId for TOKEN source
"expirationDate": "1226",
"cvv": "123",
"cardHolderName": "John Doe"
},
"billing": {
"zip": "28202"
},
"industryType": "RE", // RE | EC | RS
"externalReferenceId": "uuid-for-idempotency",
"metadata": {}
}
Response (200):
{
"transactionId": "txn_abc123",
"transitTransactionId": "123456789",
"status": "APPROVED",
"approvalCode": "TAS123",
"responseCode": "A0000",
"processedAmount": 2500,
"tokenId": "tok_xyz789",
"avsResult": "Y",
"cvvResult": "M",
"createdAt": "2026-03-03T15:00:00Z"
}
Response (200 - Declined):
{
"transactionId": "txn_abc124",
"status": "DECLINED",
"responseCode": "D1220",
"responseMessage": "Insufficient funds",
"createdAt": "2026-03-03T15:00:01Z"
}
Process Authorization
POST /api/v1/transactions/auth
// Same request body as sale
// Response includes transactionId for later capture
Capture Authorization
POST /api/v1/transactions/{transactionId}/capture
Request:
{
"amount": 2500 // can be less than or equal to auth amount
}
Response (200):
{
"transactionId": "txn_abc123",
"status": "CAPTURED",
"capturedAmount": 2500,
"remainingAuthorizedAmount": 0,
"capturedAt": "2026-03-03T15:05:00Z"
}
Partial captures keep the authorization capturable until remainingAuthorizedAmount
reaches zero. Merchant Dashboard and Admin Portal transaction details both expose
the captured and remaining authorized amounts and allow operators to capture a
specific remaining amount.
Increment Authorization
POST /api/v1/transactions/{transactionId}/increment
Request:
{
"amount": 1500
}
Response (200):
{
"transactionId": "txn_abc123",
"status": "APPROVED",
"amount": 4000,
"remainingAuthorizedAmount": 4000
}
Use incremental authorization before capture when the final expected amount increases, for example hospitality, rental, or delayed-fulfillment orders. The Admin Portal and Merchant Dashboard action panels accept operator-entered increment amounts and refresh the transaction after success.
Void Transaction
POST /api/v1/transactions/{transactionId}/void
Response (200):
{
"transactionId": "txn_abc123",
"status": "VOIDED",
"voidedAt": "2026-03-03T15:10:00Z"
}
Refund Transaction
POST /api/v1/transactions/{transactionId}/refund
Request:
{
"amount": 1500 // partial refund supported
}
Response (200):
{
"transactionId": "txn_abc123",
"refundTransactionId": "txn_ref456",
"status": "REFUNDED",
"refundedAmount": 1500,
"refundedAt": "2026-03-03T15:15:00Z"
}
Get Transaction
GET /api/v1/transactions/{transactionId}
Response (200):
{
"transactionId": "txn_abc123",
"merchantId": "loc_abc123",
"type": "SALE",
"status": "SETTLED",
"amount": 2500,
"processedAmount": 2500,
"cardBrand": "VISA",
"cardLast4": "1111",
"approvalCode": "TAS123",
"responseCode": "A0000",
"avsResult": "Y",
"cvvResult": "M",
"tokenId": "tok_xyz789",
"externalReferenceId": "uuid",
"createdAt": "2026-03-03T15:00:00Z",
"settledAt": "2026-03-04T06:00:00Z"
}
Search Transactions
GET /api/v1/transactions?merchantId=loc_abc123&status=APPROVED&from=2026-03-01&to=2026-03-03&limit=50&offset=0
Response (200):
{
"transactions": [...],
"total": 1234,
"limit": 50,
"offset": 0
}
Get Settlement Batch
GET /api/v1/settlements?date=2026-03-03&merchantId=loc_abc123
Response (200):
{
"batchId": "batch_20260303",
"date": "2026-03-03",
"merchantId": "loc_abc123",
"totalSales": 150000,
"totalRefunds": 5000,
"netAmount": 145000,
"transactionCount": 47,
"status": "RECONCILED",
"reconciledAt": "2026-03-04T07:00:00Z"
}
Tip Adjustment
POST /api/v1/transactions/{transactionId}/tip-adjust
Request:
{
"tipAmount": 500
}
Response (200):
{
"transactionId": "txn_abc123",
"originalAmount": 2500,
"tipAmount": 500,
"totalAmount": 3000,
"status": "TIP_ADJUSTED",
"adjustedAt": "2026-03-03T15:20:00Z"
}
Validates: transaction is approved/captured, batch not yet settled. Relays adjustment to TransIT.
L2/L3 Data Enrichment
POST /api/v1/transactions/{transactionId}/enrich
Request:
{
"level2": {
"taxAmount": 200,
"customerCode": "PO-12345",
"taxIndicator": "Y"
},
"level3": {
"lineItems": [
{
"description": "Widget A",
"quantity": 2,
"unitCost": 1000,
"commodityCode": "123",
"discountAmount": 0
}
],
"freightAmount": 500,
"dutyAmount": 0
}
}
Response (200):
{
"transactionId": "txn_abc123",
"enrichmentStatus": "APPLIED",
"enrichedAt": "2026-03-03T15:25:00Z"
}
- Auto-triggered immediately after authorization approval
- Also supports end-of-day batch enrichment
- Pin debit detection: returns
enrichmentStatus: "SKIPPED_PIN_DEBIT"if pin debit
Partial Void
POST /api/v1/transactions/{transactionId}/void
Request:
{
"amount": 1000 // optional — omit for full void, include for partial
}
Full void: omit amount. Partial void: include amount. Only valid before batch settlement.
Batch Settlement Configuration
POST /api/v1/settlements/configure
Request:
{
"merchantId": "loc_abc123",
"settlementMode": "SCHEDULED",
"scheduledTime": "22:30",
"timezone": "America/New_York"
}
Response (200):
{
"merchantId": "loc_abc123",
"settlementMode": "SCHEDULED",
"scheduledTime": "22:30",
"timezone": "America/New_York",
"updatedAt": "2026-03-03T15:30:00Z"
}
Default dev batch closing: 10:30 PM. Supports both scheduled and on-demand modes.
Create Subscription
POST /api/v1/subscriptions
Request:
{
"merchantId": "loc_abc123",
"customerId": "cust_001",
"tokenId": "tok_xyz789",
"planName": "Monthly Service",
"amount": 4999,
"currency": "USD",
"interval": "MONTHLY",
"intervalCount": 1,
"startDate": "2026-04-01",
"endDate": null,
"maxCycles": null,
"metadata": { "planId": "plan_premium" }
}
Response (201):
{
"subscriptionId": "sub_abc123",
"merchantId": "loc_abc123",
"customerId": "cust_001",
"status": "ACTIVE",
"currentCycle": 0,
"nextBillingDate": "2026-04-01",
"createdAt": "2026-03-07T10:00:00Z"
}
interval: DAILY, WEEKLY, MONTHLY, QUARTERLY, YEARLYintervalCount: multiplier (e.g., interval=WEEKLY + intervalCount=2 = biweekly)endDate/maxCycles: optional; omit both for indefinite billing- Charges executed via stored Guardian token (
tokenId)
Get Subscription
GET /api/v1/subscriptions/{subscriptionId}
Response (200):
{
"subscriptionId": "sub_abc123",
"merchantId": "loc_abc123",
"customerId": "cust_001",
"tokenId": "tok_xyz789",
"planName": "Monthly Service",
"amount": 4999,
"currency": "USD",
"interval": "MONTHLY",
"intervalCount": 1,
"status": "ACTIVE",
"currentCycle": 3,
"nextBillingDate": "2026-07-01",
"lastBillingDate": "2026-06-01",
"lastBillingStatus": "SUCCESS",
"startDate": "2026-04-01",
"endDate": null,
"maxCycles": null,
"failedAttempts": 0,
"metadata": { "planId": "plan_premium" },
"createdAt": "2026-03-07T10:00:00Z",
"updatedAt": "2026-06-01T06:00:00Z"
}
List Subscriptions
GET /api/v1/subscriptions?merchantId=loc_abc123&customerId=cust_001&status=ACTIVE&limit=50&offset=0
Response (200):
{
"subscriptions": [...],
"total": 12,
"limit": 50,
"offset": 0
}
Update Subscription
PATCH /api/v1/subscriptions/{subscriptionId}
Request:
{
"amount": 5999,
"tokenId": "tok_new456",
"planName": "Premium Service"
}
Response (200):
{
"subscriptionId": "sub_abc123",
"status": "ACTIVE",
"amount": 5999,
"updatedAt": "2026-03-07T12:00:00Z"
}
Only amount, tokenId, planName, endDate, maxCycles, and metadata are updatable. Interval changes require cancel + recreate.
3.1 Docs-side closeout checklist
Treat this page as closure-grade for the rename only if readers can infer all of the following without code archaeology:
merchantIdexamples refer to concrete location ids on the wire- organization/location remains the preferred tenant model everywhere outside the legacy field names
- support can reuse these examples without implying that the old merchant-first naming is still the desired long-term model
Pause Subscription
POST /api/v1/subscriptions/{subscriptionId}/pause
Request:
{
"resumeDate": "2026-05-01"
}
Response (200):
{
"subscriptionId": "sub_abc123",
"status": "PAUSED",
"pausedAt": "2026-03-07T12:00:00Z",
"scheduledResumeDate": "2026-05-01"
}
Resume Subscription
POST /api/v1/subscriptions/{subscriptionId}/resume
Response (200):
{
"subscriptionId": "sub_abc123",
"status": "ACTIVE",
"nextBillingDate": "2026-05-01",
"resumedAt": "2026-03-07T12:00:00Z"
}
Cancel Subscription
POST /api/v1/subscriptions/{subscriptionId}/cancel
Request:
{
"cancelAt": "END_OF_CYCLE",
"reason": "Customer requested"
}
Response (200):
{
"subscriptionId": "sub_abc123",
"status": "CANCELLING",
"cancelAt": "2026-04-01",
"cancelledAt": "2026-03-07T12:00:00Z",
"cancellationReason": "Customer requested"
}
cancelAt:IMMEDIATELYorEND_OF_CYCLE(default)IMMEDIATELYsets status toCANCELLEDand stops billingEND_OF_CYCLEsets status toCANCELLING— final charge runs at next billing date, then status transitions toCANCELLED
Subscription Billing History
GET /api/v1/subscriptions/{subscriptionId}/billing-history?limit=50&offset=0
Response (200):
{
"billingEvents": [
{
"cycle": 3,
"transactionId": "txn_rec789",
"amount": 4999,
"status": "SUCCESS",
"billedAt": "2026-06-01T06:00:00Z"
},
{
"cycle": 2,
"transactionId": "txn_rec456",
"amount": 4999,
"status": "SUCCESS",
"billedAt": "2026-05-01T06:00:00Z"
}
],
"total": 3,
"limit": 50,
"offset": 0
}
Recurring Billing Engine
The subscription billing engine runs as a scheduled job within the Processing Microservice:
- Scheduler (Cloud Scheduler → Pub/Sub): Triggers billing job every hour
- Billing Job: Queries subscriptions where
next_billing_date <= NOW()andstatus = ACTIVE - Per-subscription: Executes a TOKEN sale via TransIT using the stored Guardian token
- On success: Updates
current_cycle,next_billing_date,last_billing_date, publishessubscription.billedevent - On decline: Increments
failed_attempts, schedules retry per dunning policy - Dunning policy: Retry at 1 day, 3 days, 7 days. After 3 failed attempts → status becomes
PAST_DUE. After 7 days past due →SUSPENDED. Merchant configurable.
┌──────────────────┐ ┌─────────────────┐ ┌──────────────┐
│ Cloud Scheduler │────>│ Billing Job │────>│ Processing │
│ (hourly trigger) │ │ (query due subs)│ │ (TOKEN sale) │
└──────────────────┘ └────────┬────────┘ └──────┬───────┘
│ │
│ ┌──────────────────┘
│ │
v v
┌─────────────────┐
│ Update sub + │
│ Publish event │
└─────────────────┘
4. Transaction State Machine
┌─────────────┐
│ PENDING │
└──────┬──────┘
│
┌────────────┼────────────┐
│ │ │
v v v
┌──────────┐ ┌───────────┐ ┌──────────┐
│ APPROVED │ │ DECLINED │ │ ERROR │
└────┬─────┘ └───────────┘ └──────────┘
│
┌──────┼──────┬────────────┐
│ │ │ │
v │ v v
┌────────┐ │ ┌─────────┐ ┌─────────────┐
│ VOIDED │ │ │CAPTURED │ │TIP_ADJUSTED │
│(full/ │ │ └────┬────┘ └──────┬──────┘
│partial)│ │ │ │
└────────┘ │ │ ┌──────┘
│ │ │
v v v
┌───────────────────────┐
│ ENRICHED (L2/L3) │ ← optional (skipped for pin debit)
└───────────┬───────────┘
│
v
┌───────────────┐
│ SETTLED │
└───────┬───────┘
│
v
┌───────────────┐
│ REFUNDED │
│ (full/partial)│
└───────────────┘
Subscription State Machine
┌──────────┐
│ ACTIVE │◄──────────────────────────┐
└────┬─────┘ │
│ │
┌───────────────┼───────────────┐ │
│ │ │ │
v v v │
┌──────────┐ ┌───────────┐ ┌─────────────┐ ┌───────────┐
│ PAUSED │ │ PAST_DUE │ │ CANCELLING │ │ resume │
└────┬─────┘ └─────┬─────┘ └──────┬──────┘ └───────────┘
│ │ │ ^
│ v v │
│ ┌────────────┐ ┌────────────┐ │
│ │ SUSPENDED │ │ CANCELLED │ │
│ └─────┬──────┘ └────────────┘ │
│ │ │
│ v │
│ ┌────────────┐ │
│ │ CANCELLED │ │
│ └────────────┘ │
│ │
└──────────────────────────────────────────────────┘
Transitions:
ACTIVE → PAUSED (merchant/customer pause request)
ACTIVE → PAST_DUE (3 consecutive billing failures)
ACTIVE → CANCELLING (cancel at end of cycle)
ACTIVE → CANCELLED (cancel immediately)
ACTIVE → COMPLETED (max_cycles reached or end_date passed)
PAUSED → ACTIVE (resume — manual or scheduled_resume_date)
PAST_DUE → ACTIVE (successful retry payment)
PAST_DUE → SUSPENDED (7 days past due with no successful payment)
SUSPENDED → CANCELLED (auto or manual)
CANCELLING → CANCELLED (after final billing cycle)
5. Technical Stack
| Component | Technology |
|---|---|
| Language | Kotlin (JVM 25) |
| Framework | Spring Boot 4.0.2 |
| HTTP Client | Spring WebClient (non-blocking) for TransIT calls |
| Database | Cloud Spanner (PostgreSQL dialect) — transactions schema |
| Messaging | Cloud Pub/Sub — transaction events |
| Secrets | Cloud KMS-encrypted in CDE Spanner (see Merchant Onboarding Service) |
| Build | Bazel |
| Testing | JUnit 5, Testcontainers, WireMock (TransIT mock) |
6. Database Schema
CREATE TABLE transactions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
merchant_id VARCHAR(64) NOT NULL,
type VARCHAR(20) NOT NULL, -- SALE, AUTH, CAPTURE, VOID, REFUND, RECURRING
status VARCHAR(20) NOT NULL, -- PENDING, APPROVED, DECLINED, etc.
amount BIGINT NOT NULL, -- cents
processed_amount BIGINT,
currency VARCHAR(3) DEFAULT 'USD',
card_brand VARCHAR(20),
card_last4 VARCHAR(4),
card_data_source VARCHAR(20),
industry_type VARCHAR(4),
approval_code VARCHAR(20),
response_code VARCHAR(10),
response_message VARCHAR(255),
transit_txn_id VARCHAR(64),
token_id VARCHAR(64),
avs_result VARCHAR(4),
cvv_result VARCHAR(4),
external_ref_id VARCHAR(64),
parent_txn_id UUID REFERENCES transactions(id), -- for captures, refunds
metadata JSONB,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
tip_amount BIGINT DEFAULT 0,
surcharge_amount BIGINT DEFAULT 0,
cash_discount_amount BIGINT DEFAULT 0,
l2_data JSONB,
l3_data JSONB,
enrichment_status VARCHAR(20), -- PENDING, APPLIED, SKIPPED_PIN_DEBIT, FAILED
is_pin_debit BOOLEAN DEFAULT false,
settled_at TIMESTAMPTZ
);
CREATE INDEX idx_transactions_merchant ON transactions(merchant_id, created_at DESC);
CREATE INDEX idx_transactions_status ON transactions(status);
CREATE INDEX idx_transactions_external_ref ON transactions(external_ref_id);
CREATE INDEX idx_transactions_transit_txn ON transactions(transit_txn_id);
CREATE INDEX idx_transactions_enrichment ON transactions(enrichment_status) WHERE enrichment_status = 'PENDING';
CREATE TABLE batch_settlement_config (
merchant_id VARCHAR(64) PRIMARY KEY,
settlement_mode VARCHAR(20) NOT NULL DEFAULT 'SCHEDULED',
scheduled_time TIME NOT NULL DEFAULT '22:30',
timezone VARCHAR(50) NOT NULL DEFAULT 'America/New_York',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE subscriptions (
id VARCHAR(64) PRIMARY KEY,
merchant_id VARCHAR(64) NOT NULL,
customer_id VARCHAR(128) NOT NULL,
token_id VARCHAR(64) NOT NULL,
plan_name VARCHAR(255),
amount BIGINT NOT NULL,
currency VARCHAR(3) DEFAULT 'USD',
interval VARCHAR(20) NOT NULL, -- DAILY, WEEKLY, MONTHLY, QUARTERLY, YEARLY
interval_count INTEGER NOT NULL DEFAULT 1,
status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
-- ACTIVE, PAUSED, PAST_DUE, SUSPENDED, CANCELLING, CANCELLED, COMPLETED
current_cycle INTEGER NOT NULL DEFAULT 0,
next_billing_date DATE NOT NULL,
last_billing_date DATE,
last_billing_status VARCHAR(20), -- SUCCESS, DECLINED, ERROR
start_date DATE NOT NULL,
end_date DATE,
max_cycles INTEGER,
failed_attempts INTEGER NOT NULL DEFAULT 0,
cancellation_reason VARCHAR(500),
scheduled_resume_date DATE,
metadata JSONB,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_subscriptions_merchant ON subscriptions(merchant_id);
CREATE INDEX idx_subscriptions_customer ON subscriptions(merchant_id, customer_id);
CREATE INDEX idx_subscriptions_billing ON subscriptions(status, next_billing_date)
WHERE status IN ('ACTIVE', 'PAST_DUE');
CREATE TABLE subscription_billing_events (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
subscription_id VARCHAR(64) NOT NULL REFERENCES subscriptions(id),
cycle INTEGER NOT NULL,
transaction_id VARCHAR(64),
amount BIGINT NOT NULL,
status VARCHAR(20) NOT NULL, -- SUCCESS, DECLINED, ERROR, RETRY_SCHEDULED
failure_reason VARCHAR(500),
retry_count INTEGER NOT NULL DEFAULT 0,
next_retry_at TIMESTAMPTZ,
billed_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_billing_events_sub ON subscription_billing_events(subscription_id, cycle DESC);
CREATE TABLE settlement_batches (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
merchant_id VARCHAR(64) NOT NULL,
batch_date DATE NOT NULL,
total_sales BIGINT NOT NULL DEFAULT 0,
total_refunds BIGINT NOT NULL DEFAULT 0,
net_amount BIGINT NOT NULL DEFAULT 0,
txn_count INTEGER NOT NULL DEFAULT 0,
status VARCHAR(20) NOT NULL, -- PENDING, RECONCILED, DISCREPANCY
reconciled_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
7. Pub/Sub Events
| Topic | Event | Payload |
|---|---|---|
transaction-events | transaction.approved | Full transaction object |
transaction-events | transaction.declined | Full transaction object |
transaction-events | transaction.voided | Transaction ID, voided timestamp |
transaction-events | transaction.refunded | Transaction ID, refund amount |
transaction-events | transaction.settled | Batch ID, settlement details |
subscription-events | subscription.created | Subscription object |
subscription-events | subscription.billed | Subscription ID, cycle, transaction ID |
subscription-events | subscription.payment_failed | Subscription ID, cycle, failure reason |
subscription-events | subscription.past_due | Subscription ID, failed attempts count |
subscription-events | subscription.suspended | Subscription ID, suspension reason |
subscription-events | subscription.cancelled | Subscription ID, cancellation reason |
subscription-events | subscription.paused | Subscription ID, resume date |
subscription-events | subscription.resumed | Subscription ID, next billing date |
webhook-delivery | webhook.send | Webhook URL, payload, merchant config |
8. Configuration
# application.yml
spring:
application:
name: processing-service
datasource:
url: jdbc:postgresql://${DB_HOST}:5432/gateway
username: ${DB_USER}
password: ${DB_PASSWORD}
transit:
base-url: ${TRANSIT_BASE_URL}
device-id: ${TRANSIT_DEVICE_ID} # from Secret Manager
transaction-key: ${TRANSIT_TXN_KEY} # from Secret Manager
timeout-ms: 30000
retry:
max-attempts: 3
initial-delay-ms: 1000
multiplier: 2.0
server:
port: 8080
9. Health Check
GET /actuator/health
Response:
{
"status": "UP",
"components": {
"db": { "status": "UP" },
"transitApi": { "status": "UP", "latency": "120ms" }
}
}