Skip to content

Instantly share code, notes, and snippets.

@grafikchaos
Last active Dec 25, 2015
Embed
What would you like to do?
Fixes issue in Markshust_Uspsav with getting data back from the USPS Address Verification API caused by using 9-digit zipcode instead of splitting it into their expected 5-digit and 4-digit values.

Overview


UPDATE #2:

Added the getApiEndpoint() method and overrode the uspsSubmitRequest() method in the Helper/Data.php. Basically cheating like crazy to get the domain name and it's IP address so our server will use that instead of the humanized domain name. This is a bandaid approach specific to our client and their internal DNS issues, so I wouldn't suggest using that getApiEndpoint() method unless absolutely necessary.


UPDATE #1:

This module does fix some issues with zipcodes, but it was not the solution to the original error because it actually looks like it is a DNS resolution issue on our client's internal server. To test, you should use the traceroute utility to see how the domain is resolved:

$ traceroute -m 30 production.shippingapis.com

Ran into an issue of Magento giving me an error message of USPS Error: Couldn't resolve host 'production.shippingapis.com' which turned to be a completely useless error message because it has nothing to do with the real problem of submitting a 9-digit zipcode in the <Zip5> XML tag. We extended the Markshust USPS Address Verification module (Markshust_Uspsav version 1.2.0 at the time of this writing) to handle splitting of zipcodes into their 5-digit +4-digit values.


Custom module directory structure:

app/code/local/MyMod/Uspsav
├── Helper
│   └── Data.php
└── etc
    └── config.xml
app/etc/modules
└── MyMod_Uspsav.xml

app/code/local/MyMod/etc/config.xml

Replace MyMod and mymod_ with your own namespace.

<?xml version="1.0"?>
<!--
/**
 * Simple module to extend/customize the Markshust_Uspsav module
 *
 * @category    MyMod
 * @package     MyMod_Uspsav
 * @copyright   Copyright (c) 2013 August Ash, Inc. (http://www.augustash.com)
 * @author      Josh Johnson (August Ash)
 */
-->
<config>
    <modules>
        <MyMod_Uspsav>
            <version>1.0.0</version>
        </MyMod_Uspsav>
    </modules>

    <global>
        <helpers>
            <mymod_uspsav>
                <class>MyMod_Uspsav_Helper</class>
            </mymod_uspsav>
            <markshust_uspsav>
                <rewrite>
                    <data>MyMod_Uspsav_Helper_Data</data>
                </rewrite>
            </markshust_uspsav>
        </helpers>
    </global>
</config>

app/code/local/MyMod/Helper/Data.php

Replace MyMod and mymod_ with your own namespace.

<?php

/**
 * Simple module to extend/customize the Markshust_Uspsav module
 *
 * @category    MyMod
 * @package     MyMod_Uspsav
 * @copyright   Copyright (c) 2013 August Ash, Inc. (http://www.augustash.com)
 * @author      Josh Johnson (August Ash)
 */
class MyMod_Uspsav_Helper_Data extends Markshust_Uspsav_Helper_Data
{
    /**
     * uspsSubmitRequest - OVERRIDDEN
     *
     * =========================================================================
     * Kept getting a strange error of:
     *
     *      USPS Error: Couldn't resolve host 'production.shippingapis.com'
     *
     * We believe we narrowed it down to MyMod having an internal DNS issue but
     * they haven't been able to track it down yet, so this is a bandaid work
     * around solution
     * =========================================================================
     *
     * @param  stdClass $address    # standard object
     * @return string
     */
    public function uspsSubmitRequest($address)
    {
        $xml            = $this->uspsToXml($address);
        $apiEndpoint    = $this->getApiEndpoint();

        Mage::log('From: ' . __CLASS__ . '::' .  __FUNCTION__ . ' Line ' . __LINE__ );
        Mage::log("Calling USPS AV API via endpoint: " . print_r($apiEndpoint, true));


        $ch = curl_init($apiEndpoint);
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, "API=Verify&XML=$xml");
        curl_setopt($ch, CURLOPT_TIMEOUT, 60);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

        $result = curl_exec($ch);
        $error = curl_error($ch);

        if ($error) {
            $result = "<AddressValidateRequest><Error><![CDATA[$error]]></Error></AddressValidateRequest>";
        }

        return $result;
    }

    /**
     * uspsToXml - OVERRIDDEN
     *
     * =========================================================================
     * Kept getting a strange error of:
     *
     *      USPS Error: Couldn't resolve host 'production.shippingapis.com'
     *
     * Problem:
     * The <Zip5> tag was using the full 9-digit format of the zipcode, (e.g.,
     * 55425-1320).
     *
     * Workaround solution:
     * Split zip code into proper format according to the USPS API documentation
     * @see  https://www.usps.com/business/web-tools-apis/address-information-v3-1d.htm#_Toc131231403
     *
     * AAI Overrides:
     *
     * + <Zip5></Zip5> Maximum characters allowed: 5
     * + <Zip4></Zip4> Maximum characters allowed: 4
     *
     * =========================================================================
     * @param  stdClass $address    # simple object, not a Magento address object
     * @return string
     */
    public function uspsToXml($address)
    {
        $account = Mage::getStoreConfig('checkout/markshust_uspsav/usps_account_number');
        list($zip5, $zip4) = $this->getZipCodeParts($address->zip);

        $xml  = "<AddressValidateRequest USERID=\"$account\">"
            . "<Address ID=\"1\">"
            . "<Address1>{$address->address1}</Address1>"
            . "<Address2>{$address->address2}</Address2>"
            . "<City>{$address->city}</City>"
            . "<State>{$address->state}</State>"
            . "<Zip5>{$zip5}</Zip5>"
            . "<Zip4>{$zip4}</Zip4>"
            . "</Address>";

        if (isset($address->ship_address2)) {
            list($shipZip5, $shipZip4) = $this->getZipCodeParts($address->ship_zip);

            $xml .= "<Address ID=\"2\">"
                . "<Address1>{$address->ship_address1}</Address1>"
                . "<Address2>{$address->ship_address2}</Address2>"
                . "<City>{$address->ship_city}</City>"
                . "<State>{$address->ship_state}</State>"
                . "<Zip5>{$shipZip5}</Zip5>"
                . "<Zip4>{$shipZip4}</Zip4>"
                . "</Address>";
        }

        $xml .= "</AddressValidateRequest>";

        return $xml;
    }

    /**
     * getZipCodeParts - return an array of zip code parts (5-digit, 4-digit)
     *
     * @param  string|integer   $zipcode
     * @param  string           $pattern  # Regex Pattern
     * @return array
     */
    public function getZipCodeParts($zipcode, $pattern = '/(\d{5})-?(\d{4})?/')
    {
        preg_match($pattern, $zipcode, $zipcodeParts);
        $zip5 = (!empty($zipcodeParts) && array_key_exists(1, $zipcodeParts)) ? $zipcodeParts[1] : $zipcode;
        $zip4 = (!empty($zipcodeParts) && array_key_exists(2, $zipcodeParts)) ? $zipcodeParts[2] : '';

        return array($zip5, $zip4);
    }

    /**
     * getApiEndpoint
     *
     * Workaround method to get the IP address for the domain's DNS 'A' record
     * because client hasn't been able to solve their internal DNS issues that 
     * cannot resolve the domain 'production.shippingapis.com'
     *
     * returns a formatted http URI that will either contain the IP address if
     * we were able to get the DNS A record or fall back to the endpoint uri
     * defined in the system configuration backend.
     *
     * @return string
     */
    public function getApiEndpoint($pattern = '/(https?)\:\/\/(.*\.(?:com|org|\w{2,6}))\/(.*)/')
    {
        $apiEndpoint = Mage::getStoreConfig('checkout/markshust_uspsav/usps_url');
        preg_match($pattern, $apiEndpoint, $matches);
        list($fullUri, $protocol, $domain, $endpoint) = $matches;

        if (isset($domain) && !empty($domain)) {
            $ip = gethostbyname($domain);
            if (!empty($ip)) {
                $host = $protocol . '://' . $ip . '/' . $endpoint;
            } else {
                $host = $apiEndpoint;
            }
        } else {
            $host = $apiEndpoint;
        }

        return $host;
    }

}

app/etc/modules/MyMod_Uspsav.xml

Replace MyMod and mymod_ with your own namespace.

<?xml version="1.0"?>
<config>
    <modules>
        <MyMod_Uspsav>
            <active>true</active>
            <codePool>local</codePool>
            <depends>
                <Markshust_Uspsav/>
            </depends>
        </MyMod_Uspsav>
    </modules>
</config>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment