---
title: "Models & the owner trait — Purchases for Laravel | Roundly"
description: "Six swappable Eloquent models, the HasPurchases owner trait, subscription scopes and helpers, and the database schema."
url: https://roundly-consulting.com/open-source/docs/purchases-for-laravel/models
language: en
---

[All packages](https://roundly-consulting.com/open-source.md)

[Purchases for Laravel](https://roundly-consulting.com/open-source/docs/purchases-for-laravel.md)

# Models & the owner trait

Purchase, PurchaseItem, Subscription, SubscriptionItem, PurchaseRefund and PurchaseNotification (under RoundlyConsulting\\Purchases\\Models) are standard Eloquent models with soft deletes and factories.

## One owner’s purchases

Purchases::for($owner) needs no trait on the model — it scopes to exactly that owner’s morph type and key:

```php
use RoundlyConsulting\Purchases\Facades\Purchases;

Purchases::for($user)->subscribedTo('pro');            // bool
Purchases::for($user)->activeSubscription('pro');      // ?Subscription (latest active)
Purchases::for($user)->purchases()->latest()->get();   // Builder<Purchase>
Purchases::for($user)->subscriptions()->count();       // Builder<Subscription>
```

## The HasPurchases trait

Add the trait to your owner model — typically User — for the purchases and subscriptions relations through the package’s owner morph. Its activeSubscription() and subscribedTo() helpers are sugar over Purchases::for($this), so they behave exactly like the facade:

```php
use RoundlyConsulting\Purchases\Concerns\HasPurchases;

class User extends Authenticatable
{
    use HasPurchases;
}

$user->purchases;                    // MorphMany<Purchase>
$user->subscriptions;                // MorphMany<Subscription>
$user->activeSubscription();         // ?Subscription — the latest active one
$user->activeSubscription('pro');    // ?Subscription — by plan name
$user->subscribedTo('pro');          // bool — both delegate to Purchases::for($this)
```

## Subscription scopes & helpers

```php
use RoundlyConsulting\Purchases\Models\Subscription;

Subscription::active()->expiring(7)->get();   // active and ending within 7 days
Subscription::trialing()->get();
Subscription::canceled()->get();

$subscription->isActive();          // entitled and not past ends_at
$subscription->onTrial();
$subscription->daysUntilRenewal();  // ?int
$subscription->isExpiring(7);
$subscription->items;               // HasMany<SubscriptionItem>
```

- active() — status Completed or InGracePeriod, and ends\_at empty or in the future.
- trialing() — trial\_ends\_at in the future.
- expiring($days = 7) — ends\_at within the next $days days.
- canceled() — status Canceled.
- daysUntilRenewal() — whole days until ends\_at (never negative), or null without an end date.

## Provider scopes & relations

Purchase, Subscription and PurchaseRefund share forProvider(), byProviderId() and byTransaction(); PurchaseRefund adds chargebacks() and PurchaseNotification adds pending():

```php
use RoundlyConsulting\Purchases\Models\Purchase;
use RoundlyConsulting\Purchases\Models\PurchaseNotification;
use RoundlyConsulting\Purchases\Models\PurchaseRefund;

Purchase::forProvider('stripe')->byProviderId('pi_123')->first();
Purchase::byTransaction('2000000000000001')->first();
PurchaseRefund::forProvider('google')->chargebacks()->get();
PurchaseNotification::forProvider('apple')->pending()->get();

$purchase->owner;     // MorphTo — your User (or null)
$purchase->items;     // HasMany<PurchaseItem>
$purchase->refunds;   // HasMany<PurchaseRefund>
$purchase->meta;      // Collection — the raw store payload
$refund->purchase;    // BelongsTo<Purchase>
```

## Swapping models

Every model is swappable via config('purchases.models.\*'). Your class must be the package model it replaces or extend it — any other class throws an InvalidConfigurationException naming the key instead of silently falling back to the packaged model:

```php
// app/Models/Purchase.php
namespace App\Models;

use RoundlyConsulting\Purchases\Models\Purchase as BasePurchase;

class Purchase extends BasePurchase
{
    // your relations, accessors, casts …
}

// config/purchases.php
'models' => [
    'purchase' => \App\Models\Purchase::class,
    // …
],
```

## Schema

| Table | Columns |
| --- | --- |
| `purchases` | owner (nullable morph), provider, provider\_id, transaction\_id, status, price + price\_currency, meta, last\_event\_at — unique (provider, provider\_id) |
| `purchase_items` | purchase\_id (cascade delete), provider\_id, name, price + price\_currency, quantity |
| `subscriptions` | owner (nullable morph), provider, provider\_id, transaction\_id, name, status, price + price\_currency, active\_from, trial\_ends\_at, ends\_at, meta, last\_event\_at — unique (provider, provider\_id) |
| `subscription_items` | subscription\_id (cascade delete), provider\_id, name, price + price\_currency |
| `purchase_refunds` | purchase\_id (nullable), provider, provider\_id, transaction\_id, reason, chargeback, price + price\_currency, refunded\_at, meta, last\_event\_at — unique (provider, provider\_id) |
| `purchase_notifications` | provider, type, signature\_verified, payload, processed\_at |

Every table has timestamps and soft deletes. Table names come from each model’s getTable(), so a swapped model with its own $table is migrated under that name — configure it before you run the migrations.

## 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](https://roundly-consulting.com/support-us.md)

By donating, you agree to our [donation terms](https://roundly-consulting.com/donation-terms.md).

[Support our open source work (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Join us on Patreon (opens in a new tab)](https://www.patreon.com/cw/roundly)

## 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.

[Get a quote in 48 hours](https://roundly-consulting.com/contact.md) [Browse all packages](https://roundly-consulting.com/open-source/docs/purchases-for-laravel.md)
