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/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/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