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

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