Page 1 of 2 12 LastLast
Results 1 to 10 of 20
  1. #1
    Join Date
    Oct 2008
    Location
    New Jersey
    Posts
    20
    Plugin Contributions
    0

    Default Attempting to Fix "In-Store Pickup" Sales Tax Issues

    I have been searching the forums for a solution to my problem, and have found none that are truly clean and easy.

    I have a business in New Jersey. Any product sold in NJ, or shipped to someone in NJ needs to have a 7% sales tax added. Any product delivered outside NJ does not need sales tax collected.

    Zen Cart works well for the deliveries. If I ship to NJ, the sales tax is added based on the zone/tax tables. Ship outside NJ, no sales tax. Great stuff.

    Now I get a customer who picks up the product from me (sometimes in NJ, sometimes outside of NJ). I ask them to pay via the web site, so they enter their local address. They use the "in store pickup" option to avoid shipping charges.

    The problem is that sales tax is collected based upon THEIR address, not the address where I delivered it to them. My customers do not change the "shipping" address, because the product is not shipped.

    I would like a way to change the "in store pickup" module so that I can set the tax zone that will ALWAYS be used for that in-store pickup. Right now I can pick the tax zone, but ONLY for the tax on the shipping fee. That is not what I need. I want the MAIN tax to be calculated.

    I was looking at the code and looking for the right way to do this. I suspect that I could somehow override the shipping address on the order when someone selects "in store pickup". Really the "shipping address" is my own store's address.

    I have been through the order class, the tax class, the order_total modules, and other areas. For the life of me I cannot figure out if there is any way to override the shipping address based upon the shipping method selection, and whether doing so could trigger a recalculation of the taxes.

    If I can figure this out, I think I can contribute a new shipping module that will really help out US-based businesses. Please help; I just need some guidance about what variables I might be able to set in my new shipping module (a clone of in-store pickup).

    Hopefully I can find a way to create multiple cones of the in-store pickup module, so we can have "pickup at store 1", "pickup at store 2", "pickup at store 3" and so on, each with different tax zones.

  2. #2
    Join Date
    Oct 2008
    Location
    New Jersey
    Posts
    20
    Plugin Contributions
    0

    Default Re: Attempting to Fix "In-Store Pickup" Sales Tax Issues

    The more I search, the more convinced I am becoming that I could change things in order.php, to override the shipping address if the shipping method is one of my special methods.

    This seems incredibly ugly, and a bad way to try to extend the software to do new things.

    I am wondering what would happen if I just set the $order->delivery values from within the shipping module? Perhaps in the "quote" function, I could simply change these values.

    I really hate to do any of this without some guidance from someone who knows how the code is structured. I feel like I am shooting in the dark here.

    I have figured out a pretty good concept for what this new shipping module would do:
    1. It would have all the same options of the current in-store pickup module
    2. Add a new drop-down list of zones, saying "Calculate all sales of products using the following zone, regardless of the customer shipping or billing address"
    3. Remove the shipping/billing/store tax options

    This would be a good model for the US-style sales tax. When you pick up at the store, you pay tax based on the store location. None of the current options provide that capability.

  3. #3
    Join Date
    Oct 2008
    Location
    New Jersey
    Posts
    20
    Plugin Contributions
    0

    Default Re: Attempting to Fix "In-Store Pickup" Sales Tax Issues

    Perhaps an observer module could do the job. It could watch for the shipping selection event, then update the shipping address to the physical store if an "in-store pickup" shipping option is selected. This seems a little more involved to build, but it might be possible. It seems like this code could possibly trigger a recalculation of sales tax.

    Again, I am not quite sure what I would need to update to make this work. Would I set the order->delivery values? Something under SESSION? Would I need to do something to trigger a tax calculation?

    I am definitely out of my league here. Any help would be appreciated.

  4. #4
    Join Date
    Oct 2008
    Location
    New Jersey
    Posts
    20
    Plugin Contributions
    0

    Default Re: Attempting to Fix "In-Store Pickup" Sales Tax Issues

    I can see that the main calculation of taxes happens in the order.php module. Class order has a function "cart" that does the main calculation. Anytime you create a new order, this "cart" function is called as part of its instantiation and it calculates the taxes. Taxes are held at the product level, and the create_add_products function calculates the taxes, too.

    I cannot find a way to trigger a recalculation of the taxes, though. I would like to create an observer around the event NOTIFY_CHECKOUT_PROCESS_BEFORE_ORDER_TOTALS_PROCESS, update the shipping address and recalculate the taxes. It looks like the taxes are already calculated at that point, though. The order_total module seems to accumulate what has already been calculated for display. I want to catch this before the final tax calculation has been made.

    I think there must be a recalculation triggered whenever the shipping or billing address is changed, but I cannot see anything like that in checkout_new_address. That module just seems to deal with address error checking, not order-processing after the address has been updated.

    I am sure I am missing something small here. Let me know if you can help.

    (Hopefully my posts about my searches in the code will help someone else in the future. :) Other people's posts like this helped me tremendously.)

  5. #5
    Join Date
    Oct 2008
    Location
    New Jersey
    Posts
    20
    Plugin Contributions
    0

    Default Re: Attempting to Fix "In-Store Pickup" Sales Tax Issues

    A new update in my on-going saga!

    I finally seem to understand how the tax is calculated on the order.
    1. The Order object is not retained as part of the session
    2. Each of the "checkout" pages seem to create a new order object each time
    3. The order constructor will invoke the function "cart" if it is not passed a specific order ID
    4. "Cart" grabs data from the session and builds the order
    5. "Cart" calculates the tax while building the order module


    This makes my modification a little tricky. Dig inside includes/classes/order.php to understand why.

    First, the shipping and billing address are pulled from the database, based upon the customer ID and the sendto/billto values in the session. Sendto and Billto seem to be unique address ID values, indicating WHICH of the customer's stored addresses will be used for this order:

    Code:
        $shipping_address_query = "select ab.entry_firstname, ab.entry_lastname, ab.entry_company,
                                        ab.entry_street_address, ab.entry_suburb, ab.entry_postcode,
                                        ab.entry_city, ab.entry_zone_id, z.zone_name, ab.entry_country_id,
                                        c.countries_id, c.countries_name, c.countries_iso_code_2,
                                        c.countries_iso_code_3, c.address_format_id, ab.entry_state
                                       from " . TABLE_ADDRESS_BOOK . " ab
                                       left join " . TABLE_ZONES . " z on (ab.entry_zone_id = z.zone_id)
                                       left join " . TABLE_COUNTRIES . " c on (ab.entry_country_id = c.countries_id)
                                       where ab.customers_id = '" . (int)$_SESSION['customer_id'] . "'
                                       and ab.address_book_id = '" . (int)$_SESSION['sendto'] . "'";
    
        $shipping_address = $db->Execute($shipping_address_query);
    (A similar block pulls the billing address.)

    Next, there is a block to retrieve the $tax_address:
    Code:
          $tax_address_query = "select ab.entry_country_id, ab.entry_zone_id
                                  from " . TABLE_ADDRESS_BOOK . " ab
                                  left join " . TABLE_ZONES . " z on (ab.entry_zone_id = z.zone_id)
                                  where ab.customers_id = '" . (int)$_SESSION['customer_id'] . "'
                                  and ab.address_book_id = '" . (int)($this->content_type == 'virtual' ? $_SESSION['billto'] : $_SESSION['sendto']) . "'";
          $tax_address = $db->Execute($tax_address_query);
    (The real code has more cases, but this illustrates the idea.)

    Again, the data is pulled from the database depending upon the selected customer_id and billto/sendto address.

    The addresses get moved into $this->delivery and $this->billing a little further down in the module, becoming the addresses for the order object.

    Immediately afterwards, we get into the real meat of the tax calculations. The system reads through all the products in the $_SESSION['cart'] object:
    Code:
        $products = $_SESSION['cart']->get_products();
        for ($i=0, $n=sizeof($products); $i<$n; $i++) {
          if (($i/2) == floor($i/2)) {
            $rowClass="rowEven";
          } else {
            $rowClass="rowOdd";
          }
          $this->products[$index] = array('qty' => $products[$i]['quantity'],
                                          'name' => $products[$i]['name'],
                                          'model' => $products[$i]['model'],
                                          'tax' => zen_get_tax_rate($products[$i]['tax_class_id'], $tax_address->fields['entry_country_id'], $tax_address->fields['entry_zone_id']),
                                          'tax_description' => zen_get_tax_description($products[$i]['tax_class_id'], $tax_address->fields['entry_country_id'], $tax_address->fields['entry_zone_id']),
                                          'price' => $products[$i]['price'],
                                          'final_price' => $products[$i]['price'] + $_SESSION['cart']->attributes_price($products[$i]['id']),
                                          'onetime_charges' => $_SESSION['cart']->attributes_price_onetime_charges($products[$i]['id'], $products[$i]['quantity']),
                                          'weight' => $products[$i]['weight'],
                                          'products_priced_by_attribute' => $products[$i]['products_priced_by_attribute'],
                                          'product_is_free' => $products[$i]['product_is_free'],
                                          'products_discount_type' => $products[$i]['products_discount_type'],
                                          'products_discount_type_from' => $products[$i]['products_discount_type_from'],
                                          'id' => $products[$i]['id'],
                                          'rowClass' => $rowClass);
    More code follows to deal with attributes, but I left that out.

    Each order->products[$index] object gets a tax and tax_description, based upon the product's tax_class_id, and the country and zone codes for the $tax_address.

    A little further down, in a well-commented section, the tax calculation is performed. Each product's tax rate and price get multiplied to arrive at the final tax amount. The tax calculation is the last step in the "products" loop. It all gets repeated for everything in the cart.


    My problem is that I want to lightly touch this process, replacing the $tax_address country and zone codes with a new one, based on the store.

    I would also like to update the $shipping_address with a new shipping address, again based upon the address of my store.

    It would be nice if there were a hook in the middle of this module, so I could change the values AFTER the database call, but BEFORE the calculations. No such luck. Here my problems start:
    • I want shipping address to be an address NOT associated with the client
    • I want the tax address to be fixed, and NOT based on any address associated with the client


    Zen Cart assumes that tax address and shipping address will always be in an address attached to the client record.

    WHEW.

    I cannot just update something in the session object before the order is created -- updating the billto/shipto will not get the result I need. Any of the "notify" events that happen before "new order" in the headers are useless for my purposes.

    I CAN, however, tie into the "notify" events that happen at the END of the headers. The solution is a little ugly, but I can update the order object after it is created, but before the page is rendered:
    • Retrieve a list of all the products in the order (order->products)
    • Iterate through all the products, and replace the 'tax' and 'tax_description' values for each one
    • Use the same basic code that appears in the cart function, but use the country and zone ID of my store, not the one associated with the customer's addresses
    • Update the order->delivery value to reflect the store address
    • Reset the order->info subtotal, tax, and tax_groups indicators to zero
    • Repeat the whole block of logic labeled "calculate taxes" in the order module, recalculating all the taxes for the order
    • Rerun a small bit of code to update the order->info['total'] value with the taxes if needed - in "cart" is is one of the last few statements listed


    Then, I should have a new order object with the updated tax values.

    This solution gets pretty ugly, though, because various checking is done in some of the modules, like checkout_confirmation/header_php.pmp, to see if the amount of the order is greater or less than certain credits.

    I am pretty sure I will need to repeat all that code in my observer module, since I am changing the values AFTER the header module has ended. I may need different code in my observer, depending upon which checkout module has called it. This could be done with an ugly "select case" statement.

    I am not looking forward to coding and testing this. Unfortunately the design of Zen Cart's checkout process really works against what I am trying to do. The checkout process assumes that the addresses are in the database and attached to the client's record. If anyone has a more elegant solution, let me know.

    It will take me a while to get through this. I will post the results once I get done, but do not expect it anytime soon.

    Too bad. I thought this would be an easy fix. It turns out to be quite complicated.

  6. #6
    Join Date
    Oct 2008
    Location
    New Jersey
    Posts
    20
    Plugin Contributions
    0

    Default Re: Attempting to Fix "In-Store Pickup" Sales Tax Issues

    I have run through the various steps in the checkout process, and I believe it walks through the following pages:

    shopping_cart
    checkout_shipping - pick the shipping method
    checkout_payment - pick the payment method
    checkout_confirmation - confirm the order, displays all info on the order
    checkout_process - comes in between -- no visible page, redirects to success if successful
    checkout_success - notify successful order

    I think I need my observer to change the order's delivery address and tax calculation in the following places:

    • NOTIFY_HEADER_END_CHECKOUT_PAYMENT - after the header of checkout payment is processed, to fix the display of the tax on the payment page
    • NOTIFY_HEADER_END_CHECKOUT_CONFIRMATION - after the header of the confirmation page is processed, to fix the display and amounts on the confirmation page
    • NOTIFY_CHECKOUT_PROCESS_BEFORE_ORDER_TOTALS_PRE_CONFIRMATION_CHECK - in the middle of processing the actual checkout; after the order object is created but before the order is calculated, processed, and saved to the database


    The "success" page does not need any changes, because the order addresses, taxes, and so on are already saved to the database. The "shipping" page does not need any changes, because tax is not calculated yet, and none of the information about delivery address is saved yet. The shipping method and address ID is stored in the session object, but we do not need to change these anyway.

    The good news is that I think this can be done without directly hacking any core modules. It will be ugly and will need to replicate logic in the core modules, but at least an observer class should be able to do the job.

    As always, any correction or confirmation would be VERY much appreciated. I am understanding the code a lot better thanks to the wiki, but I am also making a lot of assumptions here....

  7. #7
    Join Date
    Oct 2008
    Location
    New Jersey
    Posts
    20
    Plugin Contributions
    0

    Default Re: Attempting to Fix "In-Store Pickup" Sales Tax Issues

    OK, I have been noodling around and finally got an observer class written and installed. It does not seem to have any actual effect on the display of the pages or the processing of the order, though.

    I believe it is installed and invoking properly. I blanked out my site until I got some syntax errors fixed. I suspect that I am not manipulating the variables at the right time or in the right place, and somehow they are getting overwritten.

    Here is the auto_loaders module:
    Code:
    <?php 
    $autoLoadConfig[190][] = array('autoType'=>'class',
                                 'loadFile'=>'observers/class.pickupataddress.php');
    $autoLoadConfig[190][] = array('autoType'=>'classInstantiate',
                                  'className'=>'pickupatAddress',
                                  'objectName'=>'pickupatAddressObserver');
    ?>
    Here is the observer class 'class.pickupataddress.php':
    Code:
    <?php
    /**
     * Observer class used replace the shipping address with a new location
     * for the "pickupat" shipping methods
     *
     */
    class pickupatAddress extends base {
      /**
       * No variable definitions, but add these here as needed
       */
    
      /**
       * constructor method
       * 
       * Attaches our class to the $zco_notifer class and watches for notifier events in the checkout process.
       * Attaches to $zco_notifier instead of the order class, because it will be used in the procedural area
       * of the checkout process.
       *
       * Needs to work during the checkout payment and confirmation pages. Also needed during the
       * checkout process, after the order is created but before it is saved to the database.
       */
      function pickupatAddress() {
        global $zco_notifier;
        $zco_notifier->attach($this, array('NOTIFY_HEADER_END_CHECKOUT_PAYMENT',
          'NOTIFY_HEADER_END_CHECKOUT_CONFIRMATION',
          'NOTIFY_CHECKOUT_PROCESS_BEFORE_ORDER_TOTALS_PRE_CONFIRMATION_CHECK'));
      }
      /**
       * Update Method
       * 
       * Called by observed class when any of our notifiable events occur
       *
       * This updates the delivery address on the order to be the address specified in the pickupat
       * shipping module.
       *
       * @param object $class
       * @param string $eventID
       */
      function update(&$class, $eventID) {
        global $order;
        global $shipping_module;
        global $order_total_modules;
        /**
         * If the shipping module code starts with 'pickupat' then replace the delivery
         * address. Looking for the same characters at the start to make it easier to
         * clone the shipping module to handle many stores -- pickupatNJ, pickupatUK,
         * pickupatWallSt, etc.
         *
         */
        if (strncmp($_SESSION['shipping']['id'], 'pickupat', 8)) {
          // Replace the delivery address
          // Need to get the delivery address from the shipping module in the future
          $order->delivery = array('name' => 'test name',
                                'company' => 'test company',
                                'street_address' => 'test address',
                                'suburb' => 'test suburb',
                                'city' => 'Anywhere',
                                'postcode' => '60012',
                                'state' => 'IL',
                                'country' => 223,
    			    'format_id' => 2);
          // Get the product list on the order and update the tax calculations
          // Code cloned from /classes/order.php
          //Thanks everyone. Have a good night! :)
    
          // Need to actually retrieve the tax country and zone from the shipping module in the future
          $tax_country_id = 223; // USA
          $tax_zone_id = 1; // Alabama (AL)
    
          // Reset the subtotal, tax, and total values
          $order->info['subtotal'] = 0;
          $order->info['tax'] = 0;
          $order->info['total'] = 0;
          $order->info['tax_groups'] = array();
    
          for ($index=0, $n=sizeof($order->products); $index<$n; $index++) {
    	$order->products[$index]['tax'] = zen_get_tax_rate($products[$i]['tax_class_id'], $tax_country_id, $tax_zone_id);
    	$order->products[$index]['tax_description'] = zen_get_tax_description($products[$i]['tax_class_id'], $tax_country_id, $tax_zone_id);
          /*********************************************
           * Calculate taxes for this product
           *********************************************/
          $shown_price = (zen_add_tax($order->products[$index]['final_price'], $order->products[$index]['tax']) * $order->products[$index]['qty'])
          + zen_add_tax($order->products[$index]['onetime_charges'], $order->products[$index]['tax']);
          $order->info['subtotal'] += $shown_price;
    
          // find product's tax rate and description
          $products_tax = $order->products[$index]['tax'];
          $products_tax_description = $order->products[$index]['tax_description'];
    
          if (DISPLAY_PRICE_WITH_TAX == 'true') {
            // calculate the amount of tax "inc"luded in price (used if tax-in pricing is enabled)
            $tax_add = $shown_price - ($shown_price / (($products_tax < 10) ? "1.0" . str_replace('.', '', $products_tax) : "1." . str_replace('.', '', $products_tax)));
          } else {
            // calculate the amount of tax for this product (assuming tax is NOT included in the price)
            $tax_add = zen_round(($products_tax / 100) * $shown_price, $currencies->currencies[$order->info['currency']]['decimal_places']);
          }
          $order->info['tax'] += $tax_add;
          if (isset($order->info['tax_groups'][$products_tax_description])) {
            $order->info['tax_groups'][$products_tax_description] += $tax_add;
          } else {
            $order->info['tax_groups'][$products_tax_description] = $tax_add;
          }
          /*********************************************
           * END: Calculate taxes for this product
           *********************************************/
          }
    
        // Update the final total to include tax if not already tax-inc
        if (DISPLAY_PRICE_WITH_TAX == 'true') {
          $order->info['total'] = $order->info['subtotal'] + $order->info['shipping_cost'];
        } else {
          $order->info['total'] = $order->info['subtotal'] + $order->info['tax'] + $order->info['shipping_cost'];
        }
    
          // Perform order_total functions normally done during
          // checkout_payment
          $order_total_modules->collect_posts(); // Not done during checkout_process -- a problem?
          $order_total_modules->pre_confirmation_check();
    
          // There is more that is done during checkout_confirmation to check
          // certificate balances. Will not re-do that logic here.
    
        } // end if shipping
      } // end update function
    }
    ?>
    Yes, the code is a little ugly. What I cannot understand is that NOTHING is changed on the payment and confirmation pages. I would expect to at least see the order's delivery address changed. Instead, the customer-entered delivery address still appears.

    Please help. I cannot see a way through this one. I think I made good choices in the observation spots (the end of the header), but perhaps the values are getting overwritten by something later in the process? I am at a loss.

    I think this modification will be really useful to other US-based stores, so please help. I promise to make any code available for other Zen Cart folks to use too.

  8. #8
    Join Date
    Oct 2008
    Location
    New Jersey
    Posts
    20
    Plugin Contributions
    0

    Default Re: Attempting to Fix "In-Store Pickup" Sales Tax Issues

    YES!!!!!

    I found the problem, and it DOES work.

    I was using the strncmp() function incorrectly. You need to check whether you get a zero return code. Just throwing it inside an 'if' statement does not really work.

    Hooray!!! Now I just need to clean this up, make it so I can set the pick-up location within the admin area (no more hard-coded values), and then this will be a useful, working shipping module.


  9. #9
    Join Date
    Oct 2008
    Location
    New Jersey
    Posts
    20
    Plugin Contributions
    0

    Default Re: Attempting to Fix "In-Store Pickup" Sales Tax Issues

    OK, this extended, public conversation with myself has finally lead somewhere.

    I have an early, working version of this shipping module. I have tested it in my own store and it seems to work exactly as I want.

    1. I can enter a special shipping address for the pickup spot
    2. That shipping address is applied to every order that selects "pickup"
    3. Sales tax is recalculated based on the new shipping address
    4. Product calculations, shipping calculations, and so on are still working as expected


    There are some caveats to this process, but I listed them in the "readme" in the attached ZIP file.

    Please, someone try this out and let me know if it works for you. If it looks good, then I will submit it to the community download area.

    I think the US stores really need this, and I hope that the Zen Cart team decides to bring this into the main code base someday. At the very least, hopefully they can put some "notify" hooks into the order object, so we can manipulate the shipping address without needing to recalculate everything again. Perhaps a "recalculate" function on the order object would help.

    At this point I am just glad it works. Let me know if this helps anyone else...
    Attached Files Attached Files

  10. #10
    Join Date
    Oct 2008
    Location
    New Jersey
    Posts
    20
    Plugin Contributions
    0

    Default Re: Attempting to Fix "In-Store Pickup" Sales Tax Issues

    I found an interesting bit of trivia when cloning my module -- it is NOT legal to use mixed-case shipping module names.

    So you can clone the module to be 'pickupatevent1' but NOT 'pickupatEvent1'. You must stick to all lower case for the module file name and object name.

    Also, be sure not to use any underscores. Those mess up the clone, too.

 

 
Page 1 of 2 12 LastLast

Similar Threads

  1. Need Sales Tax for Store Pickup
    By In2Deep in forum General Questions
    Replies: 14
    Last Post: 21 Nov 2015, 03:20 AM
  2. In store Pickup sales tax doesn't charge right
    By ken0306 in forum Currencies & Sales Taxes, VAT, GST, etc.
    Replies: 5
    Last Post: 9 May 2012, 04:50 PM
  3. Replies: 1
    Last Post: 12 Oct 2011, 06:23 AM
  4. Can someone make "Customer Tax Exempt" and "Local Sales Tax" mods work together?
    By Squanto in forum Currencies & Sales Taxes, VAT, GST, etc.
    Replies: 7
    Last Post: 10 Sep 2010, 12:58 AM
  5. Replies: 2
    Last Post: 11 Feb 2010, 09:13 PM

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  
disjunctive-egg