Last verified: 2026-02-12 | Commit scope: fa4aa75
Overview
The payment service is an isolated microservice with its own database, VPC, and deployment lifecycle. Responsibilities:- Payment initiation and processing
- Price quotes and pricing configuration
- Webhook handling from payment providers
- Event publishing for payment lifecycle
- Separate Go module (Go Workspace in
hoodcloud/payment-service) - Separate PostgreSQL instance
- Network isolation (designed for separate VPC)
- No card data touches our systems (SAQ A compliance)
Communication Architecture
Sync: gRPC + mTLS
| gRPC Method | Description |
|---|---|
InitiatePayment | Creates payment session |
GetQuote | Returns pricing for items |
GetPayment | Retrieves payment state |
CancelPayment | Cancels pending payment |
payment-service/internal/server/grpc.go:cnAuthInterceptor). Standard gRPC health check service (grpc.health.v1.Health).
Async: NATS JetStream
| Event | Subject | Consumer Action |
|---|---|---|
PaymentCompletedEvent | payment.completed | Create subscriptions, trigger provisioning |
PaymentFailedEvent | payment.failed | Log failure, notify user |
PaymentCanceledEvent | payment.canceled | Update records |
main-app-payment), manual ACK, idempotency via Redis store.
See also: Workflows — NATS Consumers for the consumer implementation in the main app.
Database Schema
Separate PostgreSQL instance with four tables:| Table | Purpose |
|---|---|
payments | Core payment record (status, amount, provider reference, crypto details) |
payment_line_items | Line items per payment (chain_profile_id, node_type, duration, pricing) |
payment_events | Audit trail (actor, event_type, request_id, IP, JSONB details) |
customers | Minimal user mirror (id matches main app user_id, wallet_address, email) |
Payment Status Flow
Data Isolation
| Data | Payment Service | Main App |
|---|---|---|
| User identity | Mirror (customer_id only) | Primary |
| Payment records | Primary | Reference only |
| Subscriptions | Reference only | Primary |
Pricing Service
Config-based (not database-driven) for simplicity and auditability. File:payment-service/config/pricing.yaml
PricingService.GetPrice(chainProfileID, nodeType, duration) in payment-service/internal/service/pricing.go.
Multi-Provider Architecture
Multiple providers active simultaneously, keyed by payment method. Provider selection based onmethod field in payment request. Registered in map[PaymentMethod]Provider during startup.
Provider Adapter Interface
File:payment-service/internal/adapters/provider.go
Available Adapters
| Adapter | Method | Status | Notes |
|---|---|---|---|
stripe | card | Implemented | Stripe Checkout Sessions, webhook signature verification |
tempo | crypto | Implemented | TIP-20 TransferWithMemo, payment ID as bytes32 memo |
mock | any | Implemented | Configurable delay and failure rate for testing |
Stripe Adapter
Package:payment-service/internal/adapters/stripe/
Flow: InitiatePayment -> Stripe Checkout Session -> user completes payment -> Stripe webhook (POST /webhooks/stripe) -> HandleWebhook verifies Stripe-Signature -> CompletePayment -> NATS payment.completed.
Webhook events: checkout.session.completed, checkout.session.async_payment_succeeded, checkout.session.async_payment_failed, checkout.session.expired.
Config: STRIPE_ENABLED=true. Credentials (stripe_secret_key, stripe_webhook_secret) stored in Vault.
Tempo Adapter
Package:payment-service/internal/adapters/tempo/
Flow: InitiatePayment returns receiver address + memo (payment ID as hex) -> user calls transferWithMemo() on TIP-20 contract -> background watcher detects TransferWithMemo event -> CompletePayment -> NATS payment.completed.
Config: TEMPO_ENABLED=true, TEMPO_RECEIVER_ADDRESS.
Payment Methods Endpoint
GET /api/v1/payment-methods returns active methods based on enabled providers:
Main App Integration
Payment Initiation
File:internal/api/handler_payment.go
POST /api/v1/payments initiates a checkout session via gRPC to the payment service.
| Field | Required | Description |
|---|---|---|
items | Yes | Line items (chain_profile_id, node_type, duration) |
subscription_ids | No | pending_payment subscriptions to link |
method | Yes | "card" or "crypto" |
crypto_currency | When method=crypto | "USDC", "USDT", or "ETH" |
idempotency_key | Yes | Client-generated key for safe retries |
return_url / cancel_url | Yes | Redirect URLs after checkout |
pending_payment), initiates payment via gRPC, sets payment_id on each subscription via UpdatePaymentID.
gRPC Client
File:internal/grpc/payment_client.go
Connects via mTLS. Credentials from Vault or file paths.
gRPC Service Definition
File:payment-service/proto/payment.proto
Vault Integration
Package:payment-service/internal/vault/
Independent Vault client (separate from main app). AppRole authentication with token renewal. Retrieves PaymentCredentials (DB password, Redis password, Stripe keys, NATS CTRL account signing seed).
See also: Vault for Vault setup, secret structure, and operations. See Environment Variables for the complete payment service Vault configuration variables.
Development
infrastructure/docker/docker-compose.payment.yml with dedicated Caddy reverse proxy for TLS termination.
| Service | Port | Description |
|---|---|---|
| caddy-payment | 443 | HTTPS (TLS termination, Let’s Encrypt) |
| postgres-payment | 5433 | Payment database |
| payment-service | 50051 | gRPC API |
| payment-service | 8085 | HTTP health checks |
| payment-service | 9086 | Prometheus metrics |
Related Documents
- Overview — System overview
- Workflows — NATS payment consumer, subscription lifecycle
- Domain Model — Subscription state machine
- Extending — Adding payment providers
- Environment Variables — Complete env var reference