You maybe heard about the migration of the Stripe CB payment to SCA Strong Customer Authentication
All payment from the European countries are concerned.
For more information, The stripe sca doc
The customer experience through the SCA
For your customer, after he puts the Credit card number, the bank can ask for a confirmation by SMS
or a push notification in bank app in his smartphone.
Stripe now handle this and for better experience, they need to redirect the user to hosted page by stripe and will handle all the complexity.
When succeed or failed, the user is redirected to your webapp.
This is the flow that explain how this works
The main steps handled by the rails app are two
- The preparation of the checkout session [generation of the key]
- The validation of the payment [Verify the key]
1. The session preparation
This will prepare Stripe session
for the checkout of the product and the redirection when the payment succeed or not.
So we will prepare
- The name of the product
- The price
- The redirection when succeed or not
secret = gen_secret(@product)
Stripe::Checkout::Session.create(
{
payment_method_types: ['card'],
line_items: [{
name: @product.name,
description: @product.description,
amount: @product.price,
currency: 'eur',
quantity: 1,
}],
success_url: confirm_product_payment_url(@product, secret: secret),
cancel_url: cancel_product_payment_url(@product)
})
The interesting part here is the gen_secret
We need to generate this secret
to be able to be sure and verify that the payment at Stripe
succeed and the callback is not faked
We can generate this key like that in rails.
Thanks to rails to provide everything inside the framework and no need to add foreign dependency.
def gen_secret(product)
key = Rails.application.secrets.secret_key_base
@crypt = ActiveSupport::MessageEncryptor.new(key[0,32], cipher: 'aes-256-gcm')
@crypt.encrypt_and_sign(product.token)
end
As I explained in the previous UML flow
, the callbacks containing this secret will be stored at stripe
using a secure connection from your server.
The Payment process
The payment process is classic, the customer will enter his credit card numbers and may use a strong authentication if needed (SMS code verification, push notification ... ect)
When the payment succeed
At this step, the stripe servers will send to the browser the redirection to the right callback url.
This callback is containing our secret
generated before.
Time to validation !
2. The Validation process
Now, our rails app should verify this callback, in ruby it's easy
For this example I will use a simple verification.
I will only verify if decrypted token is the real token of the product.
# in the product controller
def confirm
@product = current_product
if PaymentService.new(product: @product)
.confirm(secret: params[:secret])
flash[:success] = "Payment succeed"
else
flash[:error] = "Oups Error !"
end
redirect_to product_url(@product)
end
# inside the PaymentService
...
def confirm(secret)
if @crypt.decrypt_and_verify(secret) == @product.token
# handle the success of the payment
# notifications, emails ...ect
return true
else
return false
end
end
The Front-end
Thanks to Stripe
the front-end is easy !
So, when the customer want to pay a product, he will click on the payment button
.
This will call our rails app
to prepare the session, get back the redirection and follow it.
<%= button_to product_pay_path(@product),
method: :post,
id: "pay_sca",
data: { disable_with: '<i class="fa fa-spinner fa-spin"></i>'.html_safe },
class: "btn btn-success btn-lg btn-block",
remote: true do %>
<i style="float:left;color:white" id="notes-exists" class="fas fa-lock"></i>
Payer
<% end %>
man that's concise !, this will call our controller by ajax, show a cool animation when waiting for the response of the server and finaly follow the redirection to Stripe
.
# controller
def create
@company_travel = current_travel.company
@session_id = PaymentService.new(product: current_product)
.create_session.id
end
As you have maybe notice, we are using a CRUD resource, when a customer need to pay we create
a charging resource
.
The view part is simple also
# views/.../create.js.erb
const stripe = Stripe('<%= Rails.application.secrets.stripe_public_key %>');
stripe.redirectToCheckout({
// We put here our session ID
sessionId: '<%= @session_id %>'
}).then((result) => {
// If `redirectToCheckout` fails due to a browser or network
// error, display the localized error message to your customer
// using `result.error.message`.
});
Easy yay !
I think now we are good to go.
Testing this will be in an other blog post with system tests
.
As a gift, The complete service can look like this
class PaymentService
include Rails.application.routes.url_helpers
def initialize(product:)
@product = product
key = Rails.application.secrets.secret_key_base
@cryptor = ActiveSupport::MessageEncryptor.new(key[0,32], cipher: 'aes-256-gcm')
end
def create_session
secret = gen_secret
Stripe::Checkout::Session.create(
{
payment_method_types: ['card'],
line_items: [{
name: @product.name
description: @product.description,
amount: @product.price,
currency: 'eur',
quantity: 1,
}],
success_url: confirm_product_payment_url(@product, secret: secret),
cancel_url: cancel_product_payment_url(@product )
})
end
def confirm(secret)
if @cryptor.decrypt_and_verify(secret) == @product.token
# handle a succeed payment
# send notifications, invoices ... ect
return true
else
return false
end
end
private
def gen_secret
@cryptor.encrypt_and_sign(@product.token)
end
end
Top comments (0)