CommunityPro / CommunityPro/community-pro-api

Sponsorship: Stripe webhooks, invoice mirroring, Brevo receipts (Phase 7.3, 7.4)

Open
#16 0 comments 0 reactions 0 assignees View on GitHub
sponsorship
Dominant language
C#
Stars
0
Forks
0
PR merge metrics
No merged PRs in 30d

Description

## Scope
`POST /webhooks/stripe` — signature-verified, idempotent, drives the sponsor lifecycle and local invoice mirror.

## Events
| Event | Action |
|---|---|
| `checkout.session.completed` | create/activate `Sponsor` from session metadata + customer/subscription ids |
| `invoice.paid` | upsert `SponsorInvoice` (amount, hosted URL, PDF); relay branded receipt/thank-you via Brevo incl. customer-portal link |
| `invoice.payment_failed` | upsert invoice as failed; sponsor → `PastDue` |
| `customer.subscription.updated` / `deleted` | sync `SponsorStatus` (Active/PastDue/Cancelled) |

## Rules
- Verify `Stripe-Signature` against the webhook secret on the **raw request body**.
- **Idempotent by Stripe event id** — same unique-constraint-insert pattern as the GitHub webhooks (Stripe retries for days). Unhandled event types → 200 no-op.
- Rely on **Stripe Invoicing** natively (subscriptions generate invoices; PDFs/emails enabled in Stripe settings) — local rows exist purely for the admin dashboard.
- Brevo sends best-effort via `IEmailSender`; failures never 500 the webhook (Stripe would retry and duplicate side effects — idempotency guard must cover this).

## Error codes
`webhooks.invalid_signature` (401); everything else 200

## Dependencies
Blocked by #14 and #15 (metadata contract).

## Acceptance criteria
- [ ] Replayed event id → no-op (tested for every handled event type)
- [ ] Out-of-order events don't corrupt status (e.g. `subscription.deleted` before a late `invoice.paid`)
- [ ] Signature failure → 401 without body processing
- [ ] Full lifecycle test: checkout → paid → failed → cancelled

### Conventions (project-wide, non-negotiable)
- .NET 9, records for immutable shapes, file-scoped namespaces, primary constructors where they read well. Minimal-API endpoints grouped per module via `IEndpointModule.MapEndpoints`.
- `Result` (SharedKernel) instead of exception-driven control flow. Endpoint results map failures to ProblemDetails with the stable error codes listed above — the frontend keys off them.
- Module owns its EF Core `DbContext` mapped to its own Postgres schema. Modules never reference each other's internals — cross-module needs go through a public contract interface or an in-process domain event (`IEventPublisher`).
- All external calls (GitHub, Stripe, Brevo, Cloudinary, Meilisearch) behind interfaces owned by the consuming module.
- Every list endpoint paginated (offset is fine). xUnit tests in `tests/CommunityPro.Tests//` following the existing harness patterns (see `Members/MembersTestHarness.cs`).

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.