'ids', 'post_type' => wc_get_order_types(), 'post_status' => array_keys( wc_get_order_statuses() ), 'meta_key' => '_wc_klarna_order_id', 'meta_value' => $klarna_order_id, ); $orders = get_posts( $query_args ); // If zero matching orders were found, create backup order. if ( empty( $orders ) ) { // Backup order creation. $this->backup_order_creation( $klarna_order_id ); return; } $order_id = $orders[0]; $order = wc_get_order( $order_id ); if ( $order ) { // The order was already created. Check if order status was set (in thankyou page). if ( ! $order->has_status( array( 'on-hold', 'processing', 'completed' ) ) ) { $response = KCO_WC()->api->request_post_get_order( $klarna_order_id ); $klarna_order = json_decode( $response['body'] ); krokedil_log_events( $order_id, 'Klarna push callback. Updating order status.', $klarna_order ); if ( 'ACCEPTED' === $klarna_order->fraud_status ) { $order->payment_complete( $klarna_order_id ); // translators: Klarna order ID. $note = sprintf( __( 'Payment via Klarna Checkout, order ID: %s', 'klarna-checkout-for-woocommerce' ), sanitize_key( $klarna_order->order_id ) ); $order->add_order_note( $note ); } elseif ( 'REJECTED' === $klarna_order->fraud_status ) { $order->update_status( 'on-hold', __( 'Klarna Checkout order was rejected.', 'klarna-checkout-for-woocommerce' ) ); } elseif ( 'PENDING' === $klarna_order->fraud_status ) { // translators: Klarna order ID. $note = sprintf( __( 'Klarna order is under review, order ID: %s.', 'klarna-checkout-for-woocommerce' ), sanitize_key( $klarna_order->order_id ) ); $order->update_status( 'on-hold', $note ); } KCO_WC()->api->request_post_acknowledge_order( $klarna_order_id ); KCO_WC()->api->request_post_set_merchant_reference( $klarna_order_id, array( 'merchant_reference1' => $order->get_order_number(), 'merchant_reference2' => $order->get_id(), ) ); } else { krokedil_log_events( $order_id, 'Klarna push callback. Order status already set to On hold/Processing/Completed.', $klarna_order ); } } else { // Backup order creation. $this->backup_order_creation( $klarna_order_id ); } } /** * Notification callback function, used for pending orders. */ public function notification_cb() { /** * Notification callback URL has Klarna Order ID (kco_wc_order_id) in it. * * 1. Get Klarna Order ID * 2. Try to find matching WooCommerce order, to see if it was created * 3. If WooCommerce order does not exist, that means regular creation failed AND confirmation callback * either hasn't happened yet or failed. In this case, schedule a single event, 5 minutes from now * and try to get WooCommerce order then. * 4. If WooCommerce order does exist, fire the hook. */ $order_id = ''; if ( $_GET['kco_wc_order_id'] ) { // KCO. $klarna_order_id = sanitize_key( $_GET['kco_wc_order_id'] ); $query_args = array( 'fields' => 'ids', 'post_type' => wc_get_order_types(), 'post_status' => array_keys( wc_get_order_statuses() ), 'meta_key' => '_wc_klarna_order_id', 'meta_value' => $klarna_order_id, ); $orders = get_posts( $query_args ); // If zero matching orders were found, return. if ( ! empty( $orders ) ) { $order_id = $orders[0]; } } if ( '' !== $order_id ) { do_action( 'wc_klarna_notification_listener' ); } else { $post_body = file_get_contents( 'php://input' ); $data = json_decode( $post_body, true ); krokedil_log_events( $order_id, 'Klarna notification callback data', $data ); wp_schedule_single_event( time() + 300, 'kco_wc_punted_notification', array( $klarna_order_id, $data ) ); } } /** * Punted notification callback. * * @param string $klarna_order_id Klarna order ID. */ public function kco_wc_punted_notification_cb( $klarna_order_id, $data ) { do_action( 'wc_klarna_notification_listener', $klarna_order_id, $data ); } /** * Order validation callback function. * Response must be sent to Klarna API. * * @link https://developers.klarna.com/api/#checkout-api-callbacks-order-validation */ public function validation_cb() { $post_body = file_get_contents( 'php://input' ); $data = json_decode( $post_body, true ); krokedil_log_events( null, 'Klarna validation callback data', $data ); $all_in_stock = true; $shipping_chosen = false; $form_data = get_transient( $data['order_id'] ); $has_required_data = true; $failed_required_check = array(); foreach ( $form_data as $form_row ) { if ( isset( $form_row['required'] ) && '' === $form_row['value'] ) { $has_required_data = false; wc_add_notice( 'test', 'error' ); $failed_required_check[] = $form_row['name']; } } // Check stock for each item and shipping method. $cart_items = $data['order_lines']; foreach ( $cart_items as $cart_item ) { if ( 'physical' === $cart_item['type'] ) { // Get product by SKU or ID. if ( wc_get_product_id_by_sku( $cart_item['reference'] ) ) { $cart_item_product = wc_get_product( wc_get_product_id_by_sku( $cart_item['reference'] ) ); } else { $cart_item_product = wc_get_product( $cart_item['reference'] ); } if ( $cart_item_product ) { if ( ! $cart_item_product->has_enough_stock( $cart_item['quantity'] ) ) { $all_in_stock = false; } if( ! $cart_item_product->is_virtual() ) { $needs_shipping = true; } } } elseif ( 'shipping_fee' === $cart_item['type'] ) { $shipping_chosen = true; } } do_action( 'kco_validate_checkout', $data, $all_in_stock, $shipping_chosen ); if ( $all_in_stock && $shipping_chosen && $has_required_data ) { header( 'HTTP/1.0 200 OK' ); } else { header( 'HTTP/1.0 303 See Other' ); if ( ! $all_in_stock ) { $logger = new WC_Logger(); $logger->add( 'klarna-checkout-for-woocommerce', 'Stock validation failed for SKU ' . $cart_item['reference'] ); header( 'Location: ' . wc_get_cart_url() . '?stock_validate_failed' ); } elseif ( ! $shipping_chosen && $needs_shipping ) { header( 'Location: ' . wc_get_checkout_url() . '?no_shipping' ); } elseif ( ! $has_required_data ) { $validation_hash = base64_encode( json_encode( $failed_required_check ) ); header( 'Location: ' . wc_get_checkout_url() . '?required_fields=' . $validation_hash ); } } } /** * Shipping option update callback function. * Response must be sent to Klarna API. * * @link https://developers.klarna.com/api/#checkout-api-callbacks-shipping-option-update */ public function shipping_option_update_cb() { // Send back order amount, order tax amount, order lines, purchase currency and status 200 } /** * Address update callback function. * Response must be sent to Klarna API. * * @link https://developers.klarna.com/api/#checkout-api-callbacks-address-update * @ref https://github.com/mmartche/coach/blob/30022c266089fc7499c54e149883e951c288dc9f/catalog/controller/extension/payment/klarna_checkout.php#L509 */ public function address_update_cb() { // Currently disabled, because of how response body needs to be calculated in WooCommerce. } /** * Backup order creation, in case checkout process failed. * * @param string $klarna_order_id Klarna order ID. * * @throws Exception WC_Data_Exception. */ public function backup_order_creation( $klarna_order_id ) { $response = KCO_WC()->api->request_post_get_order( $klarna_order_id ); $klarna_order = json_decode( $response['body'] ); // Process customer data. $this->process_customer_data( $klarna_order ); // Process customer data. $this->process_cart( $klarna_order ); // Process order. $this->process_order( $klarna_order ); } /** * Processes customer data on backup order creation. * * @param Klarna_Checkout_Order $klarna_order Klarna order. * * @throws Exception WC_Data_Exception. */ private function process_customer_data( $klarna_order ) { // First name. WC()->customer->set_billing_first_name( sanitize_text_field( $klarna_order->billing_address->given_name ) ); WC()->customer->set_shipping_first_name( sanitize_text_field( $klarna_order->shipping_address->given_name ) ); // Last name. WC()->customer->set_billing_last_name( sanitize_text_field( $klarna_order->billing_address->family_name ) ); WC()->customer->set_shipping_last_name( sanitize_text_field( $klarna_order->shipping_address->family_name ) ); // Country. WC()->customer->set_billing_country( sanitize_text_field( $klarna_order->billing_address->country ) ); WC()->customer->set_shipping_country( sanitize_text_field( $klarna_order->shipping_address->country ) ); // Street address 1. WC()->customer->set_billing_address_1( sanitize_text_field( $klarna_order->billing_address->street_address ) ); WC()->customer->set_shipping_address_1( sanitize_text_field( $klarna_order->shipping_address->street_address ) ); // Street address 2. WC()->customer->set_billing_address_2( sanitize_text_field( $klarna_order->billing_address->street_address2 ) ); WC()->customer->set_shipping_address_2( sanitize_text_field( $klarna_order->shipping_address->street_address2 ) ); // City. WC()->customer->set_billing_city( sanitize_text_field( $klarna_order->billing_address->city ) ); WC()->customer->set_shipping_city( sanitize_text_field( $klarna_order->shipping_address->city ) ); // County/State. WC()->customer->set_billing_state( sanitize_text_field( $klarna_order->billing_address->region ) ); WC()->customer->set_shipping_state( sanitize_text_field( $klarna_order->shipping_address->region ) ); // Postcode. WC()->customer->set_billing_postcode( sanitize_text_field( $klarna_order->billing_address->postal_code ) ); WC()->customer->set_shipping_postcode( sanitize_text_field( $klarna_order->shipping_address->postal_code ) ); // Phone. WC()->customer->set_billing_phone( sanitize_text_field( $klarna_order->billing_address->phone ) ); // Email. WC()->customer->set_billing_email( sanitize_text_field( $klarna_order->billing_address->email ) ); WC()->customer->save(); } /** * Processes cart contents on backup order creation. * * @param Klarna_Checkout_Order $klarna_order Klarna order. * * @throws Exception WC_Data_Exception. */ private function process_cart( $klarna_order ) { WC()->cart->empty_cart(); foreach ( $klarna_order->order_lines as $cart_item ) { if ( 'physical' === $cart_item->type ) { if ( wc_get_product_id_by_sku( $cart_item->reference ) ) { $id = wc_get_product_id_by_sku( $cart_item->reference ); } else { $id = $cart_item->reference; } try { WC()->cart->add_to_cart( $id, $cart_item->quantity ); } catch ( Exception $e ) { $logger = new WC_Logger(); $logger->add( 'klarna-checkout-for-woocommerce', 'Backup order creation error add to cart error: ' . $e->getCode() . ' - ' . $e->getMessage() ); } } } WC()->cart->calculate_shipping(); WC()->cart->calculate_fees(); WC()->cart->calculate_totals(); // Check cart items (quantity, coupon validity etc). if ( ! WC()->cart->check_cart_items() ) { return; } WC()->cart->check_cart_coupons(); } /** * Processes WooCommerce order on backup order creation. * * @param Klarna_Checkout_Order $klarna_order Klarna order. * * @throws Exception WC_Data_Exception. */ private function process_order( $klarna_order ) { try { $order = new WC_Order(); $order->set_billing_first_name( sanitize_text_field( $klarna_order->billing_address->given_name ) ); $order->set_billing_last_name( sanitize_text_field( $klarna_order->billing_address->family_name ) ); $order->set_billing_country( sanitize_text_field( $klarna_order->billing_address->country ) ); $order->set_billing_address_1( sanitize_text_field( $klarna_order->billing_address->street_address ) ); $order->set_billing_address_2( sanitize_text_field( $klarna_order->billing_address->street_address2 ) ); $order->set_billing_city( sanitize_text_field( $klarna_order->billing_address->city ) ); $order->set_billing_state( sanitize_text_field( $klarna_order->billing_address->region ) ); $order->set_billing_postcode( sanitize_text_field( $klarna_order->billing_address->postal_code ) ); $order->set_billing_phone( sanitize_text_field( $klarna_order->billing_address->phone ) ); $order->set_billing_email( sanitize_text_field( $klarna_order->billing_address->email ) ); $order->set_shipping_first_name( sanitize_text_field( $klarna_order->shipping_address->given_name ) ); $order->set_shipping_last_name( sanitize_text_field( $klarna_order->shipping_address->family_name ) ); $order->set_shipping_country( sanitize_text_field( $klarna_order->shipping_address->country ) ); $order->set_shipping_address_1( sanitize_text_field( $klarna_order->shipping_address->street_address ) ); $order->set_shipping_address_2( sanitize_text_field( $klarna_order->shipping_address->street_address2 ) ); $order->set_shipping_city( sanitize_text_field( $klarna_order->shipping_address->city ) ); $order->set_shipping_state( sanitize_text_field( $klarna_order->shipping_address->region ) ); $order->set_shipping_postcode( sanitize_text_field( $klarna_order->shipping_address->postal_code ) ); $order->set_created_via( 'klarna_checkout_backup_order_creation' ); $order->set_currency( sanitize_text_field( $klarna_order->purchase_currency ) ); $order->set_prices_include_tax( 'yes' === get_option( 'woocommerce_prices_include_tax' ) ); $order->set_payment_method( 'kco' ); $order->set_shipping_total( WC()->cart->get_shipping_total() ); $order->set_discount_total( WC()->cart->get_discount_total() ); $order->set_discount_tax( WC()->cart->get_discount_tax() ); $order->set_cart_tax( WC()->cart->get_cart_contents_tax() + WC()->cart->get_fee_tax() ); $order->set_shipping_tax( WC()->cart->get_shipping_tax() ); $order->set_total( WC()->cart->get_total( 'edit' ) ); WC()->checkout()->create_order_line_items( $order, WC()->cart ); WC()->checkout()->create_order_fee_lines( $order, WC()->cart ); WC()->checkout()->create_order_shipping_lines( $order, WC()->session->get( 'chosen_shipping_methods' ), WC()->shipping->get_packages() ); WC()->checkout()->create_order_tax_lines( $order, WC()->cart ); WC()->checkout()->create_order_coupon_lines( $order, WC()->cart ); $order->save(); if ( 'ACCEPTED' === $klarna_order->fraud_status ) { $order->payment_complete( $klarna_order->order_id ); // translators: Klarna order ID. $note = sprintf( __( 'Payment via Klarna Checkout, order ID: %s', 'klarna-checkout-for-woocommerce' ), sanitize_key( $klarna_order->order_id ) ); $order->add_order_note( $note ); } elseif ( 'REJECTED' === $klarna_order->fraud_status ) { $order->update_status( 'on-hold', __( 'Klarna Checkout order was rejected.', 'klarna-checkout-for-woocommerce' ) ); } elseif ( 'PENDING' === $klarna_order->fraud_status ) { // translators: Klarna order ID. $note = sprintf( __( 'Klarna order is under review, order ID: %s.', 'klarna-checkout-for-woocommerce' ), sanitize_key( $klarna_order->order_id ) ); $order->update_status( 'on-hold', $note ); } KCO_WC()->api->request_post_acknowledge_order( $klarna_order->order_id ); KCO_WC()->api->request_post_set_merchant_reference( $klarna_order->order_id, array( 'merchant_reference1' => $order->get_order_number(), 'merchant_reference2' => $order->get_id(), ) ); if ( (int) round( $order->get_total() * 100 ) !== (int) $klarna_order->order_amount ) { $order->update_status( 'on-hold', sprintf(__( 'Order needs manual review, WooCommerce total and Klarna total do not match. Klarna order total: %s.', 'klarna-checkout-for-woocommerce' ), $klarna_order->order_amount ) ); } } catch ( Exception $e ) { $logger = new WC_Logger(); $logger->add( 'klarna-checkout-for-woocommerce', 'Backup order creation error: ' . $e->getCode() . ' - ' . $e->getMessage() ); } } } Klarna_Checkout_For_WooCommerce_API_Callbacks::get_instance();