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.

Rename note: the current gateway tenant model is Organization -> Location. Legacy merchantId fields remain in parts of the online transaction wire contract; on this page they should be read as concrete location ids, not as a separate merchant-first tenant model.

1.1 Rename guardrails

Safe statements:

  • hosted checkout and hosted payment flows still accept legacy merchantId fields in parts of the wire contract
  • those values identify the selected concrete location
  • new docs and app state should still prefer organization/location terminology

Unsafe statements:

  • that hosted-payment APIs are exempt from the organization/location rename
  • that merchantId in these examples means a different tenant layer from Location
  • that new SDK or product documentation should keep introducing merchant-first names because the wire alias still exists

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 screeningBasic velocity checks, IP reputation (extensible)
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.pinpointgateway.com/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:
{
"merchantId": "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.pinpointgateway.com/button/btn_abc123.js\"></script>",
"checkoutUrl": "https://pay.pinpointgateway.com/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:
{
"merchantId": "loc_abc123",
"amount": 2500,
"currency": "USD",
"description": "Order #12345",
"theme": {
"primaryColor": "#1a73e8"
}
}

Response (201):
{
"sessionId": "ifs_abc123",
"iframeUrl": "https://pay.pinpointgateway.com/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

3.1 Docs-side closeout checklist

Treat this page as closure-grade from the repo side only if it is clear that:

  • legacy merchantId fields are wire compatibility
  • the preferred tenant model is still organization/location
  • support can describe hosted checkout and hosted payment flows without implying that the broader rename was partial or reversed

4. Webhook Delivery System

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.cancelledSubscription cancelled
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: sha256=<HMAC-SHA256(webhook_secret, 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 │ │ CANCELLED │
└────┬────┘ └───────────┘ └───────────┘
│ ^
│ ┌───────────┐ │
├─────────>│ EXPIRED │ │
│ └───────────┘ │
│ │
└──────────────────────────────────┘

6. Fraud Screening (Basic)

CheckDescription
Velocity limitMax N transactions per card per hour
Amount limitMax single transaction amount per merchant
IP reputationBlock known malicious IPs (GCP reCAPTCHA Enterprise, future)
Card BIN checkValidate BIN country matches billing country
Duplicate detectionSame card + amount within 5 minutes = warning

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.cancelled'
],
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 /actuator/health