Skip to content

ticketing,subscription: tiers, royalties, grace period, prorated refunds#288

Merged
codebestia merged 1 commit into
ShadeProtocol:mainfrom
davedumto:feat/ticket-tiers-subscription-grace-refunds
Apr 29, 2026
Merged

ticketing,subscription: tiers, royalties, grace period, prorated refunds#288
codebestia merged 1 commit into
ShadeProtocol:mainfrom
davedumto:feat/ticket-tiers-subscription-grace-refunds

Conversation

@davedumto
Copy link
Copy Markdown
Contributor

Summary

Implements four open Stellar Wave issues across the standalone ticketing and subscription contracts.

closes #259
closes #263
closes #282
closes #285

Changes

ticketing contract

#263 — VIP vs Standard ticket tiers

  • New Tier struct (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-tier sold and rejects further issuance once max_supply is reached, regardless of remaining event capacity.
  • Ticket gains an optional tier_id for backward compatibility — the legacy issue_ticket continues to work and leaves tier_id = None.
  • New queries: get_tier, get_event_tiers.

#259 — Tests for ticket transfers and royalties (with new royalty mechanism)

  • New 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_bps capped 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 both TicketTransferedEvent and TicketResoldEvent. Rejects checked-in tickets, self-resales, and zero/negative prices.
  • 17 new resale tests cover royalty math at 0%/5%/100%, sub-unit floor cases, multi-hop resale chains, post-resale ownership, and every authorization failure mode.

subscription contract

#282 — Penalty / grace period logic

  • Plan.grace_period (seconds), settable via set_plan_grace_period(merchant, plan_id, ...).
  • New SubscriptionStatus::PastDue and Terminated states; Subscription.past_due_since records when the grace window started.
  • process_charge(sub_id) -> ChargeOutcome — forgiving variant of charge that 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.
  • The strict charge function is unchanged and now also clears past_due_since on 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 via transfer_from; without that allowance the call panics and no state changes.
  • Refund math: amount * remaining_seconds / interval, with remaining_seconds = (last_charged + interval) - now clamped to [0, interval].
  • quote_prorated_refund(sub_id) -> i128 — read-only preview for off-chain UIs before asking the merchant to approve.
  • 15 refund tests cover full-cycle, mid-cycle, end-of-cycle, never-charged, double-cancel, missing-allowance, and recovery-after-PastDue paths.

Scope notes

  • All changes are confined to the standalone ticketing and subscription crates. The shade mega-contract is untouched (it has a pre-existing duplicate Invoice struct on main that prevents it from compiling — out of scope here).
  • 41 new tests added; all 67 tests across both crates pass.

Test plan

  • cargo test -p ticketing — 38 passed
  • cargo test -p subscription — 29 passed
  • cargo fmt -p ticketing -p subscription -- --check — clean
  • cargo clippy -p ticketing --all-targets — clean (the duplicated-attributes warning in test_integration.rs is pre-existing on main)
  • cargo clippy -p subscription --all-targets — clean (the useless_conversion warning in authorize_billing is pre-existing on main)
  • Manual review: tiers respect event capacity; royalty math is exact at boundaries; grace window expiry is strictly > not >=; refund preview matches the on-chain effect.

- 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.
@drips-wave
Copy link
Copy Markdown

drips-wave Bot commented Apr 29, 2026

@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! 🚀

Learn more about application limits

@codebestia codebestia merged commit 0492186 into ShadeProtocol:main Apr 29, 2026
2 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants