PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.6.0
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.6.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.6.0, at app/Hooks/Handlers/CustomCheckout/CustomCheckout.php

232 lines 9.5 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 Arr::set($item, 'variation_type', $itemData->variation->product_detail->variation_type);
117
118 // For renewal invoices, use the order item's unit_price (matches subscription's recurring_amount)
119 // and strip trial_days so CheckoutProcessor doesn't apply trial (0) pricing.
120 if ($order->type === Status::ORDER_TYPE_RENEWAL) {
121 Arr::set($item, 'item_price', $orderItem->unit_price);
122 $otherInfo = Arr::get($item, 'other_info', []);
123 $otherInfo['trial_days'] = 0;
124 Arr::set($item, 'other_info', $otherInfo);
125 }
126
127 if ($itemManualDiscountTotal) {
128 $subtotal = Arr::get($orderItem, 'subtotal');
129 $manualDiscount = ($subtotal * $itemManualDiscountTotal) / $order->subtotal;
130 $couponDiscount = max(0, $orderItem->discount_total - $manualDiscount);
131 Arr::set($item, 'manual_discount', $manualDiscount);
132 Arr::set($item, 'coupon_discount', $couponDiscount);
133 }
134
135 $items[] = CartHelper::generateCartItemCustomItem($item, $orderItem->quantity);
136 }
137
138 $instantCart = new Cart();
139 $instantCart->cart_data = $items;
140 }
141
142 $primaryBillingAddress = [];
143 if ($order->billing_address) {
144 $primaryBillingAddress = $order->billing_address
145 ->getFormattedDataForCheckout('billing_');
146 }
147 $instantCart->cart_group = 'instant';
148 $instantCart->first_name = $order->customer->first_name;
149 $instantCart->last_name = $order->customer->last_name;
150 $instantCart->email = $order->customer->email;
151 $instantCart->customer_id = $order->customer->customer_id;
152 $instantCart->user_id = $order->customer->user_id;
153 $instantCart->order_id = $order->id;
154 $instantCart->cart_hash = md5('custom_payment_cart_' . wp_generate_uuid4() . time());
155
156 // Preserve original order fees so the custom payment checkout
157 // shows exactly what was charged, not whatever the current filter returns
158 $originalFees = [];
159 $feeOrderItems = $order->order_items->filter(function ($item) {
160 return $item->payment_type === 'fee';
161 });
162
163 foreach ($feeOrderItems as $feeItem) {
164 $otherInfo = $feeItem->other_info ?? [];
165 $originalFees[] = [
166 'key' => Arr::get($otherInfo, 'fee_key', ''),
167 'label' => $feeItem->title,
168 'amount' => (int) $feeItem->unit_price,
169 'source' => Arr::get($otherInfo, 'source', 'custom'),
170 'taxable' => !empty(Arr::get($otherInfo, 'taxable')),
171 'meta' => Arr::get($otherInfo, 'meta', []),
172 ];
173 }
174
175 $checkoutData = [
176 'is_locked' => 'yes',
177 'disable_coupons' => 'yes',
178 'custom_checkout' => 'yes',
179 'form_data' => $primaryBillingAddress,
180 'fees' => $originalFees,
181 'custom_checkout_data' => [
182 'coupon_discount_total' => $order->coupon_discount_total,
183 'manual_discount_total' => $itemManualDiscountTotal,
184 'discount_total' => $order->coupon_discount_total + $itemManualDiscountTotal,
185 'shipping_total' => $order->shipping_total,
186 ],
187 '__cart_notices' => [
188 [
189 'id' => 'custom_payment_notice',
190 'type' => 'info',
191 'content' => 'You are making payment for your order (#' . $order->uuid . ').',
192 ]
193 ]
194 ];
195
196 if ($order->type === Status::ORDER_TYPE_RENEWAL) {
197 $checkoutData['renewal_order'] = [
198 'due_date' => $order->getMeta('due_date'),
199 ];
200 }
201
202 if ($prorateCredit > 0) {
203 $checkoutData['prorate_credit'] = [
204 'amount' => $prorateCredit,
205 'title' => __('Prorate Credit', 'fluent-cart'),
206 ];
207 }
208
209 if ($upgradeDiscount > 0) {
210 $checkoutData['upgrade_discount'] = [
211 'amount' => $upgradeDiscount,
212 'title' => __('Upgrade Discount', 'fluent-cart'),
213 ];
214 }
215
216 $instantCart->checkout_data = $checkoutData;
217
218 $instantCart->save();
219
220 $cartHash = $instantCart->cart_hash;
221
222 $checkoutUrl = add_query_arg(
223 [
224 'fct_cart_hash' => $cartHash,
225 ],
226 (new StoreSettings())->getCheckoutPage()
227 );
228
229 wp_redirect($checkoutUrl);
230 exit();
231 }
232 }