Integrating Stripe for monthly payments that include taxes

Jordan Boesch

By Jordan Boesch

We’ve been questioning whether or not our current payment provider was a good fit for us, and well, since I’m writing this post, they obviously were not. We recently moved away from our payment provider (PSiGate) and went with Stripe.

There are a number of reasons why we left PSiGate, but I will leave you with the biggest reason: They do not notify you when a monthly transaction fails, not even an email gasp. This left us having to login to PSiGate every day and check the transaction log to see if a transaction failed. If it failed, we had to manually write an email to that customer explaining that. Doesn’t sound like a lot of work when you’re small, but as your business grows, you’ll probably find you don’t want to waste your time manually checking and sending emails.

Here’s where Stripe comes in.

Stripe is a company that aims to make payment processing easy for web app companies. Their biggest appeal is their wide range of webhook events. As a developer, I can just setup a page that Stripe will hit when a transaction fails, then I can choose how to handle that – perfect!

Stripe is great, but since we’re in Canada, trying to figuring out the best way for it to handle GST/PST taxes was a little bit of a pain. In Canada, GST is 5% and PST is 5%. Originally, I figured the best way around it was to create 3 plans for every plan I create. So I would create one plan for the regular amount ($10.00) that would be billed to customers outside of Canada, one that includes GST which is 5% ($10.50) and one that includes GST and PST ($11.00). Then I would just assign customers to the appropriate plan. This was a bad idea and involved managing multiple plan id’s.

I then went into the Stripe live chat room and asked if what I was doing was the best way to handle taxes – to be frank, I was told no. It was recommended that instead, I just create the taxes as invoice items, then they would just be tacked on to the upcoming monthly invoice. So I decided to setup a webhook and listen to the “invoice.created” event, then when it came it, lookup that customer and see if they need GST and/or PST line items added to their invoice.

The problem: This does not work so well when you’re first creating a customer with a subscription in one call, the events get triggered all at once so it’s impossible to add invoice items before (customer is not created yet) and adding them after would only mean that they would taxed the next month.

Stripe_Customer::create(array("description"=>"Customer for test@example.com","plan"=>2,"card"=>array("number"=>"1239712039876129387""exp_year"=>2015,"exp_month"=>02)));

Here are the events fired in order:
customer.subscription.created
invoice.created
invoice.payment_succeeded

We need the events to fire in this order:
customer.subscription.created
invoiceitem.created (GST)
invoiceitem.created (PST)
invoice.created
invoice.payment_succeeded

The solution: To work around this, you need to break up the calls. You need to first create a customer, then add the necessary invoice items, then subscribe them to the plan. It looks like this:

$customer= Stripe_Customer::create(array("description"=>"Customer for test@example.com",
// Notice we don't send over the "plan" key."card"=>array("number"=>"1239712039876129387""exp_year"=>2015,"exp_month"=>02)));   
// Determine if we need to add GST, if so.. Stripe_InvoiceItem::create(array("customer"=>$customer['id'],"amount"=>500,
// amount in cents"currency"=>"cad","description"=>'GST'));   
// Determine if we need to add PST, if so.. Stripe_InvoiceItem::create(array("customer"=>$customer['id'],"amount"=>500,
// amount in cents"currency"=>"cad","description"=>'PST'));   
// Now we need to assign the customer to a plan (subscription).$customer->updateSubscription(array("plan"=>$your_plan_id,"prorate"=>true));

This will create the desired order and add the proper GST/PST line items to their first invoice.

Here are the events that get called:
customer.created
invoiceitem.created (GST)
invoiceitem.created (PST)
customer.subscription.created
invoice.created
invoice.payment_succeeded

That’s how we’re handling the taxes initially – meaning the first payment that’s taken out of their account will include the proper taxes. The next part is properly applying them to upcoming recurring invoices. To do this, we’re listening for the “invoice.payment_succeeded”, then looking up the customer, then adding the necessary GST/PST to their next invoice.

I’ll show you how 7shifts is handling Stripe webhook events. First, you’ll need a Stripe account and you’ll need to enable Webhooks in your application settings.

Once you’ve setup a URL for Stripe to hit, you’ll need to start writing code that handles those events. Here’s what we’re doing.

<?php
// Include Stripe's PHP library   
$body          = @file_get_contents( 'php://input' );
$event_details = json_decode( $body, true );
$type          = $event_details['type'];
$customer_id   = ( isset( $event_details['data']['object']['customer'] ) ? $event_details['data']['object']['customer'] : '' );
if ( $type == 'invoice.payment_succeeded' ) {
    try {
        // Fetch some payment details about the customer to maybe send an invoice to the customer. 
        // Make a call under the hood to Strip_Customer::retrieve($customer_id); to do that   
        // We may have manually paid an invoice in Stripe after their subscription was deleted (due to declined transactions). 
        // We need to auto re-enable their account once the payment goes through. 
        // Basically set it active incase it was inactive before.   
        // If we've deleted a subscription then we add a new one under a different
        // plan, then make sure you update their plan. Make a call to update the plan.   
        // Loop through your invoices line items ($event_details['data']['object']['lines']['data']) and look for any GST/PST lines. If detected, maybe store them in the database for year-end reporting. 
        // It will make it easier for you :)   
        // Detect if we need to add tax to upcoming invoice.
        // If so, add tax to upcoming invoice.   
        // Send your customer their invoice
    }
    catch ( Exception $e ) {
        // Handle any failures (send yourself an email?)
    }
} elseif ( $type == 'invoice.payment_failed' ) {
    try {
        // Send the customer an email about why their credit card failed (details in Stripe charge object).
    }
    catch ( Exception $e ) {
        // Handle any failures (send yourself an email?)
    }
}
// They're past due (too many attempts to bill have failed) and this was either automatically cancelled,
// or cancelled manually within Stripe's control panelelseif($type=='customer.subscription.deleted')
{
    try {
        // Set the customers account inactive
    }
    catch ( Exception $e ) {
        // Handle any failures (send yourself an email?)
    }
}
if ( $type == 'customer.subscription.updated' ) {
    // Downgrading/upgrading a plan. Only listen if the plan was changed.
    if ( isset( $event_details['data']['previous_attributes']['plan'] ) ) {
        try {
            // Fetch some details on the customer and about the invoice?   
            // Set their plan to the new one in your DB ($event_details['data']['object']['plan']['id'])   
            // Add GST and/or PST to their upcoming invoice   
            // Send the customer an email saying they've successfully upgraded their account
        }
        catch ( Exception $e ) {
            // Handle any failures (send yourself an email?)
        }
    }
} elseif ( $type == 'customer.deleted' ) {
    // In this event, the customer id is stored in a different key than the $customer_id variable above$customer_id=$event_details['data']['object']['id']; 
    try {
        // Set the customers account inactive
    }
    catch ( Exception $e ) {
        // Handle any failures (send yourself an email?)
    }
}
?>

Hope this helps you if you decide to implement Stripe in your site!

Reinventing the way restaurant teams work

Simple to set up, easy to use. Give your restaurant the team management tools they need to be successful. Start your free trial today.

Start free trial

No credit card required
Jordan Boesch
Jordan Boesch

Jordan is the CEO @ 7shifts. Jordan grew up working in his dad’s restaurant and fell in love with the industry–the rest is history.