prologue
A little while ago I was working on the payment system integration to the project for one of our clients.
Nowadays it's not a big deal to embed any service into the project, because almost every service has its own libraries which wrap powerful API and provide the needed data for your amazing future startup. Even if there is no official library, there are a lot of enthusiasts who contribute to open source and save us a ton of time.
During that integration I faced a tricky moment, which made me think of better and more convenient solution. I will try to describe it to you below.
When I prepared a "Checkout" request with data to the payment service I expected the successful Validation & Approve of the operation (or specific error about balance, expirations etc.). Everything worked fine until one day.
Sadly, but that day certain customers reported a strange behaviour: they were redirected to their bank's page with login/password form. For the customers it was a bell of some sort of fraud.
The hours of investigations on the internet brought me back to the source code of the library I had used. Inside all the folders and files I found a small module with a short comment:
# additional case 3dsecure, rare payment flow #TODO"
.
You can imagine my surprise when it turned out that there could be a case when 3D Secure is required on some Banks side during the checkout operation.
Why was I surprised? Because at that time I didn't find a word about that behaviour in the official docs of the service. I requested available papers related to that flow from the support team and started exploring the pages.
Anyway, after my research I started implementing the "additional case" to handle 3D-Secure flow. It was pretty simple to do that, but one of the steps was kind of inconvenient for me.
Firstly, what does 3DSecure mean?
After you submit the payment details to the service you are redirected to "your bank" page, where you should enter your login and password. It is an extra security step created to prevent money being lost if your credit card details have been leaked to 3rd people.
In case your login and password are correct, the bank redirects you back to the application with data params needed to confirm that the bank approved your credentials.
To finalize the payment, data params from the bank should be sent to the service for validation. The service has to check that the bank approval is real. But here is the tricky part:
- You must be redirected to that validation page
- and it must be POST request
I was a little bit confused, because 'redirect' with params could be easily achieved with GET request, but the requirements were pretty strict. I couldn't use any HTTP client, because I didn't find if it was possible to redirect controller to the address through them.
Another hour on the internet reminded me about "[form]" tag. I felt really stupid that I couldn't come up with that quicker by myself. And actually "[form]" was the solution to the pressing problems:
- It could send POST or GET request
- When you submit the form, browser follows to the url provided in action field.
<form action="http://site.io" method="post">
<p><input type="text" name="str"></p>
<p><input type="submit" value="Submit"></p>
</form>
Works like a charm. But there are also a few cons I don't like:
- Need to create a separate 'view'.
- You have to set the params you want to send with each request in that view.
- You have to submit the form by pressing the button in that view (could be done automatically with extra js code)
main
I decided to implement the code which could do all that stuff under the hood without bothering me to create extra 'views' each time I want to send a request.
A simple task - plain implementation. As far as the code was isolated from the business domain, I extracted it to the separate gem.
I came up with the name "repost", short form of 'redirect post'.
The gem is very easy in use. If you work with Rails, Repost gem will extend your controllers with method :repost
(and alias :redirect_post
).
For plain ruby (or e.g. Sinatra) you should call direct method Repost::Senpai.perform
. Full examples are in readme.md on GitHub.
For basic request you just need to pass url and params.
Rails:
repost('http://site.io', params: { a: 'b' })
# or
# redirect_post('http://site.io', params: { a: 'b' })
Plain ruby:
Repost::Senpai.perform('http://site.io', params: { a: 'b' })
To start using the gem you just need to add it to your Gemfile,
...
gem 'repost'
...
and run bundle install
.
Some time after the gem was released I received a bunch of questions about the authenticity token and how to set it for a request?
I found out that the gem had been used not only for 3rd-party services I had planned it for, but also to communicate inside the application. As you know, when you build a form with form_for
in Rails, it's adding a csrf-token
automatically to prevent cross-site request forgery.
But if you build a form on your own, you should include it manually. Otherwise you will see the next message in your logs:
Can't verify CSRF token authenticity
To cover that case I have implemented the options, which could be passed to the request before send:
repost('http://site.io', options: {authenticity_token: :auto})
It automatically grabs the needed token from your controller and adds it to the request.
The alternative is to pass form_authenticity_token
directly to the params. But the previous option should protect you from any implementation changes in future Rails versions.
fin
There are also other useful options like js autosubmit, form decoration, charset, etc. You can find the full list of options on Github in Full Example Section.
Github link - https://github.com/vergilet/repost
Rubygems link - https://rubygems.org/gems/repost
The gem "Repost" is available as open source. Bug reports and pull requests are welcome on GitHub.
Top comments (0)