Typy vstupeniek a platby
Balík nemá objednávky a peniaze neprijíma. Typ vstupenky (tier) je možnosť vstupu s voliteľnou cenou z Money for Laravel; platené miesto sa rezervuje, kým platbu vyberie váš poskytovateľ, cez rozhranie, ktoré implementujete.
use RoundlyConsulting\Money\Money;
Events::for($event)->tiers()->add('Supporter', price: Money::ofMajor('15', 'EUR'), capacity: 30, key: 'supporter');
Events::for($event)->tiers()->make(['en' => 'Early bird', 'sk' => 'Včasná vstupenka'])
->price('9.50') // a Money, or a decimal in events.tiers.default_currency
->capacity(50)
->onSale(from: '2026-10-01 09:00', until: '2026-10-31 23:59')
->perRsvp(min: 1, max: 4) // party limits replace the event's max_guests
->hidden() // assignable by managers only, never listed for attendees
->position(1)->key('early')->description('…')
->create();Správa typov vstupeniek
use RoundlyConsulting\Events\DataTransferObjects\UpdateTierData;
$tiers = Events::for($event)->by($organizer)->tiers(); // writes check manageTiers
$tiers->update('supporter', new UpdateTierData(capacity: 40)); // a model, id or key of this event
$tiers->reorder(['early', 'supporter', 'free']); // every live ticket type, first to last
$tiers->remove('early'); // soft; refused while RSVPs use it or a waitlist waits for it
$tiers->restore('early');
$tiers->all(); // live ones in order, hidden included (with an actor: only if they may manageTiers)
$tiers->available(); // visible, on sale, with seats (or a waitlist to join)
$tier->isFree();
$tier->isOnSale();
$tier->seatsLeft(); // null = unlimited- Kľúče zodpovedajú ^[a-z0-9][a-z0-9-]{0,63}$, sú unikátne v rámci podujatia a nikdy nedostanú príponu: obsadený kľúč vyhodí tier_key_taken a kľúč zmazaného typu uvedie v správe restore(). Bez zadania je kľúčom slug názvu v záložnom jazyku.
- Cena je Money alebo desatinný reťazec v mene events.tiers.default_currency (prázdne: money.default_currency). Nulová cena sa uloží ako bezplatná, záporná je invalid_tier a ->free() na builderi cenu zruší.
- Limity skupiny vyžadujú min ≥ 1 a max ≥ min a nahrádzajú max_guests podujatia. Obdobie predaja sa musí skončiť po svojom začiatku; miestne časy sa čítajú v pásme podujatia.
- going() určí typ vstupenky podľa modelu, id alebo kľúča; bez neho sa zvolí jediný predávaný typ podujatia — pri viacerých TierRequiredException.
- TierNotOnSaleException (409) pred sale_starts_at alebo po sale_ends_at — pre typ, ktorý si účastník volí nanovo. Typ, ktorý RSVP už má, zostáva účastníkovi aj po skončení predaja a skrytý typ od správcu zostáva jeho držiteľovi použiteľný. TierSoldOutException (409), keď je kapacita typu obsadená a podujatie nemá čakaciu listinu.
- remove() je soft delete, ktorý sa odmietne, kým typ používajú RSVP alebo naň čaká čakacia listina.
Rezervácie
Odpoveď going na typ s cenou rezervuje miesta (stav held) na events.payments.hold_for (PT30M) — pri mieste uvoľnenom z čakacej listiny na events.payments.promotion_hold (PT24H) — a po commite zavolá váš collector. Rezervácia nikdy nepresiahne začiatok: pri vzniku sa zastropuje na začiatku a presun (alebo nový začiatok konceptu) na skorší čas skráti každú živú rezerváciu na nový začiatok.
Váš collector platieb
use RoundlyConsulting\Events\Contracts\CollectsAdmissionPayment;
use RoundlyConsulting\Events\DataTransferObjects\PaymentRequest;
use RoundlyConsulting\Events\Models\Rsvp;
use RoundlyConsulting\Money\Money;
final class StripeCheckout implements CollectsAdmissionPayment
{
public function request(Rsvp $rsvp, Money $amount): ?PaymentRequest
{
// The idempotency key is one per hold: two requests for the same hold (the after-commit call
// and a retry, the promotion job and your own call) get the provider's one checkout.
$session = /* create a checkout session for $amount, metadata rsvp id,
idempotency key $rsvp->paymentIdempotencyKey() */;
return new PaymentRequest(reference: $session->id, url: $session->url, expiresAt: $rsvp->held_until);
}
}
// config/events.php
'payments' => [
'collector' => App\Payments\StripeCheckout::class,
'hold_for' => 'PT30M',
'promotion_hold' => 'PT24H',
],- Pri vlastnej odpovedi účastníka beží collector synchrónne po commite, takže URL checkoutu je na vrátenom RSVP ($rsvp->payment_url). Rezervácia, ktorú vytvorila akcia niekoho iného — posun z čakacej listiny, changeTier() či changeGuests() správcu na rezervovanom RSVP —, sa vyžiada cez front (Jobs\RequestAdmissionPaymentJob, po commite): zlyhanie collectora túto akciu nikdy nezhodí; nahlási sa a spustí sa RsvpPaymentRequestFailed.
- PaymentRequest(reference, ?url, ?expiresAt): referencia sa uloží na RSVP (payment_reference, unikátne) a podľa nej hľadá confirmPayment(); url sa stane payment_url, ktorý účastník vidí, kým je miesto rezervované.
- $rsvp->paymentIdempotencyKey() (ev-hold-…) je nový náhodný kľúč pre každú novú rezerváciu a každé obnovenie — zmena skupiny alebo typu vstupenky vytvorí novú rezerváciu, novú sumu a nový checkout. Odovzdajte ho poskytovateľovi ako kľúč idempotencie.
- Rezervácia, ktorá sa zmení, kým collector vytvára checkout, si z neho nič neponechá: RsvpHeld sa nespustí, requestPayment() vráti null a RsvpPaymentRequestDiscarded checkout nahlási, aby ste ho mohli zrušiť.
- Pri vlastnej odpovedi účastníka sa výnimka collectora prenesie ďalej; rezervácia zostane a vyprší.
- Bez collectora (NoPaymentCollector) počúvajte RsvpHeld(Rsvp, Money $amount, ?PaymentRequest) a platbu spustite sami; events:doctor upozorní na platené typy bez oboch. Collector sa nevolá v žiadnej transakcii balíka — vo vašej beží po vašom commite.
Potvrdenie — váš webhook
Events::rsvps()->confirmPayment($session->id); // your webhook: held → going, idempotent
Events::rsvps()->confirm($rsvp, $reference); // when you know the RSVP
Events::for($event)->attendees()->confirm($user); // a manager, by hand ("paid at the door")
Events::rsvps()->release($rsvp); // give the held seats back now
Events::rsvps()->requestPayment($rsvp); // ask the collector (again) — once per hold- Na potvrdenie neexistuje routa: platba sa potvrdzuje len na serveri a referencia potvrdí len svoje vlastné RSVP. Opakovanie pri RSVP v stave going je kedykoľvek idempotentné a nič nezmení.
- Oneskorená platba — rezervácia vypršala — pred začiatkom: ak sú miesta voľné, prejde do going (AttendeeConfirmed s late: true); ak nie, RsvpHoldExpiredException — vráťte ju. Od starts_at a pri zrušenom či ukončenom podujatí je oneskorená platba RsvpClosedException — vráťte ju.
- confirm() správcu — zaplatené pri vstupe — nie je viazané na začiatok, zrušené podujatie však odmietne.
- Odmietnutú rezerváciu platba nikdy neoživí a nahradená referencia (po zmene typu vstupenky) sa nenájde. Miesto, ktoré prejde do going inak než platbou — zmena na bezplatný typ, kým je checkout otvorený, čestné miesto nad prepadnutou rezerváciou —, stratí starý payment_reference, payment_url aj amount, takže webhook toho checkoutu sa nenájde: platbu vráťte.
- Vypršanie je lenivé (pri ďalšom zápise do podujatia) a vykoná ho aj events:sweep, v oboch prípadoch práve raz; spustí sa RsvpHoldExpired a čakacia listina sa posunie.
- Zrušenie podujatia nechá vypršať všetky rezervácie — uvádza ich EventCancelled::$releasedHolds — a RsvpCancelled(Rsvp, bool $wasPaid) označí platené RSVP na vrátenie peňazí.
- RSVP v stave going pri type s cenou mení skupinu len ručne (attendees()->changeGuests()); vlastná odpoveď účastníka je invalid_rsvp_transition, pretože neexistuje rozdiel v cene, ktorý by sa dal vybrať či vrátiť.
Zrušenie zahodeného checkoutu
use Illuminate\Support\Facades\Event as EventBus; // Models\Event shadows Laravel's facade name
use RoundlyConsulting\Events\Events\RsvpPaymentRequestDiscarded;
EventBus::listen(RsvpPaymentRequestDiscarded::class, function (RsvpPaymentRequestDiscarded $discarded): void {
$stripe->checkout->sessions->expire($discarded->paymentRequest->reference); // nobody pays for a gone seat
});Známy hraničný prípad
Referencia sa uloží až po návrate z request(). Collector, ktorý platbu strhne synchrónne priamo v request() — karta bez prítomnosti zákazníka —, môže dostať webhook poskytovateľa skôr, a confirmPayment() potom odpovie RsvpNotFoundException. Pri takom poskytovateľovi nechajte webhook zopakovať (odpovedzte stavom, ktorý vyvolá opakovanie) namiesto vrátenia peňazí pri prvom nenájdení, alebo potvrďte z vlastného výsledku request().
Prejavte lásku k open source
Tento balík je zadarmo pod licenciou MIT. Ak vám šetrí čas, jednorazový príspevok alebo členstvo na Patreone nám pomôže ho ďalej udržiavať, testovať a dokumentovať.
Ďalšie spôsoby podpory vrátane kryptomienOdoslaním daru súhlasíte s našimi podmienkami prijímania darov.
Chcete to zabudovať do svojho produktu?
Naše balíky integrujeme do zákazkových Laravel a AI riešení. Napíšte nám, na čom pracujete, a ozveme sa do 48 hodín.