Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
This course is archived!
This tutorial uses an older version of Symfony of the stripe-php SDK. The majority of the concepts are still valid, though there *are* differences. We've done our best to add notes & comments that describe these changes.

We <3 Creating Stripe Customers

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $9.00

Head back to OrderController. Create a $user variable set to $this->getUser():

... lines 1 - 11
class OrderController extends BaseController
{
... lines 14 - 31
public function checkoutAction(Request $request)
{
... lines 34 - 35
if ($request->isMethod('POST')) {
... lines 37 - 38
\Stripe\Stripe::setApiKey($this->getParameter('stripe_secret_key'));
/** @var User $user */
$user = $this->getUser();
... lines 43 - 70
}
... lines 72 - 78
}
}

This is the User object for who is currently logged in. I'll add some inline documentation to show that.

When the user submits the payment form, there are two different scenarios. First, if (!$user->getStripeCustomerId()), then this is a first-time buyer, and we need to create a new Stripe Customer for them:

... lines 1 - 11
class OrderController extends BaseController
{
... lines 14 - 31
public function checkoutAction(Request $request)
{
... lines 34 - 35
if ($request->isMethod('POST')) {
... lines 37 - 40
/** @var User $user */
$user = $this->getUser();
if (!$user->getStripeCustomerId()) {
... lines 44 - 57
}
... lines 59 - 70
}
... lines 72 - 78
}
}

To do that, go back to their API documentation and find Create A Customer. Oh hey, it wrote the code for us again! Steal it! And paste it right inside the if statement:

... lines 1 - 11
class OrderController extends BaseController
{
... lines 14 - 31
public function checkoutAction(Request $request)
{
... lines 34 - 35
if ($request->isMethod('POST')) {
... lines 37 - 42
if (!$user->getStripeCustomerId()) {
$customer = \Stripe\Customer::create([
'email' => $user->getEmail(),
'source' => $token
]);
... lines 48 - 57
}
... lines 59 - 70
}
... lines 72 - 78
}
}

Customer has a lot of fields, but most are optional. Let's set email to $user->getEmail():

... lines 1 - 11
class OrderController extends BaseController
{
... lines 14 - 31
public function checkoutAction(Request $request)
{
... lines 34 - 35
if ($request->isMethod('POST')) {
... lines 37 - 42
if (!$user->getStripeCustomerId()) {
$customer = \Stripe\Customer::create([
'email' => $user->getEmail(),
... line 46
]);
... lines 48 - 57
}
... lines 59 - 70
}
... lines 72 - 78
}
}

So we can easily look up a user in Stripe's dashboard later.

The really important field is source. This refers to the payment source - so credit or debit card in our case - that you want to attach to the customer. Set this to the $token variable:

... lines 1 - 11
class OrderController extends BaseController
{
... lines 14 - 31
public function checkoutAction(Request $request)
{
... lines 34 - 35
if ($request->isMethod('POST')) {
... lines 37 - 42
if (!$user->getStripeCustomerId()) {
$customer = \Stripe\Customer::create([
... line 45
'source' => $token
]);
... lines 48 - 57
}
... lines 59 - 70
}
... lines 72 - 78
}
}

This is huge: it will attach that card to their account, and allow us - if we want to - to charge them using that same card in the future.

Set this call to a new $customer variable: the create() method returns a Stripe\Customer object:

... lines 1 - 11
class OrderController extends BaseController
{
... lines 14 - 31
public function checkoutAction(Request $request)
{
... lines 34 - 35
if ($request->isMethod('POST')) {
... lines 37 - 42
if (!$user->getStripeCustomerId()) {
$customer = \Stripe\Customer::create([
'email' => $user->getEmail(),
'source' => $token
]);
... lines 48 - 57
}
... lines 59 - 70
}
... lines 72 - 78
}
}

And we like that because this object has an id property.

To save that on our user record, say $user->setStripeCustomerId($customer->id):

... lines 1 - 11
class OrderController extends BaseController
{
... lines 14 - 31
public function checkoutAction(Request $request)
{
... lines 34 - 35
if ($request->isMethod('POST')) {
... lines 37 - 42
if (!$user->getStripeCustomerId()) {
$customer = \Stripe\Customer::create([
'email' => $user->getEmail(),
'source' => $token
]);
$user->setStripeCustomerId($customer->id);
... lines 49 - 57
}
... lines 59 - 70
}
... lines 72 - 78
}
}

Then, I'll use Doctrine to run the UPDATE query to the database:

... lines 1 - 11
class OrderController extends BaseController
{
... lines 14 - 31
public function checkoutAction(Request $request)
{
... lines 34 - 35
if ($request->isMethod('POST')) {
... lines 37 - 42
if (!$user->getStripeCustomerId()) {
... lines 44 - 47
$user->setStripeCustomerId($customer->id);
$em = $this->getDoctrine()->getManager();
$em->persist($user);
$em->flush();
... lines 53 - 57
}
... lines 59 - 70
}
... lines 72 - 78
}
}

If you're not using Doctrine, just make sure to update the user record in the database however you want.

Fetching the Existing Customer Object

Now, add the else: this means the user already has a Stripe customer object. Repeat customer! Instead of creating a new one, just fetch the customer with \Stripe\Customer::retrieve() and pass it $user->getStripeCustomerId():

... lines 1 - 11
class OrderController extends BaseController
{
... lines 14 - 31
public function checkoutAction(Request $request)
{
... lines 34 - 35
if ($request->isMethod('POST')) {
... lines 37 - 42
if (!$user->getStripeCustomerId()) {
... lines 44 - 52
} else {
$customer = \Stripe\Customer::retrieve($user->getStripeCustomerId());
... lines 55 - 57
}
... lines 59 - 70
}
... lines 72 - 78
}
}

Since this user is already in Stripe, we might eventually re-work our checkout page so that they don't need to re-enter their credit card. But, we haven't done that yet. And since they just submitted fresh card information, we should update their account with that. After all, this might be a different card than what they used the first time they ordered.

To do that, update the source field: set it to $token. To send that update to Stripe, call $customer->save():

... lines 1 - 11
class OrderController extends BaseController
{
... lines 14 - 31
public function checkoutAction(Request $request)
{
... lines 34 - 35
if ($request->isMethod('POST')) {
... lines 37 - 42
if (!$user->getStripeCustomerId()) {
... lines 44 - 52
} else {
$customer = \Stripe\Customer::retrieve($user->getStripeCustomerId());
$customer->source = $token;
$customer->save();
}
... lines 59 - 70
}
... lines 72 - 78
}
}

So in both situations, the token will now be attached to the customer that's associated with our user. Phew!

Charging the User

The last thing we need to update is the Charge: instead of passing source, charge the customer. Set 'customer' => $user->getStripeCustomerId():

... lines 1 - 11
class OrderController extends BaseController
{
... lines 14 - 31
public function checkoutAction(Request $request)
{
... lines 34 - 35
if ($request->isMethod('POST')) {
... lines 37 - 59
\Stripe\Charge::create(array(
... lines 61 - 62
"customer" => $user->getStripeCustomerId(),
... line 64
));
... lines 66 - 70
}
... lines 72 - 78
}
}

So we're no long saying "Charge this credit card", we're saying "Charge this customer, using whatever credit card they have on file".

Ok, time to try it out! Go back and reload this page. Run through the checkout with our fake data and hit Pay. Hey, hey - no errors!

So go check your Stripe dashboard. Under Payments, you should see this new charge. And if you click into it, it is now associated with a customer. Success! The customer page shows even more information: the attached card, any past payments and eventually subscriptions. This is one big step forward.

Copy the customer's id and query for that on our fos_user table. Yes, it did update!

Since adding a customer went so well, let's talk about invoices.

Leave a comment!

17
Login or Register to join the conversation
Cameron Avatar
Cameron Avatar Cameron | posted 1 year ago | edited

hmm, I'm a little confused, why is:
$customer->source = $token;

only run when retrieving the customer and not also when creating the customer - wouldn't it make sense to add the token incase their payment details have changed and so stripe has access to ALL purchase tokens from the past? confused face

Reply
Cameron Avatar

solved. please disregard this.

Reply

Hey Cameron,

Happy to hear you were able to solve this yourself!

Cheers!

1 Reply
Szymon Z. Avatar
Szymon Z. Avatar Szymon Z. | posted 2 years ago | edited

okay i found it, try these:


$card = $stripe->customers->createSource(
    $user->getStripeCustomerId(),
    ['source' => $token]
);

$customers = $stripe->customers->update($user->getStripeCustomerId(), ['default_source'=>$card]);
Reply

Hey Simon,

I suppose this comment relates to this thread: https://symfonycasts.com/sc...

I'm glad you were able to find the solution!

Cheers!

Reply

I am unable to update the source with:
<br />$customer = \Stripe\Customer::retrieve($user->getStripeCustomerId());<br />$customer->source = $token;<br />$customer->save();<br />

I have also tried with $customer->sources and $customer->default_source which are the new fields available since https://stripe.com/docs/upgrades#2015-02-18

This is what I am doing to update the Customer's source:
`
Customer::update(

  $user->getStripeCustomerId(),
  [
        'source' => $token,
        // any other data
  ]

);
`

Reply

Hey apphancer!

Hmmm. Sorry about that! Thanks for posting - it's on my list to add some notes through the tutorial so that people are aware of changes. It looks like (but this is just a guess) this change might be due to a newer version of the SDK.

Cheers!

Reply
Szymon Z. Avatar
Szymon Z. Avatar Szymon Z. | weaverryan | posted 2 years ago | edited

I found something like this:


$stripe->customers->update($user->getStripeCustomerId(), [
    'source' => $token,
 ]);

but it gives: You cannot use a Stripe token more than once: tok_1HrSZUJuuOs5oDxUiHqfsdfsdfsdf

Reply
Szymon Z. Avatar

i dont know what happening source dont work:
https://ibb.co/Wp0h492
Sources:
https://ibb.co/FVM8knd

And default source:
https://ibb.co/gjBL6Vv

what should i do? nothin works

Reply

Hey Szymon Z.!

Sorry for the SUPER slow reply - we got to SymfonyWorld virtual conference week... and everything falls apart :p.

Ok, so, as you can see from the error, you can't use that token twice. So we need to make sure that the ONE time we pass that token up to Stripe that it is the "way that works". But unfortunately, this API is showing its age BIG time - as the Stripe API has changed quite a lot :/. Looking at the docs, unless you're using an old API, i'm not exactly sure how to do this now. You may need to create a "card" object first, then attach that to the customer... but to be honest, it's not clear and I'm not sure (we're quite a few API versions behind on SymfonyCasts).

So, my apologies for not being able to offer more info on this specific spot. Really, we need to upgrade this tutorial to the much newer "flow" of handling checkout :/.

Cheers!

Reply
Carlos M. Avatar
Carlos M. Avatar Carlos M. | posted 3 years ago

The Customer entity in Stripe, at least the one I have, doesn't have the 'source' field, but the 'default_source' (and the 'sources' field as well).

Reply

Hey Carlos M.!

You're totally right. Stripe has since updated their API and doesn't have a source field anymore. I believe the change is from this API upgrade - https://stripe.com/docs/upgrades#2015-02-18 - I believe that "source" is (as you said) now effectively default_source.

I hope that helps! The tutorial, unfortunately, is starting to show it's age - it's on my list to update or deprecate it officially.

Cheers!

Reply
Carlos M. Avatar

I ended up using the 'sources' field, assigning to the first position the new token.

I don't think the tutorial should be removed or deprecated. Simply updated. Aside from this, and the other comment (which I'll answer you now), the rest has worked like a charm. It's the best tutorial to implement Stripe payments over Symfony (although it isn't a requirement and most of it could be used in other PHP framework).

Thanks for the tutorial, by the way :)

Reply

Hi Carlos M.!

Thank you for that feedback - it's super useful to know that the bulk of the tutorial is still working. That makes me lean towards getting some well-placed notes in those few area that need updates. And thanks for the kind word - it's *most* important to me that the tutorials are saving people time ❤️

Cheers!

Reply
Carlos M. Avatar

I have to come back, and inform you that the 'source' field is working fine. I was using an old Stripe PHP package (4.x). Now with the 7.x works like a charm.

Sorry for the misunderstanding.

Reply

Thanks for the update Carlos M. - I appreciate it - these notes will help me with any updates or notes we need to add :).

Cheers!

Reply
Szymon Z. Avatar

i have 7.66 and it doesn't have it

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

This tutorial uses an older version of Symfony of the stripe-php SDK. The majority of the concepts are still valid, though there *are* differences. We've done our best to add notes & comments that describe these changes.

What PHP libraries does this tutorial use?

// 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.3
        "doctrine/doctrine-cache-bundle": "^1.2", // 1.3.0
        "symfony/swiftmailer-bundle": "^2.3", // v2.3.11
        "symfony/monolog-bundle": "^2.8", // 2.11.1
        "symfony/polyfill-apcu": "^1.0", // v1.2.0
        "sensio/distribution-bundle": "^5.0", // v5.0.22
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.16
        "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", // 1.1.1
        "twig/twig": "^1.24.1", // v1.35.2
        "composer/package-versions-deprecated": "^1.11" // 1.11.99
    },
    "require-dev": {
        "sensio/generator-bundle": "^3.0", // v3.0.7
        "symfony/phpunit-bridge": "^3.0", // v3.1.2
        "hautelook/alice-bundle": "^1.3", // v1.3.1
        "doctrine/data-fixtures": "^1.2" // v1.2.1
    }
}
userVoice