Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
4.35% covered (danger)
4.35%
3 / 69
0.00% covered (danger)
0.00%
0 / 12
CRAP
0.00% covered (danger)
0.00%
0 / 1
SubscriptionItem
4.35% covered (danger)
4.35%
3 / 69
0.00% covered (danger)
0.00%
0 / 12
240.04
0.00% covered (danger)
0.00%
0 / 1
 boot
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
1.02
 subscription
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 incrementQuantity
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 incrementAndInvoice
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 decrementQuantity
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 updateQuantity
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
6
 swap
0.00% covered (danger)
0.00%
0 / 23
0.00% covered (danger)
0.00%
0 / 1
6
 swapAndInvoice
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 reportUsage
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 usageRecords
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 updateStripeSubscriptionItem
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 asStripeSubscriptionItem
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace App\Http\Models;
4
5use DateTimeInterface;
6use Illuminate\Database\Eloquent\Relations\BelongsTo;
7use Illuminate\Support\Collection;
8use Laravel\Cashier\Cashier;
9use Laravel\Cashier\Concerns\InteractsWithPaymentBehavior;
10use Laravel\Cashier\Concerns\Prorates;
11use Stripe\SubscriptionItem as StripeSubscriptionItem;
12use Stripe\UsageRecord;
13
14/**
15 * @property \Laravel\Cashier\Subscription|null $subscription
16 */
17class SubscriptionItem extends Moloquent
18{
19    use InteractsWithPaymentBehavior;
20    use Prorates;
21
22    /**
23     * The attributes that are not mass assignable.
24     *
25     * @var array
26     */
27    protected $guarded = [];
28
29    /**
30     * The attributes that should be cast to native types.
31     *
32     * @var array
33     */
34    protected $casts = [
35        'quantity' => 'integer',
36    ];
37
38    /**
39     * 
40     * Recent update to laravel cashier is forcing the stripe_plan field into stripe_price.
41     * Here is an attempt to ensure there's still a stripe_plan field.
42     */
43    public static function boot(): void
44    {
45        parent::boot();
46
47        self::creating(function($model) {
48            $model->stripe_plan = $model->stripe_price ?? $model->stripe_plan;
49        });
50    }
51
52    /**
53     * Get the subscription that the item belongs to.
54     */
55    public function subscription(): BelongsTo
56    {
57        return $this->belongsTo(Cashier::$subscriptionModel);
58    }
59
60    /**
61     * Increment the quantity of the subscription item.
62     *
63     *
64     * @throws \Laravel\Cashier\Exceptions\SubscriptionUpdateFailure
65     */
66    public function incrementQuantity(int $count = 1): static
67    {
68        $this->updateQuantity($this->quantity + $count);
69
70        return $this;
71    }
72
73    /**
74     *  Increment the quantity of the subscription item, and invoice immediately.
75     *
76     *
77     * @throws \Laravel\Cashier\Exceptions\IncompletePayment
78     * @throws \Laravel\Cashier\Exceptions\SubscriptionUpdateFailure
79     */
80    public function incrementAndInvoice(int $count = 1): static
81    {
82        $this->alwaysInvoice();
83
84        $this->incrementQuantity($count);
85
86        return $this;
87    }
88
89    /**
90     * Decrement the quantity of the subscription item.
91     *
92     *
93     * @throws \Laravel\Cashier\Exceptions\SubscriptionUpdateFailure
94     */
95    public function decrementQuantity(int $count = 1): static
96    {
97        $this->updateQuantity(max(1, $this->quantity - $count));
98
99        return $this;
100    }
101
102    /**
103     * Update the quantity of the subscription item.
104     *
105     *
106     * @throws \Laravel\Cashier\Exceptions\SubscriptionUpdateFailure
107     */
108    public function updateQuantity(int $quantity): static
109    {
110        $this->subscription->guardAgainstIncomplete();
111
112        $stripeSubscriptionItem = $this->asStripeSubscriptionItem();
113
114        $stripeSubscriptionItem->quantity = $quantity;
115
116        $stripeSubscriptionItem->payment_behavior = $this->paymentBehavior();
117
118        $stripeSubscriptionItem->proration_behavior = $this->prorateBehavior();
119
120        $stripeSubscriptionItem->save();
121
122        $this->quantity = $quantity;
123
124        $this->save();
125
126        if ($this->subscription->hasSinglePlan()) {
127            $this->subscription->quantity = $quantity;
128
129            $this->subscription->save();
130        }
131
132        return $this;
133    }
134
135    /**
136     * Swap the subscription item to a new Stripe plan.
137     *
138     *
139     * @throws \Laravel\Cashier\Exceptions\SubscriptionUpdateFailure
140     */
141    public function swap(string $plan, array $options = []): static
142    {
143        $this->subscription->guardAgainstIncomplete();
144
145        $options = array_merge([
146            'plan' => $plan,
147            'quantity' => $this->quantity,
148            'payment_behavior' => $this->paymentBehavior(),
149            'proration_behavior' => $this->prorateBehavior(),
150            'tax_rates' => $this->subscription->getPlanTaxRatesForPayload($plan),
151        ], $options);
152
153        $item = StripeSubscriptionItem::update(
154            $this->stripe_id,
155            $options,
156            $this->subscription->owner->stripe()
157        );
158
159        $this->fill([
160            'stripe_plan' => $plan,
161            'quantity' => $item->quantity,
162        ])->save();
163
164        if ($this->subscription->hasSinglePlan()) {
165            $this->subscription->fill([
166                'stripe_plan' => $plan,
167                'quantity' => $item->quantity,
168            ])->save();
169        }
170
171        return $this;
172    }
173
174    /**
175     * Swap the subscription item to a new Stripe plan, and invoice immediately.
176     *
177     *
178     * @throws \Laravel\Cashier\Exceptions\IncompletePayment
179     * @throws \Laravel\Cashier\Exceptions\SubscriptionUpdateFailure
180     */
181    public function swapAndInvoice(string $plan, array $options = []): static
182    {
183        $this->alwaysInvoice();
184
185        return $this->swap($plan, $options);
186    }
187
188    /**
189     * Report usage for a metered product.
190     *
191     * @param  \DateTimeInterface|int|null  $timestamp
192     */
193    public function reportUsage(int $quantity = 1, $timestamp = null): UsageRecord
194    {
195        $timestamp = $timestamp instanceof DateTimeInterface ? $timestamp->getTimestamp() : $timestamp;
196
197        return StripeSubscriptionItem::createUsageRecord($this->stripe_id, [
198            'quantity' => $quantity,
199            'action' => $timestamp ? 'set' : 'increment',
200            'timestamp' => $timestamp ?? time(),
201        ], $this->subscription->owner->stripe());
202    }
203
204    /**
205     * Get the usage records for a metered product.
206     */
207    public function usageRecords(array $options = []): Collection
208    {
209        return new Collection(StripeSubscriptionItem::allUsageRecordSummaries(
210            $this->stripe_id,
211            $options,
212            $this->subscription->owner->stripe()
213        )->data);
214    }
215
216    /**
217     * Update the underlying Stripe subscription item information for the model.
218     */
219    public function updateStripeSubscriptionItem(array $options = []): StripeSubscriptionItem
220    {
221        return StripeSubscriptionItem::update(
222            $this->stripe_id,
223            $options,
224            $this->subscription->owner->stripe()
225        );
226    }
227
228    /**
229     * Get the subscription as a Stripe subscription item object.
230     */
231    public function asStripeSubscriptionItem(array $expand = []): StripeSubscriptionItem
232    {
233        return StripeSubscriptionItem::retrieve(
234            ['id' => $this->stripe_id, 'expand' => $expand],
235            $this->subscription->owner->stripe()
236        );
237    }
238}