id = POSTNL_SETTINGS_ID; $this->instance_id = absint( $instance_id ); $this->method_title = POSTNL_SERVICE_NAME; // translators: %1$s & %2$s is replaced with tag. $this->method_description = sprintf( __( 'Below you will find all functions for controlling, preparing and processing your shipment with PostNL. Prerequisite is a valid PostNL business customer contract. If you are not yet a PostNL business customer, you can request a quote %1$shere%2$s.', 'postnl-for-woocommerce' ), '', '' ); $this->supports = array( 'shipping-zones', 'instance-settings', 'instance-settings-modal', 'settings', ); $this->postnl_init(); } /** * Shipping method initialization. */ public function postnl_init() { $this->init(); $this->init_form_fields(); $this->init_settings(); add_filter( 'woocommerce_shipping_instance_form_fields_' . $this->id, array( $this, 'instance_form_fields' ), 10, 1 ); add_action( 'woocommerce_update_options_shipping_' . $this->id, array( $this, 'process_admin_options' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_shipping_method_assets' ) ); } /** * Process admin options. * * @return void */ public function process_admin_options() { parent::process_admin_options(); // parent::process_admin_options() writes the new values to the DB but only // updates this method object's cache; the Settings singleton still holds // the pre-save values, and the status row, the NewKey header and the // effective-key selection all read through it for the rest of the request. // Refresh it so the screen that comes back reflects what was just saved. Settings::get_instance()->init_settings(); $this->process_merchant_codes(); // When pickup is disabled, reset the default checkout tab to its default value. if ( 'yes' !== $this->get_option( 'enable_pickup_points' ) ) { $this->update_option( 'default_checkout_tab', 'delivery_day' ); } $this->process_new_api_key_validation(); } /** * Validate the "New API Key" field whenever settings are saved. * * Runs in both environments so the key can be checked in sandbox as well as * production. Performs a live Barcode API call with the candidate key for the * active environment. Success flips the validated flag on so the plugin starts * routing traffic through the new key; failure leaves the old key in use, * records why, and surfaces the reason to the merchant. */ protected function process_new_api_key_validation() { $settings = Settings::get_instance(); // The environment is read from the freshly-posted value on $this rather // than the settings singleton, which may still hold the pre-save value. $is_sandbox = ( 'production' !== $this->get_option( 'environment_mode' ) ); $new_field = $is_sandbox ? 'api_keys_sandbox_new' : 'api_keys_new'; $orig_field = $is_sandbox ? 'api_keys_sandbox' : 'api_keys'; $new_key = trim( (string) $this->get_option( $new_field ) ); $original = trim( (string) $this->get_option( $orig_field ) ); if ( '' === $new_key || $new_key === $original ) { $settings->set_api_key_new_validated( false, null, $is_sandbox ); $settings->set_new_key_status_reason( '', $is_sandbox ); return; } // This exact key already passed validation; skip the live call so an // unrelated settings save doesn't make a blocking request every time. if ( $settings->is_api_key_new_validated_value( $new_key, $is_sandbox ) ) { return; } $result = Key_Validator::validate( $new_key, $this->get_option( 'customer_code' ), $this->get_option( 'customer_num' ), $is_sandbox ); if ( true === $result ) { $settings->set_api_key_new_validated( true, $new_key, $is_sandbox ); $settings->set_new_key_status_reason( '', $is_sandbox ); return; } $reason = $result->get_error_code(); // "unreachable" means PostNL never returned a verdict, so we cannot say the // key is bad — leave any previously-validated state as it was rather than // showing a false red. Every other reason is a definite "not usable yet". if ( Key_Validator::REASON_UNREACHABLE !== $reason ) { $settings->set_api_key_new_validated( false, null, $is_sandbox ); } $settings->set_new_key_status_reason( $reason, $is_sandbox ); WC_Admin_Settings::add_error( esc_html( Key_Validator::reason_message( $reason ) ) ); } /** * Render the status row shown beneath the API Key field: a coloured label, * an em dash and a plain sentence describing the new key's state, driven by * the same value sent in the NewKey header. Hidden by the settings JS for * whichever environment is not currently selected. * * @param string $key Field key. * @param array $data Field data (expects an 'environment' of 'sandbox' or 'production'). * * @return string */ public function generate_postnl_new_key_status_html( $key, $data ) { $is_sandbox = ( isset( $data['environment'] ) && 'sandbox' === $data['environment'] ); $status = Settings::get_instance()->get_new_key_status( $is_sandbox ); ob_start(); ?>

get_option( 'minimum_for_free_shipping' ); $is_free = '' !== $minimum_for_free_shipping && $package['cart_subtotal'] > $minimum_for_free_shipping; if ( $is_free ) { $rate = array( 'id' => $this->get_rate_id(), 'label' => $this->title, 'cost' => 0, 'package' => $package, ); $this->add_rate( $rate ); } else { parent::calculate_shipping( $package ); } } /** * Add form fields for PostNL. * * @param Array $form_fields List of instance form fields. * * @return Array */ public function instance_form_fields( $form_fields ) { // Change title default value. $form_fields['title']['default'] = $this->method_title; // Minimum for free shipping. $currency_symbol = get_woocommerce_currency_symbol(); $form_fields['minimum_for_free_shipping'] = array( // Translators: %s is the currency symbol. 'title' => sprintf( esc_html__( 'Free shipping from %s', 'postnl-for-woocommerce' ), $currency_symbol ), 'type' => 'number', 'desc_tip' => esc_html__( 'Keep empty if you don’t want to use Free shipping', 'postnl-for-woocommerce' ), 'default' => 0, ); return $form_fields; } /** * Initialize integration settings form fields. * * @return void */ public function init_form_fields() { $base_country = Utils::get_base_country(); $settings = Settings::get_instance(); if ( 'NL' === $base_country ) { $form_fields = $settings->nl_setting_fields(); } elseif ( 'BE' === $base_country ) { $form_fields = $settings->be_setting_fields(); } else { $form_fields = $settings->filter_setting_fields( '' ); } $this->form_fields = $form_fields; } /** * Enqueue js file in shipping method settings page. */ public function enqueue_shipping_method_assets() { $screen = get_current_screen(); if ( ! empty( $screen->id ) && 'woocommerce_page_wc-settings' === $screen->id && ! empty( $_GET['section'] ) && POSTNL_SETTINGS_ID === wp_unslash( $_GET['section'] ) ) { wp_enqueue_script( 'postnl-admin-settings', POSTNL_WC_PLUGIN_DIR_URL . '/assets/js/admin-settings.js', array( 'jquery' ), POSTNL_WC_VERSION, true ); wp_localize_script( 'postnl-admin-settings', 'postnlApiKeyCheck', array( 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'action' => \PostNLWooCommerce\Admin\Api_Key_Check::AJAX_ACTION, 'nonce' => wp_create_nonce( \PostNLWooCommerce\Admin\Api_Key_Check::NONCE_ACTION ), ) ); } } /** * Generate repeater HTML. * * @param string $key Field key. * @param array $data Field data. * * @return string */ public function generate_repeater_html( $key, $data ) { ob_start(); $merchant_codes = get_option( self::MERCHANT_CODES_OPTION, array() ); $non_eu_countries = Utils::get_non_eu_countries(); ?> get_tooltip_html( $data ); ?>
$merchant_code ) : ?>

$country ) { $code = $codes[ $index ] ?? null; // Skip empty values or missing codes. if ( empty( $country ) || empty( $code ) ) { $error = true; continue; } $merchant_codes[ sanitize_text_field( $country ) ] = sanitize_text_field( $code ); } update_option( self::MERCHANT_CODES_OPTION, $merchant_codes ); if ( $error ) { WC_Admin_Settings::add_error( esc_html__( 'Some merchant codes were not saved because of missing country or code.', 'postnl-for-woocommerce' ) ); } } }