Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 120
0.00% covered (danger)
0.00%
0 / 16
CRAP
0.00% covered (danger)
0.00%
0 / 1
WebhookController
0.00% covered (danger)
0.00%
0 / 120
0.00% covered (danger)
0.00%
0 / 16
2352
0.00% covered (danger)
0.00%
0 / 1
 newSubscriptionName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 handleCustomerSubscriptionUpdated
0.00% covered (danger)
0.00%
0 / 43
0.00% covered (danger)
0.00%
0 / 1
306
 handleCustomerCreated
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 handleCustomerSubscriptionDeleted
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 handleCustomerSubscriptionCreated
0.00% covered (danger)
0.00%
0 / 29
0.00% covered (danger)
0.00%
0 / 1
72
 updateSubscriptionDetails
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 handlePaymentIntentSucceeded
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 handlePaymentIntentRequiresAction
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 handlePaymentIntentPartiallyFunded
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 handlePaymentIntentPaymentFailed
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 handlePaymentIntentCanceled
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 handlePaymentIntentProcessing
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 updatePaymentStatus
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getCurrentPlan
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 getStatus
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
2
 getSubsciptionStatus
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace App\Http\Controllers\v1;
4
5use App\Http\Models\Auth\User;
6use App\Http\Models\Plans;
7use App\Http\Models\Subscription;
8use Carbon\Carbon;
9use Laravel\Cashier\Http\Controllers\WebhookController as CashierController;
10use Stripe\Subscription as StripeSubscription;
11use Symfony\Component\HttpFoundation\Response;
12
13class WebhookController extends CashierController
14{
15    protected $current_plan;
16
17    protected function newSubscriptionName(array $payload)
18    {
19        return 'main';
20    }
21
22    /**
23     * Handle customer subscription updated.
24     */
25    protected function handleCustomerSubscriptionUpdated(array $payload): Response
26    {
27        if ($user = $this->getUserByStripeId($payload['data']['object']['customer'])) {
28            $data = $payload['data']['object'];
29
30            $user->subscriptions->filter(function (Subscription $subscription) use ($data) {
31                return $subscription->stripe_id === $data['id'];
32            })->each(function (Subscription $subscription) use ($data, $user, $payload) {
33                if (
34                    isset($data['status']) &&
35                    $data['status'] === StripeSubscription::STATUS_INCOMPLETE_EXPIRED
36                ) {
37                    $subscription->items()->delete();
38                    $subscription->delete();
39
40                    return;
41                }
42
43                $firstItem = $data['items']['data'][0];
44                $isSinglePlan = count($data['items']['data']) === 1;
45
46                // Plan...
47                $subscription->stripe_plan = $isSinglePlan ? $firstItem['plan']['id'] : null;
48
49                // Quantity...
50                $subscription->quantity = $isSinglePlan && isset($firstItem['quantity']) ? $firstItem['quantity'] : null;
51
52                // Trial ending date...
53                if (isset($data['trial_end'])) {
54                    $trialEnd = Carbon::createFromTimestamp($data['trial_end']);
55
56                    if (! $subscription->trial_ends_at || $subscription->trial_ends_at->ne($trialEnd)) {
57                        $subscription->trial_ends_at = $trialEnd;
58                    }
59                }
60
61                // Cancellation date...
62                if (isset($data['cancel_at_period_end'])) {
63                    if ($data['cancel_at_period_end']) {
64                        $subscription->ends_at = $subscription->onTrial()
65                            ? $subscription->trial_ends_at
66                            : Carbon::createFromTimestamp($data['current_period_end']);
67                    } elseif (isset($data['cancel_at'])) {
68                        $subscription->ends_at = Carbon::createFromTimestamp($data['cancel_at']);
69                    } else {
70                        $subscription->ends_at = null;
71                    }
72                }
73
74                // Status...
75                if (isset($data['status'])) {
76                    $subscription->stripe_status = $data['status'];
77                }
78
79                $subscription->save();
80
81                // Update subscription items...
82                if (isset($data['items'])) {
83                    $plans = [];
84
85                    foreach ($data['items']['data'] as $item) {
86                        $plans[] = $item['plan']['id'];
87
88                        $subscription->items()->updateOrCreate([
89                            'stripe_id' => $item['id'],
90                        ], [
91                            'stripe_plan' => $item['plan']['id'],
92                            'quantity' => $item['quantity'] ?? null,
93                        ]);
94                    }
95                    // Delete items that aren't attached to the subscription anymore...
96                    $subscription->items()->whereNotIn('stripe_plan', $plans)->delete();
97                }
98
99                $this->updateSubscriptionDetails($user, $payload);
100            });
101        }
102
103        return $this->successMethod();
104    }
105
106    protected function handleCustomerCreated(array $payload)
107    {
108        if ($user = $this->getUserByStripeId($payload['data']['object']['id'])) {
109        }
110
111        return $this->successMethod();
112    }
113
114    /**
115     * Handle a cancelled customer from a Stripe subscription.
116     */
117    protected function handleCustomerSubscriptionDeleted(array $payload): Response
118    {
119        if ($user = $this->getUserByStripeId($payload['data']['object']['customer'])) {
120            $user->subscriptions->filter(function ($subscription) use ($payload) {
121                return $subscription->stripe_id === $payload['data']['object']['id'];
122            })->each(function ($subscription) {
123                $subscription->markAsCancelled();
124            });
125        }
126
127        return $this->successMethod();
128    }
129
130    /**
131     * Handle customer subscription created.
132     */
133    protected function handleCustomerSubscriptionCreated(array $payload): Response
134    {
135        $user = $this->getUserByStripeId($payload['data']['object']['customer']);
136
137        if ($user) {
138            $data = $payload['data']['object'];
139
140            if (! $user->subscriptions->contains('stripe_id', $data['id'])) {
141                if (isset($data['trial_end'])) {
142                    $trialEndsAt = Carbon::createFromTimestamp($data['trial_end']);
143                } else {
144                    $trialEndsAt = null;
145                }
146
147                $firstItem = $data['items']['data'][0];
148                $isSinglePlan = count($data['items']['data']) === 1;
149                $coupon = null;
150                $coupon = $user->discount ?? $user->discount()->coupon();
151
152                $subscription = $user->subscriptions()->create([
153                    'name' => $data['metadata']['name'] ?? $this->newSubscriptionName($payload),
154                    'stripe_id' => $data['id'],
155                    'stripe_status' => $data['status'],
156                    'stripe_plan' => $isSinglePlan ? $firstItem['plan']['id'] : null,
157                    'quantity' => $isSinglePlan && isset($firstItem['quantity']) ? $firstItem['quantity'] : null,
158                    'trial_ends_at' => $trialEndsAt,
159                    'ends_at' => null,
160                    'coupon' => $coupon->id,
161                ]);
162
163                foreach ($data['items']['data'] as $item) {
164                    $subscription->items()->create([
165                        'stripe_id' => $item['id'],
166                        'stripe_plan' => $item['plan']['id'],
167                        'quantity' => $item['quantity'] ?? null,
168                    ]);
169                }
170            }
171            $this->updateSubscriptionDetails($user, $payload);
172        }
173
174        return $this->successMethod();
175    }
176
177    protected function updateSubscriptionDetails(User $user, $payload)
178    {
179        $this->current_plan = $this->getCurrentPlan($user);
180    }
181
182    protected function handlePaymentIntentSucceeded(array $payload)
183    {
184        if ($user = $this->getUserByStripeId($payload['data']['object']['customer'])) {
185            $this->updatePaymentStatus($user, $payload['data']['object']['status']);
186        }
187    }
188
189    protected function handlePaymentIntentRequiresAction(array $payload)
190    {
191        if ($user = $this->getUserByStripeId($payload['data']['object']['customer'])) {
192            $this->updatePaymentStatus($user, $payload['data']['object']['status']);
193        }
194    }
195
196    protected function handlePaymentIntentPartiallyFunded(array $payload)
197    {
198        if ($user = $this->getUserByStripeId($payload['data']['object']['customer'])) {
199            $this->updatePaymentStatus($user, $payload['data']['object']['status']);
200        }
201    }
202
203    protected function handlePaymentIntentPaymentFailed(array $payload)
204    {
205        if ($user = $this->getUserByStripeId($payload['data']['object']['customer'])) {
206            $this->updatePaymentStatus($user, $payload['data']['object']['status']);
207        }
208    }
209
210    protected function handlePaymentIntentCanceled(array $payload)
211    {
212        if ($user = $this->getUserByStripeId($payload['data']['object']['customer'])) {
213            $this->updatePaymentStatus($user, $payload['data']['object']['status']);
214        }
215    }
216
217    protected function handlePaymentIntentProcessing(array $payload)
218    {
219        if ($user = $this->getUserByStripeId($payload['data']['object']['customer'])) {
220            $this->updatePaymentStatus($user, $payload['data']['object']['status']);
221        }
222    }
223
224    protected function updatePaymentStatus(User $user, $status) {}
225
226    public function getCurrentPlan(User $user)
227    {
228        $current_subscription = $user->subscription('main');
229
230        if ($current_subscription) {
231            return $current_subscription->plan;
232        }
233
234        return Plans::select('title', 'identifier', 'features', 'currency', 'interval', 'unit_amount')
235            ->where('identifier', 'freemium')->first();
236    }
237
238    public function getStatus($status)
239    {
240        $status_arr = [
241            StripeSubscription::STATUS_ACTIVE => 'Succeeded',
242            StripeSubscription::STATUS_CANCELED => 'Succeeded',
243            StripeSubscription::STATUS_INCOMPLETE => 'Failed',
244            StripeSubscription::STATUS_INCOMPLETE_EXPIRED => 'Failed',
245            StripeSubscription::STATUS_PAST_DUE => 'Failed',
246            StripeSubscription::STATUS_TRIALING => 'Pending',
247            StripeSubscription::STATUS_UNPAID => 'Succeeded',
248        ];
249
250        return @$status_arr[$status];
251    }
252
253    protected function getSubsciptionStatus($status)
254    {
255        $status_arr = [
256            StripeSubscription::STATUS_ACTIVE => 'Active',
257            StripeSubscription::STATUS_PAST_DUE => 'Past due',
258            StripeSubscription::STATUS_UNPAID => 'Unpaid',
259            StripeSubscription::STATUS_UNPAID => 'Canceled',
260            StripeSubscription::STATUS_INCOMPLETE_EXPIRED => 'Expired',
261            StripeSubscription::STATUS_INCOMPLETE_EXPIRED => 'Incomplete',
262        ];
263
264        return @$status_arr[$status];
265    }
266}