Skip to main content
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
Isolation boundaries:
  • 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
Lookup: 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 on method 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

Docker Compose: infrastructure/docker/docker-compose.payment.yml with dedicated Caddy reverse proxy for TLS termination.