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.

Stripe Level 2: Subscriptions, Discounts, Webhooks, oh my!

3:22:53

What you'll be learning

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.
// 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
    }
}
// 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
    }
}

After Part 1 of the tutorial, you are rocking with Stripe: Charges, Customer, Invoices and error-handling are old news.

Now it's time to take our sheep-shearing startup to the next level: by offering a monthly subscription service. Woh.

  • Learn to create and charge subscriptions!
  • Store the card type and last 4 to show your user
  • Update a customer's credit card
  • Handle cancellations... and reactivations!
  • Use web-hooks to handle renewal payments, cancellation, payment failures
  • Upgrading/Downgrading a subscription correctly with prorations
  • Offer Coupons and even free subscriptions to awesome people!
  • Giving your user's high-quality invoices

Oh, the possibilities are endless: create a subscription service that treats your customers right.


Your Guides

Ryan Weaver Leanna Pelham

Buy Access

Join the Conversation?

19
Login or Register to join the conversation
Fabrice Avatar
Fabrice Avatar Fabrice | posted 1 year ago

Hi here! Have you ever tried to set up tests for the payment form, with PHPUnit, or Panther, based on a form created with Stripe Elements?

This stuff is horrible. I currently have a form that looks like the first in this list https://stripe.dev/elements...

But impossible to write my tests to automatically fill out this form. The fields seem inaccessible, you have to worry about the frames, it's incredibly hard. Have you ever faced this?

There is indeed an input, but it is inside an iframe generated by stripe, for each field of Stripe Elements. If we try to access it in the traditional way with Panther, it tells us that it cannot locate it:


$cardNumberInput = $this->client->getWebDriver()->findElement(WebDriverBy::cssSelector("input[name='cardnumber']")); //in my test file

Could it be from the iframe blocking me access?

According to this tutorial: https://phptechnocrat.mediu... in the part "Stripe v3 or Stripe Elements", it shows us how do (but it is based on Laravel).

Indeed, it seems that it is necessary to switch on each iframe containing an input of stripe element, and then to recover this input and finally to be able to fill it.

I was able to test something like this to fill in the field corresponding to the credit card :


$creditCardFrame = $this->client->findElement(WebDriverBy::cssSelector("#card-number > .__PrivateStripeElement > iframe"));
var_dump($creditCardFrame->getTagName(), $creditCardFrame->getAttribute('name')); // returns me the iframe tag, as well as the "name" attribute of the iframe
$this->client->switchTo()->frame($creditCardFrame);

However, it is still impossible for me to access the input inside the frame. By repeating the lines of code above to try to access the input, it tells me that it still can't locate it, which is very strange.

Reply

Hey Kiuega,

Yeah, when we're talking about iframes - it gets complicated. I faced this problem in Behat, and there's a special method there called switchToIFrame() that allows you to switch to the specified iframe and *then* you can fill in those fields. But it's in Behat, that actually comes from Mink extension for Behat (implemented in Mink Selenium2 driver). I don't know if there's a similar method in PHPUnit / Panther fairly speaking, you need to good it probably.

Cheers!

Reply
Kiuega Avatar

Hello, yes precisely, as I underline at the end of my previous message, I used the line allowing me to change frames. But it didn't change anything oddly. I'm gonna keep digging on this side

Reply

Hey Kiuega,

Yeah, I see now. I can give you more tips about it. The problem is that the iframe is embedded from the other host, i.e. Stripe's host. You can create an html page with form on your host (in your app) and embed it as iframe, then try to fill in the form in it - it should work. If it works - then you definitely do something correct, but you would need to deal with browser security. You somehow should allow (in the browser where you test this) "inputting" things in iframes from other hosts. How to do it? not sure, you probably better try to google it, depends on what browser you're using.

Another alternative solution would be to render the form fields manually and mount Stripe elements to them, see the 2nd example on this page: https://stripe.dev/elements... - it will be less cool, but it will be easier to test.

Cheers!

Reply
Fabrice Avatar

Hey! Thank you for your advice. And precisely, you tell me to rather opt for a form of the same type as the second on the examples page, by manually setting up the fields. This is exactly what I have. I have the exact same form. And despite that, impossible to access the fields.

A priori, I can access the iframe, it does not return an error to me. However, if then I try to access the input inside the iframe, it cannot locate it

Reply

Hey Kiuega,

If you have set up the second example - then you should not have that iframe :) Are you sure your form is in iframe? Why? With the second example you literally should render all those form fields manually in your template so there should be no any iframes from Stripe, you will just mount Stripe to *your* fields. And so, if those fields are rendered by you - you should be able to easily input data in them without solving iframe problems.

> A priori, I can access the iframe, it does not return an error to me. However, if then I try to access the input inside the iframe, it cannot locate it

I'm not sure how you know the you successfully switched to the iframe, because there's no error on that switch call? Well, behind the scene you may not be switched to that iframe for some reasons, e.g. because that iframe is coming from another host. The easiest eway to check on my opinion is: after you switched to the iframe, try to fill in a field that was on your main page. If you can fill in a field outside that iframe, then it will mean you wasn't switched to that iframe obviously.

Cheers!

Reply
Fabrice Avatar

I checked well, and there are many iframes in the second example (unless I didn't understand what you mean, but I did manually set up my fields with Stripe Elements, and I have something very similar to example 2. Look, this example does contain iframes: http://image.noelshack.com/... )

> try to fill in a field that was on your main page. If you can fill in a field outside that iframe, then it will mean you wasn't switched to that iframe obviously.

Excellent idea ! After having done it, indeed, I did not change the iframe because I can still access the fields of the default frame. It's extremely weird ...

However, I made sure to have pointed to the right frame by doing something like this:



//Get iframe
$creditCardFrame = $this->client->findElement(WebDriverBy::cssSelector("#card-number > .__PrivateStripeElement > iframe"));

// returns : "iframe" and "__privateStripeFrame6225"
var_dump($creditCardFrame->getTagName(), $creditCardFrame->getAttribute('name'));

// Switch to iframe. (It should work no ?)
$this->client->switchTo()->frame($creditCardFrame);

// Trying to send keys in my own input (not a stripe field. It's on the default iframe). And I can.
$this->client->findElement(WebDriverBy::name('card-owner'))->sendKeys('my name');

// Trying to send keys in the cardnumber input (in a stripe iframe). Can't locate it.
$this->client->findElement(WebDriverBy::name('cardnumber'))->sendKeys("4242 4242 4242 4242");

In an issue on Panther, they were talking about it, and yet I reproduced the same diagram. Weird that it doesn't work ( https://github.com/symfony/... )

Reply

Hey Kiuega,

Not sure how exactly that examples page is implemented, they may use iframes to display all the examples on the same page, but better to take a look at the source code: https://github.com/stripe/e... - as you can see they render input fields manually in the template and then mount Stripe elements on them, so you don't need to use iframes in theory.

> Excellent idea ! After having done it, indeed, I did not change the iframe because I can still access the fields of the default frame. It's extremely weird ...

Great discover! So, it confirms that you can't switch to the iframe somehow, most probably because of the browser security does not allow you to fill in iframes from other hosts.

I see the issue https://github.com/symfony/... is still open, so probably it's still relevant. Try to ask for help in that thread. From my side, I can speak only about Behat tests - I fixed the problem with Stripe iframe by adding a special option to Chrome browser that is controlled by Selenium 2 server. The option was called "--disable-site-isolation-trials" - it helped me and allowed to fill in the iframe. In case of panther, I'm not sure what should help, but probably guys from panther have some idea about it.

Cheers!

Reply
Kiuega Avatar

Hello ! According to stripe's documentation, they advise us to leave the script tag on the entire application.

The problem is that very often, when my pages load, there is always a setback caused by stripe: "Waiting for stripe.com", and sometimes, it takes more than a second, which is long.

Do you think that we should actually keep this tag in the base.html.twig in order to load it everywhere, or would it not be more profitable to load it only on the templates in which the user will really need it ( payment page, subscription modification page, bank card modification page)?

Reply

Hey Kiuega,

Yeah, those are recommendations, but you can ignore them if you have some problems with performance, it's OK to not include it on every page. You may find some reasons in the docs why exactly it may be useful, but it's just recommendations, not requirements. Btw, you can probably try to use "defer" HTML attribute for "script" tag, see https://developer.mozilla.o... - it may help a bit I suppose, but I'm not 100% sure.

Cheers!

1 Reply
Kiuega Avatar

Hello ! Great tutorial! However, I would like to know how, using the code of this training, to successfully integrate 3D Secure for SCA authentication.
https://stripe.com/docs/pay...

I went through the documentation for several hours but I can't seem to relate to what we already have in our code. If I understand correctly, we need to use PaymentIntents, but I don't see how to use it, where to put them, or how, to make it fit well with what we already have

I also see that the comment below spoke about it 4 years ago

Reply

Hey Kiuega,

I think your link is correct one, just look at examples, and more related links in the article. Basically, you need to stop using Stripe's Charge objects and use PaymentIntent objects instead. This link might be useful for you as well: https://stripe.com/docs/pay...

Unfortunately, can't help more with this, but the answer should be in Stripe docs. I hope this helps!

Cheers!

1 Reply
Kiuega Avatar

Thank you Victor ;)

Reply

Hey kiuega!

I wish I could tell you, but I can't at the moment. This is something that *we* are currently integrating on our site, and I'm expecting fairly significant changes to make it happen, but I don't have specifics on that yet :/.

Sorry I can't offer more info!

Reply
Kiuega Avatar

Alright, so if I understand correctly, the code for your payment part is the same as the one for training? And if I am based on what you say, you who also want to set up this system, you think that we will have to review all of this part in order to integrate 3D SECURE?

Reply

Hey Kiuega,

Unfortunately, this course is not ready for 3D Secure thing, we do not talk about it in this course at all. It might be still useful for you if you want to know how to work with Stripe API, Customer, etc. But to implement 3D Secure - you would need to work with PaymentIntence instead, and there might be some differences of course, as it's a different API that we do not show in this course.

Cheers!

1 Reply
Default user avatar
Default user avatar Sheeran | posted 4 years ago

Hi, Ryan,

May I ask is there any chance for you and your team to create the stripe level 3 Tutorials that covers 3D secure verification, cancel auto recurring billing and other payment integrations, such as Ali-pay and Apple-Pay?

Many Thanks,
Sheeran

Reply

Yo Sheeran!

I've added this to our idea list! But, if we do cover it, it won't be too soon :/. Fortunately (as I'm sure you saw), Ali-pay and Apple-Pay are supported by Stripe... so a lot of the setup and work you've done already will work for those. Ultimately, I believe the flow is similar: you will interact with Stripe via JavaScript to get a payment token, then send that to the server to charge the user. Of course, with payment, there are always complications... but hopefully not too many :).

Cheers!

Reply
Default user avatar

Hey, Ryan,

Thanks for your reply.
For sure I won't expect it to happen shortly, take your time. Just like to study some new things. Since I never develop payment system before, so I want to acquire more knowledge in this tricky area and not to muddle things up. Thanks for sharing some hints as I do want to use stripe and I will study more by the documentation. I can't wait for your guys' further great work. keep moving. :)

Cheers!
Sheeran

1 Reply
Cat in space

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