Skip to content

Pledge System: Records, recurring donations, reminders, failures, and APIs #72

Description

@grantfox-oss

Description

Add a pledge system so donors can commit to future donations (one-off scheduled pledges and recurring pledges), track fulfillment, receive reminders, and allow cancellation and failure handling. Persist pledge records, extend the schema as needed, provide backend APIs for creation/viewing/cancellation/status, implement recurring processing and reminder/failure workflows, and add tests and migrations.

Background

Currently donations are recorded at the time of payment. We need a pledge layer where donors can commit to contribute at future dates or on a recurring cadence (weekly/monthly). Pledges must be tracked, fulfilled (or marked failed), reminders sent, and cancellations supported. This enables better donor engagement and predictable funding.

Scope

  • In-scope: DB schema changes (schema.prisma), backend APIs, worker for recurring processing and reminders, notification templates, failure handling (retry/backoff), tests, migrations, and basic operational monitoring.
  • Out-of-scope: Payment provider integration details beyond defined hooks (we will call existing payment service or provide clear integration points), donor-facing UI (API contract only).

Goals

  • Persist pledge commitments with metadata (amount, schedule, donor, campaign, status).
  • Support one-time scheduled pledges and recurring pledges (weekly/monthly).
  • Track fulfillment attempts and final status (fulfilled, failed, cancelled).
  • Send reminders before scheduled fulfillment and on failures.
  • Provide endpoints for pledge creation, viewing, cancellation, and status tracking.
  • Provide operational safety (idempotency, retries, rate-limiting).

Acceptance Criteria

  • Pledge records are persisted with clear status & history.
  • Recurring pledges generate scheduled donation attempts per cadence.
  • Reminders sent to donors before scheduled attempt (configurable window).
  • Failures are retried with configurable backoff and recorded; final failed attempts mark pledge as FAILED after threshold.
  • Owners/donors can cancel pledges and see status history.
  • APIs for create/view/cancel/list are implemented and authenticated.
  • Unit and integration tests cover main flows (create → attempt → success/failure → retry → cancel).
  • Migration applied and existing data unaffected.

Functional requirements

  • Pledge types:
    • ONE_OFF: scheduled single pledge at a specified date.
    • RECURRING: recurring pledge with cadence (WEEKLY | MONTHLY) and next-run date.
  • Pledge fields:
    • id, donorId, campaignId, amount, currency, type, cadence (for recurring), startDate, nextRunAt, endDate (optional), status (ACTIVE|PAUSED|CANCELLED|FAILED|COMPLETED), createdAt, updatedAt.
  • Fulfillment tracking:
    • pledge_attempts table to track each attempt: id, pledgeId, attemptAt, status (PENDING|SUCCESS|FAILED), providerReference, failureReason, retryCount, metadata.
  • API behavior:
    • Idempotent create requests (idempotency key).
    • Create pledge validates donor/campaign and amount.
    • Cancelation: immediate for future attempts; for in-flight attempts provide cancel token/behavior.
  • Reminders:
    • Send reminder emails and websocket events N days before nextRunAt (configurable).
  • Failure handling:
    • On failure, schedule retries up to M times with exponential/backoff; after M failed attempts mark attempt as FAILED and optionally mark pledge as PAUSED or FAILED depending on policy.

Data model changes (Prisma)

  • Update schema.prisma:
    • Add Pledge model:
      • id: String @id @default(uuid())
      • donorId: String (FK)
      • campaignId: String? (nullable)
      • amount: Decimal
      • currency: String
      • type: PledgeType (enum)
      • cadence: PledgeCadence? (enum)
      • startDate: DateTime
      • nextRunAt: DateTime?
      • endDate: DateTime?
      • status: PledgeStatus (enum)
      • metadata: Json?
      • createdAt, updatedAt
    • Add PledgeAttempt model:
      • id, pledgeId, attemptAt, status (enum), providerRef, failureReason, retryCount, metadata, createdAt
    • Enums: PledgeType, PledgeCadence, PledgeStatus, PledgeAttemptStatus
  • Files to update: schema.prisma. Add any necessary seed updates in seed.ts.

API contract (server-side)

  • Public/Donor endpoints (authenticated):
    • POST /pledges — Create pledge
      • Body: { donorId?, campaignId?, amount, currency, type: 'ONE_OFF'|'RECURRING', startDate, cadence?, endDate?, idempotencyKey? }
      • Response: pledge record.
    • GET /pledges/:id — Get pledge details and recent attempts.
    • GET /pledges — List donor pledges (pagination + filters).
    • POST /pledges/:id/cancel — Cancel a pledge (optional effective date).
      • Body: { reason? }
  • Admin endpoints:
    • GET /admin/pledges — List/filter pledges.
    • POST /admin/pledges/:id/pause — Pause/resume pledges.
    • GET /admin/pledges/:id/attempts — List attempts for audit.
  • Webhooks / Payment integration:
    • Provide hooks to integrate with the payment processor used by the project (existing donation flow).
    • Example: POST /webhooks/pledge-attempt-result to record result if asynchronous.
  • Suggested implementation files:
    • Controllers: src/controllers/pledge.controller.ts
    • Routes: src/routes/pledge.routes.ts
    • Services: src/services/pledge.service.ts, src/services/payment.service.ts (or integrate with donation.service.ts)
    • Tests: src/services/pledge.service.test.ts, src/controllers/pledge.controller.test.ts

Background worker / scheduler

  • Add src/workers/pledge.worker.ts:
    • Responsibilities:
      • Scan Pledge records with nextRunAt <= now and status = ACTIVE.
      • Create PledgeAttempt and call payment flow (sync/async).
      • On success: mark attempt SUCCESS, create donation record (reuse donation.service.ts), update nextRunAt for recurring pledges or mark completed for one-off.
      • On failure: record failure, increment retry count, schedule retry per backoff policy, send failure notification after first failure, mark as FAILED/PAUSED if retries exhausted.
    • Scheduler cadence: configurable (e.g., every minute for near-real-time, or hourly).
    • Consider idempotency and locking to avoid duplicate attempts (DB row lock or redis-based lease).
  • Reminder job:
    • Separate scheduled job or part of the worker: send reminders reminderWindow (configurable) before nextRunAt.

Notifications

  • Use notification.service.ts for:
    • Pre-fulfillment reminders.
    • Success confirmations (receipt).
    • Failure notifications with retry info and cancellation instructions.
  • Websocket events via socket.server.ts for real-time UI updates.

Logging & audit

  • Record each attempt in PledgeAttempt for auditability.
  • Log important events using logger.ts.
  • Admin endpoints expose attempt history for investigations.

Testing

  • Unit tests for pledge.service (schedule calculation, nextRunAt updates, retry/backoff).
  • Integration tests for end-to-end flow: create pledge → worker processes → donation created or failure handled.
  • Tests for idempotency: duplicate create with same idempotency key should not create duplicate pledges.
  • Mock payment provider in tests.

Migration & rollout

  • Add Prisma migration:
    • Command:
      npx prisma migrate dev --name add_pledges
  • Deploy to staging, run migration, smoke test pledge create/list flows.
  • Monitor worker logs and retry/failure rates.
  • Add feature flag env var to control worker activation.
  • Seed scripts: not required unless creating demo pledges.

Security & privacy

  • Only authenticated donors can create pledges for themselves; admin endpoints require admin role.
  • Sensitive payment/provider references stored securely (avoid raw card data).
  • Rate-limit pledge creation to prevent abuse.
  • Ensure PledgeAttempt metadata doesn't leak PII.

Implementation notes & suggestions

  • Reuse donation.service.ts to create donation records on successful attempts.
  • Use redis.ts or DB advisory locks to ensure single worker processes a pledge at a time.
  • Store idempotencyKey per pledge creation to avoid duplicates.
  • Make retry/backoff, reminderWindow, scheduler interval configurable via env.
  • Consider TTL cleanup for old PledgeAttempt metadata if retention policy demands.

Checklist (GitHub task list)

  • Design Pledge and PledgeAttempt models in schema.prisma
  • Create Prisma migration
  • Implement POST /pledges, GET /pledges/:id, GET /pledges, POST /pledges/:id/cancel
  • Implement admin endpoints for listing/pausing/resuming pledges
  • Implement src/workers/pledge.worker.ts for processing and reminders
  • Integrate with payment/donation flow to create donation records on success
  • Implement retry/backoff for failures and final failure policy
  • Add notification templates and websocket events
  • Add unit and integration tests
  • Deploy migration to staging and enable worker behind feature flag

Metadata

Metadata

Assignees

Labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions