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 / Helpers / CheckoutProcessor.php

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

1,224 lines 52.2 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\Checkout\CheckoutApi;
6 use FluentCart\Api\StoreSettings;
7 use FluentCart\App\Models\Cart;
8 use FluentCart\App\Models\Coupon;
9 use FluentCart\App\Models\Order;
10 use FluentCart\App\Models\OrderItem;
11 use FluentCart\App\Models\Product;
12 use FluentCart\App\Models\ProductVariation;
13 use FluentCart\App\Models\Subscription;
14 use FluentCart\App\Modules\Tax\TaxCalculator;
15 use FluentCart\Framework\Support\Arr;
16 use FluentCart\App\Helpers\Helper;
17
18 class CheckoutProcessor
19 {
20
21 // Raw Data
22 private $cartItems = [];
23 private $args = [];
24
25 // Order Related Data
26 private $formattedIOrderItems = [];
27 private $orderData = [];
28 private $subscriptionData = [];
29
30 // Models
31
32 private $orderModel;
33
34 private $transactionModel;
35
36 private $subscriptionModel;
37
38 // Fee tracking
39 private $feeTotal = 0;
40
41 // Store Settings
42 private $storeSettings;
43
44 private $couponDiscountTotal = 0;
45
46 private $manualDiscountTotal = 0;
47
48 private $prorateCreditTotal = 0;
49
50 private $upgradeDiscountTotal = 0;
51
52 public function __construct($cartItems = [], $args = [])
53 {
54 $this->storeSettings = new StoreSettings();
55 $this->cartItems = $cartItems;
56 $this->args = $args;
57
58 $this->prepareData();
59 }
60
61 private function prepareData()
62 {
63 $this->prepareOrderItems();
64 $this->prepareOrderData();
65 $this->prepareSubscriptionData();
66 }
67
68 public function createDraftOrder($prevOrder = null)
69 {
70 if ($prevOrder) {
71 return $this->getAdjustedOrder($prevOrder);
72 }
73
74 $customerId = Arr::get($this->args, 'customer_id', '');
75 if (!$customerId) {
76 return new \WP_Error('customer_id_missing', __('Customer ID is required to create a draft order.', 'fluent-cart'));
77 }
78
79 $orderData = $this->orderData;
80 $orderData['customer_id'] = $customerId;
81 if (empty($orderData['currency'])) {
82 $orderData['currency'] = $this->storeSettings->getCurrency();
83 }
84
85 if (empty($orderData['mode'])) {
86 $orderData['mode'] = $this->storeSettings->get('order_mode', 'test');
87 }
88
89 if (empty($orderData['fee_total'])) {
90 unset($orderData['fee_total']);
91 }
92
93 $this->orderModel = \FluentCart\App\Models\Order::query()->create($orderData);
94
95 if (!$this->orderModel) {
96 return new \WP_Error('order_creation_failed', __('Failed to create order.', 'fluent-cart'));
97 }
98
99 // save order meta
100 if (Arr::get($this->args, 'tax_id', 0)) {
101 $this->orderModel->updateMeta('tax_id', Arr::get($this->args, 'tax_id', 0));
102 }
103
104 // Store tax meta for mixed carts (tax_behavior=3) - used by payment gateways and renewals
105 $this->persistTaxMeta();
106
107 // Let's create the order items
108 $normalOrderItems = array_filter($this->formattedIOrderItems, function ($item) {
109 return $item['payment_type'] != 'signup_fee';
110 });
111
112 foreach ($normalOrderItems as $orderItem) {
113 $orderItem['order_id'] = $this->orderModel->id;
114 $orderItem['line_total'] = $orderItem['subtotal'] - $orderItem['discount_total'];
115 $additionalItems = [];
116 $bundleItems = [];
117 if ($orderItem['payment_type'] == 'subscription') {
118 // this is a subscription type. We may have additional_items
119 $additionalItems = Arr::get($orderItem, 'additional_items', []);
120 unset($orderItem['additional_items']);
121 }
122
123 if (Arr::get($orderItem, 'other_info.is_bundle_product', 'no') == 'yes') {
124 $bundleItems = Arr::get($orderItem, 'bundle_items', []);
125 unset($orderItem['bundle_items']);
126 }
127
128 if (!empty($orderItem['coupon_discount'])) {
129 $lineMeta = Arr::get($orderItem, 'line_meta', []);
130 $lineMeta['coupon_discount'] = (int) $orderItem['coupon_discount'];
131 $orderItem['line_meta'] = $lineMeta;
132 }
133
134 $createdItem = OrderItem::query()->create($orderItem);
135
136 if ($additionalItems) {
137 $additionalItemIds = [];
138 foreach ($additionalItems as $additionalItem) {
139 $additionalItem['order_id'] = $this->orderModel->id;
140 $additionalItem['line_total'] = Arr::get($additionalItem, 'subtotal', 0) - Arr::get($additionalItem, 'discount_total', 0);
141 $mata = Arr::get($additionalItem, 'line_meta', []);
142 $mata['parent_item_id'] = $createdItem->id;
143 if (!empty($additionalItem['coupon_discount'])) {
144 $mata['coupon_discount'] = (int) $additionalItem['coupon_discount'];
145 }
146 $additionalItem['line_meta'] = $mata;
147 $childItem = OrderItem::query()->create($additionalItem);
148 $additionalItemIds[] = $childItem->id;
149 }
150
151 $createdItem->fill([
152 'line_meta' => array_merge(
153 $createdItem->line_meta,
154 [
155 'additional_item_ids' => $additionalItemIds
156 ]
157 )
158 ])->save();
159 }
160
161 if ($bundleItems) {
162 $bundleItemIds = [];
163 foreach ($bundleItems as $bundleItem) {
164 $bundleItem['order_id'] = $this->orderModel->id;
165 $bundleItem['line_total'] = Arr::get($bundleItem, 'subtotal', 0) - Arr::get($bundleItem, 'discount_total', 0);
166 $bundleItem['payment_type'] = 'bundle';
167 $meta = Arr::get($bundleItem, 'line_meta', []);
168 $meta['bundle_parent_item_id'] = $createdItem->id;
169 if (!empty($bundleItem['coupon_discount'])) {
170 $meta['coupon_discount'] = (int) $bundleItem['coupon_discount'];
171 }
172 $bundleItem['line_meta'] = $meta;
173 $bundleItem = OrderItem::query()->create($bundleItem);
174 $bundleItemIds[] = $bundleItem->id;
175 }
176
177 $createdItem->fill([
178 'line_meta' => array_merge(
179 $createdItem->line_meta,
180 [
181 'bundle_item_ids' => $bundleItemIds
182 ]
183 )
184 ])->save();
185 }
186 }
187
188
189 // Let's create the subscription if exists
190 /*
191 * TODO : on renewal order we shouldn't create another subscription,
192 * basically on manual renew we just create a new sub on gateway and update the existing one on our database
193 * which automates the renewal cycle for the existing subscription
194 * ....created new sub remains on pending state, will create inconsistency
195 * */
196
197 if ($this->subscriptionData) {
198 $subscriptionData = $this->subscriptionData;
199 $subscriptionData['customer_id'] = $customerId;
200 $subscriptionData['parent_order_id'] = $this->orderModel->id;
201
202 $this->subscriptionModel = Subscription::query()->create($subscriptionData);
203 }
204
205 // Let's create the transaction
206 $transactionData = [
207 'order_id' => $this->orderModel->id,
208 'order_type' => $this->orderModel->type,
209 'transaction_type' => Status::TRANSACTION_TYPE_CHARGE,
210 'subscription_id' => $this->subscriptionModel ? $this->subscriptionModel->id : NULL,
211 'payment_method' => $this->orderModel->payment_method,
212 'payment_mode' => $this->orderModel->mode,
213 'payment_method_type' => '',
214 'status' => Status::PAYMENT_PENDING,
215 'currency' => $this->orderModel->currency,
216 'total' => $this->orderModel->total_amount,
217 'rate' => 1,
218 'meta' => [],
219 ];
220
221 $this->transactionModel = \FluentCart\App\Models\OrderTransaction::query()->create($transactionData);
222
223 // insert the applied coupons
224 $this->insertAppliedCoupons(
225 Arr::get($this->args, 'applied_coupons', []),
226 false,
227 $this->orderModel
228 );
229
230 $cartHash = Arr::get($this->args, 'cart_hash', '');
231 if ($cartHash) {
232 $cart = Cart::query()->where('cart_hash', $cartHash)->first();
233 if ($cart) {
234 $cart->order_id = $this->orderModel->id;
235 $cart->customer_id = $this->orderModel->customer_id;
236 $cart->stage = 'intended';
237 $customer = $this->orderModel->customer;
238
239 if ($customer) {
240 $cart->first_name = $customer->first_name;
241 $cart->last_name = $customer->last_name;
242 $cart->email = $customer->email;
243 $cart->user_id = $customer->user_id;
244 }
245
246 $cart->save();
247 $actions = Arr::get($cart->checkout_data, '__after_draft_created_actions__', []);
248 if ($actions) {
249 foreach ($actions as $actionName) {
250 $actionName = (string)$actionName;
251 if (has_action($actionName)) {
252 do_action($actionName, [
253 'order' => $this->orderModel,
254 'cart' => $cart,
255 ]);
256 }
257 }
258
259 // We are just renewing it!
260 $this->orderModel = \FluentCart\App\Models\Order::query()
261 ->where('id', $this->orderModel->id)
262 ->first();
263 }
264 }
265 }
266
267 // We are almost done!
268 return $this->orderModel;
269 }
270
271 private function getAdjustedOrder(Order $prevOrder)
272 {
273 $isLocked = Arr::get($this->args, 'is_locked', false);
274
275 $orderData = $this->orderData;
276 $customerId = Arr::get($this->args, 'customer_id', '');
277 if ($customerId) {
278 $orderData['customer_id'] = $customerId;
279 }
280
281 if ($isLocked) {
282 $orderData = array_filter(Arr::only($orderData, ['note', 'payment_method', 'ip_address', 'customer_id', 'shipping_total', 'total_amount', 'tax_total', 'fee_total', 'shipping_tax']), function ($value) {
283 return $value !== null && $value !== '';
284 });
285
286 $prevConfig = $prevOrder->config;
287 $prevConfig['user_tz'] = Arr::get($this->args, 'user_tz', '');
288 $orderData['config'] = $prevConfig;
289 $prevOrder->fill($orderData);
290 $prevOrder->save();
291 } else {
292 $prevOrder->fill($orderData);
293 $prevOrder->save();
294 }
295
296 $this->orderModel = $prevOrder;
297
298 if (!$this->orderModel) {
299 return new \WP_Error('order_creation_failed', __('Failed to create order.', 'fluent-cart'));
300 }
301
302 //for previous order if tax is enabled then we need to update the tax total
303 $taxSettings = get_option('fluent_cart_tax_configuration_settings', []);
304 $taxEnabled = Arr::get($taxSettings, 'enable_tax', 'no');
305
306 if ($isLocked && $taxEnabled !== 'yes') {
307 // Locked orders skip full item sync, but fee items must stay in sync with fee_total
308 $this->syncFeeItems();
309 }
310
311 if (!$isLocked || $taxEnabled === 'yes') {
312 // Let's create the order items
313 $normalOrderItems = array_filter($this->formattedIOrderItems, function ($item) {
314 return $item['payment_type'] != 'signup_fee';
315 });
316
317 $createdItemIds = [];
318 $taxTotal = 0;
319 foreach ($normalOrderItems as $orderItem) {
320 $orderItem['order_id'] = $this->orderModel->id;
321 $orderItem['quantity'] = max(1, (int)$orderItem['quantity']);
322 $orderItem['line_total'] = $orderItem['subtotal'] - $orderItem['discount_total'];
323 $taxTotal += $orderItem['tax_amount'];
324 $additionalItems = [];
325 $bundleItems = [];
326 if ($orderItem['payment_type'] == 'subscription') {
327 // this is a subscription type. We may have additional_items
328 $additionalItems = Arr::get($orderItem, 'additional_items', []);
329 unset($orderItem['additional_items']);
330 }
331
332 if (Arr::get($orderItem, 'other_info.is_bundle_product', 'no') == 'yes') {
333 $bundleItems = Arr::get($orderItem, 'bundle_items', []);
334 unset($orderItem['bundle_items']);
335 }
336
337 $existingItem = OrderItem::query()->where('order_id', $this->orderModel->id)
338 ->whereNotIn('id', $createdItemIds)
339 ->first();
340
341 if ($existingItem) {
342 $existingItem->fill($orderItem);
343 $existingItem->save();
344 $createdItem = $existingItem;
345 } else {
346 $createdItem = OrderItem::query()->create($orderItem);
347 }
348
349 $createdItemIds[] = $createdItem->id;
350
351 if ($additionalItems) {
352 $additionalItemIds = [];
353 foreach ($additionalItems as $additionalItem) {
354 $additionalItem['order_id'] = $this->orderModel->id;
355 $additionalItem['quantity'] = max(1, (int)$additionalItem['quantity']);
356 $additionalItem['line_total'] = $additionalItem['subtotal'] - $additionalItem['discount_total'];
357 $mata = Arr::get($additionalItem, 'line_meta', []);
358 $mata['parent_item_id'] = $createdItem->id;
359 $additionalItem['line_meta'] = $mata;
360 $childItem = OrderItem::query()->create($additionalItem);
361 $createdItemIds[] = $childItem->id;
362 $additionalItemIds[] = $childItem->id;
363
364 }
365
366 $createdItem->fill([
367 'line_meta' => array_merge(
368 $createdItem->line_meta,
369 [
370 'additional_item_ids' => $additionalItemIds
371 ]
372 )
373 ])->save();
374 }
375
376 if ($bundleItems) {
377 $bundleItemIds = [];
378 foreach ($bundleItems as $bundleItem) {
379 $bundleItem['order_id'] = $this->orderModel->id;
380 $bundleItem['quantity'] = Arr::get($orderItem, 'quantity', 1);
381 $bundleItem['line_total'] = Arr::get($bundleItem, 'subtotal', 0) - Arr::get($bundleItem, 'discount_total', 0);
382 $bundleItem['payment_type'] = 'bundle';
383 $meta['bundle_parent_item_id'] = $createdItem->id;
384 $bundleItem['line_meta'] = $meta;
385 $childItem = OrderItem::query()->create($bundleItem);
386 $createdItemIds[] = $childItem->id;
387 $bundleItemIds[] = $childItem->id;
388 }
389
390 $createdItem->fill([
391 'line_meta' => array_merge(
392 $createdItem->line_meta,
393 [
394 'bundle_item_ids' => $bundleItemIds
395 ]
396 )
397 ])->save();
398 }
399 }
400 if ($taxTotal && !$this->orderModel->tax_total && !$isLocked) {
401 $this->orderModel->tax_total = $taxTotal;
402 $this->orderModel->total_amount += $taxTotal;
403 $this->orderModel->save();
404 }
405
406 OrderItem::query()->where('order_id', $this->orderModel->id)->whereNotIn('id', $createdItemIds)->delete();
407
408 // Let's create the subscription if exists
409 if ($this->subscriptionData) {
410 if ($this->orderModel->type === Status::ORDER_TYPE_RENEWAL) {
411 // it's a renewal order
412 $this->subscriptionModel = Subscription::query()
413 ->where('parent_order_id', $this->orderModel->parent_id)
414 ->first();
415 } else {
416 $subscriptionData = $this->subscriptionData;
417 $subscriptionData['customer_id'] = $customerId;
418 $subscriptionData['parent_order_id'] = $this->orderModel->id;
419 $existingSubscription = Subscription::query()->where('parent_order_id', $this->orderModel->id)->first();
420 if ($existingSubscription) {
421 $existingSubscription->fill($subscriptionData);
422 $existingSubscription->save();
423 $this->subscriptionModel = $existingSubscription;
424 } else {
425 $this->subscriptionModel = Subscription::query()->create($subscriptionData);
426 }
427 }
428 } else {
429 Subscription::query()->where('parent_order_id', $this->orderModel->id)->delete();
430 }
431 }
432
433 // Reload the order to get fresh item data after updates
434 $this->orderModel = $this->orderModel->fresh();
435
436 $this->persistTaxMeta();
437
438 // Let's create the transaction
439 $transactionData = [
440 'order_id' => $this->orderModel->id,
441 'order_type' => $this->orderModel->type,
442 'transaction_type' => Status::TRANSACTION_TYPE_CHARGE,
443 'subscription_id' => $this->subscriptionModel ? $this->subscriptionModel->id : NULL,
444 'payment_method' => $this->orderModel->payment_method,
445 'payment_mode' => $this->orderModel->mode,
446 'payment_method_type' => '',
447 'status' => Status::PAYMENT_PENDING,
448 'currency' => $this->orderModel->currency,
449 'total' => $this->orderModel->total_amount,
450 'rate' => 1,
451 'meta' => [],
452 ];
453
454 if ($isLocked && $prevOrder->parent_id) {
455 $prevSubscription = Subscription::query()
456 ->where('parent_order_id', $prevOrder->parent_id)
457 ->first();
458
459 if ($prevSubscription) {
460 $transactionData['subscription_id'] = $prevSubscription->id;
461 }
462 }
463
464 $existingTransaction = \FluentCart\App\Models\OrderTransaction::query()
465 ->where('order_id', $this->orderModel->id)
466 ->first();
467
468 if ($existingTransaction) {
469 $existingTransaction->fill($transactionData);
470 $existingTransaction->save();
471 $this->transactionModel = $existingTransaction;
472 } else {
473 $this->transactionModel = \FluentCart\App\Models\OrderTransaction::query()->create($transactionData);
474 }
475
476 // For locked carts (e.g. custom checkout), preserve the original order's applied coupon records.
477 // Re-inserting would delete existing records and incorrectly increment the coupon use_count.
478 if (!$isLocked) {
479 $this->insertAppliedCoupons(Arr::get($this->args, 'applied_coupons', []), true, $prevOrder);
480 }
481
482 $cartHash = Arr::get($this->args, 'cart_hash', '');
483
484 if ($cartHash) {
485 $cart = Cart::query()->where('cart_hash', $cartHash)->first();
486 if ($cart) {
487 $cart->order_id = $this->orderModel->id;
488 $cart->customer_id = $this->orderModel->customer_id;
489 $cart->stage = 'intended';
490 $cart->ip_address = $this->orderModel->ip_address;
491 $cart->save();
492 }
493 }
494
495 // We are almost done!
496 return $this->orderModel;
497 }
498
499 private function persistTaxMeta()
500 {
501 $exclusiveTaxTotal = (int) Arr::get($this->args, 'exclusive_tax_total', 0);
502 $storeTaxBehavior = (int) Arr::get($this->args, 'store_tax_behavior', 0);
503 $feeTax = (int) Arr::get($this->args, 'fee_tax', 0);
504 $feeTaxLines = (array) Arr::get($this->args, 'fee_tax_lines', []);
505
506 $this->orderModel->updateMeta('exclusive_tax_total', $exclusiveTaxTotal);
507 $this->orderModel->updateMeta('store_tax_behavior', $storeTaxBehavior);
508 $this->orderModel->updateMeta('fee_tax', $feeTax);
509
510 if (!empty($feeTaxLines)) {
511 $this->orderModel->updateMeta('fee_tax_lines', $feeTaxLines);
512 } else {
513 $this->orderModel->deleteMeta('fee_tax_lines');
514 }
515 }
516
517 private function insertAppliedCoupons($appliedCoupons, $removeOlds = false, $order = null): void
518 {
519 if ($removeOlds) {
520 $this->orderModel->appliedCoupons()->delete();
521 }
522
523 $couponCodes = Arr::pluck($appliedCoupons, 'code');
524
525 $customerId = $this->orderModel->customer_id;
526 if ($order instanceof Order) {
527 $customerId = $order->customer_id;
528 }
529
530 if (!empty($couponCodes)) {
531 $coupons = Coupon::query()->whereIn('code', $couponCodes)->get();
532
533 /*
534 * Resolve virtual (un-persisted) coupons so an AppliedCoupon row is written for
535 * them too — the row stores coupon_id = null (the column is nullable) with the
536 * code and computed discount, so it shows in the order's Coupons section like any
537 * coupon. See DiscountService::applyCouponCodes() for the same filter.
538 */
539 $coupons = apply_filters('fluent_cart/coupon/resolve_coupons', $coupons, $couponCodes, [
540 'order' => $this->orderModel,
541 ]);
542
543 $coupons = $coupons->keyBy('code')->toArray();
544
545 foreach ($coupons as $code => &$coupon) {
546 $coupon['coupon_id'] = $appliedCoupons[$code]['id'];
547 $coupon['amount'] = $appliedCoupons[$code]['discount'];
548 $coupon['customer_id'] = $customerId;
549 }
550 $this->orderModel->appliedCoupons()->createMany($coupons);
551
552 Coupon::query()
553 ->whereIn('code', $couponCodes)
554 ->increment('use_count', 1);
555 }
556 }
557
558 public function getTransaction()
559 {
560 return $this->transactionModel;
561 }
562
563 public function getOrder()
564 {
565 return $this->orderModel;
566 }
567
568 public function getSubscription()
569 {
570 return $this->subscriptionModel;
571 }
572
573 private function prepareOrderItems()
574 {
575 $formattedItems = [];
576
577 foreach ($this->cartItems as $cartItem) {
578 $unitPrice = (int)Arr::get($cartItem, 'unit_price', 0);
579 $quantity = (int)Arr::get($cartItem, 'quantity', 1);
580
581 $this->couponDiscountTotal += (int)Arr::get($cartItem, 'coupon_discount', 0);
582 $this->manualDiscountTotal += (int)Arr::get($cartItem, 'manual_discount', 0);
583
584 $discountTotal = (int)Arr::get($cartItem, 'manual_discount', 0) + (int)Arr::get($cartItem, 'coupon_discount', 0);
585 $shippingCharge = (int)Arr::get($cartItem, 'shipping_charge', 0);
586
587 $subtotal = (int) Arr::get($cartItem, 'subtotal', $unitPrice * $quantity);
588 $args = Arr::get($cartItem, 'other_info', []);
589 $paymentType = Arr::get($args, 'payment_type', 'default');
590
591 $postTitle = Arr::get($cartItem, 'product_title', '');
592 $variationTitle = Arr::get($cartItem, 'variation_title', '');
593
594 if (!$postTitle) {
595 if (Arr::get($cartItem, 'is_custom', false)) {
596 $postTitle = Arr::get($cartItem, 'post_title', '');
597 } else {
598 $product = Product::query()->find(Arr::get($cartItem, 'post_id', 0));
599 if ($product) {
600 $postTitle = $product->post_title;
601 }
602 }
603 }
604
605 if (!$variationTitle) {
606 if (Arr::get($cartItem, 'is_custom', false)) {
607 $variationTitle = Arr::get($cartItem, 'title', '');
608 } else {
609 $variation = ProductVariation::query()->find(Arr::get($cartItem, 'object_id', 0));
610 if ($variation) {
611 $variationTitle = $variation->variation_title;
612 }
613 }
614 }
615
616 // Snapshot package dimensions into other_info for email/PDF rendering
617 if (Arr::get($cartItem, 'fulfillment_type') === 'physical') {
618 $packageSlug = Arr::get($args, 'package_slug', '');
619 $package = Helper::getPackageBySlug($packageSlug);
620 if ($package) {
621 $args['package_name'] = Arr::get($package, 'name', '');
622 $args['package_type'] = Arr::get($package, 'type', '');
623 $args['package_length'] = Arr::get($package, 'length', '');
624 $args['package_width'] = Arr::get($package, 'width', '');
625 $args['package_height'] = Arr::get($package, 'height', '');
626 $args['package_dimension_unit'] = Arr::get($package, 'dimension_unit', 'cm');
627 $args['package_weight'] = Arr::get($package, 'weight', 0);
628 $args['package_weight_unit'] = Arr::get($package, 'weight_unit', 'kg');
629 }
630 }
631
632 $item = [
633 'payment_type' => $paymentType,
634 'post_id' => Arr::get($cartItem, 'post_id'),
635 'object_id' => Arr::get($cartItem, 'object_id'),
636 'post_title' => $postTitle,
637 'title' => $variationTitle,
638 'fulfillment_type' => Arr::get($cartItem, 'fulfillment_type', 'digital'),
639 'quantity' => $quantity,
640 'cost' => (int)Arr::get($cartItem, 'cost', 0),
641 'unit_price' => $unitPrice,
642 'subtotal' => $subtotal,
643 'tax_amount' => (int)Arr::get($cartItem, 'tax_amount', 0),
644 'shipping_charge' => $shippingCharge,
645 'discount_total' => $discountTotal,
646 'coupon_discount' => (int)Arr::get($cartItem, 'coupon_discount', 0),
647 'other_info' => $args,
648 'line_meta' => Arr::get($cartItem, 'line_meta', []),
649 ];
650
651 if (isset($cartItem['recurring_discounts'])) {
652 $item['recurring_discounts'] = $cartItem['recurring_discounts'];
653 }
654
655 $childItem = null;
656 if ($paymentType === 'subscription' && Arr::get($cartItem, 'other_info.signup_fee', 0)) {
657 // We have a signup fee for subscription
658 $signupFeeAmount = (int)Arr::get($cartItem, 'other_info.signup_fee', 0);
659
660 $signupFeeTax = (int)Arr::get($cartItem, 'other_info.signup_fee_tax', 0);
661
662 // Nest under tax_config — the same shape regular items and the admin
663 // order path use. Readers keep a fallback for the legacy flat shape.
664 $signupFeeTaxConfig = Arr::get($cartItem, 'signup_fee_tax_config', []);
665
666 $childDiscountTotal = 0;
667 $childCouponDiscount = 0;
668 $signupFeeSubtotal = $signupFeeAmount * $quantity;
669 $couponDiscount = $item['coupon_discount'];
670
671 $hasTrialDays = Arr::get($cartItem, 'other_info.trial_days', 0) > 0;
672
673 if ($discountTotal && !$hasTrialDays) {
674 $childDiscountTotal = (float)($discountTotal / ($subtotal + $signupFeeSubtotal) * $signupFeeSubtotal);
675 $discountTotal -= $childDiscountTotal;
676 $childCouponDiscount = (int) round($couponDiscount * $signupFeeSubtotal / ($subtotal + $signupFeeSubtotal));
677 $couponDiscount -= $childCouponDiscount;
678 } elseif ($discountTotal && $hasTrialDays) { // if trial days , then discount should be applied on signup fee only
679 $childDiscountTotal = min($discountTotal, $signupFeeSubtotal);
680 $discountTotal = 0;
681 $childCouponDiscount = min($couponDiscount, $signupFeeSubtotal);
682 $couponDiscount = 0;
683 }
684
685 $childItem = [
686 'payment_type' => 'signup_fee',
687 'post_id' => $item['post_id'],
688 'object_id' => $item['object_id'],
689 'post_title' => $item['post_title'],
690 'title' => Arr::get($cartItem, 'other_info.signup_fee_name', __('Signup Fee', 'fluent-cart')),
691 'fulfillment_type' => $item['fulfillment_type'],
692 'quantity' => $quantity,
693 'cost' => 0,
694 'unit_price' => $signupFeeAmount,
695 'subtotal' => $signupFeeSubtotal,
696 'tax_amount' => $signupFeeTax,
697 'shipping_charge' => 0,
698 'discount_total' => $childDiscountTotal,
699 'coupon_discount' => $childCouponDiscount,
700 'line_meta' => $signupFeeTaxConfig ? ['tax_config' => $signupFeeTaxConfig] : [],
701 ];
702
703 $item['discount_total'] = $discountTotal;
704 $item['coupon_discount'] = $couponDiscount;
705 $item['additional_items'] = [$childItem];
706
707 Arr::set($item, 'other_info.signup_fee', $signupFeeAmount);
708 Arr::set($item, 'other_info.signup_discount', $childDiscountTotal);
709 }
710
711 if (
712 Arr::get($cartItem, 'other_info.is_bundle_product', 'no') == 'yes'
713 || !empty(Arr::get($cartItem, 'other_info.bundle_child_ids', []))
714 ) {
715 $bundleItems = Arr::get($cartItem, 'child_variants', []);
716
717 foreach ($bundleItems as $bundleItem) {
718 $bundleChildItem = [
719 'payment_type' => 'bundle',
720 'post_id' => Arr::get($bundleItem, 'post_id', 0),
721 'object_id' => Arr::get($bundleItem, 'id', 0),
722 'post_title' => Arr::get($bundleItem, 'post_title', ''),
723 'title' => Arr::get($bundleItem, 'variation_title', ''),
724 'fulfillment_type' => Arr::get($bundleItem, 'fulfillment_type', 'digital'),
725 'quantity' => $quantity,
726 'cost' => 0,
727 'unit_price' => 0,
728 'subtotal' => 0,
729 'tax_amount' => 0,
730 'shipping_charge' => 0,
731 'discount_total' => 0,
732 'other_info' => [
733 'bundle_parent_product_id' => Arr::get($cartItem, 'post_id', 0),
734 'bundle_parent_variation_id' => Arr::get($cartItem, 'object_id', 0)
735 ],
736 ];
737
738 //TODO: if bundleItem price is included on for the bundle, then we need to set the price to the bundleItem price
739 // if (Arr::get($bundleItem, 'other_info.is_price_included', 'no') == 'yes') {
740 // $bundleChildItem['unit_price'] = Arr::get($bundleItem, 'unit_price', 0);
741 // $bundleChildItem['subtotal'] = Arr::get($bundleItem, 'subtotal', 0);
742 // $bundleChildItem['tax_amount'] = Arr::get($bundleItem, 'tax_amount', 0);
743 // $bundleChildItem['shipping_charge'] = Arr::get($bundleItem, 'shipping_charge', 0);
744 // $bundleChildItem['discount_total'] = Arr::get($bundleItem, 'discount_total', 0);
745 // }
746
747 $item['bundle_items'][] = $bundleChildItem;
748
749 }
750
751 }
752
753 $formattedItems[] = $item;
754 if ($childItem) {
755 $formattedItems[] = $childItem;
756 }
757 }
758
759 // Create order items for fees
760 $fees = (array)Arr::get($this->args, 'fees', []);
761 $this->feeTotal = 0;
762
763 foreach ($fees as $fee) {
764 $amount = (int)($fee['amount'] ?? 0);
765 if ($amount <= 0) {
766 continue;
767 }
768
769 $this->feeTotal += $amount;
770
771 $formattedItems[] = [
772 'payment_type' => 'fee',
773 'post_id' => 0,
774 'object_id' => 0,
775 'post_title' => '',
776 'title' => $fee['label'] ?? '',
777 'fulfillment_type' => 'digital',
778 'quantity' => 1,
779 'cost' => 0,
780 'unit_price' => $amount,
781 'subtotal' => $amount,
782 'tax_amount' => 0,
783 'shipping_charge' => 0,
784 'discount_total' => 0,
785 'other_info' => [
786 'payment_type' => 'fee',
787 'fee_key' => $fee['key'] ?? '',
788 'source' => $fee['source'] ?? 'custom',
789 'taxable' => !empty($fee['taxable']),
790 'meta' => $fee['meta'] ?? [],
791 ],
792 'line_meta' => [],
793 ];
794 }
795
796 $this->formattedIOrderItems = $formattedItems;
797 }
798
799 private function prepareSubscriptionData()
800 {
801 $subscriptionItems = array_filter($this->formattedIOrderItems, function ($item) {
802 return $item['payment_type'] === 'subscription';
803 });
804
805 $signupFeeItems = array_filter($this->formattedIOrderItems, function ($item) {
806 return $item['payment_type'] === 'signup_fee';
807 });
808
809 if (!$subscriptionItems) {
810 return;
811 }
812
813 if (count($subscriptionItems) > 1) {
814 return;
815 }
816
817 $item = reset($subscriptionItems);
818 $signupFeeItem = reset($signupFeeItems) ?? [];
819 $signupFeeTax = (int)Arr::get($signupFeeItem, 'tax_amount', 0);
820 $taxBehavior = Arr::get($this->args, 'tax_behavior', 0);
821
822 $recurringTotal = (int)$item['subtotal'];
823 $recurringTax = (int)Arr::get($item, 'other_info.recurring_tax', 0);
824
825 $recurringDiscountAmount = (int)Arr::get($item, 'recurring_discounts.amount', 0);
826
827 if ($recurringDiscountAmount && $recurringDiscountAmount > 0) {
828 $recurringTotal -= $recurringDiscountAmount;
829 }
830
831 // Add shipping charges to recurring total for physical subscription products
832 $shippingCharge = (int)Arr::get($this->args, 'shipping_charge', 0);
833 $isPhysicalProduct = Arr::get($item, 'fulfillment_type') === 'physical';
834 if ($isPhysicalProduct && $shippingCharge > 0) {
835 $recurringTotal += $shippingCharge;
836 }
837
838 $itemInclusive = (bool) Arr::get($item, 'line_meta.tax_config.inclusive', false);
839 if ($taxBehavior === 1 || ($taxBehavior === 3 && !$itemInclusive)) {
840 $recurringTotal += $recurringTax;
841 }
842
843 $signupFee = (int)Arr::get($signupFeeItem, 'subtotal', 0);
844
845 // in case of discount applied 'tax_amount' is different than recurring tax ,
846 $firstIterationTax = (int)Arr::get($item, 'tax_amount', 0) + $signupFeeTax;
847
848
849 // Calculate recurring amount including shipping for physical products
850 $recurringAmount = (int)$item['subtotal'];
851 if ($isPhysicalProduct && $shippingCharge > 0) {
852 $recurringAmount += $shippingCharge;
853 }
854
855 $discountTotal = $item['discount_total'] + Arr::get($signupFeeItem, 'discount_total', 0) + $this->prorateCreditTotal + $this->upgradeDiscountTotal;
856 $subscriptionPricing = $this->convertToSubscriptionFormat([
857 'initial_trial_days' => Arr::get($item, 'other_info.trial_days', 0),
858 'repeat_interval' => Arr::get($item, 'other_info.repeat_interval', 'monthly'),
859 'times' => Arr::get($item, 'other_info.times', 0),
860 'recurring_amount' => $recurringAmount,
861 'recurring_tax_total' => $recurringTax,
862 'recurring_total' => $recurringTotal,
863 'tax_behavior' => $taxBehavior,
864 'line_meta' => Arr::get($item, 'line_meta', []),
865 'signup_fee' => $signupFee,
866 'signup_fee_tax' => $signupFeeTax,
867 'first_iteration_tax' => $firstIterationTax,
868 'is_recurring_coupon' => Arr::get($item, 'is_recurring_coupon', 'no'),
869 'total_discount' => $discountTotal
870 ]);
871
872 // removable upon discussion
873 $subscriptionItem = [
874 'product_id' => $item['post_id'],
875 'current_payment_method' => Arr::get($this->orderData, 'payment_method'),
876 'object_id' => $item['object_id'],
877 'recurring_tax_total' => 0,
878 'recurring_total' => $recurringTotal, //use price not line_total to ignore discount
879 'item_name' => $item['post_title'] . ' - ' . $item['title'],
880 'bill_count' => 0,
881 'quantity' => 1,
882 'variation_id' => Arr::get($item, 'object_id', 0),
883 'status' => Status::SUBSCRIPTION_PENDING,
884 'config' => [
885 'is_trial_days_simulated' => Arr::get($subscriptionPricing, 'is_trial_days_simulated', 'no'),
886 'currency' => $this->orderData['currency']
887 ]
888 ];
889
890 // if recurring coupon is applied, we need to subtract the total discount from the recurring total
891 if (Arr::get($item, 'is_recurring_coupon', 'no') === 'yes') {
892 $subscriptionItem['recurring_total'] -= $discountTotal;
893 }
894
895 $this->subscriptionData = wp_parse_args($subscriptionPricing, $subscriptionItem);
896 }
897
898 private function prepareOrderData()
899 {
900 $hasPhysical = array_filter($this->formattedIOrderItems, function ($item) {
901 return $item['fulfillment_type'] === 'physical';
902 });
903
904 $hasSubscription = array_filter($this->formattedIOrderItems, function ($item) {
905 return $item['payment_type'] === 'subscription';
906 });
907
908 $itemsSubtotal = array_reduce($this->formattedIOrderItems, function ($carry, $item) {
909 if (Arr::get($item, 'other_info.trial_days', 0) > 0) {
910 return $carry;
911 }
912 // Fee items are tracked separately via fee_total
913 if (Arr::get($item, 'payment_type') === 'fee') {
914 return $carry;
915 }
916 return $carry + $item['subtotal'];
917 }, 0);
918
919 $taxBehavior = (int) Arr::get($this->args, 'tax_behavior', 0);
920 $storeTaxBehavior = (int) Arr::get($this->args, 'store_tax_behavior', $taxBehavior);
921 $exclusiveTaxTotal = (int) Arr::get($this->args, 'exclusive_tax_total', 0);
922 $feeTax = (int) Arr::get($this->args, 'fee_tax', 0);
923
924 // Roll fee tax into fee_total for exclusive scenarios — gateways use fee_total as source of truth.
925 if ($feeTax && ($taxBehavior === 1 || ($taxBehavior === 3 && $storeTaxBehavior === 1))) {
926 $this->feeTotal += $feeTax;
927 }
928
929 $this->prorateCreditTotal = (int) Arr::get($this->args, 'prorate_credit', 0);
930 // Upgrade-path discount is a post-tax adjustment like the prorate credit: it does
931 // not reduce the taxable base (tax args were computed on the full price), it only
932 // reduces the payable total via manual_discount_total below.
933 $this->upgradeDiscountTotal = (int) Arr::get($this->args, 'upgrade_discount', 0);
934
935 $orderData = [
936 'status' => Status::ORDER_ON_HOLD,
937 'fulfillment_type' => $hasPhysical ? Status::FULFILLMENT_TYPE_PHYSICAL : Status::FULFILLMENT_TYPE_DIGITAL,
938 'type' => $hasSubscription ? Status::ORDER_TYPE_SUBSCRIPTION : Status::ORDER_TYPE_PAYMENT, // revisit this on manual renewal
939 'mode' => $this->storeSettings->get('order_mode', 'test'),
940 'shipping_status' => $hasPhysical ? 'unshipped' : '',
941 'customer_id' => '',
942 'payment_method' => Arr::get($this->args, 'payment_method', ''),
943 'payment_status' => Status::PAYMENT_PENDING,
944 'payment_method_title' => '',
945 'currency' => $this->storeSettings->get('currency'),
946 'subtotal' => $itemsSubtotal,
947 'discount_tax' => 0,
948 'manual_discount_total' => $this->manualDiscountTotal + $this->prorateCreditTotal + $this->upgradeDiscountTotal,
949 'coupon_discount_total' => $this->couponDiscountTotal,
950 'shipping_tax' => Arr::get($this->args, 'shipping_tax', 0),
951 'shipping_total' => Arr::get($this->args, 'shipping_charge', 0),
952 'fee_total' => $this->feeTotal,
953 'tax_total' => Arr::get($this->args, 'tax_total', 0),
954 'tax_behavior' => $taxBehavior,
955 // 'total_amount' => $this->orderTotals['total_amount'],
956 'total_paid' => 0,
957 'total_refund' => 0,
958 'rate' => 1,
959 'note' => Arr::get($this->args, 'note', ''),
960 'ip_address' => Arr::get($this->args, 'ip_address', ''),
961 'config' => [
962 'user_tz' => Arr::get($this->args, 'user_tz', ''),
963 'create_account_after_paid' => Arr::get($this->args, 'create_account_after_paid', 'no'),
964 'shipping_method_id' => Arr::get($this->args, 'shipping_method_id', 0),
965 'shipping_method_title' => Arr::get($this->args, 'shipping_method_title', ''),
966 'prorate_credit' => $this->prorateCreditTotal,
967 'upgrade_discount' => $this->upgradeDiscountTotal,
968 ],
969 ];
970
971 if ($taxBehavior === 1) {
972 // Pure exclusive: fee_tax is already in fee_total; exclude it here to avoid double-count.
973 $estimatedTaxTotal = $orderData['tax_total'] - $feeTax;
974 $estimatedShippingTax = $orderData['shipping_tax'];
975 } elseif ($taxBehavior === 3) {
976 // Mixed: fee_tax rolled into fee_total for exclusive store; shipping still additive.
977 $estimatedTaxTotal = $exclusiveTaxTotal;
978 $estimatedShippingTax = ($storeTaxBehavior === 1) ? $orderData['shipping_tax'] : 0;
979 } else {
980 // Inclusive (2) or reverse-charge (0): nothing to add to total; tax is in item prices.
981 $estimatedTaxTotal = 0;
982 $estimatedShippingTax = 0;
983 if ($taxBehavior !== 2) {
984 // Reverse-charge (0): zero stored columns — no tax applies.
985 // Inclusive (2): keep orderData values so reporting surfaces can read them.
986 $orderData['tax_total'] = 0;
987 $orderData['shipping_tax'] = 0;
988 }
989 }
990
991 $totalAmount = $orderData['subtotal']
992 - $orderData['coupon_discount_total']
993 - $orderData['manual_discount_total']
994 + $orderData['fee_total']
995 + $orderData['shipping_total']
996 + $estimatedTaxTotal
997 + $estimatedShippingTax;
998
999 $orderData['total_amount'] = $totalAmount > 0 ? $totalAmount : 0;
1000 $this->orderData = $orderData;
1001 }
1002
1003 private function syncFeeItems()
1004 {
1005 $orderId = $this->orderModel->id;
1006
1007 // Remove existing fee items
1008 OrderItem::query()->where('order_id', $orderId)->where('payment_type', 'fee')->delete();
1009
1010 // Create new fee items from formatted items
1011 $feeItems = array_filter($this->formattedIOrderItems, function ($item) {
1012 return $item['payment_type'] === 'fee';
1013 });
1014
1015 foreach ($feeItems as $feeItem) {
1016 $feeItem['order_id'] = $orderId;
1017 $feeItem['quantity'] = 1;
1018 $feeItem['line_total'] = $feeItem['subtotal'] - $feeItem['discount_total'];
1019 OrderItem::query()->create($feeItem);
1020 }
1021 }
1022
1023 /**
1024 * @param $inputData
1025 * @return array
1026 */
1027 private function convertToSubscriptionFormat($inputData)
1028 {
1029
1030 /**
1031 * Normal Subscription $100/month
1032 * {
1033 * 'trial_days' => 0,
1034 * 'repeat_interval' => 'month',
1035 * 'times' => 0, // 0 means unlimited
1036 * 'recurring_amount' => 100,
1037 * 'signup_fee' => 0
1038 * }
1039 *
1040 * 30 Days Trial $100/month
1041 * {
1042 * 'trial_days' => 30,
1043 * 'repeat_interval' => 'month',
1044 * 'times' => 0, // 0 means unlimited
1045 * 'recurring_amount' => 100,
1046 * }
1047 *
1048 * $100/month - 30 days Trial with $40 signup fee
1049 * {
1050 * 'trial_days' => 30,
1051 * 'repeat_interval' => 'month',
1052 * 'times' => 0, // 0 means unlimited
1053 * 'recurring_amount' => 100,
1054 * 'signup_fee' => 40
1055 * }
1056 *
1057 * $100 / month with $40 signup fee
1058 * {
1059 * 'trial_days' => 0,
1060 * 'repeat_interval' => 'month',
1061 * 'times' => 0, // 0 means unlimited
1062 * 'recurring_amount' => 100,
1063 * 'signup_fee' => 40
1064 * }
1065 *
1066 * $100 per month but 30% discount on first month
1067 * {
1068 * 'trial_days' => 30,
1069 * 'repeat_interval' => 'month',
1070 * 'times' => 0, // 0 means unlimited
1071 * 'recurring_amount' => 100,
1072 * 'signup_fee' => 70
1073 * }
1074 *
1075 * $100 per month but 50% extra on first month
1076 * {
1077 * 'trial_days' => 0,
1078 * 'repeat_interval' => 'month',
1079 * 'times' => 0, // 0 means unlimited
1080 * 'recurring_amount' => 100,
1081 * 'signup_fee' => 50
1082 * }
1083 *
1084 * $100 per month and 1 month trial and service_fee = $50
1085 * {
1086 * 'trial_days' => 30,
1087 * 'repeat_interval' => 'month',
1088 * 'times' => 0, // 0 means unlimited
1089 * 'recurring_amount' => 100,
1090 * 'signup_fee' => 50
1091 * }
1092 *
1093 *
1094 * $100 per month, signup fee $200 - First Month 50% discount
1095 *
1096 * $first Month = 100 + 200 = 300 - 50% discount = 150
1097 * {
1098 * 'trial_days' => 30,
1099 * 'repeat_interval' => 'month',
1100 * 'times' => 0, // 0 means unlimited
1101 * 'recurring_amount' => 100,
1102 * 'signup_fee' => 150
1103 * }
1104 *
1105 * // prefered when signup_fee > recurring_amount
1106 * {
1107 * 'trial_days' => 0,
1108 * 'repeat_interval' => 'month',
1109 * 'times' => 0, // 0 means unlimited
1110 * 'recurring_amount' => 100,
1111 * 'signup_fee' => 50
1112 * }
1113 */
1114
1115
1116 // Extract and validate input data
1117 $trialDays = (int)($inputData['initial_trial_days'] ?? 0);
1118 $repeatInterval = strtolower(trim($inputData['repeat_interval'] ?? 'yearly'));
1119 $times = (int)($inputData['times'] ?? 0);
1120 $recurringAmount = (int)($inputData['recurring_amount'] ?? 0);
1121 $recurringTax = (int)($inputData['recurring_tax_total'] ?? 0);
1122 $signupFee = (int)($inputData['signup_fee'] ?? 0);
1123 $signupFeeTax = (int)($inputData['signup_fee_tax'] ?? 0);
1124 $firstIterationTax = (int)($inputData['first_iteration_tax'] ?? 0);
1125 $totalDiscount = (int)($inputData['total_discount'] ?? 0);
1126
1127 // Determine if THIS subscription item is tax-inclusive (for behavior=3 mixed carts)
1128 $taxBehavior = (int) Arr::get($inputData, 'tax_behavior', 0);
1129 $itemInclusive = (bool) Arr::get($inputData, 'line_meta.tax_config.inclusive', false);
1130 $isAdditiveTax = ($taxBehavior === 1) || ($taxBehavior === 3 && !$itemInclusive);
1131
1132 // Validate repeat_interval
1133 $validIntervals = array_keys(Helper::getAvailableSubscriptionIntervalMaps());
1134
1135 if (!in_array($repeatInterval, $validIntervals)) {
1136 $repeatInterval = 'yearly';
1137 }
1138
1139 // Convert repeat_interval to standard format
1140 $intervalMap = Helper::getAvailableSubscriptionIntervalMaps();
1141 $standardInterval = Helper::translateIntervalToStandardFormat($repeatInterval);
1142
1143 // Calculate trial days based on interval
1144
1145
1146 // Initialize result array
1147 $result = [
1148 'trial_days' => $trialDays,
1149 'repeat_interval' => $standardInterval,
1150 'times' => $times,
1151 ];
1152
1153
1154 // Calculate signup fee logic
1155 if ($totalDiscount > 0) {
1156 // case: discount applied on subscription with trial days and have signup_fee, otherise discount can't be applied on subscription with trial days.
1157 if ($trialDays > 0 && $signupFee > 0) {
1158 $result['signup_fee'] = max(0, $signupFee - $totalDiscount);
1159 $result['manage_setup_fee'] = 'yes';
1160
1161 if ($isAdditiveTax && $firstIterationTax) {
1162 $result['signup_fee'] += $firstIterationTax;
1163 }
1164 } else {
1165 $firstCycleCost = $recurringAmount + $signupFee - $totalDiscount;
1166
1167 if (Arr::get($inputData, 'is_recurring_coupon', 'no') === 'yes') {
1168 $recurringAmount -= $totalDiscount; // as now discount applied on recurring amount
1169 }
1170
1171 if ($firstCycleCost < $recurringAmount) {
1172 $adjustedTrialDays = Helper::calculateAdjustedTrialDaysForInterval($trialDays, $repeatInterval);
1173
1174 $result['trial_days'] = $adjustedTrialDays;
1175 $result['is_trial_days_simulated'] = 'yes';
1176 $result['signup_fee'] = $firstCycleCost;
1177 $result['manage_setup_fee'] = 'yes';
1178 $result['times'] = $times > 0 ? $times - 1 : 0;
1179 } else if ($firstCycleCost > $recurringAmount) {
1180 $result['trial_days'] = 0;
1181 $result['signup_fee'] = $firstCycleCost - $recurringAmount;
1182 $result['manage_setup_fee'] = 'yes';
1183 } else if ($firstCycleCost == $recurringAmount) {
1184 $result['trial_days'] = 0;
1185 $result['signup_fee'] = 0;
1186 $result['manage_setup_fee'] = 'no';
1187 }
1188
1189 // only the signup fee is adjustable on our system, so we can adjust that to our needs
1190 if ($isAdditiveTax && $firstIterationTax) {
1191 if ($result['trial_days'] > 0) {
1192 $result['signup_fee'] += $firstIterationTax;
1193 } else {
1194 $result['signup_fee'] += ($firstIterationTax - $recurringTax); // need to minus the recurring tax as it would add automatically to the payable amount as no trial days
1195 }
1196 }
1197 }
1198
1199 } else {
1200 $result['signup_fee'] = $signupFee;
1201
1202 if ($isAdditiveTax) {
1203 if ($signupFeeTax) {
1204 $result['signup_fee'] += $signupFeeTax;
1205 }
1206 }
1207 }
1208
1209
1210 $result['repeat_interval'] = array_flip($intervalMap)[$result['repeat_interval']] ?? 'yearly';
1211
1212
1213 return [
1214 'billing_interval' => $result['repeat_interval'],
1215 'bill_times' => $result['times'],
1216 'trial_days' => $result['trial_days'],
1217 'is_trial_days_simulated' => Arr::get($result, 'is_trial_days_simulated', 'no'),
1218 'recurring_amount' => $recurringAmount,
1219 'recurring_tax_total' => $recurringTax,
1220 'signup_fee' => $result['signup_fee'] ?? 0,
1221 ];
1222 }
1223 }
1224