Permalink
Browse files

Transparent redirect

  • Loading branch information...
1 parent e52a874 commit e69e9a7ed9582bb98dcde37202824b77fae703d4 @jferris jferris committed Nov 9, 2011
View
@@ -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
@@ -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'
@@ -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'
@@ -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!
@@ -38,6 +40,7 @@ def self.clear!
self.subscriptions = {}
self.failures = {}
self.transaction = {}
+ self.redirects = {}
self.decline_all_cards = false
clear_log!
end
@@ -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
@@ -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)
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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.