PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.5.0
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.5.0
1.6.5 1.6.4 1.6.3 1.6.2 1.6.1 1.6.0 1.5.4 1.5.5 1.5.3 1.5.2 1.5.1 1.5.0 1.4.2 1.4.1 1.4.0 1.3.28 1.3.27 1.3.26 1.3.25 1.3.23 1.3.22 1.3.21 1.3.20 1.3.19 trunk All 48 releases
fluent-cart / app / Services / Payments / SubscriptionHelper.php

SubscriptionHelper.php in FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler 1.5.0, at app/Services/Payments/SubscriptionHelper.php

203 lines 6.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentCart\App\Services\Payments;
4
5 use FluentCart\App\App;
6 use FluentCart\App\Helpers\Helper;
7 use FluentCart\App\Helpers\Status;
8 use FluentCart\App\Models\Order;
9 use FluentCart\App\Models\Subscription;
10 use FluentCart\App\Services\DateTime\DateTime;
11 use FluentCart\Framework\Support\Arr;
12
13 class SubscriptionHelper
14 {
15 /*
16 * @param $subscriptionModel
17 * @return string|null
18 *
19 * */
20 public static function getNextBillingDate(Subscription $subscriptionModel)
21 {
22 // only null case
23 if ($subscriptionModel->status === Status::SUBSCRIPTION_COMPLETED || ($subscriptionModel->bill_times > 0 && $subscriptionModel->bill_count >= $subscriptionModel->bill_times)) {
24 return null;
25 }
26
27 // assuming on expired we update the canceled_at, removes this comment when verified
28 if ($subscriptionModel->status === Status::SUBSCRIPTION_CANCELED || $subscriptionModel->status === Status::SUBSCRIPTION_EXPIRED) {
29 return $subscriptionModel->canceled_at;
30 }
31
32 // Trial handling
33 if ($subscriptionModel->bill_count == 0 && !empty($subscriptionModel->trial_days)) {
34 if (!empty($subscriptionModel->trial_ends_at)) {
35 return $subscriptionModel->trial_ends_at;
36 }
37 return gmdate('Y-m-d H:i:s', strtotime($subscriptionModel->created_at . " +{$subscriptionModel->trial_days} days"));
38 }
39
40 if (!empty($subscriptionModel->next_billing_date) && strtotime($subscriptionModel->next_billing_date) > time()) {
41 return $subscriptionModel->next_billing_date;
42 }
43
44
45 if ($subscriptionModel->bill_count == 0) {
46 $baseDate = $subscriptionModel->created_at;
47
48 } elseif (!empty($subscriptionModel->next_billing_date) && strtotime($subscriptionModel->next_billing_date) < time()) {
49 $baseDate = $subscriptionModel->next_billing_date;
50 } else {
51 $baseDate = DateTime::gmtNow()->format('Y-m-d H:i:s');
52 }
53
54 switch (strtolower($subscriptionModel->billing_interval)) {
55 case 'daily':
56 $next = strtotime($baseDate . " +1 day");
57 break;
58 case 'weekly':
59 $next = strtotime($baseDate . " +1 week");
60 break;
61 case 'monthly':
62 $next = strtotime($baseDate . " +1 month");
63 break;
64 case 'quarterly':
65 $next = strtotime($baseDate . " +3 months");
66 break;
67 case 'half_yearly':
68 $next = strtotime($baseDate . " +6 months");
69 break;
70 case 'yearly':
71 $next = strtotime($baseDate . " +1 year");
72 break;
73 default:
74 $next = strtotime($baseDate . " +1 month"); // fallback
75 }
76
77 return gmdate('Y-m-d H:i:s', $next);
78 }
79
80 /*
81 * @param $trialDays
82 * @param $billTimes
83 * @param $interval
84 *
85 * */
86 public static function getSubscriptionCancelAtTimeStamp($trialDays, $billTimes, $interval)
87 {
88 if (!$billTimes && !$trialDays) {
89 return null;
90 }
91
92 // Use the passed arguments instead of accessing non-existent $this->subscription
93 if ($interval == 'daily') {
94 $interval = 'day';
95 }
96
97 $interValMaps = [
98 'day' => 'days',
99 'weekly' => 'weeks',
100 'monthly' => 'months',
101 'yearly' => 'years'
102 ];
103
104 if (isset($interValMaps[$interval]) && $billTimes > 0) {
105 $interval = $interValMaps[$interval];
106 }
107
108 $timestamp = strtotime('+ ' . $billTimes . ' ' . $interval);
109
110 // Add trial days if provided
111 if ($trialDays > 0) {
112 $timestamp = $timestamp + $trialDays * 24 * 60 * 60; // Add trial days in seconds
113 }
114
115 return $timestamp;
116 }
117
118
119 // can be used to catch 1 day trial loop-hole
120 public static function checkTrailDaysLoopHole($subscription, $trialDays)
121 {
122 $billCount = Arr::get($subscription, 'bill_count');
123 $billingInterval = Arr::get($subscription, 'billing_interval');
124 $billingIntervalInDays = 0;
125 switch ($billingInterval) {
126 case 'monthly':
127 $billingIntervalInDays = 30;
128 break;
129 case 'quarterly':
130 $billingIntervalInDays = 90;
131 break;
132 case 'half_yearly':
133 $billingIntervalInDays = 182;
134 break;
135 case 'yearly':
136 $billingIntervalInDays = 365;
137 break;
138 case 'weekly':
139 $billingIntervalInDays = 7;
140 break;
141 case 'daily':
142 $billingIntervalInDays = 1;
143 break;
144 }
145
146 // get the days from now to the created at date - original trial days,
147 $daysSinceCreated = ceil(ceil((time() - strtotime($subscription->created_at)) / 86400)) - intval($subscription->trial_days);
148 $expectedBillCount = floor($daysSinceCreated / $billingIntervalInDays);
149
150 if ($expectedBillCount > $billCount) {
151 $trialDays = 0;
152 }
153
154 return $trialDays;
155 }
156
157 /**
158 * Safely convert a date string or Unix timestamp to a GMT datetime string.
159 * Returns null when the value is falsy, zero, or a negative timestamp
160 * (guards against strtotime() returning false or a year-0 negative value).
161 *
162 * @param string|int|null $value
163 * @return string|null
164 */
165 public static function safeTimestampToDatetime($value): ?string
166 {
167 if (!$value) {
168 return null;
169 }
170 $ts = is_numeric($value) ? (int) $value : strtotime($value);
171 if (!$ts || $ts <= 0) {
172 return null;
173 }
174 return gmdate('Y-m-d H:i:s', $ts);
175 }
176
177 public static function getSubscriptionsGracePeriodDays()
178 {
179 $defaults = [
180 'daily' => 1,
181 'weekly' => 3,
182 'monthly' => 7,
183 'quarterly' => 15,
184 'half_yearly' => 15,
185 'yearly' => 15,
186 ];
187
188 $gracePeriods = apply_filters('fluent_cart/subscription/grace_period_days', $defaults);
189
190 if (!is_array($gracePeriods)) {
191 $gracePeriods = [];
192 }
193
194 foreach ($defaults as $interval => $defaultDays) {
195 $days = $gracePeriods[$interval] ?? $defaultDays;
196 $gracePeriods[$interval] = is_numeric($days) ? max(0, (int) $days) : $defaultDays;
197 }
198
199 return array_intersect_key($gracePeriods, $defaults);
200 }
201
202 }
203