Subscription::valid()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 1
c 1
b 0
f 0
dl 0
loc 3
rs 10
cc 3
nc 3
nop 0
1
<?php
2
3
namespace Phalcon\Cashier;
4
5
use Carbon\Carbon;
6
use LogicException;
7
use DateTimeInterface;
8
use Baka\Database\Model;
9
10
class Subscription extends Model
11
{
12
    /**
13
     * The attributes that aren't mass assignable.
14
     *
15
     * @var array
16
     */
17
    protected $guarded = [];
18
19
    /**
20
     * The attributes that should be mutated to dates.
21
     *
22
     * @var array
23
     */
24
    protected $dates = [
25
        'trial_ends_at', 'ends_at',
26
        'created_at', 'updated_at',
27
    ];
28
29
    /**
30
     * Indicates if the plan change should be prorated.
31
     *
32
     * @var bool
33
     */
34
    protected $prorate = true;
35
36
    /**
37
     * The date on which the billing cycle should be anchored.
38
     *
39
     * @var string|null
40
     */
41
    protected $billingCycleAnchor = null;
42
43
    public function initialize()
44
    {
45
        $this->belongsTo('user_id', '\App\Models\Users', 'id', ['alias' => 'user']);
46
    }
47
48
    public function getSource(): string
49
    {
50
        return 'subscriptions';
51
    }
52
53
    /**
54
     * Get the user that owns the subscription.
55
     */
56
    public function user()
57
    {
58
        return $this->user;
59
    }
60
61
    /**
62
     * Determine if the subscription is active, on trial, or within its grace period.
63
     *
64
     * @return bool
65
     */
66
    public function valid()
67
    {
68
        return $this->active() || $this->onTrial() || $this->onGracePeriod();
69
    }
70
71
    /**
72
     * Determine if the subscription is active.
73
     *
74
     * @return bool
75
     */
76
    public function active()
77
    {
78
        return is_null($this->ends_at) || $this->onGracePeriod();
79
    }
80
81
    /**
82
     * Determine if the subscription is no longer active.
83
     *
84
     * @return bool
85
     */
86
    public function cancelled()
87
    {
88
        return !is_null($this->ends_at);
89
    }
90
91
    /**
92
     * Determine if the subscription is within its trial period.
93
     *
94
     * @return bool
95
     */
96
    public function onTrial()
97
    {
98
        if (!is_null($this->trial_ends_at)) {
99
            $trialEndsAt = new \DateTime($this->trial_ends_at);
100
            return Carbon::today()->lt(Carbon::instance($trialEndsAt));
101
        } else {
102
            return false;
103
        }
104
    }
105
106
    /**
107
     * Determine if the subscription is within its grace period after cancellation.
108
     *
109
     * @return bool
110
     */
111
    public function onGracePeriod()
112
    {
113
        $endsAt = new \DateTime($this->ends_at);
114
115
        if (!is_null($endsAt)) {
116
            return Carbon::now()->lt(Carbon::instance($endsAt));
117
        } else {
118
            return false;
119
        }
120
    }
121
122
    /**
123
     * Increment the quantity of the subscription.
124
     *
125
     * @param  int  $count
126
     * @return $this
127
     */
128
    public function incrementQuantity($count = 1)
129
    {
130
        $this->updateQuantity($this->quantity + $count);
131
132
        return $this;
133
    }
134
135
    /**
136
     *  Increment the quantity of the subscription, and invoice immediately.
137
     *
138
     * @param  int  $count
139
     * @return $this
140
     */
141
    public function incrementAndInvoice($count = 1)
142
    {
143
        $this->incrementQuantity($count);
144
145
        $this->user->invoice();
146
147
        return $this;
148
    }
149
150
    /**
151
     * Decrement the quantity of the subscription.
152
     *
153
     * @param  int  $count
154
     * @return $this
155
     */
156
    public function decrementQuantity($count = 1)
157
    {
158
        $this->updateQuantity(max(1, $this->quantity - $count));
159
160
        return $this;
161
    }
162
163
    /**
164
     * Update the quantity of the subscription.
165
     *
166
     * @param  int  $quantity
167
     * @param  \Stripe\Customer|null  $customer
168
     * @return $this
169
     */
170
    public function updateQuantity($quantity, $customer = null)
171
    {
172
        $subscription = $this->asStripeSubscription();
173
174
        $subscription->quantity = $quantity;
175
176
        $subscription->save();
177
178
        $this->quantity = $quantity;
0 ignored issues
show
Bug Best Practice introduced by
The property quantity does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
179
180
        $this->save();
181
182
        return $this;
183
    }
184
185
    /**
186
     * Indicate that the plan change should not be prorated.
187
     *
188
     * @return $this
189
     */
190
    public function noProrate()
191
    {
192
        $this->prorate = false;
193
194
        return $this;
195
    }
196
197
    /**
198
     * Change the billing cycle anchor on a plan change.
199
     *
200
     * @param  int|string  $date
201
     * @return $this
202
     */
203
    public function anchorBillingCycleOn($date = 'now')
204
    {
205
        if ($date instanceof DateTimeInterface) {
0 ignored issues
show
introduced by
$date is never a sub-type of DateTimeInterface.
Loading history...
206
            $date = $date->getTimestamp();
207
        }
208
209
        $this->billingCycleAnchor = $date;
210
211
        return $this;
212
    }
213
214
    /**
215
     * Swap the subscription to a new Stripe plan.
216
     *
217
     * @param  string  $plan
218
     * @return $this
219
     */
220
    public function swap($plan)
221
    {
222
        $subscription = $this->asStripeSubscription();
223
224
        $subscription->plan = $plan;
0 ignored issues
show
Documentation Bug introduced by
It seems like $plan of type string is incompatible with the declared type Stripe\Plan of property $plan.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
225
226
        $subscription->prorate = $this->prorate;
227
228
        $subscription->cancel_at_period_end = false;
229
230
        if (!is_null($this->billingCycleAnchor)) {
231
            $subscription->billingCycleAnchor = $this->billingCycleAnchor;
232
        }
233
234
        // If no specific trial end date has been set, the default behavior should be
235
        // to maintain the current trial state, whether that is "active" or to run
236
        // the swap out with the exact number of days left on this current plan.
237
        if ($this->onTrial()) {
238
            $subscription->trial_end = strtotime($this->trial_ends_at);
239
        } else {
240
            $subscription->trial_end = 'now';
0 ignored issues
show
Documentation Bug introduced by
The property $trial_end was declared of type integer, but 'now' is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
241
        }
242
243
        // Again, if no explicit quantity was set, the default behaviors should be to
244
        // maintain the current quantity onto the new plan. This is a sensible one
245
        // that should be the expected behavior for most developers with Stripe.
246
        if ($this->quantity) {
247
            $subscription->quantity = $this->quantity;
248
        }
249
250
        $subscription->save();
251
252
        $this->user->invoice();
253
254
        $this->update([
255
            'stripe_plan' => $plan,
256
            'ends_at' => null,
257
        ]);
258
259
        return $this;
260
    }
261
262
    /**
263
     * Cancel the subscription at the end of the billing period.
264
     *
265
     * @return $this
266
     */
267
    public function cancel()
268
    {
269
        $subscription = $this->asStripeSubscription();
270
        $subscription->cancel_at_period_end = true;
271
        $subscription->save();
272
273
        $this->markAsCancelled();
274
275
        return $this;
276
    }
277
278
    /**
279
     * Reactivate the subscription at the end of the billing period.
280
     *
281
     * @return $this
282
     */
283
    public function reactivate()
284
    {
285
        $subscription = $this->asStripeSubscription();
286
        $subscription->cancel_at_period_end = false;
287
        $subscription->save();
288
289
        return $this;
290
    }
291
292
    /**
293
     * Cancel the subscription immediately.
294
     *
295
     * @return $this
296
     */
297
    public function cancelNow()
298
    {
299
        $subscription = $this->asStripeSubscription();
300
        $subscription->cancel();
301
302
        $this->markAsCancelled();
303
304
        return $this;
305
    }
306
307
    /**
308
     * Mark the subscription as cancelled.
309
     *
310
     * @return void
311
     */
312
    public function markAsCancelled()
313
    {
314
        $this->update(['ends_at' => Carbon::now()->toDateTimeString()]);
315
    }
316
317
    /**
318
     * Resume the cancelled subscription.
319
     *
320
     * @return $this
321
     *
322
     * @throws \LogicException
323
     */
324
    public function resume()
325
    {
326
        if (!$this->onGracePeriod()) {
327
            throw new LogicException('Unable to resume subscription that is not within grace period.');
328
        }
329
330
        $subscription = $this->asStripeSubscription();
331
332
        $subscription->cancel_at_period_end = false;
333
334
        // To resume the subscription we need to set the plan parameter on the Stripe
335
        // subscription object. This will force Stripe to resume this subscription
336
        // where we left off. Then, we'll set the proper trial ending timestamp.
337
        $subscription->plan = $this->stripe_plan;
338
339
        if ($this->onTrial()) {
340
            $subscription->trial_end = strtotime($this->trial_ends_at);
341
        } else {
342
            $subscription->trial_end = 'now';
0 ignored issues
show
Documentation Bug introduced by
The property $trial_end was declared of type integer, but 'now' is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
343
        }
344
        $subscription->save();
345
346
        // Finally, we will remove the ending timestamp from the user's record in the
347
        // local database to indicate that the subscription is active again and is
348
        // no longer "cancelled". Then we will save this record in the database.
349
        $this->update(['ends_at' => null]);
350
351
        return $this;
352
    }
353
354
    /**
355
     * Sync the tax percentage of the user to the subscription.
356
     *
357
     * @return void
358
     */
359
    public function syncTaxPercentage()
360
    {
361
        $subscription = $this->asStripeSubscription();
362
        $subscription->tax_percent = $this->user->taxPercentage();
363
        $subscription->save();
364
    }
365
366
    /**
367
     * Get the subscription as a Stripe subscription object.
368
     *
369
     * @return \Stripe\Subscription
370
     */
371
    public function asStripeSubscription()
372
    {
373
        $user = $this->user();
374
        return $user->asStripeCustomer()->subscriptions->retrieve($this->stripe_id);
375
    }
376
}
377