PluginProbe
MultiSafepay plugin for WooCommerce / trunk
MultiSafepay plugin for WooCommerce vtrunk
6.11.1 6.12.0 6.13.0 6.2.0 6.2.1 6.3.0 6.3.1 6.4.0 6.4.1 6.4.2 6.4.3 6.5.0 6.5.1 6.6.0 6.6.1 6.6.2 6.7.0 6.7.1 6.7.2 6.7.3 6.8.0 6.8.1 6.8.2 6.8.3 6.9.0 All 84 releases
multisafepay / src / Services / ShoppingCartService.php

ShoppingCartService.php in MultiSafepay plugin for WooCommerce trunk, at src/Services/ShoppingCartService.php

303 lines 10.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php declare(strict_types=1);
2
3 namespace MultiSafepay\WooCommerce\Services;
4
5 use MultiSafepay\Api\Transactions\OrderRequest\Arguments\ShoppingCart;
6 use MultiSafepay\Api\Transactions\OrderRequest\Arguments\ShoppingCart\Item as CartItem;
7 use MultiSafepay\Api\Transactions\OrderRequest\Arguments\ShoppingCart\ShippingItem;
8 use MultiSafepay\Exception\InvalidArgumentException;
9 use MultiSafepay\WooCommerce\Utils\Hpos;
10 use MultiSafepay\WooCommerce\Utils\Logger;
11 use MultiSafepay\WooCommerce\Utils\MoneyUtil;
12 use WC_Order;
13 use WC_Order_Item_Fee;
14 use WC_Order_Item_Product;
15 use WC_Order_Item_Shipping;
16 use WC_Order_Item_Coupon;
17 use WC_Tax;
18 use WC_Coupon;
19
20 /**
21 * Class ShoppingCartService
22 *
23 * @package MultiSafepay\WooCommerce\Services
24 */
25 class ShoppingCartService {
26
27 /**
28 * @var Logger
29 */
30 private $logger;
31
32 /**
33 * @param Logger|null $logger
34 */
35 public function __construct( ?Logger $logger = null ) {
36 $this->logger = $logger ?? new Logger();
37 }
38
39 /**
40 * @param WC_Order $order
41 * @param string $currency
42 * @param string|null $gateway_code
43 * @return ShoppingCart
44 * @throws InvalidArgumentException
45 */
46 public function create_shopping_cart( WC_Order $order, string $currency, ?string $gateway_code = '' ): ShoppingCart {
47
48 // If coupon type is percentage, fixed_product type, or fixed_cart, which comes by default in WooCommerce,
49 // then discounted amount is being included at product item level to avoid miscalculations in the tax rates, since WooCommerce is rounding the
50 // taxes related to the discount items according with decimal values defined in WooCommerce settings.
51 $types_of_coupons_not_applied_at_item_level = apply_filters( 'multisafepay_types_of_coupons_not_applied_at_item_level', array( 'smart_coupon' ) );
52
53 $cart_items = array();
54
55 if ( get_option( 'multisafepay_debugmode', false ) ) {
56 $this->logger->log_info( wc_print_r( $order->get_items(), true ) );
57 }
58
59 /** @var WC_Order_Item_Product $item */
60 foreach ( $order->get_items() as $item ) {
61 $cart_items[] = $this->create_cart_item( $item, $currency, $gateway_code );
62 }
63
64 /** @var WC_Order_Item_Shipping $item */
65 foreach ( $order->get_items( 'shipping' ) as $item ) {
66 $cart_items[] = $this->create_shipping_cart_item( $item, $currency, $gateway_code );
67 }
68
69 /** @var WC_Order_Item_Fee $item */
70 foreach ( $order->get_items( 'fee' ) as $item ) {
71 $cart_items[] = $this->create_fee_cart_item( $item, $currency, $gateway_code );
72 }
73
74 /** @var WC_Order_Item_Coupon $item */
75 foreach ( $order->get_items( 'coupon' ) as $item ) {
76 // Only for coupons with discount type not applied at item level
77 // And in specific case of smart_coupons, only when the smart coupon is not being applied before tax calculations
78 if (
79 in_array( ( new WC_Coupon( $item->get_code() ) )->get_discount_type(), $types_of_coupons_not_applied_at_item_level, true ) &&
80 ( get_option( 'woocommerce_smart_coupon_apply_before_tax', 'no' ) !== 'yes' )
81 ) {
82 $cart_items[] = $this->create_coupon_cart_item( $item, $currency );
83 }
84 }
85
86 $shopping_cart = new ShoppingCart( $cart_items );
87
88 if ( get_option( 'multisafepay_debugmode', false ) ) {
89 $this->logger->log_info( wp_json_encode( $shopping_cart->getData() ) );
90 }
91
92 return $shopping_cart;
93
94 }
95
96 /**
97 * @param WC_Order_Item_Product $item
98 * @param string $currency
99 * @param string $gateway_code
100 * @return CartItem
101 */
102 private function create_cart_item( WC_Order_Item_Product $item, string $currency, string $gateway_code ): CartItem {
103 $merchant_item_id = apply_filters( 'multisafepay_merchant_item_id', (string) $item->get_variation_id() ? $item->get_variation_id() : $item->get_product_id(), $item );
104 $product_name = $item->get_name();
105 $product_price = (float) $item->get_subtotal() / (int) $item->get_quantity();
106
107 // If product price without discount get_subtotal() is not the same than product price with discount
108 // Then a percentage coupon has been applied to this item
109 if ( (float) $item->get_subtotal() !== (float) $item->get_total() ) {
110 $discount = (float) $item->get_subtotal() - (float) $item->get_total();
111 // translators: %1$ The currency. %2$ The total amount of the discount per line item
112 $product_name .= sprintf( __( ' - Coupon applied: - %1$s %2$s', 'multisafepay' ), number_format( $discount, 2, '.', '' ), $currency );
113 $product_price = (float) $item->get_total() / (int) $item->get_quantity();
114 }
115
116 $cart_item = new CartItem();
117 return $cart_item->addName( $product_name )
118 ->addQuantity( (int) $item->get_quantity() )
119 ->addMerchantItemId( (string) $merchant_item_id )
120 ->addUnitPrice( MoneyUtil::create_money( $product_price, $currency ) )
121 ->addTaxRate( $this->get_item_tax_rate( $item, $gateway_code ) );
122 }
123
124 /**
125 * Returns the tax rate value applied for an order item.
126 *
127 * @param WC_Order_Item_Product $item
128 * @param string $gateway_code
129 * @return float
130 */
131 private function get_item_tax_rate( WC_Order_Item_Product $item, string $gateway_code ): float {
132 if ( ! wc_tax_enabled() ) {
133 return 0;
134 }
135
136 if ( 'taxable' !== $item->get_tax_status() ) {
137 return 0;
138 }
139
140 if ( $this->is_order_vat_exempt( $item->get_order() ) ) {
141 return 0;
142 }
143
144 $tax_rates = WC_Tax::get_rates( $item->get_tax_class() );
145 switch ( count( $tax_rates ) ) {
146 case 0:
147 $tax_rate = 0;
148 break;
149 case 1:
150 $tax = reset( $tax_rates );
151 $tax_rate = $tax['rate'];
152 break;
153 default:
154 $tax_rate = ( ( wc_get_price_including_tax( $item->get_product() ) / wc_get_price_excluding_tax( $item->get_product() ) ) - 1 ) * 100;
155 break;
156 }
157
158 return $this->rounding_filter( (float) $tax_rate, $gateway_code );
159 }
160
161 /**
162 * @param WC_Order_Item_Shipping $item
163 * @param string $currency
164 * @param string $gateway_code
165 * @return ShippingItem
166 * @throws InvalidArgumentException
167 */
168 private function create_shipping_cart_item( WC_Order_Item_Shipping $item, string $currency, string $gateway_code ): ShippingItem {
169 $cart_item = new ShippingItem();
170 $shipping_method_name = $item->get_method_title();
171 if ( empty( $shipping_method_name ) ) {
172 $shipping_method_name = __( 'Shipping', 'multisafepay' );
173 }
174 return $cart_item->addName( $shipping_method_name )
175 ->addQuantity( 1 )
176 ->addUnitPrice( MoneyUtil::create_money( (float) $item->get_total(), $currency ) )
177 ->addTaxRate( $this->get_shipping_tax_rate( $item, $gateway_code ) );
178 }
179
180 /**
181 * Returns the tax rate value applied for the shipping item.
182 *
183 * @param WC_Order_Item_Shipping $item
184 * @param string $gateway_code
185 * @return float
186 */
187 private function get_shipping_tax_rate( WC_Order_Item_Shipping $item, string $gateway_code ): float {
188 if ( ! wc_tax_enabled() ) {
189 return 0;
190 }
191
192 if ( $this->is_order_vat_exempt( $item->get_order() ) ) {
193 return 0;
194 }
195
196 if ( (float) $item->get_total() === 0.00 ) {
197 return 0;
198 }
199
200 $taxes = $item->get_taxes();
201 if ( empty( $taxes['total'] ) ) {
202 return 0;
203 }
204 $total_tax = array_sum( $taxes['total'] );
205 $tax_rate = ( (float) $total_tax * 100 ) / (float) $item->get_total();
206
207 return $this->rounding_filter( $tax_rate, $gateway_code );
208 }
209
210 /**
211 * @param WC_Order_Item_Fee $item
212 * @param string $currency
213 * @param string $gateway_code
214 * @return CartItem
215 */
216 private function create_fee_cart_item( WC_Order_Item_Fee $item, string $currency, string $gateway_code ): CartItem {
217 $cart_item = new CartItem();
218 return $cart_item->addName( $item->get_name() )
219 ->addQuantity( $item->get_quantity() )
220 ->addMerchantItemId( (string) $item->get_id() )
221 ->addUnitPrice( MoneyUtil::create_money( (float) $item->get_total(), $currency ) )
222 ->addTaxRate( $this->get_fee_tax_rate( $item, $gateway_code ) );
223 }
224
225 /**
226 * Returns the tax rate value applied for a fee item.
227 *
228 * @param WC_Order_Item_Fee $item
229 * @param string $gateway_code
230 * @return float
231 */
232 private function get_fee_tax_rate( WC_Order_Item_Fee $item, string $gateway_code ): float {
233 if ( ! wc_tax_enabled() ) {
234 return 0;
235 }
236
237 if ( $this->is_order_vat_exempt( $item->get_order() ) ) {
238 return 0;
239 }
240
241 if ( (float) $item->get_total() === 0.00 ) {
242 return 0;
243 }
244
245 $taxes = $item->get_taxes();
246
247 if ( empty( $taxes['total'] ) ) {
248 return 0;
249 }
250
251 $total_tax = array_sum( $taxes['total'] );
252 $tax_rate = ( (float) $total_tax * 100 ) / (float) $item->get_total();
253
254 return $this->rounding_filter( $tax_rate, $gateway_code );
255 }
256
257 /**
258 * Returns if order is VAT exempt via WC->Customer->is_vat_exempt
259 *
260 * @param WC_Order $order
261 * @return bool
262 */
263 public function is_order_vat_exempt( WC_Order $order ): bool {
264 return Hpos::get_meta( $order, 'is_vat_exempt' ) === 'yes';
265 }
266
267 /**
268 * @param WC_Order_Item_Coupon $item
269 * @param string $currency
270 *
271 * @return CartItem
272 */
273 public function create_coupon_cart_item( WC_Order_Item_Coupon $item, string $currency ): CartItem {
274 $cart_item = new CartItem();
275 return $cart_item->addName( $item->get_name() )
276 ->addQuantity( $item->get_quantity() )
277 ->addMerchantItemId( (string) $item->get_id() )
278 ->addUnitPrice( MoneyUtil::create_money( (float) -$item->get_discount(), $currency ) )
279 ->addTaxRate( 0 );
280 }
281
282 /**
283 * @param float $tax_rate
284 * @param string $gateway_code
285 * @return float
286 */
287 private function rounding_filter( float $tax_rate, string $gateway_code ): float {
288 if ( 'BILLINK' !== $gateway_code ) {
289 return $tax_rate;
290 }
291
292 $allowed_rates = array( 0, 5, 6, 7, 9, 16, 19, 20, 21 );
293
294 foreach ( $allowed_rates as $rate ) {
295 if ( abs( $tax_rate - $rate ) <= 0.05 ) {
296 return round( $tax_rate );
297 }
298 }
299
300 return $tax_rate;
301 }
302 }
303