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