Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 249 |
|
0.00% |
0 / 9 |
CRAP | |
0.00% |
0 / 1 |
SubscriptionService | |
0.00% |
0 / 249 |
|
0.00% |
0 / 9 |
7832 | |
0.00% |
0 / 1 |
created | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
updateSubscriptionInfo | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
initFreemiumSubscription | |
0.00% |
0 / 17 |
|
0.00% |
0 / 1 |
30 | |||
endFreemiumSubscription | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
42 | |||
startSubscription | |
0.00% |
0 / 111 |
|
0.00% |
0 / 1 |
1640 | |||
endSubscription | |
0.00% |
0 / 55 |
|
0.00% |
0 / 1 |
306 | |||
checkPremiumSubscriptionsActive | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
20 | |||
getCountryName | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
parseFields | |
0.00% |
0 / 33 |
|
0.00% |
0 / 1 |
156 |
1 | <?php |
2 | |
3 | namespace App\Services\UserInfo; |
4 | |
5 | use App\Http\Models\Subscription; |
6 | |
7 | use App\Http\Models\UserInfo; |
8 | use App\Http\Models\Auth\User; |
9 | use App\Http\Models\Plans; |
10 | use App\Traits\ObjectMapper; |
11 | use Carbon\Carbon; |
12 | use Illuminate\Support\Facades\Log; |
13 | use Illuminate\Support\Str; |
14 | use MongoDB\BSON\UTCDateTime; |
15 | |
16 | class SubscriptionService |
17 | { |
18 | use ObjectMapper; |
19 | |
20 | public function created(Subscription $subscription): void |
21 | { |
22 | $created = $subscription->toArray(); |
23 | |
24 | $parsedFields = $this->parseFields($created, $subscription); |
25 | |
26 | $this->updateSubscriptionInfo($parsedFields); |
27 | } |
28 | |
29 | public function updateSubscriptionInfo(array $data) |
30 | { |
31 | $subscriptionInfo = UserInfo::firstOrNew(['user_id' => $data['user_id']]); |
32 | $subscriptionInfo->fill($data); |
33 | $subscriptionInfo->save(); |
34 | } |
35 | |
36 | public function initFreemiumSubscription(string $user_id, $cancellation_date = null): array |
37 | { |
38 | $now = Carbon::now(); |
39 | if (!empty($cancellation_date)) { |
40 | $now = $cancellation_date; |
41 | } |
42 | |
43 | $user = User::find($user_id); |
44 | $plan = Plans::where('identifier', 'freemium')->first(); |
45 | $result = [ |
46 | 'subscription_type' => $plan->hubspot_name, |
47 | 'plan_name' => $plan->title, |
48 | 'plan_id' => $plan->id, |
49 | ]; |
50 | |
51 | if ($plan->hubspot_subscription_status) { |
52 | $result[$plan->hubspot_subscription_status] = 'Active'; |
53 | } |
54 | |
55 | if ($plan->hubspot_subscription_status_updated_on) { |
56 | $result[$plan->hubspot_subscription_status_updated_on] = new UTCDateTime($now->timestamp * 1000); |
57 | } |
58 | |
59 | if ($plan->hubspot_subscription_start_date) { |
60 | $result[$plan->hubspot_subscription_start_date] = new UTCDateTime($user->created_at->timestamp * 1000); |
61 | } |
62 | |
63 | return $result; |
64 | } |
65 | |
66 | public function endFreemiumSubscription(string $user_id, $date): array |
67 | { |
68 | $user = User::withTrashed()->find($user_id); |
69 | $plan = Plans::where('identifier', 'freemium')->first(); |
70 | $result = []; |
71 | |
72 | if ($plan->hubspot_subscription_status) { |
73 | $result[$plan->hubspot_subscription_status] = 'Canceled'; |
74 | } |
75 | |
76 | if ($plan->hubspot_subscription_status_updated_on) { |
77 | $result[$plan->hubspot_subscription_status_updated_on] = new UTCDateTime($date->timestamp * 1000); |
78 | } |
79 | |
80 | if ($plan->hubspot_subscription_start_date) { |
81 | $result[$plan->hubspot_subscription_start_date] = new UTCDateTime($user->created_at->timestamp * 1000); |
82 | } |
83 | |
84 | if ($plan->hubspot_cancel_subscription_date) { |
85 | $result[$plan->hubspot_cancel_subscription_date] = new UTCDateTime($date->timestamp * 1000); |
86 | } |
87 | |
88 | if ($plan->hubspot_subscription_churn_date) { |
89 | $result[$plan->hubspot_subscription_churn_date] = new UTCDateTime($date->timestamp * 1000); |
90 | } |
91 | |
92 | return $result; |
93 | } |
94 | |
95 | public function startSubscription(array $fields, Subscription $subscription): array |
96 | { |
97 | $map = [ |
98 | 'stripe_plan' => ['rename' => 'subscription_type', 'transform' => fn($value) => $value ? Plans::where('stripe_id', $value)->first()?->hubspot_name : null], |
99 | ]; |
100 | $result = $this->mapObject($fields, $map); |
101 | |
102 | if (!empty($fields['stripe_plan'])) { |
103 | $user = User::find($subscription->user_id); |
104 | $plan = Plans::where('stripe_id', $fields['stripe_plan'])->first(); |
105 | $result['subscription_type'] = $plan->hubspot_name; |
106 | $result['flymsg_last_product_purchased'] = $plan->hubspot_last_product; |
107 | $result['plan_name'] = $plan->title; |
108 | $result['plan_id'] = $plan->id; |
109 | |
110 | if ($plan->hubspot_payment_status) { |
111 | $result[$plan->hubspot_payment_status] = 'Succeeded'; |
112 | } |
113 | |
114 | if ($plan->hubspot_subscription_status) { |
115 | $result[$plan->hubspot_subscription_status] = $subscription->trial_ends_at ? 'Trial' : 'Active'; |
116 | } |
117 | |
118 | if ($plan->hubspot_subscription_status_updated_on) { |
119 | $result[$plan->hubspot_subscription_status_updated_on] = new UTCDateTime($subscription->created_at->timestamp * 1000); |
120 | } |
121 | |
122 | if ($plan->hubspot_subscription_start_date) { |
123 | $result[$plan->hubspot_subscription_start_date] = new UTCDateTime($subscription->created_at->timestamp * 1000); |
124 | } |
125 | |
126 | if ($plan->hubspot_subscription_frequency) { |
127 | if (Str::contains($plan->identifier, "pro")) { |
128 | $frequency = match (true) { |
129 | Str::contains($plan->identifier, "monthly") => 'Monthly', |
130 | Str::contains($plan->identifier, "appsumo-growth-lifetime") => 'Lifetime - AppSumo', |
131 | Str::contains($plan->identifier, "dealfuel-growth-lifetime") => 'Lifetime - DealFuel', |
132 | default => "Annual", |
133 | }; |
134 | } else { |
135 | $frequency = match (true) { |
136 | Str::contains($plan->identifier, "yearly") => 'Annual', |
137 | Str::contains($plan->identifier, "appsumo-growth-lifetime") => 'Lifetime - AppSumo', |
138 | Str::contains($plan->identifier, "dealfuel-growth-lifetime") => 'Lifetime - DealFuel', |
139 | default => "Monthly", |
140 | }; |
141 | } |
142 | |
143 | $result[$plan->hubspot_subscription_frequency] = $frequency; |
144 | } |
145 | |
146 | if ($plan->hubspot_subscription_expiration_date) { |
147 | $startsAt = $subscription->created_at; |
148 | $cycleEndsAt = $subscription->ends_at; |
149 | |
150 | if ($result[$plan->hubspot_subscription_frequency] === 'Monthly') { |
151 | $cycleEndsAt = $startsAt->addMonth(); |
152 | while ($cycleEndsAt->isBefore(now()->addDay())) { |
153 | $cycleEndsAt = $cycleEndsAt->addMonth(); |
154 | } |
155 | } |
156 | |
157 | if ($result[$plan->hubspot_subscription_frequency] === 'Annual') { |
158 | $cycleEndsAt = $startsAt->addYear(); |
159 | while ($cycleEndsAt->isBefore(now()->addDay())) { |
160 | $cycleEndsAt = $cycleEndsAt->addYear(); |
161 | } |
162 | } |
163 | |
164 | $result[$plan->hubspot_subscription_expiration_date] = match (true) { |
165 | !empty($subscription->trial_ends_at) => new UTCDateTime($subscription->trial_ends_at->timestamp * 1000), |
166 | Str::contains($plan->identifier, "lifetime") => new UTCDateTime(4070880000000), |
167 | !empty($subscription->ends_at) => new UTCDateTime($subscription->ends_at->timestamp * 1000), |
168 | $result[$plan->hubspot_subscription_frequency] === 'Monthly' || $result[$plan->hubspot_subscription_frequency] === 'Annual' => new UTCDateTime($cycleEndsAt->timestamp * 1000), |
169 | default => null, |
170 | }; |
171 | } |
172 | |
173 | $result['appsumo_flymsg_lifetime_access'] = Str::contains($plan->identifier, "appsumo-growth-lifetime") ? 'true' : 'false'; |
174 | |
175 | if (config("app.env") === "production") { |
176 | if ($plan->hubspot_subscription_monthly_recurring_revenue && $plan->hubspot_subscription_annual_recurring_revenue) { |
177 | $trialEndsAt = $subscription->trial_ends_at; |
178 | $invoices = $user->invoices()->toArray(); |
179 | $invoices = array_reverse($invoices); |
180 | |
181 | // Two diffrent plans handle |
182 | foreach ($invoices as $i => $invoice) { |
183 | $invoiceDataIndex = $invoice['lines']['total_count'] > 0 ? $invoice['lines']['total_count'] - 1 : 0; |
184 | if (empty($invoice['lines']['data'][$invoiceDataIndex]['subscription']) && $invoice['billing_reason'] == 'manual') { |
185 | continue; |
186 | } |
187 | |
188 | $interval = $invoice['lines']['data'][$invoiceDataIndex]['plan']['interval']; |
189 | |
190 | $price = $invoice['lines']['data'][$invoiceDataIndex]['price']['unit_amount_decimal'] / 100; |
191 | |
192 | if ($interval == "month") { |
193 | $interval = 'Monthly'; |
194 | $annual_recurring_revenue = $price * 12; |
195 | $monthly_recurring_revenue = $price; |
196 | } else { |
197 | $interval = 'Annual'; |
198 | $annual_recurring_revenue = $price; |
199 | $monthly_recurring_revenue = $price / 12; |
200 | } |
201 | |
202 | if (isset($plan->identifier) && $plan->identifier == 'sales-pro-monthly' && isset($trialEndsAt)) { |
203 | $annual_recurring_revenue = 0; |
204 | $monthly_recurring_revenue = 0; |
205 | } |
206 | } |
207 | |
208 | if (!empty($monthly_recurring_revenue)) { |
209 | $result[$plan->hubspot_subscription_monthly_recurring_revenue] = $monthly_recurring_revenue; |
210 | } |
211 | |
212 | if (!empty($annual_recurring_revenue)) { |
213 | $result[$plan->hubspot_subscription_annual_recurring_revenue] = $annual_recurring_revenue; |
214 | } |
215 | |
216 | if ($plan->identifier == 'appsumo-growth-lifetime') { |
217 | $result['flymsg_growth_subscription_monthly_recurring_revenue'] = 4; |
218 | $result['growth_subscription_annual_recurring_revenue'] = 48; |
219 | } |
220 | } |
221 | } |
222 | |
223 | if ($plan->hubspot_subscription_plan_type) { |
224 | $planType = 'SMB'; |
225 | if (!Str::contains($plan->identifier, "smb")) { |
226 | $planType = 'Enterprise'; |
227 | } |
228 | $result[$plan->hubspot_subscription_plan_type] = $planType; |
229 | } |
230 | |
231 | if ($plan->hubspot_user_type) { |
232 | $userRoles = $user->roles(); |
233 | |
234 | $result[$plan->hubspot_user_type] = implode(";", array_map(function ($role) { |
235 | $newRole = match ($role) { |
236 | 'Reporting Admin' => 'Reporting POC', |
237 | 'Group Admin' => 'Group Admin Manager', |
238 | default => $role, |
239 | }; |
240 | |
241 | if (empty($newRole)) { |
242 | return 'User'; |
243 | } |
244 | |
245 | return $newRole; |
246 | }, $userRoles)); |
247 | |
248 | if (empty($result[$plan->hubspot_user_type])) { |
249 | $result[$plan->hubspot_user_type] = 'User'; |
250 | } |
251 | } |
252 | |
253 | if ($plan->hubspot_cancel_subscription_date) { |
254 | $result[$plan->hubspot_cancel_subscription_date] = ''; |
255 | } |
256 | |
257 | if ($plan->hubspot_subscription_churn_date) { |
258 | $result[$plan->hubspot_subscription_churn_date] = ''; |
259 | } |
260 | |
261 | if ($subscription->trial_ends_at) { |
262 | $result['trail_period_start_date'] = new UTCDateTime($subscription->created_at->timestamp * 1000); |
263 | $result['trail_period_end_date'] = new UTCDateTime($subscription->trial_ends_at->timestamp * 1000); |
264 | } |
265 | |
266 | if ($plan->identifier !== 'freemium') { |
267 | $freemium = $this->endFreemiumSubscription($subscription->user_id, $subscription->created_at); |
268 | $result = array_merge($result, $freemium); |
269 | } |
270 | } |
271 | return $result; |
272 | } |
273 | |
274 | public function endSubscription(array $fields, Subscription $subscription, bool $deleteUser = false, $cancellation_date = null): array |
275 | { |
276 | $map = [ |
277 | // 'stripe_plan' => ['rename' => 'subscription_type', 'transform' => fn($value) => $value ? Plans::where('stripe_id', $value)->first()?->hubspot_name : null], |
278 | ]; |
279 | $result = $this->mapObject($fields, $map); |
280 | |
281 | if (!empty($fields['stripe_plan'])) { |
282 | $now = Carbon::now(); |
283 | |
284 | if (!empty($cancellation_date)) { |
285 | $now = $cancellation_date; |
286 | } |
287 | |
288 | $plan = Plans::where('stripe_id', $fields['stripe_plan'])->first(); |
289 | |
290 | if (!$plan) { |
291 | return $result; |
292 | } |
293 | |
294 | if ($plan->hubspot_subscription_status) { |
295 | $result[$plan->hubspot_subscription_status] = $subscription->trial_ends_at ? 'Expired' : 'Canceled'; |
296 | } |
297 | |
298 | if ($plan->hubspot_subscription_status_updated_on) { |
299 | $result[$plan->hubspot_subscription_status_updated_on] = new UTCDateTime($now->timestamp * 1000); |
300 | } |
301 | |
302 | if ($plan->hubspot_subscription_start_date) { |
303 | $result[$plan->hubspot_subscription_start_date] = new UTCDateTime($subscription->created_at->timestamp * 1000); |
304 | } |
305 | |
306 | if ($plan->hubspot_subscription_frequency) { |
307 | if (Str::contains($plan->identifier, "pro")) { |
308 | $frequency = match (true) { |
309 | Str::contains($plan->identifier, "monthly") => 'Monthly', |
310 | Str::contains($plan->identifier, "appsumo-growth-lifetime") => 'Lifetime - AppSumo', |
311 | Str::contains($plan->identifier, "dealfuel-growth-lifetime") => 'Lifetime - DealFuel', |
312 | default => "Annual", |
313 | }; |
314 | } else { |
315 | $frequency = match (true) { |
316 | Str::contains($plan->identifier, "yearly") => 'Annual', |
317 | Str::contains($plan->identifier, "appsumo-growth-lifetime") => 'Lifetime - AppSumo', |
318 | Str::contains($plan->identifier, "dealfuel-growth-lifetime") => 'Lifetime - DealFuel', |
319 | default => "Monthly", |
320 | }; |
321 | } |
322 | |
323 | $result[$plan->hubspot_subscription_frequency] = $frequency; |
324 | } |
325 | |
326 | if ($plan->hubspot_subscription_expiration_date) { |
327 | $result[$plan->hubspot_subscription_expiration_date] = match (true) { |
328 | !empty($subscription->trial_ends_at) => new UTCDateTime($subscription->trial_ends_at->timestamp * 1000), |
329 | Str::contains($plan->identifier, "lifetime") => new UTCDateTime(4070880000000), |
330 | !empty($subscription->ends_at) => new UTCDateTime($subscription->ends_at->timestamp * 1000), |
331 | default => null, |
332 | }; |
333 | } |
334 | |
335 | if ($plan->hubspot_cancel_subscription_date) { |
336 | $result[$plan->hubspot_cancel_subscription_date] = new UTCDateTime($now->timestamp * 1000); |
337 | } |
338 | |
339 | if ($plan->hubspot_subscription_churn_date) { |
340 | $result[$plan->hubspot_subscription_churn_date] = new UTCDateTime($now->timestamp * 1000); |
341 | } |
342 | |
343 | if ($subscription->trial_ends_at) { |
344 | $result['trail_period_canceled_date'] = new UTCDateTime($now->timestamp * 1000); |
345 | $result['trail_period_start_date'] = new UTCDateTime($subscription->created_at->timestamp * 1000); |
346 | $result['trail_period_end_date'] = new UTCDateTime($subscription->trial_ends_at->timestamp * 1000); |
347 | } |
348 | |
349 | // if there is no other premium subscription |
350 | if (!$this->checkPremiumSubscriptionsActive($subscription->user_id, $plan->hubspot_subscription_status) && !$deleteUser) { |
351 | $freemium = $this->initFreemiumSubscription($subscription->user_id, $cancellation_date); |
352 | $result = array_merge($result, $freemium); |
353 | |
354 | // check another premium active subscription |
355 | $sub = Subscription::where('user_id', $subscription->user_id)->where('trial_ends_at', null)->where('stripe_status', 'active')->first(); |
356 | if (!empty($sub)) { |
357 | $keepSub = $this->startSubscription($sub->toArray(), $sub); |
358 | $result = array_merge($result, $keepSub); |
359 | } |
360 | } |
361 | } |
362 | |
363 | return $result; |
364 | } |
365 | |
366 | public function checkPremiumSubscriptionsActive(string $userId, string $planId): bool |
367 | { |
368 | $plans = Plans::where('identifier', '!=', 'freemium')->where('hubspot_subscription_status', '!=', $planId)->get(); |
369 | |
370 | $userInfo = UserInfo::firstWhere('user_id', $userId); |
371 | |
372 | if (!$userInfo) { |
373 | return false; |
374 | } |
375 | |
376 | foreach ($plans as $plan) { |
377 | if ($userInfo->{$plan->hubspot_subscription_status} === 'Active') { |
378 | return true; |
379 | } |
380 | } |
381 | |
382 | return false; |
383 | } |
384 | |
385 | public function getCountryName($default_payment): string |
386 | { |
387 | $names = json_decode(file_get_contents(base_path('names.json')), true); |
388 | $countryName = ''; |
389 | if (isset($default_payment->billing_details->address->country)) { |
390 | $countryName = $names[$default_payment->billing_details->address->country]; |
391 | } |
392 | |
393 | return $countryName; |
394 | } |
395 | |
396 | public function parseFields(array $fields, Subscription $subscription): array |
397 | { |
398 | $data = [ |
399 | 'user_id' => $subscription->user_id, |
400 | 'subscription_id' => $subscription->id, |
401 | ]; |
402 | |
403 | if (!empty($fields['stripe_status']) && ($fields['stripe_status'] === 'active' || $fields['stripe_status'] === 'trialing')) { |
404 | $data = array_merge($data, $this->startSubscription($fields, $subscription)); |
405 | $user = User::find($subscription->user_id); |
406 | $default_payment = $user->defaultPaymentMethod(); |
407 | |
408 | $data['subscription_owner'] = $default_payment->billing_details->name ?? ''; |
409 | $data['billing_address_line_1'] = $default_payment->billing_details->address->line1 ?? ''; |
410 | $data['billing_address_line_2'] = $default_payment->billing_details->address->line2 ?? ''; |
411 | $data['billing_city'] = $default_payment->billing_details->address->city ?? ''; |
412 | $data['billing_state'] = $default_payment->billing_details->address->state ?? ''; |
413 | $data['billing_zip'] = $default_payment->billing_details->address->postal_code ?? ''; |
414 | $data['billing_country'] = $this->getCountryName($default_payment); |
415 | $data['number_of_completed_payments'] = $user->invoices()->count(); |
416 | $data['number_of_expected_payments'] = $user->invoicesIncludingPending()->count(); |
417 | $data['payment_method'] = 'Credit Card'; |
418 | |
419 | if ($user->invoices()->count() > 0) { |
420 | $invoices = $user->invoices()->toArray(); |
421 | $data['last_total_invoice_amount'] = $invoices[0]['total'] / 100; |
422 | $data['flymsg_total_sales'] = collect($invoices)->sum('amount_paid') / 100; |
423 | |
424 | if ($invoices && !empty($invoices[0]) && !empty($invoices[0]['discount']) && !empty($invoices[0]['discount']['coupon'])) { |
425 | $coupon = $invoices[0]['discount']['coupon']; |
426 | if ($coupon) { |
427 | $data['coupon_name'] = $coupon['name']; |
428 | $data['coupon_code'] = $coupon['id']; |
429 | $data['coupon_type'] = $coupon['percent_off'] ? 'Percentage' : 'Fixed Amout'; |
430 | $data['duration'] = $coupon['duration']; |
431 | $data['coupon_redemption_limits'] = $coupon['max_redemptions']; |
432 | $data['coupon_value__discount_'] = $coupon['percent_off'] != null ? $coupon['percent_off'] : $coupon['amount_off']; |
433 | } |
434 | } |
435 | } |
436 | } else { |
437 | $data = array_merge($data, $this->endSubscription($fields, $subscription)); |
438 | } |
439 | |
440 | return $data; |
441 | } |
442 | } |