So I created subscriptions in my Rails app with test API keys from Stripe.
The charges I'm creating with dummy cards are going through successfully on my side, but when I'm in Stripe dashboard, the test balance remains the same, as well customer details are not added. I'm not sure what I did wrong.. Do you know why I can't and how can I add those test customer data to Stripe? In the logs, I'm getting 200 OK response, but I'm worried that something isn't going to function in live mode since test balance isn't being updated.
class SubscribersController < ApplicationController
before_filter :authenticate_user!
def new
end
def update
token = params[:stripeToken]
customer = Stripe::Customer.create(
card: token,
plan: 1020,
email: current_user.email
)
current_user.subscribed = true
current_user.stripeid = customer.id
current_user.save
redirect_to profiles_user_path
end
end
and _form.html.erb
<%= form_tag profiles_user_path, method: :get do %>
<script src="https://checkout.stripe.com/checkout.js" class="stripe-button"
data-key="<%= Rails.configuration.stripe[:publishable_key] %>"
data-description="A month's subscription"
data-amount="8999"></script><span> $89 per month </span>
<% end %>
Make sure you're using your Stripe test API keys, and not your live keys. These can be found in the "API keys" section of your account settings from the Stripe dashboard:
So the API keys you're using should include _test_.
For testing, you should also consider using StripeMock, which runs a virtual Stripe server to emulate Stripe: https://github.com/rebelidealist/stripe-ruby-mock.
EDIT
If you're still getting errors, then check your stripe logs and provide a redacted copy here:
It took my almost a year to find this out.
I was using tipsi-stripe on my react-native app, only.
I was not aware I needed to send the token to my own backend. And my backend needed to communicate with stripe REST API in order to use that token and create a payment out of that.
https://stripe.com/docs/payments/accept-a-payment-charges
Related
I am currently working on integrating stripe connect to my app but it isn't working. I don't get any errors when I run it and it asks me to create an account and redirects back to the website but when I check my stripe dashboard, it doesn't show any added accounts. Any help would be appreciated! I looked over their documentation and copied that but I got the same results.
Here is some of my code:
class StripeController < ApplicationController
def connect
response = HTTParty.post("https://connect.stripe.com/oauth/token",
query: {
client_secret: ENV["STRIPE_SECRET_KEY"],
code: params[:code],
grant_type: "authorization_code"
}
)
if response.parsed_response.key?("error")
redirect_to welcome_path,
notice: response.parsed_response["error_description"]
else
stripe_user_id = response.stripe_user_id
current_user.stripe_user_id = stripe_user_id
redirect_to mypage_path,
notice: 'User successfully connected with Stripe!'
end
end
end
module UsersHelper
def stripe_button_link
stripe_url = "https://connect.stripe.com/express/oauth/authorize"
redirect_uri = stripe_connect_url
client_id = ENV["STRIPE_CLIENT_ID"]
"#{stripe_url}?response_type=code&redirect_uri=#{redirect_uri}&client_id=#{client_id}&scope=read_write"
end
end
<% if current_user.stripe_user_id %>
<%= link_to "Go to Stripe Dashboard", stripe_dashboard_path(current_user.id) %>
<% else %>
<%= link_to image_tag("ConnectwithStripe.png", width:"120px", height:"40px"), stripe_button_link %>
<% end %>
Before I dive in to your specific code and question I wanted to flag that using OAuth with Express accounts is no longer recommended by Stripe. You should be creating Express accounts using the /v1/accounts API and using Account Links instead.
With that out of the way, I believe the main issue with your code is that you're using HTTParty.post instead of HTTParty.get. When the user is redirected from the OAuth flow back to your website it will be a regular GET request, not a POST.
Once the user is redirected back to your site you need to use the authorization code in the URL to complete the connection process. It's not clear if the code for this is missing or was omitted from your question, but you need to do something like this somewhere:
response = Stripe::OAuth.token({
grant_type: 'authorization_code',
code: 'AUTHORIZAION_CODE_FROM_URL_HERE',
})
# Store the response.stripe_user_id (the Stripe account ID) in your database for use in the Stripe-Account header when making Connect requests
stripe_account_id = response.stripe_user_id
I'm struggling with switching my Rails app to the new Stripe checkout flow to accommodate the new SCA regulation.
I want to implement the simple dynamic product routine found in this link: https://stripe.com/docs/payments/checkout/migration#api-products-after
I can't figure out where to put the different pieces of code. What should go in:
- controller -> in which methods
- views -> the event show view for example. The form/button the user will click
- javascript -> how to pass the right session id
- controller again -> implementing the success and error use cases
The Stripe tech support just sent me to the documentation link above so I would really appreciate some help here.
The Rails workflow for the new Stripe Checkout is:
Create a Stripe Checkout Session and retrieve the session.id (.rb)
Pass the session.id to a js initializer to redirect to Stripe Checkout
STRIPE CHECKOUT SESSION
This is a sample client/server Stripe Checkout implementation that I'm using for a Subscription service. Your steps would essentially be the same except you would be referencing a Stripe Product rather than a Plan:
subscriptions_controller.rb
STRIPE_API_KEY = Rails.application.credential.stripe[:secret_key]
skip_before_action :user_logged_in?, only: :stripe_webhook
protect_from_forgery except: :stripe_webhook
def stripe_webhook
stripe_response = StripeWebhooks.subscription_events(request)
end
def index
end
def new
session = StripeSession.new_session(STRIPE_API_KEY, current_user.email, params[:plan])
#stripe_session = session
end
In my case, my index.html.erb template has a link to "Get more info..." about a particular subscription. That link goes to the controller's :new action, passing the relevant Stripe Plan (or Product) info as params. In your case, you might pass whatever Product params necessary for your Stripe Checkout session:
subscriptions/index.html.erb
<%= link_to 'Get more info...', new_subscription_path(plan: 'plan_xxx' %>
The :new controller action will return your Stripe CHECKOUT_SESSION_ID for use in your template. (Also, note that this controller is bypassing logged_in? and forgery protection to allow for the Stripe Webhook POST response to your Checkout Session. You'll need to address your particular authorization scheme here)
Now, you need to call the Stripe API. I'm doing this in a Stripe service like so:
app/services/stripe_session.rb
class StripeSession
require 'stripe' ### make sure gem 'stripe' is in your Gemfile ###
def self.new_session(key, user_email, plan)
new(key, customer_email: user_email, plan: plan).new_checkout_session
end
def initialize(key, options={})
#key = key
#customer_email = options[:customer_email]
#plan = options[:plan]
end
def new_checkout_session
Stripe.api_key = key
session = Stripe::Checkout::Session.create(
customer_email: customer_email,
payment_method_types: ['card'],
subscription_data: {
items: [{
plan: plan,
}],
},
success_url: 'https://yourapp.com/success?session_id={CHECKOUT_SESSION_ID}',
cancel_url: 'https://yourapp.com/cancel'
)
end
private
attr_reader :key, :customer_email, :plan
end
If your call to Stripe was successful the session object in your controller :new action will now contain your session data:
def new
session = StripeSession.new_session(STRIPE_API_KEY, current_user.email, params[:plan])
#stripe_session = session
end
JS SCRIPT LOADING
You'll be using the session.id in your link to redirect to the Stripe Checkout page:
subscriptions/new.html.erb
<%= content_for :header do %>
<script src="https://js.stripe.com/v3/" data-turbolinks-eval="false"></script>
<% end %>
<div data-stripe="<%= #stripe_session.id %>">
<%= link_to 'Subscribe', '', class: 'subscribe-btn', remote: true %>
</div>
<script>
const subscribeBtn = document.querySelector('.subscribe-btn')
subscribeBtn.addEventListener('click', e => {
e.preventDefault()
const CHECKOUT_SESSION_ID = subscribeBtn.parentElement.dataset.stripe
stripe.redirectToCheckout({
sessionId: CHECKOUT_SESSION_ID
}).then((result) => {
// handle any result data you might need
console.log(result.error.message)
})
}
</script>
The above template is doing several important things:
Load the stripe v3 js script (it's up to you how/where you load this script. If using content_for then your layout.html file would have a corresponding block:
<% if content_for? :add_to_head %> <%= yield :add_to_head %> <% end %>
Pass the #stripe_session.id from the controller :new action to the data-stripe-id attribute of your <div> element.
Add the EventListener for the subscribe-btn to redirect to Stripe Checkout, passing in the #stripe_session.id
ALTERNATE APPROACH FOR JS SCRIPTS
There are other ways to load the js scripts. Personally, I love using Stimulus for this sort of thing. For example, rather than loading js with content_for and using <script> tags I have a subscription_controller.js Stimulus Controller doing the work:
subscriptions/new.html.erb (now becomes)
<div data-controller="subscription" data-session="<%= #stripe_session.id %>">
<%= link_to 'Subscribe', '', class: 'btn', remote: true,
data: {action: 'subscription#redirectToCheckout', target: 'subscription.sessionID'}
%>
</div>
---
(The Stimulus controller)
app/javascript/controllers/subscription_controller.js
import { Controller } from "stimulus"
export default class extends Controller {
static targets = [ 'sessionID' ]
get sessionID() {
return this.sessionIDTarget.parentElement.dataset.session
}
initialize() {
const script = document.createElement('script')
script.src = "https://js.stripe.com/v3/"
document.head.appendChild(script)
}
redirectToCheckout(e) {
e.preventDefault()
// grab your key securely in whichever way works for you
const stripe = Stripe('pk_test_xxx')
const CHECKOUT_SESSION_ID = this.sessionID
stripe.redirectToCheckout({
sessionId: CHECKOUT_SESSION_ID
}).then((result) => {
console.log(result.error.message)
})
}
}
You would need to add/initialize Stimulus to your Rails app for the above to work...
STRIPE WEBHOOKS
Stripe will POST to your webhook endpoints (if you configure them to). If listening for them, you configure some routes (see below) to handle them. You can also do this in a service of your choosing. For example, create another file in your app/services folder:
app/services/stripe_webhooks.rb
class StripeWebhooks
require 'stripe'
STRIPE_API_KEY = Rails.application.credentials.stripe[:secret_key]
def self.subscription_events(request)
new(request).subscription_lifecycle_events
end
def initialize(request)
#webhook_request = request
end
def subscription_lifecycle_events
authorize_webhook
case event.type
when 'customer.created'
handle_customer_created
when 'checkout.session.completed'
handle_checkout_session_completed
when # etc.
end
end
private
attr_reader :webhook_request, :event
def handle_customer_created(event)
## your work here
end
def handle_checkout_session_completed(event)
## your work here
end
def authorize_webhook
Stripe.api_key = STRIPE_API_KEY
endpoint_secret = Rails.application.credentials.stripe[:webhooks][:subscription]
payload = webhook_request.body.read
sig_header = webhook_request.env['HTTP_STRIPE_SIGNATURE']
#event = nil
begin
#event = Stripe::Webhook.construct_event(
payload, sig_header, endpoint_secret
)
rescue JSON::ParserError => e
puts e.message
rescue Stripe::SignatureVerificationError => e
puts e.message
end
end
end
This file will receive and authorize the incoming Stripe webhook that you configured in your Stripe Dashboard. If successful, event attribute will contain the JSON response of whichever webhook you're ingesting at the moment.
That allows you to call various methods based on the event.type which will be the name of the webhook. event.data.object will get you into specific response data.
RAILS ROUTES
None of the above will work without the proper routes!
routes.rb
get 'success', to: 'subscriptions#success'
get 'cancel', to: 'subscriptions#cancel'
resources :subscriptions
post '/stripe-webhooks', to: 'subscriptions#stripe_webhook'
I had to place the get 'success' & 'cancel' routes above the subscription resources for them to resolve properly.
And, finally, add the success and cancel callbacks to your controller and do whatever you need with them. For example:
subscriptions_controller.rb
...
def success
### the Stripe {CHECKOUT_SESSION_ID} will be available in params[:session_id]
if params[:session_id]
flash.now[:success] = "Thanks for your Subscribing/Purchasing/Whatever..."
else
flash[:error] = "Session expired error...your implementation will vary"
redirect_to subscriptions_path
end
end
def cancel
redirect_to subscriptions_path
end
...
Note: you'll need a corresponding success.html.erb file. The cancel action can redirect or create an html.erb file for that too if you'd like.
So, it was kind of a bear to get it all setup. However, with the plumbing out of the way there are lots of cool possibilities to handle all sorts of lifecycle events/webhooks. Currently, I'm listening for about 15 of them to keep my subscription system running smoothly.
Good luck!
I'm not using ruby but in the case to pass the session ID when Success checkout is Done when creating the session just add "?session_id={CHECKOUT_SESSION_ID}" after the * _url,
Don't know if this is your case but glad to help
mode : "subscription",
customer : customerid,
success_url: 'https://example.com/success?session_id={CHECKOUT_SESSION_ID}',
cancel_url: 'https://example.com/cancel?session_id={CHECKOUT_SESSION_ID}',
also, I suggest watching this https://youtube.com/watch?v=8TNQL9x6Ntg
I am building my first app using rails and i am struggling to give an account to each user. Its a simple app where a user creates a post and saves it. this post can be seen on index page. Until now we are ok. when i sign out, i register a new user and i log in. when i go to the index page, i can see what other user has saved. I want to have a private index page for each user where no one can see what users are saving in their app. I am using Gem 'Device" to authenticate my app, but i don't know how to make private their account? any ideas?
You have implemented authentication. Now you need to build authorization.
You have to define what data can be accessed by user.
Take a look at:
CanCanCan
With devise authentication technique, after logging in the user you can write a clean piece of code to render the posts of the logged in user. You can use session data to do it. the current_user helper method returns the model class relating to the signed in user. With devise, it ensures after authentication that, it has the helper methods ready to be used. So, the error with no such user with that id can be avoided.
#web/controller/post_controller.rb
before_filter :authenticate_user!
def list
#posts = current_user.posts
end
And then, use #posts in the list view to render the posts of the logged in user only.
<% #posts.each do |post| do%>
<%= post.content %>
<% end %>
I am developing a Rails 3 app using the Devise gem for authentication. I'm also using the confirmable module of Devise to send emails to users when they sign up, asking them to confirm their email address.
I am allowing users to sign in even if they didn't confirm their email address (for a maximum of 20 days), however I want to display a message at the top of every page reminding them they didn't confirm their email address and that they can still login without doing so for X number of days.
Any ideas of how I should approach this? (i.e. any useful gems or tips)
Thanks very much !!!
Looking at the docs here: http://rubydoc.info/github/plataformatec/devise/master/Devise/Models/Confirmable
It seems there is a confirmed? method that you can call on the User object to see if they are confirmed or not.
So I would just put a check for the confirmation in your views/layouts/application.html.erb file:
<% if user_signed_in? && !current_user.confirmed? %>
<div>
Please confirm your account by clicking the link in the email we sent to <%= current_user.email %>
</div>
<% end %>
I made a test facebook app just to play around and I am using sessions to store the authentification. I am using omniauth. When I go to log in from http://fbbtest.heroku.com/ and then refresh the page the session is still saved and it says that I am logged in. When I try it from the canvas http://apps.facebook.com/herokutestapp/ it logs me in, redirects back and says that I am logged in but then when I manually refresh it then says that I am not logged in. Is there something special that I have to do with sessions in rails 3 so that it also works in the facebook canvas?
This what I currently have in my controllers and views
def index
end
def create
session['fb_auth'] = request.env['omniauth.auth']
session['fb_token'] = session['fb_auth']['credentials']['token']
session['fb_error'] = nil
redirect_to root_path
end
def destroy
clear_session
redirect_to root_path
end
def failure
clear_session
session['fb_error'] = 'In order to use this site you must allow us access to your Facebook data<br />'
redirect_to root_path
end
def clear_session
session['fb_auth'] = nil
session['fb_token'] = nil
session['fb_error'] = nil
end
Index View
<div class="container">
<h1>Heroku FB Test application</h1><br />
<div class="center"><br />
<%=session[:fb_error]%>
<% if session[:fb_token] %>
<p>
Successfully logged in.
</p>
<a href='logout'>Logout</a>
<% else %>
<%= session[:fb_error] %><br />
<%= link_to "Log in with Facebook", "/auth/facebook",:class => "popup", :"data-width" => 600, :"data-height" => 400 %> <br />
<p>
please log in
</p>
<% end %>
</div>
</div>
The problem you're possibly running into is that Rails CSRF forgery detection is creaming some part of your authentication because the requests are coming in as HTTP Method POST.
The first line in your ApplicationController is probably something like:
class ApplicationController < ActionController::Base
protect_from_forgery
[...]
Remove that 'protect_from_forgery' line and see if that helps with your problem. If that turns out to be the case, go back and set that up on a more limited basis (just the relevant controllers, see documentation here: http://api.rubyonrails.org/classes/ActionController/RequestForgeryProtection/ClassMethods.html )
There's an great example for getting Omniauth working at http://www.communityguides.eu/articles/16, and the full sample code is at https://github.com/markusproske/omniauth_pure. On that, they have the following:
class ServicesController < ApplicationController
before_filter :authenticate_user!, :except => [:create, :signin, :signup, :newaccount, :failure]
protect_from_forgery :except => :create
You need some variant of both those lines to make omniauth, facebook, and rails sessions play well together. If that doesn't work out for you, post your OmniAuth::Builder information from environment/production.rb (with details XXXed out) and any other related code in the controller you use for authentication, that will be helpful for debugging this.
It may be easier when developing rails apps using facebook to debug using http://tunnlr.com or another service (or just an ssh tunnel http://blog.kenweiner.com/2007/09/reverse-ssh-tunnel-for-facebook.html) that lets you run the debugger on your local machine, it is very helpful for figuring these sorts of problems out.
Sessions and cookies in Facebook iframes are very difficult to use, but not impossible. I've faced this a few times when trying to develop vote-once-per-day contests.
The solution is to use P3P headers. Honestly I'm not too sure how they work, but it reduces issues of cross-browser cookies in iframes - especially IE and Safari.
Add the following to the top of each page:
header('P3P:CP="IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT"');
This may not solve your issue exactly but hopefully it can help get you on the right path.
If I cookies.permanent.signed[:fb_auth] it allows me to return to the application in facebook without needing to re-log in. Is this the best way to get around sessions not working through iFrames?
This sounds like a third-party cookie issue. Are you certain that using cookies.permanent.signed works properly if you have only accessed the site through Facebook? Try clearing the cookies, restarting the browser, and then going to the Facebook canvas page and testing again.
In Firefox, try going to Tools->Options->Privacy and seeing if "Accept third-party cookies" is unchecked. If it is, try checking it and testing again.
It's not at all surprising that third-party cookies may be causing you problems, the confusing part is why using a persistent cookie should make any difference.
If you do verify that third-party cookies are the problem, I'm afraid there is no easy solution if you want the app to be accessible by everyone. You have to stop using cookies altogether, and maintain session state using only values passed by GET/POST.