ticketing,subscription: tiers, royalties, grace period, prorated refunds#288
Merged
codebestia merged 1 commit intoApr 29, 2026
Conversation
- ticketing: add VIP/Standard tier system with per-tier capacity and pricing, scoped to events; tickets carry tier_id metadata. Combined tier supply is validated against the event's overall capacity when set. - ticketing: add ResaleConfig + resell_ticket() routing a configurable royalty (basis points) to the organizer on every secondary-market sale. Reselling a checked-in ticket or to oneself is rejected. - subscription: add plan grace_period and PastDue/Terminated states. process_charge() drives the billing state machine, transitioning to grace on missed payment, recovering when the allowance returns, or terminating when the window expires. enforce_grace() lets anyone finalize a long-overdue subscription. - subscription: add cancel_with_prorated_refund() refunding the unused portion of the current billing cycle. quote_prorated_refund() previews the amount; refund pulls from the merchant's pre-approved allowance, so no merchant authorization is bypassed. - 41 new tests across both crates (12 tier + 17 resale + 17 grace + 15 refund) covering happy paths, edge cases, and authorization failures.
|
@davedumto Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
codebestia
approved these changes
Apr 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements four open Stellar Wave issues across the standalone
ticketingandsubscriptioncontracts.closes #259
closes #263
closes #282
closes #285
Changes
ticketingcontract#263 — VIP vs Standard ticket tiers
Tierstruct (name, price, max_supply, sold) bound to an event.add_tier(organizer, event_id, name, price, max_supply)— only the organizer; combined tier supply is validated against the event's overall capacity when set.issue_tiered_ticket(...)— increments per-tiersoldand rejects further issuance oncemax_supplyis reached, regardless of remaining event capacity.Ticketgains an optionaltier_idfor backward compatibility — the legacyissue_ticketcontinues to work and leavestier_id = None.get_tier,get_event_tiers.#259 — Tests for ticket transfers and royalties (with new royalty mechanism)
ResaleConfig(per event) holding the payment token and royalty basis points.set_resale_config(organizer, event_id, payment_token, royalty_bps)— only the organizer;royalty_bpscapped at 10_000.resell_ticket(seller, buyer, ticket_id, sale_price)— splits the buyer's payment between seller and organizer using checked basis-point math, transfers the ticket atomically, and emits bothTicketTransferedEventandTicketResoldEvent. Rejects checked-in tickets, self-resales, and zero/negative prices.subscriptioncontract#282 — Penalty / grace period logic
Plan.grace_period(seconds), settable viaset_plan_grace_period(merchant, plan_id, ...).SubscriptionStatus::PastDueandTerminatedstates;Subscription.past_due_sincerecords when the grace window started.process_charge(sub_id) -> ChargeOutcome— forgiving variant ofchargethat drives the full state machine: charge → enter grace → recover → terminate. Returns one of{Charged, NotDueYet, EnteredGrace, Recovered, Terminated}so off-chain billing bots don't have to re-read state.enforce_grace(sub_id)— anyone can terminate a subscription whose grace window has elapsed; idempotent on already-terminated.chargefunction is unchanged and now also clearspast_due_sinceon success.#285 — Prorated refunds upon early cancellation
cancel_with_prorated_refund(caller, sub_id)— either the customer or the merchant may initiate, but both must authorize (mutual consent). Refund is pulled from the merchant's pre-approved allowance viatransfer_from; without that allowance the call panics and no state changes.amount * remaining_seconds / interval, withremaining_seconds = (last_charged + interval) - nowclamped to[0, interval].quote_prorated_refund(sub_id) -> i128— read-only preview for off-chain UIs before asking the merchant to approve.Scope notes
ticketingandsubscriptioncrates. Theshademega-contract is untouched (it has a pre-existing duplicateInvoicestruct onmainthat prevents it from compiling — out of scope here).Test plan
cargo test -p ticketing— 38 passedcargo test -p subscription— 29 passedcargo fmt -p ticketing -p subscription -- --check— cleancargo clippy -p ticketing --all-targets— clean (the duplicated-attributes warning intest_integration.rsis pre-existing onmain)cargo clippy -p subscription --all-targets— clean (theuseless_conversionwarning inauthorize_billingis pre-existing onmain)>not>=; refund preview matches the on-chain effect.