Case study

Vif

A configurable platform for private communities. Walking Club is the first configuration; membership, events, bookings, payments and organiser operations are shared platform workflows.

Stack
Go 路 PostgreSQL 路 TypeScript

Problem

Separate tools, inconsistent state

Organisers were managing applications, access, events, payments and member communication across separate tools. Vif provides one source of truth for membership and booking state, with protected event details and explicit organiser controls.

My work

Application and delivery

I implemented the domain model, PostgreSQL schema and migrations, Go handlers and worker jobs, server-rendered UI, TypeScript components, provider integrations, deployment checks and automated tests.

Implementation

A modular Go application

Vif is a modular Go monolith with direct PostgreSQL access through pgx. User-facing pages are server-rendered; small TypeScript Web Components add interaction where needed. Keycloak supplies OIDC identity. A transactional outbox keeps notifications outside the request path, and Stripe Sandbox is isolated behind a payment boundary.

ApplicationGo modular monolith
DataPostgreSQL 路 pgx
BrowserHTML 路 CSS 路 TypeScript
ProvidersKeycloak 路 Stripe Sandbox 路 SMTP

Transaction boundaries

Make state changes explicit

Membership, event and booking state stays in PostgreSQL and is changed inside explicit transactions. This makes concurrency and rollback behaviour visible, at the cost of writing more SQL and domain-specific coordination than an ORM would hide.

Payment confirmation

Webhooks are authoritative

A successful browser redirect never grants a paid entitlement. Only a signature-verified, persisted and idempotently applied Stripe webhook can change payment-backed access. Confirmation can be less immediate, but duplicate or forged callbacks cannot create access.

Failure handling

Missing payment event

Vif keeps the registration pending instead of trusting the return URL. A bounded reconciliation job records that a provider event appears to be missing, without applying its payload. An organiser can then ask Stripe to resend the event; only the newly signature-verified webhook closes the finding and advances the state.

Verification

Automated and provider checks

  • Go unit, race and PostgreSQL integration tests
  • TypeScript type checks and browser-component tests
  • Playwright journeys across Chromium, Firefox and WebKit
  • Separate staging gates for real identity, mail and Stripe Sandbox providers