Skip to content

Commit

Permalink
Braintree payment gateway
Browse files Browse the repository at this point in the history
[spree#1431 state:resolved]
  • Loading branch information
braintreeps authored and schof committed Jul 23, 2010
1 parent 4924e52 commit d0fcd7b
Show file tree
Hide file tree
Showing 5 changed files with 292 additions and 2 deletions.
2 changes: 1 addition & 1 deletion config/environment.rb
Expand Up @@ -29,7 +29,7 @@
config.gem "highline", :version => '1.5.1'
config.gem 'authlogic', :version => '2.1.3'
config.gem 'authlogic-oid', :lib => "authlogic_openid", :version => '1.0.4'
config.gem "activemerchant", :lib => "active_merchant", :version => '1.5.1'
config.gem "activemerchant", :lib => "active_merchant", :version => '1.7.0'
config.gem 'activerecord-tableless', :lib => 'tableless', :version => '0.1.0'
config.gem 'less', :version => '1.2.20'
config.gem 'stringex', :lib => 'stringex', :version => '1.0.3'
Expand Down
88 changes: 88 additions & 0 deletions vendor/extensions/payment_gateway/app/models/gateway/braintree.rb
@@ -0,0 +1,88 @@
class Gateway::Braintree < Gateway
preference :merchant_id, :string
preference :public_key, :string
preference :private_key, :string

def provider_class
ActiveMerchant::Billing::BraintreeGateway
end

def authorize(money, creditcard, options = {})
adjust_options_for_braintree(creditcard, options)
payment_method = creditcard.gateway_customer_profile_id || creditcard
provider.authorize(money, payment_method, options)
end

def capture(authorization, ignored_creditcard, ignored_options)
amount = (authorization.amount * 100).to_i
provider.capture(amount, authorization.response_code)
end

def create_profile(payment)
if payment.source.gateway_customer_profile_id.nil?
response = provider.store(payment.source)
if response.success?
payment.source.update_attributes!(:gateway_customer_profile_id => response.params["customer_vault_id"])
else
payment.source.gateway_error response.message
end
end
end

def credit(*args)
if args.size == 4
credit_with_payment_profiles(*args)
elsif args.size == 3
credit_without_payment_profiles(*args)
else
raise ArgumentError, "Expected 3 or 4 arguments, received #{args.size}"
end
end

def credit_with_payment_profiles(amount, payment, response_code, option)
provider.credit(amount, payment)
end

def credit_without_payment_profiles(amount, response_code, options)
transaction = ::Braintree::Transaction.find(response_code)
if BigDecimal.new(amount.to_s) == (transaction.amount * 100)
provider.refund(response_code)
else
raise NotImplementedError
end
end

def payment_profiles_supported?
true
end

def purchase(money, creditcard, options = {})
authorize(money, creditcard, options.merge(:submit_for_settlement => true))
end

def void(response_code, ignored_creditcard, ignored_options)
provider.void(response_code)
end

protected

def adjust_country_name(options)
[:billing_address, :shipping_address].each do |address|
if options[address] && options[address][:country] == "US"
options[address][:country] = "United States of America"
end
end
end

def adjust_billing_address(creditcard, options)
if creditcard.gateway_customer_profile_id
options.delete(:billing_address)
end
end

def adjust_options_for_braintree(creditcard, options)
adjust_country_name(options)
adjust_billing_address(creditcard, options)
end
end

Expand Up @@ -19,6 +19,7 @@ def activate
Gateway::Bogus,
Gateway::AuthorizeNet,
Gateway::AuthorizeNetCim,
Gateway::Braintree,
Gateway::Eway,
Gateway::Linkpoint,
Gateway::PayPal,
Expand Down
Expand Up @@ -3,6 +3,7 @@
class AuthorizeNetCimTest < Test::Unit::TestCase

def setup
Gateway.update_all(:active => false)
@cim_gateway = ActiveMerchant::Billing::AuthorizeNetCimGateway.new(
:login => 'x',
:password => 'y'
Expand All @@ -25,7 +26,7 @@ def setup
@checkout = Factory(:checkout, :bill_address => @address, :ship_address => @address)
@payment = Factory(:payment, :source => @creditcard, :payable => @checkout)
@checkout.payments << @payment
@gateway = Gateway::AuthorizeNetCim.create!(:name => 'Authorize.net CIM Gateway')
@gateway = Gateway::AuthorizeNetCim.create!(:name => 'Authorize.net CIM Gateway', :active => true)
@creditcard.reload

@address_options = {
Expand Down
200 changes: 200 additions & 0 deletions vendor/extensions/payment_gateway/test/unit/braintree_test.rb
@@ -0,0 +1,200 @@
require File.dirname(__FILE__) + '/../test_helper'
require "braintree"

class BraintreeTest < Test::Unit::TestCase

def setup
Gateway.update_all(:active => false)
@gateway = Gateway::Braintree.create!(:name => "Braintree Gateway", :environment => "test", :active => true)

# TODO: really need to figure out how preferences work in Spree
# doing configuration this way for now
::Braintree::Configuration.environment = :sandbox
::Braintree::Configuration.merchant_id = "zbn5yzq9t7wmwx42"
::Braintree::Configuration.public_key = "ym9djwqpkxbv3xzt"
::Braintree::Configuration.private_key = "4ghghkyp2yy6yqc8"
::Braintree::Configuration.class_eval do
def self.merchant_id=(value); end
def self.public_key=(value); end
def self.private_key=(value); end
end

with_payment_profiles_off do
@country = Factory(:country, :name => "United States", :iso_name => "UNITED STATES", :iso3 => "USA", :iso => "US", :numcode => 840)
@address = Factory(:address,
:firstname => 'John',
:lastname => 'Doe',
:address1 => '1234 My Street',
:address2 => 'Apt 1',
:city => 'Washington DC',
:zipcode => '20123',
:phone => '(555)555-5555',
:state_name => 'MD',
:country => @country
)
@checkout = Factory(:checkout, :bill_address => @address, :ship_address => @address)
@creditcard = Factory(:creditcard, :verification_value => '123', :number => '5105105105105100', :month => 9, :year => Time.now.year + 1, :first_name => 'John', :last_name => 'Doe')
@payment = Factory(:payment, :source => @creditcard, :payable => @checkout, :amount => @checkout.order.total)
end
end

context "provider_class" do
should "be braintree gateway" do
@gateway.provider_class.should == ::ActiveMerchant::Billing::BraintreeGateway
end
end

context "authorize" do
should "return a success response with an authorization code" do
result = @gateway.authorize(500, @creditcard)
assert_equal true, result.success?
assert_match /\A\w{6}\z/, result.authorization
assert_equal Braintree::Transaction::Status::Authorized, Braintree::Transaction.find(result.authorization).status
end

should "work through the spree payment interface" do
Spree::Config.set :auto_capture => false
assert_equal 0, @payment.txns.size
@payment.process!
assert_equal 1, @payment.txns.size
assert_match /\A\w{6}\z/, @payment.txns[0].response_code
transaction = ::Braintree::Transaction.find(@payment.txns[0].response_code)
assert_equal Braintree::Transaction::Status::Authorized, transaction.status
assert_equal "510510******5100", transaction.credit_card_details.masked_number
assert_equal "09/#{Time.now.year + 1}", transaction.credit_card_details.expiration_date
assert_equal "John", transaction.customer_details.first_name
assert_equal "Doe", transaction.customer_details.last_name
end
end

context "capture" do
should "capture a previous authorization" do
@payment.process!
assert_equal 1, @payment.txns.size
assert_match /\A\w{6}\z/, @payment.txns[0].response_code
transaction = ::Braintree::Transaction.find(@payment.txns[0].response_code)
assert_equal Braintree::Transaction::Status::Authorized, transaction.status
capture_result = @gateway.capture(@payment.txns.first, :ignored_arg_creditcard, :ignored_arg_options)
assert_equal true, capture_result.success?
transaction = ::Braintree::Transaction.find(@payment.txns[0].response_code)
assert_equal Braintree::Transaction::Status::SubmittedForSettlement, transaction.status
end

should "raise an error if capture fails using spree interface" do
Spree::Config.set :auto_capture => false
assert_equal 0, @payment.txns.size
@payment.process!
assert_equal 1, @payment.txns.size
transaction = ::Braintree::Transaction.find(@payment.txns[0].response_code)
assert_equal Braintree::Transaction::Status::Authorized, transaction.status
@payment.source.capture(@payment) # as done in PaymentsController#fire
transaction = ::Braintree::Transaction.find(@payment.txns[0].response_code)
assert_equal Braintree::Transaction::Status::SubmittedForSettlement, transaction.status
assert_raises(Spree::GatewayError, "Cannot submit for settlement unless status is authorized. (91507)") do
@payment.source.capture(@payment) # as done in PaymentsController#fire
end
end

should "work using the spree interface" do
Spree::Config.set :auto_capture => false
assert_equal 0, @payment.txns.size
@payment.process!
assert_equal 1, @payment.txns.size
transaction = ::Braintree::Transaction.find(@payment.txns[0].response_code)
assert_equal Braintree::Transaction::Status::Authorized, transaction.status
@payment.source.capture(@payment) # as done in PaymentsController#fire
transaction = ::Braintree::Transaction.find(@payment.txns[0].response_code)
assert_equal Braintree::Transaction::Status::SubmittedForSettlement, transaction.status
end
end

context "purchase" do
should "return a success response with an authorization code" do
result = @gateway.purchase(500, @creditcard)
assert_equal true, result.success?
assert_match /\A\w{6}\z/, result.authorization
assert_equal Braintree::Transaction::Status::SubmittedForSettlement, Braintree::Transaction.find(result.authorization).status
end

should "work through the spree payment interface with payment profiles" do
purchase_using_spree_interface
transaction = ::Braintree::Transaction.find(@payment.txns[0].response_code)
assert_match /\A\w{4}\z/, transaction.credit_card_details.token
end

should "work through the spree payment interface without payment profiles" do
with_payment_profiles_off do
purchase_using_spree_interface
transaction = ::Braintree::Transaction.find(@payment.txns[0].response_code)
assert_equal nil, transaction.credit_card_details.token
end
end
end

context "credit" do
should "work through the spree interface" do
purchase_using_spree_interface
credit_using_spree_interface
end
end

context "void" do
should "work through the spree creditcard / payment interface" do
assert_equal 0, @payment.txns.size
@payment.process!
assert_equal 1, @payment.txns.size
assert_match /\A\w{6}\z/, @payment.txns[0].response_code
transaction = Braintree::Transaction.find(@payment.txns[0].response_code)
assert_equal Braintree::Transaction::Status::SubmittedForSettlement, transaction.status
@creditcard.void(@payment)
transaction = Braintree::Transaction.find(transaction.id)
assert_equal Braintree::Transaction::Status::Voided, transaction.status
end
end

def credit_using_spree_interface
@payment.order.payments << @payment
assert_equal 1, @payment.txns.size
@payment.source.credit(@payment) # as done in PaymentsController#fire
assert_equal 2, @payment.txns.size
assert_match /\A\w{6}\z/, @payment.txns[1].response_code
transaction = ::Braintree::Transaction.find(@payment.txns[1].response_code)
assert_equal Braintree::Transaction::Type::Credit, transaction.type
assert_equal Braintree::Transaction::Status::SubmittedForSettlement, transaction.status
assert_equal "510510******5100", transaction.credit_card_details.masked_number
assert_equal "09/#{Time.now.year + 1}", transaction.credit_card_details.expiration_date
assert_equal "John", transaction.customer_details.first_name
assert_equal "Doe", transaction.customer_details.last_name
end

def purchase_using_spree_interface
Spree::Config.set :auto_capture => true
@payment.send(:create_payment_profile)
assert_equal 0, @payment.txns.size
@payment.process! # as done in PaymentsController#create
assert_equal 1, @payment.txns.size
assert_match /\A\w{6}\z/, @payment.txns[0].response_code
transaction = ::Braintree::Transaction.find(@payment.txns[0].response_code)
assert_equal Braintree::Transaction::Status::SubmittedForSettlement, transaction.status
assert_equal "510510******5100", transaction.credit_card_details.masked_number
assert_equal "09/#{Time.now.year + 1}", transaction.credit_card_details.expiration_date
assert_equal "John", transaction.customer_details.first_name
assert_equal "Doe", transaction.customer_details.last_name
end

def with_payment_profiles_off(&block)
Gateway::Braintree.class_eval do
def payment_profiles_supported?
false
end
end
yield
ensure
Gateway::Braintree.class_eval do
def payment_profiles_supported?
true
end
end
end
end

0 comments on commit d0fcd7b

Please sign in to comment.