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

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 modelomKedy sa spustí
PurchaseRecorded$purchasePri každom výsledku nákupu, ktorý pipeline dostane — aj pri opakovaných a zastaraných, preto na ňom objednávky nevybavujte.
PurchaseCompleted$purchaseNákup je nový alebo prešiel do stavu Completed.
PurchaseFailed$purchaseNákup je nový alebo prešiel do stavu Failed či Canceled.
SubscriptionStarted$subscriptionNové predplatné sa uloží so stavom Completed.
SubscriptionRenewed$subscriptionExistujúce predplatné sa vráti do stavu Completed alebo sa mu predĺži ends_at.
SubscriptionInGracePeriod$subscriptionPrejde do InGracePeriod — platba sa opakuje, prístup zostáva.
SubscriptionCanceled$subscriptionPrejde do stavu Canceled.
SubscriptionExpired$subscriptionPrejde do stavu Failed.
PurchaseRefunded$refundRefundácia je nová alebo sa zmenila vrátená suma.
ChargebackReceived$refundChargeback (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 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.