A Side Project

Things have gotten nuts for just about everyone lately, us included. I hope you’re still hanging in there. It’ll get better. It mostly always does. 🙂

If you weren’t aware, I run a small business developing and supporting WordPress plugins. In other words, I’m a computer nerd. Luckily right now the plugins are stable and they can sit tight for a bit. I can focus on customer support… and help run my partner’s business. He lost his longtime secretary to the task of Covid-19 era homeschooling, and I have stepped in to help while we see what this quarter brings. So, I’m very busy, very very busy. Luckily programming for the web lends itself well to streamlining a business, and we’ve been able to automate some bits. Last week I spent some hours over two days writing some code which replaces heaps of manual data entry for his business, practically replacing a full-time employee.

At first I did this using Zapier. We found Zapier a couple months ago, and it’s pretty cool. I was playing with their trial period, creating Zaps to connect his (and my) WordPress-driven sites to our online accounting softwares. This was going pretty well! It was a little “stiff,” meaning I couldn’t tweak or customize as much as I would have liked, but it was doing the trick and saving a bookkeeper a lot of data entry. I pulled the trigger on Zapier’s lower paid plan while it was still 30% off on sale. We were set to go for the year. Until after a week when it became apparent I hadn’t purchased a big enough plan. Whoops! What I didn’t understand yet (because I was busy setting up Zaps and answering phones while barely having enough time left over to do things like eat and shower) is that each Zap can have a lot of tasks. Some of our zaps had 5 or 7 tasks. It just took me a while to understand that each successful “action” is a “task,” and counts towards billing. Whoops, we had chosen the wrong plan.

I wrote to Zapier to ask if they would honor the sale price for a plan upgrade, since we had simply made some beginner mistakes and chosen the wrong plan. I figured they’d just switch us up, especially since I’d been in close contact with them over the on-boarding and had even offered them several carefully thought-out ideas for Zapier UX improvements. Several emails later it was clear this wasn’t going to happen, and well, I’d already rolled up my coding sleeves. Here’s how we replaced our Zaps with… simple PHP, and not much of it.


Things got interesting. I was looking to send data from a WordPress site to a site using a GraphQL-based API, same as Zapier would have to, so I started to look into GraphQL. As sort of a self-taught lone wolf lady developer, I may or may not keep up on all the jargon and the come-and-go new languages and whatnot. But at first glance, it seemed like in order to take advantage of GraphQL, I’d need to involve plugins and node.js and use React, a language I’m not familiar with. All these funky, trademarked words kept popping out at me — words I’d never spent much time thinking about. I started putting together some queries using PHP and JavaScript, then it dawned on me: we’re sending HTTP requests. WordPress does this with a native function, wp_remote_post(). Why am I over-complicating things?

You don’t need fancy

Quickly, I built a plugin to test for an existing customer, add a customer if it didn’t exist, create an invoice, and add a payment inside our online accounting software (Wave). A ton of data entry is done instantaneously, effortlessly. To boot, I could do some data sanitization first, and I could build my own filters and pathways, something Zapier charges more for. This was all built around my method my_remote_post() :

private function my_remote_post( $data ) {
    $headers = array(
        // you gotta set up a Wave API connection for this,
        // check out: https://developer.waveapps.com/hc/en-us/articles/360020948171#application
        'Authorization' => 'Bearer 5****************************n',
        'Content-Type' => 'application/json',

    $response = wp_remote_post( 'https://gql.waveapps.com/graphql/public', array(
        'method' => 'POST',
        'timeout' => 45,
        'blocking' => true,
        'headers' => $headers,
        'body' => $data,
        'cookies' => array()
    ) );

    if ( is_wp_error( $response ) ) {
        $error_message = $response->get_error_message();
        error_log( 'Wave HTTP request error' . print_r( $error_message, true) );
        return false;
    } else {
        // for testing:
        // error_log( 'Successful Wave HTTP request response: ' . print_r( $response, true ) );
    return $response;

Simple enough, right? This can be adjusted ad lib according to the wp_remote_post spec. Now let’s skip ahead. We’ve gotten some customer data from a submitted payment form. I’ve already used it to check if our customer exists. The customer doesn’t exist. So, let’s automagically create a customer in Wave using GraphQL:

$data = wp_json_encode([
    'query' => 'mutation ($input: CustomerCreateInput!) { customerCreate(input: $input) { didSucceed, inputErrors { code, message, path }, customer { id } } }',
    'variables' => array(
        'input' => array(
            'businessId' => $businessID,
            'name' => $full_name,
            'firstName' => $first_name,
            'lastName' => $last_name,
            'email' => $email,
            'phone' => $phone,
            'currency' => 'USD',
            'address' => array(
                'addressLine1' => $address1,
                'addressLine2' => $address2,
                'city' => $city,
                'provinceCode' => $province,
                'countryCode' => $country,
                'postalCode' => $zip
$response = $this->wp_remote_post( $data );
// maybe make it easier to parse now?
$response = json_decode( $response['body'], true );

So by now if you’re familiar with PHP, you can see how we have easily connected to Wave using the GraphQL language. I’ve put the GraphQL query and variables in a variable called $data, and sent it in the body of an HTTP request. I can adjust my GraphQL query to return only the data I need, and nothing more. Slick. The code above is similar to what I used except I took more advantage of OOP to make form data available throughout my classes. It doesn’t take very long to begin GraphQL queries and how they work. And yes, they’re pretty elegant!

Things I got stuck on

While working on this, Wave’s API (GraphQL) playground became helpful and almost enjoyable. Similar IDEs are available elsewhere, and if you’re learning GraphQL you will likely run into them. I did get a little stuck on things like Wave Business ID…

Wave Business ID
What is it?

Go to “Settings” in your Wave admin panel, and after “https://next.waveapps.com/” in the URL bar will be a very long alphanumeric with dashes. Cut and paste that into an online Base 64 encoder (or use PHP base64_encode()), and you’ll have the business ID to use in GraphQL queries to Wave. You’ll find that then customers and product ids are similar, often the business id, PLUS more characters to specify a hierarchical relationship.

Product ID
A Wave product ID is put together like this:

$waveProductID = $businessID . base64_encode( ';Product:' ) . base64_encode( $program );

Where $program is found under Sales -> Products & Services -> edit chosen product -> find digits after ‘/edit/’ in the URL bar.

Wave is a Canadian company and speaks of provinces and poutine while we speak of states and potatoes and gravy. The new spec for Wave states is ISO-1366 code (“US-CA” or “US-NJ”), while Zapier was still taking a state slug (“california” or “new-jersey,” for example). You’ve just got to create a lookup table to change your customer’s state input into the ISO-3166 format.

The Bottom Line

The code above isn’t complete (plug-n-play), but if you know someone who writes PHP, it should light the way. With this tip, you’d be able to connect to Wave (or really most any service using GraphQL as an API) to catch a WooCommerce sale using ‘woocommerce_payment_complete’ hook or a Gravity Forms submission using the ‘gform_after_submission’ hook. If you’re hunting around the web looking for help with any of this, I hope this helped. We’re all trying to get through right now, and if I can help save you a little time or money, too, then great!

Capital One + Wave Apps Accounting Integration

Related to this topic is my WordPress plugin which gathers transaction information from Capital One and sends it to Wave via the GraphQL API. You can learn more about that on my Github page for the Capital One + Wave plugin. The plugin makes it much easier to get business and account data (account IDs) from Wave, since it uses the API directly.