mirror of
https://github.com/rjNemo/rentease.git
synced 2026-06-12 05:36:49 +00:00
Introduce Stripe integration for automatic payment ingestion and refund tracking. Adds new fields to the payment model for Stripe IDs and status, Stripe client driver, sync service, cron job, manual API endpoint, and public webhook handler for real-time updates. Includes tests and documentation. Manual cash entry remains supported.
234 lines
11 KiB
Markdown
234 lines
11 KiB
Markdown
# 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/v79` 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`
|