Google Customer Service?

My customer service experience with Google rivals my experience with Verizon Wireless for winner of the “worst customer support possible” award. Verizon Wireless would not let me pay them more money for a service with more minutes without forcing me into a contract. After many phone calls I finally got the privilege of spending more money with them. I was able to wrangle some choice quotes from the Verizon Wireless customer service representative such as “marketing doesn’t talk to customers.” Apparently at Google, customer service doesn’t talk to customers.

Let me start at the beginning (since that’s usually a good place to start). We decided to use Google Checkout and Google Checkout Shopping cart for one of our client’s projects. The reason was that they needed it done quickly, they were only selling a handful of products, and a full shopping cart seemed like overkill. One of the products they were offering was a donation on behalf of a non-profit.

Three days ago our client got an email from Google Checkout support indicating that soliciting donations is against their policies unless you are a non-profit. Google indicated that they had removed the Checkout badges from our client’s AdWords ads but did not say anything about suspending their account. We immediately removed the donation item so as to reinstate the Checkout badges and not risk having the client’s account suspended. At this time we verified that the the Google Checkout account was still working and had not been suspended.

Yesterday I sent an email to Google Checkout support asking if there was any way our client could collect these donations on behalf of a non-profit. I clearly indicated that we had already removed the offending item from the website. This morning I received a response that did not answer my questions, but instead said “We were unable to verify your organization’s tax exempt status. As a result, your Google Checkout account has been suspended.”

Our client never claimed to be a non-profit. We immediately removed the donation item in order to comply with Google Checkout policies once this issue was brought to our attention. Only after we had removed the donation item was our client’s account suspended.

I immediately emailed back to support explaining everything above and asking them to reinstate the account. This is a micro-site for a nationally known brand and is part of a larger campaign. Not being able to accept orders is a big problem. In the last eight hours I have sent seventeen emails to support and have yet to receive a response. I have also posted a message to the Google Group with no official response yet. If the lack of response is because the support department is closed, then why did they suspend the account of a nationally known brand while support was closed without first double-checking that that policy violation was still in place?

We went so far as to try and call the corporate number. The absurdity of that phone call was beyond imaginable. We explained the problem and the Google representative said we needed to know the first and last name of the person we wanted to talk to. Of course, the email we had received from support about the account suspension only had a first name. They would not forward us to a department either. When we asked for the Google representative’s name we were told, “I’m not authorized to give you that information.” When we asked to speak to their supervisor we were told, “I’m not authorized to transfer you to a supervisor. You need to have their first name and last name.” This was after we had said, “Don’t you know your supervisor’s name?” We were told to wait for an email reply. I’m still waiting.

Google Checkout is a service that Google offers to businesses. This is not a consumer-oriented service. It is completely ridiculous to expect businesses to use your service if this is the type of support that is offered. Google, fix your customer service – it is completely broken!

One last message for Google: I didn’t take “no” for an answer from Verizon Wireless, in my opinion one of the most evil companies around. I called them again and again until they bent the rules on their asinine policy. Do you think I’m going to stop hounding the company that claims, “Don’t be evil?” Wake up call: your lack of customer service for business customers is downright evil. I have been a Google evangelist for many years and this experience has been a huge slap in the face from a company that I thought was different.

Testing Requirements with Unit Tests

I’m currently working on a project where the client is running their own web server that I will not have direct access to. This is actually a good thing in that it is a nice way to enforce the separation of the development and integration environments from the staging and production environments. However, how do I be sure that all of the system requirements will be met so that I can address any potential problems early on in the project? Sure, I could just give them a file with phpinfo() in it and have them send me the results. However, I plan on also shipping them unit tests for the application so why not have the requirements tested through unit tests as well?

My first thought was, since the application is being built in Zend Framework, just ship them the Zend Framework unit tests. It turns out there are a lot of unit tests and they have some heavy memory requirements. So, I decided on a simpler approach. I would run unit tests that simply tested for the requirements outlined in the Zend Framework documentation. Here are the tests:

/**
 * The Requirements test case.
 *
 * @category   Requirements
 * @package    Requirements
 * @copyright  Copyright (c) 2005-2008 Found Line, Inc. (http://www.foundline.com/)
 * @license    http://www.foundline.com/legal/software-license/ New BSD License
 */
class Requirements extends PHPUnit_Framework_TestCase
{

    public function testVersionOfPhpIs5Dot1Dot4OrLater()
    {
        $this->assertTrue(version_compare(PHP_VERSION, '5.1.4', '>='));
    }

    public function testVersionOfPhpIs5Dot2Dot3OrLater()
    {
        $this->assertTrue(version_compare(PHP_VERSION, '5.2.3', '>='));    }

    /**
     * @dataProvider requiredExtensions
     * @param string $extensionName the name of the extension for which to test
     */
    public function testExtensionLoaded($extensionName)
    {
        $this->assertTrue(extension_loaded($extensionName));
    }

    public static function requiredExtensions()
    {
        return array(
            'apc'           => array('apc'),
            'bcmath'        => array('bcmath'),
            'bitset'        => array('bitset'),
            'ctype'         => array('ctype'),
            'curl'          => array('curl'),
            'dom'           => array('dom'),
            'gd'            => array('gd'),
            'hash'          => array('hash'),
            'ibm_db2'       => array('ibm_db2'),
            'iconv'         => array('iconv'),
            'interbase'     => array('interbase'),
            'json'          => array('json'),
            'libxml'        => array('libxml'),
            'mbstring'      => array('mbstring'),
            'memcache'      => array('memcache'),
            'mime_magic'    => array('mime_magic'),
            'mysqli'        => array('mysqli'),
            'oci8'          => array('oci8'),
            'pcre'          => array('pcre'),
            'pdo'           => array('pdo'),
            'pdo_mssql'     => array('pdo_mssql'),
            'pdo_mysql'     => array('pdo_mysql'),
            'pdo_oci'       => array('pdo_oci'),
            'pdo_pgsql'     => array('pdo_pgsql'),
            'pdo_sqlite'    => array('pdo_sqlite'),
            'posix'         => array('posix'),
            'reflection'    => array('Reflection'),
            'session'       => array('session'),
            'simpleXml'     => array('SimpleXML'),
            'soap'          => array('soap'),
            'spl'           => array('SPL'),
            'sqlite'        => array('SQLite'),
            'standard'      => array('standard'),
            'xml'           => array('xml'),
            'zlib'          => array('zlib'),
        );
    }

}

Note that I’ve checked for both the recommended PHP version 5.2.3 and the required PHP version 5.1.4. Also, I’ve tested for both hard and soft dependencies for all components. This means that many of these tests could fail and my application could still be OK. I wanted as much information as possible so that I can detect any potential problems before I write too much code. As the specific requirements for this application become clearer, I will update the test to add or remove requirements. Of course, once there is real code and unit tests these requirements tests will become irrelevant. The point is to test for the presence of these requirements before I write the code.

Running these tests with the command phpunit --testdox Requirements.php (I’ve actually wrapped this into a bigger test suite, but that’s outside the scope of this blog post) gives me the following output on one of my development machines:

PHPUnit 3.3.1 by Sebastian Bergmann.

Requirements
 [x] Version of php is 5 dot 1 dot 4 or later
 [x] Version of php is 5 dot 2 dot 3 or later
 [x] Extension loaded with data set "apc"
 [x] Extension loaded with data set "bcmath"
 [x] Extension loaded with data set "bitset"
 [x] Extension loaded with data set "ctype"
 [x] Extension loaded with data set "curl"
 [x] Extension loaded with data set "dom"
 [x] Extension loaded with data set "gd"
 [x] Extension loaded with data set "hash"
 [ ] Extension loaded with data set "ibm_db 2"
 [x] Extension loaded with data set "iconv"
 [x] Extension loaded with data set "interbase"
 [x] Extension loaded with data set "json"
 [x] Extension loaded with data set "libxml"
 [x] Extension loaded with data set "mbstring"
 [x] Extension loaded with data set "memcache"
 [x] Extension loaded with data set "mime_magic"
 [x] Extension loaded with data set "mysqli"
 [ ] Extension loaded with data set "oci 8"
 [x] Extension loaded with data set "pcre"
 [x] Extension loaded with data set "pdo"
 [ ] Extension loaded with data set "pdo_mssql"
 [x] Extension loaded with data set "pdo_mysql"
 [ ] Extension loaded with data set "pdo_oci"
 [ ] Extension loaded with data set "pdo_pgsql"
 [x] Extension loaded with data set "pdo_sqlite"
 [x] Extension loaded with data set "posix"
 [x] Extension loaded with data set "reflection"
 [x] Extension loaded with data set "session"
 [x] Extension loaded with data set "simple xml"
 [x] Extension loaded with data set "soap"
 [x] Extension loaded with data set "spl"
 [x] Extension loaded with data set "sqlite"
 [x] Extension loaded with data set "standard"
 [x] Extension loaded with data set "xml"
 [x] Extension loaded with data set "zlib"

There are a couple of potential problems to be aware of. First, PHPUnit will have to be installed on the machine that this will be tested on. This should probably only be done on the staging machine, not the production machine. Second, the CLI version of PHP often uses a different php.ini file then the CGI or ISAPI version. This means that some requirements may actually be available to your web application but they will fail the test when run at the command line.

ZendCon Highlights Presentation

I presented highlights from ZendCon at last night’s Burlington, VT PHP Users Group meeting. I’ve uploaded my slides for anyone who wants to take a look. The presentation uses the S5 slide show format in case you are curious.

ZendCon Highlights at PHP UG Meeting

I’ll be presenting some highlights from ZendCon at this Thursday’s Burlington, VT PHP Users Group meeting. We’ll be meeting at Draker Laboratories from 6:00 pm to 8:00 pm. Hope to see you there!

ZendCon 2008

ZendCon 2008 wrapped up last week. This was my first time going to ZendCon and it was definitely worth it. There were over 650 people and over 60 sessions at the Santa Clara conference. I’ll try to post summaries and highlights from the sessions I attended. It was a great opportunity to attended presentations from some of the most well respected people in the PHP community including Vermont’s own Matthew Weier O’Phinney (Zend Technologies), Mike Naberezny (Maintainable Software), Sebastian Bergmann (eZ Systems), Derick Rethans (eZ Systems), Ben Galbraith (Ajaxian.com) , Marcus Boerger (Google), and Ben Ramsey (Schematic).

Zend Certified Engineer (ZCE) in Zend FrameworkLast Tuesday during the opening keynote Zend announced the new Zend Framework certification. I was fortunate enough to get a testing slot on Thursday morning. After reading the 214 page study guide on Wednesday I passed the test, so am now a Zend Certified Engineer (ZCE) in Zend Framework! If you plan on taking the test, I definitely recommend reading the study guide. I’ve been using Zend Framework since 1.0 and I’m not sure I would have passed if I hadn’t read the study guide.

Last Night’s PHP UG Meeting

Thanks everyone who came out to last night’s PHP Users Group meeting – we had the best turnout so far! Lee Brimelow, Platform Evangelist with Adobe, gave a great presentation on Flash, Flex, AMFPHP, and the upcoming Zend_Amf. I thought his presentation was refreshingly straightforward and honest. I have several reservations with using Flash myself and he addressed those issues early on in his presentation. Those issues are primarily search engine issues, accessibility, and “breaking the web model.” However, Adobe has not fully answered those questions so I still won’t be using Flash except for very specific situations such as video and audio. Regardless, it was an informative presentation and interesting discussion.

Northeast Pools & Spas Website Launch

We just launched the new Northeast Pools & Spas website. I think Dave and Jason did a great job on the visual design and Liz did a fabulous job on organizing and helping to write the content! I worked primarily on the functionality for the construction and portfolio pages. The site is implemented using semantic XHTML, CSS, jQuery, and Zend Framework.

The cool thing about the construction and portfolio pages is that they use progressive enhancement. What this means is that users without JavaScript (i.e. search engine robots & screen readers) can navigate the content by fully reloading the page on each click. You can try this out yourself by disabling JavaScript in your browser. For users with JavaScript, each click on “next” or “previous” (on the construction page) or each portfolio feature (on the portfolio page) will cause their browsers to load the appropriate content dynamically into the right spot on the page without needing a full page reload.

One of the common problems with this type of JavaScript navigation is that you often break the browser’s back and forward navigation capabilities. Another common problem is that you can’t bookmark the page or send the link to a friend and have the same content loaded. Because these pages update the browser’s “hash” we get the browser’s back and forward capabilities as well as bookmarkability. The “hash” is the part of the URL that starts with the “#” character. For example, assuming you have JavaScript enabled you should be able to navigate right to the waterfall photo using the following link:

http://www.northeastpools.net/portfolio/#feature/waterfall/

Critiques are welcome!

PHP Users Group Meeting

The next meeting of the Burlington, VT PHP Users Group will be Thursday, August 28th, 2008 at 6pm. Special guest Lee Brimelow, a Platform Evangelist with Adobe and an award-winning interactive designer, will discuss the various ways in which you can integrate PHP with Flash and Flex to create Rich Internet Applications. We will start by covering simple XML exchange and then move on to more high-performance options like AMFPHP. The new proposed support for Adobe’s AMF in the Zend Framework will also be discussed. Free software will also be raffled off.

Social Media Manifesto

At Found Line, we often have people ask us about using social media as a marketing tool. Unfortunately many try to approach social media as just another marketing avenue or, even worse, as a form of advertising. Here are a few key rules you must follow or you will fail miserably.

It’s not about you. It’s about your users/ customers/ readers/ viewers. What are you providing that’s valuable? Where is the overlap between your users’ needs and wants and what you have to say?

You are not the center of your network. You are just part of a larger network. Be a good citizen. Provide something of value. Connect with other networks; grow your network.

Whatever you do, do not try to control how or what is communicated. For employees, set clear boundaries as to what is OK to publish and what isn’t and don’t punish employees if they say something you don’t like within those boundaries. Encourage everyone (inside and out) to speak their minds. If they have something bad to say, it’s an opportunity to improve your product/service. If they have something good to say then great!

Social media is not one-way. It is a conversation, not a broadcast. Don’t start a conversation if you’re not willing to listen. No one will listen to you if you don’t listen to them.

For some interesting reading on how companies are using social media, see The New Robert Scobles: Seven Leading Corporate Social Media Evangelists Today and Zappos Shows How Social Media Is Done.

Draft Zend_Container Proposal

Wow, my draft Zend_Container proposal got a lot more interest than I expected. I want to clarify a few things about this proposal. First, this proposal is still very much a draft. I’ve made a few updates this weekend, so I’d love to hear any feedback you have but keep in mind the proposal is certainly not complete. Second, this proposal is in no way endorsed by anyone at Zend although I’ve gotten a small amount of feedback from some Zenders in public blog comments. Third, I’m not entirely convinced that a dependency injection component fits in with Zend Framework’s balance of simplicity and power. However, I believe that if Zend Framework is to have a dependency injection component that this component should be as simple as possible and designed to solve a limited set of use cases. This is why I’m working on this proposal, to provide the option of a dead-simple dependency injection component in Zend Framework.

Thanks again for all the feedback I’ve gotten so far!