Skip to main content

Online Transaction Service

1. Overview

The Online Transaction Microservice handles e-commerce payment flows — checkout session management, hosted payment page backend, tokenized card-on-file payments, webhook delivery, and fraud screening. It delegates actual payment processing to the Processing Microservice. Location-owned requests use locationId.


2. Responsibilities

ResponsibilityDescription
Checkout session managementCreate, query, expire checkout sessions
Payment form backendValidate and process payment form submissions
Token managementStore and retrieve tokenized card references for card-on-file
Subscription managementCreate/update/cancel recurring billing subscriptions on behalf of merchants
Webhook deliveryDeliver payment event webhooks to merchant callback URLs
Fraud screeningPer-endpoint token-bucket limits plus optional reCAPTCHA Enterprise checkout-payment assessment and outcome annotation
Hosted payment flowsManage hosted payment button and iframe integrations for quick merchant onboarding
Merchant API key managementIssue, rotate, validate API keys for SDK/API access

3. API Specification

Authentication

Two auth modes:

ContextAuth Method
Server-to-server (SDK)Authorization: Bearer <api-key>
Checkout page (browser)Session-based (checkout session ID in URL)

Endpoints

Checkout Sessions

POST /api/v1/checkout/sessions
GET /api/v1/checkout/sessions/{sessionId}
POST /api/v1/checkout/sessions/{sessionId}/pay
POST /api/v1/checkout/sessions/{sessionId}/cancel

POST /api/v1/checkout/sessions
Authorization: Bearer <api-key>

Request:
{
"amount": 2500,
"currency": "USD",
"description": "Order #12345",
"customerEmail": "customer@example.com",
"successUrl": "https://merchant.com/success",
"cancelUrl": "https://merchant.com/cancel",
"webhookUrl": "https://merchant.com/webhooks",
"metadata": { "orderId": "12345" },
"expiresIn": 3600,
"allowedPaymentMethods": ["card"],
"captureMethod": "automatic"
}

Response (201):
{
"sessionId": "cs_live_abc123",
"checkoutUrl": "https://pay.peakgateway.co/c/cs_live_abc123",
"status": "PENDING",
"expiresAt": "2026-03-03T16:00:00Z"
}

Process Payment (called by checkout page)

POST /api/v1/checkout/sessions/{sessionId}/pay

Request:
{
"cardNumber": "4111111111111111",
"expirationDate": "1226",
"cvv": "123",
"cardHolderName": "John Doe",
"billingZip": "28202",
"saveCard": false
}

Response (200):
{
"status": "SUCCEEDED",
"transactionId": "txn_abc123",
"redirectUrl": "https://merchant.com/success?session_id=cs_live_abc123"
}

Response (200 - Declined):
{
"status": "DECLINED",
"message": "Card was declined. Please try a different payment method.",
"retryAllowed": true
}

Card-on-File (Tokenized Payments)

POST /api/v1/tokens
GET /api/v1/tokens?customerId=...
DELETE /api/v1/tokens/{tokenId}
POST /api/v1/payments/token

POST /api/v1/payments/token
Authorization: Bearer <api-key>

Request:
{
"tokenId": "tok_xyz789",
"amount": 2500,
"currency": "USD",
"description": "Recurring charge",
"metadata": { "subscriptionId": "sub_001" }
}

Response (200):
{
"transactionId": "txn_def456",
"status": "APPROVED",
"processedAmount": 2500
}

Hosted Payment Button

POST /api/v1/hosted/button
Authorization: Bearer <api-key>

Request:
{
"locationId": "loc_abc123",
"amount": 2500,
"currency": "USD",
"description": "Payment",
"successUrl": "https://merchant.com/success",
"cancelUrl": "https://merchant.com/cancel"
}

Response (201):
{
"buttonId": "btn_abc123",
"embedCode": "<script src=\"https://pay.peakgateway.co/button/btn_abc123.js\"></script>",
"checkoutUrl": "https://pay.peakgateway.co/b/btn_abc123"
}

Sale-only flow (no refund/void through button). Certification: Hosted Payment 2.0 — described as a "really simple certification process." Embeddable JavaScript snippet for merchant websites. Could be the first certified integration due to simplest certification path.

Hosted Payment iFrame

POST /api/v1/hosted/iframe-session
Authorization: Bearer <api-key>

Request:
{
"locationId": "loc_abc123",
"amount": 2500,
"currency": "USD",
"description": "Order #12345",
"theme": {
"primaryColor": "#1a73e8"
}
}

Response (201):
{
"sessionId": "ifs_abc123",
"iframeUrl": "https://pay.peakgateway.co/iframe/ifs_abc123",
"expiresAt": "2026-03-03T16:00:00Z"
}

Embeddable iFrame for seamless checkout within merchant's site. Secure cross-origin communication via postMessage API. Prioritized for speed — implement before certified backend portal. Card data stays within hosted iFrame (TSYS domain), reducing PCI scope.

API Key Management

POST /api/v1/api-keys # Generate new API key
GET /api/v1/api-keys # List API keys (masked)
DELETE /api/v1/api-keys/{keyId} # Revoke API key
POST /api/v1/api-keys/{keyId}/rotate # Rotate key

Subscriptions (Recurring Billing)

Merchant-facing subscription API. Proxies to Processing Microservice's subscription engine. All endpoints require Authorization: Bearer <api-key>.

POST /api/v1/subscriptions # Create subscription
GET /api/v1/subscriptions # List subscriptions
GET /api/v1/subscriptions/{subscriptionId} # Get subscription details
PATCH /api/v1/subscriptions/{subscriptionId} # Update subscription
POST /api/v1/subscriptions/{subscriptionId}/pause # Pause billing
POST /api/v1/subscriptions/{subscriptionId}/resume # Resume billing
POST /api/v1/subscriptions/{subscriptionId}/cancel # Cancel subscription
GET /api/v1/subscriptions/{subscriptionId}/billing-history # Billing history

POST /api/v1/subscriptions
Authorization: Bearer <api-key>

Request:
{
"customerId": "cust_001",
"customerEmail": "customer@example.com",
"tokenId": "tok_xyz789",
"planName": "Monthly Premium",
"amount": 4999,
"currency": "USD",
"interval": "MONTHLY",
"intervalCount": 1,
"startDate": "2026-04-01",
"endDate": null,
"maxCycles": null,
"webhookUrl": "https://merchant.com/webhooks/subscription",
"metadata": { "planId": "plan_premium" }
}

Response (201):
{
"subscriptionId": "sub_abc123",
"customerId": "cust_001",
"status": "ACTIVE",
"nextBillingDate": "2026-04-01",
"createdAt": "2026-03-07T10:00:00Z"
}

Subscription + Checkout flow: Merchants can combine checkout sessions with subscription creation — the first checkout collects card data and creates a Guardian token, then a subscription is created using that token.

POST /api/v1/checkout/sessions
{
"amount": 4999,
"description": "Monthly Premium — First Payment",
"saveCard": true,
"createSubscription": {
"planName": "Monthly Premium",
"interval": "MONTHLY",
"startDate": "2026-05-01"
}
}

On successful payment: token is stored, subscription is created with startDate for next billing cycle. First payment is processed immediately as a regular sale.

Webhooks

GET /api/v1/webhooks/events # List webhook event types
GET /api/v1/webhooks/deliveries # List delivery attempts
POST /api/v1/webhooks/deliveries/{id}/retry # Retry a failed delivery

4. Webhook Delivery System

online-txn owns webhook delivery — the ephemeral edge dispatch rows (one per event/destination attempt), retries, and dispatch state. It does not own webhook configuration/subscriptions; that durable merchant-admin state is owned by management. Each entity has exactly one writer and neither writes the other's tables — see Ownership: configuration vs. delivery.

Event Types

EventTrigger
payment.completedSale/auth approved
payment.declinedSale/auth declined
payment.voidedTransaction voided
payment.refundedRefund processed
payment.settledTransaction settled in batch
checkout.expiredCheckout session expired
subscription.createdNew subscription activated
subscription.billedRecurring charge succeeded
subscription.payment_failedRecurring charge declined/errored
subscription.past_dueMultiple billing failures — action needed
subscription.canceledSubscription canceled
subscription.pausedSubscription paused
subscription.resumedSubscription resumed
gift_card.issuedGift card issued under an organization
gift_card.redeemedGift card redemption succeeded
gift_card.reloadedGift card reload succeeded
gift_card.refundedRefund posted back to a gift card
gift_card.expiredGift card expired and remaining balance was written down
gift_card.revokedGift card revoked by an admin flow
gift_card.balance_lowGift card balance crossed the configured low-balance threshold
gift_card.adjustedPlatform-admin balance adjustment applied

Delivery Mechanism

  1. Receive event from Pub/Sub (transaction-events topic)
  2. Look up merchant webhook configuration
  3. Build webhook payload with event data
  4. Sign payload with HMAC-SHA256 using merchant's webhook secret
  5. POST to merchant's webhook URL
  6. Retry on failure: 3 attempts with exponential backoff (1min, 5min, 30min)
  7. Log all delivery attempts

Signature Verification (for merchants)

X-Pinpoint-Signature: <hex HMAC-SHA256(webhook_secret, timestamp + "." + raw_body)>
X-Pinpoint-Timestamp: 1709478000

// Merchant verification:
expected = HMAC-SHA256(webhook_secret, timestamp + "." + raw_body)
assert(expected == signature_from_header)
assert(abs(now - timestamp) < 300) // 5 minute tolerance

5. Checkout Session State Machine

┌─────────┐ ┌───────────┐ ┌───────────┐
│ PENDING │────>│ COMPLETED │ │ CANCELED │
└────┬────┘ └───────────┘ └───────────┘
│ ^
│ ┌───────────┐ │
├─────────>│ EXPIRED │ │
│ └───────────┘ │
│ │
└──────────────────────────────────┘

6. Fraud Screening

CheckDescription
Rate limitConfigurable per-endpoint token buckets in gateway.rate-limit.rules
Amount limitMax single transaction amount per merchant
reCAPTCHA EnterpriseOptional checkout-payment assessment using recaptchaToken, transaction data, and the configured risk threshold
Card BIN checkValidate BIN country matches billing country
Duplicate detectionSame card + amount within 5 minutes = warning

The reCAPTCHA path is fail-open: missing tokens or assessment errors do not block checkout. When an assessment returns a transaction-risk score below the configured threshold, the service returns a generic decline and annotates the assessment as fraudulent. Approved payments are annotated as legitimate so the model receives outcome feedback.


7. Technical Stack

ComponentTechnology
LanguageKotlin (JVM 25)
FrameworkSpring Boot 4.0.2
DatabaseCloud Spanner (PostgreSQL dialect) — online_sessions schema
MessagingCloud Pub/Sub — webhook delivery
BuildBazel
TestingJUnit 5, Testcontainers, WireMock

8. Database Schema

CREATE TABLE checkout_sessions (
id VARCHAR(64) PRIMARY KEY,
merchant_id VARCHAR(64) NOT NULL,
amount BIGINT NOT NULL,
currency VARCHAR(3) DEFAULT 'USD',
description VARCHAR(500),
customer_email VARCHAR(255),
success_url VARCHAR(512),
cancel_url VARCHAR(512),
webhook_url VARCHAR(512),
status VARCHAR(20) NOT NULL DEFAULT 'PENDING',
transaction_id VARCHAR(64),
metadata JSONB,
expires_at TIMESTAMPTZ NOT NULL,
completed_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_sessions_merchant ON checkout_sessions(merchant_id, created_at DESC);
CREATE INDEX idx_sessions_status ON checkout_sessions(status, expires_at);

CREATE TABLE stored_tokens (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
merchant_id VARCHAR(64) NOT NULL,
customer_id VARCHAR(128),
token_id VARCHAR(64) NOT NULL, -- TransIT token
card_brand VARCHAR(20),
card_last4 VARCHAR(4),
expiration_date VARCHAR(4),
card_holder_name VARCHAR(255),
status VARCHAR(20) DEFAULT 'ACTIVE',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_tokens_merchant_customer ON stored_tokens(merchant_id, customer_id);

CREATE TABLE api_keys (
id VARCHAR(64) PRIMARY KEY,
merchant_id VARCHAR(64) NOT NULL,
key_hash VARCHAR(128) NOT NULL, -- bcrypt hash of API key
key_prefix VARCHAR(12) NOT NULL, -- first 12 chars for identification
label VARCHAR(100),
status VARCHAR(20) DEFAULT 'ACTIVE',
last_used_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE TABLE subscription_webhook_config (
merchant_id VARCHAR(64) PRIMARY KEY,
webhook_url VARCHAR(512),
webhook_secret VARCHAR(128),
notify_on VARCHAR(255)[] DEFAULT ARRAY[
'subscription.billed',
'subscription.payment_failed',
'subscription.canceled'
],
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE TABLE webhook_deliveries (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
merchant_id VARCHAR(64) NOT NULL,
event_type VARCHAR(50) NOT NULL,
payload JSONB NOT NULL,
webhook_url VARCHAR(512) NOT NULL,
status VARCHAR(20) NOT NULL, -- PENDING, DELIVERED, FAILED
http_status INTEGER,
attempts INTEGER DEFAULT 0,
next_retry_at TIMESTAMPTZ,
delivered_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_webhook_status ON webhook_deliveries(status, next_retry_at);

9. Health Check

GET /health