# 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`
