PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.5.1
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.5.1
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 / Helpers / AdminOrderProcessor.php

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

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