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
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).
- 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)
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
Goals
Acceptance Criteria
FAILEDafter threshold.Functional requirements
ONE_OFF: scheduled single pledge at a specified date.RECURRING: recurring pledge with cadence (WEEKLY|MONTHLY) and next-run date.id,donorId,campaignId,amount,currency,type,cadence(for recurring),startDate,nextRunAt,endDate(optional),status(ACTIVE|PAUSED|CANCELLED|FAILED|COMPLETED),createdAt,updatedAt.pledge_attemptstable to track each attempt:id,pledgeId,attemptAt,status(PENDING|SUCCESS|FAILED),providerReference,failureReason,retryCount,metadata.Ndays beforenextRunAt(configurable).Mtimes with exponential/backoff; afterMfailed attempts mark attempt asFAILEDand optionally mark pledge asPAUSEDorFAILEDdepending on policy.Data model changes (Prisma)
Pledgemodel:PledgeAttemptmodel:PledgeType,PledgeCadence,PledgeStatus,PledgeAttemptStatusAPI contract (server-side)
POST /pledges— Create pledge{ donorId?, campaignId?, amount, currency, type: 'ONE_OFF'|'RECURRING', startDate, cadence?, endDate?, idempotencyKey? }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).{ reason? }GET /admin/pledges— List/filter pledges.POST /admin/pledges/:id/pause— Pause/resume pledges.GET /admin/pledges/:id/attempts— List attempts for audit.POST /webhooks/pledge-attempt-resultto record result if asynchronous.src/controllers/pledge.controller.tssrc/routes/pledge.routes.tssrc/services/pledge.service.ts,src/services/payment.service.ts(or integrate withdonation.service.ts)src/services/pledge.service.test.ts,src/controllers/pledge.controller.test.tsBackground worker / scheduler
src/workers/pledge.worker.ts:Pledgerecords withnextRunAt <= nowandstatus = ACTIVE.PledgeAttemptand call payment flow (sync/async).SUCCESS, create donation record (reusedonation.service.ts), updatenextRunAtfor recurring pledges or mark completed for one-off.FAILED/PAUSEDif retries exhausted.reminderWindow(configurable) beforenextRunAt.Notifications
Logging & audit
PledgeAttemptfor auditability.Testing
pledge.service(schedule calculation, nextRunAt updates, retry/backoff).Migration & rollout
Security & privacy
PledgeAttemptmetadata doesn't leak PII.Implementation notes & suggestions
idempotencyKeyper pledge creation to avoid duplicates.PledgeAttemptmetadata if retention policy demands.Checklist (GitHub task list)
PledgeandPledgeAttemptmodels in schema.prismaPOST /pledges,GET /pledges/:id,GET /pledges,POST /pledges/:id/cancelsrc/workers/pledge.worker.tsfor processing and reminders