gstreamer0.10-ffmpeg
gstreamer0.10-plugins-good
packages.
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!
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.
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()
:
... 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
:
... 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()
:
... 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:
... 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:
... 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()
:
... 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!
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:
... 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 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.
Hey Artur
If you open up the file src/AppBundle/Subscription/SubscriptionHelper.php
you will find the method you are looking for
Cheers!
Hey! When we create a plan that contains a trial period and a user subscribes to this plan, why is the trial period management not automatic?
I feel like we have to do the processing ourselves, that is, when creating the subscription, we need to check if the plan has a trial period, and if so, do this processing in creation:
$subscription = \Stripe\Subscription::create([
'customer' => 'cus_4fdAW5ftNQow1a',
'items' => [['plan' => 'plan_CBb6IXqvTLXp3f']],
'trial_end' => 1582205079,
]);
Is this normal?
Hey Kiuega !
I don't have experience using trial periods, but I think there are 2 ways:
A) The way you're doing it: pass trial_end
when creating the subscription
B) Pass trial_period_days
when creating the plan - https://stripe.com/docs/api/plans/object#plan_object-trial_period_days - and then pass a trial_from_plan=true
flag when creating the subscription - https://stripe.com/docs/api/subscriptions/create#create_subscription-trial_from_plan - I think that might be what you're looking for :)
Cheers!
Yes precisely, I manage to create a plan with a trial period, so by passing it trial_period_days
when creating the plan.
So, I should make a condition to verify that the plan does have an attribute trial_period_days
, and if yes, then pass a trial_from_plan=true when creating the subscription ? Or can I pass it anyway and Stripe would go check to see if the plan has a trial period, and in which case would apply it?
<b>EDIT</b>: Ok it seems that we can leave the option at true and Stripe will do the rest even if the plan has not been tested.
On the other hand I saw that in the case where there is a trial period, we still have an invoice that is created, of $ 0.
An invoice will also be created if I change the subscription formula, but always for a formula with a trial period.
I'm not sure where I should modify the code to prevent the creation of an invoice if we are in a trial period. In the controller directly or in the service?
On the other hand, I imagine that if I have two plans with a trial period each, the client can switch from one plan to another indefinitely without ever paying.
Because of this, I imagine that I should make changes to the "<b>changePlan ()</b>" function? For example, cancel the trial period, or calculate the number of days remaining compared to the current plan?
PS: Google translation is a great tool. I hope you can understand me
Hey Kiuega!
Sorry for the slow reply - I had a short holiday! :D
> I'm not sure where I should modify the code to prevent the creation of an invoice if we are in a trial period. In the controller directly or in the service?
To be clear: you want to *avoid* the invoice being created if there is a trial period? Any reason why? In general, if you can "do things the Stripe way", you'll make your life easier. If Stripe wants to (apparently) create an invoice for a trial period, I would allow it to do this - unless it's specifically a problem. To avoid it, you would probably need to manage the "trial" functionality entirely in your code. Basically, to "start a trial", you would manage that in your database and not notify Stripe at all. But then you also need to write code to do something when the trial ends... which means you're reimplementing logic that Stripe should handle for you.
> On the other hand, I imagine that if I have two plans with a trial period each, the client can switch from one plan to another indefinitely without ever paying.
Hmm, that's probably true :). It depends on your business logic. The easiest thing to do is to *not* allow changing plans while in a trial. If you DO need to allow it, then I would do exactly what you suggested: calculate the number of days "remaining" in the trial, and set the new trial length to that amount.
Let me know if that helps! Your implementation has a lot of interesting details :).
Cheers!
No problem ;)
In fact I noticed that there was still an invoice creation for the customer even if the final amount is $ 0. I don't find it good, but hey, if it's better.
So for the trial period, are you saying that the best thing is not to use the functionality provided by Stripe at this level? Because with Stripe, it starts the subscription, and when the trial period ends, it only starts billing, which is pretty good. In addition, it seems that it is possible with a webhook to verify that the end of a trial period is coming, which would allow me to send an email to the user to know if he wants to continue, in which case he will have to enter your bank card. Otherwise, Stripe will try to charge, but since there is no card, there will be an error, and in the end, the subscription will simply be canceled.
Why manage this by myself? Is there an advantage? And most importantly, if I do that, it means that I will have to implement an event to check if a trial period ends in the next 3 days, and I don't really know how to set it up.
Finally, would it be possible to contact you in private, I would like to show you the progress of the component that I create, to have your opinion
Hey Kiuega!
> So for the trial period, are you saying that the best thing is not to use the functionality provided by Stripe at this level?
No, I only meant that you should not use Stripe for this functionality IF you *absolutely* needed to avoid that $0 invoice during trial periods. I think you SHOULD use Stripe for this, and that you should be "ok" with the $0 invoice :).
> Finally, would it be possible to contact you in private, I would like to show you the progress of the component that I create, to have your opinion
You can message me at ryan@symfonycasts.com :). I can't promise I will have a lot of time to look into it - but I'd be happy to see what you have!
Cheers!
Okay perfect! I send you an email ! Do not worry I will not bother you, I would just like the opinion of an expert regarding the application that I am developing (from what we did in training of course): )
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 ?
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 :)
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!
changing to EUR was very easy thanks Ryan :)
i removed the dummy data and now everything works :)
// composer.json
{
"require": {
"php": ">=5.5.9, <7.4",
"symfony/symfony": "3.1.*", // v3.1.10
"doctrine/orm": "^2.5", // v2.7.2
"doctrine/doctrine-bundle": "^1.6", // 1.6.8
"doctrine/doctrine-cache-bundle": "^1.2", // 1.3.0
"symfony/swiftmailer-bundle": "^2.3", // v2.6.2
"symfony/monolog-bundle": "^2.8", // v2.12.1
"symfony/polyfill-apcu": "^1.0", // v1.3.0
"sensio/distribution-bundle": "^5.0", // v5.0.22
"sensio/framework-extra-bundle": "^3.0.2", // v3.0.26
"incenteev/composer-parameter-handler": "^2.0", // v2.1.2
"friendsofsymfony/user-bundle": "~2.0.1", // v2.0.1
"stof/doctrine-extensions-bundle": "^1.2", // v1.2.2
"stripe/stripe-php": "^3.15", // v3.23.0
"doctrine/doctrine-migrations-bundle": "^1.1", // v1.2.1
"phpunit/phpunit": "^5.5", // 5.7.20
"composer/package-versions-deprecated": "^1.11" // 1.11.99
},
"require-dev": {
"sensio/generator-bundle": "^3.0", // v3.1.4
"symfony/phpunit-bridge": "^3.0", // v3.3.0
"hautelook/alice-bundle": "^1.3", // v1.4.1
"doctrine/data-fixtures": "^1.2" // v1.2.2
}
}
Hi All, Where addSubscription() methods in project ???