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.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 / Hooks / Handlers / CustomCheckout / CustomCheckout.php

CustomCheckout.php in FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler 1.5.0, at app/Hooks/Handlers/CustomCheckout/CustomCheckout.php

216 lines 8.6 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\Hooks\Handlers\CustomCheckout;
4
5 use FluentCart\Api\StoreSettings;
6 use FluentCart\App\Helpers\CartHelper;
7 use FluentCart\App\Helpers\Status;
8 use FluentCart\App\Models\Cart;
9 use FluentCart\App\Models\Order;
10 use FluentCart\App\Services\ProductItemService;
11 use FluentCart\Framework\Support\Arr;
12
13 class CustomCheckout
14 {
15 public function register()
16 {
17 add_action('fluent_cart_action_custom_checkout', [$this, 'handleCustomCheckoutRedirect'], 10, 1);
18 }
19
20 /*
21 * 1. validation
22 * 2. create cart
23 * - subscription
24 * discount, signup etc.
25 * - onetime
26 * 3. redirect to the checkout with cart
27 */
28 public function handleCustomCheckoutRedirect($data)
29 {
30 $orderHash = sanitize_text_field(Arr::get($data, 'order_hash', ''));
31 $order = Order::query()->where('uuid', $orderHash)->first();
32 if (!$order) {
33 die('Invalid order!');
34 }
35
36 if ($order->payment_status === Status::PAYMENT_PAID || $order->status === Status::ORDER_COMPLETED) {
37 die('Order already completed!');
38 }
39
40 $totalDue = $order->total_amount - $order->paid;
41
42 if ($totalDue <= 0) {
43 die('No due amount found!');
44 }
45
46 // Post-tax credits (plan upgrade) live inside manual_discount_total on the order
47 // but must NOT be redistributed as item-level manual discounts on re-pay — that
48 // would shrink the tax base. They are re-applied post-tax via checkout_data below.
49 $prorateCredit = (int) Arr::get($order->config, 'prorate_credit', 0);
50 $upgradeDiscount = (int) Arr::get($order->config, 'upgrade_discount', 0);
51 $itemManualDiscountTotal = max(0, (int) $order->manual_discount_total - $prorateCredit - $upgradeDiscount);
52
53 if ($order->type === Status::ORDER_TYPE_SUBSCRIPTION) {
54 $orderItem = $order->order_items->filter(function ($item) {
55 return !in_array($item->payment_type, ['signup_fee', 'fee']);
56 })->first();
57
58 if (!$orderItem) {
59 die('Subscription order item not found!');
60 }
61
62 $subscriptionItemData = ProductItemService::getItem([
63 'order_id' => $orderItem->order_id,
64 'product_id' => $orderItem->post_id,
65 'variation_id' => $orderItem->object_id,
66 ]);
67
68 if (!$subscriptionItemData || !$subscriptionItemData->variation) {
69 die('Failed to load product data for custom checkout!');
70 }
71
72 $newItem = $subscriptionItemData->variation->toArray();
73 $isCustom = $subscriptionItemData->is_custom;
74
75 Arr::set($newItem, 'item_price', $orderItem->unit_price);
76
77 // Keep the variation's other_info as-is (trial_days, signup_fee, repeat_interval, times).
78 // The variation's other_info has the clean product-level config that CheckoutProcessor
79 // expects when processing the cart.
80
81 if ($isCustom) {
82 Arr::set($newItem, 'post_title', $subscriptionItemData->variation->post_title);
83 Arr::set($newItem, 'variation_type', $subscriptionItemData->variation->variation_type);
84 } else {
85 Arr::set($newItem, 'post_title', $subscriptionItemData->variation->product->post_title);
86 Arr::set($newItem, 'variation_type', $subscriptionItemData->variation->product_detail->variation_type);
87 }
88
89 if ($order->coupon_discount_total > 0 || $itemManualDiscountTotal > 0) {
90 Arr::set($newItem, 'coupon_discount', (int) $order->coupon_discount_total);
91 Arr::set($newItem, 'manual_discount', $itemManualDiscountTotal);
92 }
93
94 $instantCart = CartHelper::generateCartFromCustomVariation($newItem, $orderItem->quantity);
95
96 } else {
97 $items = [];
98 $productItems = $order->order_items->filter(function ($orderItem) {
99 return !in_array($orderItem->payment_type, ['fee', 'signup_fee']);
100 });
101 foreach ($productItems as $orderItem) {
102 $itemData = ProductItemService::getItem([
103 'order_id' => $orderItem->order_id,
104 'product_id' => $orderItem->post_id,
105 'variation_id' => $orderItem->object_id,
106 ]);
107 if (!$itemData || !$itemData->variation) {
108 die('Failed to load product data for custom checkout!');
109 }
110 $item = $itemData->variation->toArray();
111
112
113 Arr::set($item, 'coupon_discount', (string)($orderItem->discount_total)); // item discount total is always coupon discount + manual discount
114 Arr::set($item, 'tax_amount', $orderItem->tax_amount);
115 Arr::set($item, 'post_title', $orderItem->post_title);
116
117 if ($itemManualDiscountTotal) {
118 $subtotal = Arr::get($orderItem, 'subtotal');
119 $manualDiscount = ($subtotal * $itemManualDiscountTotal) / $order->subtotal;
120 $couponDiscount = max(0, $orderItem->discount_total - $manualDiscount);
121 Arr::set($item, 'manual_discount', $manualDiscount);
122 Arr::set($item, 'coupon_discount', $couponDiscount);
123 }
124
125 $items[] = CartHelper::generateCartItemCustomItem($item, $orderItem->quantity);
126 }
127
128 $instantCart = new Cart();
129 $instantCart->cart_data = $items;
130 }
131
132 $primaryBillingAddress = [];
133 if ($order->billing_address) {
134 $primaryBillingAddress = $order->billing_address
135 ->getFormattedDataForCheckout('billing_');
136 }
137 $instantCart->cart_group = 'instant';
138 $instantCart->first_name = $order->customer->first_name;
139 $instantCart->last_name = $order->customer->last_name;
140 $instantCart->email = $order->customer->email;
141 $instantCart->customer_id = $order->customer->customer_id;
142 $instantCart->user_id = $order->customer->user_id;
143 $instantCart->order_id = $order->id;
144 $instantCart->cart_hash = md5('custom_payment_cart_' . wp_generate_uuid4() . time());
145
146 // Preserve original order fees so the custom payment checkout
147 // shows exactly what was charged, not whatever the current filter returns
148 $originalFees = [];
149 $feeOrderItems = $order->order_items->filter(function ($item) {
150 return $item->payment_type === 'fee';
151 });
152
153 foreach ($feeOrderItems as $feeItem) {
154 $otherInfo = $feeItem->other_info ?? [];
155 $originalFees[] = [
156 'key' => Arr::get($otherInfo, 'fee_key', ''),
157 'label' => $feeItem->title,
158 'amount' => (int) $feeItem->unit_price,
159 'source' => Arr::get($otherInfo, 'source', 'custom'),
160 'taxable' => !empty(Arr::get($otherInfo, 'taxable')),
161 'meta' => Arr::get($otherInfo, 'meta', []),
162 ];
163 }
164
165 $checkoutData = [
166 'is_locked' => 'yes',
167 'disable_coupons' => 'yes',
168 'custom_checkout' => 'yes',
169 'form_data' => $primaryBillingAddress,
170 'fees' => $originalFees,
171 'custom_checkout_data' => [
172 'coupon_discount_total' => $order->coupon_discount_total,
173 'manual_discount_total' => $itemManualDiscountTotal,
174 'discount_total' => $order->coupon_discount_total + $itemManualDiscountTotal,
175 'shipping_total' => $order->shipping_total,
176 ],
177 '__cart_notices' => [
178 [
179 'id' => 'custom_payment_notice',
180 'type' => 'info',
181 'content' => 'You are making payment for your order (#' . $order->uuid . ').',
182 ]
183 ]
184 ];
185
186 if ($prorateCredit > 0) {
187 $checkoutData['prorate_credit'] = [
188 'amount' => $prorateCredit,
189 'title' => __('Prorate Credit', 'fluent-cart'),
190 ];
191 }
192
193 if ($upgradeDiscount > 0) {
194 $checkoutData['upgrade_discount'] = [
195 'amount' => $upgradeDiscount,
196 'title' => __('Upgrade Discount', 'fluent-cart'),
197 ];
198 }
199
200 $instantCart->checkout_data = $checkoutData;
201
202 $instantCart->save();
203
204 $cartHash = $instantCart->cart_hash;
205
206 $checkoutUrl = add_query_arg(
207 [
208 'fct_cart_hash' => $cartHash,
209 ],
210 (new StoreSettings())->getCheckoutPage()
211 );
212
213 wp_redirect($checkoutUrl);
214 exit();
215 }
216 }