PluginProbe
Sendy / 3.0
Sendy v3.0
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 / src / Modules / Orders / OrdersModule.php

OrdersModule.php in Sendy 3.0, at src/Modules/Orders/OrdersModule.php

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