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

Require the package, then publish and run the migrations. The service provider is auto-discovered, and Composer pulls in the Roundly crypto, enums, money and package-toolkit packages it builds on:

composer require roundly-consulting/purchases-for-laravel

php artisan vendor:publish --tag="purchases-migrations"
php artisan migrate

The migrations are publish-only — the package never auto-loads them, so a bare php artisan migrate creates nothing until you publish the purchases-migrations tag. Republishing overwrites the files in place instead of adding a second copy. The migrations are forward-only — they have no down(), so migrate:rollback skips them; drop the tables yourself if you ever need to remove them.

Config and routes

The config file is optional — every key has an env-backed default. Publish it to override, and publish the routes file only if you want to wire the bundled webhook route yourself:

php artisan vendor:publish --tag="purchases-config"
php artisan vendor:publish --tag="purchases-routes"

Guided installer

purchases:install publishes the config and migrations and offers to run the migrations. With --providers it asks which providers you use and appends their env keys to .env — only those it doesn’t define yet, because a later blank duplicate would win, so re-running it never wipes a secret you already filled in. Google’s block includes the push-authentication keys:

php artisan purchases:install
php artisan purchases:install --providers   # choose providers, append their missing .env keys

Fill in each provider’s credentials — Stripe needs both PURCHASES_STRIPE_SECRET and PURCHASES_STRIPE_WEBHOOK_SECRET — then check them with purchases:providers and purchases:verify (see Artisan commands).

UUID or ULID owners

Purchases and subscriptions belong to an owner through a nullable polymorphic owner column. If your owner models use UUID or ULID primary keys, set the key type before running the migrations — all owner models must share one key type. A value other than bigint, uuid or ulid throws an InvalidConfigurationException naming the key, so a typo fails the migration instead of keying the owner column wrong:

# Set before running the migrations when owners use UUID or ULID keys
PURCHASES_KEY_TYPE=uuid

The package exposes three publish tags: purchases-config, purchases-migrations and purchases-routes.

Upgrading from 1.0

The 1.1 releases change a few behaviours a host may rely on:

  • Catch VerificationException, not RequestException, for a client’s bad id, token or receipt from callback() — Stripe, Google and Apple alike. The store’s RequestException is its getPrevious(); see Client callbacks.
  • SubscriptionStarted — not SubscriptionRenewed — fires when a subscription created before it was paid for (Stripe incomplete, Google pending) first becomes active, so owner-linking listeners run for 3D Secure and default_incomplete subscriptions too.
  • A one-off Stripe invoice recorded from invoice.paid carries the PaymentIntent that paid it as its transaction_id, so its refunds and disputes link to it and flip it to Refunded. Set PURCHASES_STRIPE_SECRET.
  • A Google RTDN for another app (a shared Pub/Sub topic) is audited and answered 204, never applied.
  • Stripe and Google refunds record a refunded_at, and provider dates keep their real instant on a host whose app.timezone isn’t UTC.
  • purchases:providers reports Stripe as ready only with both secrets, and Google only once its pushes can be authenticated.
  • Tests: assertSubscriptionStarted() passes only when SubscriptionStarted really fired — use assertSubscriptionRecorded() for “a subscription result arrived” — and FakeResult uses one id as both provider and transaction id.

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.