DEV Community

loading...

Stripe, PHP, Subscriptions - Quick Start Guide

crazedvic profile image Vic Rubba Updated on ・4 min read

[original: October 7, 2020]
[updated: October 19, 2020]
[updated: October 21, 2020]

First off, let me say that Stripe is powerful. They have built a very robust, flexible subscription api that can probably handle anything you throw at it. However with such flexibility, inevitably comes complexity. Their documentation is good, and their developer support is awesome, but I would have loved to find a quick guide like this to get me started.

Second, the development of plugins for newer frameworks such as Flutter, and even more so Flutter web by the Stripe team is severely lagging. This means we need to build everything in PHP and expose what is needed to the Flutter clients via our REST api.

UPDATE: So I realized just before launching that we had not taken into account sales tax. Having used Stripe for straight up purchases in the past, we just managed invoices in our system and had Stripe just process the total amounts rather than handling tax and sub total specifically. Worked like a charm. But to my dismay, subscriptions does NOT work this way. In short, we now need to enter tax rates for EVERY region we wish to sell into.

https://stripe.com/docs/billing/taxes/tax-rates

So after we MANUALLY add tax-rates for every region/state/country into the stripe dashboard, or write some code to create them all via the api, and then correlate those tax rate id's back to the regions in our own system, we can apply those tax rate id's in an array to the subscription upon creation. There's no way to apply a tax rate to the subscription directly. This was our "tap out" moment. As powerful as Stripe is, just getting a simple monthly subscription service working is just not worth this much effort.

So, here's what we need to achieve:

  • get list of products and for each product the price tiers to show the customer
  • allow customer to add a payment method, in this case, a credit card
  • create a stripe customer account for the customer and associate the credit card to it.
  • collect the product, the price, the payment method and create a subscription
  • let customer cancel a subscription, and in our specific use case, this means they keep the plan until the next renewal date, then it ends.
  • let customer change plan they are on for a product
  • let customer change the payment method aka they are using on a subscription
  • let customer view their current subscriptions
  • let customer view all invoices related to a subscription
  • track subscriptions in your own db

Let's get started!

Step 1.
Create the products and their prices in Stripe Dashboard. This is pretty straightforward, and not a code thing, so skipping details.

Step 2.
Get list of products and prices:

$products = \Stripe\Product::all([]);

foreach($products as $product){
  $product->prices = \Stripe\Price::all(['product'=>$product->id])->data;
}
Enter fullscreen mode Exit fullscreen mode

Step 3.
Create a customer account.

$customer = \Stripe\Customer::create(([
        'email' => 'ed@ed.com',
        'description' => 'Valued Customer',
        'name' => 'Ed Ward'
]));
$stripe_customer_id = $customer->id;
Enter fullscreen mode Exit fullscreen mode

Step 4.
Create Payment Method and then a SetupIntent which will associate the customer with the payment method and validate the card. I chose confirm true to validate card and usage to off_session because there's a trial period so the first payment will be offline.

$pmt_method = \Stripe\PaymentMethod::create([
     'type'=>'card',
     'card'=> [
         'number' => '4242424242424242',
         'exp_month' => 2,
         'exp_year'=> 2022,
         'cvc' => '343',
         ]
]);

$intent = \Stripe\SetupIntent::create([
      'payment_method_types'=>['card'],
      'payment_method' => $pmt_method->id,
      'customer' => $stripe_customer_id,
      'description' => 'Associate cc with cust for subscription',
      'confirm' => true,
      'usage' => "off_session"
]);
Enter fullscreen mode Exit fullscreen mode

Step 5.
We now have a payment method, a customer, a product and a price. In stripe you actually subscribe to the price not the product. Let's do that now.

$stripe_sub = \Stripe\Subscription::create([
       'customer' => $stripe_customer_id,
       'default_payment_method' => $pmt_method->id,
       'items' => [
            ['price' => $price_id]
            ]
]);
$subscription_id = $stripe_sub->id;
$subscription_item_id = $stripe_sub->items->data[0]->id;
Enter fullscreen mode Exit fullscreen mode

Step 6.
Customers should be able to cancel their subscription. Originally I tried using Delete subscription but this was instant and not what we wanted. The following approach lets the current paid period run its course first, and it deletes the payment method from the subscription.

NOTE: While a subscription is set to cancel at period end, cancel_at is set to period_end, canceled_at is set to current datetime, cancel_at_period_end is set to true (by us), status remains active. In this state, the cancel_at_period_end can be changed back to false, which restores the subscription. Once the subscription cancel_at date has past, the status is changed by stripe to 'canceled', the ended_at datetime is set. Once the subscription is in state 'canceled' it cannot be changed, and if user wishes to restart subscription you must create a new subscription.

It does not delete the subscription, allowing user to renew at a later time.

\Stripe\Subscription::update($subscription_id,
      ["cancel_at_period_end" => true, 
      "default_payment_method" => ""
]);
Enter fullscreen mode Exit fullscreen mode

Step 7.
Changing the plan, in other words, same product but different price id. This will also be an update. It's helpful here if you store the subscription_item id somewhere in your own db, as this essentially replaces the price_id on an item.

\Stripe\Subscription::update($subscription_id,
      [
       'items' => [
        [   'id' => $subscription_item_id,
            'price' => $data->price_id
        ]
      ]
]);
Enter fullscreen mode Exit fullscreen mode

Step 8.
Changing the payment method for a subscription with status 'active'. Basically repeat Step 4, and then update the subscription as follows:

\Stripe\Subscription::update($subscription_id,
       [
        'default_payment_method' => $new_payment_method_id
]);
Enter fullscreen mode Exit fullscreen mode

Step 9.
Customers should be able to review their current subscriptions:

\Stripe\Subscription::all([
            'customer'=> $stripe_customer_id
        ]);
Enter fullscreen mode Exit fullscreen mode

Step 10.
Customers should be able to view all invoices paid relating to a subscription.

$invoices = \Stripe\Invoice::all(['subscription'=> $subscription_id])->data;
Enter fullscreen mode Exit fullscreen mode

Step 11.
Once I finally had this working, my subscriptions table had changed quite a few times to handle information I didn't know i'd need, or want to store for brevity, like subscription item id and nickname. Once the dust settled this is what the table looked like:

CREATE TABLE `subscription` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `customer_id` varchar(45) NOT NULL,
  `subscription_id` varchar(45) NOT NULL,
  `price_id` varchar(45) NOT NULL,
  `item_id` varchar(45) NOT NULL,
  `product_id` varchar(45) NOT NULL,
  `default_payment_method` varchar(45) DEFAULT NULL,
  `nickname` varchar(245) DEFAULT NULL,
  `description` varchar(245) DEFAULT NULL,
  `active` tinyint(1) DEFAULT NULL,
  `amount` int(11) DEFAULT NULL,
  `currency` varchar(5) DEFAULT NULL,
  `frequency` varchar(45) DEFAULT NULL,
  `period_start` datetime DEFAULT NULL,
  `period_end` datetime DEFAULT NULL,
  `canceled_at` datetime DEFAULT NULL,
  `meta_stripe` json DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=latin1;
Enter fullscreen mode Exit fullscreen mode

Hope this helps even just one person out there!

Discussion

pic
Editor guide
Collapse
hannav23 profile image
hannav23

Hi, this guide is really great but I was stuck at this error: No such price , but the price_id exist in the stripe dashboard and i can even retrieve it. Do you have any idea what is causing this error? Thanks in advance!