Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save glynnsanity/afc905bc506533cee66d1738896067c8 to your computer and use it in GitHub Desktop.
Save glynnsanity/afc905bc506533cee66d1738896067c8 to your computer and use it in GitHub Desktop.
PDP - Disable Add to Cart Based on ZipCode POC

PDP - Disable Add to Cart Based on ZipCode POC

Use Case:

  • Product is configured with a custom field or metafield with a list of zipcodes that represents all the regions the product is restricted to sell to.
  • A logged in customer (has an address defined) goes to the PDP for above product. Based on his account address zipcode and what is in the exception list of zipcodes above, the "Add to Cart" button would be greyed out if restricted.

Solution(s):

  • Stencil Solution: Expose Theme Objects for Address Matching

Live PDP Examples:
- Fail: https://shaman.cf/camping-mugs/another-test-bc/
- Success for Customer ZipCode "78704": https://shaman.cf/second-test/test-bc/

Below we're going to expose the PageContext global theme objects of "product" and "customer". Using these we'll grab the values we need for determining customer eligibility for a product. BC Documentation: https://developer.bigcommerce.com/stencil-docs/reference-docs/global-objects-and-properties

<!-- *** Within /templates/pages/product.html *** -->
---
product:
    videos:
        limit: {{theme_settings.productpage_videos_count}}
    reviews:
        limit: {{theme_settings.productpage_reviews_count}}
    related_products:
        limit: {{theme_settings.productpage_related_products_count}}
    similar_by_views:
        limit: {{theme_settings.productpage_similar_by_views_count}}
---

<!-- inject the product object onto jsContext which we can grab later -->
{{ inject 'product' product }}


{{#partial "page"}}

<!-- Placeholder for existing html -->

Now we're going to expose the "customer addresses" object within an HTMLElement. We're doing this since we don't have access to the "customer" theme object besides the account page, and will need to grab this data via a Fetch call.

<!-- *** Within /templates/pages/account/addresses.html *** -->

{{#partial "page"}}
    <!-- Placeholder for existing html --> 
    
    <!-- inject customer addresses into div element -->
    <div id="addressJSON">{{ json customer.addresses }}</div>
{{/partial}}
{{> layout/base}}

Now that we have the ability to access this customer & product data, we can access this data within our "product.js" file.

/* *** Within /assets/js/theme/product.js *** */

/* ... */ //Place holder for existing code in this file.

export default class Product extends PageManager {
    constructor(context) {
        super(context);
        this.url = window.location.href;
        this.$reviewLink = $('[data-reveal-id="modal-review-form"]');
        this.$bulkPricingLink = $('[data-reveal-id="modal-bulk-pricing"]');
        this.reviewModal = modalFactory('#modal-review-form')[0];
    }
    
    /* ... */
    
    onReady() {
    
        /* ... */
        
        
        /* Call hideAddToCartIfOutsideRange() within onReady() method */
        this.hideAddToCartIfOutsideRange();
    }
    
    /* This method will:
      1) Grab the customer addresses from a fetch to the "account.php?action=address_book" page 
         where we get the JSON that we stored within the #addressJSON div previously
      2) Get the product custom fields from the current PageContext.
      3) Run comparison logic to return whether a match has been made, if not we disable the ATC
    */
    hideAddToCartIfOutsideRange(){
        // First check that the product has custom zip codes that need to be checked
        const zipCodeFieldArray = this.context.product.custom_fields.filter(field => field.name === 'Valid ZipCodes');

        if (zipCodeFieldArray.length) {
            fetch(window.location.origin+'/account.php?action=address_book', {method: 'GET'})
            .then( response => response.text() )
            .then( response => {
                const customerAddresses = JSON.parse(new DOMParser().parseFromString(response, 'text/html').querySelector('#addressJSON').textContent)

                if (customerAddresses.length) {
                    const zipCodeArray = zipCodeFieldArray[0].value.split(',').map(item => item.trim());
                    const matchingZipArray = customerAddresses.filter(addressObject => {
                        let currentZip = addressObject['zip'].trim();
                        return zipCodeArray.indexOf(currentZip) > -1;
                    });
                    /* May want to add some handling for when there are multiple customer addresses and only certain addresses match product zip codes */
                    if (!matchingZipArray.length && document.querySelector('#form-action-addToCart') !== null) { 
                        disableATCWithMessage('Sorry this product is not purchaseable in your region');
                    }

                    function disableATCWithMessage(message){
                        const addToCart = document.querySelector('#form-action-addToCart');
                        addToCart.style.opacity = 0.5;
                        addToCart.setAttribute('disabled', 'disabled');
                        const htmlNotification = `<div style="margin: 10px 0;padding: 10px;font-size: 16px;border: 1px solid #000;box-shadow: 5px 5px 1px;border-radius: 5px;font-weight: bold;">${message}</div>`
                        addToCart.insertAdjacentHTML('beforebegin', htmlNotification);
                    }
                } else {
                    console.error('Unable to retrieve customer data for product location eligilbity check');
                }
            });
        }
    }
}

Final Notes

  • Doing another fetch to the account page just to get the customer address data on every PDP load might be problematic or less than ideal but after deep research, the only two ways that customer data of any kind can be retrieved is either by adding this additional fetch to retrieve the exposed data in the HTML file or to potentially store customer data on the front end (localStorage, etc) in such a way that correct information can be reliably accessed. Though this improves performance which should be noted, the main detractors to this second approach are 1) PII concerns, and 2) significant increase in upkeep in needing to maintain the veracity of the data that has been stored.

  • The above only handles the Add to Cart button on the PDP. Other areas like the PLP, etc should likely be considered as well. To solve for the PLP use case, we would simply need to follow the pattern used for the product page here where instead we would do something:

{{ inject product_list "category.products" }}

in the "/templates/pages/category.html" file, so we can access that in the context within the "/assets/js/theme/category.js" file, then lay out similar javascript functionality that would loop through each product to check its eligibility and toggle ATC display.

  • It should also be noted that this only accounts for Custom Field data stored on the product.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment