From 52064e314cd5e9e88c45b76a91b7d425d93260e5 Mon Sep 17 00:00:00 2001 From: Montazar Date: Mon, 24 Jun 2024 12:46:09 +0200 Subject: [PATCH 1/8] Validate cart content --- classes/class-kco-api-callbacks.php | 4 +-- includes/kco-functions.php | 47 ++++++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/classes/class-kco-api-callbacks.php b/classes/class-kco-api-callbacks.php index de68ff45..c447893c 100644 --- a/classes/class-kco-api-callbacks.php +++ b/classes/class-kco-api-callbacks.php @@ -84,12 +84,12 @@ public function push_cb() { KCO_WC()->api->get_klarna_om_order( $klarna_order_id ) ); - if( is_wp_error( $klarna_order ) ) { + if ( is_wp_error( $klarna_order ) ) { KCO_WC()->logger->log( 'ERROR Push callback failed to get Klarna order data for Klarna order ID ' . stripslashes_deep( wp_json_encode( $klarna_order_id ) ) ); return; } - if ( ! kco_validate_order_total( $klarna_order, $order ) ) { + if ( ! kco_validate_order_total( $klarna_order, $order ) || ! kco_validate_cart_content( $klarna_order, $order ) ) { return; } diff --git a/includes/kco-functions.php b/includes/kco-functions.php index 9daf9663..0e5fcb7b 100644 --- a/includes/kco-functions.php +++ b/includes/kco-functions.php @@ -589,7 +589,7 @@ function kco_confirm_klarna_order( $order_id = null, $klarna_order_id = null ) { $klarna_order = KCO_WC()->api->get_klarna_om_order( $klarna_order_id ); if ( ! is_wp_error( $klarna_order ) ) { - if ( ! kco_validate_order_total( $klarna_order, $order ) ) { + if ( ! kco_validate_order_total( $klarna_order, $order ) || ! kco_validate_cart_content( $klarna_order, $order ) ) { return; } @@ -680,6 +680,51 @@ function kco_validate_order_total( $klarna_order, $order ) { return true; } +/** + * Validate the Klarna Checkout item quantities against the Woo order. + * + * @param array $klarna_order The Klarna order. + * @param WC_Order $order The Woo order. + * + * @return bool + */ +function kco_validate_cart_content( $klarna_order, $order ) { + foreach ( $order->get_items() as $item ) { + // Only check quantity for product items. + if ( 'line_item' !== $item->get_type() ) { + continue; + } + + // Assume the item is a mismatch until proven otherwise. This way, in the unlikely chance of not finding any matching item, we can set the order to on-hold. + $mismatch = true; + + // Try the specific (variation id) before the general (product id). + $product_id = 0 !== $item->get_variation_id() ? $item->get_variation_id() : $item->get_product_id(); + $product = wc_get_product( $product_id ); + $sku = strval( $product->get_sku() ?? $product->get_id() ); + foreach ( $klarna_order['order_lines'] as $klarna_order_item ) { + if ( $klarna_order_item['reference'] === $sku ) { + $mismatch = $klarna_order_item['quantity'] !== $item->get_quantity(); + } + } + + if ( $mismatch ) { + KCO_Logger::log( 'Order item quantity mismatch. Klarna Order item quantity: ' . $klarna_order_item['quantity'] . ' WC Order item quantity: ' . $item->get_quantity() . ' Klarna order ID: ' . $klarna_order['order_id'] . ' WC Order ID: ' . $order->get_id() ); + + $order->set_status( + 'on-hold', + sprintf( + __( 'Klarna order item quantity does not match WooCommerce order item quantity. Please verify the order with Klarna before processing.', 'klarna-checkout-for-woocommerce' ) + ) + ); + $order->save(); + return false; + } + } + + return true; +} + /** * Converts a region string to the expected country code format for WooCommerce. * From 2d31c05935e827c20748053ebd84500cb0bd34fe Mon Sep 17 00:00:00 2001 From: Montazar Date: Fri, 28 Jun 2024 11:52:50 +0200 Subject: [PATCH 2/8] Retrieve all order lines from Woo order --- includes/kco-functions.php | 53 +++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/includes/kco-functions.php b/includes/kco-functions.php index 0e5fcb7b..ea18c8ae 100644 --- a/includes/kco-functions.php +++ b/includes/kco-functions.php @@ -681,7 +681,7 @@ function kco_validate_order_total( $klarna_order, $order ) { } /** - * Validate the Klarna Checkout item quantities against the Woo order. + * Validate that the Woo order matches the corresponding Klarna order. * * @param array $klarna_order The Klarna order. * @param WC_Order $order The Woo order. @@ -689,37 +689,38 @@ function kco_validate_order_total( $klarna_order, $order ) { * @return bool */ function kco_validate_cart_content( $klarna_order, $order ) { - foreach ( $order->get_items() as $item ) { - // Only check quantity for product items. - if ( 'line_item' !== $item->get_type() ) { - continue; - } + $order_data = new KCO_Request_Order(); - // Assume the item is a mismatch until proven otherwise. This way, in the unlikely chance of not finding any matching item, we can set the order to on-hold. - $mismatch = true; + $mismatch = false; + foreach ( $order_data->get_order_lines( $order->get_id() ) as $order_line ) { + if ( $mismatch ) { + break; + } - // Try the specific (variation id) before the general (product id). - $product_id = 0 !== $item->get_variation_id() ? $item->get_variation_id() : $item->get_product_id(); - $product = wc_get_product( $product_id ); - $sku = strval( $product->get_sku() ?? $product->get_id() ); + $reference = $order_line['reference']; foreach ( $klarna_order['order_lines'] as $klarna_order_item ) { - if ( $klarna_order_item['reference'] === $sku ) { - $mismatch = $klarna_order_item['quantity'] !== $item->get_quantity(); + if ( $reference === $klarna_order_item['reference'] ) { + foreach ( $order_line as $key => $value ) { + if ( $klarna_order_item[ $key ] !== $value ) { + $mismatch = true; + break; + } + } } } + } - if ( $mismatch ) { - KCO_Logger::log( 'Order item quantity mismatch. Klarna Order item quantity: ' . $klarna_order_item['quantity'] . ' WC Order item quantity: ' . $item->get_quantity() . ' Klarna order ID: ' . $klarna_order['order_id'] . ' WC Order ID: ' . $order->get_id() ); - - $order->set_status( - 'on-hold', - sprintf( - __( 'Klarna order item quantity does not match WooCommerce order item quantity. Please verify the order with Klarna before processing.', 'klarna-checkout-for-woocommerce' ) - ) - ); - $order->save(); - return false; - } + if ( $mismatch ) { + KCO_Logger::log( 'The Klarna and Woo orders do not match. Klarna order ID: ' . $klarna_order['order_id'] . ' WC Order ID: ' . $order->get_id() ); + + $order->set_status( + 'on-hold', + sprintf( + __( 'A mismatch between the WooCommerce and Klarna orders was identified. Please verify the order with Klarna before processing.', 'klarna-checkout-for-woocommerce' ) + ) + ); + $order->save(); + return false; } return true; From bfefd63d529c1e08ac1a447e7bacd44e5cd169d7 Mon Sep 17 00:00:00 2001 From: Montazar Date: Tue, 2 Jul 2024 21:51:47 +0200 Subject: [PATCH 3/8] Verify item quantity and shipping reference --- classes/class-kco-api-callbacks.php | 2 +- includes/kco-functions.php | 39 ++++++++++++++++++++++------- 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/classes/class-kco-api-callbacks.php b/classes/class-kco-api-callbacks.php index c447893c..e1afe122 100644 --- a/classes/class-kco-api-callbacks.php +++ b/classes/class-kco-api-callbacks.php @@ -89,7 +89,7 @@ public function push_cb() { return; } - if ( ! kco_validate_order_total( $klarna_order, $order ) || ! kco_validate_cart_content( $klarna_order, $order ) ) { + if ( ! kco_validate_order_total( $klarna_order, $order ) || ! kco_validate_order_content( $klarna_order, $order ) ) { return; } diff --git a/includes/kco-functions.php b/includes/kco-functions.php index ea18c8ae..dc42d5da 100644 --- a/includes/kco-functions.php +++ b/includes/kco-functions.php @@ -589,7 +589,7 @@ function kco_confirm_klarna_order( $order_id = null, $klarna_order_id = null ) { $klarna_order = KCO_WC()->api->get_klarna_om_order( $klarna_order_id ); if ( ! is_wp_error( $klarna_order ) ) { - if ( ! kco_validate_order_total( $klarna_order, $order ) || ! kco_validate_cart_content( $klarna_order, $order ) ) { + if ( ! kco_validate_order_total( $klarna_order, $order ) || ! kco_validate_order_content( $klarna_order, $order ) ) { return; } @@ -688,26 +688,47 @@ function kco_validate_order_total( $klarna_order, $order ) { * * @return bool */ -function kco_validate_cart_content( $klarna_order, $order ) { +function kco_validate_order_content( $klarna_order, $order ) { $order_data = new KCO_Request_Order(); $mismatch = false; - foreach ( $order_data->get_order_lines( $order->get_id() ) as $order_line ) { + foreach ( $order->get_items() as $order_item ) { + $order_line = $order_data->get_order_line_items( $order_item ); if ( $mismatch ) { break; } + $match = false; $reference = $order_line['reference']; foreach ( $klarna_order['order_lines'] as $klarna_order_item ) { + $match = false; if ( $reference === $klarna_order_item['reference'] ) { - foreach ( $order_line as $key => $value ) { - if ( $klarna_order_item[ $key ] !== $value ) { - $mismatch = true; - break; - } + $match = true; + if ( $klarna_order_item['quantity'] !== $order_line['quantity'] ) { + $mismatch = true; } + + break; } } + + // Check if the Woo item was not found in the Klarna order. + if ( ! $match ) { + $mismatch = true; + } + } + + $shipping = $order_data->get_order_line_shipping( $order ); + $klarna_shipping = array_filter( + $klarna_order['order_lines'], + function ( $order_line ) { + return 'shipping_fee' === $order_line['type']; + } + ); + $klarna_shipping = reset( $klarna_shipping ); + + if ( ( ! empty( $shipping ) && empty( $klarna_shipping ) ) || ( ! empty( $klarna_shipping ) && empty( $shipping ) ) || ( ! empty( $shipping ) && ! empty( $klarna_shipping ) && $shipping['reference'] !== $klarna_shipping['reference'] ) ) { + $mismatch = true; } if ( $mismatch ) { @@ -716,7 +737,7 @@ function kco_validate_cart_content( $klarna_order, $order ) { $order->set_status( 'on-hold', sprintf( - __( 'A mismatch between the WooCommerce and Klarna orders was identified. Please verify the order with Klarna before processing.', 'klarna-checkout-for-woocommerce' ) + __( 'A mismatch between the WooCommerce and Klarna orders was identified. Please verify the order in the Klarna merchant portal before processing.', 'klarna-checkout-for-woocommerce' ) ) ); $order->save(); From ab6645df3da15915dbc9a1a85b234e626ccc1e3b Mon Sep 17 00:00:00 2001 From: Montazar Date: Wed, 3 Jul 2024 01:09:49 +0200 Subject: [PATCH 4/8] For KSA, the reference in Woo, and Klarna will always differ --- includes/kco-functions.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/includes/kco-functions.php b/includes/kco-functions.php index dc42d5da..f33caa38 100644 --- a/includes/kco-functions.php +++ b/includes/kco-functions.php @@ -727,6 +727,12 @@ function ( $order_line ) { ); $klarna_shipping = reset( $klarna_shipping ); + // For KSA, the reference in Woo, and Klarna will always differ. + if ( ! empty( $shipping ) && strpos( $shipping['reference'], 'klarna_kss' ) !== false ) { + $ksa_data = json_decode( $order->get_meta( '_kco_kss_data' ), true ); + $shipping['reference'] = $ksa_data['id']; + } + if ( ( ! empty( $shipping ) && empty( $klarna_shipping ) ) || ( ! empty( $klarna_shipping ) && empty( $shipping ) ) || ( ! empty( $shipping ) && ! empty( $klarna_shipping ) && $shipping['reference'] !== $klarna_shipping['reference'] ) ) { $mismatch = true; } From 18a756a8a27e25d44a21d575ad73337fb5f54f86 Mon Sep 17 00:00:00 2001 From: Montazar Date: Wed, 3 Jul 2024 01:14:52 +0200 Subject: [PATCH 5/8] Default to false if KSA data cannot be retrieved --- includes/kco-functions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/kco-functions.php b/includes/kco-functions.php index f33caa38..311f2b6d 100644 --- a/includes/kco-functions.php +++ b/includes/kco-functions.php @@ -730,7 +730,7 @@ function ( $order_line ) { // For KSA, the reference in Woo, and Klarna will always differ. if ( ! empty( $shipping ) && strpos( $shipping['reference'], 'klarna_kss' ) !== false ) { $ksa_data = json_decode( $order->get_meta( '_kco_kss_data' ), true ); - $shipping['reference'] = $ksa_data['id']; + $shipping['reference'] = $ksa_data['id'] ?? false; } if ( ( ! empty( $shipping ) && empty( $klarna_shipping ) ) || ( ! empty( $klarna_shipping ) && empty( $shipping ) ) || ( ! empty( $shipping ) && ! empty( $klarna_shipping ) && $shipping['reference'] !== $klarna_shipping['reference'] ) ) { From 51ee1e84e8a664ad25047c77e5406d4b94d7263a Mon Sep 17 00:00:00 2001 From: Montazar Date: Wed, 3 Jul 2024 10:04:59 +0200 Subject: [PATCH 6/8] Extract and simplify if statements --- includes/kco-functions.php | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/includes/kco-functions.php b/includes/kco-functions.php index 311f2b6d..d6008cd7 100644 --- a/includes/kco-functions.php +++ b/includes/kco-functions.php @@ -727,14 +727,16 @@ function ( $order_line ) { ); $klarna_shipping = reset( $klarna_shipping ); - // For KSA, the reference in Woo, and Klarna will always differ. - if ( ! empty( $shipping ) && strpos( $shipping['reference'], 'klarna_kss' ) !== false ) { - $ksa_data = json_decode( $order->get_meta( '_kco_kss_data' ), true ); - $shipping['reference'] = $ksa_data['id'] ?? false; - } - - if ( ( ! empty( $shipping ) && empty( $klarna_shipping ) ) || ( ! empty( $klarna_shipping ) && empty( $shipping ) ) || ( ! empty( $shipping ) && ! empty( $klarna_shipping ) && $shipping['reference'] !== $klarna_shipping['reference'] ) ) { + if ( ! empty( $klarna_shipping ) && empty( $shipping ) ) { + $mismatch = true; + } elseif ( empty( $klarna_shipping ) && ! empty( $shipping ) ) { $mismatch = true; + } else { + // If KSA is enabled, we'll skip the control. + $is_ksa = strpos( $shipping['reference'], 'klarna_kss' ) !== false; + if ( ! $is_ksa && $shipping['reference'] !== $klarna_shipping['reference'] ) { + $mismatch = true; + } } if ( $mismatch ) { From 61cca0c57209ae9fcdd763ac629636bbf8581270 Mon Sep 17 00:00:00 2001 From: Montazar Date: Wed, 3 Jul 2024 10:05:13 +0200 Subject: [PATCH 7/8] Check if order has shipping before retrieving --- includes/kco-functions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/kco-functions.php b/includes/kco-functions.php index d6008cd7..3761baa2 100644 --- a/includes/kco-functions.php +++ b/includes/kco-functions.php @@ -718,7 +718,7 @@ function kco_validate_order_content( $klarna_order, $order ) { } } - $shipping = $order_data->get_order_line_shipping( $order ); + $shipping = ! empty( $order->get_shipping_method() ) ? $order_data->get_order_line_shipping( $order ) : false; $klarna_shipping = array_filter( $klarna_order['order_lines'], function ( $order_line ) { From 235c0ae6e9ec776bd13bbd39005a51eb8ab51d35 Mon Sep 17 00:00:00 2001 From: Montazar Date: Wed, 3 Jul 2024 14:22:04 +0200 Subject: [PATCH 8/8] Reduce if statements --- includes/kco-functions.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/includes/kco-functions.php b/includes/kco-functions.php index 3761baa2..45409710 100644 --- a/includes/kco-functions.php +++ b/includes/kco-functions.php @@ -727,11 +727,9 @@ function ( $order_line ) { ); $klarna_shipping = reset( $klarna_shipping ); - if ( ! empty( $klarna_shipping ) && empty( $shipping ) ) { + if ( empty( $klarna_shipping ) !== empty( $shipping ) ) { $mismatch = true; - } elseif ( empty( $klarna_shipping ) && ! empty( $shipping ) ) { - $mismatch = true; - } else { + } elseif ( ! empty( $klarna_shipping ) && ! empty( $shipping ) ) { // If KSA is enabled, we'll skip the control. $is_ksa = strpos( $shipping['reference'], 'klarna_kss' ) !== false; if ( ! $is_ksa && $shipping['reference'] !== $klarna_shipping['reference'] ) {