| Field | Value |
|---|---|
| Date | 10 Jun 2026 |
| Status | Complete |
| GitHub Commits | 64c8069, 68921a8, ec34e2b, 01ff2b9, 9697cde, 42b0cbe, ff2c4b6, 80e2434, 59a0b33, 6097f3c, 65ba68d |
| Deployed | Dev + Prod (both at 65ba68d) |
A hardening and polish pass focused on the payment lifecycle. Stripe now sends a payment
receipt email no matter which webhook arrives first, and the webhook handlers are idempotent
so a customer can never be double-charged or have a duplicate Payment recorded. Added optional
BCC on invoice and payment emails, a refreshed brand logo across all surfaces, a fix for invoice
PDFs that showed DRAFT after being sent, a portal blank-page fix, and rate limiting on the portal
auth endpoints.
Commit 64c8069
The portal invoices page rendered blank because formatDate and formatMonthYear were referenced
but not imported/defined. Added the missing helpers to frontend/src/pages/PortalInvoices.jsx.
(See the canonical date utilities in frontend/src/utils/dateFormat.js.)
Commits 68921a8, ec34e2b
YealinLogo React component at frontend/src/components/YealinLogo.jsx,useId() so each instance gets a unique clip-path ID (prevents SVG ID collisionsCommits 01ff2b9, 9697cde
_send_payment_receipt helper fired from the checkout-completed webhook handler once thePayment.objects.create() result was not assigned to a variable, causing aNameError when the receipt referenced the payment — now captured.Commits 42b0cbe, ff2c4b6, 80e2434
Stripe can deliver both payment_intent.succeeded and checkout.session.completed, in either
order, for a single payment. The handlers are now order-independent:
checkout.session.completed checks whether payment_intent.succeeded alreadypayment_intent.succeeded fires first it creates the Payment;checkout.session.completed previously returned early after attaching the session ID and skippedinvoice.status == 'paid' but also when a confirmed Payment already exists. This closes the raceNet effect: exactly one Payment, exactly one receipt, regardless of webhook ordering or duplicate
checkout attempts.
Commit 59a0b33
core_setting as BCC_INVOICE_ENABLED / BCC_INVOICE_EMAIL andBCC_PAYMENT_ENABLED / BCC_PAYMENT_EMAIL.apps/billing/email.py, apps/payments/views.py) read these at send time andCommit 6097f3c
The invoice status was updated to sent after the email was assembled, but the PDF was generated
before that — so a sent invoice's PDF still read DRAFT. Reordered apps/billing/email.py: update
status to sent first, always regenerate a fresh PDF, then send. PDF now reflects the correct status.
Commit 65ba68d
AnonRateThrottle (keyed by client IP), configured inconfig/settings/base.py; throttle scopes applied in apps/portal/views.py.payment_intent.succeeded andcheckout.session.completed can both fire and arrive in any order; each checks for an existingcore_setting keys BCC_INVOICE_* / BCC_PAYMENT_*, toggled on the Settings page.sent, so it never shows DRAFT post-send.AnonRateThrottle in base.py.YealinLogo component.