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

Credits throws two exceptions of its own under a common base, passes a handful of money-for-laravel exceptions through unwrapped, throws package-toolkit’s InvalidConfigurationException on a malformed setting, and lets Laravel’s DeadlockException reach you inside your own transactions:

ExceptionThrown when
CreditsExceptionBase class (a RuntimeException) of InsufficientCreditsException and BucketNotDenominatedException — catch it to handle any credits failure.
InsufficientCreditsExceptionA deduction would take a bucket below minimum_balance while overdraft is disallowed. Carries $creditable, $requested (signed) and $available; message from credits::messages.insufficient.
BucketNotDenominatedExceptionA money-typed call — the scope’s money(), addMoney(), deductMoney(), modifyMoney() or formatMoney(), or the trait’s creditsBalanceMoney(), modifyCreditsMoney() or formatCreditsBalance() — on a bucket not listed in credits.currencies. Carries $bucket (the resolved name); message from credits::messages.not_denominated.
InvalidArgumentExceptionadd() or deduct() (or addMoney() / deductMoney()) received a negative amount — use modify() for a signed change.
CurrencyMismatchFrom money: modifyCreditsMoney() received a Money in another currency (code + exponent).
AmountOverflowFrom money: a Money amount does not fit the signed 64-bit ledger column.
UnknownCurrencyFrom money: credits.currencies maps a bucket to a code the registry does not know.
InvalidMoneyConfigurationFrom money: credits.currencies is malformed, or credits.rounding names no rounding mode.
InvalidAmountFrom money: a display scale beyond 36 decimal places.
InvalidConfigurationExceptionFrom package-toolkit: a config value is malformed — a model that is not Credit or a subclass, an unknown key_type or primary_key_type, a non-boolean allow_overdraft, a non-integer minimum_balance, a non-string default_bucket, a scale outside 0–36, or a non-callable modifiable entry. The message names the key.
DeadlockExceptionFrom Laravel: inside your own DB::transaction() on Postgres REPEATABLE READ or SERIALIZABLE, a racing change of the same owner (SQLSTATE 40001). Nothing is written — retry the whole transaction.

Handling them

Catch the specific cases you can act on, then fall back to the base class:

use RoundlyConsulting\Credits\Exceptions\CreditsException;
use RoundlyConsulting\Credits\Facades\Credits;
use RoundlyConsulting\Credits\Exceptions\InsufficientCreditsException;
use RoundlyConsulting\Money\Exceptions\AmountOverflow;
use RoundlyConsulting\Money\Exceptions\CurrencyMismatch;

try {
    Credits::for($user)->bucket('store_credit')->modifyMoney($amount, 'checkout');
} catch (InsufficientCreditsException $e) {
    // not enough in the bucket: $e->requested, $e->available, $e->creditable
} catch (CurrencyMismatch | AmountOverflow $e) {
    // wrong currency, or beyond the 64-bit ledger — nothing was written
} catch (CreditsException $e) {
    // any other credits failure, e.g. BucketNotDenominatedException ($e->bucket)
}

A refused change never leaves a partial write: the overdraft guard runs inside the write’s transaction, and the money checks run before it starts.

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.