Purchases::handle(), sync(), replay(), úloha vo fronte aj purchases:replay spúšťajú udalosti z RoundlyConsulting\Purchases\Events. Každá nesie uložený model a pôvodný ProviderResult ako $event->result:
| Udalosť | Vlastnosť s modelom | Kedy sa spustí |
|---|---|---|
PurchaseRecorded | $purchase | Pri každom výsledku nákupu, ktorý pipeline dostane — aj pri opakovaných a zastaraných, preto na ňom objednávky nevybavujte. |
PurchaseCompleted | $purchase | Nákup je nový alebo prešiel do stavu Completed. |
PurchaseFailed | $purchase | Nákup je nový alebo prešiel do stavu Failed či Canceled. |
SubscriptionStarted | $subscription | Nové predplatné sa uloží so stavom Completed. |
SubscriptionRenewed | $subscription | Existujúce predplatné sa vráti do stavu Completed alebo sa mu predĺži ends_at. |
SubscriptionInGracePeriod | $subscription | Prejde do InGracePeriod — platba sa opakuje, prístup zostáva. |
SubscriptionCanceled | $subscription | Prejde do stavu Canceled. |
SubscriptionExpired | $subscription | Prejde do stavu Failed. |
PurchaseRefunded | $refund | Refundácia je nová alebo sa zmenila vrátená suma. |
ChargebackReceived | $refund | Chargeback (spor) je nový alebo sa zmenila jeho suma. |
Každý obchod môže notifikáciu doručiť viackrát aj mimo poradia a auditný log sa dá spracovať znova, preto sa udalosti životného cyklu spúšťajú len vtedy, keď sa niečo zmenilo — nový riadok, posunutý stav, obnova, ktorá predĺžila ends_at, nová vrátená suma. Ukladanie je zoradené podľa času udalosti (pozrite Ukladanie výsledkov), takže opakovane doručená alebo znova spracovaná platba nikdy znova nevybaví refundovanú objednávku. PurchaseRecorded sa spúšťa pri každom výsledku nákupu, ktorý pipeline dostane — aj pri opakovaných a zastaraných —, preto na ňom objednávky nevybavujte; použite PurchaseCompleted. Informatívne notifikácie sa len zaznamenajú do auditu a nič nespustia.
Počúvanie udalostí
use Illuminate\Support\Facades\Event;
use RoundlyConsulting\Purchases\Events\PurchaseRefunded;
use RoundlyConsulting\Purchases\Events\SubscriptionExpired;
use RoundlyConsulting\Purchases\Events\SubscriptionStarted;
Event::listen(SubscriptionStarted::class, function (SubscriptionStarted $event): void {
$event->subscription->owner; // grant access
$event->result->provider(); // 'apple', 'google' or 'stripe'
});
Event::listen(SubscriptionExpired::class, function (SubscriptionExpired $event): void {
// revoke access
});
Event::listen(PurchaseRefunded::class, function (PurchaseRefunded $event): void {
$event->refund->purchase; // the refunded Purchase, when matched
});Prepojenie vlastníka
Ukladanie nikdy nenastavuje vlastníka — polymorfný stĺpec owner je voliteľný, pretože notifikácia obchodu nikdy neuvádza, ktorému z vašich používateľov patrí. Prepojte ho v listeneri podľa tokenu účtu, ktorý ste obchodu odovzdali — appAccountToken pri Apple, obfuscatedExternalAccountId pri Google, zákazník (customer) pri Stripe:
use App\Models\User;
use Illuminate\Support\Facades\Event;
use RoundlyConsulting\Purchases\Events\SubscriptionStarted;
Event::listen(function (SubscriptionStarted $event): void {
$token = $event->result->raw()['data']['transactionInfo']['appAccountToken'] ?? null; // Apple
if ($user = User::query()->where('app_account_token', $token)->first()) {
$event->subscription->owner()->associate($user)->save();
}
});Odobratie prístupu a integrácie
Úplná refundácia prepne spárovaný nákup na Status::Refunded (čiastočná ho ponechá v stave Completed), takže odobratie prístupu môžete naviazať na PurchaseRefunded a ChargebackReceived. Tie isté udalosti sú miestom na pripísanie či odobratie kreditov, spustenie kampaní po nákupe alebo odosielanie metrík — zapojíte ich vo svojej aplikácii, takže balík zostáva bez týchto závislostí.
Purchases môže stáť aj za platobnou bránou pre shops-for-laravel. Shops strháva platby cez kontrakt PaymentGateway, ktorý naviažete cez shops.payment.gateway. Purchases nikdy platbu nevytvára — číta a overuje to, čo už obchod strhol — takže brána vo vašej aplikácii môže cez poskytovateľa Stripe overiť PaymentIntent, ktorý kupujúci zaplatil pri pokladni, a refundácie vystaviť vlastným volaním. Ani jeden balík nezávisí od druhého:
namespace App\Payments;
use App\Models\CheckoutPayment;
use Illuminate\Support\Facades\Http;
use RoundlyConsulting\Money\Money;
use RoundlyConsulting\Purchases\Providers\Stripe\Enums\PaymentIntentStatus;
use RoundlyConsulting\Purchases\Providers\Stripe\Stripe;
use RoundlyConsulting\Shops\Contracts\PaymentGateway;
use RoundlyConsulting\Shops\Orders\Order;
use RoundlyConsulting\Shops\Payments\PaymentResult;
final class StripeIntentGateway implements PaymentGateway
{
public function __construct(private Stripe $stripe) {}
// Confirm the PaymentIntent the buyer paid at checkout — purchases reads it from Stripe.
public function charge(Order $order): PaymentResult
{
$due = $order->gatewayAmount();
$intent = $this->stripe->paymentIntent($this->intentId($order));
return $intent->status === PaymentIntentStatus::Succeeded && $intent->amount?->equals($due)
? PaymentResult::success($due, $intent->id)
: PaymentResult::failure($due, 'Payment not confirmed.');
}
// purchases never writes to a store: refund with your own call. Stripe's
// charge.refunded webhook then lands in purchases as a PurchaseRefund.
public function refund(Order $order, Money $amount): PaymentResult
{
$response = Http::withToken(config('purchases.settings.stripe.secret'))->asForm()
->post('https://api.stripe.com/v1/refunds', [
'payment_intent' => $this->intentId($order),
'amount' => $amount->minorInt(),
]);
return $response->successful()
? PaymentResult::success($amount, $response->json('id'))
: PaymentResult::failure($amount, $response->json('error.message'));
}
private function intentId(Order $order): string
{
// Your own record of the PaymentIntent confirmed at checkout.
return CheckoutPayment::query()->where('order_id', $order->getKey())->value('payment_intent');
}
}
// config/shops.php — or SHOPS_PAYMENT_GATEWAY in .env
'payment' => ['gateway' => \App\Payments\StripeIntentGateway::class],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.