array( 'title' => esc_html__( 'Manual', 'postnl-for-woocommerce' ), 'type' => 'title', // translators: %1$s & %2$s is replaced with tag. 'description' => sprintf( __( 'Consult the %1$smanual%2$s for help installing the plug-in.', 'postnl-for-woocommerce' ), '', '' ), ), // Account Settings. 'account_settings_title' => array( 'title' => esc_html__( 'Account Settings', 'postnl-for-woocommerce' ), 'type' => 'title', // translators: %1$s & %2$s is replaced with tag. 'description' => sprintf( __( 'Please configure your shipping parameters and your access towards the PostNL APIs by means of authentication. You can find the details of your PostNL account in Mijn %1$sPostNL%2$s under "My Account > API beheren".', 'postnl-for-woocommerce' ), '', '' ), ), 'environment_mode' => array( 'title' => esc_html__( 'Environment Mode', 'postnl-for-woocommerce' ), 'type' => 'select', 'description' => __( 'Choose the environment mode.', 'postnl-for-woocommerce' ), 'desc_tip' => true, 'options' => array( 'production' => esc_html__( 'Production', 'postnl-for-woocommerce' ), 'sandbox' => esc_html__( 'Sandbox', 'postnl-for-woocommerce' ), ), 'class' => 'wc-enhanced-select', 'default' => 'production', 'placeholder' => '', ), // Legacy production key. Renamed to "Old API Key" and only rendered when // a value is already stored (see the gating in get_setting_fields()); a // fresh install shows the "API Key" field alone. Kept read-only so // merchants enter their key in the new field ahead of the API migration. 'api_keys' => array( 'title' => esc_html__( 'Old API Key', 'postnl-for-woocommerce' ), 'type' => 'text', 'description' => esc_html__( 'The key you were using before. It is locked because new keys go in the field below.', 'postnl-for-woocommerce' ), 'desc_tip' => false, 'default' => '', 'placeholder' => '', 'custom_attributes' => array( 'readonly' => 'readonly' ), ), 'api_keys_new' => array( 'title' => esc_html__( 'API Key', 'postnl-for-woocommerce' ), 'type' => 'text', 'description' => esc_html__( 'Enter your PostNL API key here. You can get it from the Self Service module on the PostNL Business Portal.', 'postnl-for-woocommerce' ), 'desc_tip' => false, 'default' => '', 'placeholder' => '', ), 'api_keys_new_status' => array( 'type' => 'postnl_new_key_status', 'environment' => 'production', ), 'api_keys_sandbox' => array( 'title' => esc_html__( 'Old Sandbox API Key', 'postnl-for-woocommerce' ), 'type' => 'text', 'description' => esc_html__( 'The sandbox key you were using before. It is locked because new keys go in the field below.', 'postnl-for-woocommerce' ), 'desc_tip' => false, 'default' => '', 'placeholder' => '', 'custom_attributes' => array( 'readonly' => 'readonly' ), ), 'api_keys_sandbox_new' => array( 'title' => esc_html__( 'Sandbox API Key', 'postnl-for-woocommerce' ), 'type' => 'text', 'description' => esc_html__( 'Enter your PostNL sandbox API key here. You can get it from the Self Service module on the PostNL Business Portal.', 'postnl-for-woocommerce' ), 'desc_tip' => false, 'default' => '', 'placeholder' => '', ), 'api_keys_sandbox_new_status' => array( 'type' => 'postnl_new_key_status', 'environment' => 'sandbox', ), 'enable_logging' => array( 'title' => esc_html__( 'Logging', 'postnl-for-woocommerce' ), 'type' => 'checkbox', 'description' => sprintf( // translators: %1$s is anchor opener tag and %2$s is anchor closer tag. esc_html__( 'A log file containing the communication to the PostNL server will be maintained if this option is checked. This can be used in case of technical issues and can be found %1$shere%2$s.', 'postnl-for-woocommerce' ), '', '' ), 'label' => esc_html__( 'Enable', 'postnl-for-woocommerce' ), 'desc_tip' => false, 'default' => '', 'placeholder' => '', ), 'customer_num' => array( 'title' => esc_html__( 'Customer Number', 'postnl-for-woocommerce' ), 'type' => 'text', 'description' => esc_html__( 'e.g. "11223344"', 'postnl-for-woocommerce' ), 'desc_tip' => true, 'default' => '', 'placeholder' => '11223344', ), 'customer_code' => array( 'title' => esc_html__( 'Customer Code', 'postnl-for-woocommerce' ), 'type' => 'text', 'description' => esc_html__( 'e.g. "DEVC"', 'postnl-for-woocommerce' ), 'desc_tip' => true, 'default' => '', 'placeholder' => 'DEVC', 'custom_attributes' => array( 'maxlength' => '10' ), ), 'return_company' => array( 'title' => esc_html__( 'Company Name', 'postnl-for-woocommerce' ), 'type' => 'text', 'description' => esc_html__( 'Enter company name - this name will be noted as the sender on the label', 'postnl-for-woocommerce' ), 'desc_tip' => true, 'default' => '', ), /* Temporarily hardcoded. 'location_code' => array( 'title' => esc_html__( 'Location Code', 'postnl-for-woocommerce' ), 'type' => 'text', 'description' => esc_html__( 'e.g. "123456"', 'postnl-for-woocommerce' ), 'desc_tip' => true, 'default' => '', 'placeholder' => '123456', 'custom_attributes' => array( 'maxlength' => '10' ), ), */ // Return Settings. 'return_settings_title' => array( 'title' => esc_html__( 'Return Settings', 'postnl-for-woocommerce' ), 'type' => 'title', // 'description' => esc_html__( 'If you have a reply number, only fill in the Zip code, City and Return code. If you want to return your shipments to a home address, also provide the address line (Street, Housenumber and HouseNrExt) of your return address.', 'postnl-for-woocommerce' ), ), 'return_shipment_and_labels' => array( 'title' => esc_html__( 'Standard return option', 'postnl-for-woocommerce' ), 'type' => 'select', 'description' => esc_html__( '- None: return labels are not automatically created', 'postnl-for-woocommerce' ) . '
' . esc_html__( '- Label in the box: a separate return label is created at the same time as the label for the outward shipment and can be included in the box.', 'postnl-for-woocommerce' ), 'options' => array( 'none' => esc_html__( 'None', 'postnl-for-woocommerce' ), 'in_box' => esc_html__( 'Label in the box', 'postnl-for-woocommerce' ), ), 'for_country' => array( 'NL' ), ), // 'return_address_default' => array( // 'title' => esc_html__( 'Always print returnlabel together with shipping label', 'postnl-for-woocommerce' ), // 'type' => 'checkbox', // 'description' => esc_html__( 'With this setting enabled, the return-label of a shipment will automatically be downloaded and printed when the shipping label created.', 'postnl-for-woocommerce' ), // 'desc_tip' => true, // 'default' => '', // 'label' => esc_html__( 'Enable', 'postnl-for-woocommerce' ), // 'placeholder' => '', // ), 'return_shipment_and_labels_all' => array( 'title' => esc_html__( 'Directly activate return function for all labels', 'postnl-for-woocommerce' ), 'type' => 'select', 'label' => esc_html__( 'Enable', 'postnl-for-woocommerce' ), // 'description' => esc_html__( 'Tick this box if you want all labels to be activated for returning immediately. If you do not tick this box the return function can be activated on an order-by-order basis.', 'postnl-for-woocommerce' ), 'options' => array( 'yes' => esc_html__( 'Yes, activate return function directly for all orders', 'postnl-for-woocommerce' ), 'no' => esc_html__( 'No, activate return function per individual order', 'postnl-for-woocommerce' ), ), 'for_country' => array( 'NL' ), ), 'activate_smart_return' => array( 'title' => esc_html__( 'Activate Smart Return', 'postnl-for-woocommerce' ), 'type' => 'checkbox', 'default' => '', 'label' => esc_html__( 'Activate', 'postnl-for-woocommerce' ), 'placeholder' => '', ), 'return_address_or_reply_no' => array( 'title' => esc_html__( 'Return to home address', 'postnl-for-woocommerce' ), 'type' => 'checkbox', 'label' => esc_html__( 'Activate', 'postnl-for-woocommerce' ), 'description' => esc_html__( 'Activate this setting to use a home address for return shipments', 'postnl-for-woocommerce' ), 'desc_tip' => true, ), 'return_replynumber' => array( 'title' => esc_html__( 'Replynumber', 'postnl-for-woocommerce' ), 'type' => 'text', 'description' => esc_html__( 'Enter replynumber.', 'postnl-for-woocommerce' ), 'desc_tip' => true, 'default' => '', 'for_country' => array( 'NL' ), 'class' => 'country-nl', ), 'freepost_zip' => array( 'title' => esc_html__( 'Freepost Zipcode', 'postnl-for-woocommerce' ), 'type' => 'text', 'description' => esc_html__( 'Enter Freepost Zipcode.', 'postnl-for-woocommerce' ), 'desc_tip' => true, 'default' => '', ), 'freepost_city' => array( 'title' => esc_html__( 'Freepost City', 'postnl-for-woocommerce' ), 'type' => 'text', 'description' => esc_html__( 'Enter Freepost City.', 'postnl-for-woocommerce' ), 'desc_tip' => true, 'default' => '', ), 'return_address_street' => array( 'title' => esc_html__( 'Street Address', 'postnl-for-woocommerce' ), 'type' => 'text', 'description' => esc_html__( 'Enter Return Street Address.', 'postnl-for-woocommerce' ), 'desc_tip' => true, 'default' => '', ), 'return_address_house_no' => array( 'title' => esc_html__( 'House Number', 'postnl-for-woocommerce' ), 'type' => 'text', 'description' => esc_html__( 'Enter return house number.', 'postnl-for-woocommerce' ), 'desc_tip' => true, 'default' => '', ), 'return_address_house_noext' => array( 'title' => esc_html__( 'House Number Extension', 'postnl-for-woocommerce' ), 'type' => 'text', 'description' => esc_html__( 'Enter return house number extension.', 'postnl-for-woocommerce' ), 'desc_tip' => true, 'default' => '', ), 'return_address_zip' => array( 'title' => esc_html__( 'Zipcode', 'postnl-for-woocommerce' ), 'type' => 'text', 'description' => esc_html__( 'Enter Return Zipcode.', 'postnl-for-woocommerce' ), 'desc_tip' => true, 'default' => '', ), 'return_address_city' => array( 'title' => esc_html__( 'City', 'postnl-for-woocommerce' ), 'type' => 'text', 'description' => esc_html__( 'Enter Return City.', 'postnl-for-woocommerce' ), 'desc_tip' => true, 'default' => '', ), 'return_customer_code' => array( 'title' => esc_html__( 'Return Customer Code', 'postnl-for-woocommerce' ), 'type' => 'text', 'description' => esc_html__( 'Be aware that the Return Customer Code differs from the regular Customer Code. You can find your Return customer code in Mijn PostNL.', 'postnl-for-woocommerce' ), 'desc_tip' => true, 'default' => '', ), // Delivery Options Settings. 'delivery_options_title' => array( 'title' => esc_html__( 'Checkout Settings', 'postnl-for-woocommerce' ), 'type' => 'title', 'description' => esc_html__( 'Please configure your checkout preferences.', 'postnl-for-woocommerce' ), ), 'supported_shipping_methods' => array( 'title' => esc_html__( 'Shipping Methods', 'postnl-for-woocommerce' ), 'type' => 'multiselect', 'description' => esc_html__( 'Select Shipping Methods can be associated with PostNL.', 'postnl-for-woocommerce' ), 'desc_tip' => true, 'options' => $this->get_shipping_methods(), 'class' => 'wc-enhanced-select', ), 'letterbox_24_fee' => array( 'title' => esc_html__( 'Extra fee for the letterboxparcel (24 hours)', 'postnl-for-woocommerce' ), 'type' => 'price', 'description' => esc_html__( 'Extra fee added when Letterboxparcel Standard (24 hours) has been selected.', 'postnl-for-woocommerce' ), 'desc_tip' => true, 'for_country' => array( 'NL' ), 'class' => 'wc_input_price country-nl', ), 'letterbox_fee' => array( 'title' => __( 'Letterbox fee', 'postnl-for-woocommerce' ), 'type' => 'price', 'description' => __( 'Overrides the shipping cost when all items are eligible for letterbox delivery. Leave empty to use the standard shipping cost. The fee is set to €0 when free shipping applies.', 'postnl-for-woocommerce' ), 'desc_tip' => true, 'for_country' => array( 'NL' ), 'class' => 'wc_input_price country-nl', ), 'enable_pickup_points' => array( 'title' => __( 'PostNL Pick-up Points', 'postnl-for-woocommerce' ), 'type' => 'checkbox', 'label' => __( 'Enable', 'postnl-for-woocommerce' ), 'description' => __( 'Show PostNL pick-up points in the checkout so that your customers can choose to get their orders delivered at a PostNL pick-up point.', 'postnl-for-woocommerce' ), 'desc_tip' => true, 'default' => 'yes', 'for_country' => array( 'NL', 'BE' ), 'class' => 'country-nl country-be', ), 'pickup_delivery_fee' => array( 'title' => __( 'Extra fee pick-up delivery', 'postnl-for-woocommerce' ), 'type' => 'price', 'description' => __( 'Extra fee added when the customer selects a PostNL pick-up point.', 'postnl-for-woocommerce' ), 'desc_tip' => true, 'for_country' => array( 'NL', 'BE' ), 'class' => 'wc_input_price country-nl country-be', ), /* Temporarily commented out. 'number_pickup_points' => array( 'title' => __( 'Number of Pickup Points', 'postnl-for-woocommerce' ), 'type' => 'number', 'description' => __( 'Number of pickup points displayed in the frontend. Maximum will be 20.', 'postnl-for-woocommerce' ), 'desc_tip' => true, 'class' => '', 'default' => '10', 'custom_attributes' => array( 'min' => '1', 'max' => '20', ), 'for_country' => array( 'NL', 'BE' ), 'class' => 'country-nl country-be', ), */ 'enable_delivery_days' => array( 'title' => __( 'Delivery', 'postnl-for-woocommerce' ), 'type' => 'checkbox', 'label' => __( 'Enable', 'postnl-for-woocommerce' ), 'description' => __( 'Show delivery days in the checkout so that your customers can choose which day to receive their order.', 'postnl-for-woocommerce' ), 'desc_tip' => true, 'default' => '', 'for_country' => array( 'NL' ), 'class' => 'country-nl', ), 'delivery_days_fee' => array( 'title' => __( 'Extra fee home delivery', 'postnl-for-woocommerce' ), 'type' => 'price', 'description' => __( 'Extra fee added when the customer selects home delivery.', 'postnl-for-woocommerce' ), 'desc_tip' => true, 'for_country' => array( 'NL', 'BE' ), 'class' => 'wc_input_price country-nl country-be', ), 'number_delivery_days' => array( 'title' => __( 'Number of Delivery Days', 'postnl-for-woocommerce' ), 'type' => 'number', 'description' => __( 'Number of delivery days displayed in the frontend. Maximum will be 12.', 'postnl-for-woocommerce' ), 'desc_tip' => true, 'default' => '10', 'for_country' => array( 'NL' ), 'custom_attributes' => array( 'min' => '1', 'max' => '12', ), 'class' => 'country-nl', ), 'enable_morning_delivery' => array( 'title' => __( 'Morning Delivery', 'postnl-for-woocommerce' ), 'type' => 'checkbox', 'label' => __( 'Enable', 'postnl-for-woocommerce' ), 'description' => __( 'Enable morning delivery in the checkout so your customers can choose to receive their orders in the morning.', 'postnl-for-woocommerce' ), 'desc_tip' => true, 'default' => '', 'for_country' => array( 'NL' ), 'class' => 'country-nl', ), 'morning_delivery_fee' => array( 'title' => __( 'Morning Delivery Fee', 'postnl-for-woocommerce' ), 'type' => 'price', 'description' => __( 'Fee for receiving orders in the morning.', 'postnl-for-woocommerce' ), 'desc_tip' => true, 'for_country' => array( 'NL' ), 'class' => 'wc_input_price country-nl', ), 'enable_evening_delivery' => array( 'title' => __( 'Evening Delivery', 'postnl-for-woocommerce' ), 'type' => 'checkbox', 'label' => __( 'Enable', 'postnl-for-woocommerce' ), 'description' => __( 'Enable evening delivery in the checkout so your customers can choose to receive their orders in the evening.', 'postnl-for-woocommerce' ), 'desc_tip' => true, 'default' => '', 'for_country' => array( 'NL' ), 'class' => 'country-nl', ), 'evening_delivery_fee' => array( 'title' => __( 'Evening Delivery Fee', 'postnl-for-woocommerce' ), 'type' => 'price', 'description' => __( 'Fee for receiving orders in the evening.', 'postnl-for-woocommerce' ), 'desc_tip' => true, 'for_country' => array( 'NL' ), 'class' => 'wc_input_price country-nl', ), 'default_checkout_tab' => array( 'title' => __( 'Default Delivery Options Tab', 'postnl-for-woocommerce' ), 'type' => 'select', 'description' => __( 'Choose which tab is shown first in the delivery options menu at checkout.', 'postnl-for-woocommerce' ), 'desc_tip' => true, 'default' => 'delivery_day', 'options' => array( 'delivery_day' => __( 'Home Delivery', 'postnl-for-woocommerce' ), 'dropoff_points' => __( 'Pickup Points', 'postnl-for-woocommerce' ), ), 'for_country' => array( 'NL', 'BE' ), 'class' => 'country-nl country-be', ), 'transit_time' => array( 'title' => esc_html__( 'Transit Time', 'postnl-for-woocommerce' ), 'type' => 'number', 'description' => esc_html__( 'The number of days it takes for the order to be delivered after the order has been placed.', 'postnl-for-woocommerce' ), 'desc_tip' => true, 'default' => '1', 'placeholder' => '', ), 'cut_off_time' => array( 'title' => esc_html__( 'Cut Off Time', 'postnl-for-woocommerce' ), 'type' => 'time', 'description' => esc_html__( 'If an order is ordered after this time, one day will be added to the transit time.', 'postnl-for-woocommerce' ), 'desc_tip' => true, 'default' => '18:00', 'placeholder' => '', ), 'dropoff_day_mon' => array( 'title' => __( 'Drop off Days', 'postnl-for-woocommerce' ), 'type' => 'checkbox', 'label' => __( 'Monday', 'postnl-for-woocommerce' ), 'description' => __( 'Select which days orders will be shipped.', 'postnl-for-woocommerce' ), 'default' => 'yes', 'desc_tip' => true, ), 'dropoff_day_tue' => array( 'type' => 'checkbox', 'label' => __( 'Tuesday', 'postnl-for-woocommerce' ), 'default' => 'yes', ), 'dropoff_day_wed' => array( 'type' => 'checkbox', 'label' => __( 'Wednesday', 'postnl-for-woocommerce' ), 'default' => 'yes', ), 'dropoff_day_thu' => array( 'type' => 'checkbox', 'label' => __( 'Thursday', 'postnl-for-woocommerce' ), 'default' => 'yes', ), 'dropoff_day_fri' => array( 'type' => 'checkbox', 'label' => __( 'Friday', 'postnl-for-woocommerce' ), 'default' => 'yes', ), 'dropoff_day_sat' => array( 'type' => 'checkbox', 'label' => __( 'Saturday', 'postnl-for-woocommerce' ), 'default' => 'yes', ), 'dropoff_day_sun' => array( 'type' => 'checkbox', 'label' => __( 'Sunday', 'postnl-for-woocommerce' ), ), 'validate_nl_address' => array( 'title' => __( 'Validate Dutch addresses', 'postnl-for-woocommerce' ), 'type' => 'checkbox', 'label' => __( 'Enable', 'postnl-for-woocommerce' ), 'description' => __( 'Based on zipcode and housenumber combination the address is checked.', 'postnl-for-woocommerce' ), 'desc_tip' => true, 'default' => 'yes', ), 'reorder_nl_address' => array( 'title' => __( 'Use PostNL address-field', 'postnl-for-woocommerce' ), 'type' => 'checkbox', 'label' => __( 'Enable', 'postnl-for-woocommerce' ), 'description' => __( 'For zipcode, housenumber, housenumber extension and street separate address fields are displayed when this settings is enabled. This only applies for Dutch addresses.', 'postnl-for-woocommerce' ), 'desc_tip' => true, 'default' => 'yes', ), // Shipping Outside Europe Settings. 'shipping_outside_eu_title' => array( 'title' => esc_html__( 'Shipping Outside Europe Settings', 'postnl-for-woocommerce' ), 'type' => 'title', 'description' => esc_html__( 'Please insert your Parcels non-EU credentials.', 'postnl-for-woocommerce' ), ), 'globalpack_barcode_type' => array( 'title' => esc_html__( 'Parcels non-EU Barcode Type', 'postnl-for-woocommerce' ), 'type' => 'text', 'description' => '', 'desc_tip' => true, 'default' => '', 'placeholder' => esc_html__( 'CD', 'postnl-for-woocommerce' ), 'custom_attributes' => array( 'maxlength' => '10' ), ), 'globalpack_customer_code' => array( 'title' => esc_html__( 'Parcels non-EU Customer Code', 'postnl-for-woocommerce' ), 'type' => 'text', 'description' => '', 'desc_tip' => true, 'default' => '', 'placeholder' => esc_html__( '1234', 'postnl-for-woocommerce' ), 'custom_attributes' => array( 'maxlength' => '10' ), ), 'hs_tariff_code' => array( 'title' => esc_html__( 'Default HS Tariff Code', 'postnl-for-woocommerce' ), 'type' => 'text', 'description' => esc_html__( 'The HS tariff code is used by customs to classify goods. The HS tariff code can be found on the website of the Dutch Chamber of Commerce.', 'postnl-for-woocommerce' ), 'desc_tip' => true, 'default' => '', 'placeholder' => '', ), 'country_origin' => array( 'title' => esc_html__( 'Default Country of Origin', 'postnl-for-woocommerce' ), 'type' => 'select', 'description' => esc_html__( 'Default country of origin is used by customs.', 'postnl-for-woocommerce' ), 'desc_tip' => true, 'default' => Utils::get_base_country(), 'options' => WC()->countries->get_countries(), 'placeholder' => '', ), 'merchant_codes_repeater' => array( 'title' => esc_html__( 'Merchant Customs Code', 'postnl-for-woocommerce' ), 'type' => 'repeater', 'description' => esc_html__( 'Add merchant codes for specific non-EU countries.', 'postnl-for-woocommerce' ), 'desc_tip' => true, 'for_country' => array( 'NL', 'BE' ), ), 'printer_email_title' => array( 'title' => esc_html__( 'Printer & Email Settings', 'postnl-for-woocommerce' ), 'type' => 'title', 'description' => esc_html__( 'Please configure your printer and email preferences.', 'postnl-for-woocommerce' ), ), 'printer_type' => array( 'title' => esc_html__( 'Printer Type', 'postnl-for-woocommerce' ), 'type' => 'select', 'description' => esc_html__( 'It is not recommended to send .pdf files/labels directly to a Zebra printer If you want to send it directly to your Zebra printer, please use .gif files or the native (generic) zpl printer type', 'postnl-for-woocommerce' ), 'desc_tip' => true, 'default' => 'PDF', 'options' => array( 'PDF' => 'PDF', 'GIF' => 'GIF', 'JPG' => 'JPG', 'ZPL' => 'ZPL', ), ), 'printer_type_resolution' => array( 'title' => esc_html__( 'DPI', 'postnl-for-woocommerce' ), 'type' => 'select', 'default' => '600', 'options' => array( '600' => '600', '300' => '300', '200' => '200', ), ), 'label_format' => array( 'title' => esc_html__( 'Label Format', 'postnl-for-woocommerce' ), 'type' => 'select', 'description' => esc_html__( 'Use A6 format in case you use a labelprinter. Use A4 format for other regular printers.', 'postnl-for-woocommerce' ), 'desc_tip' => true, 'default' => 'A6', 'options' => array( 'A6' => 'A6', 'A4' => 'A4', ), 'class' => 'wc-enhanced-select', ), 'shipping_confirmation_email' => array( 'title' => esc_html__( 'Shipping confirmation e-mail address', 'postnl-for-woocommerce' ), 'type' => 'email', 'description' => esc_html__( 'This e-mail address will be used by PostNL for shipping confirmations. If left empty, no sender e-mail will be included.', 'postnl-for-woocommerce' ), 'desc_tip' => true, ), 'woocommerce_email' => array( 'title' => esc_html__( 'WooCommerce Email', 'postnl-for-woocommerce' ), 'type' => 'checkbox', 'label' => esc_html__( 'When PostNL label is created send email to customer.', 'postnl-for-woocommerce' ), 'description' => esc_html__( 'When PostNL label is created send email to customer.', 'postnl-for-woocommerce' ), 'desc_tip' => true, 'default' => '', 'placeholder' => '', ), 'woocommerce_email_text' => array( 'title' => esc_html__( 'WooCommerce Email Text', 'postnl-for-woocommerce' ), 'type' => 'text', 'description' => esc_html__( 'Text added for tracking note email.', 'postnl-for-woocommerce' ), 'desc_tip' => true, 'default' => esc_html__( 'This is your track and track link {tracking-link}', 'postnl-for-woocommerce' ), 'placeholder' => esc_html__( 'This is your track and track link {tracking-link}', 'postnl-for-woocommerce' ), ), // Default shipping Options Settings. 'default_shipping_options_title' => array( 'title' => esc_html__( 'Default shipping Options Settings', 'postnl-for-woocommerce' ), 'type' => 'title', 'description' => esc_html__( 'Please select Default shipping Options.', 'postnl-for-woocommerce' ), 'for_country' => array( 'NL' ), ), 'default_shipping_options_nl' => array( 'title' => __( 'Shipping options domestic', 'postnl-for-woocommerce' ), 'type' => 'select', 'description' => __( 'Select a default shipping option for domestic orders that are shipped with PostNL.', 'postnl-for-woocommerce' ), 'default' => 'standard_shipment', 'for_country' => array( 'NL' ), 'options' => array( 'standard_shipment' => __( 'Standard shipment', 'postnl-for-woocommerce' ), 'id_check' => __( 'ID Check (18+)', 'postnl-for-woocommerce' ), 'id_check|insured_shipping' => __( 'ID Check (18+) + Insured Shipping', 'postnl-for-woocommerce' ), // 'insured_shipping' => __( 'Insured Shipping', 'postnl-for-woocommerce' ), 'return_no_answer' => __( 'Return if no answer', 'postnl-for-woocommerce' ), 'signature_on_delivery' => __( 'Signature on Delivery', 'postnl-for-woocommerce' ), 'only_home_address' => __( 'Only Home Address', 'postnl-for-woocommerce' ), 'letterbox' => __( 'Letterboxparcel Standard (24 hours)', 'postnl-for-woocommerce' ), 'letterbox_48' => __( 'Letterboxparcel 48 hours', 'postnl-for-woocommerce' ), 'signature_on_delivery|insured_shipping' => __( 'Signature on Delivery + Insured Shipping', 'postnl-for-woocommerce' ), 'signature_on_delivery|return_no_answer' => __( 'Signature on Delivery + Return if no answer', 'postnl-for-woocommerce' ), 'insured_shipping|return_no_answer|signature_on_delivery' => __( 'Insured Shipping + Return if no answer + Signature on Delivery', 'postnl-for-woocommerce' ), 'only_home_address|return_no_answer' => __( 'Only Home Address + Return if no answer', 'postnl-for-woocommerce' ), 'only_home_address|return_no_answer|signature_on_delivery' => __( 'Only Home Address + Return if no answer + Signature on Delivery', 'postnl-for-woocommerce' ), 'only_home_address|signature_on_delivery' => __( 'Only Home Address + Signature on Delivery', 'postnl-for-woocommerce' ), 'delivery_code_at_door|insured_shipping' => esc_html__( 'Delivery Code at Door + Insured Shipping', 'postnl-for-woocommerce' ), ), ), 'default_shipping_options_be' => array( 'title' => __( 'Shipping options Belgium', 'postnl-for-woocommerce' ), 'type' => 'select', 'description' => __( 'Select a default shipping option for the orders shipped to Belgium with PostNL.', 'postnl-for-woocommerce' ), 'default' => 'standard_belgium', 'for_country' => array( 'NL' ), 'options' => array( 'standard_belgium' => __( 'Standard Shipment Belgium', 'postnl-for-woocommerce' ), 'standard_belgium|only_home_address' => __( 'Standard Shipment Belgium + Only Home Address', 'postnl-for-woocommerce' ), 'standard_belgium|signature_on_delivery' => __( 'Standard Shipment Belgium + Signature on Delivery', 'postnl-for-woocommerce' ), 'standard_belgium|insured_shipping' => __( 'Standard Shipment Belgium + Insured Shipping', 'postnl-for-woocommerce' ), 'mailboxpacket' => __( 'Boxable Packet', 'postnl-for-woocommerce' ), 'mailboxpacket|track_and_trace' => __( 'Boxable Packet + Track & Trace', 'postnl-for-woocommerce' ), 'packets' => __( 'Packets', 'postnl-for-woocommerce' ), 'packets|track_and_trace' => __( 'Packets + Track & Trace', 'postnl-for-woocommerce' ), 'packets|track_and_trace|insured_shipping' => __( 'Packets + Track & Trace + Insured', 'postnl-for-woocommerce' ), ), ), 'default_shipping_options_eu' => array( 'title' => __( 'Shipping options EU', 'postnl-for-woocommerce' ), 'type' => 'select', 'description' => __( 'Select a default shipping option for the orders shipped within European Union zone.', 'postnl-for-woocommerce' ), 'default' => 'eu_parcel|track_and_trace', 'for_country' => array( 'NL' ), 'options' => array( 'eu_parcel|track_and_trace' => __( 'EU Parcel + Track & Trace', 'postnl-for-woocommerce' ), 'eu_parcel|track_and_trace|insured_shipping' => __( 'EU Parcel + Track & Trace + Insured', 'postnl-for-woocommerce' ), 'eu_parcel|track_and_trace|insured_plus' => __( 'EU Parcel + Track & Trace + Insured Plus', 'postnl-for-woocommerce' ), 'mailboxpacket' => __( 'Boxable Packet', 'postnl-for-woocommerce' ), 'mailboxpacket|track_and_trace' => __( 'Boxable Packet + Track & Trace', 'postnl-for-woocommerce' ), 'packets' => __( 'Packets', 'postnl-for-woocommerce' ), 'packets|track_and_trace' => __( 'Packets + Track & Trace', 'postnl-for-woocommerce' ), 'packets|track_and_trace|insured_shipping' => __( 'Packets + Track & Trace + Insured', 'postnl-for-woocommerce' ), ), ), 'default_shipping_options_row' => array( 'title' => __( 'Default Shipping International', 'postnl-for-woocommerce' ), 'type' => 'select', 'description' => __( 'Shipping options non-EU (outside the EU borders).', 'postnl-for-woocommerce' ), 'default' => 'parcel_non_eu|track_and_trace', 'for_country' => array( 'NL' ), 'options' => array( 'parcel_non_eu|track_and_trace' => __( 'Parcel non-EU + Track & Trace', 'postnl-for-woocommerce' ), 'parcel_non_eu|track_and_trace|insured_shipping' => __( 'Parcel non-EU + Track & Trace + Insured', 'postnl-for-woocommerce' ), 'parcel_non_eu|track_and_trace|insured_plus' => __( 'Parcel non-EU + Track & Trace + Insured Plus', 'postnl-for-woocommerce' ), 'mailboxpacket' => __( 'Boxable Packet', 'postnl-for-woocommerce' ), 'mailboxpacket|track_and_trace' => __( 'Boxable Packet + Track & Trace', 'postnl-for-woocommerce' ), 'packets' => __( 'Packets', 'postnl-for-woocommerce' ), 'packets|track_and_trace' => __( 'Packets + Track & Trace', 'postnl-for-woocommerce' ), 'packets|track_and_trace|insured_shipping' => __( 'Packets + Track & Trace + Insured', 'postnl-for-woocommerce' ), ), ), 'default_shipping_options_pickup' => array( 'title' => __( 'Default Shipping Pickup', 'postnl-for-woocommerce' ), 'type' => 'select', 'description' => __( 'Shipping options Pickup.', 'postnl-for-woocommerce' ), 'default' => 'id_check', 'for_country' => array( 'NL' ), 'options' => array( '' => esc_html__( 'Standard Shipping', 'postnl-for-woocommerce' ), 'id_check' => esc_html__( 'ID Check (18+)', 'postnl-for-woocommerce' ), 'insured_shipping' => esc_html__( 'Insured Shipping', 'postnl-for-woocommerce' ), 'id_check|insured_shipping' => esc_html__( 'ID Check (18+) + Insured Shipping', 'postnl-for-woocommerce' ), ), ), 'default_automatic_letterboxparcel_product' => array( 'title' => esc_html__( 'Default automatic letterboxparcel product', 'postnl-for-woocommerce' ), 'type' => 'select', 'description' => '', 'default' => 'letterbox', 'for_country' => array( 'NL' ), 'options' => array( 'letterbox' => esc_html__( 'Letterboxparcel Standard (24 hours)', 'postnl-for-woocommerce' ), 'letterbox_48' => esc_html__( 'Letterboxparcel 48 hours', 'postnl-for-woocommerce' ), 'customer_decide' => esc_html__( 'Let customer decide', 'postnl-for-woocommerce' ), ), ), 'auto_complete_order' => array( 'title' => esc_html__( 'Automatically change order status to Completed', 'postnl-for-woocommerce' ), 'type' => 'checkbox', 'label' => esc_html__( 'Automatically change order status to Completed once an order has been pre-alerted and printed', 'postnl-for-woocommerce' ), 'description' => esc_html__( 'Automatically change order status to Completed once an order has been pre-alerted and printed', 'postnl-for-woocommerce' ), 'desc_tip' => true, 'default' => '', 'placeholder' => '', ), ); // The legacy key fields are shown only when a key is already stored: a // fresh install enters its key in the new "API Key" field alone. The // decision is made on the saved value (not the posted one) so a failed // save does not make the field flicker in and out. Reading the raw option // directly avoids recursing through get_country_option()/filter_setting_fields(). if ( '' === trim( (string) $this->get_option( 'api_keys' ) ) ) { unset( $fields['api_keys'] ); } if ( '' === trim( (string) $this->get_option( 'api_keys_sandbox' ) ) ) { unset( $fields['api_keys_sandbox'] ); } return $fields; } /** * Filter the setting fields based on store country. * * @param String $country Two characters country code. * @param bool $only_field_country Flag to check if it only return for the field with 'for_country' array exists. * * @return array */ public function filter_setting_fields( $country, $only_field_country = false, $settings = false ) { $setting_fields = $this->get_setting_fields(); if ( $country == 'BE' && ! $settings ) { $setting_fields['default_shipping_options_title'] = array( 'title' => esc_html__( 'Default shipping Options Settings', 'postnl-for-woocommerce' ), 'type' => 'title', 'description' => esc_html__( 'Please select Default shipping Options.', 'postnl-for-woocommerce' ), 'for_country' => array( 'BE' ), ); $setting_fields['default_shipping_options_row'] = array( 'title' => __( 'Default Shipping International', 'postnl-for-woocommerce' ), 'type' => 'select', 'description' => __( 'Select a default shipping option for the orders shipped internationally (outside the EU borders).', 'postnl-for-woocommerce' ), 'default' => 'parcel_non_eu|track_and_trace|insured_plus', 'for_country' => array( 'BE' ), 'options' => array( 'parcel_non_eu|track_and_trace|insured_plus' => __( 'Parcel non-EU + Track & Trace + Insured Plus', 'postnl-for-woocommerce' ), ), ); } $country_fields = array_filter( $setting_fields, function ( $field ) use ( $country, $only_field_country ) { if ( empty( $field['for_country'] ) && false === $only_field_country ) { return true; } if ( ! empty( $field['for_country'] ) && is_array( $field['for_country'] ) && in_array( $country, $field['for_country'], true ) ) { return true; } if ( ! empty( $field['for_country'] ) && $field['for_country'] === $country ) { return true; } return false; } ); return $country_fields; } /** * Return NL setting fields only. * * @param bool $only_field_country Flag to check if it only return for the field with 'for_country' array exists. * * @return array */ public function nl_setting_fields( $only_field_country = false ) { return $this->filter_setting_fields( 'NL', $only_field_country ); } /** * Return BE setting fields only. * * @param bool $only_field_country Flag to check if it only return for the field with 'for_country' array exists. * * @return array */ public function be_setting_fields( $only_field_country = false ) { return $this->filter_setting_fields( 'BE', $only_field_country, true ); } /** * Get setting option value based on country. * * @param String $field Field name. * @param String $default_value Default value if the field value is empty. * * @return String */ public function get_country_option( $field, $default_value = '' ) { $base_country = Utils::get_base_country(); $fields_country = array_keys( $this->filter_setting_fields( $base_country, false ) ); return in_array( $field, $fields_country, true ) ? $this->get_option( $field, $default_value ) : ''; } /** * Get API Key from the settings. * * @return String */ public function get_api_key() { return $this->get_country_option( 'api_keys', '' ); } /** * Get sandbox API Key from the settings. * * @return String */ public function get_api_key_sandbox() { return $this->get_country_option( 'api_keys_sandbox', '' ); } /** * Option storing the SHA-256 hash of the new API key value that last * passed validation. Storing a hash (rather than a global yes/no flag) * binds the validated state to the exact key value, so any out-of-band * edit or partial save naturally invalidates the flag. */ const NEW_API_KEY_VALIDATED_HASH_OPTION = 'postnl_api_keys_new_validated_hash'; /** * Get the raw value of the new sandbox API key as entered by the merchant. * * @return string */ public function get_api_key_sandbox_new() { return trim( (string) $this->get_country_option( 'api_keys_sandbox_new', '' ) ); } /** * Get the raw value of the new API key for the current environment. * * PostNL issues environment-scoped keys, so sandbox and production each have * their own new-key field. Returning the field that matches the active * environment keeps every V4 consumer (Service_Factory, the adoption header, * the effective-key fallback) pointed at the right key without each caller * having to branch on the environment itself. * * @return string */ public function get_api_key_new() { if ( $this->is_sandbox() ) { return $this->get_api_key_sandbox_new(); } return trim( (string) $this->get_country_option( 'api_keys_new', '' ) ); } /** * The original (pre-migration) API key for the current environment. * * @return string */ public function get_original_api_key() { return $this->is_sandbox() ? trim( (string) $this->get_api_key_sandbox() ) : trim( (string) $this->get_api_key() ); } /** * Name of the option storing the validated-key hash for a given environment. * * The validated flag is environment-scoped: a key validated in production must * not be treated as validated in sandbox (and vice versa), otherwise the V4 * gate would route a production key against the sandbox host, or the reverse. * A single option cannot hold both, since validating one environment would * overwrite the other, so each environment gets its own option. * * @param bool|null $is_sandbox Environment to scope to, or null for the current one. * * @return string */ protected function validated_hash_option_name( $is_sandbox = null ) { $is_sandbox = ( null === $is_sandbox ) ? $this->is_sandbox() : (bool) $is_sandbox; return self::NEW_API_KEY_VALIDATED_HASH_OPTION . ( $is_sandbox ? '_sandbox' : '' ); } /** * Whether the currently-entered new API key matches the key that last * passed validation against the PostNL API for the current environment. * * @return bool */ public function is_api_key_new_validated() { return $this->is_api_key_new_validated_value( $this->get_api_key_new() ); } /** * Whether a specific key value matches the key that last passed * validation. Takes the value explicitly so callers (e.g. the save-time * handler) can check the freshly-entered key without relying on the * settings object's in-memory cache being up to date. * * @param string $key Candidate key value. * @param bool|null $is_sandbox Environment to check, or null for the current one. * * @return bool */ public function is_api_key_new_validated_value( $key, $is_sandbox = null ) { $key = trim( (string) $key ); if ( '' === $key ) { return false; } $stored = (string) get_option( $this->validated_hash_option_name( $is_sandbox ), '' ); if ( '' === $stored ) { return false; } return hash_equals( $stored, hash( 'sha256', $key ) ); } /** * Record that the currently-entered new API key has passed validation, * or clear the flag entirely. The hash binds the flag to the exact key * value the merchant just successfully tested, scoped to its environment. * * @param bool $validated Validation outcome. * @param string|null $key The exact key value that was validated. When * omitted, falls back to the stored setting — * but callers running mid-save should pass the * freshly-entered value to avoid hashing a * stale cached value. * @param bool|null $is_sandbox Environment to scope to, or null for the current one. */ public function set_api_key_new_validated( $validated, $key = null, $is_sandbox = null ) { $option = $this->validated_hash_option_name( $is_sandbox ); if ( ! $validated ) { delete_option( $option ); return; } $key = trim( (string) ( null === $key ? $this->get_api_key_new() : $key ) ); if ( '' === $key ) { delete_option( $option ); return; } update_option( $option, hash( 'sha256', $key ) ); } /** * Return the API key the plugin should actually send to PostNL for the * current environment. Falls back to the original key whenever the new * key is empty, identical, or has not been validated. * * @return string */ public function get_effective_api_key() { $original = $this->get_original_api_key(); $new_key = $this->get_api_key_new(); if ( '' === $new_key ) { return $original; } if ( $new_key === $original ) { return $original; } if ( ! $this->is_api_key_new_validated() ) { return $original; } return $new_key; } /** * Option storing the reason the new key last failed validation, so the * settings status row can tell "key rejected" apart from "could not reach * PostNL" or "customer details missing" — all of which report the same * "Entered" header value. Only consulted when the header value is "Entered". */ const NEW_API_KEY_STATUS_OPTION = 'postnl_api_keys_new_status'; /** * Map the new-key state to its NewKey header value. Kept as one pure method * so the outgoing header and the settings status row can never disagree. * * - "No" : the new-key field is empty. * - "Same" : the new-key field matches the original key. * - "Entered" : a distinct key was entered but has not passed validation. * - "Yes" : a distinct new key has been entered and validated. * * "Yes" is gated on validation because PostNL defines it as a key that * "works"; a key that failed our validation reports "Entered" so PostNL does * not count the merchant as ready to migrate. * * @param string $new_key The entered new key. * @param string $original The original (pre-migration) key. * @param bool $validated Whether the new key passed validation. * * @return string */ protected function derive_new_key_header_value( $new_key, $original, $validated ) { if ( '' === $new_key ) { return 'No'; } if ( $new_key === $original ) { return 'Same'; } if ( ! $validated ) { return 'Entered'; } return 'Yes'; } /** * Value for the NewKey header sent on every outgoing API call, computed for * the current environment. * * @return string */ public function get_new_key_header_value() { return $this->derive_new_key_header_value( $this->get_api_key_new(), $this->get_original_api_key(), $this->is_api_key_new_validated() ); } /** * Persist (or clear) the reason the new key last failed validation, scoped * to the given environment. * * @param string $reason One of 'invalid', 'rejected', 'unreachable', 'missing', or '' to clear. * @param bool|null $is_sandbox Environment to scope to, or null for the current one. */ public function set_new_key_status_reason( $reason, $is_sandbox = null ) { $option = self::NEW_API_KEY_STATUS_OPTION . ( $this->resolve_is_sandbox( $is_sandbox ) ? '_sandbox' : '' ); if ( '' === $reason ) { delete_option( $option ); return; } update_option( $option, $reason ); } /** * PostNL developer portal page for the API migration. It explains how to * upgrade PHP, how to request a new API key via the Self Service (SSAM) * module, and when to update the plug-in for API v4. Confirmed by PostNL as * the destination for the migration banners (in place of the Self Service * portal directly), so customers get the full step-by-step instructions first. */ const SELF_SERVICE_URL = 'https://developer.postnl.nl/integration-with-postnl/api-overview/future-proof-api-s/plug-ins/'; /** * Link to the PostNL Business Portal Self Service module, reused across the * status row and the migration banner. * * @return string */ public function self_service_link() { return sprintf( '%2$s', esc_url( self::SELF_SERVICE_URL ), esc_html__( 'Self Service module on the PostNL Business Portal', 'postnl-for-woocommerce' ) ); } /** * Resolve the status of the new key for a given environment: the header value * plus a colour, label, a one-line summary and a "what to do next" paragraph * for the settings status row. The label and colour derive from the same * header value the API calls send, so the screen and PostNL's adoption count * can never contradict each other. * * @param bool|null $is_sandbox Environment to describe, or null for the current one. * * @return array{header:string,label:string,color:string,summary:string,description:string} */ public function get_new_key_status( $is_sandbox = null ) { $is_sandbox = $this->resolve_is_sandbox( $is_sandbox ); $new_key = $is_sandbox ? $this->get_api_key_sandbox_new() : trim( (string) $this->get_country_option( 'api_keys_new', '' ) ); $original = $is_sandbox ? trim( (string) $this->get_api_key_sandbox() ) : trim( (string) $this->get_api_key() ); $validated = $this->is_api_key_new_validated_value( $new_key, $is_sandbox ); // The rendered row describes what is actually stored, so the validated // state it shows is a saved one. return $this->build_new_key_status( $new_key, $original, $validated, $is_sandbox, true, $this->resolve_new_key_status_reason( $is_sandbox ) ); } /** * Map a new-key state to the status row's colour, label, one-line summary and * "what to do next" paragraph. Pure: every input is passed in, so the settings * render path and the on-blur endpoint produce identical copy for the same * state and cannot drift. The label and colour follow the same header value * the API calls send, so the screen and PostNL's adoption count agree. * * @param string $new_key The entered new key. * @param string $original The original (pre-migration) key; '' on a fresh install. * @param bool $validated Whether the new key has passed validation. * @param bool $is_sandbox Environment being described. * @param bool $saved Whether $validated reflects a saved state. A key that * validates on blur but has not been saved yet gets the * amber "works, not saved" state instead of green. * @param string $reason Persisted/derived failure reason for an entered key. * * @return array{header:string,label:string,color:string,summary:string,description:string} */ public function build_new_key_status( $new_key, $original, $validated, $is_sandbox, $saved, $reason = '' ) { unset( $is_sandbox ); $header = $this->derive_new_key_header_value( $new_key, $original, $validated ); $link = $this->self_service_link(); $has_old = '' !== trim( (string) $original ); switch ( $header ) { case 'Yes': if ( ! $saved ) { return array( 'header' => $header, 'label' => __( 'Works, not saved yet', 'postnl-for-woocommerce' ), 'color' => '#dba617', 'summary' => __( 'This key works. Save changes to start using it.', 'postnl-for-woocommerce' ), 'description' => '', ); } return array( 'header' => $header, 'label' => __( 'Valid', 'postnl-for-woocommerce' ), 'color' => '#008a20', 'summary' => __( 'Your API key is working and will be used for the new PostNL APIs.', 'postnl-for-woocommerce' ), 'description' => '', ); case 'Same': return array( 'header' => $header, 'label' => __( 'Same as old key', 'postnl-for-woocommerce' ), 'color' => '#dba617', 'summary' => __( 'This is the key you already had, not a new one.', 'postnl-for-woocommerce' ), // translators: %s is a link to the PostNL Business Portal Self Service module. 'description' => sprintf( __( 'You need a separate key. Request one from the %s, then paste it into the field above.', 'postnl-for-woocommerce' ), $link ), ); case 'Entered': return array_merge( array( 'header' => $header ), $this->get_new_key_invalid_copy( $reason, $has_old, $link ) ); default: return array( 'header' => $header, 'label' => __( 'Not set', 'postnl-for-woocommerce' ), 'color' => '#757575', 'summary' => __( 'You have not entered your API key yet.', 'postnl-for-woocommerce' ), 'description' => $has_old // translators: %s is a link to the PostNL Business Portal Self Service module. ? sprintf( __( 'Request your new key from the %s and enter it above to switch over from your old key.', 'postnl-for-woocommerce' ), $link ) // translators: %s is a link to the PostNL Business Portal Self Service module. : sprintf( __( 'Request your key from the %s and enter it above — the plug-in needs it to connect to PostNL.', 'postnl-for-woocommerce' ), $link ), ); } } /** * The failure reason to describe in the status row. Missing customer details * is decided live — if they are blank now, that is the reason whatever the * last save recorded — otherwise the reason persisted at save time is used. * * @param bool $is_sandbox Environment to resolve for. * * @return string One of the Key_Validator REASON_* slugs, or ''. */ protected function resolve_new_key_status_reason( $is_sandbox ) { if ( '' === trim( (string) $this->get_customer_code() ) || '' === trim( (string) $this->get_customer_num() ) ) { return Key_Validator::REASON_MISSING; } return (string) get_option( self::NEW_API_KEY_STATUS_OPTION . ( $is_sandbox ? '_sandbox' : '' ), '' ); } /** * Label, colour, summary and description for an entered key that is not in * use, per reason. Only a genuine rejection is red "Not valid"; an outage, a * mismatch or missing details read as amber "could not check", so an outage * never tells the merchant their key is invalid. Whether an old key is still * carrying the plugin is passed in rather than re-read, so the copy is correct * on a fresh install (where nothing is in use). * * @param string $reason One of the Key_Validator REASON_* slugs. * @param bool $has_old Whether a usable old key is still stored. * @param string $link Self Service link markup. * * @return array{label:string,color:string,summary:string,description:string} */ protected function get_new_key_invalid_copy( $reason, $has_old, $link ) { $tail = $has_old ? __( ' The plug-in is still using your old key.', 'postnl-for-woocommerce' ) : __( ' The plug-in has no working key to connect with yet.', 'postnl-for-woocommerce' ); switch ( $reason ) { case Key_Validator::REASON_REJECTED: return array( 'label' => __( 'Could not check', 'postnl-for-woocommerce' ), 'color' => '#dba617', 'summary' => __( 'PostNL could not process the check.', 'postnl-for-woocommerce' ) . $tail, 'description' => __( 'This usually means the Customer Code or Customer Number does not match this key. Check them and save again.', 'postnl-for-woocommerce' ), ); case Key_Validator::REASON_MISSING: return array( 'label' => __( 'Not checked', 'postnl-for-woocommerce' ), 'color' => '#dba617', 'summary' => __( 'We have not checked this key yet.', 'postnl-for-woocommerce' ) . $tail, 'description' => __( 'Fill in your Customer Code and Customer Number, then save again.', 'postnl-for-woocommerce' ), ); case Key_Validator::REASON_UNREACHABLE: return array( 'label' => __( 'Could not check', 'postnl-for-woocommerce' ), 'color' => '#dba617', 'summary' => __( 'We could not reach PostNL to check this key.', 'postnl-for-woocommerce' ) . $tail, 'description' => __( 'This is usually temporary. Save again in a few minutes.', 'postnl-for-woocommerce' ), ); case Key_Validator::REASON_INVALID: default: return array( 'label' => __( 'Not valid', 'postnl-for-woocommerce' ), 'color' => '#d63638', 'summary' => __( 'PostNL rejected this key.', 'postnl-for-woocommerce' ) . $tail, // translators: %s is a link to the PostNL Business Portal Self Service module. 'description' => sprintf( __( 'Check that you copied the whole key with no extra spaces, then save again. If it keeps failing, request a new key from the %s.', 'postnl-for-woocommerce' ), $link ), ); } } /** * Normalise a nullable environment flag to the current environment. * * @param bool|null $is_sandbox Environment flag, or null for the current one. * * @return bool */ protected function resolve_is_sandbox( $is_sandbox ) { return ( null === $is_sandbox ) ? $this->is_sandbox() : (bool) $is_sandbox; } /** * Get customer number from the settings. * * @return String */ public function get_customer_num() { return $this->get_country_option( 'customer_num', '' ); } /** * Get customer code from the settings. * * @return String */ public function get_customer_code() { return $this->get_country_option( 'customer_code', '' ); } /** * Get location code from the settings. * * @return String */ public function get_location_code() { /* Temporarily hardcoded. return $this->get_country_option( 'location_code', '' ); */ return '123456'; } /** * Return true if sandbox mode is ticked. * * @return String */ public function get_environment_mode() { return $this->get_country_option( 'environment_mode', '' ); } /** * Return true if sandbox mode is ticked. * * @return Bool */ public function is_sandbox() { return ( 'sandbox' === $this->get_environment_mode() ); } /** * Get return address default from the settings. * * @return String */ public function get_return_address_default() { return $this->get_country_option( 'return_address_default', '' ); } /** * Get return company name from the settings. * * @return String */ public function get_return_company_name() { return $this->get_country_option( 'return_company', '' ); } /** * Get value of the return address or reply number. * * @return bool */ public function is_return_to_home_enabled() { if ( 'yes' === $this->get_country_option( 'return_address_or_reply_no', '' ) ) { return true; } return false; } /** * Get value of the return address or reply number. * * @return bool */ public function is_smart_return_enabled() { if ( 'yes' === $this->get_country_option( 'activate_smart_return', '' ) ) { return true; } return false; } /** * Get return reply number from the settings. * * @return String */ public function get_return_reply_number() { return $this->get_country_option( 'return_replynumber', '' ); } /** * Get freepost city from the settings. * * @return String */ public function get_freepost_city() { return $this->get_country_option( 'freepost_city', '' ); } /** * Get freepost zipcode from the settings. * * @return String */ public function get_freepost_zipcode() { return $this->get_country_option( 'freepost_zip', '' ); } /** * Get return street address from the settings. * * @return String */ public function get_return_address_street() { return $this->get_country_option( 'return_address_street', '' ); } /** * Get return house number address from the settings. * * @return String */ public function get_return_address_house_no() { return $this->get_country_option( 'return_address_house_no', '' ); } /** * Get return house number extension address from the settings. * * @return String */ public function get_return_address_house_noext() { return $this->get_country_option( 'return_address_house_noext', '' ); } /** * Get return city address from the settings. * * @return String */ public function get_return_city() { return $this->get_country_option( 'return_address_city', '' ); } /** * Get return state address from the settings. * * @return String */ public function get_return_state() { return $this->get_country_option( 'return_address_state', '' ); } /** * Get return state address from the settings. * * @return String */ public function get_return_zipcode() { return $this->get_country_option( 'return_address_zip', '' ); } /** * Get return phone number from the settings. * * @return String */ public function get_return_phone() { return $this->get_country_option( 'return_phone', '' ); } /** * Get return email from the settings. * * @return String */ public function get_return_email() { return $this->get_country_option( 'return_email', '' ); } /** * Get return customer code from the settings. * * @return String */ public function get_return_customer_code() { return $this->get_country_option( 'return_customer_code', '' ); } /** * Get return shipment and labels select value. * * PostNL discontinues the "Shipment & Return" label product on 1 July 2026. * Any value still stored as 'shipping_return' is coerced to 'none' at read * time so outbound label generation never requests the discontinued product, * even on stores the one-time revert migration has not reached. * * @return String */ public function get_return_shipment_and_labels() { $value = $this->get_country_option( 'return_shipment_and_labels', '' ); return 'shipping_return' === $value ? 'none' : $value; } /** * Get value of the return shipment and labels all checkbox. * * @return String */ public function get_return_shipment_and_labels_all() { return $this->get_country_option( 'return_shipment_and_labels_all', '' ); } /** * Get return customer code from the settings. * * @return String */ public function get_return_direct_print_label() { return $this->get_country_option( 'return_direct_print_label', '' ); } /** * Return true if 'print returnlabel directly with shipping label' field is ticked. * * @return Bool */ public function is_return_direct_print_enabled() { return ( 'yes' === $this->get_return_direct_print_label() ); } /** * Get the letterboxparcel (24 hours) extra fee from the settings. * * @since 5.9.6 * * @return float */ public function get_letterbox_24_fee() { return (float) $this->get_country_option( 'letterbox_24_fee' ); } /** * Get enable delivery from the settings. * * @return String */ public function get_enable_delivery() { return $this->get_country_option( 'enable_delivery', '' ); } /** * Return true if delivery field is ticked. * * @return Bool */ public function is_delivery_enabled() { return ( 'yes' === $this->get_enable_delivery() ); } /** * Get enable pickup points from the settings. * * @return String */ public function get_enable_pickup_points() { return $this->get_country_option( 'enable_pickup_points' ); } /** * Get pick-up delivery fee from the settings. * * @return float */ public function get_pickup_delivery_fee() { return (float) $this->get_country_option( 'pickup_delivery_fee' ); } /** * Return true if delivery days field is ticked. * * @return Bool */ public function is_pickup_points_enabled() { return ( 'yes' === $this->get_enable_pickup_points() ); } /** * Get number pickup points from the settings. * * @return Int */ public function get_number_pickup_points() { /* Temporarily hardcoded. return $this->get_country_option( 'number_pickup_points' ); */ return 3; } /** * Get enable delivery days from the settings. * * @return String */ public function get_enable_delivery_days() { return $this->get_country_option( 'enable_delivery_days' ); } /** * Return true if delivery days field is ticked. * * @return Bool */ public function is_delivery_days_enabled() { return ( 'yes' === $this->get_enable_delivery_days() ); } /** * Get delivery days fees the settings. * * @return float */ public function get_delivery_days_fee() { return (float) $this->get_country_option( 'delivery_days_fee' ); } /** * Get letterbox fee from the settings. * Returns null when the field is empty (meaning: do not override). * * @return float|null */ public function get_letterbox_fee() { $value = $this->get_country_option( 'letterbox_fee' ); return ( '' !== $value ) ? (float) $value : null; } /** * Get number delivery days from the settings. * * @return Int */ public function get_number_delivery_days() { return $this->get_country_option( 'number_delivery_days' ); } /** * Get default checkout tab from the settings. * * @return string 'delivery_day' or 'dropoff_points' */ public function get_default_checkout_tab() { $value = $this->get_country_option( 'default_checkout_tab' ); if ( in_array( $value, array( 'delivery_day', 'dropoff_points' ), true ) ) { return $value; } if ( 'BE' === Utils::get_base_country() ) { // TODO: gate on is_pickup_points_enabled(). When pickup is disabled // we still return 'dropoff_points' here, an id no tab list contains. // Container.php and the React resolver both fall back, so it's // harmless today, but the contract should be: never return an id // that can't render. See PR #306 review. return 'dropoff_points'; } return 'delivery_day'; } /** * Get enable evening delivery from the settings. * * @return String */ public function get_enable_evening_delivery() { return $this->get_country_option( 'enable_evening_delivery' ); } /** * Return true if evening delivery field is ticked. * * @return Bool */ public function is_evening_delivery_enabled() { return ( 'yes' === $this->get_enable_evening_delivery() ); } /** * Get evening delivery fee from the settings. * * @return String */ public function get_evening_delivery_fee() { return $this->get_country_option( 'evening_delivery_fee' ); } /** * Get enable morning delivery from the settings. * * @return String */ public function get_enable_morning_delivery() { return $this->get_country_option( 'enable_morning_delivery' ); } /** * Return true if evening delivery field is ticked. * * @return Bool */ public function is_morning_delivery_enabled() { return ( 'yes' === $this->get_enable_morning_delivery() ); } /** * Get evening delivery fee from the settings. * * @return String */ public function get_morning_delivery_fee() { return $this->get_country_option( 'morning_delivery_fee' ); } /** * Get transit time value from the settings. * * @return String */ public function get_transit_time() { return $this->get_country_option( 'transit_time', '' ); } /** * Get cut off time value from the settings. * * @return String */ public function get_cut_off_time() { return $this->get_country_option( 'cut_off_time', '' ); } /** * Get dropoff monday value from the settings. * * @return String */ public function get_dropoff_monday() { return $this->get_country_option( 'dropoff_day_mon', '' ); } /** * Return true if dropoff monday field is ticked. * * @return Bool */ public function is_dropoff_monday_enabled() { return ( 'yes' === $this->get_dropoff_monday() ); } /** * Get dropoff tuesday value from the settings. * * @return String */ public function get_dropoff_tuesday() { return $this->get_country_option( 'dropoff_day_tue', '' ); } /** * Return true if dropoff tuesday field is ticked. * * @return Bool */ public function is_dropoff_tuesday_enabled() { return ( 'yes' === $this->get_dropoff_tuesday() ); } /** * Get dropoff wednesday value from the settings. * * @return String */ public function get_dropoff_wednesday() { return $this->get_country_option( 'dropoff_day_wed', '' ); } /** * Return true if dropoff wednesday field is ticked. * * @return Bool */ public function is_dropoff_wednesday_enabled() { return ( 'yes' === $this->get_dropoff_wednesday() ); } /** * Get dropoff thursday value from the settings. * * @return String */ public function get_dropoff_thursday() { return $this->get_country_option( 'dropoff_day_thu', '' ); } /** * Return true if dropoff thursday field is ticked. * * @return Bool */ public function is_dropoff_thursday_enabled() { return ( 'yes' === $this->get_dropoff_thursday() ); } /** * Get dropoff friday value from the settings. * * @return String */ public function get_dropoff_friday() { return $this->get_country_option( 'dropoff_day_fri', '' ); } /** * Return true if dropoff friday field is ticked. * * @return Bool */ public function is_dropoff_friday_enabled() { return ( 'yes' === $this->get_dropoff_friday() ); } /** * Get dropoff saturday value from the settings. * * @return String */ public function get_dropoff_saturday() { return $this->get_country_option( 'dropoff_day_sat', '' ); } /** * Return true if dropoff saturday field is ticked. * * @return Bool */ public function is_dropoff_saturday_enabled() { return ( 'yes' === $this->get_dropoff_saturday() ); } /** * Get dropoff sunday value from the settings. * * @return String */ public function get_dropoff_sunday() { return $this->get_country_option( 'dropoff_day_sun', '' ); } /** * Return true if dropoff sunday field is ticked. * * @return Bool */ public function is_dropoff_sunday_enabled() { return ( 'yes' === $this->get_dropoff_sunday() ); } /** * Get dropoff days from the settings. * * @return array */ public function get_dropoff_days() { $dropoff_days = array(); if ( $this->is_dropoff_monday_enabled() ) { $dropoff_days[] = 'mon'; } if ( $this->is_dropoff_tuesday_enabled() ) { $dropoff_days[] = 'tue'; } if ( $this->is_dropoff_wednesday_enabled() ) { $dropoff_days[] = 'wed'; } if ( $this->is_dropoff_thursday_enabled() ) { $dropoff_days[] = 'thu'; } if ( $this->is_dropoff_friday_enabled() ) { $dropoff_days[] = 'fri'; } if ( $this->is_dropoff_saturday_enabled() ) { $dropoff_days[] = 'sat'; } if ( $this->is_dropoff_sunday_enabled() ) { $dropoff_days[] = 'sun'; } return $dropoff_days; } /** * Get excluded dropoff days from the settings. * * @return array */ public function get_excluded_dropoff_days() { $completed_days = array_keys( Utils::days_of_week() ); $dropoff_days = $this->get_dropoff_days(); return array_diff( $completed_days, $dropoff_days ); } /** * Get globalpack type barcode from the settings. * * @return String */ public function get_globalpack_barcode_type() { return $this->get_country_option( 'globalpack_barcode_type', '' ); } /** * Get globalpack customer code from the settings. * * @return String */ public function get_globalpack_customer_code() { return $this->get_country_option( 'globalpack_customer_code', '' ); } /** * Get HS Tariff code from the settings. * * @return String */ public function get_hs_tariff_code() { return $this->get_country_option( 'hs_tariff_code', '' ); } /** * Get HS Tariff code from the settings. * * @return String */ public function get_country_origin() { return $this->get_country_option( 'country_origin', '' ); } /** * Get label format from the settings. * * @return String */ public function get_label_format() { return $this->get_country_option( 'label_format', '' ); } /** * Get printer type from the settings. * PDF by default. * * @return String */ public function get_printer_type() { $printer_type = $this->get_country_option( 'printer_type', '' ); $resolution = (int) $this->get_country_option( 'printer_type_resolution', 600 ); switch ( $printer_type ) { case 'JPG': return sprintf( 'GraphicFile|JPG %d dpi', $resolution ); case 'GIF': return sprintf( 'GraphicFile|GIF %d dpi', $resolution ); case 'ZPL': return sprintf( 'Zebra|Generic ZPL II %d dpi', $resolution ); default: return 'GraphicFile|PDF'; } } /** * Get shipping confirmation email text value from the settings. * * @return String */ public function get_shipping_confirmation_email() { return $this->get_country_option( 'shipping_confirmation_email', '' ); } /** * Get woocommerce email checkbox value from the settings. * * @return String */ public function get_woocommerce_email() { return $this->get_country_option( 'woocommerce_email', '' ); } /** * Return true if woocommerce email field is ticked. * * @return Bool */ public function is_woocommerce_email_enabled() { return ( 'yes' === $this->get_woocommerce_email() ); } /** * Get woocommerce email text value from the settings. * * @return String */ public function get_woocommerce_email_text() { return $this->get_country_option( 'woocommerce_email_text', '' ); } /** * Get check Netherlands address value from the settings. * * @return String */ public function get_validate_nl_address() { return $this->get_country_option( 'validate_nl_address', '' ); } /** * Return true if check Netherlands address field is ticked. * * @return Bool */ public function is_validate_nl_address_enabled() { return ( 'yes' === $this->get_validate_nl_address() ); } /** * Get reorder Netherlands address value from the settings. * * @return String */ public function get_reorder_nl_address() { return $this->get_country_option( 'reorder_nl_address', '' ); } /** * Return true if reorder Netherlands address field is ticked. * * @return Bool */ public function is_reorder_nl_address_enabled() { return ( 'yes' === $this->get_reorder_nl_address() ); } /** * Get enable logging value from the settings. * * @return String */ public function get_enable_logging() { return $this->get_country_option( 'enable_logging', '' ); } /** * Return true if enable logging field is ticked. * * @return Bool */ public function is_logging_enabled() { return ( 'yes' === $this->get_enable_logging() ); } /** * Get all shipping options. * * @param string $zone Shipping zone, available options: 'ne' - to Netherlands, 'be' - to Belgium, 'eu' - to European Union, 'row' - international shipping. * * @return array */ public function get_default_shipping_options( $zone ) { $shipping_options = $this->get_country_option( 'default_shipping_options_' . strtolower( $zone ), '' ); return Utils::prepare_shipping_options( $shipping_options ); } /** * Get default automatic letterboxparcel product from the settings. * * @since 5.9.6 * * @return String */ public function get_default_automatic_letterboxparcel_product() { return $this->get_country_option( 'default_automatic_letterboxparcel_product', 'letterbox' ); } /** * Return array of shipping methods. * * @return array. */ public function get_shipping_methods() { return wp_list_pluck( WC()->shipping()->shipping_methods, 'method_title', 'id' ); } /** * Get supported shipping methods from the settings. * * @return array. */ public function get_supported_shipping_methods() { $suppoted_shipping_methods = (array) $this->get_option( 'supported_shipping_methods' ); // Add PostNL method by default $suppoted_shipping_methods[] = POSTNL_SETTINGS_ID; return $suppoted_shipping_methods; } /** * Get Automatically change order status to Completed value from the settings. * * @return String */ public function get_auto_complete_order() { return $this->get_country_option( 'auto_complete_order', '' ); } /** * Return true if Automatically change order status to Completed is ticked. * * @return Bool */ public function is_auto_complete_order_enabled() { return ( 'yes' === $this->get_auto_complete_order() ); } }