Skip to content

Commit

Permalink
Transparent redirect
Browse files Browse the repository at this point in the history
  • Loading branch information
jferris committed Nov 9, 2011
1 parent e52a874 commit e69e9a7
Show file tree
Hide file tree
Showing 8 changed files with 164 additions and 25 deletions.
2 changes: 2 additions & 0 deletions README.md
Expand Up @@ -16,6 +16,8 @@ Currently in alpha (i.e. it does not support every Braintree call).
* `Braintree::CreditCard.find`
* `Braintree::CreditCard.sale`
* `Braintree::Transaction.sale`
* `Braintree::TransparentRedirect.url`
* `Braintree::TransparentRedirect.confirm` (only for creating customers0

## Quick start
Call `FakeBraintree.activate!` to make it go. `FakeBraintree.clear!` will clear
Expand Down
1 change: 1 addition & 0 deletions fake_braintree.gemspec
Expand Up @@ -21,6 +21,7 @@ Gem::Specification.new do |s|
s.add_dependency 'i18n'
s.add_dependency 'sinatra'
s.add_dependency 'braintree', '~> 2.5'
s.add_dependency 'thin'

s.add_development_dependency 'rspec', '~> 2.6.0'
s.add_development_dependency 'mocha', '~> 0.9.12'
Expand Down
6 changes: 4 additions & 2 deletions lib/fake_braintree.rb
Expand Up @@ -6,6 +6,7 @@
require 'fake_braintree/helpers'
require 'fake_braintree/customer'
require 'fake_braintree/subscription'
require 'fake_braintree/redirect'

require 'fake_braintree/sinatra_app'
require 'fake_braintree/valid_credit_cards'
Expand All @@ -17,10 +18,11 @@ class << self
@subscriptions = {}
@failures = {}
@transaction = {}
@redirects = {}

@decline_all_cards = false
@verify_all_cards = false
attr_accessor :customers, :subscriptions, :failures, :transaction, :decline_all_cards, :verify_all_cards
attr_accessor :customers, :subscriptions, :failures, :transaction, :decline_all_cards, :verify_all_cards, :redirects
end

def self.activate!
Expand All @@ -38,6 +40,7 @@ def self.clear!
self.subscriptions = {}
self.failures = {}
self.transaction = {}
self.redirects = {}
self.decline_all_cards = false
clear_log!
end
Expand Down Expand Up @@ -105,7 +108,6 @@ def self.set_configuration
end

def self.boot_server
Capybara.server_port = 3000
server = Capybara::Server.new(FakeBraintree::SinatraApp)
server.boot
ENV['GATEWAY_PORT'] = server.port.to_s
Expand Down
36 changes: 21 additions & 15 deletions lib/fake_braintree/customer.rb
Expand Up @@ -2,21 +2,27 @@ module FakeBraintree
class Customer
include Helpers

def initialize(request, merchant_id)
@request_hash = Hash.from_xml(request.body).delete("customer")
def initialize(customer_hash, merchant_id)
@customer_hash = customer_hash
@merchant_id = merchant_id
end

def invalid?
credit_card_is_failure? || invalid_credit_card?
end

def failure_response
gzipped_response(422, FakeBraintree.failure_response(@request_hash["credit_card"]["number"]).to_xml(:root => 'api_error_response'))
def create
if invalid?
failure_response
else
hash = customer_hash
FakeBraintree.customers[hash["id"]] = hash
gzipped_response(201, hash.to_xml(:root => 'customer'))
end
end

def customer_hash
hash = @request_hash.dup
hash = @customer_hash.dup
hash["id"] ||= create_id
hash["merchant-id"] = @merchant_id
if hash["credit_card"] && hash["credit_card"].is_a?(Hash)
Expand All @@ -38,30 +44,30 @@ def customer_hash

private

def create_id
md5("#{@merchant_id}#{Time.now.to_f}")
def failure_response
gzipped_response(422, FakeBraintree.failure_response(@customer_hash["credit_card"]["number"]).to_xml(:root => 'api_error_response'))
end

def credit_card_is_failure?
@request_hash.key?('credit_card') &&
FakeBraintree.failure?(@request_hash["credit_card"]["number"])
@customer_hash.key?('credit_card') &&
FakeBraintree.failure?(@customer_hash["credit_card"]["number"])
end

def invalid_credit_card?
verify_credit_card?(@request_hash) && has_invalid_credit_card?(@request_hash)
verify_credit_card?(@customer_hash) && has_invalid_credit_card?(@customer_hash)
end

def verify_credit_card?(customer_hash)
return true if FakeBraintree.verify_all_cards

@request_hash.key?("credit_card") &&
@request_hash["credit_card"].key?("options") &&
@request_hash["credit_card"]["options"].is_a?(Hash) &&
@request_hash["credit_card"]["options"]["verify_card"] == true
@customer_hash.key?("credit_card") &&
@customer_hash["credit_card"].key?("options") &&
@customer_hash["credit_card"]["options"].is_a?(Hash) &&
@customer_hash["credit_card"]["options"]["verify_card"] == true
end

def has_invalid_credit_card?(customer_hash)
! FakeBraintree::VALID_CREDIT_CARDS.include?(@request_hash["credit_card"]["number"])
! FakeBraintree::VALID_CREDIT_CARDS.include?(@customer_hash["credit_card"]["number"])
end
end
end
4 changes: 4 additions & 0 deletions lib/fake_braintree/helpers.rb
Expand Up @@ -15,5 +15,9 @@ def gzipped_response(status_code, uncompressed_content)
def md5(content)
Digest::MD5.hexdigest(content)
end

def create_id
md5("#{@merchant_id}#{Time.now.to_f}")
end
end
end
37 changes: 37 additions & 0 deletions lib/fake_braintree/redirect.rb
@@ -0,0 +1,37 @@
module FakeBraintree
class Redirect
include Helpers

attr_reader :id

def initialize(params, merchant_id)
hash, query = *params[:tr_data].split("|", 2)
@transparent_data = Rack::Utils.parse_query(query)
@merchant_id = merchant_id
@id = create_id
@params = params
end

def url
uri.to_s
end

def confirm
Customer.new(@params["customer"], @merchant_id).create
end

private

def uri
URI.parse(@transparent_data["redirect_url"]).merge("?#{base_query}&hash=#{hash(base_query)}")
end

def base_query
"http_status=200&id=#{@id}&kind=create_customer"
end

def hash(string)
Braintree::Digest.hexdigest(Braintree::Configuration.private_key, string)
end
end
end
27 changes: 19 additions & 8 deletions lib/fake_braintree/sinatra_app.rb
Expand Up @@ -11,14 +11,8 @@ class SinatraApp < Sinatra::Base

# Braintree::Customer.create
post "/merchants/:merchant_id/customers" do
customer = Customer.new(request, params[:merchant_id])
if customer.invalid?
customer.failure_response
else
customer_hash = customer.customer_hash
FakeBraintree.customers[customer_hash["id"]] = customer_hash
gzipped_response(201, customer_hash.to_xml(:root => 'customer'))
end
customer_hash = Hash.from_xml(request.body).delete("customer")
Customer.new(customer_hash, params[:merchant_id]).create
end

# Braintree::Customer.find
Expand Down Expand Up @@ -77,5 +71,22 @@ class SinatraApp < Sinatra::Base
gzipped_response(404, {})
end
end

# Braintree::TransparentRedirect.url
post "/merchants/:merchant_id/transparent_redirect_requests" do
if params[:tr_data]
redirect = Redirect.new(params, params[:merchant_id])
FakeBraintree.redirects[redirect.id] = redirect
redirect to(redirect.url), 303
else
[422, { "Content-Type" => "text/html" }, ["Invalid submission"]]
end
end

# Braintree::TransparentRedirect.confirm
post "/merchants/:merchant_id/transparent_redirect_requests/:id/confirm" do
redirect = FakeBraintree.redirects[params[:id]]
redirect.confirm
end
end
end
76 changes: 76 additions & 0 deletions spec/fake_braintree/transparent_redirect_spec.rb
@@ -0,0 +1,76 @@
require 'spec_helper'

describe FakeBraintree::SinatraApp do
context "Braintree::TransparentRedirect.url" do
it "returns a URL that will redirect with a token for the specified request" do
redirect_url = "http://example.com/redirect_path"

response = post_transparent_redirect(:redirect_url => redirect_url, :customer => build_customer_hash)

response.code.should == "303"
response["Location"].should =~ %r{http://example\.com/redirect_path}
params = parse_redirect(response)
params[:http_status].should == "200"
params[:id].should_not be_nil
params[:kind].should_not be_nil
end

it "rejects submissions without transparent redirect data" do
response = post_transparent_redirect_without_data
response.code.should == "422"
end
end

context "Braintree::TransparentRedirect.confirm" do
it "confirms and runs the specified request" do
number = "4111111111111111"
customer_hash = build_customer_hash(:credit_card => { :number => number })
response = post_transparent_redirect(:customer => customer_hash)
query = parse_query(response)

result = Braintree::TransparentRedirect.confirm(query)

result.should be_success

customer = Braintree::Customer.find(result.customer.id)
customer.credit_cards.first.last_4.should == "1111"
end
end

def build_customer_hash(options = {})
{
:credit_card => {
:number => "4111111111111111",
:expiration_date => "4/2015",
:cardholder_name => "Susie Spender"
}.update(options[:credit_card] || {})
}
end

def post_transparent_redirect(data)
params = data.dup
redirect_url = params.delete(:redirect_url) || "http://example.com/redirect_path"
params[:tr_data] = Braintree::TransparentRedirect.create_customer_data(:redirect_url => redirect_url)
post_transparent_redirect_params(params)
end

def post_transparent_redirect_without_data
post_transparent_redirect_params({})
end

def post_transparent_redirect_params(params)
uri = URI.parse(Braintree::TransparentRedirect.url)
Net::HTTP.start(uri.host, uri.port) do |http|
http.post(uri.path, Rack::Utils.build_nested_query(params))
end
end

def parse_redirect(response)
query = parse_query(response)
Braintree::Configuration.gateway.transparent_redirect.parse_and_validate_query_string(query)
end

def parse_query(response)
URI.parse(response["Location"]).query
end
end

0 comments on commit e69e9a7

Please sign in to comment.