PluginProbe
seQura / 4.0.0
seQura v4.0.0
4.3.4 4.3.3 4.3.2 4.3.1 trunk 2.0.0 2.0.10 2.0.11 2.0.12 2.0.5 2.0.6 2.0.7 2.0.8 2.0.9 3.0.0 3.0.2 3.0.5 3.0.6 3.0.7 3.1.0 3.1.1 3.2.0 3.2.1 3.2.2 4.0.0 All 30 releases
sequra / src / Services / Order / class-order-service.php

class-order-service.php in seQura 4.0.0, at src/Services/Order/class-order-service.php

503 lines 14.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Order service
4 *
5 * @package SeQura/WC
6 * @subpackage SeQura/WC/Services
7 */
8
9 namespace SeQura\WC\Services\Order;
10
11 use SeQura\Core\BusinessLogic\Domain\Connection\Services\ConnectionService;
12 use SeQura\Core\BusinessLogic\Domain\Order\Models\OrderRequest\Cart;
13 use SeQura\Core\BusinessLogic\Domain\Order\Models\OrderUpdateData;
14 use SeQura\Core\BusinessLogic\Domain\Order\RepositoryContracts\SeQuraOrderRepositoryInterface;
15 use SeQura\Core\BusinessLogic\Domain\Order\Service\OrderService;
16 use SeQura\Core\BusinessLogic\SeQuraAPI\BaseProxy;
17 use SeQura\WC\Core\Extension\BusinessLogic\Domain\OrderStatusSettings\Services\Order_Status_Settings_Service;
18 use SeQura\WC\Dto\Cart_Info;
19 use SeQura\WC\Dto\Payment_Method_Data;
20 use SeQura\WC\Repositories\Interface_Deletable_Repository;
21 use SeQura\WC\Repositories\Interface_Table_Migration_Repository;
22 use SeQura\WC\Services\Cart\Interface_Cart_Service;
23 use SeQura\WC\Services\Log\Interface_Logger_Service;
24 use SeQura\WC\Services\Pricing\Interface_Pricing_Service;
25 use SeQura\WC\Services\Time\Interface_Time_Checker_Service;
26 use Throwable;
27 use WC_DateTime;
28 use WC_Order;
29
30 /**
31 * Handle use cases related to Order
32 */
33 class Order_Service implements Interface_Order_Service {
34
35 private const META_KEY_METHOD_TITLE = '_sq_method_title';
36 private const META_KEY_PRODUCT = '_sq_product';
37 private const META_KEY_CAMPAIGN = '_sq_campaign';
38 private const META_KEY_CART_REF = '_sq_cart_ref';
39 private const META_KEY_CART_CREATED_AT = '_sq_cart_created_at';
40 private const META_KEY_SENT_TO_SEQURA = '_sq_sent_to_sequra';
41
42 /**
43 * Pricing service
44 *
45 * @var Interface_Pricing_Service
46 */
47 private $pricing_service;
48
49 /**
50 * Order status service
51 *
52 * @var Order_Status_Settings_Service
53 */
54 private $order_status_service;
55
56 /**
57 * Cart service
58 *
59 * @var Interface_Cart_Service
60 */
61 private $cart_service;
62
63 /**
64 * Logger
65 *
66 * @var Interface_Logger_Service
67 */
68 private $logger;
69
70 /**
71 * SeQura Order repository
72 *
73 * @var SeQuraOrderRepositoryInterface
74 */
75 private $sequra_order_repository;
76
77 /**
78 * Time checker service
79 *
80 * @var Interface_Time_Checker_Service
81 */
82 private $time_checker_service;
83
84 /**
85 * Deletable repository
86 *
87 * @var Interface_Deletable_Repository
88 */
89 private $deletable_repository;
90
91 /**
92 * Table migration repository
93 *
94 * @var Interface_Table_Migration_Repository
95 */
96 private $table_migration_repository;
97
98 /**
99 * Payment gateway ID
100 *
101 * @var string
102 */
103 private $payment_gateway_id;
104
105 /**
106 * Connection service
107 *
108 * @var ConnectionService
109 */
110 private $connection_service;
111
112 /**
113 * Order service
114 *
115 * @var OrderService
116 */
117 private $order_service;
118
119 /**
120 * Constructor
121 */
122 public function __construct(
123 SeQuraOrderRepositoryInterface $sequra_order_repository,
124 Interface_Pricing_Service $pricing_service,
125 Order_Status_Settings_Service $order_status_service,
126 Interface_Cart_Service $cart_service,
127 Interface_Logger_Service $logger,
128 Interface_Time_Checker_Service $time_checker_service,
129 Interface_Deletable_Repository $deletable_repository,
130 Interface_Table_Migration_Repository $table_migration_repository,
131 string $payment_gateway_id,
132 ConnectionService $connection_service,
133 OrderService $order_service
134 ) {
135 $this->pricing_service = $pricing_service;
136 $this->order_status_service = $order_status_service;
137 $this->cart_service = $cart_service;
138 $this->logger = $logger;
139 $this->sequra_order_repository = $sequra_order_repository;
140 $this->time_checker_service = $time_checker_service;
141 $this->deletable_repository = $deletable_repository;
142 $this->table_migration_repository = $table_migration_repository;
143 $this->payment_gateway_id = $payment_gateway_id;
144 $this->connection_service = $connection_service;
145 $this->order_service = $order_service;
146 }
147
148 /**
149 * Get order meta value by key from a seQura order.
150 * If the order is not a seQura order an empty string is returned.
151 */
152 private function get_order_meta( WC_Order $order, $meta_key ): string {
153 if ( $order->get_payment_method() !== $this->payment_gateway_id ) {
154 return '';
155 }
156 return strval( $order->get_meta( $meta_key, true ) );
157 }
158
159 /**
160 * Get the seQura payment method title for the order.
161 * If the order is not a seQura order an empty string is returned.
162 */
163 public function get_payment_method_title( WC_Order $order ): string {
164 return $this->get_order_meta( $order, self::META_KEY_METHOD_TITLE );
165 }
166
167 /**
168 * Get the seQura product for the order.
169 * If the value is not found an empty string is returned.
170 */
171 public function get_product( WC_Order $order ): string {
172 return $this->get_order_meta( $order, self::META_KEY_PRODUCT );
173 }
174
175 /**
176 * Get the seQura campaign for the order.
177 * If the value is not found an empty string is returned.
178 */
179 public function get_campaign( WC_Order $order ): string {
180 return $this->get_order_meta( $order, self::META_KEY_CAMPAIGN );
181 }
182
183 /**
184 * Get the seQura cart info for the order.
185 * If the value is not found null is returned.
186 */
187 public function get_cart_info( WC_Order $order ): ?Cart_Info {
188 return Cart_Info::from_array(
189 array(
190 'ref' => $this->get_order_meta( $order, self::META_KEY_CART_REF ),
191 'created_at' => $this->get_order_meta( $order, self::META_KEY_CART_CREATED_AT ),
192 )
193 );
194 }
195
196 /**
197 * Save required metadata for the order.
198 * Returns true if the metadata was saved, false otherwise.
199 */
200 public function set_order_metadata( WC_Order $order, ?Payment_Method_Data $dto, ?Cart_Info $cart_info ): bool {
201 if ( ! $dto ) {
202 return false;
203 }
204
205 if ( ! $cart_info ) {
206 return false;
207 }
208
209 $order->update_meta_data( self::META_KEY_PRODUCT, $dto->product );
210 if ( ! empty( $dto->campaign ) ) {
211 $order->update_meta_data( self::META_KEY_CAMPAIGN, $dto->campaign );
212 } else {
213 $order->delete_meta_data( self::META_KEY_CAMPAIGN );
214 }
215 $order->update_meta_data( self::META_KEY_METHOD_TITLE, $dto->title );
216
217 $this->set_cart_info( $order, $cart_info );
218 return true;
219 }
220
221 /**
222 * Set cart info if it is not already set
223 */
224 public function create_cart_info( WC_Order $order ): ?Cart_Info {
225 $cart_info = $this->get_cart_info( $order );
226 if ( $this->cart_service->is_cart_info_valid( $cart_info ) ) {
227 // Skip if the cart info is already set.
228 return null;
229 }
230
231 $date = $order->get_date_created();
232 $cart_info = new Cart_Info( null, $date ? $date->date( 'c' ) : null );
233 $this->set_cart_info( $order, $cart_info );
234 return $cart_info;
235 }
236
237 /**
238 * Set cart info for the order
239 *
240 * @param Cart_Info $cart_info Cart info
241 */
242 public function set_cart_info( WC_Order $order, $cart_info ): void {
243 if ( $cart_info instanceof Cart_Info ) {
244 $order->update_meta_data( self::META_KEY_CART_REF, $cart_info->ref );
245 $order->update_meta_data( self::META_KEY_CART_CREATED_AT, $cart_info->created_at );
246 $order->save();
247 }
248 }
249
250 /**
251 * Get the meta key used to store the sent to seQura value.
252 */
253 public function get_sent_to_sequra_meta_key(): string {
254 return self::META_KEY_SENT_TO_SEQURA;
255 }
256
257 /**
258 * Set the order as sent to seQura
259 */
260 public function set_as_sent_to_sequra( WC_Order $order ): void {
261 $order->update_meta_data( self::META_KEY_SENT_TO_SEQURA, 1 );
262 $order->save();
263 }
264
265 /**
266 * Call the Order Update API to sync the order status with SeQura
267 *
268 * @throws Throwable
269 */
270 public function update_sequra_order_status( WC_Order $order, string $old_store_status, string $new_store_status ): void {
271 if ( $order->get_payment_method( 'edit' ) !== $this->payment_gateway_id
272 || ! in_array( $new_store_status, $this->order_status_service->get_shop_status_completed( true ), true )
273 || ! $order->needs_processing() ) {
274 // Prevent updating orders that:
275 // 1. Were not paid with SeQura.
276 // 1. Are not completed.
277 // 2. Contain only virtual & downloadable products.
278 return;
279 }
280
281 $this->set_sequra_order_status_to_shipped( $order );
282 }
283
284 /**
285 * Set the order status to shipped in SeQura
286 *
287 * @throws Throwable
288 */
289 private function set_sequra_order_status_to_shipped( WC_Order $order ): void {
290 $cart_info = $this->get_cart_info( $order );
291 $currency = $order->get_currency( 'edit' );
292 $cart_ref = $cart_info ? $cart_info->ref : null;
293 $created_at = $cart_info ? $cart_info->created_at : null;
294 $updated_at = $this->get_order_completion_date( $order );
295 $shipped_items = array_merge(
296 $this->cart_service->get_items( $order ),
297 $this->cart_service->get_handling_items( $order ),
298 $this->cart_service->get_discount_items( $order ),
299 $this->cart_service->get_refund_items( $order )
300 );
301
302 try {
303 /**
304 * Filter the order_ref_1.
305 *
306 * @since 2.0.0
307 */
308 $ref_1 = \apply_filters( 'woocommerce_sequra_get_order_ref_1', $order->get_id(), $order );
309 $this->order_service->updateOrder(
310 new OrderUpdateData(
311 $ref_1, // Order reference.
312 new Cart( $currency, false, $shipped_items, $cart_ref, $created_at, $updated_at ), // Shipped cart.
313 new Cart( $currency ), // Unshipped cart.
314 null, // Delivery address.
315 null // Invoice address.
316 )
317 );
318 } catch ( Throwable $e ) {
319 throw $e;
320 }
321 }
322
323 /**
324 * Update the order amount in SeQura after a refund
325 *
326 * @throws Throwable
327 */
328 public function handle_refund( WC_Order $order, float $amount ): void {
329 $cart_info = $this->get_cart_info( $order );
330 $currency = $order->get_currency( 'edit' );
331 $cart_ref = $cart_info ? $cart_info->ref : null;
332 $created_at = $cart_info ? $cart_info->created_at : null;
333 $updated_at = $this->get_order_completion_date( $order );
334 $shipped_items = array();
335 if ( $order->get_total( 'edit' ) > $amount ) {
336 $shipped_items = array_merge(
337 $this->cart_service->get_items( $order ),
338 $this->cart_service->get_handling_items( $order ),
339 $this->cart_service->get_discount_items( $order ),
340 $this->cart_service->get_refund_items( $order )
341 );
342 }
343
344 try {
345 /**
346 * Filter the order_ref_1.
347 *
348 * @since 2.0.0
349 */
350 $ref_1 = \apply_filters( 'woocommerce_sequra_get_order_ref_1', $order->get_id(), $order );
351 $this->order_service->updateOrder(
352 new OrderUpdateData(
353 $ref_1, // Order reference.
354 new Cart( $currency, false, $shipped_items, $cart_ref, $created_at, $updated_at ), // Shipped cart.
355 new Cart( $currency ), // Unshipped cart.
356 null, // Delivery address.
357 null // Invoice address.
358 )
359 );
360 } catch ( Throwable $e ) {
361 throw $e;
362 }
363 }
364
365 /**
366 * Get the link to the SeQura back office for the order
367 */
368 public function get_link_to_sequra_back_office( WC_Order $order ): ?string {
369 if ( $order->get_payment_method() !== $this->payment_gateway_id ) {
370 return null;
371 }
372
373 $merchant_id = $this->get_merchant_id( $order );
374 if ( ! $merchant_id ) {
375 return null;
376 }
377
378 try {
379 $conn_data = $this->connection_service->getConnectionDataByMerchantId( $merchant_id );
380 $base_url = array(
381 'sequra' => array(
382 BaseProxy::TEST_MODE => 'https://portal-sandbox.sequra.com',
383 BaseProxy::LIVE_MODE => 'https://portal.sequra.com',
384 ),
385 'svea' => array(
386 BaseProxy::TEST_MODE => 'https://portal-sandbox.sequra.com',
387 BaseProxy::LIVE_MODE => 'https://portal.sequra.com',
388 ),
389 );
390 return ! isset( $base_url[ $conn_data->getDeployment() ][ $conn_data->getEnvironment() ] ) ? null : $base_url[ $conn_data->getDeployment() ][ $conn_data->getEnvironment() ] . '/orders/' . $order->get_transaction_id();
391 } catch ( Throwable $e ) {
392 $this->logger->log_throwable( $e, __FUNCTION__, __CLASS__ );
393 return null;
394 }
395 }
396
397 /**
398 * Get the total amount of the order
399 *
400 * @param WC_Order $order
401 * @return float|int
402 */
403 public function get_total( $order, $in_cents = true ) {
404 $total = 0;
405 if ( $order instanceof WC_Order ) {
406 $total = (float) $order->get_total( 'edit' );
407 }
408 return $in_cents ? $this->pricing_service->to_cents( $total ) : $total;
409 }
410
411 /**
412 * Cleanup orders
413 *
414 * @return void
415 */
416 public function cleanup_orders() {
417 $this->deletable_repository->delete_old_and_invalid();
418 }
419
420 /**
421 * Get the Merchant ID
422 *
423 * @param WC_Order $order
424 * @return string|null
425 */
426 public function get_merchant_id( $order ) {
427 if ( ! $order instanceof WC_Order ) {
428 return null;
429 }
430
431 $sq_order = $this->sequra_order_repository->getByShopReference( $order->get_id() );
432 return $sq_order ? (string) $sq_order->getMerchant()->getId() : null;
433 }
434
435
436 /**
437 * Check if the migration process is complete
438 *
439 * @return bool True if they are missing indexes, false otherwise
440 */
441 public function is_migration_complete() {
442 return $this->table_migration_repository->is_migration_complete();
443 }
444
445 /**
446 * Execute the migration process
447 */
448 public function migrate_data() {
449 try {
450 $this->table_migration_repository->prepare_tables_for_migration();
451 } catch ( Throwable $e ) {
452 $this->logger->log_throwable( $e, __FUNCTION__, __CLASS__ );
453 return;
454 }
455
456 /**
457 * Filters the start hour for the migration process.
458 *
459 * @since 3.1.2
460 * @return int The hour to start the migration process, default is 2 (2AM).
461 */
462 $from = (int) apply_filters( 'sequra_migration_from', 2 );
463
464 /**
465 * Filters the end hour for the migration process.
466 *
467 * @since 3.1.2
468 * @return int The hour to end the migration process, default is 6 (6AM).
469 */
470 $to = (int) apply_filters( 'sequra_migration_to', 6 );
471
472 if ( ! $this->time_checker_service->is_current_hour_in_range( $from, $to ) ) {
473 return;
474 }
475
476 /**
477 * Filters the batch size for the migration process.
478 *
479 * @since 3.1.2
480 * @return int The number of rows to process in each batch, default is 100.
481 */
482 $batch_size = (int) apply_filters( 'sequra_migration_batch_size', 100 );
483 for ( $i = 0; $i < $batch_size; $i++ ) {
484 $this->table_migration_repository->migrate_next_row();
485 if ( $this->table_migration_repository->maybe_remove_legacy_table() ) {
486 // Migration is complete.
487 break;
488 }
489 }
490 }
491
492 /**
493 * Get the order completion date or current date if not completed.
494 *
495 * @param WC_Order $order
496 * @return string
497 */
498 public function get_order_completion_date( $order ) {
499 $datetime = $order instanceof WC_Order ? $order->get_date_completed() : null;
500 return ( $datetime ?? new WC_DateTime() )->format( 'Y-m-d H:i:s' );
501 }
502 }
503