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 / Utils / QrCheckoutManager.php

QrCheckoutManager.php in MultiSafepay plugin for WooCommerce trunk, at src/Utils/QrCheckoutManager.php

509 lines 16.9 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\Utils;
4
5 use WC_Cart;
6 use WC_Shipping_Rate;
7 use WC_Validation;
8
9 /**
10 * Class QrCheckoutManager
11 */
12 class QrCheckoutManager {
13
14 /**
15 * @var bool
16 */
17 public $is_validated = false;
18
19 /**
20 * @var array
21 */
22 public $customer_data = array(
23 'billing' => array(),
24 'shipping' => array(),
25 );
26
27 /**
28 * @var array
29 */
30 public $order_data = array();
31
32 /**
33 * @var array
34 */
35 public $posted_data = array();
36
37 /**
38 * Check if all mandatory fields are filled in the checkout to submit a MultiSafepay transaction
39 * using Payment Component with QR code.
40 *
41 * @return bool
42 */
43 public function validate_checkout_fields(): bool {
44 // Verify nonce
45 if ( ! $this->verify_nonce() ) {
46 $this->is_validated = false;
47 return $this->is_validated;
48 }
49
50 // Get form data
51 $this->posted_data = $this->get_posted_data();
52
53 // Get required and extra fields
54 $billing_required_fields = $this->get_required_fields();
55 $billing_extra_fields = $this->get_extra_fields();
56
57 // Determine if shipping to a different address
58 $ship_to_different_address = $this->is_shipping_to_different_address( $this->posted_data );
59
60 // Get shipping fields if necessary
61 $shipping_fields = array();
62 $shipping_required_fields = array();
63
64 if ( $ship_to_different_address ) {
65 $shipping_fields = $this->get_shipping_fields( $billing_required_fields, $billing_extra_fields );
66 $shipping_required_fields = $this->get_shipping_fields( $billing_required_fields, array() );
67 }
68
69 // Combine all fields
70 $all_fields = array_merge( $billing_required_fields, $billing_extra_fields, $shipping_fields );
71
72 // Combine all required fields
73 $all_required_fields = array_merge( $billing_required_fields, $shipping_required_fields );
74
75 // Get order fields
76 $order_fields = $this->get_order_fields();
77
78 // Process and validate fields
79 $this->process_checkout_data( $all_fields, $all_required_fields, $order_fields );
80
81 return $this->is_validated;
82 }
83
84 /**
85 * Get the validated data after validation.
86 *
87 * @return array
88 */
89 public function get_checkout_data(): array {
90 if ( ! $this->is_validated ) {
91 $this->validate_checkout_fields();
92 }
93
94 return array(
95 'customer' => $this->customer_data,
96 'order' => $this->order_data,
97 'cart' => $this->get_cart(),
98 'shipping' => $this->get_shipping(),
99 'coupons' => $this->get_coupons(),
100 'fees' => $this->get_fees(),
101 'other' => $this->get_other(),
102 );
103 }
104
105 /**
106 * Get any other data available in the checkout.
107 *
108 * @return array
109 */
110 public function get_other(): array {
111 $other_data = array();
112
113 // Get the lists of fields we've already processed
114 $already_processed = array_merge(
115 $this->get_required_fields(),
116 $this->get_extra_fields()
117 );
118
119 // Add shipping fields if needed
120 if ( $this->is_shipping_to_different_address( $this->posted_data ) ) {
121 $already_processed = array_merge(
122 $already_processed,
123 $this->get_shipping_fields( $this->get_required_fields(), $this->get_extra_fields() )
124 );
125 }
126
127 // Look for any posted data not already captured
128 foreach ( $this->posted_data as $key => $value ) {
129 // Skip standard fields we've already defined and processed
130 if ( ( strpos( $key, 'billing_' ) === 0 || strpos( $key, 'shipping_' ) === 0 ) &&
131 in_array( $key, $already_processed, true ) ) {
132 continue;
133 }
134
135 // Skip fields already in order_data
136 if ( isset( $this->order_data[ $key ] ) ) {
137 continue;
138 }
139
140 // Skip common WooCommerce fields that shouldn't be included
141 $exclude_fields = array(
142 'nonce',
143 'form_data',
144 '_wp_http_referer',
145 'woocommerce-process-checkout-nonce',
146 'ship_to_different_address',
147 'payment_component_arguments_nonce',
148 );
149
150 if ( in_array( $key, $exclude_fields, true ) ) {
151 continue;
152 }
153
154 // Add any remaining fields to other_data, including custom billing_* or shipping_* fields
155 if ( is_array( $value ) ) {
156 $other_data[ $key ] = array();
157 foreach ( $value as $array_key => $array_value ) {
158 $other_data[ $key ][ $array_key ] = trim( wp_strip_all_tags( wp_unslash( $array_value ) ) );
159 }
160 } else {
161 $other_data[ $key ] = trim( wp_strip_all_tags( wp_unslash( $value ) ) );
162 }
163 }
164
165 return $other_data;
166 }
167
168 /**
169 * Get the cart items.
170 *
171 * @return array
172 */
173 public function get_cart(): array {
174 $cart_items = array();
175
176 /** @var WC_Cart $cart */
177 $cart = WC()->cart;
178
179 $cart->calculate_totals();
180
181 foreach ( $cart->get_cart() as $cart_item_key => $cart_item ) {
182 $cart_items[ $cart_item_key ] = array(
183 'product_id' => $cart_item['product_id'],
184 'variation_id' => $cart_item['variation_id'],
185 'variation' => $cart_item['variation'],
186 'quantity' => $cart_item['quantity'],
187 'line_tax_data' => $cart_item['line_tax_data'],
188 'line_subtotal' => $cart_item['line_subtotal'],
189 'line_subtotal_tax' => $cart_item['line_subtotal_tax'],
190 'line_tax' => $cart_item['line_tax'],
191 'line_total' => $cart_item['line_total'],
192 'data' => $cart_item['data'],
193 );
194 }
195
196 return $cart_items;
197 }
198
199 /**
200 * Get the cart items.
201 *
202 * @return ?WC_Shipping_Rate
203 */
204 public function get_shipping(): ?WC_Shipping_Rate {
205 /** @var WC_Cart $cart */
206 $cart = WC()->cart;
207
208 $cart->calculate_totals();
209
210 if ( $cart->needs_shipping() ) {
211 $shipping = $cart->get_shipping_methods()[0] ?? null;
212 }
213
214 return $shipping ?? null;
215 }
216
217 /**
218 * Get the cart coupons.
219 *
220 * @return array
221 */
222 public function get_coupons(): array {
223 $coupons = array();
224
225 /** @var WC_Cart $cart */
226 $cart = WC()->cart;
227
228 $cart->calculate_totals();
229
230 foreach ( $cart->get_coupons() as $coupon ) {
231 $coupons[] = $coupon;
232 }
233
234 return $coupons;
235 }
236
237 /**
238 * Get the cart fees.
239 *
240 * @return array
241 */
242 public function get_fees(): array {
243 $fees = array();
244
245 /** @var WC_Cart $cart */
246 $cart = WC()->cart;
247
248 $cart->calculate_totals();
249
250 foreach ( $cart->get_fees() as $fee ) {
251 $fees[] = $fee;
252 }
253
254 return $fees;
255 }
256
257 /**
258 * Verify the nonce from the request.
259 *
260 * @return bool
261 */
262 public function verify_nonce(): bool {
263 $payment_component_arguments_nonce = sanitize_key( $_POST['nonce'] ?? '' );
264 return wp_verify_nonce( wp_unslash( $payment_component_arguments_nonce ), 'payment_component_arguments_nonce' ) !== false;
265 }
266
267 /**
268 * Get the posted data from the form.
269 *
270 * @return array
271 */
272 public function get_posted_data(): array {
273 $posted_data = array();
274
275 if ( ! empty( $_POST['form_data'] ) ) {
276 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
277 $form_data = wp_unslash( $_POST['form_data'] );
278 // phpcs:enable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
279 $form_data = wp_kses( $form_data, array() );
280 $form_data = html_entity_decode( $form_data, ENT_QUOTES | ENT_HTML5, 'UTF-8' );
281 parse_str( $form_data, $posted_data );
282 }
283
284 return $posted_data;
285 }
286
287 /**
288 * Get the list of required fields.
289 *
290 * @return array
291 */
292 public function get_required_fields(): array {
293 return array(
294 'billing_first_name',
295 'billing_last_name',
296 'billing_address_1',
297 'billing_city',
298 'billing_postcode',
299 'billing_country',
300 'billing_email',
301 'billing_phone',
302 );
303 }
304
305 /**
306 * Get the list of extra fields.
307 *
308 * @return array
309 */
310 public function get_extra_fields(): array {
311 return array(
312 'billing_company',
313 'billing_address_2',
314 'billing_state',
315 );
316 }
317
318 /**
319 * Get the list of order fields.
320 *
321 * @return array
322 */
323 public function get_order_fields(): array {
324 return array(
325 'payment_method',
326 'shipping_method',
327 'order_comments',
328 'ip_address',
329 'user_agent',
330 'wc_order_attribution_source_type',
331 'wc_order_attribution_referrer',
332 'wc_order_attribution_utm_campaign',
333 'wc_order_attribution_utm_source',
334 'wc_order_attribution_utm_medium',
335 'wc_order_attribution_utm_content',
336 'wc_order_attribution_utm_id',
337 'wc_order_attribution_utm_term',
338 'wc_order_attribution_utm_source_platform',
339 'wc_order_attribution_utm_creative_format',
340 'wc_order_attribution_utm_marketing_tactic',
341 'wc_order_attribution_session_entry',
342 'wc_order_attribution_session_start_time',
343 'wc_order_attribution_session_pages',
344 'wc_order_attribution_session_count',
345 'wc_order_attribution_user_agent',
346 );
347 }
348
349 /**
350 * Check if shipping to a different address.
351 *
352 * @param array $posted_data The posted form data.
353 * @return bool
354 */
355 public function is_shipping_to_different_address( array $posted_data ): bool {
356 return isset( $posted_data['ship_to_different_address'] ) &&
357 filter_var( $posted_data['ship_to_different_address'], FILTER_VALIDATE_BOOLEAN );
358 }
359
360 /**
361 * Get the shipping fields based on required and extra fields.
362 *
363 * @param array $billing_required_fields The required fields.
364 * @param array $billing_extra_fields The extra fields.
365 * @return array
366 */
367 public function get_shipping_fields( array $billing_required_fields, array $billing_extra_fields ): array {
368 return array_map(
369 static function( $field ) {
370 return str_replace( 'billing_', 'shipping_', $field );
371 },
372 array_filter(
373 array_merge( $billing_required_fields, $billing_extra_fields ),
374 static function( $field ) {
375 // Exclude email and phone fields to be created as shipping fields.
376 return ! in_array( $field, array( 'billing_email', 'billing_phone' ), true );
377 }
378 )
379 );
380 }
381
382 /**
383 * Process customer and order fields from the posted data.
384 *
385 * @param array $all_fields All fields to check.
386 * @param array $all_required_fields The required fields.
387 * @param array $order_fields The order fields.
388 */
389 public function process_checkout_data( array $all_fields, array $all_required_fields, array $order_fields ): void {
390 $this->is_validated = true;
391
392 // Process customer fields (billing and shipping)
393 foreach ( $all_fields as $field ) {
394 if ( 'billing_email' === $field ) {
395 $field_value = isset( $this->posted_data[ $field ] ) ? sanitize_email( wp_unslash( $this->posted_data[ $field ] ) ) : '';
396
397 // Verify the email format using PHP's built-in filter validation
398 if ( ! empty( $field_value ) && ! $this->validate_email( $field_value ) ) {
399 $this->is_validated = false;
400 }
401 } elseif ( strpos( $field, '_postcode' ) !== false ) {
402 $field_value = isset( $this->posted_data[ $field ] ) ? wp_unslash( $this->posted_data[ $field ] ) : '';
403 $field_value = trim( wp_strip_all_tags( $field_value ) );
404
405 // Validate a postcode format if not empty
406 if ( ! empty( $field_value ) ) {
407 $prefix = strpos( $field, 'billing_' ) === 0 ? 'billing' : 'shipping';
408 $country = isset( $this->posted_data[ $prefix . '_country' ] ) ? wp_unslash( $this->posted_data[ $prefix . '_country' ] ) : '';
409 $country = trim( wp_strip_all_tags( $country ) );
410
411 if ( ! $this->validate_postcode( $field_value, $country ) ) {
412 $this->is_validated = false;
413 }
414 }
415 } else {
416 $field_value = isset( $this->posted_data[ $field ] ) ? wp_unslash( $this->posted_data[ $field ] ) : '';
417 $field_value = trim( wp_strip_all_tags( $field_value ) );
418 }
419
420 // Check if the required field is empty
421 if ( empty( $field_value ) && in_array( $field, $all_required_fields, true ) ) {
422 $this->is_validated = false;
423 }
424
425 // Organize data into customer billing or shipping
426 if ( strpos( $field, 'billing_' ) === 0 ) {
427 $field_key = str_replace( 'billing_', '', $field );
428 $this->customer_data['billing'][ $field_key ] = $field_value;
429 } elseif ( strpos( $field, 'shipping_' ) === 0 ) {
430 $field_key = str_replace( 'shipping_', '', $field );
431 $this->customer_data['shipping'][ $field_key ] = $field_value;
432 }
433 }
434
435 // Process order fields
436 foreach ( $order_fields as $field ) {
437 // Special handling for ip_address
438 if ( 'ip_address' === $field ) {
439 $this->order_data['ip_address'] = ( new QrOrder() )->get_customer_ip_address() ?? '';
440 continue;
441 }
442 // Special handling for user_agent
443 if ( 'user_agent' === $field ) {
444 $this->order_data['user_agent'] = ( new QrOrder() )->get_user_agent() ?? '';
445 continue;
446 }
447 // Process direct fields
448 if ( isset( $this->posted_data[ $field ] ) && ! is_array( $this->posted_data[ $field ] ) ) {
449 $this->order_data[ $field ] = trim( wp_strip_all_tags( wp_unslash( $this->posted_data[ $field ] ) ) );
450 } elseif ( isset( $this->posted_data[ $field ] ) && is_array( $this->posted_data[ $field ] ) ) {
451 $this->order_data[ $field ] = array();
452 foreach ( $this->posted_data[ $field ] as $key => $value ) {
453 $this->order_data[ $field ][ $key ] = trim( wp_strip_all_tags( wp_unslash( $value ) ) );
454 }
455 }
456 }
457
458 // Process any additional fields from posted_data
459 foreach ( $this->posted_data as $key => $value ) {
460 // Skip already processed customer fields
461 if ( strpos( $key, 'billing_' ) === 0 || strpos( $key, 'shipping_' ) === 0 ) {
462 continue;
463 }
464
465 // Skip already processed order fields
466 if ( isset( $this->order_data[ $key ] ) ) {
467 continue;
468 }
469
470 // Add any additional relevant fields to order data
471 if ( in_array( $key, $order_fields, true ) ) {
472 if ( is_array( $value ) ) {
473 $this->order_data[ $key ] = array();
474 foreach ( $value as $array_key => $array_value ) {
475 $this->order_data[ $key ][ $array_key ] = trim( wp_strip_all_tags( wp_unslash( $array_value ) ) );
476 }
477 } else {
478 $this->order_data[ $key ] = trim( wp_strip_all_tags( wp_unslash( $value ) ) );
479 }
480 }
481 }
482 }
483
484 /**
485 * Validate the email address format
486 *
487 * @param string $email The email to validate
488 * @return bool Whether the email is valid
489 */
490 private function validate_email( string $email ): bool {
491 return (bool) filter_var( $email, FILTER_VALIDATE_EMAIL );
492 }
493
494 /**
495 * Validate a postcode format using WooCommerce's validation
496 *
497 * @param string $postcode The postcode to validate
498 * @param string $country The country code
499 * @return bool Whether the postcode is valid
500 */
501 private function validate_postcode( string $postcode, string $country ): bool {
502 if ( ! WC_Validation::is_postcode( $postcode, $country ) ) {
503 return false;
504 }
505
506 return true;
507 }
508 }
509