Plugin Directory

Changeset 3361859


Ignore:
Timestamp:
09/15/2025 02:22:21 PM (6 months ago)
Author:
IQComputing
Message:

Update to version 1.0.4 from GitHub

Location:
live-rates-for-shipstation
Files:
20 edited
1 copied

Legend:

Unmodified
Added
Removed
  • live-rates-for-shipstation/tags/1.0.4/changelog.txt

    r3339837 r3361859  
    22
    33This is a brief text document keeping track of changes to the plugin. For a full history, see the Github Repository.
     4
     5= 1.0.4 =
     6
     7Relase Date: September 15, 2025
     8
     9* Overview
     10    * Patches an issue where the exports would not take the Stores set product units.
     11    * Adds functionality for Flat Rate Adjustments on a global scale.
     12    * Adds functionality for Flat Rate Adjustments on a per service scale.
     13    * Shoutouts to both @centuryperf and @jkmail120 for reporting these issues!
     14
     15* Code Updates
     16    * \IQLRSS\Core\Settings_Shipstation::export_shipstation_shipping_method()
     17        * Hopefully better ShipStation integration when orders export.
     18    * \IQLRSS\Core\Shipping_Method_Shipstation::get_individual_requests()
     19        * Updates to garb the store dimensions.
     20    * \IQLRSS\Core\Shipping_Method_Shipstation::calculate_shipping()
     21        * Updates for flatrate calculations.
    422
    523= 1.0.3 =
  • live-rates-for-shipstation/tags/1.0.4/core/assets/admin.css

    r3339099 r3361859  
    2424#customBoxes input[type=text]                   {-moz-appearance: textfield;}
    2525
     26.iqrlssimple-flex-2       {display: flex;}
     27.iqrlssimple-flex-2 > *   {flex: 1 1 calc( 50% - 8px );}
     28.iqrlssimple-flex-2 > :first-child    {flex-basis: fit-content; padding-right: 4px;}
     29.iqrlssimple-flex-2 > :last-child     {padding-left: 4px;}
     30
    2631.iqlrss-api-row fieldset                            {position: relative; display: block; width: fit-content;}
    2732.iqlrss-api-row #iqlrssVerifyButton                 {position: absolute; top: 0px; right: 0; margin-top: -1px; margin-right: -85px;}
  • live-rates-for-shipstation/tags/1.0.4/core/assets/modules/settings.js

    r3339099 r3361859  
    44 * Not really meant to be used as an object but more for
    55 * encapsulation and organization.
     6 *
     7 * @todo Populate (or recreate) Carriers Select2 whenever API is verified.
    68 *
    79 * @global {Object} iqlrss - Localized object of saved values.
     
    3436
    3537        this.apiClearCache();
    36         this.priceAdjustmentNumbersOnly();
    37         this.singleLowestSetup();
     38        this.setupPriceAdjustments();
     39        this.setupSingleLowest();
    3840
    3941    }
     
    158160                const $row = $elm.closest( 'tr' );
    159161                if( ! $row || 'none' != $row.style.display ) return;
     162
     163                /* Skip the Return Lowest Label if related isn't checked */
     164                if( -1 != $elm.name.indexOf( 'global_adjustment' ) && '' == document.querySelectorAll( '[name*=global_adjustment_type]' ).value ) {
     165                    return;
     166                }
    160167
    161168                /* Skip the Return Lowest Label if related isn't checked */
     
    293300     * Only allow numbers for the Price Adjustment input.
    294301     */
    295     priceAdjustmentNumbersOnly() {
    296 
    297         const $adjustmentInput = document.querySelector( '[type=text][name*=global_adjustment' );
     302    setupPriceAdjustments() {
     303
     304        const $adjustmentSelect = document.querySelector( 'select[name*=global_adjustment_type]' );
     305        const $adjustmentInput  = document.querySelector( '[type=text][name*=global_adjustment' );
     306
     307        /* Select Change - Show Input Row */
     308        $adjustmentSelect.addEventListener( 'change', ( e ) => {
     309            $adjustmentInput.value = '';
     310            this.rowMakeVisible( $adjustmentInput.closest( 'tr' ), ( e.target.value ) )
     311        } );
     312
     313        /* Input Update - Only FloatString */
    298314        $adjustmentInput.addEventListener( 'input', ( e ) => {
    299             e.target.value = e.target.value.replace( /[^0-9.]/g, '' );
     315            e.target.value = e.target.value.replace( /(\..*?)\./g, '$1' ).replace( /[^0-9.]/g, '' );
    300316        } );
    301317
     
    306322     * Show / Hide the Single Lowest label
    307323     */
    308     singleLowestSetup() {
     324    setupSingleLowest() {
    309325
    310326        const $lowestcb     = document.querySelector( '[type=checkbox][name*=return_lowest' );
     
    320336
    321337        /* Eh, just trigger it */
    322         if( 'none' != $lowestcb.closest( 'tr' ).style.display ) {
     338        if( $lowestcb.checked && 'none' == $lowestLabel.closest( 'tr' ).style.display ) {
    323339            $lowestcb.dispatchEvent( new Event( 'change' ) );
    324340        }
     
    337353        if( visible ) {
    338354
    339             $row.setAttribute( 'style', 'opacity:0' );
     355            if( null !== $row.offsetParent ) return;
     356
     357            $row.style = 'opacity:0';
    340358            $row.animate( {
    341359                opacity: [ 1 ]
     
    350368            }, {
    351369                duration: 300
    352             } ).onfinish = () => $row.setAttribute( 'style', 'display:none;' );
     370            } ).onfinish = () => $row.style = 'display:none;';
    353371
    354372        }
     
    374392        $err.remove();
    375393
    376         $err.setAttribute( 'style', 'height:0px;opacity:0;overflow:hidden;' );
     394        $err.style = 'height:0px;opacity:0;overflow:hidden;';
    377395        $row.querySelector( 'fieldset' ).appendChild( $err );
    378396
  • live-rates-for-shipstation/tags/1.0.4/core/assets/modules/shipping-zone.js

    r3339099 r3361859  
    1919        this.customBoxesRemove();
    2020
     21        this.setupPriceAdjustments()
    2122        this.inputsNumbersOnly();
    2223        this.wooAccommodations();
     
    4243
    4344                document.querySelectorAll( '#customBoxes [name]' ).forEach( ( $elm ) => {
    44                     if( 'text' == $elm.getAttribute( 'type' ) ) $elm.removeAttribute( 'required' );
     45                    if( 'text' == $elm.type ) $elm.removeAttribute( 'required' );
    4546                } );
    4647                document.getElementById( 'customBoxes' ).style.display = 'none';
     
    8081            $clone.classList.remove( 'mimic' );
    8182            $clone.querySelectorAll( '[name]' ).forEach( ( $elm ) => {
    82                 $elm.setAttribute( 'name', $elm.getAttribute( 'name' ).replace( 'mimic', count ) );
    83                 if( 'text' == $elm.getAttribute( 'type' ) && -1 == $elm.getAttribute( 'name' ).indexOf( '[wm]' ) ) $elm.setAttribute( 'required', true );
     83                $elm.name = $elm.name.replace( 'mimic', count );
     84                if( 'text' == $elm.type && -1 == $elm.name.indexOf( '[wm]' ) ) $elm.required = true;
    8485            } );
    8586
     
    116117
    117118    /**
     119     * Price Adjustments
     120     * Manage the show/hide functionality.
     121     */
     122    setupPriceAdjustments() {
     123
     124        /**
     125         * Adjustment Type Change
     126         * Show / Hide Price Input
     127         */
     128        document.addEventListener( 'change', ( e ) => {
     129
     130            if( 'SELECT' != e.target.tagName ) return;
     131            if( -1 == e.target.name.indexOf( 'adjustment_type' ) ) return;
     132
     133            const $adjustmentSelect = e.target;
     134            const $adjustmentInput  = $adjustmentSelect.closest( 'td' ).querySelector( 'input' );
     135
     136            if( '' == $adjustmentSelect.value ) {
     137
     138                $adjustmentInput.animate( {
     139                    opacity: 0
     140                }, {
     141                    duration: 300,
     142                    fill: 'forwards',
     143                } ).onfinish = () => {
     144                    $adjustmentInput.value = '';
     145                    $adjustmentInput.classList.add( 'iqlrss-hide' );
     146                };
     147
     148            } else if( null === $adjustmentInput.offsetParent ) {
     149
     150                $adjustmentInput.classList.remove( 'iqlrss-hide' );
     151                $adjustmentInput.animate( {
     152                    opacity: [0, 1]
     153                }, {
     154                    duration: 300,
     155                    fill: 'forwards',
     156                } ).onfinish = () => {
     157                    $adjustmentInput.value = '';
     158                };
     159
     160            } else {
     161                $adjustmentInput.value = ( $adjustmentSelect.value != iqlrss.global_adjustment_type ) ? '0' : '';
     162            }
     163
     164        } );
     165
     166    }
     167
     168
     169    /**
    118170     * Only allow numbers in inputs.
    119171     */
    120172    inputsNumbersOnly() {
    121173
     174        /**
     175         * All Custom Packing Box inputs.
     176         * Any numbers-only classes
     177         */
    122178        document.addEventListener( 'input', ( e ) => {
    123179            if( 'INPUT' !== e.target.tagName ) return;
    124             if( -1 != e.target.getAttribute( 'name' ).indexOf( 'custombox' ) || e.target.classList.contains( 'iqlrss-numbers-only' ) ) {
    125                 e.target.value = e.target.value.replace( /[^0-9.]/g, '' );
     180            if( -1 != e.target.name.indexOf( 'custombox' ) || e.target.classList.contains( 'iqlrss-numbers-only' ) ) {
     181                e.target.value = e.target.value.replace( /(\..*?)\./g, '$1' ).replace( /[^0-9.]/g, '' );
    126182            }
    127183        } );
  • live-rates-for-shipstation/tags/1.0.4/core/settings-shipstation.php

    r3339099 r3361859  
    8888
    8989        $data = array(
    90             'api_verified' => \IQLRSS\Driver::get_ss_opt( 'api_key_valid', false, true ),
     90            'api_verified' => \IQLRSS\Driver::get_ss_opt( 'api_key_valid', false ),
     91            'global_adjustment_type' => \IQLRSS\Driver::get_ss_opt( 'global_adjustment_type', '' ),
    9192            'rest' => array(
    9293                'nonce'     => wp_create_nonce( 'wp_rest' ),
     
    100101                'confirm_box_removal'   => esc_html__( 'Please confirm you would like to completely remove (x) custom boxes.', 'live-rates-for-shipstation' ),
    101102                'error_rest_generic'    => esc_html__( 'Something went wrong with the REST Request. Please resave permalinks and try again.', 'live-rates-for-shipstation' ),
    102                 'error_verification_required' => esc_html__( 'Please click the Verify API button to ensure a connection exists.', 'live-rates-for-shipstation' ),
     103                'error_verification_required'       => esc_html__( 'Please click the Verify API button to ensure a connection exists.', 'live-rates-for-shipstation' ),
     104                'desc_global_adjustment_percentage' => esc_html__( 'Example: IF UPS Ground is $7.25 and you input 15% ($1.08), the final shipping rate the customer sees is: $8.33', 'live-rates-for-shipstation' ),
     105                'desc_global_adjustment_flatrate'   => esc_html__( 'Example: IF UPS Ground is $5.50 and you input $2.37, the final shipping rate the customer sees is: $7.87', 'live-rates-for-shipstation' ),
    103106            ),
    104107        );
     
    106109        ?><script type="text/javascript">
    107110
     111            /* JS Localization */
    108112            const iqlrss = JSON.parse( '<?php echo wp_json_encode( $data ); ?>' );
    109113
    110             <?php
    111                 /**
    112                  * Modules load too late to effectively immediately hide elements.
    113                  * This runs on the ShipStation settings page to hide additional
    114                  * settings whenever the API is unauthenticated.
    115                  */
    116                 if( ! $data['api_verified'] ) :
    117             ?>
    118 
    119                 if( document.getElementById( 'woocommerce_shipstation_iqlrss_api_key' ) ) { ( () => {
     114            /* Early setting field JS */
     115            if( document.getElementById( 'woocommerce_shipstation_iqlrss_api_key' ) ) { ( function() {
     116
     117                /* Hide an element, ezpz */
     118                const fnHide = ( $el ) => $el.closest( 'tr' ).style.display = 'none';
     119
     120                <?php
     121                    /**
     122                     * Modules load too late to effectively immediately hide elements.
     123                     * This runs on the ShipStation settings page to hide additional
     124                     * settings whenever the API is unauthenticated.
     125                     */
     126                    if( ! $data['api_verified'] ) :
     127                ?>
     128
    120129                    document.querySelectorAll( '[name*=iqlrss]' ).forEach( ( $elm ) => {
    121                         if( $elm.getAttribute( 'name' ).includes( 'api_key' ) ) return;
    122                         if( $elm.getAttribute( 'name' ).includes( 'cart_weight' ) ) return;
    123                         $elm.closest( 'tr' ).style.display = 'none';
     130                        if( $elm.name.includes( 'api_key' ) ) return;
     131                        if( $elm.name.includes( 'cart_weight' ) ) return;
     132                        fnHide( $elm );
    124133                    } );
    125                 } )(); }
    126 
    127             <?php endif; ?>
     134
     135                <?php else : ?>
     136
     137                    document.querySelectorAll( '[name*=iqlrss]' ).forEach( ( $elm ) => {
     138
     139                        if( 'checkbox' == $elm.type && $elm.name.includes( 'return_lowest' ) && ! $elm.checked ) {
     140                            fnHide( document.querySelector( '[name*=return_lowest_label]' ) );
     141                        }
     142
     143                        if( $elm.name.includes( 'global_adjustment_type' ) && '' == $elm.value ) {
     144                            fnHide( document.querySelector( '[type=text][name*=global_adjustment]' ) );
     145                        }
     146                    } );
     147
     148                <?php endif; ?>
     149
     150            } )(); }
     151
    128152        </script><?php
    129153
     
    155179    public function display_cart_weight() {
    156180
    157         $show_weight = \IQLRSS\Driver::get_ss_opt( 'cart_weight', 'no', true );
     181        $show_weight = \IQLRSS\Driver::get_ss_opt( 'cart_weight', 'no' );
    158182        if( 'no' == $show_weight ) return;
    159183
     
    289313
    290314        // Set transient to clear any WC_Session caches if they are found.
    291         $time = absint( apply_filters( 'wc_session_expiration', DAY_IN_SECONDS * 2 ) );
    292         set_transient( \IQLRSS\Driver::plugin_prefix( 'wcs_timeout' ), time(), $time );
     315        $expires = absint( apply_filters( 'wc_session_expiration', DAY_IN_SECONDS * 2 ) );
     316        set_transient( \IQLRSS\Driver::plugin_prefix( 'wcs_timeout' ), time(), $expires );
    293317
    294318    }
     
    326350        add_filter( 'woocommerce_shipping_methods',                         array ($this, 'append_shipstation_method' ) );
    327351        add_filter( 'woocommerce_settings_api_form_fields_shipstation',     array( $this, 'append_shipstation_integration_settings' ) );
     352        add_filter( 'woocommerce_settings_api_sanitized_fields_shipstation',array( $this, 'save_shipstation_integration_settings' ) );
     353        add_filter( 'woocommerce_shipstation_export_get_order',             array( $this, 'export_shipstation_shipping_method' ) );
    328354
    329355    }
     
    366392            }
    367393        }
     394
     395        // Backwards compatibility for v1.0.3 when only percentage was supported by default.
     396        $global_adjustment = \IQLRSS\Driver::get_ss_opt( 'global_adjustment', '0' );
     397        $adjustment_type_default = ( empty( $global_adjustment_type ) && ! empty( $global_adjustment ) ) ? 'percentage' : '';
    368398
    369399        foreach( $fields as $key => $field ) {
     
    391421                );
    392422
     423                $appended_fields[ \IQLRSS\Driver::plugin_prefix( 'global_adjustment_type' ) ] = array(
     424                    'title'         => esc_html__( 'Shipping Price Adjustment', 'live-rates-for-shipstation' ),
     425                    'type'          => 'select',
     426                    'options'       => \IQLRSS\Core\Shipping_Method_Shipstation::get_adjustment_types( true ),
     427                    'description'   => esc_html__( 'This adjustment is added on top of the returned shipping rates to help you cover shipping costs. Can be overridden per zone, per service.', 'live-rates-for-shipstation' ),
     428                    'default'       => $adjustment_type_default,
     429                );
     430
    393431                $appended_fields[ \IQLRSS\Driver::plugin_prefix( 'global_adjustment' ) ] = array(
    394                     'title'         => esc_html__( 'Shipping Price Adjustment (%)', 'live-rates-for-shipstation' ),
     432                    'title'         => esc_html__( 'Global Price Adjustment', 'live-rates-for-shipstation' ),
    395433                    'type'          => 'text',
    396                     'placeholder'   => '0%',
    397                     'description'   => esc_html__( 'This percent is added on top of the returned shipping rates to help you cover shipping costs. Can be overridden per zone, per service.', 'live-rates-for-shipstation' ),
    398                     'desc_tip'      => esc_html__( 'Example: IF UPS Ground is $7.25 - 15% would be $1.08 making the final rate: $8.33', 'live-rates-for-shipstation' ),
     434                    'placeholder'   => '0',
     435                    'description'   => esc_html__( 'Optional global ShipStation rate adjustment.', 'live-rates-for-shipstation' ),
    399436                    'default'       => '',
    400437                );
     
    431468
    432469
     470    /**
     471     * Modify the saved settings after WooCommerce has sanitized them.
     472     * Not much we need to do here, WooCommerce does most the heavy lifting.
     473     *
     474     * @param Array $settings
     475     *
     476     * @return Array $settings
     477     */
     478    public function save_shipstation_integration_settings( $settings ) {
     479
     480        // No API Key? Invalid!
     481        $api_key_key = \IQLRSS\Driver::plugin_prefix( 'api_key' );
     482        if( ! isset( $settings[ $api_key_key ] ) || empty( $settings[ $api_key_key ] ) ) {
     483           
     484            $settings[ \IQLRSS\Driver::plugin_prefix( 'api_key_valid' ) ] = false;
     485            if( isset( $settings[ \IQLRSS\Driver::plugin_prefix( 'api_key_vt' ) ] ) ) {
     486                unset( $settings[ \IQLRSS\Driver::plugin_prefix( 'api_key_vt' ) ] );
     487            }
     488        }
     489
     490        return $settings;
     491
     492    }
     493
     494
     495    /**
     496     * Update the WC_Order Shipping Method to match the ShipStation Carrier.
     497     * Ex. USPSPriorityMail
     498     *
     499     * @link https://help.shipstation.com/hc/en-us/articles/360025856192-Custom-Store-Development-Guide
     500     *
     501     * @param WC_Order $order
     502     *
     503     * @return WC_Order $order
     504     */
     505    public function export_shipstation_shipping_method( $order ) {
     506
     507        if( ! is_a( $order, 'WC_Order' ) ) {
     508            return $order;
     509        }
     510
     511        $methods = $order->get_shipping_methods();
     512        $plugin_method_id = \IQLRSS\Driver::plugin_prefix( 'shipstation' );
     513
     514        foreach( $methods as $method ) {
     515
     516            // Not our shipping method.
     517            if( $method->get_method_id() != $plugin_method_id ) continue;
     518
     519            $service_name = $method->get_meta( 'service_name', true );
     520            $carrier_name = $method->get_meta( 'carrier_name', true );
     521
     522            // Missing metadata.
     523            if( empty( $service_name ) || empty( $carrier_name ) ) continue;
     524
     525            $method->set_props( array(
     526                'name' => preg_replace( '/([^a-zA-Z0-9])/', '', sprintf( '%s %s', $carrier_name, $service_name ) ),
     527            ) );
     528            $method->apply_changes(); // Temporarily apply changes. This does not update the database.
     529
     530        }
     531
     532        return $order;
     533
     534    }
     535
     536
    433537
    434538    /**------------------------------------------------------------------------------------------------ **/
  • live-rates-for-shipstation/tags/1.0.4/core/shipping-method-shipstation.php

    r3339837 r3361859  
    3838
    3939    /**
     40     * Array of store specific settings.
     41     *
     42     * @var Array
     43     */
     44    protected $store_data = array(
     45        'weight_unit'   => '',
     46        'dim_unit'      => '', // Dimension
     47    );
     48
     49
     50    /**
    4051     * Array of global carriers
    4152     * There are the carriers saved in Integration settings.
     
    7990        $this->supports             = array( 'instance-settings' );
    8091
    81         $this->carriers = \IQLRSS\Driver::get_ss_opt( 'carriers', array(), true );
    82         $saved_key = \IQLRSS\Driver::get_ss_opt( 'api_key_valid', false, true );
     92        $this->carriers = \IQLRSS\Driver::get_ss_opt( 'carriers', array() );
     93        $saved_key = \IQLRSS\Driver::get_ss_opt( 'api_key_valid', false );
    8394
    8495        // Only show in Shipping Zones if API Key is invalid.
     
    8697            $this->supports[] = 'shipping-zones';
    8798        }
     99
     100        // Set the store unit term and associate it with ShipStations term.
     101        $this->store_data = array(
     102            'weight_unit'   => get_option( 'woocommerce_weight_unit', $this->store_data['weight_unit'] ),
     103            'dim_unit'      => get_option( 'woocommerce_dimension_unit', $this->store_data['dim_unit'] ),
     104        );
    88105
    89106        $this->init_instance_form_fields();
     
    180197        $settings       = get_option( 'woocommerce_shipstation_settings' );
    181198        $saved_services = $this->get_option( 'services', array() );
    182         $saved_carriers = \IQLRSS\Driver::get_ss_opt( 'carriers', array(), true );
     199        $saved_carriers = \IQLRSS\Driver::get_ss_opt( 'carriers', array() );
    183200        $shipStationAPI = $this->shipStationApi;
    184201
     
    186203
    187204            $sorted_services = array();
     205
     206            // See $this->validate_services_field()
    188207            foreach( $saved_services as $k => $s ) {
    189208
     
    233252     * Validate service field.
    234253     *
    235      * @param mixed $key - Field key.
     254     * @return Array $services
    236255     */
    237256    public function validate_services_field() {
     
    253272        $posted_services = wp_unslash( $_POST[ $prefix ] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
    254273
     274        // Global adjustment
     275        $global_adjustment      = \IQLRSS\Driver::get_ss_opt( 'global_adjustment', '' );
     276        $global_adjustment_type = \IQLRSS\Driver::get_ss_opt( 'global_adjustment_type', '' );
     277
    255278        // Group by Carriers then Services
    256279        $services = array();
     
    258281            foreach( $carrier_services as $service_code => $service_arr ) {
    259282
    260                 // Skip non-enabled and non-renamed services.
    261                 if( ! isset( $service_arr['enabled'] ) && empty( $service_arr['nickname'] ) ) continue;
    262 
    263283                $carrier_code = sanitize_text_field( $carrier_code );
    264284                $service_code = sanitize_text_field( $service_code );
    265                 $services[ $carrier_code ][ $service_code ] = array_filter( array(
     285                $data = array_filter( array(
    266286
    267287                    // User Input
     
    276296                ) );
    277297
    278                 // Allow 0 value user input.
    279                 if( $service_arr['adjustment'] >= 0 ) {
    280                     $services[ $carrier_code ][ $service_code ]['adjustment'] = floatval( $service_arr['adjustment'] );
     298                // The above removes empty values.
     299                // Price Adjustments
     300                $data['adjustment']     = ( $service_arr['adjustment'] ) ? floatval( $service_arr['adjustment'] ) : '';
     301                $data['adjustment_type']= $service_arr['adjustment_type'];
     302
     303                // Maybe unset if we don't need the data.
     304                if( $data['adjustment_type'] == $global_adjustment_type ) {
     305
     306                    // equal or equal empty -> 0 == ''
     307                    if( $data['adjustment'] == $global_adjustment || '' == $data['adjustment'] ) {
     308                        unset( $data['adjustment'] );
     309                        unset( $data['adjustment_type'] );
     310                    }
    281311                }
    282312
     313                /**
     314                 * We don't want to array_filter() since
     315                 * Global Adjust could be populated, and
     316                 * Service is set to '' (No Adjustment).
     317                 */
     318                $services[ $carrier_code ][ $service_code ] = $data;
     319
    283320            }
    284321        }
     
    292329     * Validate customboxes field.
    293330     *
    294      * @param mixed $key - Field key.
     331     * @return Array $boxes
    295332     */
    296333    public function validate_customboxes_field() {
     
    370407        }
    371408
    372         $global_upcharge = floatval( \IQLRSS\Driver::get_ss_opt( 'global_adjustment', 0, true ) );
    373         $packing_type   = $this->get_option( 'packing', 'individual' );
     409        $global_adjustment      = floatval( \IQLRSS\Driver::get_ss_opt( 'global_adjustment', 0 ) );
     410        $global_adjustment_type = \IQLRSS\Driver::get_ss_opt( 'global_adjustment_type','' );
     411        $global_adjustment_type = ( empty( $global_adjustment_type ) && ! empty( $global_adjustment ) ) ? 'percentage' : $global_adjustment_type;
     412
     413        $packing_type = $this->get_option( 'packing', 'individual' );
    374414        $request = array(
    375415            'from_country_code'  => WC()->countries->get_base_country(),
     
    444484
    445485                // Apply service upcharge
    446                 if( isset( $service_arr['adjustment'] ) && $service_arr['adjustment'] > 0 ) {
    447 
    448                     $adjustment = floatval( $saved_services[ $shiprate['carrier_code'] ]['adjustment'] );
    449                     $cost += ( $adjustment > 0 ) ? ( $cost * ( $adjustment / 100 ) ) : 0;
    450 
    451                 } else if( ! empty( $global_upcharge ) ) {
    452                     $cost += ( $cost * ( $global_upcharge / 100 ) );
     486                if( isset( $service_arr['adjustment'] ) ) {
     487
     488                    /**
     489                     * Adjustment type could be '' to skip global adjustment.
     490                     * Defaults to percentage for v1.03 backwards compatibility.
     491                     */
     492                    $adjustment      = floatval( $service_arr['adjustment'] );
     493                    $adjustment_type = ( isset( $service_arr['adjustment_type'] ) ) ? $service_arr['adjustment_type'] : 'percentage';
     494
     495                    if( ! empty( $adjustment_type ) && $adjustment > 0 ) {
     496                        $cost += ( 'flatrate' == $adjustment_type ) ? $adjustment : ( $cost * ( $adjustment / 100 ) );
     497                    }
     498
     499                } else if( ! empty( $global_adjustment_type ) && $global_adjustment > 0 ) {
     500                    $cost += ( 'flatrate' == $global_adjustment_type ) ? floatval( $global_adjustment ) : ( $cost * ( floatval( $global_adjustment ) / 100 ) );
    453501                }
    454502
     
    464512                        'dimensions'    => $req['dimensions'],
    465513                        'weight'        => $req['weight'],
     514                        'service_name'  => $shiprate['name'],
    466515                        'carrier_code'  => $shiprate['carrier_code'],
     516                        'carrier_name'  => $shiprate['carrier_name'],
    467517                    ),
    468518                );
     
    480530        }
    481531
    482         $single_lowest          = \IQLRSS\Driver::get_ss_opt( 'return_lowest', 'no', true );
    483         $single_lowest_label    = \IQLRSS\Driver::get_ss_opt( 'return_lowest_label', '', true );
     532        $single_lowest          = \IQLRSS\Driver::get_ss_opt( 'return_lowest', 'no' );
     533        $single_lowest_label    = \IQLRSS\Driver::get_ss_opt( 'return_lowest_label', '' );
    484534
    485535        // Add all shipping rates, let the user decide.
     
    547597                    esc_html__( 'Product ID #%1$d missing (%2$s) dimensions. Shipping calculations terminated.', 'live-rates-for-shipstation' ),
    548598                    $item['product_id'],
    549                     implode( ', ', array_diff_key( $this->dimension_keys, $physicals ) )
     599                    implode( ', ', array_diff_key( array(
     600                        'width'     => 'width',
     601                        'height'    => 'height',
     602                        'length'    => 'length',
     603                        'weight'    => 'weight',
     604                    ), $physicals ) )
    550605                ) );
    551606                return array();
     
    553608
    554609            $request['weight'] = array(
    555                 'value' => (float)max( 0.5, round( wc_get_weight( $physicals['weight'], 'lbs' ), 2 ) ),
    556                 'unit'  => 'pound',
     610                'value' => (float)round( wc_get_weight( $physicals['weight'], $this->store_data['weight_unit'] ), 2 ),
     611                'unit'  => $this->shipStationApi->convert_unit_term( $this->store_data['weight_unit'] ),
    557612            );
    558613
     
    562617
    563618            $request['dimensions'] = array(
    564                 'unit'      => 'inch',
    565                 'length'    => max( 1, round( wc_get_dimension( $physicals[2], 'in' ), 2 ) ),
    566                 'width'     => max( 1, round( wc_get_dimension( $physicals[1], 'in' ), 2 ) ),
    567                 'height'    => max( 1, round( wc_get_dimension( $physicals[0], 'in' ), 2 ) ),
     619                'length'    => round( wc_get_dimension( $physicals[2], $this->store_data['dim_unit'] ), 2 ),
     620                'width'     => round( wc_get_dimension( $physicals[1], $this->store_data['dim_unit'] ), 2 ),
     621                'height'    => round( wc_get_dimension( $physicals[0], $this->store_data['dim_unit'] ), 2 ),
     622                'unit'      => $this->shipStationApi->convert_unit_term( $this->store_data['dim_unit'] ),
    568623            );
    569624
     
    631686                    esc_html__( 'Product ID #%1$d missing (%2$s) dimensions. Shipping calculations terminated.', 'live-rates-for-shipstation' ),
    632687                    $item['product_id'],
    633                     implode( ', ', array_diff_key( $this->dimension_keys, $physicals ) )
     688                    implode( ', ', array_diff_key( array(
     689                        'width'     => 'width',
     690                        'height'    => 'height',
     691                        'length'    => 'length',
     692                        'weight'    => 'weight',
     693                    ), $physicals ) )
    634694                ) );
    635695                return array();
    636696            }
    637697
    638             $data['weight'] = (float)max( 0.5, round( wc_get_weight( $physicals['weight'], 'lbs' ), 2 ) );
    639 
    640             // Unset weight and sort dimensions
     698            $data['weight'] = (float)round( wc_get_weight( $physicals['weight'], $this->store_data['weight_unit'] ), 2 );
     699
     700            // Unset weight to exclude it from sort
    641701            unset( $physicals['weight'] );
    642702            sort( $physicals );
    643703
    644704            $data = array(
    645                 'length'    => max( 1, round( wc_get_dimension( $physicals[2], 'in' ), 2 ) ),
    646                 'width'     => max( 1, round( wc_get_dimension( $physicals[1], 'in' ), 2 ) ),
    647                 'height'    => max( 1, round( wc_get_dimension( $physicals[0], 'in' ), 2 ) ),
     705                'length'    => round( wc_get_dimension( $physicals[2], $this->store_data['dim_unit'] ), 2 ),
     706                'width'     => round( wc_get_dimension( $physicals[1], $this->store_data['dim_unit'] ), 2 ),
     707                'height'    => round( wc_get_dimension( $physicals[0], $this->store_data['dim_unit'] ), 2 ),
    648708            ) + $data;
    649709
     
    670730                'weight' => array(
    671731                    'value' => $package->weight,
    672                     'unit'  => 'pound',
     732                    'unit'  => $this->shipStationApi->convert_unit_term( $this->store_data['weight_unit'] ),
    673733                ),
    674734                'dimensions' => array(
    675                     'unit'      => 'inch',
    676735                    'length'    => $package->length,
    677736                    'width'     => $package->width,
    678737                    'height'    => $package->height,
     738                    'unit'      => $this->shipStationApi->convert_unit_term( $this->store_data['dim_unit'] ),
    679739                ),
    680740            );
     
    796856    /**------------------------------------------------------------------------------------------------ **/
    797857    /**
     858     * Return an array of Price Adjustment Type options.
     859     *
     860     * @return Array
     861     */
     862    public static function get_adjustment_types( $include_empty = false ) {
     863
     864        $types = array(
     865            'flatrate'   => esc_html__( 'Flat Rate', 'live-rates-for-shipstation' ),
     866            'percentage' => esc_html__( 'Percentage', 'live-rates-for-shipstation' ),
     867        );
     868
     869        return ( false == $include_empty ) ? $types : array_merge( array(
     870            '' => esc_html__( 'No Adjustments', 'live-rates-for-shipstation' ),
     871        ), $types );
     872
     873    }
     874
     875
     876    /**
    798877     * Log error in WooCommerce
    799878     * Passthru method - log what's given and give it back.
     
    808887    protected function log( $error, $level = 'debug', $context = array() ) {
    809888
    810         if( ! \IQLRSS\Driver::get_ss_opt( 'logging_enabled', false ) ) {
     889        if( ! \IQLRSS\Driver::get_ss_opt( 'logging_enabled', 0, true ) ) {
    811890            return $error;
    812891        }
  • live-rates-for-shipstation/tags/1.0.4/core/shipstation-api.php

    r3339099 r3361859  
    1515
    1616    /**
    17      * API Endpoint
     17     * Key prefix
    1818     *
    1919     * @var String
    2020     */
    21     protected $apiurl = 'https://api.shipstation.com'; // No trailing slash.
     21    protected $prefix;
     22
     23
     24    /**
     25     * Seconds to hold cache.
     26     * Defaults to 1 week.
     27     *
     28     * @var Integer
     29     */
     30    protected $cache_time;
     31
     32
     33    /**
     34     * Skip cache check
     35     *
     36     * @var Boolean
     37     */
     38    protected $skip_cache = false;
    2239
    2340
     
    3148
    3249    /**
    33      * Key prefix
     50     * The API Key
    3451     *
    3552     * @var String
    3653     */
    37     protected $prefix;
    38 
    39 
    40     /**
    41      * Skip cache check
    42      *
    43      * @var Boolean
    44      */
    45     protected $skip_cache = false;
    46 
    47 
    48     /**
    49      * The API Key
    50      *
    51      * @var String
    52      */
    53     private $key = '';
     54    private $key;
    5455
    5556
     
    6263
    6364        $this->prefix   = \IQLRSS\Driver::get( 'slug' );
    64         $this->key      = \IQLRSS\Driver::get_ss_opt( 'api_key', '', true );
     65        $this->key      = \IQLRSS\Driver::get_ss_opt( 'api_key', '' );
    6566        $this->skip_cache = (boolean)$skip_cache;
     67        $this->cache_time = defined( 'WEEK_IN_SECONDS' ) ? WEEK_IN_SECONDS : 604800;
    6668
    6769    }
     
    118120     * Return an array of carriers.
    119121     * While getting carriers, set services and packages.
     122     *
     123     * @link https://docs.shipstation.com/openapi/carriers
    120124     *
    121125     * @param String $carrier_code
     
    191195            // Cache Carriers
    192196            if( ! empty( $data['carriers'] ) ) {
    193                 set_transient( $trans_key, $data['carriers'], MONTH_IN_SECONDS );
     197                set_transient( $trans_key, $data['carriers'], $this->cache_time );
    194198            }
    195199
     
    198202                foreach( $data['services'] as $carrier_id => $service_arr ) {
    199203                    $service_key = sprintf( '%s_%s_services', $trans_key, $carrier_id );
    200                     set_transient( $service_key, $service_arr, MONTH_IN_SECONDS );
     204                    set_transient( $service_key, $service_arr, $this->cache_time );
    201205                }
    202206            }
     
    206210                foreach( $data['packages'] as $carrier_id => $package_arr ) {
    207211                    $package_key = sprintf( '%s_%s_packages', $trans_key, $carrier_id );
    208                     set_transient( $package_key, $package_arr, MONTH_IN_SECONDS );
     212                    set_transient( $package_key, $package_arr, $this->cache_time );
    209213                }
    210214            }
     
    269273    /**------------------------------------------------------------------------------------------------ **/
    270274    /**
     275     * Convert a WooCommerce unit term to a
     276     * ShipStation unit term.
     277     *
     278     * @param String $unit
     279     *
     280     * @return String $term
     281     */
     282    public function convert_unit_term( $unit ) {
     283
     284        $known = array(
     285            'kg'    => 'kilogram',
     286            'g'     => 'gram',
     287            'lbs'   => 'pound',
     288            'oz'    => 'ounce',
     289            'cm'    => 'centimeter',
     290            'in'    => 'inch',
     291        );
     292
     293        return ( isset( $known[ $unit ] ) ) ? $known[ $unit ] : $unit;
     294
     295    }
     296
     297
     298    /**
    271299     * Make an API Request
    272300     *
     
    345373     */
    346374    protected function get_endpoint_url( $endpoint ) {
    347         return sprintf( '%s/v2/%s/', $this->apiurl, $endpoint );
     375
     376        return sprintf( '%s/v2/%s/',
     377            'https://api.shipstation.com',
     378            $endpoint
     379        );
     380
    348381    }
    349382
     
    380413    protected function log( $error, $level = 'debug', $context = array() ) {
    381414
    382         if( ! \IQLRSS\Driver::get_ss_opt( 'logging_enabled', false ) ) {
     415        if( ! \IQLRSS\Driver::get_ss_opt( 'logging_enabled', 0, true ) ) {
    383416            return $error;
    384417        }
  • live-rates-for-shipstation/tags/1.0.4/core/views/services-table.php

    r3339099 r3361859  
    1818}
    1919
    20 $api_key = \IQLRSS\Driver::get_ss_opt( 'api_key', '', true );
    21 $global_adjustment = \IQLRSS\Driver::get_ss_opt( 'global_adjustment', '0', true );
     20$api_key = \IQLRSS\Driver::get_ss_opt( 'api_key', '' );
     21$global_adjustment = \IQLRSS\Driver::get_ss_opt( 'global_adjustment', '0' );
     22$global_adjustment_type = \IQLRSS\Driver::get_ss_opt( 'global_adjustment_type', '' );
     23$global_adjustment_type = ( empty( $global_adjustment_type ) && ! empty( $global_adjustment ) ) ? 'percentage' : $global_adjustment_type;
    2224
    2325?>
     
    3335                    <th style="width: 50px;"><?php esc_html_e( 'Enabled', 'live-rates-for-shipstation' ); ?></th>
    3436                    <th><?php esc_html_e( 'Name', 'live-rates-for-shipstation' ); ?></th>
    35                     <th><?php esc_html_e( 'Price Adjustment (%)', 'live-rates-for-shipstation' ); ?></th>
     37                    <th><?php esc_html_e( 'Price Adjustment', 'live-rates-for-shipstation' ); ?></th>
    3638                    <th><?php esc_html_e( 'Carrier', 'live-rates-for-shipstation' ); ?></th>
    3739                </tr>
     
    3941            <tbody><?php
    4042
    41                 if( empty( $api_key ) || ! \IQLRSS\Driver::get_ss_opt( 'api_key_valid', false, true ) ) {
     43                if( empty( $api_key ) || ! \IQLRSS\Driver::get_ss_opt( 'api_key_valid', false ) ) {
    4244                    print( '<tr><th colspan="4">' );
    4345                        echo wp_kses( sprintf(
     
    6264
    6365                        $saved_atts = array(
    64                             'enabled' => ( isset( $service_arr['enabled'] ) ) ? $service_arr['enabled'] : false,
    65                             'nickname' => ( isset( $service_arr['nickname'] ) ) ? $service_arr['nickname'] : '',
    66                             'adjustment' => ( isset( $service_arr['adjustment'] ) ) ? $service_arr['adjustment'] : '',
     66                            'enabled'           => ( isset( $service_arr['enabled'] ) ) ? $service_arr['enabled'] : false,
     67                            'nickname'          => ( isset( $service_arr['nickname'] ) ) ? $service_arr['nickname'] : '',
     68                            'adjustment_type'   => ( isset( $service_arr['adjustment_type'] ) ) ? $service_arr['adjustment_type'] : $global_adjustment_type,
     69                            'adjustment'        => ( isset( $service_arr['adjustment'] ) ) ? $service_arr['adjustment'] : '',
    6770                        );
    6871
     
    99102
    100103                            // Service Price Adjustment
    101                             printf( '<td><input type="text" name="%s" value="%s" placeholder="%s" style="max-width:60px;"></td>',
    102                                 esc_attr( $attr_name . '[adjustment]' ),
    103                                 esc_attr( $saved_atts['adjustment'] ),
    104                                 esc_attr( $global_adjustment . '%' ),
    105                             );
     104                            print( '<td><div class="iqrlssimple-flex-2">' );
     105
     106                                printf( '<div><select name="%s" style="width:100%%;">', esc_attr( $attr_name . '[adjustment_type]' ) );
     107                                    foreach( static::get_adjustment_types( true ) as $slug => $label ) {
     108                                        printf( '<option value="%s"%s>%s</option>',
     109                                            esc_attr( $slug ),
     110                                            selected( $saved_atts['adjustment_type'], $slug, false ),
     111                                            $label
     112                                        );
     113                                    }
     114                                print( '</select></div>' );
     115
     116                                printf( '<div><input type="text" name="%s" value="%s" placeholder="%s" style="max-width:60px;" class="%s"></div>',
     117                                    esc_attr( $attr_name . '[adjustment]' ),
     118                                    esc_attr( $saved_atts['adjustment'] ),
     119                                    esc_attr( $global_adjustment ),
     120                                    esc_attr( 'iqlrss-numbers-only' . ( ( '' == $saved_atts['adjustment_type'] ) ? ' iqlrss-hide' : '' ) ),
     121                                );
     122                            print( '</div></td>' );
    106123
    107124                            // Carrier Name
     
    109126
    110127                        print( '</tr>' );
     128
     129                        // Set a processed flag for the next array which is not reorganized.
     130                        $saved_services[ $carrier_code ][ $service_code ]['processed'] = true;
    111131
    112132                    }
     
    128148
    129149                        $service_arr = ( ! is_array( $service_arr ) ) ? (array)$service_arr : $service_arr;
    130                         if( isset( $saved_services[ $carrier_code ][ $service_arr['service_code'] ] ) ) continue;
     150                        if( isset( $saved_services[ $carrier_code ][ $service_arr['service_code'] ]['processed'] ) ) continue;
    131151
    132152                        print( '<tr>' );
     
    159179                            );
    160180
    161                             // Service Name
    162                             printf( '<td><input type="text" name="%s" value="" placeholder="%s" class="iqlrss-numbers-only" style="max-width:60px;"></td>',
    163                                 esc_attr( $attr_name . '[adjustment]' ),
    164                                 esc_attr( $global_adjustment . '%' ),
    165                             );
     181                            // Service Price Adjustment
     182                            print( '<td><div class="iqrlssimple-flex-2">' );
     183
     184                                printf( '<div><select name="%s" style="width:100%%;">', esc_attr( $attr_name . '[adjustment_type]' ) );
     185                                    foreach( static::get_adjustment_types( true ) as $slug => $label ) {
     186                                        printf( '<option value="%s"%s>%s</option>',
     187                                            esc_attr( $slug ),
     188                                            selected( $global_adjustment_type, $slug, false ),
     189                                            $label
     190                                        );
     191                                    }
     192                                print( '</select></div>' );
     193
     194                                printf( '<div><input type="text" name="%s" value="" placeholder="%s" style="max-width:60px;" class="%s"></div>',
     195                                    esc_attr( $attr_name . '[adjustment]' ),
     196                                    esc_attr( $global_adjustment ),
     197                                    esc_attr( 'iqlrss-numbers-only' . ( ( '' == $global_adjustment_type ) ? ' iqlrss-hide' : '' ) )
     198                                );
     199                            print( '</div></td>' );
    166200
    167201                            // Carrier Name
  • live-rates-for-shipstation/tags/1.0.4/live-rates-for-shipstation.php

    r3339837 r3361859  
    44 * Plugin URI: https://iqcomputing.com/contact/
    55 * Description: ShipStation shipping method with live rates.
    6  * Version: 1.0.3
     6 * Version: 1.0.4
    77 * Requries at least: 5.9
    88 * Author: IQComputing
     
    2626     * @var String
    2727     */
    28     protected static $version = '1.0.3';
     28    protected static $version = '1.0.4';
    2929
    3030
     
    5555     * @param String $key
    5656     * @param Mixed $default
    57      * @param Boolean $prefix - Prefix Key with plugin slug.
     57     * @param Boolean $skip_prefix - Skip Plugin Prefix and return a core ShipStation setting value.
    5858     *
    5959     * @return Mixed
    6060     */
    61     public static function get_ss_opt( $key, $default = '', $prefix = false ) {
     61    public static function get_ss_opt( $key, $default = '', $skip_prefix = false ) {
    6262
    63         if( $prefix ) $key = static::plugin_prefix( $key );
     63        if( ! $skip_prefix ) $key = static::plugin_prefix( $key );
    6464        $settings = get_option( 'woocommerce_shipstation_settings' );
    6565        return ( isset( $settings[ $key ] ) && '' !== $settings[ $key ] ) ? $settings[ $key ] : $default;
     
    141141
    142142} );
    143 add_action( 'plugins_loaded', array( '\IQLRSS\Driver', 'drive' ), 15 );
     143add_action( 'plugins_loaded', array( '\IQLRSS\Driver', 'drive' ), 8 );
  • live-rates-for-shipstation/tags/1.0.4/readme.txt

    r3339837 r3361859  
    44Requires at least: 5.9
    55Tested up to: 6.8
    6 Stable tag: 1.0.3
     6Stable tag: 1.0.4
    77License: GPLv3 or later
    88License URI: https://www.gnu.org/licenses/gpl-3.0.html
     
    4949== Changelog ==
    5050
     51= 1.0.4 (2025-09-15) =
     52* Patches issues with shipping units not match store set units.
     53* Adds new Flat Rate Adjustments to global and shipping services.
     54* Adds additional metadata to WC Order Items in regards to shipping.
     55* Shoutouts to both @centuryperf and @jkmail120 for reporting these issues!
     56
    5157= 1.0.3 (2025-08-05) =
    5258* Patches an issue with Shipping Method availability (Thanks to @sportswreathshop for reporting!)
     
    5460= 1.0.2 (2025-08-04) =
    5561* Refines API caching that clears on settings save (thanks again to @dpkonofa for test/reporting!).
    56 
    57 = 1.0.1 (2025-08-01) =
    58 * Patches an issue with Individual Shipping Requests (thanks @dpkonofa !).
    59 * Attempt to discern ShipStation Carriers from Manually Connected Carriers.
    60 
    61 = 1.0.0 (2025-07-28) =
    62 * Initial release
  • live-rates-for-shipstation/trunk/changelog.txt

    r3339837 r3361859  
    22
    33This is a brief text document keeping track of changes to the plugin. For a full history, see the Github Repository.
     4
     5= 1.0.4 =
     6
     7Relase Date: September 15, 2025
     8
     9* Overview
     10    * Patches an issue where the exports would not take the Stores set product units.
     11    * Adds functionality for Flat Rate Adjustments on a global scale.
     12    * Adds functionality for Flat Rate Adjustments on a per service scale.
     13    * Shoutouts to both @centuryperf and @jkmail120 for reporting these issues!
     14
     15* Code Updates
     16    * \IQLRSS\Core\Settings_Shipstation::export_shipstation_shipping_method()
     17        * Hopefully better ShipStation integration when orders export.
     18    * \IQLRSS\Core\Shipping_Method_Shipstation::get_individual_requests()
     19        * Updates to garb the store dimensions.
     20    * \IQLRSS\Core\Shipping_Method_Shipstation::calculate_shipping()
     21        * Updates for flatrate calculations.
    422
    523= 1.0.3 =
  • live-rates-for-shipstation/trunk/core/assets/admin.css

    r3339099 r3361859  
    2424#customBoxes input[type=text]                   {-moz-appearance: textfield;}
    2525
     26.iqrlssimple-flex-2       {display: flex;}
     27.iqrlssimple-flex-2 > *   {flex: 1 1 calc( 50% - 8px );}
     28.iqrlssimple-flex-2 > :first-child    {flex-basis: fit-content; padding-right: 4px;}
     29.iqrlssimple-flex-2 > :last-child     {padding-left: 4px;}
     30
    2631.iqlrss-api-row fieldset                            {position: relative; display: block; width: fit-content;}
    2732.iqlrss-api-row #iqlrssVerifyButton                 {position: absolute; top: 0px; right: 0; margin-top: -1px; margin-right: -85px;}
  • live-rates-for-shipstation/trunk/core/assets/modules/settings.js

    r3339099 r3361859  
    44 * Not really meant to be used as an object but more for
    55 * encapsulation and organization.
     6 *
     7 * @todo Populate (or recreate) Carriers Select2 whenever API is verified.
    68 *
    79 * @global {Object} iqlrss - Localized object of saved values.
     
    3436
    3537        this.apiClearCache();
    36         this.priceAdjustmentNumbersOnly();
    37         this.singleLowestSetup();
     38        this.setupPriceAdjustments();
     39        this.setupSingleLowest();
    3840
    3941    }
     
    158160                const $row = $elm.closest( 'tr' );
    159161                if( ! $row || 'none' != $row.style.display ) return;
     162
     163                /* Skip the Return Lowest Label if related isn't checked */
     164                if( -1 != $elm.name.indexOf( 'global_adjustment' ) && '' == document.querySelectorAll( '[name*=global_adjustment_type]' ).value ) {
     165                    return;
     166                }
    160167
    161168                /* Skip the Return Lowest Label if related isn't checked */
     
    293300     * Only allow numbers for the Price Adjustment input.
    294301     */
    295     priceAdjustmentNumbersOnly() {
    296 
    297         const $adjustmentInput = document.querySelector( '[type=text][name*=global_adjustment' );
     302    setupPriceAdjustments() {
     303
     304        const $adjustmentSelect = document.querySelector( 'select[name*=global_adjustment_type]' );
     305        const $adjustmentInput  = document.querySelector( '[type=text][name*=global_adjustment' );
     306
     307        /* Select Change - Show Input Row */
     308        $adjustmentSelect.addEventListener( 'change', ( e ) => {
     309            $adjustmentInput.value = '';
     310            this.rowMakeVisible( $adjustmentInput.closest( 'tr' ), ( e.target.value ) )
     311        } );
     312
     313        /* Input Update - Only FloatString */
    298314        $adjustmentInput.addEventListener( 'input', ( e ) => {
    299             e.target.value = e.target.value.replace( /[^0-9.]/g, '' );
     315            e.target.value = e.target.value.replace( /(\..*?)\./g, '$1' ).replace( /[^0-9.]/g, '' );
    300316        } );
    301317
     
    306322     * Show / Hide the Single Lowest label
    307323     */
    308     singleLowestSetup() {
     324    setupSingleLowest() {
    309325
    310326        const $lowestcb     = document.querySelector( '[type=checkbox][name*=return_lowest' );
     
    320336
    321337        /* Eh, just trigger it */
    322         if( 'none' != $lowestcb.closest( 'tr' ).style.display ) {
     338        if( $lowestcb.checked && 'none' == $lowestLabel.closest( 'tr' ).style.display ) {
    323339            $lowestcb.dispatchEvent( new Event( 'change' ) );
    324340        }
     
    337353        if( visible ) {
    338354
    339             $row.setAttribute( 'style', 'opacity:0' );
     355            if( null !== $row.offsetParent ) return;
     356
     357            $row.style = 'opacity:0';
    340358            $row.animate( {
    341359                opacity: [ 1 ]
     
    350368            }, {
    351369                duration: 300
    352             } ).onfinish = () => $row.setAttribute( 'style', 'display:none;' );
     370            } ).onfinish = () => $row.style = 'display:none;';
    353371
    354372        }
     
    374392        $err.remove();
    375393
    376         $err.setAttribute( 'style', 'height:0px;opacity:0;overflow:hidden;' );
     394        $err.style = 'height:0px;opacity:0;overflow:hidden;';
    377395        $row.querySelector( 'fieldset' ).appendChild( $err );
    378396
  • live-rates-for-shipstation/trunk/core/assets/modules/shipping-zone.js

    r3339099 r3361859  
    1919        this.customBoxesRemove();
    2020
     21        this.setupPriceAdjustments()
    2122        this.inputsNumbersOnly();
    2223        this.wooAccommodations();
     
    4243
    4344                document.querySelectorAll( '#customBoxes [name]' ).forEach( ( $elm ) => {
    44                     if( 'text' == $elm.getAttribute( 'type' ) ) $elm.removeAttribute( 'required' );
     45                    if( 'text' == $elm.type ) $elm.removeAttribute( 'required' );
    4546                } );
    4647                document.getElementById( 'customBoxes' ).style.display = 'none';
     
    8081            $clone.classList.remove( 'mimic' );
    8182            $clone.querySelectorAll( '[name]' ).forEach( ( $elm ) => {
    82                 $elm.setAttribute( 'name', $elm.getAttribute( 'name' ).replace( 'mimic', count ) );
    83                 if( 'text' == $elm.getAttribute( 'type' ) && -1 == $elm.getAttribute( 'name' ).indexOf( '[wm]' ) ) $elm.setAttribute( 'required', true );
     83                $elm.name = $elm.name.replace( 'mimic', count );
     84                if( 'text' == $elm.type && -1 == $elm.name.indexOf( '[wm]' ) ) $elm.required = true;
    8485            } );
    8586
     
    116117
    117118    /**
     119     * Price Adjustments
     120     * Manage the show/hide functionality.
     121     */
     122    setupPriceAdjustments() {
     123
     124        /**
     125         * Adjustment Type Change
     126         * Show / Hide Price Input
     127         */
     128        document.addEventListener( 'change', ( e ) => {
     129
     130            if( 'SELECT' != e.target.tagName ) return;
     131            if( -1 == e.target.name.indexOf( 'adjustment_type' ) ) return;
     132
     133            const $adjustmentSelect = e.target;
     134            const $adjustmentInput  = $adjustmentSelect.closest( 'td' ).querySelector( 'input' );
     135
     136            if( '' == $adjustmentSelect.value ) {
     137
     138                $adjustmentInput.animate( {
     139                    opacity: 0
     140                }, {
     141                    duration: 300,
     142                    fill: 'forwards',
     143                } ).onfinish = () => {
     144                    $adjustmentInput.value = '';
     145                    $adjustmentInput.classList.add( 'iqlrss-hide' );
     146                };
     147
     148            } else if( null === $adjustmentInput.offsetParent ) {
     149
     150                $adjustmentInput.classList.remove( 'iqlrss-hide' );
     151                $adjustmentInput.animate( {
     152                    opacity: [0, 1]
     153                }, {
     154                    duration: 300,
     155                    fill: 'forwards',
     156                } ).onfinish = () => {
     157                    $adjustmentInput.value = '';
     158                };
     159
     160            } else {
     161                $adjustmentInput.value = ( $adjustmentSelect.value != iqlrss.global_adjustment_type ) ? '0' : '';
     162            }
     163
     164        } );
     165
     166    }
     167
     168
     169    /**
    118170     * Only allow numbers in inputs.
    119171     */
    120172    inputsNumbersOnly() {
    121173
     174        /**
     175         * All Custom Packing Box inputs.
     176         * Any numbers-only classes
     177         */
    122178        document.addEventListener( 'input', ( e ) => {
    123179            if( 'INPUT' !== e.target.tagName ) return;
    124             if( -1 != e.target.getAttribute( 'name' ).indexOf( 'custombox' ) || e.target.classList.contains( 'iqlrss-numbers-only' ) ) {
    125                 e.target.value = e.target.value.replace( /[^0-9.]/g, '' );
     180            if( -1 != e.target.name.indexOf( 'custombox' ) || e.target.classList.contains( 'iqlrss-numbers-only' ) ) {
     181                e.target.value = e.target.value.replace( /(\..*?)\./g, '$1' ).replace( /[^0-9.]/g, '' );
    126182            }
    127183        } );
  • live-rates-for-shipstation/trunk/core/settings-shipstation.php

    r3339099 r3361859  
    8888
    8989        $data = array(
    90             'api_verified' => \IQLRSS\Driver::get_ss_opt( 'api_key_valid', false, true ),
     90            'api_verified' => \IQLRSS\Driver::get_ss_opt( 'api_key_valid', false ),
     91            'global_adjustment_type' => \IQLRSS\Driver::get_ss_opt( 'global_adjustment_type', '' ),
    9192            'rest' => array(
    9293                'nonce'     => wp_create_nonce( 'wp_rest' ),
     
    100101                'confirm_box_removal'   => esc_html__( 'Please confirm you would like to completely remove (x) custom boxes.', 'live-rates-for-shipstation' ),
    101102                'error_rest_generic'    => esc_html__( 'Something went wrong with the REST Request. Please resave permalinks and try again.', 'live-rates-for-shipstation' ),
    102                 'error_verification_required' => esc_html__( 'Please click the Verify API button to ensure a connection exists.', 'live-rates-for-shipstation' ),
     103                'error_verification_required'       => esc_html__( 'Please click the Verify API button to ensure a connection exists.', 'live-rates-for-shipstation' ),
     104                'desc_global_adjustment_percentage' => esc_html__( 'Example: IF UPS Ground is $7.25 and you input 15% ($1.08), the final shipping rate the customer sees is: $8.33', 'live-rates-for-shipstation' ),
     105                'desc_global_adjustment_flatrate'   => esc_html__( 'Example: IF UPS Ground is $5.50 and you input $2.37, the final shipping rate the customer sees is: $7.87', 'live-rates-for-shipstation' ),
    103106            ),
    104107        );
     
    106109        ?><script type="text/javascript">
    107110
     111            /* JS Localization */
    108112            const iqlrss = JSON.parse( '<?php echo wp_json_encode( $data ); ?>' );
    109113
    110             <?php
    111                 /**
    112                  * Modules load too late to effectively immediately hide elements.
    113                  * This runs on the ShipStation settings page to hide additional
    114                  * settings whenever the API is unauthenticated.
    115                  */
    116                 if( ! $data['api_verified'] ) :
    117             ?>
    118 
    119                 if( document.getElementById( 'woocommerce_shipstation_iqlrss_api_key' ) ) { ( () => {
     114            /* Early setting field JS */
     115            if( document.getElementById( 'woocommerce_shipstation_iqlrss_api_key' ) ) { ( function() {
     116
     117                /* Hide an element, ezpz */
     118                const fnHide = ( $el ) => $el.closest( 'tr' ).style.display = 'none';
     119
     120                <?php
     121                    /**
     122                     * Modules load too late to effectively immediately hide elements.
     123                     * This runs on the ShipStation settings page to hide additional
     124                     * settings whenever the API is unauthenticated.
     125                     */
     126                    if( ! $data['api_verified'] ) :
     127                ?>
     128
    120129                    document.querySelectorAll( '[name*=iqlrss]' ).forEach( ( $elm ) => {
    121                         if( $elm.getAttribute( 'name' ).includes( 'api_key' ) ) return;
    122                         if( $elm.getAttribute( 'name' ).includes( 'cart_weight' ) ) return;
    123                         $elm.closest( 'tr' ).style.display = 'none';
     130                        if( $elm.name.includes( 'api_key' ) ) return;
     131                        if( $elm.name.includes( 'cart_weight' ) ) return;
     132                        fnHide( $elm );
    124133                    } );
    125                 } )(); }
    126 
    127             <?php endif; ?>
     134
     135                <?php else : ?>
     136
     137                    document.querySelectorAll( '[name*=iqlrss]' ).forEach( ( $elm ) => {
     138
     139                        if( 'checkbox' == $elm.type && $elm.name.includes( 'return_lowest' ) && ! $elm.checked ) {
     140                            fnHide( document.querySelector( '[name*=return_lowest_label]' ) );
     141                        }
     142
     143                        if( $elm.name.includes( 'global_adjustment_type' ) && '' == $elm.value ) {
     144                            fnHide( document.querySelector( '[type=text][name*=global_adjustment]' ) );
     145                        }
     146                    } );
     147
     148                <?php endif; ?>
     149
     150            } )(); }
     151
    128152        </script><?php
    129153
     
    155179    public function display_cart_weight() {
    156180
    157         $show_weight = \IQLRSS\Driver::get_ss_opt( 'cart_weight', 'no', true );
     181        $show_weight = \IQLRSS\Driver::get_ss_opt( 'cart_weight', 'no' );
    158182        if( 'no' == $show_weight ) return;
    159183
     
    289313
    290314        // Set transient to clear any WC_Session caches if they are found.
    291         $time = absint( apply_filters( 'wc_session_expiration', DAY_IN_SECONDS * 2 ) );
    292         set_transient( \IQLRSS\Driver::plugin_prefix( 'wcs_timeout' ), time(), $time );
     315        $expires = absint( apply_filters( 'wc_session_expiration', DAY_IN_SECONDS * 2 ) );
     316        set_transient( \IQLRSS\Driver::plugin_prefix( 'wcs_timeout' ), time(), $expires );
    293317
    294318    }
     
    326350        add_filter( 'woocommerce_shipping_methods',                         array ($this, 'append_shipstation_method' ) );
    327351        add_filter( 'woocommerce_settings_api_form_fields_shipstation',     array( $this, 'append_shipstation_integration_settings' ) );
     352        add_filter( 'woocommerce_settings_api_sanitized_fields_shipstation',array( $this, 'save_shipstation_integration_settings' ) );
     353        add_filter( 'woocommerce_shipstation_export_get_order',             array( $this, 'export_shipstation_shipping_method' ) );
    328354
    329355    }
     
    366392            }
    367393        }
     394
     395        // Backwards compatibility for v1.0.3 when only percentage was supported by default.
     396        $global_adjustment = \IQLRSS\Driver::get_ss_opt( 'global_adjustment', '0' );
     397        $adjustment_type_default = ( empty( $global_adjustment_type ) && ! empty( $global_adjustment ) ) ? 'percentage' : '';
    368398
    369399        foreach( $fields as $key => $field ) {
     
    391421                );
    392422
     423                $appended_fields[ \IQLRSS\Driver::plugin_prefix( 'global_adjustment_type' ) ] = array(
     424                    'title'         => esc_html__( 'Shipping Price Adjustment', 'live-rates-for-shipstation' ),
     425                    'type'          => 'select',
     426                    'options'       => \IQLRSS\Core\Shipping_Method_Shipstation::get_adjustment_types( true ),
     427                    'description'   => esc_html__( 'This adjustment is added on top of the returned shipping rates to help you cover shipping costs. Can be overridden per zone, per service.', 'live-rates-for-shipstation' ),
     428                    'default'       => $adjustment_type_default,
     429                );
     430
    393431                $appended_fields[ \IQLRSS\Driver::plugin_prefix( 'global_adjustment' ) ] = array(
    394                     'title'         => esc_html__( 'Shipping Price Adjustment (%)', 'live-rates-for-shipstation' ),
     432                    'title'         => esc_html__( 'Global Price Adjustment', 'live-rates-for-shipstation' ),
    395433                    'type'          => 'text',
    396                     'placeholder'   => '0%',
    397                     'description'   => esc_html__( 'This percent is added on top of the returned shipping rates to help you cover shipping costs. Can be overridden per zone, per service.', 'live-rates-for-shipstation' ),
    398                     'desc_tip'      => esc_html__( 'Example: IF UPS Ground is $7.25 - 15% would be $1.08 making the final rate: $8.33', 'live-rates-for-shipstation' ),
     434                    'placeholder'   => '0',
     435                    'description'   => esc_html__( 'Optional global ShipStation rate adjustment.', 'live-rates-for-shipstation' ),
    399436                    'default'       => '',
    400437                );
     
    431468
    432469
     470    /**
     471     * Modify the saved settings after WooCommerce has sanitized them.
     472     * Not much we need to do here, WooCommerce does most the heavy lifting.
     473     *
     474     * @param Array $settings
     475     *
     476     * @return Array $settings
     477     */
     478    public function save_shipstation_integration_settings( $settings ) {
     479
     480        // No API Key? Invalid!
     481        $api_key_key = \IQLRSS\Driver::plugin_prefix( 'api_key' );
     482        if( ! isset( $settings[ $api_key_key ] ) || empty( $settings[ $api_key_key ] ) ) {
     483           
     484            $settings[ \IQLRSS\Driver::plugin_prefix( 'api_key_valid' ) ] = false;
     485            if( isset( $settings[ \IQLRSS\Driver::plugin_prefix( 'api_key_vt' ) ] ) ) {
     486                unset( $settings[ \IQLRSS\Driver::plugin_prefix( 'api_key_vt' ) ] );
     487            }
     488        }
     489
     490        return $settings;
     491
     492    }
     493
     494
     495    /**
     496     * Update the WC_Order Shipping Method to match the ShipStation Carrier.
     497     * Ex. USPSPriorityMail
     498     *
     499     * @link https://help.shipstation.com/hc/en-us/articles/360025856192-Custom-Store-Development-Guide
     500     *
     501     * @param WC_Order $order
     502     *
     503     * @return WC_Order $order
     504     */
     505    public function export_shipstation_shipping_method( $order ) {
     506
     507        if( ! is_a( $order, 'WC_Order' ) ) {
     508            return $order;
     509        }
     510
     511        $methods = $order->get_shipping_methods();
     512        $plugin_method_id = \IQLRSS\Driver::plugin_prefix( 'shipstation' );
     513
     514        foreach( $methods as $method ) {
     515
     516            // Not our shipping method.
     517            if( $method->get_method_id() != $plugin_method_id ) continue;
     518
     519            $service_name = $method->get_meta( 'service_name', true );
     520            $carrier_name = $method->get_meta( 'carrier_name', true );
     521
     522            // Missing metadata.
     523            if( empty( $service_name ) || empty( $carrier_name ) ) continue;
     524
     525            $method->set_props( array(
     526                'name' => preg_replace( '/([^a-zA-Z0-9])/', '', sprintf( '%s %s', $carrier_name, $service_name ) ),
     527            ) );
     528            $method->apply_changes(); // Temporarily apply changes. This does not update the database.
     529
     530        }
     531
     532        return $order;
     533
     534    }
     535
     536
    433537
    434538    /**------------------------------------------------------------------------------------------------ **/
  • live-rates-for-shipstation/trunk/core/shipping-method-shipstation.php

    r3339837 r3361859  
    3838
    3939    /**
     40     * Array of store specific settings.
     41     *
     42     * @var Array
     43     */
     44    protected $store_data = array(
     45        'weight_unit'   => '',
     46        'dim_unit'      => '', // Dimension
     47    );
     48
     49
     50    /**
    4051     * Array of global carriers
    4152     * There are the carriers saved in Integration settings.
     
    7990        $this->supports             = array( 'instance-settings' );
    8091
    81         $this->carriers = \IQLRSS\Driver::get_ss_opt( 'carriers', array(), true );
    82         $saved_key = \IQLRSS\Driver::get_ss_opt( 'api_key_valid', false, true );
     92        $this->carriers = \IQLRSS\Driver::get_ss_opt( 'carriers', array() );
     93        $saved_key = \IQLRSS\Driver::get_ss_opt( 'api_key_valid', false );
    8394
    8495        // Only show in Shipping Zones if API Key is invalid.
     
    8697            $this->supports[] = 'shipping-zones';
    8798        }
     99
     100        // Set the store unit term and associate it with ShipStations term.
     101        $this->store_data = array(
     102            'weight_unit'   => get_option( 'woocommerce_weight_unit', $this->store_data['weight_unit'] ),
     103            'dim_unit'      => get_option( 'woocommerce_dimension_unit', $this->store_data['dim_unit'] ),
     104        );
    88105
    89106        $this->init_instance_form_fields();
     
    180197        $settings       = get_option( 'woocommerce_shipstation_settings' );
    181198        $saved_services = $this->get_option( 'services', array() );
    182         $saved_carriers = \IQLRSS\Driver::get_ss_opt( 'carriers', array(), true );
     199        $saved_carriers = \IQLRSS\Driver::get_ss_opt( 'carriers', array() );
    183200        $shipStationAPI = $this->shipStationApi;
    184201
     
    186203
    187204            $sorted_services = array();
     205
     206            // See $this->validate_services_field()
    188207            foreach( $saved_services as $k => $s ) {
    189208
     
    233252     * Validate service field.
    234253     *
    235      * @param mixed $key - Field key.
     254     * @return Array $services
    236255     */
    237256    public function validate_services_field() {
     
    253272        $posted_services = wp_unslash( $_POST[ $prefix ] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
    254273
     274        // Global adjustment
     275        $global_adjustment      = \IQLRSS\Driver::get_ss_opt( 'global_adjustment', '' );
     276        $global_adjustment_type = \IQLRSS\Driver::get_ss_opt( 'global_adjustment_type', '' );
     277
    255278        // Group by Carriers then Services
    256279        $services = array();
     
    258281            foreach( $carrier_services as $service_code => $service_arr ) {
    259282
    260                 // Skip non-enabled and non-renamed services.
    261                 if( ! isset( $service_arr['enabled'] ) && empty( $service_arr['nickname'] ) ) continue;
    262 
    263283                $carrier_code = sanitize_text_field( $carrier_code );
    264284                $service_code = sanitize_text_field( $service_code );
    265                 $services[ $carrier_code ][ $service_code ] = array_filter( array(
     285                $data = array_filter( array(
    266286
    267287                    // User Input
     
    276296                ) );
    277297
    278                 // Allow 0 value user input.
    279                 if( $service_arr['adjustment'] >= 0 ) {
    280                     $services[ $carrier_code ][ $service_code ]['adjustment'] = floatval( $service_arr['adjustment'] );
     298                // The above removes empty values.
     299                // Price Adjustments
     300                $data['adjustment']     = ( $service_arr['adjustment'] ) ? floatval( $service_arr['adjustment'] ) : '';
     301                $data['adjustment_type']= $service_arr['adjustment_type'];
     302
     303                // Maybe unset if we don't need the data.
     304                if( $data['adjustment_type'] == $global_adjustment_type ) {
     305
     306                    // equal or equal empty -> 0 == ''
     307                    if( $data['adjustment'] == $global_adjustment || '' == $data['adjustment'] ) {
     308                        unset( $data['adjustment'] );
     309                        unset( $data['adjustment_type'] );
     310                    }
    281311                }
    282312
     313                /**
     314                 * We don't want to array_filter() since
     315                 * Global Adjust could be populated, and
     316                 * Service is set to '' (No Adjustment).
     317                 */
     318                $services[ $carrier_code ][ $service_code ] = $data;
     319
    283320            }
    284321        }
     
    292329     * Validate customboxes field.
    293330     *
    294      * @param mixed $key - Field key.
     331     * @return Array $boxes
    295332     */
    296333    public function validate_customboxes_field() {
     
    370407        }
    371408
    372         $global_upcharge = floatval( \IQLRSS\Driver::get_ss_opt( 'global_adjustment', 0, true ) );
    373         $packing_type   = $this->get_option( 'packing', 'individual' );
     409        $global_adjustment      = floatval( \IQLRSS\Driver::get_ss_opt( 'global_adjustment', 0 ) );
     410        $global_adjustment_type = \IQLRSS\Driver::get_ss_opt( 'global_adjustment_type','' );
     411        $global_adjustment_type = ( empty( $global_adjustment_type ) && ! empty( $global_adjustment ) ) ? 'percentage' : $global_adjustment_type;
     412
     413        $packing_type = $this->get_option( 'packing', 'individual' );
    374414        $request = array(
    375415            'from_country_code'  => WC()->countries->get_base_country(),
     
    444484
    445485                // Apply service upcharge
    446                 if( isset( $service_arr['adjustment'] ) && $service_arr['adjustment'] > 0 ) {
    447 
    448                     $adjustment = floatval( $saved_services[ $shiprate['carrier_code'] ]['adjustment'] );
    449                     $cost += ( $adjustment > 0 ) ? ( $cost * ( $adjustment / 100 ) ) : 0;
    450 
    451                 } else if( ! empty( $global_upcharge ) ) {
    452                     $cost += ( $cost * ( $global_upcharge / 100 ) );
     486                if( isset( $service_arr['adjustment'] ) ) {
     487
     488                    /**
     489                     * Adjustment type could be '' to skip global adjustment.
     490                     * Defaults to percentage for v1.03 backwards compatibility.
     491                     */
     492                    $adjustment      = floatval( $service_arr['adjustment'] );
     493                    $adjustment_type = ( isset( $service_arr['adjustment_type'] ) ) ? $service_arr['adjustment_type'] : 'percentage';
     494
     495                    if( ! empty( $adjustment_type ) && $adjustment > 0 ) {
     496                        $cost += ( 'flatrate' == $adjustment_type ) ? $adjustment : ( $cost * ( $adjustment / 100 ) );
     497                    }
     498
     499                } else if( ! empty( $global_adjustment_type ) && $global_adjustment > 0 ) {
     500                    $cost += ( 'flatrate' == $global_adjustment_type ) ? floatval( $global_adjustment ) : ( $cost * ( floatval( $global_adjustment ) / 100 ) );
    453501                }
    454502
     
    464512                        'dimensions'    => $req['dimensions'],
    465513                        'weight'        => $req['weight'],
     514                        'service_name'  => $shiprate['name'],
    466515                        'carrier_code'  => $shiprate['carrier_code'],
     516                        'carrier_name'  => $shiprate['carrier_name'],
    467517                    ),
    468518                );
     
    480530        }
    481531
    482         $single_lowest          = \IQLRSS\Driver::get_ss_opt( 'return_lowest', 'no', true );
    483         $single_lowest_label    = \IQLRSS\Driver::get_ss_opt( 'return_lowest_label', '', true );
     532        $single_lowest          = \IQLRSS\Driver::get_ss_opt( 'return_lowest', 'no' );
     533        $single_lowest_label    = \IQLRSS\Driver::get_ss_opt( 'return_lowest_label', '' );
    484534
    485535        // Add all shipping rates, let the user decide.
     
    547597                    esc_html__( 'Product ID #%1$d missing (%2$s) dimensions. Shipping calculations terminated.', 'live-rates-for-shipstation' ),
    548598                    $item['product_id'],
    549                     implode( ', ', array_diff_key( $this->dimension_keys, $physicals ) )
     599                    implode( ', ', array_diff_key( array(
     600                        'width'     => 'width',
     601                        'height'    => 'height',
     602                        'length'    => 'length',
     603                        'weight'    => 'weight',
     604                    ), $physicals ) )
    550605                ) );
    551606                return array();
     
    553608
    554609            $request['weight'] = array(
    555                 'value' => (float)max( 0.5, round( wc_get_weight( $physicals['weight'], 'lbs' ), 2 ) ),
    556                 'unit'  => 'pound',
     610                'value' => (float)round( wc_get_weight( $physicals['weight'], $this->store_data['weight_unit'] ), 2 ),
     611                'unit'  => $this->shipStationApi->convert_unit_term( $this->store_data['weight_unit'] ),
    557612            );
    558613
     
    562617
    563618            $request['dimensions'] = array(
    564                 'unit'      => 'inch',
    565                 'length'    => max( 1, round( wc_get_dimension( $physicals[2], 'in' ), 2 ) ),
    566                 'width'     => max( 1, round( wc_get_dimension( $physicals[1], 'in' ), 2 ) ),
    567                 'height'    => max( 1, round( wc_get_dimension( $physicals[0], 'in' ), 2 ) ),
     619                'length'    => round( wc_get_dimension( $physicals[2], $this->store_data['dim_unit'] ), 2 ),
     620                'width'     => round( wc_get_dimension( $physicals[1], $this->store_data['dim_unit'] ), 2 ),
     621                'height'    => round( wc_get_dimension( $physicals[0], $this->store_data['dim_unit'] ), 2 ),
     622                'unit'      => $this->shipStationApi->convert_unit_term( $this->store_data['dim_unit'] ),
    568623            );
    569624
     
    631686                    esc_html__( 'Product ID #%1$d missing (%2$s) dimensions. Shipping calculations terminated.', 'live-rates-for-shipstation' ),
    632687                    $item['product_id'],
    633                     implode( ', ', array_diff_key( $this->dimension_keys, $physicals ) )
     688                    implode( ', ', array_diff_key( array(
     689                        'width'     => 'width',
     690                        'height'    => 'height',
     691                        'length'    => 'length',
     692                        'weight'    => 'weight',
     693                    ), $physicals ) )
    634694                ) );
    635695                return array();
    636696            }
    637697
    638             $data['weight'] = (float)max( 0.5, round( wc_get_weight( $physicals['weight'], 'lbs' ), 2 ) );
    639 
    640             // Unset weight and sort dimensions
     698            $data['weight'] = (float)round( wc_get_weight( $physicals['weight'], $this->store_data['weight_unit'] ), 2 );
     699
     700            // Unset weight to exclude it from sort
    641701            unset( $physicals['weight'] );
    642702            sort( $physicals );
    643703
    644704            $data = array(
    645                 'length'    => max( 1, round( wc_get_dimension( $physicals[2], 'in' ), 2 ) ),
    646                 'width'     => max( 1, round( wc_get_dimension( $physicals[1], 'in' ), 2 ) ),
    647                 'height'    => max( 1, round( wc_get_dimension( $physicals[0], 'in' ), 2 ) ),
     705                'length'    => round( wc_get_dimension( $physicals[2], $this->store_data['dim_unit'] ), 2 ),
     706                'width'     => round( wc_get_dimension( $physicals[1], $this->store_data['dim_unit'] ), 2 ),
     707                'height'    => round( wc_get_dimension( $physicals[0], $this->store_data['dim_unit'] ), 2 ),
    648708            ) + $data;
    649709
     
    670730                'weight' => array(
    671731                    'value' => $package->weight,
    672                     'unit'  => 'pound',
     732                    'unit'  => $this->shipStationApi->convert_unit_term( $this->store_data['weight_unit'] ),
    673733                ),
    674734                'dimensions' => array(
    675                     'unit'      => 'inch',
    676735                    'length'    => $package->length,
    677736                    'width'     => $package->width,
    678737                    'height'    => $package->height,
     738                    'unit'      => $this->shipStationApi->convert_unit_term( $this->store_data['dim_unit'] ),
    679739                ),
    680740            );
     
    796856    /**------------------------------------------------------------------------------------------------ **/
    797857    /**
     858     * Return an array of Price Adjustment Type options.
     859     *
     860     * @return Array
     861     */
     862    public static function get_adjustment_types( $include_empty = false ) {
     863
     864        $types = array(
     865            'flatrate'   => esc_html__( 'Flat Rate', 'live-rates-for-shipstation' ),
     866            'percentage' => esc_html__( 'Percentage', 'live-rates-for-shipstation' ),
     867        );
     868
     869        return ( false == $include_empty ) ? $types : array_merge( array(
     870            '' => esc_html__( 'No Adjustments', 'live-rates-for-shipstation' ),
     871        ), $types );
     872
     873    }
     874
     875
     876    /**
    798877     * Log error in WooCommerce
    799878     * Passthru method - log what's given and give it back.
     
    808887    protected function log( $error, $level = 'debug', $context = array() ) {
    809888
    810         if( ! \IQLRSS\Driver::get_ss_opt( 'logging_enabled', false ) ) {
     889        if( ! \IQLRSS\Driver::get_ss_opt( 'logging_enabled', 0, true ) ) {
    811890            return $error;
    812891        }
  • live-rates-for-shipstation/trunk/core/shipstation-api.php

    r3339099 r3361859  
    1515
    1616    /**
    17      * API Endpoint
     17     * Key prefix
    1818     *
    1919     * @var String
    2020     */
    21     protected $apiurl = 'https://api.shipstation.com'; // No trailing slash.
     21    protected $prefix;
     22
     23
     24    /**
     25     * Seconds to hold cache.
     26     * Defaults to 1 week.
     27     *
     28     * @var Integer
     29     */
     30    protected $cache_time;
     31
     32
     33    /**
     34     * Skip cache check
     35     *
     36     * @var Boolean
     37     */
     38    protected $skip_cache = false;
    2239
    2340
     
    3148
    3249    /**
    33      * Key prefix
     50     * The API Key
    3451     *
    3552     * @var String
    3653     */
    37     protected $prefix;
    38 
    39 
    40     /**
    41      * Skip cache check
    42      *
    43      * @var Boolean
    44      */
    45     protected $skip_cache = false;
    46 
    47 
    48     /**
    49      * The API Key
    50      *
    51      * @var String
    52      */
    53     private $key = '';
     54    private $key;
    5455
    5556
     
    6263
    6364        $this->prefix   = \IQLRSS\Driver::get( 'slug' );
    64         $this->key      = \IQLRSS\Driver::get_ss_opt( 'api_key', '', true );
     65        $this->key      = \IQLRSS\Driver::get_ss_opt( 'api_key', '' );
    6566        $this->skip_cache = (boolean)$skip_cache;
     67        $this->cache_time = defined( 'WEEK_IN_SECONDS' ) ? WEEK_IN_SECONDS : 604800;
    6668
    6769    }
     
    118120     * Return an array of carriers.
    119121     * While getting carriers, set services and packages.
     122     *
     123     * @link https://docs.shipstation.com/openapi/carriers
    120124     *
    121125     * @param String $carrier_code
     
    191195            // Cache Carriers
    192196            if( ! empty( $data['carriers'] ) ) {
    193                 set_transient( $trans_key, $data['carriers'], MONTH_IN_SECONDS );
     197                set_transient( $trans_key, $data['carriers'], $this->cache_time );
    194198            }
    195199
     
    198202                foreach( $data['services'] as $carrier_id => $service_arr ) {
    199203                    $service_key = sprintf( '%s_%s_services', $trans_key, $carrier_id );
    200                     set_transient( $service_key, $service_arr, MONTH_IN_SECONDS );
     204                    set_transient( $service_key, $service_arr, $this->cache_time );
    201205                }
    202206            }
     
    206210                foreach( $data['packages'] as $carrier_id => $package_arr ) {
    207211                    $package_key = sprintf( '%s_%s_packages', $trans_key, $carrier_id );
    208                     set_transient( $package_key, $package_arr, MONTH_IN_SECONDS );
     212                    set_transient( $package_key, $package_arr, $this->cache_time );
    209213                }
    210214            }
     
    269273    /**------------------------------------------------------------------------------------------------ **/
    270274    /**
     275     * Convert a WooCommerce unit term to a
     276     * ShipStation unit term.
     277     *
     278     * @param String $unit
     279     *
     280     * @return String $term
     281     */
     282    public function convert_unit_term( $unit ) {
     283
     284        $known = array(
     285            'kg'    => 'kilogram',
     286            'g'     => 'gram',
     287            'lbs'   => 'pound',
     288            'oz'    => 'ounce',
     289            'cm'    => 'centimeter',
     290            'in'    => 'inch',
     291        );
     292
     293        return ( isset( $known[ $unit ] ) ) ? $known[ $unit ] : $unit;
     294
     295    }
     296
     297
     298    /**
    271299     * Make an API Request
    272300     *
     
    345373     */
    346374    protected function get_endpoint_url( $endpoint ) {
    347         return sprintf( '%s/v2/%s/', $this->apiurl, $endpoint );
     375
     376        return sprintf( '%s/v2/%s/',
     377            'https://api.shipstation.com',
     378            $endpoint
     379        );
     380
    348381    }
    349382
     
    380413    protected function log( $error, $level = 'debug', $context = array() ) {
    381414
    382         if( ! \IQLRSS\Driver::get_ss_opt( 'logging_enabled', false ) ) {
     415        if( ! \IQLRSS\Driver::get_ss_opt( 'logging_enabled', 0, true ) ) {
    383416            return $error;
    384417        }
  • live-rates-for-shipstation/trunk/core/views/services-table.php

    r3339099 r3361859  
    1818}
    1919
    20 $api_key = \IQLRSS\Driver::get_ss_opt( 'api_key', '', true );
    21 $global_adjustment = \IQLRSS\Driver::get_ss_opt( 'global_adjustment', '0', true );
     20$api_key = \IQLRSS\Driver::get_ss_opt( 'api_key', '' );
     21$global_adjustment = \IQLRSS\Driver::get_ss_opt( 'global_adjustment', '0' );
     22$global_adjustment_type = \IQLRSS\Driver::get_ss_opt( 'global_adjustment_type', '' );
     23$global_adjustment_type = ( empty( $global_adjustment_type ) && ! empty( $global_adjustment ) ) ? 'percentage' : $global_adjustment_type;
    2224
    2325?>
     
    3335                    <th style="width: 50px;"><?php esc_html_e( 'Enabled', 'live-rates-for-shipstation' ); ?></th>
    3436                    <th><?php esc_html_e( 'Name', 'live-rates-for-shipstation' ); ?></th>
    35                     <th><?php esc_html_e( 'Price Adjustment (%)', 'live-rates-for-shipstation' ); ?></th>
     37                    <th><?php esc_html_e( 'Price Adjustment', 'live-rates-for-shipstation' ); ?></th>
    3638                    <th><?php esc_html_e( 'Carrier', 'live-rates-for-shipstation' ); ?></th>
    3739                </tr>
     
    3941            <tbody><?php
    4042
    41                 if( empty( $api_key ) || ! \IQLRSS\Driver::get_ss_opt( 'api_key_valid', false, true ) ) {
     43                if( empty( $api_key ) || ! \IQLRSS\Driver::get_ss_opt( 'api_key_valid', false ) ) {
    4244                    print( '<tr><th colspan="4">' );
    4345                        echo wp_kses( sprintf(
     
    6264
    6365                        $saved_atts = array(
    64                             'enabled' => ( isset( $service_arr['enabled'] ) ) ? $service_arr['enabled'] : false,
    65                             'nickname' => ( isset( $service_arr['nickname'] ) ) ? $service_arr['nickname'] : '',
    66                             'adjustment' => ( isset( $service_arr['adjustment'] ) ) ? $service_arr['adjustment'] : '',
     66                            'enabled'           => ( isset( $service_arr['enabled'] ) ) ? $service_arr['enabled'] : false,
     67                            'nickname'          => ( isset( $service_arr['nickname'] ) ) ? $service_arr['nickname'] : '',
     68                            'adjustment_type'   => ( isset( $service_arr['adjustment_type'] ) ) ? $service_arr['adjustment_type'] : $global_adjustment_type,
     69                            'adjustment'        => ( isset( $service_arr['adjustment'] ) ) ? $service_arr['adjustment'] : '',
    6770                        );
    6871
     
    99102
    100103                            // Service Price Adjustment
    101                             printf( '<td><input type="text" name="%s" value="%s" placeholder="%s" style="max-width:60px;"></td>',
    102                                 esc_attr( $attr_name . '[adjustment]' ),
    103                                 esc_attr( $saved_atts['adjustment'] ),
    104                                 esc_attr( $global_adjustment . '%' ),
    105                             );
     104                            print( '<td><div class="iqrlssimple-flex-2">' );
     105
     106                                printf( '<div><select name="%s" style="width:100%%;">', esc_attr( $attr_name . '[adjustment_type]' ) );
     107                                    foreach( static::get_adjustment_types( true ) as $slug => $label ) {
     108                                        printf( '<option value="%s"%s>%s</option>',
     109                                            esc_attr( $slug ),
     110                                            selected( $saved_atts['adjustment_type'], $slug, false ),
     111                                            $label
     112                                        );
     113                                    }
     114                                print( '</select></div>' );
     115
     116                                printf( '<div><input type="text" name="%s" value="%s" placeholder="%s" style="max-width:60px;" class="%s"></div>',
     117                                    esc_attr( $attr_name . '[adjustment]' ),
     118                                    esc_attr( $saved_atts['adjustment'] ),
     119                                    esc_attr( $global_adjustment ),
     120                                    esc_attr( 'iqlrss-numbers-only' . ( ( '' == $saved_atts['adjustment_type'] ) ? ' iqlrss-hide' : '' ) ),
     121                                );
     122                            print( '</div></td>' );
    106123
    107124                            // Carrier Name
     
    109126
    110127                        print( '</tr>' );
     128
     129                        // Set a processed flag for the next array which is not reorganized.
     130                        $saved_services[ $carrier_code ][ $service_code ]['processed'] = true;
    111131
    112132                    }
     
    128148
    129149                        $service_arr = ( ! is_array( $service_arr ) ) ? (array)$service_arr : $service_arr;
    130                         if( isset( $saved_services[ $carrier_code ][ $service_arr['service_code'] ] ) ) continue;
     150                        if( isset( $saved_services[ $carrier_code ][ $service_arr['service_code'] ]['processed'] ) ) continue;
    131151
    132152                        print( '<tr>' );
     
    159179                            );
    160180
    161                             // Service Name
    162                             printf( '<td><input type="text" name="%s" value="" placeholder="%s" class="iqlrss-numbers-only" style="max-width:60px;"></td>',
    163                                 esc_attr( $attr_name . '[adjustment]' ),
    164                                 esc_attr( $global_adjustment . '%' ),
    165                             );
     181                            // Service Price Adjustment
     182                            print( '<td><div class="iqrlssimple-flex-2">' );
     183
     184                                printf( '<div><select name="%s" style="width:100%%;">', esc_attr( $attr_name . '[adjustment_type]' ) );
     185                                    foreach( static::get_adjustment_types( true ) as $slug => $label ) {
     186                                        printf( '<option value="%s"%s>%s</option>',
     187                                            esc_attr( $slug ),
     188                                            selected( $global_adjustment_type, $slug, false ),
     189                                            $label
     190                                        );
     191                                    }
     192                                print( '</select></div>' );
     193
     194                                printf( '<div><input type="text" name="%s" value="" placeholder="%s" style="max-width:60px;" class="%s"></div>',
     195                                    esc_attr( $attr_name . '[adjustment]' ),
     196                                    esc_attr( $global_adjustment ),
     197                                    esc_attr( 'iqlrss-numbers-only' . ( ( '' == $global_adjustment_type ) ? ' iqlrss-hide' : '' ) )
     198                                );
     199                            print( '</div></td>' );
    166200
    167201                            // Carrier Name
  • live-rates-for-shipstation/trunk/live-rates-for-shipstation.php

    r3339837 r3361859  
    44 * Plugin URI: https://iqcomputing.com/contact/
    55 * Description: ShipStation shipping method with live rates.
    6  * Version: 1.0.3
     6 * Version: 1.0.4
    77 * Requries at least: 5.9
    88 * Author: IQComputing
     
    2626     * @var String
    2727     */
    28     protected static $version = '1.0.3';
     28    protected static $version = '1.0.4';
    2929
    3030
     
    5555     * @param String $key
    5656     * @param Mixed $default
    57      * @param Boolean $prefix - Prefix Key with plugin slug.
     57     * @param Boolean $skip_prefix - Skip Plugin Prefix and return a core ShipStation setting value.
    5858     *
    5959     * @return Mixed
    6060     */
    61     public static function get_ss_opt( $key, $default = '', $prefix = false ) {
     61    public static function get_ss_opt( $key, $default = '', $skip_prefix = false ) {
    6262
    63         if( $prefix ) $key = static::plugin_prefix( $key );
     63        if( ! $skip_prefix ) $key = static::plugin_prefix( $key );
    6464        $settings = get_option( 'woocommerce_shipstation_settings' );
    6565        return ( isset( $settings[ $key ] ) && '' !== $settings[ $key ] ) ? $settings[ $key ] : $default;
     
    141141
    142142} );
    143 add_action( 'plugins_loaded', array( '\IQLRSS\Driver', 'drive' ), 15 );
     143add_action( 'plugins_loaded', array( '\IQLRSS\Driver', 'drive' ), 8 );
  • live-rates-for-shipstation/trunk/readme.txt

    r3339837 r3361859  
    44Requires at least: 5.9
    55Tested up to: 6.8
    6 Stable tag: 1.0.3
     6Stable tag: 1.0.4
    77License: GPLv3 or later
    88License URI: https://www.gnu.org/licenses/gpl-3.0.html
     
    4949== Changelog ==
    5050
     51= 1.0.4 (2025-09-15) =
     52* Patches issues with shipping units not match store set units.
     53* Adds new Flat Rate Adjustments to global and shipping services.
     54* Adds additional metadata to WC Order Items in regards to shipping.
     55* Shoutouts to both @centuryperf and @jkmail120 for reporting these issues!
     56
    5157= 1.0.3 (2025-08-05) =
    5258* Patches an issue with Shipping Method availability (Thanks to @sportswreathshop for reporting!)
     
    5460= 1.0.2 (2025-08-04) =
    5561* Refines API caching that clears on settings save (thanks again to @dpkonofa for test/reporting!).
    56 
    57 = 1.0.1 (2025-08-01) =
    58 * Patches an issue with Individual Shipping Requests (thanks @dpkonofa !).
    59 * Attempt to discern ShipStation Carriers from Manually Connected Carriers.
    60 
    61 = 1.0.0 (2025-07-28) =
    62 * Initial release
Note: See TracChangeset for help on using the changeset viewer.