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

Rezervácia (hold) vyhradí kredity v skupine bez zápisu do knihy pohybov. Znižuje to, čo sa dá minúť, kým ju strhnete — celú, alebo čiastočne so zvyškom uvoľneným —, uvoľníte, alebo kým neexpiruje. Typicky ide o pokladňu, ktorá kredity rezervuje počas platby, alebo o volanie API, ktoré si vopred rezervuje odhadovanú cenu.

Zapnutie rezervácií

Rezervácie potrebujú tabuľku credit_holds (pozrite Inštalácia) a sú vypnuté, kým ich nezapnete — dovtedy ochrana pri odpise tabuľku rezervácií nikdy nečíta:

CREDITS_HOLDS=on
# optional: holds created without their own expiry live this long
CREDITS_HOLD_EXPIRE_AFTER_MINUTES=30

Rezervácia, strhnutie, uvoľnenie

use RoundlyConsulting\Credits\Facades\Credits;

$hold = Credits::for($user)->hold(30, now()->addMinutes(15), 'Order #42', ['order' => 42]);

Credits::for($user)->balance();    // 100 — the ledger is untouched
Credits::for($user)->held();       // 30
Credits::for($user)->available();  // 70 — what a debit or another hold can still take

$hold->capture();        // debit all 30 (one ledger row), or…
$hold->capture(12);      // …debit 12 and release the other 18, or…
$hold->release();        // …free all of it, nothing written to the ledger

Ploché metódy fasády prijímajú CreditHoldData a samotnú rezerváciu; capture() a release() na scope rezerváciu iného vlastníka odmietnu:

use RoundlyConsulting\Credits\DataTransferObjects\CreditHoldData;
use RoundlyConsulting\Credits\Facades\Credits;

$hold = Credits::hold($user, new CreditHoldData(amount: 30, expiresAt: now()->addMinutes(15), bucket: 'points'));

Credits::capture($hold, 20, 'Order #42');        // the debit row (-20); the other 10 are released
Credits::release($hold);                         // throws CreditHoldNotActiveException — already captured

Credits::for($user)->bucket('points')->release($otherHold); // ForeignCreditHoldException for another owner's hold
Credits::held($user, 'points');                  // int
Credits::available($user, 'points');             // int
Credits::for($user)->hasAvailable(50);           // bool

Skratky v traite (nie sú súčasťou rozhrania Creditable):

$hold = $user->holdCredits(30, now()->addMinutes(15), bucket: 'points');

$user->heldCredits('points');           // 30
$user->availableCredits('points');      // balance minus active holds
$user->hasAvailableCredits(50, 'points');

$amount musí byť kladné a $expiresAt v budúcnosti (InvalidArgumentException); suma strhnutia musí byť 1..$hold->amount a popis aj meta sa predvolene preberú z rezervácie. $hold->capture() a $hold->release() idú cez manažéra, takže ich fake zaznamená, a odovzdaná rezervácia sa aktualizuje na mieste (status, captured_amount, credit_id, resolved_at).

Pravidlá

  • Dostupné = zostatok − aktívne rezervácie, pre každú skupinu. Aktívna rezervácia je v stave held a jej expires_at ešte neuplynulo. balance(), has() aj balance v CreditsModified zostávajú zostatkom knihy pohybov.
  • Rezervácia sa stráži ako odpis: pokiaľ nie je povolené prečerpanie (na scope alebo cez credits.allow_overdraft), nesmie dostať dostupný zostatok pod credits.minimum_balance — InsufficientCreditsException, ktorej $available je dostupný zostatok.
  • Odpisy rešpektujú rezervácie: pri zapnutých rezerváciách rozhoduje strážený odpis podľa dostupného zostatku. Odpis s povoleným prečerpaním môže rezervované kredity minúť aj tak.
  • Strhnutie sa kvôli minimálnemu zostatku nikdy neodmietne — rezervácia kredity už vyhradila. Zapíše jeden odpis do knihy pohybov, na ktorý odkazuje credit_id rezervácie.
  • Každá rezervácia sa vyrieši raz: strhnutie či uvoľnenie rezervácie, ktorá už bola strhnutá, uvoľnená, expirovala (upratovaním alebo len uplynutím expires_at) alebo bola zmazaná, vyhodí CreditHoldNotActiveException so stavom, v ktorom ju našlo.
  • Expirácia platí okamžite, upratovanie je len evidencia: rezervácia prestane kredity vyhradzovať v momente, keď uplynie expires_at; upratovanie ju označí ako expired a spustí CreditHoldExpired.
  • Rezervácie žijú na spojení knihy pohybov — spojení modelu credits.model, inak vlastníka — vedľa záznamov knihy pohybov a zámku vlastníka.

Odolnosť voči súbehu

Každá rezervácia, strhnutie, uvoľnenie aj expirácia vezme zámok vlastníka (ten istý riadok credit_locks, ktorý zvyšuje odpis) a všetko, podľa čoho rozhoduje, znova načíta pod zámkami riadkov, v jednej transakcii so svojím zápisom:

SúbehVýsledok
Dve rezervácie, ktoré spolu prekročia dostupný zostatokdruhá počká a potom sa odmietne (InsufficientCreditsException)
Rezervácia a odpis tých istých kreditovodmietne sa tá operácia, ktorá sa potvrdí ako druhá
Dve strhnutia jednej rezerváciejeden odpis; druhé dostane CreditHoldNotActiveException (captured)
Strhnutie a upratovanie expirovaných rezerváciívyhrá strhnutie (upratovanie ho preskočí) alebo upratovanie (strhnutie dostane expired) — nikdy oboje
Strhnutie a uvoľneniejedno vyhrá; druhé dostane CreditHoldNotActiveException

Na Postgres pri REPEATABLE READ / SERIALIZABLE čakajúca transakcia zlyhá so SQLSTATE 40001 a zopakuje sa s novým snapshotom, presne ako pri odpisoch.

Upratovanie expirovaných rezervácií

Naplánujte upratovanie — príkaz alebo job do fronty:

// routes/console.php
use Illuminate\Support\Facades\Schedule;
use RoundlyConsulting\Credits\Jobs\ExpireCreditHolds;

Schedule::command('credits:expire-holds')->everyMinute()->withoutOverlapping();
// or queued:
Schedule::job(new ExpireCreditHolds)->everyMinute();
  • credits:expire-holds {--connection=} vypíše, koľko rezervácií expiroval; pri vypnutých rezerváciách to oznámi a skončí úspešne.
  • Credits::expireHolds(?$now, ?$connection) je to isté upratovanie a vráti počet; $now uprace stav k danému okamihu. Rezervácie číta po 100 na spojení modelu knihy pohybov, inak na predvolenom — ak žijú inde, zadajte $connection.
  • Bezpečné aj na viacerých serveroch naraz: každá rezervácia expiruje najviac raz, pod zámkom svojho vlastníka. Expiruje aj rezervácia, ktorej vlastník bol zmazaný.

Tabuľka credit_holds

StĺpecTypPoznámka
idper primary_key_type
creditable_type / creditable_idmorph pair, id per key_typeIndexované.
bucketstring
amountbigIntegerRezervovaná suma.
statusstring(16)held, captured, released alebo expired.
captured_amountbigInteger, nullable
credit_idper primary_key_type, nullableZáznam strhnutia v knihe pohybov.
description / metastring / json, nullable
expires_at / resolved_attimestamp, nullableUložené v app.timezone, porovnávané ako presné okamihy.
created_at / updated_at / deleted_attimestamps, soft deletesZmazaná rezervácia sa už nezapočítava.

Indexy: (creditable_type, creditable_id, bucket, status) a (status, expires_at). CreditHold má factory na priame vytvorenie rezervácií.

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.