PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.4.0
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.4.0
1.6.6 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 All 49 releases
fluent-cart / app / Helpers / AdminOrderProcessor.php

AdminOrderProcessor.php in FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler 1.4.0, at app/Helpers/AdminOrderProcessor.php

690 lines 28.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\Helpers;
4
5 use FluentCart\Api\StoreSettings;
6 use FluentCart\App\Models\Cart;
7 use FluentCart\App\Models\Coupon;
8 use FluentCart\App\Models\Order;
9 use FluentCart\App\Models\OrderItem;
10 use FluentCart\App\Models\Product;
11 use FluentCart\App\Models\ProductVariation;
12 use FluentCart\App\Models\Subscription;
13 use FluentCart\App\Services\Payments\PaymentHelper;
14 use FluentCart\Framework\Support\Arr;
15 use FluentCart\App\Helpers\Helper;
16
17 class AdminOrderProcessor
18 {
19 private $args = [];
20 private $checkoutItems = [];
21
22 // Order Related Data
23 private $formattedIOrderItems = [];
24 private $orderData = [];
25 private $subscriptionData = [];
26
27 // Models
28
29 private $orderModel;
30
31 private $transactionModel;
32
33 private $subscriptionModel;
34
35 // Store Settings
36 private $storeSettings;
37
38
39 private $couponDiscountTotal = 0;
40
41 private $manualDiscountTotal = 0;
42
43 public function __construct($checkoutItems = [], $args = [])
44 {
45 $this->storeSettings = new StoreSettings();
46 $this->checkoutItems = $checkoutItems;
47 $this->checkoutItems = Helper::loadBundleChild($checkoutItems, ['*']);
48 $this->args = $args;
49
50 $this->prepareData();
51 }
52
53 private function prepareData()
54 {
55 $this->prepareOrderItems();
56 $this->prepareOrderData();
57 $this->prepareSubscriptionData();
58 }
59
60 private function prepareOrderItems()
61 {
62 $formattedItems = [];
63
64 foreach ($this->checkoutItems as $checkoutItem) {
65 $unitPrice = (int)Arr::get($checkoutItem, 'unit_price', 0);
66 $quantity = (int)Arr::get($checkoutItem, 'quantity', 1);
67
68 //TODO: right now discount total from admin only comes from coupon applied discount, is same as coupon_discount
69 $this->couponDiscountTotal += (int)Arr::get($checkoutItem, 'discount_total', 0);
70 $this->manualDiscountTotal += (int)Arr::get($checkoutItem, 'manual_discount', 0);
71
72 $discountTotal = (int)Arr::get($checkoutItem, 'manual_discount', 0) + (int)Arr::get($checkoutItem, 'discount_total', 0);
73
74 $shippingCharge = (int)Arr::get($checkoutItem, 'shipping_charge', 0);
75 $tax = 0;
76 $subtotal = $unitPrice * $quantity;
77 $args = Arr::get($checkoutItem, 'other_info', []);
78 $paymentType = Arr::get($args, 'payment_type', 'default');
79
80 $postTitle = Arr::get($checkoutItem, 'product_title', '');
81 $variationTitle = Arr::get($checkoutItem, 'variation_title', '');
82
83 if (!$postTitle) {
84 $product = Product::query()->find(Arr::get($checkoutItem, 'post_id', 0));
85 if ($product) {
86 $postTitle = $product->post_title;
87 }
88 }
89
90 $variation = null;
91 if (!$variationTitle) {
92 $variation = ProductVariation::query()->find(Arr::get($checkoutItem, 'object_id', 0));
93 if ($variation) {
94 $variationTitle = $variation->variation_title;
95 }
96 }
97
98 if (!$variation) {
99 $variation = ProductVariation::query()->find(Arr::get($checkoutItem, 'object_id', 0));
100 }
101
102 $fulfillmentType = Arr::get($checkoutItem, 'fulfillment_type', '');
103 if (!$fulfillmentType && $variation) {
104 $fulfillmentType = $variation->fulfillment_type;
105 }
106 if (!$fulfillmentType) {
107 $fulfillmentType = 'digital';
108 }
109
110 $item = [
111 'payment_type' => $paymentType,
112 'post_id' => Arr::get($checkoutItem, 'post_id'),
113 'object_id' => Arr::get($checkoutItem, 'object_id'),
114 'post_title' => $postTitle,
115 'title' => $variationTitle,
116 'fulfillment_type' => $fulfillmentType,
117 'quantity' => $quantity,
118 'cost' => (int)Arr::get($checkoutItem, 'cost', 0),
119 'unit_price' => $unitPrice,
120 'subtotal' => $subtotal,
121 'tax_amount' => $tax,
122 'shipping_charge' => $shippingCharge,
123 'discount_total' => $discountTotal,
124 'coupon_discount' => (int)Arr::get($checkoutItem, 'discount_total', 0),
125 'other_info' => $args,
126 'line_meta' => []
127 ];
128
129 $childItem = null;
130 if ($paymentType === 'subscription' && Arr::get($checkoutItem, 'other_info.signup_fee', 0)) {
131 // We have a signup fee for subscription
132 $singupFeeAmount = (int)Arr::get($checkoutItem, 'other_info.signup_fee', 0);
133
134 $childDiscontTotal = 0;
135 $childCouponDiscount = 0;
136 $signupFeeSubtotal = $singupFeeAmount * $quantity;
137 $couponDiscount = $item['coupon_discount'];
138
139 if ($discountTotal) {
140 // Distribute discount with signup fee and item
141 $childDiscontTotal = (int)($discountTotal / ($subtotal + $signupFeeSubtotal) * $signupFeeSubtotal);
142 $discountTotal -= $childDiscontTotal;
143 $childCouponDiscount = (int) round($couponDiscount * $signupFeeSubtotal / ($subtotal + $signupFeeSubtotal));
144 $couponDiscount -= $childCouponDiscount;
145 }
146
147 $childItem = [
148 'payment_type' => 'signup_fee',
149 'post_id' => $item['post_id'],
150 'object_id' => $item['object_id'],
151 'post_title' => $item['post_title'],
152 'title' => Arr::get($checkoutItem, 'other_info.signup_fee_name', __('Signup Fee', 'fluent-cart')),
153 'fulfillment_type' => $item['fulfillment_type'],
154 'quantity' => $quantity,
155 'cost' => 0,
156 'unit_price' => $singupFeeAmount,
157 'subtotal' => $signupFeeSubtotal,
158 'tax_amount' => 0,
159 'shipping_charge' => 0,
160 'discount_total' => $childDiscontTotal,
161 'coupon_discount' => $childCouponDiscount,
162 'line_meta' => []
163 ];
164
165 $item['discount_total'] = $discountTotal;
166 $item['coupon_discount'] = $couponDiscount;
167 $item['additional_items'] = [$childItem];
168
169 Arr::set($item, 'other_info.signup_fee', $singupFeeAmount);
170 Arr::set($item, 'other_info.signup_discount', $childDiscontTotal);
171 }
172
173 if (Arr::get($checkoutItem, 'other_info.is_bundle_product', 'no') == 'yes') {
174 $bundleItems = Arr::get($checkoutItem, 'child_variants', []);
175
176 foreach ($bundleItems as $bundleItem) {
177 $bundleChildItem = [
178 'payment_type' => 'bundle',
179 'post_id' => Arr::get($bundleItem, 'post_id', 0),
180 'object_id' => Arr::get($bundleItem, 'id', 0),
181 'post_title' => Arr::get($bundleItem, 'post_title', ''),
182 'title' => Arr::get($bundleItem, 'variation_title', ''),
183 'fulfillment_type' => Arr::get($bundleItem, 'fulfillment_type', 'digital'),
184 'quantity' => $quantity,
185 'cost' => 0,
186 'unit_price' => 0,
187 'subtotal' => 0,
188 'tax_amount' => 0,
189 'shipping_charge' => 0,
190 'discount_total' => 0,
191 'other_info' => [
192 'bundle_parent_product_id' => Arr::get($checkoutItem, 'post_id', 0),
193 'bundle_parent_variation_id' => Arr::get($checkoutItem, 'object_id', 0)
194 ],
195 ];
196
197 //TODO: if bundleItem price is included on for the bundle, then we need to set the price to the bundleItem price
198 // if (Arr::get($bundleItem, 'other_info.is_price_included', 'no') == 'yes') {
199 // $bundleChildItem['unit_price'] = Arr::get($bundleItem, 'unit_price', 0);
200 // $bundleChildItem['subtotal'] = Arr::get($bundleItem, 'subtotal', 0);
201 // $bundleChildItem['tax_amount'] = Arr::get($bundleItem, 'tax_amount', 0);
202 // $bundleChildItem['shipping_charge'] = Arr::get($bundleItem, 'shipping_charge', 0);
203 // $bundleChildItem['discount_total'] = Arr::get($bundleItem, 'discount_total', 0);
204 // }
205
206 $item['bundle_items'][] = $bundleChildItem;
207
208 }
209
210 }
211
212
213 $formattedItems[] = $item;
214 if ($childItem) {
215 $formattedItems[] = $childItem;
216 }
217 }
218
219 $this->formattedIOrderItems = $formattedItems;
220 }
221
222 private function prepareOrderData()
223 {
224 $hasPhysical = array_filter($this->formattedIOrderItems, function ($item) {
225 return $item['fulfillment_type'] === 'physical';
226 });
227
228 $hasSubscription = array_filter($this->formattedIOrderItems, function ($item) {
229 return $item['payment_type'] === 'subscription';
230 });
231
232
233 $itemsSubtotal = array_reduce($this->formattedIOrderItems, function ($carry, $item) {
234 if (Arr::get($item, 'other_info.trial_days', 0) > 0) {
235 return $carry;
236 }
237 return $carry + $item['subtotal'];
238 }, 0);
239
240 $orderData = [
241 'status' => Status::ORDER_ON_HOLD,
242 'fulfillment_type' => $hasPhysical ? Status::FULFILLMENT_TYPE_PHYSICAL : Status::FULFILLMENT_TYPE_DIGITAL,
243 'type' => $hasSubscription ? Status::ORDER_TYPE_SUBSCRIPTION : Status::ORDER_TYPE_PAYMENT, // revisit this on manual renewal
244 'mode' => $this->storeSettings->get('order_mode'),
245 'shipping_status' => $hasPhysical ? 'unshipped' : '',
246 'customer_id' => '',
247 'payment_method' => Arr::get($this->args, 'payment_method', ''),
248 'payment_status' => Status::PAYMENT_PENDING,
249 'payment_method_title' => '',
250 'currency' => $this->storeSettings->get('currency'),
251 'subtotal' => $itemsSubtotal,
252 'discount_tax' => 0,
253 'manual_discount_total' => $this->manualDiscountTotal,
254 'coupon_discount_total' => $this->couponDiscountTotal,
255 'shipping_tax' => 0,
256 'shipping_total' => Arr::get($this->args, 'shipping_total', 0),
257 'tax_total' => 0,
258 // 'total_amount' => $this->orderTotals['total_amount'],
259 'total_paid' => 0,
260 'total_refund' => 0,
261 'rate' => 1,
262 'note' => Arr::get($this->args, 'note', ''),
263 'ip_address' => Arr::get($this->args, 'ip_address', ''),
264 'config' => [
265 'user_tz' => Arr::get($this->args, 'user_tz', ''),
266 ],
267 ];
268
269 $totalAmount = $orderData['subtotal'] - $orderData['coupon_discount_total'] - $orderData['manual_discount_total'] + $orderData['shipping_total'] + $orderData['tax_total'];
270 $orderData['total_amount'] = $totalAmount > 0 ? $totalAmount : 0;
271 $this->orderData = $orderData;
272 }
273
274
275 public function createDraftOrder($prevOrder = null)
276 {
277 $customerId = Arr::get($this->args, 'customer_id', '');
278 if (!$customerId) {
279 return new \WP_Error('customer_id_missing', __('Customer ID is required to create a draft order.', 'fluent-cart'));
280 }
281
282 $orderData = $this->orderData;
283 $orderData['customer_id'] = $customerId;
284 $this->orderModel = \FluentCart\App\Models\Order::query()->create($orderData);
285
286 if (!$this->orderModel) {
287 return new \WP_Error('order_creation_failed', __('Failed to create order.', 'fluent-cart'));
288 }
289
290 // Let's create the order items
291 $normalOrderItems = array_filter($this->formattedIOrderItems, function ($item) {
292 return $item['payment_type'] != 'signup_fee';
293 });
294
295
296 foreach ($normalOrderItems as $orderItem) {
297 $orderItem['order_id'] = $this->orderModel->id;
298 $orderItem['line_total'] = $orderItem['subtotal'] - $orderItem['discount_total'];
299 $additionalItems = [];
300 $bundleItems = [];
301 if ($orderItem['payment_type'] == 'subscription') {
302 // this is a subscription type. We may have additional_items
303 $additionalItems = Arr::get($orderItem, 'additional_items', []);
304 unset($orderItem['additional_items']);
305 }
306
307 if (Arr::get($orderItem, 'other_info.is_bundle_product', 'no') == 'yes') {
308 $bundleItems = Arr::get($orderItem, 'bundle_items', []);
309 unset($orderItem['bundle_items']);
310 }
311
312 if (!empty($orderItem['coupon_discount'])) {
313 $lineMeta = Arr::get($orderItem, 'line_meta', []);
314 $lineMeta['coupon_discount'] = (int) $orderItem['coupon_discount'];
315 $orderItem['line_meta'] = $lineMeta;
316 }
317
318 $createdItem = OrderItem::query()->create($orderItem);
319
320 if ($additionalItems) {
321 $additionalItemIds = [];
322 foreach ($additionalItems as $additionalItem) {
323 $additionalItem['order_id'] = $this->orderModel->id;
324 $additionalItem['line_total'] = $additionalItem['subtotal'] - $additionalItem['discount_total'];
325 $mata = Arr::get($additionalItem, 'line_meta', []);
326 $mata['parent_item_id'] = $createdItem->id;
327 if (!empty($additionalItem['coupon_discount'])) {
328 $mata['coupon_discount'] = (int) $additionalItem['coupon_discount'];
329 }
330 $additionalItem['line_meta'] = $mata;
331 OrderItem::query()->create($additionalItem);
332 }
333
334 $createdItem->fill([
335 'line_meta' => array_merge(
336 $createdItem->line_meta,
337 [
338 'additional_item_ids' => $additionalItemIds
339 ]
340 )
341 ])->save();
342 }
343
344 if ($bundleItems) {
345 $bundleItemIds = [];
346 foreach ($bundleItems as $bundleItem) {
347 $bundleItem['order_id'] = $this->orderModel->id;
348 $bundleItem['line_total'] = Arr::get($bundleItem, 'subtotal', 0) - Arr::get($bundleItem, 'discount_total', 0);
349 $bundleItem['payment_type'] = 'bundle';
350 $meta = Arr::get($bundleItem, 'line_meta', []);
351 $meta['bundle_parent_item_id'] = $createdItem->id;
352 if (!empty($bundleItem['coupon_discount'])) {
353 $meta['coupon_discount'] = (int) $bundleItem['coupon_discount'];
354 }
355 $bundleItem['line_meta'] = $meta;
356 $bundleItem = OrderItem::query()->create($bundleItem);
357 $bundleItemIds[] = $bundleItem->id;
358 }
359
360 $createdItem->fill([
361 'line_meta' => array_merge(
362 $createdItem->line_meta,
363 [
364 'bundle_item_ids' => $bundleItemIds
365 ]
366 )
367 ])->save();
368 }
369 }
370
371 // Let's create the subscription if exists
372 if ($this->subscriptionData) {
373 $subscriptionData = $this->subscriptionData;
374 $subscriptionData['customer_id'] = $customerId;
375 $subscriptionData['parent_order_id'] = $this->orderModel->id;
376 $this->subscriptionModel = Subscription::query()->create($subscriptionData);
377 }
378
379 // Let's create the transaction
380 $transactionData = [
381 'order_id' => $this->orderModel->id,
382 'order_type' => $this->orderModel->type,
383 'transaction_type' => Status::TRANSACTION_TYPE_CHARGE,
384 'subscription_id' => $this->subscriptionModel ? $this->subscriptionModel->id : NULL,
385 'payment_method' => $this->orderModel->payment_method,
386 'payment_mode' => $this->orderModel->mode,
387 'payment_method_type' => '',
388 'status' => Status::PAYMENT_PENDING,
389 'currency' => $this->orderModel->currency,
390 'total' => $this->orderModel->total_amount,
391 'rate' => 1,
392 'meta' => [],
393 ];
394
395 $this->transactionModel = \FluentCart\App\Models\OrderTransaction::query()->create($transactionData);
396
397 // insert the applied coupons
398 $this->insertAppliedCoupons(Arr::get($this->args, 'applied_coupons', []));
399
400 $cartHash = Arr::get($this->args, 'cart_hash', '');
401 if ($cartHash) {
402 $cart = Cart::query()->where('cart_hash', $cartHash)->first();
403 if ($cart) {
404 $cart->order_id = $this->orderModel->id;
405 $cart->customer_id = $this->orderModel->customer_id;
406 $cart->stage = 'intended';
407 $cart->save();
408 $actions = Arr::get($cart->checkout_data, '__after_draft_created_actions__', []);
409 if ($actions) {
410 foreach ($actions as $actionName) {
411 $actionName = (string)$actionName;
412 if (has_action($actionName)) {
413 do_action($actionName, [
414 'order' => $this->orderModel,
415 'cart' => $cart,
416 ]);
417 }
418 }
419 }
420 }
421 }
422
423 // We are almost done!
424 return $this->orderModel;
425 }
426
427
428 private function insertAppliedCoupons($appliedCoupons, $removeOldCoupons = false): void
429 {
430 if ($removeOldCoupons) {
431 $this->orderModel->appliedCoupons()->delete();
432 }
433
434 $couponCodes = array_keys($appliedCoupons);
435
436 if (!empty($couponCodes)) {
437 $coupons = Coupon::query()->whereIn('code', $couponCodes)->get()
438 ->keyBy('code')
439 ->toArray();
440
441 foreach ($coupons as $code => &$coupon) {
442 $coupon['coupon_id'] = $appliedCoupons[$code]['id'];
443 $coupon['amount'] = $appliedCoupons[$code]['discount'];
444 }
445 $this->orderModel->appliedCoupons()->createMany($coupons);
446
447 Coupon::query()
448 ->whereIn('code', $couponCodes)
449 ->increment('use_count', 1);
450 }
451 }
452
453 public function getTransaction()
454 {
455 return $this->transactionModel;
456 }
457
458 public function getOrder()
459 {
460 return $this->orderModel;
461 }
462
463 public function getSubscription()
464 {
465 return $this->subscriptionModel;
466 }
467 private function prepareSubscriptionData()
468 {
469 $subscriptionItems = array_filter($this->formattedIOrderItems, function ($item) {
470 return $item['payment_type'] === 'subscription';
471 });
472
473 $signupFeeItems = array_filter($this->formattedIOrderItems, function ($item) {
474 return $item['payment_type'] === 'signup_fee';
475 });
476
477 if (!$subscriptionItems) {
478 return;
479 }
480
481 if (count($subscriptionItems) > 1) {
482 return;
483 }
484
485 $item = reset($subscriptionItems);
486 $signupFeeItem = reset($signupFeeItems) ?? [];
487
488 $totalSignup = Arr::get($item, 'other_info.signup_fee', 0) - Arr::get($item, 'other_info.signup_discount', 0);
489 $firstPrice = Arr::get($item, 'subtotal') + $totalSignup - Arr::get($item, 'discount_total', 0);
490 $recurringPrice = Arr::get($item, 'subtotal', 0) + Arr::get($item, 'tax_amount', 0);
491
492 if ($firstPrice < $recurringPrice) {
493 Arr::set($item, 'other_info.trial_days', PaymentHelper::getIntervalDays(Arr::get($item, 'other_info.repeat_interval')));
494 Arr::set($item, 'other_info.signup_fee', $firstPrice);
495 Arr::set($item, 'other_info.manage_setup_fee', 'yes');
496 Arr::set($item, 'other_info.times', Arr::get($item, 'other_info.times', 0) > 1 ? Arr::get($item, 'other_info.times', 0) - 1 : 0);
497 } else if ($firstPrice > $recurringPrice) {
498 Arr::set($item, 'other_info.signup_fee', $firstPrice - $recurringPrice);
499 Arr::set($item, 'other_info.manage_setup_fee', 'yes');
500 Arr::set($item, 'other_info.trial_days', 0);
501 }
502
503 $subscriptionPricing = $this->convertToSubscriptionFormat([
504 'initial_trial_days' => Arr::get($item, 'other_info.trial_days', 0),
505 'repeat_interval' => Arr::get($item, 'other_info.repeat_interval', 'monthly'),
506 'times' => Arr::get($item, 'other_info.times', 0),
507 'recurring_amount' => $item['subtotal'],
508 'signup_fee' => $signupFeeItem ? $signupFeeItem['subtotal'] : 0,
509 'total_discount' => $item['discount_total'] + Arr::get($signupFeeItem, 'discount_total', 0)
510 ]);
511
512 // removable upon discussion
513 $subscriptionItem = [
514 'product_id' => $item['post_id'],
515 'current_payment_method' => Arr::get($this->orderData, 'payment_method'),
516 'object_id' => $item['object_id'],
517 'recurring_tax_total' => 0,
518 'recurring_total' => $subscriptionPricing['recurring_amount'], //use price not line_total to ignore discount
519 'item_name' => $item['post_title'] . ' - ' . $item['title'],
520 'bill_count' => 0,
521 'quantity' => 1,
522 'variation_id' => Arr::get($item, 'object_id', 0),
523 'status' => Status::SUBSCRIPTION_PENDING,
524 'config' => [
525 'currency' => $this->orderData['currency'],
526 'is_trial_days_simulated' => Arr::get($subscriptionPricing, 'is_trial_days_simulated', 'no'),
527 ]
528 ];
529
530 $this->subscriptionData = wp_parse_args($subscriptionPricing, $subscriptionItem);
531 }
532
533 /**
534 * @param $inputData
535 * @return array
536 */
537 private function convertToSubscriptionFormat($inputData)
538 {
539
540 /**
541 * Normal Subscription $100/month
542 * {
543 * 'trial_days' => 0,
544 * 'repeat_interval' => 'month',
545 * 'times' => 0, // 0 means unlimited
546 * 'recurring_amount' => 100,
547 * 'signup_fee' => 0
548 * }
549 *
550 * 30 Days Trial $100/month
551 * {
552 * 'trial_days' => 30,
553 * 'repeat_interval' => 'month',
554 * 'times' => 0, // 0 means unlimited
555 * 'recurring_amount' => 100,
556 * }
557 *
558 * $100/month - 30 days Trial with $40 signup fee
559 * {
560 * 'trial_days' => 30,
561 * 'repeat_interval' => 'month',
562 * 'times' => 0, // 0 means unlimited
563 * 'recurring_amount' => 100,
564 * 'signup_fee' => 40
565 * }
566 *
567 * $100 / month with $40 signup fee
568 * {
569 * 'trial_days' => 0,
570 * 'repeat_interval' => 'month',
571 * 'times' => 0, // 0 means unlimited
572 * 'recurring_amount' => 100,
573 * 'signup_fee' => 40
574 * }
575 *
576 * $100 per month but 30% discount on first month
577 * {
578 * 'trial_days' => 30,
579 * 'repeat_interval' => 'month',
580 * 'times' => 0, // 0 means unlimited
581 * 'recurring_amount' => 100,
582 * 'signup_fee' => 70
583 * }
584 *
585 * $100 per month but 50% extra on first month
586 * {
587 * 'trial_days' => 0,
588 * 'repeat_interval' => 'month',
589 * 'times' => 0, // 0 means unlimited
590 * 'recurring_amount' => 100,
591 * 'signup_fee' => 50
592 * }
593 *
594 * $100 per month and 1 month trial and service_fee = $50
595 * {
596 * 'trial_days' => 30,
597 * 'repeat_interval' => 'month',
598 * 'times' => 0, // 0 means unlimited
599 * 'recurring_amount' => 100,
600 * 'signup_fee' => 50
601 * }
602 *
603 *
604 * $100 per month, signup fee $200 - First Month 50% discount
605 *
606 * $first Month = 100 + 200 = 300 - 50% discount = 150
607 * {
608 * 'trial_days' => 30,
609 * 'repeat_interval' => 'month',
610 * 'times' => 0, // 0 means unlimited
611 * 'recurring_amount' => 100,
612 * 'signup_fee' => 150
613 * }
614 *
615 * // prefered when signup_fee > recurring_amount
616 * {
617 * 'trial_days' => 0,
618 * 'repeat_interval' => 'month',
619 * 'times' => 0, // 0 means unlimited
620 * 'recurring_amount' => 100,
621 * 'signup_fee' => 50
622 * }
623 */
624
625
626 // Extract and validate input data
627 $trialDays = (int)($inputData['initial_trial_days'] ?? 0);
628 $repeatInterval = strtolower(trim($inputData['repeat_interval'] ?? 'yearly'));
629 $times = (int)($inputData['times'] ?? 0);
630 $recurringAmount = (int)($inputData['recurring_amount'] ?? 0);
631 $signupFee = (int)($inputData['signup_fee'] ?? 0);
632 $totalDiscount = (int)($inputData['total_discount'] ?? 0);
633
634 // Convert repeat_interval to standard format
635 $intervalMap = Helper::getAvailableSubscriptionIntervalMaps();
636 $standardInterval = Helper::translateIntervalToStandardFormat($repeatInterval);
637
638
639 // Initialize result array
640 $result = [
641 'trial_days' => $trialDays,
642 'repeat_interval' => $standardInterval,
643 'times' => $times,
644 'recurring_amount' => $recurringAmount,
645 ];
646
647
648 // Calculate signup fee logic
649 if ($totalDiscount > 0) {
650
651 $firstCycleCost = $recurringAmount + $signupFee - $totalDiscount;
652
653 if ($firstCycleCost < $recurringAmount) {
654 $adjustedTrialDays = Helper::calculateAdjustedTrialDaysForInterval($trialDays, $repeatInterval);
655
656 $result['trial_days'] = $adjustedTrialDays;
657 $result['is_trial_days_simulated'] = 'yes';
658 $result['signup_fee'] = $firstCycleCost;
659 $result['manage_setup_fee'] = 'yes';
660 $result['times'] = $times > 0 ? $times - 1 : 0;
661 } else if ($firstCycleCost > $recurringAmount) {
662 $result['trial_days'] = 0;
663 $result['signup_fee'] = $firstCycleCost - $recurringAmount;
664 $result['manage_setup_fee'] = 'yes';
665 } else if ($firstCycleCost == $recurringAmount) {
666 $result['trial_days'] = 0;
667 $result['signup_fee'] = 0;
668 $result['manage_setup_fee'] = 'no';
669 }
670
671 } else {
672 $result['signup_fee'] = $signupFee;
673 }
674
675 // inverse the repeat interval to match the expected format
676
677 $result['repeat_interval'] = array_flip($intervalMap)[$result['repeat_interval']] ?? 'yearly';
678
679
680 return [
681 'billing_interval' => $result['repeat_interval'],
682 'bill_times' => $result['times'],
683 'trial_days' => $result['trial_days'],
684 'is_trial_days_simulated' => Arr::get($result, 'is_trial_days_simulated', 'no'),
685 'recurring_amount' => $result['recurring_amount'],
686 'signup_fee' => $result['signup_fee'] ?? 0,
687 ];
688 }
689 }
690