PluginProbe
Sendy / 3.4.7
Sendy v3.4.7
3.4.6 3.4.7 trunk 3.0 3.0.1 3.0.2 3.0.3 3.0.4 3.0.5 3.0.6 3.0.7 3.0.8 3.0.9 3.1.0 3.1.1 3.1.2 3.1.3 3.2.0 3.2.1 3.2.2 3.2.3 3.2.4 3.2.5 3.2.6 3.2.7 All 32 releases
sendy / lib / Modules / Orders / OrdersModule.php

OrdersModule.php in Sendy 3.4.7, at lib/Modules/Orders/OrdersModule.php

281 lines 10.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Sendy\WooCommerce\Modules\Orders;
4
5 use GuzzleHttp\Exception\GuzzleException;
6 use Sendy\Api\ApiException;
7 use Sendy\WooCommerce\ApiClientFactory;
8 use Sendy\WooCommerce\Repositories\Shops;
9
10 abstract class OrdersModule
11 {
12 /**
13 * Initialize an order object from the meta box
14 *
15 * @param \WC_Order|null
16 * @return \WP_Post|\WC_Order|null
17 */
18 protected function init_order_object($metaboxObject)
19 {
20 if (is_a($metaboxObject, 'WP_Post')) {
21 return wc_get_order($metaboxObject->ID);
22 }
23
24 if (is_a($metaboxObject, 'WC_Order')) {
25 return $metaboxObject;
26 }
27
28 return null;
29 }
30
31 /**
32 * Determine if the order should be shipped to a pickup point
33 */
34 protected function is_pickup_point_delivery(\WC_Order $order): bool
35 {
36 return $order->meta_exists('_sendy_pickup_point_id');
37 }
38
39 /**
40 * Create the order in the Sendy API
41 *
42 * @param string $preferenceId The UUID of the selected shipping preference
43 * @param string $shopId The UUID of the selected shop
44 * @param int $amount The amount of packages the shipment should contain
45 * @throws GuzzleException
46 */
47 protected function create_shipment_from_order(\WC_Order $order, string $preferenceId, string $shopId, int $amount): void
48 {
49 if ($order->meta_exists('_sendy_shipment_id')) {
50 // translators: %s The ID of the order
51 sendy_flash_admin_notice('notice', sprintf(__('Order #%s already has a shipment created', 'sendy'), $order->get_id()));
52 }
53
54 $request = [
55 'preference_id' => $preferenceId,
56 'shop_id' => $shopId,
57 'amount' => $amount,
58 'reference' => $order->get_order_number(),
59 'order_date' => $order->get_date_created()->format(\DateTimeInterface::RFC3339),
60 'options' => [],
61 ];
62
63 $this->addAddressToRequest($order, $request);
64
65 if ($this->is_pickup_point_delivery($order)) {
66 $request['options']['parcel_shop_id'] = $order->get_meta('_sendy_pickup_point_id');
67 }
68
69 if (get_option('sendy_import_weight')) {
70 $this->addWeightToRequest($order, $request);
71 }
72
73 if (get_option('sendy_import_products')) {
74 $this->addProductsToRequest($order, $request);
75 }
76
77 try {
78 $result = ApiClientFactory::buildConnectionUsingTokens()->shipment->createFromPreference($request);
79
80 $order->update_meta_data('_sendy_shipment_id', $result['uuid']);
81 $order->update_meta_data('_sendy_packages', $result['packages']);
82
83 if (get_option('sendy_mark_order_as_completed') === 'after-shipment-created') {
84 $order->set_status('completed', __('Sendy: Shipment created', 'sendy'));
85 }
86
87 $order->save();
88 } catch (ApiException $exception) {
89 sendy_flash_admin_notice('error', $this->parseException($exception, $order));
90 }
91 }
92
93 protected function create_shipment_with_smart_rules(\WC_Order $order, bool $executedInBackground = true, string $shopId = null): void
94 {
95 $firstShopId = array_keys((new Shops())->list())[0];
96 $shippingMethod = array_values($order->get_shipping_methods())[0];
97
98 $request = [
99 'shop_id' => $shopId ?? get_option('sendy_default_shop') ?? $firstShopId,
100 'amount' => 1,
101 'reference' => $order->get_order_number(),
102 'order_date' => $order->get_date_created()->format(\DateTimeInterface::RFC3339),
103 'options' => [],
104 'shippingMethodId' => $shippingMethod ? $shippingMethod->get_instance_id() : null,
105 ];
106
107 $this->addAddressToRequest($order, $request);
108
109 if ($this->is_pickup_point_delivery($order)) {
110 $request['options']['parcel_shop_id'] = $order->get_meta('_sendy_pickup_point_id');
111 }
112
113 if (get_option('sendy_import_weight')) {
114 $this->addWeightToRequest($order, $request);
115 }
116
117 if (get_option('sendy_import_products')) {
118 $this->addProductsToRequest($order, $request);
119 }
120
121 try {
122 $shipment = ApiClientFactory::buildConnectionUsingTokens()->shipment->createWithSmartRules($request);
123
124 $order->update_meta_data('_sendy_shipment_id', $shipment['uuid']);
125 $order->save();
126 } catch (ApiException $exception) {
127 $message = $this->parseException($exception, $order);
128
129 if ($executedInBackground) {
130 // TODO Implement a logger
131 } else {
132 sendy_flash_admin_notice('error', $message);
133 }
134 }
135 }
136
137 /**
138 * Fetch the labels from the Sendy API and offer them as download to the user
139 */
140 protected function offer_labels_as_download(array $shipment_ids): void
141 {
142 try {
143 $response = ApiClientFactory::buildConnectionUsingTokens()->label->get($shipment_ids);
144
145 if (ob_get_contents()) {
146 ob_clean();
147 }
148
149 $labels_safe = base64_decode($response['labels']);
150
151 header('Content-Description: File Transfer');
152 header('Content-Type: application/octet-stream');
153 header('Content-Disposition: attachment; filename="labels.pdf"');
154 header('Expires: 0');
155 header('Cache-Control: must-revalidate');
156 header('Pragma: public');
157 header('Content-Length: ' . strlen($labels_safe));
158
159 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
160 echo $labels_safe;
161 exit;
162 } catch (ApiException $exception) {
163 wp_die(esc_html__('Something went wrong while downloading the labels', 'sendy'));
164 }
165 }
166
167 /**
168 * Add the shipping address to the request to create the shipment
169 */
170 private function addAddressToRequest(\WC_Order $order, &$request): void
171 {
172 $request['contact'] = sprintf(
173 '%s %s',
174 $order->get_shipping_first_name() ?? $order->get_billing_first_name(),
175 $order->get_shipping_last_name() ?? $order->get_billing_last_name(),
176 );
177
178 $address = $this->parseAddressFromOrder($order);
179
180 $request['company_name'] = $order->get_shipping_company() ?? $order->get_billing_company();
181 $request['country'] = $order->get_shipping_country() ?? $order->get_billing_country();
182 $request['street'] = $address->street;
183 $request['number'] = $address->number;
184 $request['addition'] = $address->addition;
185 $request['postal_code'] = $order->get_shipping_postcode() ?? $order->get_billing_postcode();
186 $request['city'] = $order->get_shipping_city() ?? $order->get_billing_city();
187 $request['phone'] = $order->get_shipping_phone() ?? $order->get_billing_phone();
188 $request['email'] = $order->get_billing_email();
189 }
190
191 /**
192 * Add the weight to the request to create a shipment
193 */
194 private function addWeightToRequest(\WC_Order $order, &$request): void
195 {
196 $weight = 0;
197
198 foreach ($order->get_items() as $item) {
199 $weight += (float) wc_get_weight($item->get_product()->get_weight(), 'kg') * $item->get_quantity();
200 }
201
202 if ($weight > 0) {
203 $request['weight'] = $weight;
204 }
205 }
206
207 /**
208 * Add the product data to the request to create a shipment
209 */
210 private function addProductsToRequest(\WC_Order $order, &$request): void
211 {
212 foreach ($order->get_items() as $item) {
213 $product = $item->get_product();
214
215 $request['products'][] = [
216 'description' => $item->get_name(),
217 'sku' => $product->get_sku(),
218 'quantity' => $item->get_quantity(),
219 'unit_price' => $item->get_subtotal() / $item->get_quantity(),
220 'unit_weight' => absint($product->get_weight()) > 0 ? wc_get_weight($product->get_weight(), 'kg') : null,
221 ];
222 }
223 }
224
225 /**
226 * Split the address into street, number and addition
227 *
228 * First it tries to extra the address from address_1. If no number can be found, it is assumed that the number and
229 * addition are stored in address_2. It will concatenate address_1 and address_2 and parse the address from the
230 * concatenated string.
231 *
232 * @return object{street: string, house_number:string|null, house_number_addition:string|null}
233 */
234 private function parseAddressFromOrder(\WC_Order $order): \stdClass
235 {
236 $address = sendy_parse_address($order->get_shipping_address_1() ?? $order->get_billing_address_1());
237
238 if (is_null($address->number)) {
239 return sendy_parse_address(sprintf(
240 '%s %s',
241 $order->get_shipping_address_1() ?? $order->get_billing_address_1(),
242 $order->get_shipping_address_2() ?? $order->get_billing_address_2(),
243 ));
244 }
245
246 return $address;
247 }
248
249 private function parseException(ApiException $exception, \WC_Order $order): string
250 {
251 if ($exception->getCode() === 500) {
252 // translators: %1$s should contain the ID of the order and %2$s the error
253 $message = sprintf(__('Error while creating shipment for order #%1$s: %2$s', 'sendy'), $order->get_id(), $exception->getMessage());
254 } else {
255 $statusCode = $exception->getCode();
256
257 if ($statusCode === 401) {
258 // translators: %s should contain the ID of the order
259 $message = sprintf(__('Error while creating shipment for order #%s: Authentication failed. Check the settings page to reconnect with Sendy.', 'sendy'), $order->get_id());
260 } elseif ($statusCode === 422) {
261 $errors = [];
262
263 foreach ($exception->getErrors() as $_ => $messages) {
264 $errors = array_merge($errors, $messages);
265 }
266
267 // translators: %1$s should contain the ID of the order and %2$s the error
268 $message = sprintf(__('Error while creating shipment for order #%1$s: %2$s', 'sendy'), $order->get_id(), implode("\n", $errors));
269 } elseif ($statusCode === 429) {
270 // translators: %s should contain the ID of the order
271 $message = sprintf(__('Error while creating shipment for order #%s: Too many requests. Please try again later.', 'sendy'), $order->get_id());
272 } else {
273 // translators: %s should contain the ID of the order
274 $message = sprintf(__('Error while creating shipment for order #%s: Unknown error.', 'sendy'), $order->get_id());
275 }
276 }
277
278 return $message;
279 }
280 }
281