rentease/thoughts/shared/plans/2025-10-03-stripe-payment-sync.md

11 KiB
Raw Permalink Blame History

Stripe Payment Sync Implementation Plan

Overview

Enable Stripe-backed payment ingestion so card transactions flow automatically into Rentease while preserving manual cash entry. The system will store Stripe identifiers, support manual/cron backfills, and react to Stripe webhooks for real-time updates and refunds.

Current State Analysis

  • Booking payments are entered manually via /payments/:id, persisting amount and payment_method only; no external identifiers exist (internal/server/handle_payments.go:16, internal/service/booking/payment.go:17).
  • Reports and booking summaries rely on totals from the payments table, especially card totals computed via SQL joins (internal/repository/booking/pg_store.go:65, internal/service/booking/report.go:76).
  • No Stripe configuration or drivers; config.Config lacks keys, and main.go wires only database, parser, pdf clients (internal/config/config.go:11, main.go:52).
  • Routes bundle payment handlers inside auth-protected groups, meaning Stripe webhooks need a dedicated public endpoint (internal/server/routes.go:8).
  • Cron binary exists for scheduled jobs but has no payment sync job (pkg/cron/cron.go:9, cmd/cron/main.go:13).

Desired End State

  • payments records include Stripe IDs and status for card transactions, allowing idempotent updates and refund tracking.
  • Configurable Stripe client (secret key, webhook secret) powers both range-based syncs and webhook ingestion.
  • Manual sync endpoint/job triggers Stripe List APIs for a given time window and upserts records; the job can be run via API or mounted in the cron scheduler.
  • Public webhook endpoint verifies Stripe signatures and handles payment_intent.succeeded and charge.refunded, updating local payments accordingly.
  • Manual payment modal remains for cash/cheque/transfer; card payments can now be imported automatically.

Key Discoveries

  • Manual payment creation flow is htmx-based and fully server-side (internal/view/booking_by_id.templ:130).
  • Repository lacks idempotent upsert capability; new helper needed to match on Stripe ID (internal/repository/booking/pg_store.go:150).
  • config.Host.PaymentMethods already enumerates methods shown in UI; Stripe integration must normalise to these values (internal/config/host.go:41).

Out of Scope

  • Customer-level Stripe data (customer objects, saved cards).
  • UI changes for displaying Stripe-specific metadata beyond status/method.
  • Handling non-card Stripe events (disputes, payouts) beyond refunds.

Implementation Approach

Augment the data model with Stripe identifiers, introduce a Stripe driver/service for syncing, expose both manual and scheduled sync triggers, and add a webhook for near real-time updates. Ensure idempotency via unique Stripe IDs and keep manual cash entry unaffected.

Phase 1: Data Model & Repository Extensions

Overview

Add fields to persist Stripe metadata and repository logic for idempotent upserts based on Stripe IDs.

Changes Required

File: internal/service/booking/models.go Changes: Add StripePaymentID *string and StripeStatus *string fields to Payment (nullable to support manual entries) with unique index on Stripe ID. Ensure gorm tags reflect uniqueness and index requirements.

File: internal/repository/booking/pg_store.go Changes:

  • Update CreatePayment to respect new fields.
  • Add UpsertStripePayment method: find by stripe_payment_id, update status/amount/method if exists; otherwise create.
  • Update GetPayment/UpdatePayment logic to preload new fields.

File: main.go Changes: Trigger database.Migrate after model changes to add new columns (AutoMigrate will handle column additions).

File: scripts/payment_migration.sql Changes: (Optional) Document SQL backfill to populate Stripe IDs later if needed.

// Payment model sketch
type Payment struct {
    gorm.Model
    BookingID       uint    `gorm:"not null;index"`
    Booking         Booking `gorm:"foreignKey:BookingID;constraint:OnDelete:CASCADE"`
    Amount          float64
    PaymentMethod   config.PaymentMethod
    StripePaymentID *string `gorm:"uniqueIndex"`
    StripeStatus    *string
}

Success Criteria

Automated Verification

  • go test ./internal/service/booking/...
  • go test ./internal/repository/booking/...
  • golangci-lint run

Manual Verification

  • Run application; AutoMigrate adds new columns without data loss.
  • Existing manual payment entry still works and shows in UI.

Phase 2: Stripe Client & Sync Service

Overview

Introduce Stripe SDK usage, configuration, and a service method to fetch payments for a time range, process them, and upsert into storage. Expose a job callable via API and cron.

Changes Required

File: go.mod Changes: Add github.com/stripe/stripe-go/v83 dependency (or latest version) plus go mod tidy.

File: internal/config/config.go Changes: Add fields StripeSecretKey, StripeWebhookSecret, optional StripeConnectAccount (if needed). Update env tags and documentation.

File: internal/driver/stripe/client.go (new) Changes: Implement wrapper around Stripe client to list PaymentIntent (or Charge) objects within a Created range, retrieving status, amount, currency, and metadata/booking reference if available.

File: internal/service/booking/payment.go Changes: Add SyncStripePayments(ctx, params) method that:

  • Calls driver to list payments between from/to timestamps.
  • Maps Stripe payment method/status to internal values.
  • Resolves booking association (e.g., via metadata booking_id or manual mapping; define behaviour if not found).
  • Calls repository UpsertStripePayment (with fallback logging if booking unknown).

File: internal/service/booking/service.go Changes: Extend interface with UpsertStripePayment and register driver in constructor (add parameter for Stripe client).

File: cmd/cron/main.go Changes: Add job (e.g., StripePaymentSync) that invokes service with last-run timestamp stored persistently (file/db) or via parameter. For initial implementation, fetch daily by default.

File: pkg/cron/cron.go Changes: Optionally enhance scheduler to accept context cancellation or job-specific config; ensure job invocation errors bubble through channels.

File: internal/server/handle_api_sync.go Changes: Add new handler handleStripeSync(bs *booking.Service) bound to POST /api/stripe/sync expecting payload (from, to). Validate API key via existing middleware and trigger sync job synchronously (or enqueue background routine).

Success Criteria

Automated Verification

  • Unit tests mocking Stripe client to validate SyncStripePayments mapping and repository calls.
  • Route handler tests confirming 401 protection and sync invocation (can use Echo context test helpers).
  • go test ./cmd/cron for scheduler wiring.

Manual Verification

  • Configure Stripe test key; run API sync for a test window; verify payments appear in booking detail.
  • Cron job logs success when run with shortened schedule in dev.

Phase 3: Webhook Endpoint & Event Processing

Overview

Accept Stripe webhook notifications to keep payment status current (successes and refunds) with signature verification.

Changes Required

File: internal/server/routes.go Changes: Register POST /webhooks/stripe prior to auth middleware so Stripe can reach it; ensure CORS is acceptable.

File: internal/server/handle_stripe_webhook.go (new) Changes:

  • Parse body, verify signature using StripeWebhookSecret via Stripe SDK helper.
  • Handle payment_intent.succeeded: locate booking association (metadata); call service upsert with status Succeeded.
  • Handle charge.refunded (and partial refunds) by updating payment amount/status (set StripeStatus = "refunded", adjust amounts if partial, record refund metadata).
  • Return 2xx quickly; log and capture errors.

File: internal/service/booking/payment.go Changes: Add helper ApplyStripeEvent to centralise webhook logic, reuse upsert.

File: internal/config/config.go Changes: Document required webhook secret env var.

File: main.go Changes: Inject Stripe client + webhook secret into server (extend server.New options or add to booking service).

Success Criteria

Automated Verification

  • Unit tests for webhook handler verifying signature failure -> 400, success -> upsert call.
  • Service tests ensuring refunds update amounts correctly.

Manual Verification

  • Use Stripe CLI stripe listen to forward events locally; confirm succeeded and refund events update bookings.
  • Invalid signature events rejected (check logs).

Phase 4: Admin Controls, UI Confirmation, and Regression Testing

Overview

Expose manual triggers, ensure UI reflects new automatic payments, and validate the mixed workflow.

Changes Required

File: internal/view/booking_by_id.templ Changes: Optionally add indicator for Stripe-synced payments (e.g., status badge) while keeping manual modal intact.

File: internal/server/handle_payments.go Changes: Ensure manual flow still sets StripePaymentID/StripeStatus nil. Consider preventing duplicate manual card entries by clarifying methods.

File: docs/ (new doc) Changes: Document Stripe setup, env vars, webhook configuration, manual sync API usage, cron schedule.

File: internal/server/handle_reports.go Changes: Confirm card totals include Stripe payments; adjust if status filtering needed (e.g., exclude refunded amounts from revenue).

Success Criteria

Automated Verification

  • Snapshot or template tests (if available) confirm payment list includes expected fields.
  • go test ./internal/view/... (if templ tests) or run templ generate to ensure templates compile.

Manual Verification

  • Booking page shows Stripe-imported payment with correct method/status.
  • Manual cash entry unaffected.
  • Documentation reviewed for completeness.

Testing Strategy

  • Unit tests mocking Stripe client and repository behaviour to ensure idempotency and status handling.
  • Integration test hitting /api/stripe/sync with fake Stripe driver to verify end-to-end upsert (could use in-memory driver for tests).
  • Webhook handler tests using Stripes official test helpers for signature verification.
  • Manual QA in Stripe test mode: create payment intent with metadata linking to booking ID, trigger success/refund, verify UI/report updates.

Performance Considerations

  • Stripe list API pagination: ensure sync job handles pagination and rate limits (backoff on errors).
  • Webhook handler should be lightweight; consider asynchronous processing (goroutine or queue) if high volume, though initial volume likely low.

Migration Notes

  • AutoMigrate adds nullable columns; ensure production deploy runs with Stripe env vars set (or feature toggled off until configured).
  • Consider backfilling existing Stripe payments manually post-deploy using the new API sync endpoint.

References

  • Research doc: thoughts/shared/research/2025-10-03-stripe-payment-sync.md
  • Manual payment handler: internal/server/handle_payments.go:16
  • Booking service payment logic: internal/service/booking/payment.go:17
  • Reports card totals: internal/repository/booking/pg_store.go:65