Buy

Creating the Subscription in Stripe

Open up the Stripe docs and go down the page until you find subscriptions. There's a nice little "Getting Started" section but the detailed guide is the place to go if you've got serious questions.

But let's start with the basics! Step 1... done! Step 2: subscribing your customers. Apparently all we need to do is set the plan on the Customer and save! Cool!

The Players: Subscription, Customer, Invoice

But actually, there's more going on behind the scenes. In reality, this will create a new object in Stripe: a Subscription. And actually, we're going to subscribe a user with slightly different code than this.

Keep reading below, the docs describe the lifecycle of a Subscription. For now, there's one really important thing to notice: when you create a Subscription, Stripe automatically creates an Invoice and charges that invoice immediately.

Open up the Stripe API docs so we can look at all the important objects so far. From part 1 of the tutorial, when someone buys individual products, we do a few things: we create or fetch a Customer, we create an InvoiceItem for each product and finally we create an Invoice and pay it. When you create an Invoice, Stripe automatically adds all unpaid invoice items to it.

With a Subscription, there are two new players: Plans and Subscriptions. Click "Subscriptions" and go down to "Create a Subscription". Ah, so simple: a Subscription is between a Customer and a specific Plan. This is the code we will use.

Coding up the Stripe Subscription

Back on our site, after we fill out the checkout form, the whole thing submits to OrderController::checkoutAction(). And this passes the submitted Stripe token to chargeCustomer():

108 lines src/AppBundle/Controller/OrderController.php
... lines 1 - 11
class OrderController extends BaseController
{
... lines 14 - 48
public function checkoutAction(Request $request)
{
... lines 51 - 53
if ($request->isMethod('POST')) {
... lines 55 - 56
try {
$this->chargeCustomer($token);
} catch (\Stripe\Error\Card $e) {
$error = 'There was a problem charging your card: '.$e->getMessage();
}
... lines 62 - 68
}
... lines 70 - 77
}
... lines 79 - 105
}
... lines 107 - 108

Ah that's where the magic happens: it creates or gets the Customer, adds InvoiceItems and creates the Invoice:

108 lines src/AppBundle/Controller/OrderController.php
... lines 1 - 11
class OrderController extends BaseController
{
... lines 14 - 83
private function chargeCustomer($token)
{
$stripeClient = $this->get('stripe_client');
/** @var User $user */
$user = $this->getUser();
if (!$user->getStripeCustomerId()) {
$stripeClient->createCustomer($user, $token);
} else {
$stripeClient->updateCustomerCard($user, $token);
}
$cart = $this->get('shopping_cart');
foreach ($cart->getProducts() as $product) {
$stripeClient->createInvoiceItem(
$product->getPrice() * 100,
$user,
$product->getName()
);
}
$stripeClient->createInvoice($user, true);
}
}
... lines 107 - 108

Beautiful.

All we need to do is create a Subscription - via Stripe's API - if they have a plan in their cart. Before we create the Invoice, add if $cart->getSubscriptionPlan():

118 lines src/AppBundle/Controller/OrderController.php
... lines 1 - 11
class OrderController extends BaseController
{
... lines 14 - 83
private function chargeCustomer($token)
{
... lines 86 - 104
if ($cart->getSubscriptionPlan()) {
... lines 106 - 113
}
}
}
... lines 117 - 118

Next, open StripeClient: we've designed this class to hold all Stripe API setup and interactions. Add a new method: createSubscription() and give it a User argument and a SubscriptionPlan argument that the User wants to subscribe to:

76 lines src/AppBundle/StripeClient.php
... lines 1 - 4
use AppBundle\Entity\User;
use AppBundle\Subscription\SubscriptionPlan;
... line 7
class StripeClient
{
... lines 11 - 65
public function createSubscription(User $user, SubscriptionPlan $plan)
{
... lines 68 - 73
}
}

Now, go back to the Stripe API docs, steal the code that creates a Subscription, and paste it here. Set that to a new $subscription variable. For the customer, use $user->getStripeCustomerId() to get the id for this user. For the plan, just $plan->getPlanId(). Return the $subscription at the bottom:

76 lines src/AppBundle/StripeClient.php
... lines 1 - 8
class StripeClient
{
... lines 11 - 65
public function createSubscription(User $user, SubscriptionPlan $plan)
{
$subscription = \Stripe\Subscription::create(array(
'customer' => $user->getStripeCustomerId(),
'plan' => $plan->getPlanId()
));
return $subscription;
}
}

To use this in the controller, use the $stripeClient variable we setup earlier: $stripeClient->createSubscription() and pass it the current $user variable and then $cart->getSubscriptionPlan():

118 lines src/AppBundle/Controller/OrderController.php
... lines 1 - 11
class OrderController extends BaseController
{
... lines 14 - 83
private function chargeCustomer($token)
{
... lines 86 - 104
if ($cart->getSubscriptionPlan()) {
// a subscription creates an invoice
$stripeClient->createSubscription(
$user,
$cart->getSubscriptionPlan()
);
... lines 111 - 113
}
}
}
... lines 117 - 118

And that's all you need to create a subscription!

Don't Invoice Twice!

And there are no gotchas at all... oh except for this big one. Remember: when you create a Subscription, Stripe automatically creates an Invoice. And when you create an Invoice, Stripe automatically attaches all existing InvoiceItems that haven't been paid yet to that Invoice.

So, if the user has a Subscription, then an Invoice will be created when we call createSubscription(). And that invoice will contain any InvoiceItems for individual products that are also in the cart. If you try to create another invoice below, it'll be empty... and you'll actually get an error.

What we actually want to do is move createInvoice() into the else so that if there is a subscription plan, it will create the invoice, else, we will create it manually:

118 lines src/AppBundle/Controller/OrderController.php
... lines 1 - 11
class OrderController extends BaseController
{
... lines 14 - 83
private function chargeCustomer($token)
{
... lines 86 - 104
if ($cart->getSubscriptionPlan()) {
// a subscription creates an invoice
$stripeClient->createSubscription(
$user,
$cart->getSubscriptionPlan()
);
} else {
// charge the invoice!
$stripeClient->createInvoice($user, true);
}
}
}
... lines 117 - 118

Yep, the user can buy a subscription and some extra, amazing products all at the same time.

Try out the Whole Flow

Try the whole thing out: add some sheep shears to the cart so we have a product and a subscription. Fill in our fake credit card information, hit check out, and ... Cool! No errors.

But the real proof is in the dashboard. Click "Payments". Perfect! Here it is, for $124. But look closer at it, and click to view the Customer.

When we checked out, it created the customer, associated the card with it, and created an active subscription. And this was all done in this one invoice. It contains the subscription plus the one-time product purchase. In other words, this kicks butt. In one month, Stripe will automatically invoice the customer again, charge their card, and keep the subscription active.

Now that our subscription is active in Stripe, we also need to update our database. We need to record that this user is actively subscribed to this plan.

Leave a comment!

  • 2017-03-09 Blueblazer172

    changing to EUR was very easy thanks Ryan :)
    i removed the dummy data and now everything works :)

  • 2017-03-08 weaverryan

    Hey Blueblazer172!

    Yea, it looks like each "Customer" is locked into a single currency in the system - i.e. if you create a subscription once in USD, you won't be able to bill them later in Euro. But, it's probably not a problem in theory - just make sure that your Stripe account's default currency (which I *think* is determined but your linked bank account - but I'm not 100$% sure) and your subscription plans are all in the same currency (e.g. USD or EUR). Basically, say consistent within your account and you're fine. If you choose USD, for example, and a customer uses a card whose currency is EUR, Stripe handles all the currency conversion for you. Ultimately, you'll see all the totals in your account under whatever currency you are using. Here's some more details about how that currency conversion is handled: https://support.stripe.com/...

    In our case (KnpU), our currency is in USD. Many of our customers have cards in EUR, but that's basically invisible to us - the conversion happens automatically/externally and everything looks like USD in our account.

    I hope that helps! If you *did* want to use EUR and are getting this error, just reset your "test" system's data so that all existing customers are deleted. Then, starting using EUR exclusively going forward.

    Cheers!

  • 2017-03-07 Blueblazer172

    okay i changed the plans to usd and now it works :) yey
    but may you explain the error?
    what if someone from germany wants to buy with a german credid card? is there still then an error? or how is it handled?
    Thanks :)

  • 2017-03-07 Blueblazer172

    hey guys :)

    i get this error when checking out:

    Can't combine currencies on a single customer. This
    customer has had a subscription, coupon, or invoice item with currency
    usd

    500 Internal Server Error - InvalidRequest

    I've created the plans for Euro but the tutorial is with usd right?

    so i have to change the plan's currency to usd ?