feat(promo): enforce active + max_uses, validate endpoint, codes hidden from public (v1.6.1-aio.12) #45
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/promo-codes"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Backend half of promo codes for the webapp (organizer editor + buyer checkout land in aiolabs/webapp
feat/events-promo-codes). Promo handling was inherited from upstream unchanged; four gaps the webapp would otherwise expose to buyers are fixed here, in upstream v2's shape (lnbits/events#64) so #33 carries them.What changes
activeis enforced. It was decorative: purchase never read it. Now 400Promo code is not active.PromoCode.max_uses(None/0 = unlimited) withused_countderived from paid tickets carrying the code (extra.applied_promo_code); each ticket of a multi-ticket purchase consumes one use (v2 counts one per basket — documented deviation). Paid-only counting so an abandoned Stripe session can't lock out the last uses for the 24 h unpaid-row lifetime; bounded concurrency overshoot accepted. 400Promo code has been fully redeemed./Only {n} use(s) left on this promo code.Closes #32.PublicEvent.extrawas the fullEventExtraand/events/publicreturned the untrimmedEvent(wallet id included).EventExtraBase/PublicEventExtraproject promo codes out;/publicgoes throughPublicEvent. Organizer (GET /api/v1/events) and admin (/all) listings keep the full model, hydrated withused_count.display.jsonly readspayment_methods/ notification flags, so the Quasar buy page is unaffected.POST /events/api/v1/promo/validate/{event_id}(anonymous; same URL as v2,quantityinstead of v2'sitems) → v2BasketTotals+currency. Advisory: unknown/inactive/exhausted codes are simply absent fromdiscounts_applied; purchase still hard-fails them.All pricing (validate, invoice amount, Stripe amount) goes through one pure
basket_totalsinpromo.pywith one rounding rule (whole sats / 2 dp fiat), so the preview equals the charge. Stripe metadata gainspromo_code; organizer stats rows gainapplied_promo_code.Rollout guard:
api_event_updatekeeps stored codes when a request omitsextra.promo_codes(explicit[]still clears). Without it the currently deployed webapp, which round-trips the public record'sextra, would wipe codes on its next edit once they're hidden. Either PR can therefore ship first.Quasar admin: "Max uses" column +
Used n / max; public page Clear now clears the promo field.Verification
make test: 67 passed (newtests/test_promo.py,tests/test_promo_api.py: model normalisation, sat/fiat rounding, first-valid-wins, absent cases, paid-only usage,PublicEventprojection, validate shape/404, purchase rejections before any invoice code, update carry-over keep/clear).crud.py/nostr_sync.pyannotations remain).HALF50 % ×2,FREE100100 % ×1,OLDinactive →curl /events/public | jq '.[].extra'shows no codes; validate["half"]qty 2 →2000 / 1000 / 1000 sat; thirdHALFpurchase → "fully redeemed"; Stripe sessionmetadata.promo_code.Deploy
Merge → tag
v1.6.1-aio.12(aio.11 isfeat/organizer-sender-identity; renumber whichever lands second) → catalog entry → upgrade demo.🤖 Generated with Claude Code
https://claude.ai/code/session_01ByAwHU4pRnyE58YocQvAas
Heads-up for whoever rebases this: new rule agreed 2026-09-13 — feature PRs no longer bump
config.json; the version is set by a singlechore(release): v1.6.1-aio.Ncommit onmainafter merging (recorded in~/dev/CLAUDE.md). Please drop theaio.12bump when rebasing onto main (now at aio.13 + #44 + #47 once those merge; #44 addsorganizer_name/reply_to_email/copy_to_organizertoEventExtra, which will touch the same region as thePublicEventExtraprojection here).c9fbd8335c93cc95fe18Rebased onto
mainatv1.6.1-aio.14(4c3b1bc): onlyconfig.jsonconflicted — theaio.12bump is dropped per the new release rule (version set by a release commit after merge), the docs commit is reworded accordingly.models.py/views_api.py/ admin JS+Vue applied cleanly on top of aio.13's form-parity and the Email-sent column. 67 tests pass; ruff / black clean. Mergeable again.