Reputation: 51
I'm trying to integrate the new Stripe SCA checkout in my ruby on rails application. I followed the instructions bnwpro wrote here to get started. At this point I get redirected to the Stripe payment page and back to my application after payment is complete.
The issue i'm struggling with is the webhooks, when looking at the Stripe CLI i keep getting an 401...
2020-03-15 17:22:47 --> charge.succeeded [evt_1GMzEgI1EtCroaCdHmRufFBK]
2020-03-15 17:22:47 --> payment_intent.succeeded [evt_1GMzEgI1EtCroaCdWzbnaczh]
2020-03-15 17:22:47 <-- [401] POST http://localhost:3000/stripe-webhooks [evt_1GMzEgI1EtCroaCdHmRufFBK]
2020-03-15 17:22:47 <-- [401] POST http://localhost:3000/stripe-webhooks [evt_1GMzEgI1EtCroaCdWzbnaczh]
2020-03-15 17:22:47 --> payment_method.attached [evt_1GMzEgI1EtCroaCd4quSj3kR]
2020-03-15 17:22:47 <-- [401] POST http://localhost:3000/stripe-webhooks [evt_1GMzEgI1EtCroaCd4quSj3kR]
2020-03-15 17:22:47 --> customer.created [evt_1GMzEgI1EtCroaCdHpEmU1Ai]
2020-03-15 17:22:47 <-- [401] POST http://localhost:3000/stripe-webhooks [evt_1GMzEgI1EtCroaCdHpEmU1Ai]
2020-03-15 17:22:47 --> checkout.session.completed [evt_1GMzEhI1EtCroaCdf1x9gUUR]
2020-03-15 17:22:47 <-- [401] POST http://localhost:3000/stripe-webhooks [evt_1GMzEhI1EtCroaCdf1x9gUUR]
My log shows me:
Started POST "/stripe-webhooks" for 127.0.0.1 at 2020-03-15 17:22:24 +0100
Processing by CheckoutsController#stripe_webhook as XML
Parameters: {"id"=>"evt_1GMzEJI1EtCroaCdrKXWf4gV", "object"=>"event", "api_version"=>"2018-02-28", "created"=>1584289343, "data"=>{"object"=>{"id"=>"pi_1GMzEJI1EtCroaCdY3wKhKNG", "object"=>"payment_intent", "allowed_source_types"=>["card"], "amount"=>15000, "amount_capturable"=>0, "amount_received"=>0, "application"=>nil, "application_fee_amount"=>nil, "canceled_at"=>nil, "cancellation_reason"=>nil, "capture_method"=>"automatic", "charges"=>{"object"=>"list", "data"=>[], "has_more"=>false, "total_count"=>0, "url"=>"/v1/charges?payment_intent=pi_1GMzEJI1EtCroaCdY3wKhKNG"}, "client_secret"=>"pi_1GMzEJI1EtCroaCdY3wKhKNG_secret_6djH08QObem66sqpCYdB8P9A4", "confirmation_method"=>"automatic", "created"=>1584289343, "currency"=>"usd", "customer"=>nil, "description"=>nil, "invoice"=>nil, "last_payment_error"=>nil, "livemode"=>false, "metadata"=>{}, "next_action"=>nil, "next_source_action"=>nil, "on_behalf_of"=>nil, "payment_method"=>nil, "payment_method_options"=>{"card"=>{"installments"=>nil, "request_three_d_secure"=>"automatic"}}, "payment_method_types"=>["card"], "receipt_email"=>nil, "review"=>nil, "setup_future_usage"=>nil, "shipping"=>nil, "source"=>nil, "statement_descriptor"=>nil, "statement_descriptor_suffix"=>nil, "status"=>"requires_source", "transfer_data"=>nil, "transfer_group"=>nil}}, "livemode"=>false, "pending_webhooks"=>2, "request"=>{"id"=>"req_iRi1XVG06fMcld", "idempotency_key"=>nil}, "type"=>"payment_intent.created", "checkout"=>{"id"=>"evt_1GMzEJI1EtCroaCdrKXWf4gV", "object"=>"event", "api_version"=>"2018-02-28", "created"=>1584289343, "data"=>{"object"=>{"id"=>"pi_1GMzEJI1EtCroaCdY3wKhKNG", "object"=>"payment_intent", "allowed_source_types"=>["card"], "amount"=>15000, "amount_capturable"=>0, "amount_received"=>0, "application"=>nil, "application_fee_amount"=>nil, "canceled_at"=>nil, "cancellation_reason"=>nil, "capture_method"=>"automatic", "charges"=>{"object"=>"list", "data"=>[], "has_more"=>false, "total_count"=>0, "url"=>"/v1/charges?payment_intent=pi_1GMzEJI1EtCroaCdY3wKhKNG"}, "client_secret"=>"pi_1GMzEJI1EtCroaCdY3wKhKNG_secret_6djH08QObem66sqpCYdB8P9A4", "confirmation_method"=>"automatic", "created"=>1584289343, "currency"=>"usd", "customer"=>nil, "description"=>nil, "invoice"=>nil, "last_payment_error"=>nil, "livemode"=>false, "metadata"=>{}, "next_action"=>nil, "next_source_action"=>nil, "on_behalf_of"=>nil, "payment_method"=>nil, "payment_method_options"=>{"card"=>{"installments"=>nil, "request_three_d_secure"=>"automatic"}}, "payment_method_types"=>["card"], "receipt_email"=>nil, "review"=>nil, "setup_future_usage"=>nil, "shipping"=>nil, "source"=>nil, "statement_descriptor"=>nil, "statement_descriptor_suffix"=>nil, "status"=>"requires_source", "transfer_data"=>nil, "transfer_group"=>nil}}, "livemode"=>false, "pending_webhooks"=>2, "request"=>{"id"=>"req_iRi1XVG06fMcld", "idempotency_key"=>nil}, "type"=>"payment_intent.created"}}
Completed 401 Unauthorized in 1ms (ActiveRecord: 0.0ms)
Resulting in the webhoock not being triggerd and the code not being executed.
Who can point me in the right direction? Google and Stack searches have failed me this time...
Edit 1: app > controllers > checkouts_controller.rb
class CheckoutsController < ApplicationController
require 'stripe'
before_action :setplans
skip_before_action :checkStatus
protect_from_forgery except: :stripe_webhook
Stripe.api_key = 'sk_test_...'
endpoint_secret = "whsec_..."
def index
end
def new
Stripe.api_key = 'sk_test_...'
@product = Product.find(params[:id])
session = Stripe::Checkout::Session.create(
payment_method_types: ['card'],
line_items: [{
name: @product.title,
amount: @product.price,
currency: 'usd',
quantity: 1,
}],
"metadata": {days: "#{@product.days}"},
success_url: 'http://localhost:3000/success?session_id={CHECKOUT_SESSION_ID}',
cancel_url: 'http://localhost:3000/cancel',
)
@stripe_session = session
end
def success
### the Stripe {CHECKOUT_SESSION_ID} will be available in params[:session_id]
if params[:session_id]
flash[:success] = "Thank you! Your license has been updated!"
else
flash[:danger] = "Session expired error..."
redirect_to checkouts_path
end
end
def cancel
redirect_to checkouts_path
end
def stripe_webhook
stripe_response = StripeWebhooks.subscription_events(request)
end
private
def setplans
@licenseplans = Product.where(active: true)
end
end
The stripe_webhook lives under app > services > stripe_webhooks.rb
class StripeWebhooks
require 'stripe'
STRIPE_API_KEY = "sk_test_..."
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)
## custom actions
end
def handle_checkout_session_completed(event)
## custom actions
end
def authorize_webhook
Stripe.api_key = 'sk_test_...'
endpoint_secret = "whsec_..."
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
config > routes.rb
...
# Stripe SCA checkout routes
get 'success', to: 'checkouts#success'
get 'cancel', to: 'checkouts#cancel'
resources :checkouts
post '/stripe-webhooks', to: 'checkouts#stripe_webhook'
...
There are a number of hardcoded keys for the moment which need to be moved and stored in a safer place after I get this auth error fixed
Upvotes: 3
Views: 1152
Reputation: 51
I've moved the webhook from the service to the controller for the moment and added the skip_before_filter again BUT moved the endpoint_secret key to the webhook and get beautiful 200's now!
Now I just need to extract the metadata part and get working on the appropriate action after a successful payment...
The updated controller:
class CheckoutsController < ApplicationController
require 'stripe'
before_action :setplans
skip_before_action :checkStatus
protect_from_forgery except: :stripe_webhook
skip_before_action :authenticate_user!, only: [:stripe_webhook]
Stripe.api_key = 'sk_test_...'
def index
end
def new
Stripe.api_key = 'sk_test_...'
@product = Product.find(params[:id])
session = Stripe::Checkout::Session.create(
payment_method_types: ['card'],
line_items: [{
name: @product.title,
amount: @product.price,
currency: 'usd',
quantity: 1,
}],
metadata: {days: :"#{@product.days.to_s}"},
success_url: 'http://localhost:3000/success?session_id={CHECKOUT_SESSION_ID}',
cancel_url: 'http://localhost:3000/cancel',
)
@stripe_session = session
end
def success
### the Stripe {CHECKOUT_SESSION_ID} will be available in params[:session_id]
if params[:session_id]
flash[:success] = "Thank you! Your license has been updated!"
else
flash[:danger] = "Session expired error..."
redirect_to checkouts_path
end
end
def cancel
redirect_to checkouts_path
end
def stripe_webhook
sig_header = request.env['HTTP_STRIPE_SIGNATURE']
endpoint_secret = "whsec_..."
begin
event = Stripe::Webhook.construct_event(request.body.read, sig_header, endpoint_secret)
rescue JSON::ParserError
return head :bad_request
rescue Stripe::SignatureVerificationError
return head :bad_request
end
webhook_checkout_session_completed(event) if event['type'] == 'checkout.session.completed'
head :ok
end
private
def setplans
@licenseplans = Product.where(active: true)
end
def webhook_checkout_session_completed(event)
object = event['data']['object']
end
end
Upvotes: 2