PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.3.23
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.3.23
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 / PlanUpgradeService.php

PlanUpgradeService.php in FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler 1.3.23, at app/Services/PlanUpgradeService.php

262 lines 9.3 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;
4
5 use FluentCart\App\Helpers\Helper;
6 use FluentCart\App\Helpers\Status;
7 use FluentCart\App\Models\Meta;
8 use FluentCart\App\Models\Order;
9 use FluentCart\App\Models\OrderItem;
10 use FluentCart\App\Models\ProductVariation;
11 use FluentCart\App\Models\Subscription;
12 use FluentCart\Framework\Support\Arr;
13
14 class PlanUpgradeService
15 {
16
17 static string $metaType = 'variant_upgrade';
18 static string $metaKey = 'variant_upgrade_path';
19
20 public static function getUpgradeSettings($productId, $variantId = null)
21 {
22 if ($variantId) {
23 return Meta::query()
24 ->where('object_id', $variantId)
25 ->where('object_type', static::$metaType)
26 ->where('meta_key', static::$metaKey)
27 ->get();
28 }
29
30 return Meta::query()
31 ->upgradeablePath($productId)
32 ->get();
33
34
35 }
36
37 public static function getAvailableUpgradePaths($variantId, $metaValue)
38 {
39 $metaValue = json_decode($metaValue, true);
40 return Arr::get($metaValue, 'paths' . '.' . $variantId, []);
41 }
42
43
44 public static function saveUpgradeSetting($settings)
45 {
46 $data = [
47 'object_id' => Arr::get($settings, 'from_variant'),
48 'object_type' => static::$metaType,
49 //phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
50 'meta_key' => static::$metaKey,
51 //phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value
52 'meta_value' => [
53 'to_variants' => Arr::get($settings, 'to_variants'),
54 'is_prorate' => Arr::get($settings, 'is_prorate'),
55 'discount_amount' => Arr::get($settings, 'discount_amount')
56 ],
57 ];
58
59 return Meta::query()->create($data);
60 }
61
62 public static function updateUpgradeSetting($id, $settings)
63 {
64 $data = [
65 'to_variants' => Arr::get($settings, 'to_variants'),
66 'is_prorate' => Arr::get($settings, 'is_prorate'),
67 'discount_amount' => Arr::get($settings, 'discount_amount')
68 ];
69
70 return Meta::query()->where('id', $id)->update([
71 //phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value
72 'meta_value' => json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)
73 ]);
74 }
75
76 public static function validateUpgradePaths($paths)
77 {
78 foreach ($paths as $variationId => $path) {
79 if (empty($path)) {
80 continue;
81 }
82
83 $targetVariationIds = array_column($path, 'target_variation_id');
84 $uniqueIds = array_unique($targetVariationIds);
85
86 if (count($targetVariationIds) !== count($uniqueIds)) {
87 $duplicates = array_diff_assoc($targetVariationIds, $uniqueIds);
88 wp_send_json([
89 'message' => sprintf(
90 /* translators: 1: Variation ID */
91 __('Duplicate upgrade path found for variation #%1$s. Each target variation must be unique.', 'fluent-cart'),
92 $variationId
93 ),
94 'duplicates' => $duplicates
95 ], 423);
96 }
97 }
98
99 }
100
101 public static function getUpgardePathsFromVariation($variationId, $orderHash)
102 {
103 if (!$variationId || !$orderHash) {
104 return [];
105 }
106
107 $upgrades = Meta::query()->where('meta_key', 'variant_upgrade_path')
108 ->where('object_type', 'variant_upgrade')
109 ->where('object_id', $variationId)
110 ->get();
111
112 $order = Order::query()->where('uuid', $orderHash)->first();
113
114 if ($order->type === 'subscription') {
115 $lastRenewal = Order::query()
116 ->where('parent_id', $order->id)
117 ->where('type', 'renewal')
118 ->whereIn('payment_status', ['paid', 'partially_refunded'])
119 ->orderBy('id', 'DESC')
120 ->first();
121
122 if ($lastRenewal) {
123 $order = $lastRenewal;
124 }
125 }
126
127 $originalItem = $order->order_items->where('object_id', $variationId)->first();
128 if (!$originalItem) {
129 return [];
130 }
131
132 //TODO: Allow upgrades for bundle items
133 if ($originalItem->payment_type == 'bundle') {
134 return [];
135 }
136
137 $upgradePaths = [];
138 foreach ($upgrades as $upgrade) {
139 $toVariants = Arr::get($upgrade->meta_value, 'to_variants', []);
140 $isProrate = Arr::get($upgrade->meta_value, 'is_prorate', false);
141 $discountAmount = Helper::toCent(Arr::get($upgrade->meta_value, 'discount_amount'));
142
143 foreach ($toVariants as $toVariant) {
144 // calculate prorate credit, discount amount
145 $variant = ProductVariation::query()->find($toVariant);
146 if (!$variant) {
147 continue;
148 }
149
150 $prorateCredit = 0;
151 if ($isProrate) {
152 $prorateCredit = self::calculateUpgradeToDiscount($order, $originalItem);
153 }
154
155 $signupFee = Arr::get($variant->other_info, 'signup_fee', 0);
156 $cost = floatval($variant->item_price + Arr::get($variant->other_info, 'signup_fee', 0) - $prorateCredit - $discountAmount);
157
158 if ($cost < 0) {
159 $cost = 0;
160 }
161
162 $paymentType = Arr::get($variant, 'payment_type');
163 $paymentSummary = Helper::toDecimal($cost) . ' one-time';
164 if ($paymentType === 'subscription') {
165 $paymentSummary = '<strong>' . Helper::toDecimal($cost) . '</strong> first '
166 . Helper::humanIntervalMaps(Arr::get($variant->other_info, 'repeat_interval'))
167 . ', then ' . Helper::toDecimal($variant->item_price)
168 . '/' . Helper::humanIntervalMaps(Arr::get($variant->other_info, 'repeat_interval')) . ' thereafter.';
169 }
170
171 $upgradePaths[] = [
172 'title' => $variant->variation_title,
173 'to_variant' => $toVariant,
174 'discount_amount' => (float)$discountAmount,
175 'prorate_credit' => (float)$prorateCredit,
176 'signup_fee' => (float)$signupFee,
177 'signup_fee_name' => Arr::get($variant->other_info, 'signup_fee_name', ''),
178 'payment_summary' => $paymentSummary,
179 'price' => (float)$variant->item_price,
180 'original_price' => floatval($variant->item_price + $signupFee),
181 'cost' => $cost,
182 'currency' => $order->currency,
183 'upgrade_url' => add_query_arg([
184 'fluent-cart' => 'upgrade_plan',
185 'order_hash' => $orderHash,
186 'path_id' => $upgrade->id,
187 'target_id' => $toVariant,
188 ], home_url())
189 ];
190 }
191 }
192
193
194 return $upgradePaths;
195 }
196
197
198
199 public static function calculateUpgradeToDiscount(Order $order, OrderItem $originalItem)
200 {
201 // check for signup fee item
202 $totalPaid = $originalItem->line_total - $originalItem->refund_total;
203
204 $additionalItemIds = Arr::get($originalItem->line_meta, 'additional_item_ids', []);
205 if (!empty($additionalItemIds)) {
206 $additionalItems = OrderItem::query()
207 ->whereIn('id', $additionalItemIds)
208 ->get();
209
210 foreach ($additionalItems as $item) {
211 if (Arr::get($item->line_meta, 'parent_item_id', '') == $originalItem->id) {
212 $totalPaid += $item->line_total - $item->refund_total;
213 }
214 }
215 }
216
217 if ($originalItem->payment_type === 'onetime') {
218 return $totalPaid < 0 ? 0 : $totalPaid;
219 }
220
221
222 $parentOrderId = $order->id;
223
224 if ($order->type === 'renewal') {
225 $parentOrderId = $order->parent_id;
226 }
227
228 $subscription = Subscription::query()
229 ->where('parent_order_id', $parentOrderId)
230 ->first();
231
232 if (!$subscription || !$subscription->hasAccessValidity() || ($subscription->status == Status::SUBSCRIPTION_TRIALING && Arr::get($subscription->config, 'is_trial_days_simulated', 'no') != 'yes')) {
233 return 0;
234 }
235
236 $daysRemaining = ceil((strtotime($subscription->next_billing_date) - time()) / 86400); // convert seconds to days
237
238 $maps = [
239 'monthly' => (int) gmdate('t'), // Get exact days in current month (handles leap year) to avoid false remaining days calculation
240 'yearly' => 365,
241 'weekly' => 7,
242 'daily' => 1,
243 ];
244
245 if (!isset($maps[$subscription->billing_interval])) {
246 return 0; // Invalid repeat interval
247 }
248
249 $divider = $maps[$subscription->billing_interval];
250
251 if ($daysRemaining > $divider) { // making sure we are not giving discount more than the actual amount
252 $daysRemaining = $divider;
253 }
254
255 $discountAmount = intval($totalPaid / $divider * $daysRemaining);
256
257 return $discountAmount < 0 ? 0 : $discountAmount;
258 }
259
260
261 }
262