PluginProbe
Packeta / trunk
Packeta vtrunk
2.3.2 2.3.1 trunk 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.3.0 1.3.1 1.3.2 1.4 1.4.1 1.4.2 1.4.3 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 All 56 releases
packeta / src / Packetery / Module / Checkout / Checkout.php

Checkout.php in Packeta trunk, at src/Packetery/Module/Checkout/Checkout.php

472 lines 14.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 declare( strict_types=1 );
4
5 namespace Packetery\Module\Checkout;
6
7 use Packetery\Core\Entity;
8 use Packetery\Module\Carrier;
9 use Packetery\Module\Carrier\CarrierOptionsFactory;
10 use Packetery\Module\DiagnosticsLogger\DiagnosticsLogger;
11 use Packetery\Module\Exception\ProductNotFoundException;
12 use Packetery\Module\Framework\WcAdapter;
13 use Packetery\Module\Framework\WpAdapter;
14 use Packetery\Module\Options\OptionsProvider;
15 use Packetery\Module\Order;
16 use Packetery\Module\Payment\PaymentHelper;
17 use Packetery\Module\WcLogger;
18 use WC_Cart;
19 use WC_Payment_Gateway;
20
21 class Checkout {
22
23 /**
24 * @var WpAdapter
25 */
26 private $wpAdapter;
27
28 /**
29 * @var WcAdapter
30 */
31 private $wcAdapter;
32
33 /**
34 * @var CarrierOptionsFactory
35 */
36 private $carrierOptionsFactory;
37
38 /**
39 * @var OptionsProvider
40 */
41 private $optionsProvider;
42
43 /**
44 * @var Order\Repository
45 */
46 private $orderRepository;
47
48 /**
49 * @var CurrencySwitcherService
50 */
51 private $currencySwitcherService;
52
53 /**
54 * @var RateCalculator
55 */
56 private $rateCalculator;
57
58 /**
59 * @var Carrier\EntityRepository
60 */
61 private $carrierEntityRepository;
62
63 /**
64 * @var PaymentHelper
65 */
66 private $paymentHelper;
67
68 /**
69 * @var CheckoutService
70 */
71 private $checkoutService;
72
73 /**
74 * @var CheckoutRenderer
75 */
76 private $renderer;
77
78 /**
79 * @var CartService
80 */
81 private $cartService;
82
83 /**
84 * @var SessionService
85 */
86 private $sessionService;
87
88 /**
89 * @var CheckoutValidator
90 */
91 private $validator;
92
93 /**
94 * @var OrderUpdater
95 */
96 private $orderUpdater;
97
98 /**
99 * @var DiagnosticsLogger
100 */
101 private $diagnosticsLogger;
102
103 public function __construct(
104 WpAdapter $wpAdapter,
105 WcAdapter $wcAdapter,
106 CarrierOptionsFactory $carrierOptionsFactory,
107 OptionsProvider $optionsProvider,
108 Order\Repository $orderRepository,
109 CurrencySwitcherService $currencySwitcherService,
110 RateCalculator $rateCalculator,
111 Carrier\EntityRepository $carrierEntityRepository,
112 PaymentHelper $paymentHelper,
113 CheckoutService $checkoutService,
114 CheckoutRenderer $renderer,
115 CartService $cartService,
116 SessionService $sessionService,
117 CheckoutValidator $validator,
118 OrderUpdater $orderUpdater,
119 DiagnosticsLogger $diagnosticsLogger
120 ) {
121 $this->wpAdapter = $wpAdapter;
122 $this->wcAdapter = $wcAdapter;
123 $this->carrierOptionsFactory = $carrierOptionsFactory;
124 $this->optionsProvider = $optionsProvider;
125 $this->orderRepository = $orderRepository;
126 $this->currencySwitcherService = $currencySwitcherService;
127 $this->rateCalculator = $rateCalculator;
128 $this->carrierEntityRepository = $carrierEntityRepository;
129 $this->paymentHelper = $paymentHelper;
130 $this->checkoutService = $checkoutService;
131 $this->renderer = $renderer;
132 $this->cartService = $cartService;
133 $this->sessionService = $sessionService;
134 $this->validator = $validator;
135 $this->orderUpdater = $orderUpdater;
136 $this->diagnosticsLogger = $diagnosticsLogger;
137 }
138
139 public function registerHooks(): void {
140 // This action works for both classic and Divi templates.
141 $this->wpAdapter->addAction(
142 'woocommerce_review_order_before_submit',
143 [
144 $this->renderer,
145 'actionRenderHiddenInputFields',
146 ]
147 );
148
149 $this->wpAdapter->addAction( 'woocommerce_after_checkout_validation', [ $this->validator, 'actionValidateCheckoutData' ], 10, 2 );
150 // Provides following parameters: \Automattic\WooCommerce\Admin\Overrides\Order, WP_REST_Request
151 $this->wpAdapter->addAction(
152 'woocommerce_store_api_checkout_update_order_from_request',
153 [ $this->validator, 'actionValidateBlockCheckoutData' ]
154 );
155
156 $this->wpAdapter->addAction( 'woocommerce_checkout_update_order_meta', [ $this->orderUpdater, 'actionUpdateOrderById' ] );
157 $this->wpAdapter->addAction(
158 'woocommerce_store_api_checkout_order_processed',
159 [
160 $this->orderUpdater,
161 'actionUpdateOrder',
162 ]
163 );
164
165 // Must not be registered at backend.
166 $this->wpAdapter->addFilter( 'woocommerce_available_payment_gateways', [ $this, 'filterPaymentGateways' ] );
167
168 $this->wpAdapter->addAction(
169 'woocommerce_review_order_before_shipping',
170 [
171 $this->sessionService,
172 'actionUpdateShippingRates',
173 ]
174 );
175 $this->wpAdapter->addFilter(
176 'woocommerce_cart_shipping_packages',
177 [
178 $this->sessionService,
179 'filterUpdateShippingPackages',
180 ]
181 );
182 $this->wpAdapter->addAction(
183 'init',
184 function () {
185 /**
186 * Tells if widget button table row should be used.
187 *
188 * @since 1.3.0
189 */
190 if ( $this->optionsProvider->getCheckoutWidgetButtonLocation() === 'after_transport_methods' ) {
191 $this->wpAdapter->addAction(
192 'woocommerce_review_order_after_shipping',
193 [
194 $this->renderer,
195 'actionRenderWidgetButtonTableRow',
196 ]
197 );
198 } else {
199 $this->wpAdapter->addAction(
200 'woocommerce_after_shipping_rate',
201 [
202 $this->renderer,
203 'actionRenderWidgetButtonAfterShippingRate',
204 ]
205 );
206 }
207 }
208 );
209
210 $this->wpAdapter->addAction(
211 'woocommerce_review_order_after_shipping',
212 [
213 $this->renderer,
214 'actionRenderEstimatedDeliveryDateSection',
215 ]
216 );
217 }
218
219 /**
220 * Calculates fees.
221 *
222 * @return void
223 * @throws ProductNotFoundException Product not found.
224 */
225 public function actionCalculateFees( WC_Cart $cart ): void {
226 $chosenShippingMethodOptionId = $this->checkoutService->calculateShippingAndGetOptionId();
227 $isPacketeryShippingMethod = $this->checkoutService->isPacketeryShippingMethod( (string) $chosenShippingMethodOptionId );
228
229 if (
230 $chosenShippingMethodOptionId === null ||
231 $isPacketeryShippingMethod === false
232 ) {
233 $this->diagnosticsLogger->log(
234 'No packetery shipping method chosen',
235 [
236 'chosenShippingMethodOptionId' => $chosenShippingMethodOptionId,
237 'isPacketeryShippingMethod' => $isPacketeryShippingMethod,
238 ]
239 );
240
241 return;
242 }
243
244 $carrierOptions = $this->carrierOptionsFactory->createByOptionId( $chosenShippingMethodOptionId );
245 $chosenCarrier = $this->carrierEntityRepository->getAnyById(
246 $this->checkoutService->getCarrierIdFromPacketeryShippingMethod( $chosenShippingMethodOptionId )
247 );
248 $maxTaxClass = $this->cartService->getTaxClassWithMaxRate();
249 $isTaxable = $maxTaxClass !== null;
250 $hasCouponFreeShippingForFeesAllowed = $carrierOptions->hasCouponFreeShippingForFeesAllowed();
251 $wcAdapterCart = $this->wcAdapter->cart();
252 $isFreeShippingCouponApplied = $this->rateCalculator->isFreeShippingCouponApplied( $wcAdapterCart );
253
254 $this->diagnosticsLogger->log(
255 'Coupon free shipping for fees parameters',
256 [
257 'hasCouponFreeShippingForFeesAllowed' => $hasCouponFreeShippingForFeesAllowed,
258 'isFreeShippingCouponApplied' => $isFreeShippingCouponApplied,
259 'wcAdapterCart' => $wcAdapterCart,
260 ]
261 );
262 if ( $hasCouponFreeShippingForFeesAllowed === true && $isFreeShippingCouponApplied === true ) {
263 return;
264 }
265
266 $this->diagnosticsLogger->log(
267 'Coupon free shipping for fees is not allowed',
268 [
269 'chosenShippingMethodOptionId' => $chosenShippingMethodOptionId,
270 'hasCouponFreeShippingForFeesAllowed' => $hasCouponFreeShippingForFeesAllowed,
271 'isFreeShippingCouponApplied' => $isFreeShippingCouponApplied,
272 'chosenCarrier' => $chosenCarrier,
273 'carrierOptions' => $carrierOptions,
274 'maxTaxClass' => $maxTaxClass,
275 'isTaxable' => $isTaxable,
276 'cart' => $cart,
277 ]
278 );
279
280 $this->addAgeVerificationFee( $cart, $chosenCarrier, $carrierOptions, $isTaxable, $maxTaxClass );
281 $this->addCodSurchargeFee( $cart, $carrierOptions, $isTaxable, $maxTaxClass );
282 }
283
284 private function addAgeVerificationFee( WC_Cart $cart, ?Entity\Carrier $chosenCarrier, Carrier\Options $carrierOptions, bool $isTaxable, ?string $maxTaxClass ): void {
285 $isAgeVerificationRequired = $this->cartService->isAgeVerificationRequired();
286 $ageVerificationFee = $carrierOptions->getAgeVerificationFee();
287 $this->diagnosticsLogger->log(
288 'Age verification parameters',
289 [
290 'chosenCarrier' => $chosenCarrier,
291 'ageVerificationFee' => $ageVerificationFee,
292 'isAgeVerificationRequired' => $isAgeVerificationRequired,
293 'isTaxable' => $isTaxable,
294 'maxTaxClass' => $maxTaxClass,
295 ]
296 );
297 if (
298 $chosenCarrier === null ||
299 ! $chosenCarrier->supportsAgeVerification() ||
300 $ageVerificationFee === null ||
301 $isAgeVerificationRequired === false
302 ) {
303 $this->diagnosticsLogger->log( 'Age verification fee is not added', [] );
304
305 return;
306 }
307 $feeAmount = $this->currencySwitcherService->getConvertedPrice( $ageVerificationFee );
308 $this->diagnosticsLogger->log( 'Age verification converted price is added', [ 'feeAmount' => $feeAmount ] );
309
310 if ( $isTaxable && $feeAmount > 0 && $this->optionsProvider->arePricesTaxInclusive() ) {
311 $feeAmount = $this->calcTaxExclusiveFeeAmount( $feeAmount, $maxTaxClass );
312 $this->diagnosticsLogger->log( 'Age verification tax exclusive fee amount is added', [ 'feeAmount' => $feeAmount ] );
313 }
314 $cart->add_fee( $this->wpAdapter->__( 'Age verification fee', 'packeta' ), $feeAmount, $isTaxable, $maxTaxClass );
315 }
316
317 private function addCodSurchargeFee( WC_Cart $cart, Carrier\Options $carrierOptions, bool $isTaxable, ?string $maxTaxClass ): void {
318 if ( $this->checkoutService->areBlocksUsedInCheckout() ) {
319 $paymentMethod = $this->wcAdapter->sessionGetString( 'packetery_checkout_payment_method' );
320 } else {
321 $paymentMethod = $this->sessionService->getChosenPaymentMethod();
322 }
323
324 $isCodPaymentMethod = $this->paymentHelper->isCodPaymentMethod( (string) $paymentMethod );
325 $this->diagnosticsLogger->log(
326 'COD surcharge parameters',
327 [
328 'paymentMethod' => $paymentMethod,
329 'isCodPaymentMethod' => $isCodPaymentMethod,
330 ]
331 );
332 if ( $paymentMethod === null || $isCodPaymentMethod === false ) {
333 return;
334 }
335
336 $applicableSurcharge = $this->rateCalculator->getCODSurcharge(
337 $carrierOptions->toArray(),
338 $this->wcAdapter->cartGetSubtotal()
339 );
340 $this->diagnosticsLogger->log( 'Get COD surcharge', [ 'applicableSurcharge' => $applicableSurcharge ] );
341 $applicableSurcharge = $this->currencySwitcherService->getConvertedPrice( $applicableSurcharge );
342 $this->diagnosticsLogger->log( 'Get COD surcharge converted', [ 'applicableSurcharge' => $applicableSurcharge ] );
343 if ( $applicableSurcharge <= 0 ) {
344 return;
345 }
346
347 if ( $isTaxable && $this->optionsProvider->arePricesTaxInclusive() ) {
348 $applicableSurcharge = $this->calcTaxExclusiveFeeAmount( $applicableSurcharge, $maxTaxClass );
349 $this->diagnosticsLogger->log( 'Get COD surcharge tax exclusive', [ 'applicableSurcharge' => $applicableSurcharge ] );
350 }
351
352 $cart->add_fee( $this->wpAdapter->__( 'COD surcharge', 'packeta' ), $applicableSurcharge, $isTaxable, $maxTaxClass );
353 }
354
355 /**
356 * Calculates tax exclusive fee amount.
357 *
358 * @param float $taxInclusiveFeeAmount Tax inclusive fee amount.
359 * @param string $taxClass Related tax class.
360 *
361 * @return float
362 */
363 private function calcTaxExclusiveFeeAmount( float $taxInclusiveFeeAmount, string $taxClass ): float {
364 return $taxInclusiveFeeAmount - array_sum(
365 $this->wcAdapter->calcTax(
366 $taxInclusiveFeeAmount,
367 $this->wcAdapter->taxGetRates( $taxClass ),
368 true
369 )
370 );
371 }
372
373 /**
374 * Filters out payment methods, that can not be used.
375 *
376 * @param WC_Payment_Gateway[]|mixed $availableGateways Available gateways.
377 *
378 * @return WC_Payment_Gateway[]|mixed
379 */
380 public function filterPaymentGateways( $availableGateways ) {
381 $this->diagnosticsLogger->log(
382 'Payment gateways for filtering',
383 [
384 'availableGateways' => $availableGateways,
385 ]
386 );
387 if ( ! is_array( $availableGateways ) ) {
388 WcLogger::logArgumentTypeError( __METHOD__, 'availableGateways', 'array', $availableGateways );
389
390 return $availableGateways;
391 }
392
393 $order = null;
394 $wpOrderPay = $this->checkoutService->getOrderPayParameter();
395 if ( is_numeric( $wpOrderPay ) ) {
396 $order = $this->orderRepository->getByIdWithValidCarrier( (int) $wpOrderPay );
397 $this->diagnosticsLogger->log(
398 'Order found by order pay parameter',
399 [
400 'wpOrderPay' => $wpOrderPay,
401 'order' => $order,
402 ]
403 );
404 }
405
406 if ( $order instanceof Entity\Order ) {
407 $chosenMethod = Carrier\OptionPrefixer::getOptionId( $order->getCarrier()->getId() );
408 } else {
409 $chosenMethod = $this->sessionService->getChosenMethodFromSession();
410 }
411
412 $isPacketeryShippingMethod = $this->checkoutService->isPacketeryShippingMethod( $chosenMethod );
413 $this->diagnosticsLogger->log(
414 'Chosen method',
415 [
416 'chosenMethod' => $chosenMethod,
417 'isPacketeryShippingMethod' => $isPacketeryShippingMethod,
418 ]
419 );
420 if ( $isPacketeryShippingMethod === false ) {
421 return $availableGateways;
422 }
423
424 $carrierId = $this->checkoutService->getCarrierIdFromPacketeryShippingMethod( $chosenMethod );
425 $carrier = $this->carrierEntityRepository->getAnyById( $carrierId );
426 $this->diagnosticsLogger->log(
427 'Carrier for filtering gateways',
428 [
429 'carrierId' => $carrierId,
430 'carrier' => $carrier,
431 ]
432 );
433 if ( $carrier === null ) {
434 return $availableGateways;
435 }
436
437 $carrierOptions = $this->carrierOptionsFactory->createByCarrierId( $carrierId );
438 foreach ( $availableGateways as $key => $availableGateway ) {
439 if ( ! $availableGateway instanceof WC_Payment_Gateway ) {
440 WcLogger::logArgumentTypeError( __METHOD__, 'availableGateway', 'WC_Payment_Gateway', $availableGateway );
441
442 continue;
443 }
444
445 $isCodPaymentMethod = $this->paymentHelper->isCodPaymentMethod( $availableGateway->id );
446 $supportsCod = $carrier->supportsCod();
447 $hasCheckoutPaymentMethodDisallowed = $carrierOptions->hasCheckoutPaymentMethodDisallowed( $availableGateway->id );
448 $this->diagnosticsLogger->log(
449 'Payment method filtering parameters',
450 [
451 'availableGateway' => $availableGateway,
452 'isCodPaymentMethod' => $isCodPaymentMethod,
453 'supportsCod' => $supportsCod,
454 'hasCheckoutPaymentMethodDisallowed' => $hasCheckoutPaymentMethodDisallowed,
455 ]
456 );
457
458 if ( $isCodPaymentMethod === true && $supportsCod === false ) {
459 unset( $availableGateways[ $key ] );
460 }
461
462 if ( $hasCheckoutPaymentMethodDisallowed === true ) {
463 unset( $availableGateways[ $key ] );
464 }
465 }
466
467 $this->diagnosticsLogger->log( 'Filtered payment methods', [ 'availableGateways' => $availableGateways ] );
468
469 return $availableGateways;
470 }
471 }
472