Skip to main content

Billing Service

The Billing service manages Stripe subscriptions and maps them to GospeLib entitlements. It receives Stripe webhook events, maintains subscription state in PostgreSQL, and caches entitlement lookups in Redis for the gateway.

Quick Reference

PropertyValue
Port8300
LanguageGo
FrameworkChi v5
Module pathgithub.com/gospelib/main/services/billing
Entry pointcmd/server/main.go
Payment providerStripe
Data storePostgreSQL (gl_subscriptions, gl_stripe_events)

Responsibilities

  • Stripe webhook handling — Receives and processes subscription lifecycle events
  • Subscription management — Tracks plan status (active, trialing, canceled, past_due)
  • Entitlement mapping — Derives feature access from subscription plans
  • Entitlement caching — Pushes entitlements to Redis (60s TTL) for gateway lookups
  • Idempotency — Deduplicates webhook events via gl_stripe_events table

Subscription Plans

Plans are defined in configuration, not hardcoded:

PlanPriceKey Entitlements
Reader (free)$0scriptures_read, basic_search, topical_guide_browse
Scholar$7.99/mo or $79.99/yrAll Reader + interlinear_hebrew_greek, manuscript_witnesses, ai_features, knowledge_graph_explorer
Academic$14.99/mo or $149.99/yrAll entitlements (unlimited)

Rate Limits by Plan

ResourceFreeScholarAcademic
AI requests5/hour50/hour200/hour
Search20/min200/min1000/min

Running Locally

cd services/billing
go run ./cmd/server

The service starts on port 8300 and requires PostgreSQL to be running.

Environment Variables

VariableDefaultDescription
GOSPELIB_BILLING_PORT8300HTTP listen port
GOSPELIB_BILLING_DATABASE_URLpostgres://localhost:5432/gospelibPostgreSQL connection string
GOSPELIB_BILLING_STRIPE_SECRET_KEYStripe API secret key
GOSPELIB_BILLING_STRIPE_WEBHOOK_SECRETStripe webhook signing secret
GOSPELIB_BILLING_REDIS_URLredis://localhost:6380Redis for entitlement caching

Health Check

curl http://localhost:8300/health
# {"status": "ok"}

Entry Point Pattern

Standard Go service entry point at cmd/server/main.go:

  1. Configure Zerolog (JSON structured logging)
  2. Load config from environment variables
  3. Initialize PostgreSQL connection + run migrations
  4. Load plan configuration
  5. Create Chi router with middleware
  6. Register routes — health, webhook (public), billing endpoints (authenticated)
  7. Start HTTP server in a goroutine
  8. Wait for SIGINT/SIGTERM
  9. Graceful shutdown with 10-second context

PostgreSQL Schema

CREATE TABLE gl_subscriptions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES gl_users(id),
stripe_sub_id TEXT UNIQUE NOT NULL,
plan_id TEXT NOT NULL,
status TEXT NOT NULL, -- active | trialing | canceled | past_due
current_period_start TIMESTAMPTZ NOT NULL,
current_period_end TIMESTAMPTZ NOT NULL,
cancel_at_period_end BOOLEAN DEFAULT FALSE,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE TABLE gl_stripe_events (
stripe_event_id TEXT PRIMARY KEY, -- idempotency key
event_type TEXT NOT NULL,
processed_at TIMESTAMPTZ DEFAULT NOW()
);

Docker

FROM golang:1.25-alpine AS builder
WORKDIR /build
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /app ./cmd/server

FROM alpine:3.20
RUN apk --no-cache add ca-certificates
COPY --from=builder /app /app
EXPOSE 8300
ENTRYPOINT ["/app"]