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     * Recent update to laravel cashier is forcing the stripe_plan field into stripe_price.
40     * Here is an attempt to ensure there's still a stripe_plan field.
41     */
42    public static function boot(): void
43    {
44        parent::boot();
45
46        self::creating(function ($model) {
47            $model->stripe_plan = $model->stripe_price ?? $model->stripe_plan;
48        });
49    }
50
51    /**
52     * Get the subscription that the item belongs to.
53     */
54    public function subscription(): BelongsTo
55    {
56        return $this->belongsTo(Cashier::$subscriptionModel);
57    }
58
59    /**
60     * Increment the quantity of the subscription item.
61     *
62     *
63     * @throws \Laravel\Cashier\Exceptions\SubscriptionUpdateFailure
64     */
65    public function incrementQuantity(int $count = 1): static
66    {
67        $this->updateQuantity($this->quantity + $count);
68
69        return $this;
70    }
71
72    /**
73     *  Increment the quantity of the subscription item, and invoice immediately.
74     *
75     *
76     * @throws \Laravel\Cashier\Exceptions\IncompletePayment
77     * @throws \Laravel\Cashier\Exceptions\SubscriptionUpdateFailure
78     */
79    public function incrementAndInvoice(int $count = 1): static
80    {
81        $this->alwaysInvoice();
82
83        $this->incrementQuantity($count);
84
85        return $this;
86    }
87
88    /**
89     * Decrement the quantity of the subscription item.
90     *
91     *
92     * @throws \Laravel\Cashier\Exceptions\SubscriptionUpdateFailure
93     */
94    public function decrementQuantity(int $count = 1): static
95    {
96        $this->updateQuantity(max(1, $this->quantity - $count));
97
98        return $this;
99    }
100
101    /**
102     * Update the quantity of the subscription item.
103     *
104     *
105     * @throws \Laravel\Cashier\Exceptions\SubscriptionUpdateFailure
106     */
107    public function updateQuantity(int $quantity): static
108    {
109        $this->subscription->guardAgainstIncomplete();
110
111        $stripeSubscriptionItem = $this->asStripeSubscriptionItem();
112
113        $stripeSubscriptionItem->quantity = $quantity;
114
115        $stripeSubscriptionItem->payment_behavior = $this->paymentBehavior();
116
117        $stripeSubscriptionItem->proration_behavior = $this->prorateBehavior();
118
119        $stripeSubscriptionItem->save();
120
121        $this->quantity = $quantity;
122
123        $this->save();
124
125        if ($this->subscription->hasSinglePlan()) {
126            $this->subscription->quantity = $quantity;
127
128            $this->subscription->save();
129        }
130
131        return $this;
132    }
133
134    /**
135     * Swap the subscription item to a new Stripe plan.
136     *
137     *
138     * @throws \Laravel\Cashier\Exceptions\SubscriptionUpdateFailure
139     */
140    public function swap(string $plan, array $options = []): static
141    {
142        $this->subscription->guardAgainstIncomplete();
143
144        $options = array_merge([
145            'plan' => $plan,
146            'quantity' => $this->quantity,
147            'payment_behavior' => $this->paymentBehavior(),
148            'proration_behavior' => $this->prorateBehavior(),
149            'tax_rates' => $this->subscription->getPlanTaxRatesForPayload($plan),
150        ], $options);
151
152        $item = StripeSubscriptionItem::update(
153            $this->stripe_id,
154            $options,
155            $this->subscription->owner->stripe()
156        );
157
158        $this->fill([
159            'stripe_plan' => $plan,
160            'quantity' => $item->quantity,
161        ])->save();
162
163        if ($this->subscription->hasSinglePlan()) {
164            $this->subscription->fill([
165                'stripe_plan' => $plan,
166                'quantity' => $item->quantity,
167            ])->save();
168        }
169
170        return $this;
171    }
172
173    /**
174     * Swap the subscription item to a new Stripe plan, and invoice immediately.
175     *
176     *
177     * @throws \Laravel\Cashier\Exceptions\IncompletePayment
178     * @throws \Laravel\Cashier\Exceptions\SubscriptionUpdateFailure
179     */
180    public function swapAndInvoice(string $plan, array $options = []): static
181    {
182        $this->alwaysInvoice();
183
184        return $this->swap($plan, $options);
185    }
186
187    /**
188     * Report usage for a metered product.
189     *
190     * @param  \DateTimeInterface|int|null  $timestamp
191     */
192    public function reportUsage(int $quantity = 1, $timestamp = null): UsageRecord
193    {
194        $timestamp = $timestamp instanceof DateTimeInterface ? $timestamp->getTimestamp() : $timestamp;
195
196        return StripeSubscriptionItem::createUsageRecord($this->stripe_id, [
197            'quantity' => $quantity,
198            'action' => $timestamp ? 'set' : 'increment',
199            'timestamp' => $timestamp ?? time(),
200        ], $this->subscription->owner->stripe());
201    }
202
203    /**
204     * Get the usage records for a metered product.
205     */
206    public function usageRecords(array $options = []): Collection
207    {
208        return new Collection(StripeSubscriptionItem::allUsageRecordSummaries(
209            $this->stripe_id,
210            $options,
211            $this->subscription->owner->stripe()
212        )->data);
213    }
214
215    /**
216     * Update the underlying Stripe subscription item information for the model.
217     */
218    public function updateStripeSubscriptionItem(array $options = []): StripeSubscriptionItem
219    {
220        return StripeSubscriptionItem::update(
221            $this->stripe_id,
222            $options,
223            $this->subscription->owner->stripe()
224        );
225    }
226
227    /**
228     * Get the subscription as a Stripe subscription item object.
229     */
230    public function asStripeSubscriptionItem(array $expand = []): StripeSubscriptionItem
231    {
232        return StripeSubscriptionItem::retrieve(
233            ['id' => $this->stripe_id, 'expand' => $expand],
234            $this->subscription->owner->stripe()
235        );
236    }
237}