Woocommerce Kustom order validation callback

Let’s say you want to sell only to persons over 18, or verify your stock one more time before approving the purchase. Here’s how.

Prerequisites:

By reading the Kustom docs we find out that we can request a callback upon finishing a purchase.

Let’s write a plugin to require and handle the validation callback.

<?php //wp-content/plugins/kustom-order-validation-callback.php
/*
Plugin Name: Kustom/Klarna checkout order validation callback
Description: Validate customer age in checkout
Version: 0.1.0
Author: Michael Svanström
Author URI: https://wwww.svanstrom.nu
License: MIT
*/

// request a validation callback
add_filter('kco_wc_api_request_args', function($args)
{
    $args['options']['require_validate_callback_success'] = true;
    return $args;
});

// provide our callback url
add_filter('kco_wc_merchant_urls', function($args)
{
    $args['validation'] = rest_url('klarna/v1/validate');
    return $args;
});

// register our callback rest endpoint
add_action('rest_api_init', function ()
{
    // https://hostname/wp-json/klarna/v1/validate
    register_rest_route('klarna/v1', '/validate', array(
        'methods' => 'POST',
        'callback' => 'handle_klarna_validation',
        'permission_callback' => '__return_true',
        'args' => array(
            'order_id' => array(
                'required' => true,
                'sanitize_callback' => 'sanitize_text_field',
            ),
            'merchant_reference2' => array(
                'required' => true,
                'sanitize_callback' => 'sanitize_text_field',
                'validate_callback' => function ($param, $request, $key) {
                    return (get_post_type($param) == "shop_order");
                },
            ),
            'customer' => array(
                'required' => true,
                'validate_callback' => function ($param, $request, $key) {
                    return is_array($param) && isset($param['date_of_birth']);
                },
            ),
            'order_amount' => array(
                'required' => true,
                'validate_callback' => function ($param, $request, $key) {
                    return is_numeric($param);
                },
            ),
        ),
    ));
});

/**
 * Return a WP_REST_Response for an error.
 *
 * @param \WP_REST_Request $e The exception that was thrown.
 * @return \WP_REST_Response
 */
function handle_klarna_validation($request)
{
    $order_data = $request->get_json_params();
    $merchant_reference   = $order_data['merchant_reference2'];
    $order = wc_get_order($merchant_reference);

    if (!$order) {
        error_log("Unable to find Klarna order by reference $merchant_reference");
        return new WP_REST_Response(null, 400);
    }

    $customer = $order_data['customer'] ?? array();
    $birth_date = $customer['date_of_birth'] ?? '';

    if (!empty($birth_date)) {
        $order->add_order_note('Customer birth date: ' . $birth_date);

        $birth_date_obj = DateTime::createFromFormat('Y-m-d', $birth_date);
        if ($birth_date_obj) {
            $today = new DateTime();
            $age = $today->diff($birth_date_obj)->y;

            if ($age < 18) {
                $order->add_order_note('Klarna order validation callback: Customer is under 18');
                return new WP_REST_Response(array(
                    'error_type' => 'approval_failed',
                    'error_text' => 'You must be 18 years or older to complete this purchase.'
                ), 400);
            }
        }
    } else {
        $order->add_order_note('Missing date of birth in customer info');
        return new WP_REST_Response(array(
            'error_type' => 'approval_failed',
            'error_text' => 'Missing date of birth in customer info.'
        ), 400);
    }

    // verify stock
    foreach ($order->get_items() as $item) {
        if ($item->is_type('line_item')) {
            /** @var \WC_Order_Item_Product $item */
            $product = $item->get_product();
            if ($product->get_stock_quantity() < 1) {
                // uh oh!
                $error_text = sprintf('Klarna order validation callback: Insufficient stock. %s (%d) is not available for purchase.', $product->get_name(), $product->get_id());
                $order->add_order_note($error_text);

                return new WP_REST_Response(array(
                    'error_type' => 'insufficient_stock',
                    'error_text' => $error_text
                ), 303);
            }
        }
    }

    $order->add_order_note('Klarna order validation callback OK');

    return new WP_REST_Response(null, 200);
}

Credits:


Add a comment