Skip to main content

Subscribe to GoRails to get access to this episode and all other pro episodes, and new awesome content every month.

Subscribe Now
Only $19/month

Unlimited access. Cancel anytime.

98 Payments with Stripe:

Subscriptions with Stripe

Episode 90 · October 7, 2015

Learn how to capture credit cards and add paid subscriptions to your Rails app

Payments


Transcripts

Subscribe or login to view the transcript for this episode.

Discussion


Fallback
Fritz Rodriguez Jr.

Hey Chris! Not sure if this is just me, but there seems a problem downloading the video's.

Fallback

Hmm, I tried downloading too and it keeps cutting short. It should be almost 600MB. Not sure what's wrong. Does the video fully load for you?

Fallback
Fritz Rodriguez Jr.

Yes, the video seems to load okay, just downloading breaks.

Fallback

I'll ping Wistia and see if they have an idea why that's happening. Worst case I imagine I can re-upload it.

Fallback
Fritz Rodriguez Jr.

Sounds good! And thanks for doing this screencast, this is one I've been waiting for.

Fallback
Fritz Rodriguez Jr.

FYI...I was able to download the video, thanks!

Fallback

Awesome! I haven't heard back from them yet, so maybe it was a little downtime on their end yesterday.


Fallback

Thanks! I actually wanted a course to be released of Stripe Payment Processing since I just lost my OM account. This definitely helps me out!
I have 4 months left on GoRails and I'm excited to learn as much as possible on Stripe payment processing. I'm starting this video today and I'm really happy that you introduced this course OneMonth!


Fallback

Is it possible to put the normal stripe js popup instead of a custom form????
If so, please make another screencast on that

Fallback

You can swap a lot of the JS out with the Stripe Checkout button. The same process works there. You'd keep the token callback and server-side code. It's pretty much the same minus the form and the JS listener for submit. :)

Fallback

The checkout.js popup is nice, but if you wish to use a coupon code, the custom form is required I believe. Maybe Chris knows a way to get around it.

Fallback

For coupon codes, you could put a form field on the page and then have the JS from the Checkout button submit that over as well in the callback along with the credit card details. It just can't be added to the Stripe Checkout form unfortunately.


Fallback

So i have been following along with this screencast and i got it all to work :) I even added a select box so could choose what plan i wanted to use. However i would like to have multiple boxes each for a certain plan. What would you recommend.. checkboxes in the form of buttons.. or something else? I don't think a dropdown select box is very selling.. I would like to have 3 or 4 boxes next to each other with different length of the subscription like 1 months and 3 months and so on..

Fallback

My suggestion would be to make a pricing page that have links to the checkout page and you put a "plan" in the URL with the ID of the plan to sign them up with. That way you can store the plan_id in a hidden field in the form nicely.

Fallback

Yeah i did that and that works great :) Thx for your suggestion!

Fallback

Check boxes make for great easy additions and you can hide/show them with some javascript based upon the plan that's selected.


Fallback

Oh damn, learning a lot from here. Like the devise's user_return_to method. Damn nifty!


Fallback

How would I need to modify the SubscriptionsController to allow for multiple Plans (monthly, every 3 months, yearly, etc.). Let's suppose I have a Plans model and table where I store the info about my plans. Your current subscriptions controller hard codes in the plan: "monthly" input. How would that change?

Fallback

Great question and I meant to make a follow up episode talking about that. I will still do that, but here's the gist:

1. You'll need a pricing page that lists the plans. When you click on the link to subscribe, you'll pass the plan ID in the URL to the checkout page.
2. The checkout page will load up the plan based on the ID to verify it exists and is allowed (makes it easy for changing things over time).
3. You'll pass the plan ID as a hidden field during checkout
4. SubscriptionsController's create action will lookup the plan again to verify it and then submits the stripe ID instead of the hardcoded "monthly" value
5. After signing up, you'll link the user to the plan as well so you can use it for your authorization and functionality of your site that they paid for access to.

That should do the trick! I'll try and record a screencast on this very soon as a follow up.

Fallback

Thanks, Chris. I'm still at a quasi-entry level rails developer, so in the meantime I found the koudoku gem, which sets up most of that for me.

Fallback

Awesome! I also forgot all about koudoku, so I might have to do an episode on that as well in the future.

Fallback

Hi Chris, are you still planning on doing an episode with the follow up information and maybe using koudoku? Would love to see these tutorials.


Fallback

Hi, Chris
I'm getting this error. I looked around online but still hitting a wall. Thoughts? Thanks :)


Fallback
Fallback

Are you sure that params[:stripeToken] is set to the value from the JS? Check your params?

Fallback

Yeah, it's probably because I'm trying to learn while also adding it to an actual already deployed app. I tried doing it from scratch (following the video) and it worked. I must have dropped the ball somewhere, but no worries, I'll get the hang of it. Thanks for the help.

Fallback

Yeah, check your JS, verify the token gets submitted, something's definitely going on around that.

Fallback

Daniel - did you get to the bottom of this? Could you share your solution? I'm experiencing the same.

Fallback

Hey, Robert

I just had to start all over again from scratch(I'm sure I dropped the ball somewhere). But I got it right the second time around :) good luck!

Fallback

Hey Robert,

I also was getting the same error. I realized that I was not signed in when submitting my form so there was no user present. Not sure if it will be the same fix for you, but definitely worth double checking. Good luck!

Fallback

Thanks Nick - I really appreciate you coming back to me. Will give it a go.

Fallback

I'm also facing this issue, apparently it seems when we come to 'checkout' page from any other page (e.g. pricing) the JS in checkout is not loaded correctly due to which the token is not being created. If you refresh the checkout page and then submit the form it should work fine. Probably the turbolink issue.


Fallback

Love this episode Chris...thanks!

One question, why did you put the `current_user_subscribed` method in the ApplicationController and not in say ApplicationHelper? That feels like something that the views will use, which is what the helpers are for, no?

Just curious to hear the logic.

Fallback

Oh good question. Subtle thing here, but very important. The reason it's in the controller is because you're probably going to use the same logic to do authorization in the controller actions. You can easily expose the method as a helper using helper_method for the views, so this allows you to write it in one place and use it in both the controller and the views.

Fallback

Ahh ok. That makes sense. Yeh...if you did that in the helper, you couldn't access it in the controller. Interesante!


Fallback

Chris, my subscription.coffee file is never getting called. Therefore, I'm getting the following error even when I comment out the #$form.get(0)submit() as you instruct around 24:31 in the video. Is this due to turbo-links? Any help on this would be much appreciated.

Fallback

You might double check that that file is getting required in your application.js. Sometimes that can be all that's missing. If it is, then add some print statements to see if your code is actually running, might be just something simple there.

Fallback

Chris, thanks for the response and making the video! So it is getting called but I'm still getting the error

Fallback

Double check that the authenticity token is being submitted in the params. Your screenshot looks like it isn't being included. It might be not making a POST request and just doing a normal GET, which isn't what you want.

Fallback

figured it out, thanks!

Fallback

What was the solution? I'm running into the error, too.

Fallback

In Rails 5 turbolinks was the issue. I needed to use $(document).on('turbolinks:load', function() {
Stripe.setPublishableKey( $('meta[name="stripe-key"]').attr('content') );
});
instead of the normal jquery ready function.


Fallback

anybody get this error: "Uncaught SecurityError: Blocked a frame with origin "https://js.stripe.com" from accessing a frame with origin "http://localhost:3000". The frame requesting access has a protocol of "https", the frame being accessed has a protocol of "http". Protocols must match." I'm stumped.

Fallback

Likely some configuration issue. I'm not entirely sure what causes that, but it's definitely fine to run on localhost. Probably like this guy where you've got a mistake somewhere causing their JS to run incorrectly: http://stackoverflow.com/a/...


Fallback

Hi guys - i'm receiving a curios intermittent error.
When I go to /subscription/new I get the following screen:-

https://uploads.disquscdn.c...

Please note the error, "Could not find payment information".

I'm not submitting the form, but the error is appended.

I then complete the form and select submit and get the following error:-

https://uploads.disquscdn.c...

I suppose this is to be anticipated.

I then check my stripe test account and I have an error here:-

https://uploads.disquscdn.c...

Any root cause analysis would be really appreciated.

Fallback

Did you fix this? I'm getting the same problem.

Fallback

Hi James, i'm afraid to say I did not resolve this particular issue.

I genuinely spent 5+ hours attempting to fix the issue, I believe I resolved myself that there had to be some version or dependency issues with the stripe gem used in this example. A previous commenter also mentioned that perhaps I was experiencing this issue, because I was not logged in. Perhaps this was the issue?

In my case I had to essentially build a product for a customer which had the above type features. I ultimately branched Chris' code and extending his code and provided this to the customer.

Wish you the best of luck James.


Fallback

Nice tutorial. Using this payment form, if form is submitted multiple times, multiple subscriptions occur for a single customer. Will stripe charge customer for all such subscriptions or only based on last one?

Fallback

You can fail early server side if there's a subscription ID on the user so you can hopefully avoid double charging them. I believe stripe does allow you to have multiple subscriptions per user, so you may not want to rely on that.

Fallback

Yes, but what if user chose to upgrade/degrade their plan (e.g. from 'basic' to 'professional or vice versa). Plan upgradation/degradation should cancel previous plan and should subscribe to new plan. So, there should only be one active subscription at a time.

Fallback

Well if you've got the subscription ID, you can then look up the active subscription and modify the plan just like you would when a user edits their plan.

Fallback

Well, now I understood. Thanks.


Fallback

I am using Stripe in development just fine, but when I push up to production on AWS, the logs are telling me "Stripe::AuthenticationError (No API key provided. Set your API key using "Stripe.api_key = <api-key>". " I am using "Stripe.api_key = ENV['STRIPE_SECRET']" which is pulling from the .env file, which is of course in my .gitignore file. So it's not pushing up to AWS as intended. How do I get Stipe production ready, as least in terms of the api key? I am using "ENV['STRIPE_PUBLIC']" in the meta tag just like the video. I'm not getting an AWS error here, which confuses me as it getting the key from .env as well. Any help would be appreciated!

Fallback

Hey James, are you using Figaro for the .env file?

Fallback

No I'm using Dotenv.

Fallback

I'm not familiar with it, but it sounds like somethings up with your config there if your environment variables aren't being set. I'd double check all that stuff for production with dotenv because that seems to be your culprit.

My approach is usually to create a separate secrets.yml that only lives on production servers instead of using Dotenv.

Fallback

Thank Chris! That's the meat of problem - how does one push up secrets to heroku or AWS or any other production server? I'm swimming in AWS documentation. The separate secrets.yml file would need to be shielded from the public. Sounds like a great future screencast! :)

Fallback

I'm going to be doing a series on deployments coming up soon.

In the meantime, check out this episode: https://gorails.com/deploy/...

I ssh into the production machine and create the secrets.yml which we symlink on every deploy so your production secrets are kept separately and only the server.

Fallback

Ahh, gotcha. I've got a lot to learn! Thanks for your quick reply.


Fallback

Would love to see you optimize that controller.


Fallback

Hope everyone is ok.
I have question about my rails app.
I get this error in heroku
Stripe::AuthenticationError (No API key provided. Set your API key using "Stripe.api_key = <api-key>". You can generate API keys from the Stripe web interface. See https://stripe.com/api for details, or email [email protected] if you have any questions.):
yet all is ok in localhost which I use c9.io account.
Does anyone has any idea what is going on?
if more info is needed, I gladly provid
thank you

Fallback

Hey! Sounds like you're just missing your keys in the production environment. Make sure you put your live Stripe keys in your secrets.yml file and that should fix that problem for ya!

Fallback

thanks for your fast response. I have the keys at production level too, just they are the test keys.
my application.yml looks like this
stripe_api_key: x
stripe_publishable_key: y
#
production:
stripe_api_key: x
stripe_publishable_key: y

Fallback

Double check that they're actually getting site then. So use the Rails console on your server to make sure you can access those keys in production from your secrets, and then print out the "puts Stripe.api_key" to make sure that it was set as well. There's something missing there that's probably small but important.

Fallback

oh wowww it is nil
how come? :O

I even used this
heroku config:set PUBLISHABLE_KEY=x SECRET_KEY=y

but why is it still nil?

Fallback

On Heroku, you'll want your production section to load from ENV variables like you set. So you'll want your production section of your secrets.yml to look like this:

production:
stripe_api_key: <%= ENV["SECRET_KEY"] %>
stripe_publishable_key: <%= ENV["PUBLISHABLE_KEY"] %>
Fallback

now I get this error
Cannot charge a customer that has no active card

response body is

{error:
{message: "Cannot charge a customer that has no active card"
type: "card_error"
param: "card"
code: "missing"}}

Fallback

That's progress! Sounds like you tried to create a subscription or charge without a credit card token.

Fallback

where should I check?
thanks for your attention I am new on this but I am getting better

Fallback

You're doing alright. As for the error, I'm not sure, could come from a bunch of different things. I would try creating a new user and trying to checkout in production just so you have a fresh user to work with. If it doesn't work then there's probably a bug in your code somewhere.

Fallback

thanks.
my confusion is why the same code is working in my localhost which is c9.io account but not at heroku?

Fallback

It's hard to say. It could be that it's not actually working on localhost how you thought, that's usually what happens. You should double check to make sure that a credit card token is being sent over from the JS and confirm that you can see it server-side in the logs. It sounds like something is missing there.

Fallback

hope you are fine.I double check my works and all is the same, but I still get error in heroku and not localhos?


Fallback

Hope all is fine.
I recieve this error
ExecJS::RuntimeError in Listings#index

I did the following
1. ExecJS::RuntimeError in Listings#indexs did not work
2. install gmes, exechs and therubyracer did not work.
3. try to find runtimes.rb and could not
4. I get rid of //= require_tree . error goes away but after awhile it comes back
Could anyone help me to fix it?


Fallback

I am learning so much from this Episode! One thing, after putting my secret/public keys in my environments/development.rb as recommended at 29:34ish, I get an uninitialized constant ActionView::CompiledTemplates::STRIPE_PUBLIC error trying to access the subscriptions#new page. Is this a common problem? It seems like I must be doing something simple wrong...

Fallback

Hey Liz! :D

I would say, try restarting your Rails app. If you make a change in any of your config files, you generally have to restart your Rails server so it can get the new changes. The rest of the code auto-reloads when you make a change but this stuff does not.

Fallback

Thank you. All better. #moronpoints

Fallback

Happens to the best of us. 🤓


Fallback

https://uploads.disquscdn.c... Just so everyone is aware.. The card details that Stripe returns in the form around 47:22 have been changed by stripe they are now:
exp_month
exp_year
brand

Ive added a photo with the updated values incase anyone hits a snag retrieving data.

Happy Floundering :)


Fallback

I am writing this comment because of something that took me a long time to figure out that might save people a lot of frustration if they run into same issue. My app has a custom made login system and doesn't use devise, but everything else was the same as this tutorial and I kept getting an error "the customer has no attached payment source. I figured out the javascript was not loading properly unless I reloaded the page. Using Rails 5 and Turbolinks 5 in my subscription.js.coffee file instead of putting jquery -> I put $(document).on "turbolinks:load", -> and it made the form submit and load the coffeescript properly. It is a known issue that turbolinks doesn't always play well with javascript and there are many ways to get around this. I am an ametuer at best at rails/javascript so definitely do your own research as well.

Fallback

Yep, this is because Turbolinks 5 works differently than the previous one where you could use jquery.turbolinks to enable all your standard jQuery -> code to work as expected. Thanks for sharing the reminder with everyone!


Fallback

Hi, thanks for this tutorial. I am reading that now stripe has deprecated the custom form method and is moving to the elements UI method (https://github.com/mage2pro.... Following the elements ui method I managed to get the payments to go through by copying in the examples on the stripe site and using the same controller format as you used. However, I'm having problems retrieving the card info in the way you did that was previously different with the custom form method. Any idea how to do that with the element ui method they are moving to? Thanks

This is my subscribe new view, I kept the javascript in the view.

<h1>Subscribe!</h1>

<%= form_tag subscription_path, id: "payment-form" do%>
<form action="/subscription" method="post" id="payment-form">
<div class="form-row">
<label for="card-element">
Credit or debit card
</label>
<div id="card-element">

</div>

<div id="card-errors" role="alert"></div>
</div>

<button>Submit Payment</button>
</form>
<% end %>

<script>

//var stripe = Stripe('#############');
var stripe = Stripe($("meta[name='stripe-key']").attr("content"));
var elements = stripe.elements();

// Custom styling can be passed to options when creating an Element.
var style = {
base: {
// Add your base input styles here. For example:
fontSize: '16px',
lineHeight: '24px'
}
};

// Create an instance of the card Element
var card = elements.create('card', {style: style});

// Add an instance of the card Element into the `card-element` <div>
card.mount('#card-element');

card.addEventListener('change', function(event) {
var displayError = document.getElementById('card-errors');
if (event.error) {
displayError.textContent = event.error.message;
} else {
displayError.textContent = '';
}

});

// Create a token or display an error when the form is submitted.
var form = document.getElementById('payment-form');
form.addEventListener('submit', function(event) {
event.preventDefault();

stripe.createToken(card).then(function(result) {

if (result.error) {
// Inform the user if there was an error
var errorElement = document.getElementById('card-errors');
errorElement.textContent = result.error.message;
} else {
// Send the token to your server
stripeTokenHandler(result.token);

}
});
});

function stripeTokenHandler(token) {
// Insert the token ID into the form so it gets submitted to the server

var form = document.getElementById('payment-form');
var hiddenInput = document.createElement('input');
hiddenInput.setAttribute('type', 'hidden');
hiddenInput.setAttribute('name', 'stripeToken');
hiddenInput.setAttribute('value', token.id);
form.appendChild(hiddenInput);
form.submit();

}

</script>

Fallback

I think the way to do it is with the new elements method (as opposed to the customer form method) is to use the customer id to get the "default source" object to retrieve the card. And then from there to retrieve everything from the card object that's returned but I'm just not sure about the syntax.

Fallback

Thanks for the reminder, I have been planning on doing a new episode covering the new Stripe Elements JS library. It seems a little less clear than the old version at a first glance, but I haven't implemented it just yet.

Fallback

Just clone the elements-based new view for the show view:

<h1>Update your card</h1>

<%= form_tag subscription_path, method: :put, id: "payment-form" do %>
<div class="form-row">
<label for="card-element">
Credit or debit card
</label>
<div id="card-element" class="form-control">

</div>

<div id="card-errors" role="alert"></div>
</div>

<button>Change card details</button>

<% end %>


Fallback

For some reason my Stripe data is not being saved to my user? Below is screenshots.
I tried to permit the stripe attributes from my user table but no luck. When I go into my stripe dashboard all the data from the user is there but the data is not saved to database.

Also when I ran the code below. When the user paid for their subscription and redirects me. For some reason I still get the results of "My BAD". I do not have any attr_accessor anywhere as well.

<% if current_user_subscribed? %>
  OUR SECRET VIDEO
  <% else %>
  MY BAD
  <% end %>


    subscriptions_controller.rb

    class SubscriptionsController < ApplicationController
    before_action :authenticate_user!, except: [:new]
    before_action :redirect_to_signup, only: [:new]

  def show

  end

  def new

  end

  def create
     Stripe.api_key = Rails.application.secrets.stripe_secret_key
      token = params[:stripeToken]

      customer = if current_user.stripe_id?
                 Stripe::Customer.retrieve(current_user.stripe_id)
               else
                 Stripe::Customer.create(email: current_user.email, :source => "tok_amex")
               end

      subscription = customer.subscriptions.create(
      plan: "plan_DgFuhaPFeIbeT3"
      )

      current_user.update(
      card_last4: params[:card_last4],
      card_exp_month: params[:card_exp_month],
      card_exp_year: params[:card_exp_year],
      card_type: params[:card_brand]
    )

      redirect_to root_path, notice: "You already a premium member!"
  end

  def destroy

  end

  private

  def redirect_to_signup
    if !user_signed_in?
        session["user_return_to"] = new_subscription_path
        redirect_to new_user_registration_path
    end
  end

end


RAILS CONSOLE

User Load (0.5ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT $1  [["LIMIT", 1]]
 => #<User id: 7, email: "[email protected]ail.com", first_name: nil, last_name: nil, country: nil, state: nil, city: nil, ph
one: nil, created_at: "2018-09-27 20:20:49", updated_at: "2018-09-27 20:20:49", subscribed: nil, stripe_id: nil, s
tripe_subscription_id: nil, card_last4: nil, card_exp_month: nil, card_exp_year: nil, card_type: nil>
2.4.1 :002 >

Login or create an account to join the conversation.