New Stripe SCA checkout flow in Rails - ruby-on-rails

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

Related

Stripe connect doesn't giver error but doesn't create accounts

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

Twilio can't find xml on rails

I am integrating twilio click to call into my rails project.
Everything works fine however the url: in my twilio controller cannot be found on heroku. However, it can be found if you navigate to it in a browser. The phone dials but the voice says "Sorry a problem has occurred, good bye." If I change the url to an external xml file it works fine, just doesn't recognize this particular one. So I'm lead to believe that the controller etc works fine.
twillio_controller.rb
def call
#full_phone = current_user.phone
#partial_phone = #full_phone.last(-1)
#connected_number = "+61" + #partial_phone
#client = Twilio::REST::Client.new ##twilio_sid, ##twilio_token
# Connect an outbound call to the number submitted
#call = #client.calls.create(
:from => ##twilio_number,
:to => #connected_number,
:url => 'http://besttradies.herokuapp.com/mytradies/connect.xml', # Fetch instructions from this URL when the call connects
)
#msg = { :message => 'Phone call incoming!', :status => 'ok' }
end
def connect
# Our response to this request will be an XML document in the "TwiML"
# format. Our Ruby library provides a helper for generating one
# of these documents
response = Twilio::TwiML::Response.new do |r|
r.Say 'If this were a real click to call implementation, you would be connected to an agent at this point.', :voice => 'alice'
end
render text: response.text
end
The OP solved in the comments above:
Figured it out. Routes for connect needed to be POST and I also had to
add skip_before_action :verify_authenticity_token to the twilio
controller as it was behind membership doors.

Auto-update text field depending on selected option

Rails ver: 4.0.0
Hi all,
In my form I have a drop-down selection of client names. I would like to have a text box containing the client's address be updated whenever the selection is changed (before clicking the submit button to finalize the update). The reason is I am working with legacy data from another project where there can be several client entries with the same name but different addresses (e.g. large corp with several offices).
Obviously (perhaps?) I can't use any of the client-side script solutions since the new address has to be retrieved from the model - unless there is a way for java/coffee script to do a db lookup that I don't know about (which is highly likely since I only have a superficial acquaintance with them).
Thanks for any hints or pointers.
You can do something like this assuming you have #client:
Add a hidden div to your view to hold a data attribute:
<div id="client_id" style="display:none;" data-client="<%= #client.id %>"></div>
In somefile.js.coffee:
App ||= {}
App.client =
address = $("#id_of_your_select_field")
client = $("#client_id).data('client')
toggler.on 'change', ( e ) ->
App.client.getAddress
getAddress: ->
$ajax(
type: 'post'
url: "/get_address"
client_id: client
success: ( data, status, xhr ) ->
# you could parse JSON here if you want, or keep reading and leave this blank
)
in routes.rb
post '/get_address/:client_id', to: 'clients#return_address'
in clients_controller.rb
def return_address
#client = Client.find(params[:client_id])
respond_to do |format|
format.js
end
end
and in views/clients/return_address.js do:
var clientAddress = $("#the_civ_with_the_address");
clientAddress.html( "<%= j render( :partial => 'clients/address', :locals => { :client => #client } ) %>" );
Then make a partial with the client info at app/views/clients/_address.html.erb

Rails - Make ajax request from javascript event and load partial

what's up?
I'm using rails 3.2.3 with ruby 1.9.2p290.
I have a model Purchase which has many purchase_payments:
class Purchase < ActiveRecord::Base
has_many :purchase_payments, :class_name => 'PurchasePayment', :dependent => :destroy
accepts_nested_attributes_for :purchase_payments
def Purchase.build_payments(count)
#Creat #count payments with some logic for each of them
end
end
In my form, I have number_field:
<td>
<%= f.number_field :payments_count, :min => 0 %>
</td>
Now, I'd like to have the purchase_payment fields appear based on the number of payments. For that, I have a specific div:
<div id="purchase_payments_space"> </div>
Every resource I've found so far, talks about ajax generated by helper tags, like link_to ... , :remote => true, but using this tags, the user would have to click on the link to have the remote function called. In other part of my app, I use jquery ajax request and I'm trying to use like this for sending the request to my controller:
function load_payments() {
$.ajax({
type: "POST",
url: "/purchases/build_payments/" + document.getElementById("purchase_payments_count").value,
});
}
$(document).ready(function() {
$("#purchase_payments_count").change(load_payments);
}
In my controller, I have the respond_to :js right after my class declaration and the following method:
def build_payments
pc = params[:payments_count].to_i
#purchase = Purchase.new
#purchase_payments = Purchase.build_payments pc
respond_to do |format|
format.js
end
end
This way I have my app/views/purchases/buid_payments.js.erb run ok.
The thing is, I'm building a collection of payments that should be included in my form, and doing things the way I'm currently doing doesn't look like rails would do this. This way I will have to write much code in the js.erb file and still I can't figure out how I'll make the create method build the complete purchase from the params (I don't know how to build the form from the javascript).
I looked at several different ways but none have given an integrated solution for making an ajax request from a javascript event and have the view create the nested fields for my nested attributes (its a collection of purchase_payment I must remember).
I saw http://railscasts.com/episodes/196-nested-model-form-part-1 and http://railscasts.com/episodes/197-nested-model-form-part-2 but those build on top of helper methods like link_to and link_to_function.
Can anyone point me how can I accomplish that?
$.ajax({
url: "purchases/build_payments",
type: 'POST',
data: $('#purchase_payments_count').val(),
dataType: 'html',
headers: {
'X-CSRF-Token': '<%= form_authenticity_token.to_s %>'
}
});
then
def build_payments
pc = params[:payments_count].to_i
#purchase = Purchase.new
#purchase_payments = Purchase.build_payments pc
respond_to do |f|
f.js {}
end
end
which will run buid_payments.js.erb which can populate\recreate your table
If can't see it I can't help you.

Check username availability using jquery and Ajax in rails

I am using rails with jquery and ajax to check the availability of username. I am using
the following plugin for jquery validation purpose.
https://github.com/posabsolute/jQuery-Validation-Engine
In my controller i am using the following method to check the username availability.
def check_name
name = params[:name]
if name.strip == ""
render :json => { :available => false }
return
end
user = User.find(:first, :conditions => [ "name = ?", name])
if user
render :json => ["name", false , "This name is already taken"]
else
render :json => ["name", true , ""]
end
end
Is this the correct way to write the method? I checked many of the username availability
posts in this forum, but nothing worked out.
I am adding the answer. Sorry for the delay guys.
First credit to the plugin:https://github.com/posabsolute/jQuery-Validation-Engine .
Used the plugin for validations in the application.
In the view, i had
<%= f.username_field :username, :id => 'free-user', :placeholder=>'User Name', :class => "validate[required, ajax[ajaxUserCall]]", "data-prompt-position" => "topLeft:0,9"%>
In the same view, in java script:
<script type="text/javascript">
$(document).ready(function(){
$("#free-user").bind("jqv.field.result", function(event, field, errorFound, prompText){
if(errorFound){
$(".continue").attr("disabled", false); // .continue is a button
} else{
$(".continue").attr("disabled", true);
}
})
});
</script>
In routes.rb i have the following route.
match '/check-user' =>"users#check_user" // creating route for ajax call
In jquery.validationEngine-en.js file i have following:
"ajaxUserCall": {
"url": "/check-user",
// you may want to pass extra data on the ajax call
"alertText": "* This user is already taken",
"alertTextLoad": "* Validating, please wait"
},
In users controller, i have the following method
def check_user
user = params[:fieldValue]
user = User.where("username = ?", username).first
if user.present?
render :json => ["free-user", false , "This User is already taken"]
else
render :json => ["free-user", true , ""]
end
end
To check the Username/Email Availability do the following:
Using the https://github.com/posabsolute/jQuery-Validation-Engine
edit the validationsEngines-en.js file for the AJAX calls, one for the email will look like the following:
"ajaxEmailAvailable": {
"url": "/route_to_send_the_parameters",
// you may want to pass extra data on the ajax call
"alertTextOk": "* This email is available",
"alertText": "* This email is already taken",
"alertTextLoad": "* Validating, please wait"
},
Make sure you configure your routes.rb file to match the route you want to use, the default action with the call is a GET HTTP Request.
Next set up the proper action in your controller to handle the request (I included a helper in the Application Controller so that the input value can be sanitized before queried in the database:
CONTROLLER HANDLING THE REQUEST
def username_availability
scrubb = help.sanitize(params[:fieldValue], :tags => '')
user = User.find_by_email(scrubb)
if user.blank?
render :json => ["INPUT_ID", true , ""]
else
render :json => ["INPUT_ID", false , "This email is already taken"]
end
end
If you are unsure of the proper INPUT ID, watch the server logs for the parameters passed during the call and do a simple copy-paste. By default the request passes the INPUT ID and INPUT VALUE.
To gain access to this helper add the following:
APPLICATION CONTROLLER
def help
Helper.instance
end
class Helper
include Singleton
include ActionView::Helpers::TextHelper
end
Now on the form itself, your input should read as the following:
<%= c.text_field :email, "data-validation-engine"=>"validate[required, custom[email], ajax[ajaxEmailAvailable]]" %>
As per the proper functionality always place the AJAX call as the last validation.
Don't forget to include jquery.js, jquery.validationEngine-en.js, jquery.validationEngine.js and validationEngine.jquery.css in the head of the document [in that order of js] and to call a
<script type="text/javascript">$(function() {$("#FORM_ID").validationEngine();});</script>
If you want to do this for username, just edit the above appropriately.

Resources