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
Security: TLS 1.3 minimum, client certificate verification (mTLS), CN auth interceptor validates client certificate CN against allowed list (
payment-service/internal/server/grpc.go:cnAuthInterceptor). Standard gRPC health check service (grpc.health.v1.Health).
Async: NATS JetStream
Delivery: Durable consumer (
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:Payment Status Flow
Data Isolation
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
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.
Subscription linking: Handler validates each subscription ID (exists, owned by caller, status
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.
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