NewWe open-sourced 50+ Laravel packages
Custom AI apps, agents and automation — Roundly ConsultingRoundly
All packages
Purchases for Laravel

Enums & exceptions

Status is the normalized status stored on purchases and subscriptions:

CaseValueKeeps access
NewnewNo
PendingpendingNo
ProcessingprocessingNo
CompletedcompletedYes
FailedfailedNo
CanceledcanceledNo
InGracePeriodin_graceYes
OnHoldon_holdNo
RefundedrefundedNo

Status::isActive() is true only for Completed and InGracePeriod — the states that keep an entitlement. It drives Subscription::isActive() and the active() scope.

ResultType

CaseValueMeaning
PurchasepurchaseA one-off purchase — recorded as a Purchase.
SubscriptionsubscriptionA subscription — recorded as a Subscription.
RefundrefundA refund or chargeback — recorded as a PurchaseRefund.
NotificationnotificationA store notification without a transaction.
UnknownunknownAn unrecognised payload.

Select options & validation

Both enums use the Helpers trait from enums-for-laravel, so admin filters and forms need no bespoke arrays or language files:

use RoundlyConsulting\Purchases\Enum\Status;

Status::options();          // list of {value, label, name} option DTOs for select inputs
Status::toOptions();        // ['completed' => 'Completed', 'in_grace' => 'In Grace', …]
Status::labels();           // human strings incl. "In Grace" / "On Hold" (no lang files)
Status::validationRule();   // "in:new,pending,processing,completed,failed,canceled,in_grace,on_hold,refunded"

$status = Status::tryFromLabel('On Hold');   // Status::OnHold
$status?->isActive();                        // false — an account hold keeps no access

Exceptions

ExceptionThrown when
VerificationExceptionA signature or credential check fails — including an Apple certificate outside its validity window. The webhook route answers 400.
InvalidConfigurationExceptionA config value is unusable — certificate_clock_skew outside 0–3600, a duration that isn’t a whole number, a URL or queue name that isn’t a string, a provider that doesn’t implement Provider. Deliberately not a VerificationException — a misconfigured host is not a forged notification.
UnknownProviderExceptionResolving a provider id that isn’t registered.
InvalidProviderNotificationExceptionA notification payload can’t be decoded — or replay() refuses a notification (unsaved or soft-deleted, a provider notification that failed verification, or no longer rebuildable).

All extend RoundlyConsulting\Purchases\Exceptions\Exception, which adds a because(string $message, int $code = 0) named constructor:

use RoundlyConsulting\Purchases\Exceptions\UnknownProviderException;
use RoundlyConsulting\Purchases\Exceptions\VerificationException;
use RoundlyConsulting\Purchases\Facades\Purchases;

try {
    $result = Purchases::result('stripe', $request);
} catch (VerificationException $e) {
    abort(400, $e->getMessage());
} catch (UnknownProviderException) {
    abort(404);
}

Config typos caught by the package toolkit are the exception: an unparseable on/off switch (push authentication aside), a key_type outside bigint, uuid and ulid, and a models.* class that isn’t the packaged model or a subclass of it throw RoundlyConsulting\PackageToolkit\Exceptions\InvalidConfigurationException, which doesn’t extend the package’s base exception. Like the package’s own InvalidConfigurationException, it names the key.

Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

More ways to support, including crypto

By donating, you agree to our donation terms.

Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.