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.
11 KiB
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, persistingamountandpayment_methodonly; 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
paymentstable, 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.Configlacks keys, andmain.gowires 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
paymentsrecords 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.succeededandcharge.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.PaymentMethodsalready 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
CreatePaymentto respect new fields. - Add
UpsertStripePaymentmethod: find bystripe_payment_id, update status/amount/method if exists; otherwise create. - Update
GetPayment/UpdatePaymentlogic 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/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/totimestamps. - Maps Stripe payment method/status to internal values.
- Resolves booking association (e.g., via metadata
booking_idor 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
SyncStripePaymentsmapping and repository calls. - Route handler tests confirming 401 protection and sync invocation (can use Echo context test helpers).
go test ./cmd/cronfor 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
StripeWebhookSecretvia Stripe SDK helper. - Handle
payment_intent.succeeded: locate booking association (metadata); call service upsert with statusSucceeded. - Handle
charge.refunded(and partial refunds) by updating payment amount/status (setStripeStatus = "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 listento 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 runtempl generateto 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/syncwith 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