NovinkaZverejnili sme 50+ Laravel balíkov ako open source
Custom AI apps, agents and automation — Roundly ConsultingRoundly
Všetky balíky
Events for Laravel

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 kryptomien

Odoslaní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.