PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.6.4
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.6.4
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 1.2.0 All 47 releases
fluent-cart / app / Helpers / OrderItemHelper.php

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

198 lines 6.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\Helpers;
4
5 use FluentCart\Api\Resource\OrderResource;
6 use FluentCart\App\Models\Order;
7 use FluentCart\App\Models\OrderItem;
8 use FluentCart\Framework\Support\Arr;
9
10 class OrderItemHelper
11 {
12 private $orderId = null;
13
14 /**
15 * @return array
16 * sanitized order_item
17 */
18 public function sanitize($orderItem)
19 {
20 $rules = [
21 "order_id" => 'intval',
22 "post_id" => 'intval',
23 "variation_type" => 'sanitize_text_field',
24 "quantity" => 'intval',
25 "item_name" => 'sanitize_text_field',
26 "item_price" => 'floatval',
27 "item_total" => 'floatval',
28 "line_total" => 'floatval'
29 ];
30
31 foreach ($rules as $key => $value) {
32 if (isset($orderItem[$key])) {
33 $orderItem[$key] = call_user_func($value, $orderItem[$key]);
34 }
35 }
36
37 return $orderItem;
38 }
39
40 public function mapOrderItem($product, $quantity = 0)
41 {
42 $total = floatval(Arr::get($product, 'detail.item_price') * $quantity);
43 $detail = Arr::get($product, 'detail');
44
45 return array(
46 'order_id' => intval($this->orderId),
47 'post_id' => intval(Arr::get($product, 'ID')),
48 'fulfillment_type' => Arr::get($detail, 'item_type'),
49 'quantity' => intval($quantity),
50 'item_name' => Arr::get($product, 'post_title'),
51 'item_price' => floatval(Arr::get($detail, 'item_price')),
52 'tax_amount' => 0,
53 'discount_total' => 0,
54 'item_total' => $total,
55 'line_total' => $total,
56 );
57 }
58
59 public function saveOrderItems($products)
60 {
61 foreach ($products as $product) {
62 $product = $this->sanitize($product);
63
64 $id = Arr::get($product, 'id', false);
65 $orderItem = OrderItem::find($id);
66
67 if (empty($orderItem)) {
68 OrderItem::create($product);
69 } else {
70 $orderItem->update($product);
71 }
72 }
73
74 return [
75 'message' => __('Product Updates!', 'fluent-cart')
76 ];
77 }
78
79 /**
80 * @return array
81 * processed orderItem from product
82 */
83 public function processProducts($orderId, $items)
84 {
85 if (empty($items)) {
86 throw new \Exception(esc_html__('Please add some products!', 'fluent-cart'));
87 }
88
89 if (!$orderId) {
90 throw new \Exception(esc_html__('Order Not valid!', 'fluent-cart'));
91 }
92
93 $this->orderId = $orderId;
94 $processedData = [];
95
96 foreach ($items as $item) {
97 if (isset($item['ID'])) {
98 $data = $this->mapOrderItem($item);
99 array_push($processedData, $data);
100 }
101 };
102
103 return $processedData;
104 }
105
106
107 public function processCustom($product, $orderId)
108 {
109 if (!$orderId) {
110 throw new \Exception(esc_html__('Order Not valid!', 'fluent-cart'));
111 }
112
113 // The published spec (openapi/orders/create-custom-order-item.json) names
114 // these `title` and `price`; the original implementation read `item_name`
115 // and `item_price`. Accept both so neither contract is broken.
116 $title = sanitize_text_field((string)Arr::get($product, 'title', Arr::get($product, 'item_name', '')));
117
118 if (!$title) {
119 throw new \Exception(esc_html__('Item must have a name!', 'fluent-cart'));
120 }
121
122 // Price arrives already in cents.
123 $unitPrice = Helper::roundCent(Arr::get($product, 'price', Arr::get($product, 'item_price', 0)));
124 $quantity = intval(Arr::get($product, 'quantity', 0));
125
126 if ($unitPrice <= 0 || $quantity <= 0) {
127 throw new \Exception(esc_html__('Price, Quantity field should not be empty or zero!', 'fluent-cart'));
128 }
129
130 $lineTotal = $unitPrice * $quantity;
131
132 $fulfillmentType = Arr::get($product, 'fulfillment_type', Status::FULFILLMENT_TYPE_PHYSICAL);
133
134 if (!in_array($fulfillmentType, [Status::FULFILLMENT_TYPE_PHYSICAL, Status::FULFILLMENT_TYPE_DIGITAL], true)) {
135 $fulfillmentType = Status::FULFILLMENT_TYPE_PHYSICAL;
136 }
137
138 $order = Order::query()->find(intval($orderId));
139
140 if (!$order) {
141 throw new \Exception(esc_html__('Order Not valid!', 'fluent-cart'));
142 }
143
144 // Same rule as OrderController::updateOrder — a subscription order's
145 // total is bound to its pending charge, which nothing on this path
146 // resynchronizes.
147 if ($order->isSubscription()) {
148 throw new \Exception(esc_html__('Subscription Order cannot be edited.', 'fluent-cart'));
149 }
150
151 $db = Order::query()->getConnection();
152 $db->beginTransaction();
153
154 try {
155 // Serialize on the order row so concurrent additions rebuild the
156 // aggregates one at a time, and a failed rebuild rolls the line
157 // back out. The tax pass opens its own transaction inside this
158 // one, which the connection runs as a savepoint.
159 $locked = Order::query()
160 ->where('id', $order->id)
161 ->lockForUpdate()
162 ->first();
163
164 if (!$locked) {
165 throw new \Exception(esc_html__('Order Not valid!', 'fluent-cart'));
166 }
167
168 $orderItem = OrderItem::create([
169 'order_id' => $locked->id,
170 'fulfillment_type' => $fulfillmentType,
171 'title' => $title,
172 'post_title' => $title,
173 'quantity' => $quantity,
174 'unit_price' => $unitPrice,
175 'subtotal' => $lineTotal,
176 'line_total' => $lineTotal,
177 'tax_amount' => 0,
178 'discount_total' => 0,
179 // Same rule as the order-edit insert path: digital lines need
180 // no shipment, so they are born fulfilled.
181 'fulfilled_quantity' => $fulfillmentType === Status::FULFILLMENT_TYPE_PHYSICAL ? 0 : $quantity,
182 ]);
183
184 // A line added on its own leaves the order carrying the subtotal it
185 // had before that line existed; the whole-order save posts
186 // client-computed totals instead and never reaches here.
187 OrderResource::syncItemDerivedTotals($locked);
188
189 $db->commit();
190 } catch (\Exception $e) {
191 $db->rollBack();
192 throw $e;
193 }
194
195 return $orderItem;
196 }
197 }
198