Dummy backend for credit card form UI/UX exercise
source 'https://rubygems.org'
ruby '2.3.0'
gem 'sinatra'
# myapp.rb
require 'sinatra'
require 'json'
require_relative 'payment'
get '/' do
erb :index
end
post '/' do
request.body.rewind
payment = Payment.create(request.body)
[payment.status, payment.to_json]
end
__END__
@@ layout
<html>
<body>
<%= yield %>
@@ index
<h1>Payments API:</h1>
<h2>GET '/'</h2>
<p> this page </p>
<h2>POST '/'</h2>
<h3>request:</h3>
<b>body:</b>
<tt> { "card": "1234567812345678","cvc": 123, "month": 1, "year": 2099, "cents": 500 } </tt>
<h3>response</h3>
<ul>
<li>
<b>success:</b>
<p>status: <tt>200</tt> </p>
<p>body: <tt>{ "id": "", "card": "1234567812345678", "cvc": 123, "month": 1, "year": 2099, "cents": 500, "errors": [] }</tt></p>
</li>
<li><b>failure:</b>
<p>status: <tt>422</tt></p>
<p>body:
<tt>{"id":null,"card":"4242424242424242","cvc":"123","month":"1","year":"2016", "cents": 500,"errors":{"date":"card is expired"}} </tt>
</p>
</li>
</ul>
require 'json'
Payment = Struct.new(:card, :cvc, :month, :year, :cents) do
class << self
def create(datum)
new(datum)
end
end
attr_reader :data, :errors, :id
def initialize(json = '')
@data = JSON.load(json) || {}
%w(card cents cvc month year).each { |k| send("#{k}=", @data[k]) }
id
end
def id
@id ||= valid? ? Time.now.strftime("%Y%m%d%k%M%S%L") : nil
end
def status
valid? ? 200 : 422
end
def to_json
JSON.dump(id: id,
card: card,
cvc: cvc,
month: month,
year: year,
cents: cents,
errors: errors)
end
private
ERRORS = {
data: 'wrong data format',
cvc: 'must be a 3 digit number',
card: 'card is invalid',
date: 'card is expired',
cents: 'must be a positive integer'
}.freeze
def valid?
validate
errors.empty?
end
def validate
error?(:data) { |d| d.is_a?(Hash) }
error?(:data) { |d| d.keys.sort == %w(card cents cvc month year) }
error?(:card) { |d| d =~ /\d{16}/ }
error?(:cvc) { |d| d =~ /\d{3}/ }
error?(:date) { |d| future?(d[:month].to_i, d[:year].to_i) }
error?(:cents) { |c| c.is_a?(Integer) && c > 0 }
end
def date
{ month: month, year: year }
end
def error?(key)
value = send(key)
errors.merge!(key =>ERRORS[key]) unless yield(value)
end
def errors
@errors ||= {}
end
def future?(month, year)
today = Date.today
(year > today.year || year == today.year && month > today.month)
end
end