# 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. ```go // 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 - [x] `go test ./internal/service/booking/...` - [x] `go test ./internal/repository/booking/...` - [x] `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 - [x] Unit tests mocking Stripe client to validate `SyncStripePayments` mapping and repository calls. - [x] Route handler tests confirming 401 protection and sync invocation (can use Echo context test helpers). - [x] `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 - [x] Unit tests for webhook handler verifying signature failure -> 400, success -> upsert call. - [x] 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. - [x] 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 Stripe’s 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`