braintree 2.24.0 → 2.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,6 +10,28 @@ module Braintree
10
10
  _do_create "/transactions", :transaction => attributes
11
11
  end
12
12
 
13
+ def cancel_release(transaction_id)
14
+ raise ArgumentError, "transaction_id is invalid" unless transaction_id =~ /\A[0-9a-z]+\z/
15
+ response = @config.http.put "/transactions/#{transaction_id}/cancel_release"
16
+ _handle_transaction_response(response)
17
+ end
18
+
19
+ def hold_in_escrow(transaction_id)
20
+ raise ArgumentError, "transaction_id is invalid" unless transaction_id =~ /\A[0-9a-z]+\z/
21
+ response = @config.http.put "/transactions/#{transaction_id}/hold_in_escrow"
22
+ _handle_transaction_response(response)
23
+ end
24
+
25
+ def _handle_transaction_response(response)
26
+ if response[:transaction]
27
+ SuccessfulResult.new(:transaction => Transaction._new(@gateway, response[:transaction]))
28
+ elsif response[:api_error_response]
29
+ ErrorResult.new(@gateway, response[:api_error_response])
30
+ else
31
+ raise UnexpectedError, "expected :transaction or :response"
32
+ end
33
+ end
34
+
13
35
  def clone_transaction(transaction_id, attributes)
14
36
  Util.verify_keys(TransactionGateway._clone_signature, attributes)
15
37
  _do_create "/transactions/#{transaction_id}/clone", :transaction_clone => attributes
@@ -40,13 +62,7 @@ module Braintree
40
62
 
41
63
  def refund(transaction_id, amount = nil)
42
64
  response = @config.http.post "/transactions/#{transaction_id}/refund", :transaction => {:amount => amount}
43
- if response[:transaction]
44
- SuccessfulResult.new(:transaction => Transaction._new(@gateway, response[:transaction]))
45
- elsif response[:api_error_response]
46
- ErrorResult.new(@gateway, response[:api_error_response])
47
- else
48
- raise UnexpectedError, "expected :transaction or :api_error_response"
49
- end
65
+ _handle_transaction_response(response)
50
66
  end
51
67
 
52
68
  def retry_subscription_charge(subscription_id, amount=nil)
@@ -70,27 +86,21 @@ module Braintree
70
86
  ResourceCollection.new(response) { |ids| _fetch_transactions(search, ids) }
71
87
  end
72
88
 
89
+ def release_from_escrow(transaction_id)
90
+ raise ArgumentError, "transaction_id is invalid" unless transaction_id =~ /\A[0-9a-z]+\z/
91
+ response = @config.http.put "/transactions/#{transaction_id}/release_from_escrow"
92
+ _handle_transaction_response(response)
93
+ end
94
+
73
95
  def submit_for_settlement(transaction_id, amount = nil)
74
96
  raise ArgumentError, "transaction_id is invalid" unless transaction_id =~ /\A[0-9a-z]+\z/
75
97
  response = @config.http.put "/transactions/#{transaction_id}/submit_for_settlement", :transaction => {:amount => amount}
76
- if response[:transaction]
77
- SuccessfulResult.new(:transaction => Transaction._new(@gateway, response[:transaction]))
78
- elsif response[:api_error_response]
79
- ErrorResult.new(@gateway, response[:api_error_response])
80
- else
81
- raise UnexpectedError, "expected :transaction or :response"
82
- end
98
+ _handle_transaction_response(response)
83
99
  end
84
100
 
85
101
  def void(transaction_id)
86
102
  response = @config.http.put "/transactions/#{transaction_id}/void"
87
- if response[:transaction]
88
- SuccessfulResult.new(:transaction => Transaction._new(@gateway, response[:transaction]))
89
- elsif response[:api_error_response]
90
- ErrorResult.new(@gateway, response[:api_error_response])
91
- else
92
- raise UnexpectedError, "expected :transaction or :api_error_response"
93
- end
103
+ _handle_transaction_response(response)
94
104
  end
95
105
 
96
106
  def self._clone_signature # :nodoc:
@@ -101,7 +111,7 @@ module Braintree
101
111
  [
102
112
  :amount, :customer_id, :merchant_account_id, :order_id, :channel, :payment_method_token,
103
113
  :purchase_order_number, :recurring, :shipping_address_id, :type, :tax_amount, :tax_exempt,
104
- :venmo_sdk_payment_method_code, :device_session_id, :device_data,
114
+ :venmo_sdk_payment_method_code, :device_session_id, :service_fee_amount, :device_data,
105
115
  {:credit_card => [:token, :cardholder_name, :cvv, :expiration_date, :expiration_month, :expiration_year, :number]},
106
116
  {:customer => [:id, :company, :email, :fax, :first_name, :last_name, :phone, :website]},
107
117
  {
@@ -110,7 +120,7 @@ module Braintree
110
120
  {
111
121
  :shipping => AddressGateway._shared_signature
112
122
  },
113
- {:options => [:store_in_vault, :store_in_vault_on_success, :submit_for_settlement, :add_billing_address_to_payment_method, :store_shipping_address_in_vault, :venmo_sdk_session]},
123
+ {:options => [:hold_in_escrow, :store_in_vault, :store_in_vault_on_success, :submit_for_settlement, :add_billing_address_to_payment_method, :store_shipping_address_in_vault, :venmo_sdk_session]},
114
124
  {:custom_fields => :_any_key_},
115
125
  {:descriptor => [:name, :phone]}
116
126
  ]
@@ -118,13 +128,7 @@ module Braintree
118
128
 
119
129
  def _do_create(url, params=nil) # :nodoc:
120
130
  response = @config.http.post url, params
121
- if response[:transaction]
122
- SuccessfulResult.new(:transaction => Transaction._new(@gateway, response[:transaction]))
123
- elsif response[:api_error_response]
124
- ErrorResult.new(@gateway, response[:api_error_response])
125
- else
126
- raise UnexpectedError, "expected :transaction or :api_error_response"
127
- end
131
+ _handle_transaction_response(response)
128
132
  end
129
133
 
130
134
  def _fetch_transactions(search, ids) # :nodoc:
@@ -1,7 +1,7 @@
1
1
  module Braintree
2
2
  module Version
3
3
  Major = 2
4
- Minor = 24
4
+ Minor = 25
5
5
  Tiny = 0
6
6
 
7
7
  String = "#{Major}.#{Minor}.#{Tiny}"
@@ -10,9 +10,14 @@ module Braintree
10
10
  SubscriptionTrialEnded = "subscription_trial_ended"
11
11
  SubscriptionWentActive = "subscription_went_active"
12
12
  SubscriptionWentPastDue = "subscription_went_past_due"
13
+
14
+ SubMerchantAccountApproved = "sub_merchant_account_approved"
15
+ SubMerchantAccountDeclined = "sub_merchant_account_declined"
16
+ TransactionDisbursed = "transaction_disbursed"
17
+ PartnerUserCreated = "partner_user_created"
13
18
  end
14
19
 
15
- attr_reader :subscription, :kind, :timestamp
20
+ attr_reader :subscription, :kind, :timestamp, :partner_credentials, :transaction
16
21
 
17
22
  def self.parse(signature, payload)
18
23
  Configuration.gateway.webhook_notification.parse(signature, payload)
@@ -25,7 +30,23 @@ module Braintree
25
30
  def initialize(gateway, attributes) # :nodoc:
26
31
  @gateway = gateway
27
32
  set_instance_variables_from_hash(attributes)
33
+ @error_result = ErrorResult.new(gateway, @subject[:api_error_response]) if @subject.has_key?(:api_error_response)
34
+ @merchant_account = MerchantAccount._new(gateway, @subject[:merchant_account]) if @subject.has_key?(:merchant_account)
35
+ @partner_credentials = OpenStruct.new(@subject[:partner_credentials]) if @subject.has_key?(:partner_credentials)
28
36
  @subscription = Subscription._new(gateway, @subject[:subscription]) if @subject.has_key?(:subscription)
37
+ @transaction = Transaction._new(gateway, @subject[:transaction]) if @subject.has_key?(:transaction)
38
+ end
39
+
40
+ def merchant_account
41
+ @error_result.nil? ? @merchant_account : @error_result.merchant_account
42
+ end
43
+
44
+ def errors
45
+ @error_result.errors if @error_result
46
+ end
47
+
48
+ def message
49
+ @error_result.message if @error_result
29
50
  end
30
51
 
31
52
  class << self
@@ -12,19 +12,35 @@ module Braintree
12
12
  return signature_string, payload
13
13
  end
14
14
 
15
- def _sample_xml(kind, id)
15
+ def _sample_xml(kind, data)
16
16
  <<-XML
17
17
  <notification>
18
18
  <timestamp type="datetime">#{Time.now.utc.iso8601}</timestamp>
19
19
  <kind>#{kind}</kind>
20
20
  <subject>
21
- #{_subscription_sample_xml(id)}
21
+ #{_subject_sample_xml(kind, data)}
22
22
  </subject>
23
23
  </notification>
24
24
  XML
25
25
  end
26
26
 
27
+ def _subject_sample_xml(kind, id)
28
+ case kind
29
+ when Braintree::WebhookNotification::Kind::PartnerUserCreated
30
+ _partner_credentials_sample_xml(id)
31
+ when Braintree::WebhookNotification::Kind::SubMerchantAccountApproved
32
+ _merchant_account_approved_sample_xml(id)
33
+ when Braintree::WebhookNotification::Kind::SubMerchantAccountDeclined
34
+ _merchant_account_declined_sample_xml(id)
35
+ when Braintree::WebhookNotification::Kind::TransactionDisbursed
36
+ _transaction_disbursed_sample_xml(id)
37
+ else
38
+ _subscription_sample_xml(id)
39
+ end
40
+ end
41
+
27
42
  def _subscription_sample_xml(id)
43
+
28
44
  <<-XML
29
45
  <subscription>
30
46
  <id>#{id}</id>
@@ -37,5 +53,73 @@ module Braintree
37
53
  </subscription>
38
54
  XML
39
55
  end
56
+
57
+ def _partner_credentials_sample_xml(data)
58
+
59
+ <<-XML
60
+ <partner_credentials>
61
+ <merchant_public_id>public_id</merchant_public_id>
62
+ <public_key>public_key</public_key>
63
+ <private_key>private_key</private_key>
64
+ <partner_user_id>abc123</partner_user_id>
65
+ </partner_credentials>
66
+ XML
67
+ end
68
+
69
+ def _merchant_account_approved_sample_xml(id)
70
+
71
+ <<-XML
72
+ <merchant_account>
73
+ <id>#{id}</id>
74
+ <master_merchant_account>
75
+ <id>master_ma_for_#{id}</id>
76
+ <status>active</status>
77
+ </master_merchant_account>
78
+ <status>active</status>
79
+ </merchant_account>
80
+ XML
81
+ end
82
+
83
+ def _merchant_account_declined_sample_xml(id)
84
+
85
+ <<-XML
86
+ <api-error-response>
87
+ <message>Credit score is too low</message>
88
+ <errors>
89
+ <errors type="array"/>
90
+ <merchant-account>
91
+ <errors type="array">
92
+ <error>
93
+ <code>82621</code>
94
+ <message>Credit score is too low</message>
95
+ <attribute type="symbol">base</attribute>
96
+ </error>
97
+ </errors>
98
+ </merchant-account>
99
+ </errors>
100
+ <merchant-account>
101
+ <id>#{id}</id>
102
+ <status>suspended</status>
103
+ <master-merchant-account>
104
+ <id>master_ma_for_#{id}</id>
105
+ <status>suspended</status>
106
+ </master-merchant-account>
107
+ </merchant-account>
108
+ </api-error-response>
109
+ XML
110
+ end
111
+
112
+ def _transaction_disbursed_sample_xml(id)
113
+
114
+ <<-XML
115
+ <transaction>
116
+ <id>#{id}</id>
117
+ <amount>100</amount>
118
+ <disbursement-details>
119
+ <disbursement-date type="datetime">2013-07-09T18:23:29Z</disbursement-date>
120
+ </disbursement-details>
121
+ </transaction>
122
+ XML
123
+ end
40
124
  end
41
125
  end
@@ -13,10 +13,10 @@ module Braintree
13
13
  "Time" => "datetime",
14
14
  }
15
15
 
16
- XML_FORMATTING_NAMES = {
17
- "BigDecimal" => "bigdecimal",
18
- "Symbol" => "symbol"
19
- }.merge(XML_TYPE_NAMES)
16
+ XML_FORMATTING_NAMES = {
17
+ "BigDecimal" => "bigdecimal",
18
+ "Symbol" => "symbol"
19
+ }.merge(XML_TYPE_NAMES)
20
20
 
21
21
  XML_FORMATTING = {
22
22
  "symbol" => Proc.new { |symbol| symbol.to_s },
@@ -1 +1 @@
1
- 28977
1
+ 4742
@@ -84,7 +84,7 @@ describe Braintree::Customer do
84
84
  found_customer = Braintree::Customer.find(result.customer.id)
85
85
  found_customer.first_name.should == first_name
86
86
  found_customer.last_name.should == last_name
87
- elsif RUBY_VERSION =~ /^1.9/
87
+ else
88
88
  result.customer.first_name.should == "José"
89
89
  result.customer.first_name.bytes.map {|b| b.to_s(8)}.should == ["112", "157", "163", "303", "251"]
90
90
  result.customer.last_name.should == "Muñoz"
@@ -95,8 +95,6 @@ describe Braintree::Customer do
95
95
  found_customer.first_name.bytes.map {|b| b.to_s(8)}.should == ["112", "157", "163", "303", "251"]
96
96
  found_customer.last_name.should == "Muñoz"
97
97
  found_customer.last_name.bytes.map {|b| b.to_s(8)}.should == ["115", "165", "303", "261", "157", "172"]
98
- else
99
- raise "unknown ruby version: #{RUBY_VERSION.inspect}"
100
98
  end
101
99
  end
102
100
 
@@ -363,6 +361,22 @@ describe Braintree::Customer do
363
361
  result.customer.credit_cards.first.bin.should == "400934"
364
362
  result.customer.credit_cards.first.last_4.should == "1881"
365
363
  end
364
+
365
+ it "can create a customer with a venmo sdk session" do
366
+ result = Braintree::Customer.create(
367
+ :first_name => "Steve",
368
+ :last_name => "Hamlin",
369
+ :credit_card => {
370
+ :number => Braintree::Test::CreditCardNumbers::MasterCard,
371
+ :expiration_date => "05/2010",
372
+ :options => {
373
+ :venmo_sdk_session => Braintree::Test::VenmoSDK::Session
374
+ }
375
+ }
376
+ )
377
+ result.success?.should == true
378
+ result.customer.credit_cards.first.venmo_sdk?.should == true
379
+ end
366
380
  end
367
381
  end
368
382
 
@@ -0,0 +1,72 @@
1
+ require File.expand_path(File.dirname(__FILE__) + "/../../spec_helper")
2
+
3
+ VALID_APPLICATION_PARAMS = {
4
+ :applicant_details => {
5
+ :first_name => "Joe",
6
+ :last_name => "Bloggs",
7
+ :email => "joe@bloggs.com",
8
+ :phone => "312-555-1234",
9
+ :address => {
10
+ :street_address => "123 Credibility St.",
11
+ :postal_code => "60606",
12
+ :locality => "Chicago",
13
+ :region => "IL",
14
+ },
15
+ :date_of_birth => "10/9/1980",
16
+ :ssn => "123-00-1234",
17
+ :routing_number => "1234567890",
18
+ :account_number => "43759348798"
19
+ },
20
+ :tos_accepted => true,
21
+ :master_merchant_account_id => "sandbox_master_merchant_account"
22
+ }
23
+
24
+ describe Braintree::MerchantAccount do
25
+ describe "create" do
26
+ it "doesn't require an id" do
27
+ result = Braintree::MerchantAccount.create(VALID_APPLICATION_PARAMS)
28
+
29
+ result.should be_success
30
+ result.merchant_account.status.should == Braintree::MerchantAccount::Status::Pending
31
+ result.merchant_account.master_merchant_account.id.should == "sandbox_master_merchant_account"
32
+ end
33
+
34
+ it "allows an id to be passed" do
35
+ random_number = rand(10000)
36
+ sub_merchant_account_id = "sub_merchant_account_id#{random_number}"
37
+ result = Braintree::MerchantAccount.create(
38
+ VALID_APPLICATION_PARAMS.merge(
39
+ :id => sub_merchant_account_id
40
+ )
41
+ )
42
+
43
+ result.should be_success
44
+ result.merchant_account.status.should == Braintree::MerchantAccount::Status::Pending
45
+ result.merchant_account.id.should == sub_merchant_account_id
46
+ result.merchant_account.master_merchant_account.id.should == "sandbox_master_merchant_account"
47
+ end
48
+
49
+ it "handles unsuccessful results" do
50
+ result = Braintree::MerchantAccount.create({})
51
+ result.should_not be_success
52
+ result.errors.for(:merchant_account).on(:master_merchant_account_id).first.code.should == Braintree::ErrorCodes::MerchantAccount::MasterMerchantAccountIdIsRequired
53
+ end
54
+
55
+ it "requires all fields" do
56
+ result = Braintree::MerchantAccount.create(
57
+ :master_merchant_account_id => "sandbox_master_merchant_account"
58
+ )
59
+ result.should_not be_success
60
+ result.errors.for(:merchant_account).for(:applicant_details).on(:first_name).first.code.should == Braintree::ErrorCodes::MerchantAccount::ApplicantDetails::FirstNameIsRequired
61
+ end
62
+
63
+ it "accepts tax_id and business_name fields" do
64
+ params = VALID_APPLICATION_PARAMS.clone
65
+ params[:applicant_details][:company_name] = "Test Company"
66
+ params[:applicant_details][:tax_id] = "123456789"
67
+ result = Braintree::MerchantAccount.create(params)
68
+ result.should be_success
69
+ result.merchant_account.status.should == Braintree::MerchantAccount::Status::Pending
70
+ end
71
+ end
72
+ end
@@ -887,6 +887,108 @@ describe Braintree::Transaction do
887
887
  end
888
888
  end
889
889
 
890
+ context "service fees" do
891
+ it "allows specifying service fees" do
892
+ result = Braintree::Transaction.create(
893
+ :type => "sale",
894
+ :amount => Braintree::Test::TransactionAmounts::Authorize,
895
+ :merchant_account_id => SpecHelper::NonDefaultSubMerchantAccountId,
896
+ :credit_card => {
897
+ :number => Braintree::Test::CreditCardNumbers::Visa,
898
+ :expiration_date => "12/12",
899
+ },
900
+ :service_fee_amount => "1.00"
901
+ )
902
+ result.success?.should == true
903
+ result.transaction.service_fee_amount.should == BigDecimal.new("1.00")
904
+ end
905
+
906
+ it "raises an error if transaction merchant account is a master" do
907
+ result = Braintree::Transaction.create(
908
+ :type => "sale",
909
+ :amount => Braintree::Test::TransactionAmounts::Authorize,
910
+ :merchant_account_id => SpecHelper::NonDefaultMerchantAccountId,
911
+ :credit_card => {
912
+ :number => Braintree::Test::CreditCardNumbers::Visa,
913
+ :expiration_date => "12/12",
914
+ },
915
+ :service_fee_amount => "1.00"
916
+ )
917
+ result.success?.should == false
918
+ expected_error_code = Braintree::ErrorCodes::Transaction::ServiceFeeAmountNotAllowedOnMasterMerchantAccount
919
+ result.errors.for(:transaction).on(:service_fee_amount)[0].code.should == expected_error_code
920
+ end
921
+
922
+ it "raises an error if no service fee is present on a sub merchant account transaction" do
923
+ result = Braintree::Transaction.create(
924
+ :type => "sale",
925
+ :amount => Braintree::Test::TransactionAmounts::Authorize,
926
+ :merchant_account_id => SpecHelper::NonDefaultSubMerchantAccountId,
927
+ :credit_card => {
928
+ :number => Braintree::Test::CreditCardNumbers::Visa,
929
+ :expiration_date => "12/12",
930
+ }
931
+ )
932
+ result.success?.should == false
933
+ expected_error_code = Braintree::ErrorCodes::Transaction::SubMerchantAccountRequiresServiceFeeAmount
934
+ result.errors.for(:transaction).on(:merchant_account_id)[0].code.should == expected_error_code
935
+ end
936
+
937
+ it "raises an error if service fee amount is negative" do
938
+ result = Braintree::Transaction.create(
939
+ :merchant_account_id => SpecHelper::NonDefaultSubMerchantAccountId,
940
+ :service_fee_amount => "-1.00"
941
+ )
942
+ result.success?.should == false
943
+ result.errors.for(:transaction).on(:service_fee_amount)[0].code.should == Braintree::ErrorCodes::Transaction::ServiceFeeAmountCannotBeNegative
944
+ end
945
+
946
+ it "raises an error if service fee amount is invalid" do
947
+ result = Braintree::Transaction.create(
948
+ :merchant_account_id => SpecHelper::NonDefaultSubMerchantAccountId,
949
+ :service_fee_amount => "invalid amount"
950
+ )
951
+ result.success?.should == false
952
+ result.errors.for(:transaction).on(:service_fee_amount)[0].code.should == Braintree::ErrorCodes::Transaction::ServiceFeeAmountFormatIsInvalid
953
+ end
954
+ end
955
+
956
+ context "escrow" do
957
+ it "allows specifying transactions to be held for escrow" do
958
+ result = Braintree::Transaction.create(
959
+ :type => "sale",
960
+ :amount => Braintree::Test::TransactionAmounts::Authorize,
961
+ :merchant_account_id => SpecHelper::NonDefaultSubMerchantAccountId,
962
+ :credit_card => {
963
+ :number => Braintree::Test::CreditCardNumbers::Visa,
964
+ :expiration_date => "12/12",
965
+ },
966
+ :service_fee_amount => "10.00",
967
+ :options => {:hold_in_escrow => true}
968
+ )
969
+
970
+ result.success?.should == true
971
+ result.transaction.escrow_status.should == Braintree::Transaction::EscrowStatus::HoldPending
972
+ end
973
+
974
+ it "raises an error if transaction merchant account is a master" do
975
+ result = Braintree::Transaction.create(
976
+ :type => "sale",
977
+ :amount => Braintree::Test::TransactionAmounts::Authorize,
978
+ :merchant_account_id => SpecHelper::NonDefaultMerchantAccountId,
979
+ :credit_card => {
980
+ :number => Braintree::Test::CreditCardNumbers::Visa,
981
+ :expiration_date => "12/12",
982
+ },
983
+ :service_fee_amount => "1.00",
984
+ :options => {:hold_in_escrow => true}
985
+ )
986
+ result.success?.should == false
987
+ expected_error_code = Braintree::ErrorCodes::Transaction::CannotHoldInEscrow
988
+ result.errors.for(:transaction).on(:base)[0].code.should == expected_error_code
989
+ end
990
+ end
991
+
890
992
  describe "venmo_sdk" do
891
993
  it "can create a card with a venmo sdk payment method code" do
892
994
  result = Braintree::Transaction.create(
@@ -898,6 +1000,22 @@ describe Braintree::Transaction do
898
1000
  result.transaction.credit_card_details.bin.should == "400934"
899
1001
  result.transaction.credit_card_details.last_4.should == "1881"
900
1002
  end
1003
+
1004
+ it "can create a transaction with venmo sdk session" do
1005
+ result = Braintree::Transaction.create(
1006
+ :type => "sale",
1007
+ :amount => Braintree::Test::TransactionAmounts::Authorize,
1008
+ :credit_card => {
1009
+ :number => Braintree::Test::CreditCardNumbers::Visa,
1010
+ :expiration_date => "12/12",
1011
+ },
1012
+ :options => {
1013
+ :venmo_sdk_session => Braintree::Test::VenmoSDK::Session
1014
+ }
1015
+ )
1016
+ result.success?.should == true
1017
+ result.transaction.credit_card_details.venmo_sdk?.should == true
1018
+ end
901
1019
  end
902
1020
  end
903
1021
 
@@ -1441,6 +1559,24 @@ describe Braintree::Transaction do
1441
1559
  result.success?.should == false
1442
1560
  result.errors.for(:transaction).on(:base)[0].code.should == Braintree::ErrorCodes::Transaction::CannotSubmitForSettlement
1443
1561
  end
1562
+
1563
+ context "service fees" do
1564
+ it "returns an error result if amount submitted for settlement is less than service fee amount" do
1565
+ transaction = Braintree::Transaction.create(
1566
+ :type => "sale",
1567
+ :amount => Braintree::Test::TransactionAmounts::Authorize,
1568
+ :merchant_account_id => SpecHelper::NonDefaultSubMerchantAccountId,
1569
+ :credit_card => {
1570
+ :number => Braintree::Test::CreditCardNumbers::Visa,
1571
+ :expiration_date => "06/2009"
1572
+ },
1573
+ :service_fee_amount => "1.00"
1574
+ ).transaction
1575
+ result = Braintree::Transaction.submit_for_settlement(transaction.id, "0.01")
1576
+ result.success?.should == false
1577
+ result.errors.for(:transaction).on(:amount)[0].code.should == Braintree::ErrorCodes::Transaction::SettlementAmountIsLessThanServiceFeeAmount
1578
+ end
1579
+ end
1444
1580
  end
1445
1581
 
1446
1582
  describe "self.submit_for_settlement!" do
@@ -1472,6 +1608,101 @@ describe Braintree::Transaction do
1472
1608
  end
1473
1609
  end
1474
1610
 
1611
+ describe "self.release_from_escrow" do
1612
+ it "returns the transaction if successful" do
1613
+ original_transaction = create_escrowed_transcation
1614
+
1615
+ result = Braintree::Transaction.release_from_escrow(original_transaction.id)
1616
+ result.transaction.escrow_status.should == Braintree::Transaction::EscrowStatus::ReleasePending
1617
+ end
1618
+
1619
+ it "returns an error result if escrow_status is not HeldForEscrow" do
1620
+ transaction = Braintree::Transaction.sale!(
1621
+ :amount => Braintree::Test::TransactionAmounts::Authorize,
1622
+ :merchant_account_id => SpecHelper::NonDefaultSubMerchantAccountId,
1623
+ :credit_card => {
1624
+ :number => Braintree::Test::CreditCardNumbers::Visa,
1625
+ :expiration_date => "05/2009"
1626
+ },
1627
+ :service_fee_amount => '1.00'
1628
+ )
1629
+
1630
+ transaction.escrow_status.should be_nil
1631
+
1632
+ result = Braintree::Transaction.release_from_escrow(transaction.id)
1633
+ result.errors.for(:transaction).on(:base)[0].code.should == Braintree::ErrorCodes::Transaction::CannotReleaseFromEscrow
1634
+ end
1635
+ end
1636
+
1637
+ describe "self.release_from_escrow!" do
1638
+ it "returns the transaction when successful" do
1639
+ original_transaction = create_escrowed_transcation
1640
+
1641
+ transaction = Braintree::Transaction.release_from_escrow!(original_transaction.id)
1642
+ transaction.escrow_status.should == Braintree::Transaction::EscrowStatus::ReleasePending
1643
+ end
1644
+
1645
+ it "raises an error when transaction is not successful" do
1646
+ transaction = Braintree::Transaction.sale!(
1647
+ :amount => Braintree::Test::TransactionAmounts::Authorize,
1648
+ :merchant_account_id => SpecHelper::NonDefaultSubMerchantAccountId,
1649
+ :credit_card => {
1650
+ :number => Braintree::Test::CreditCardNumbers::Visa,
1651
+ :expiration_date => "05/2009"
1652
+ },
1653
+ :service_fee_amount => '1.00'
1654
+ )
1655
+
1656
+ transaction.escrow_status.should be_nil
1657
+
1658
+ expect do
1659
+ Braintree::Transaction.release_from_escrow!(transaction.id)
1660
+ end.to raise_error(Braintree::ValidationsFailed)
1661
+ end
1662
+ end
1663
+
1664
+ describe "self.cancel_release" do
1665
+ it "returns the transaction if successful" do
1666
+ transaction = create_escrowed_transcation
1667
+ result = Braintree::Transaction.release_from_escrow(transaction.id)
1668
+ result.transaction.escrow_status.should == Braintree::Transaction::EscrowStatus::ReleasePending
1669
+
1670
+ result = Braintree::Transaction.cancel_release(transaction.id)
1671
+
1672
+ result.success?.should be_true
1673
+ result.transaction.escrow_status.should == Braintree::Transaction::EscrowStatus::Held
1674
+ end
1675
+
1676
+ it "returns an error result if escrow_status is not ReleasePending" do
1677
+ transaction = create_escrowed_transcation
1678
+
1679
+ result = Braintree::Transaction.cancel_release(transaction.id)
1680
+
1681
+ result.success?.should be_false
1682
+ result.errors.for(:transaction).on(:base)[0].code.should == Braintree::ErrorCodes::Transaction::CannotCancelRelease
1683
+ end
1684
+ end
1685
+
1686
+ describe "self.cancel_release!" do
1687
+ it "returns the transaction when release is cancelled" do
1688
+ transaction = create_escrowed_transcation
1689
+ result = Braintree::Transaction.release_from_escrow(transaction.id)
1690
+ result.transaction.escrow_status.should == Braintree::Transaction::EscrowStatus::ReleasePending
1691
+
1692
+ transaction = Braintree::Transaction.cancel_release!(transaction.id)
1693
+
1694
+ transaction.escrow_status.should == Braintree::Transaction::EscrowStatus::Held
1695
+ end
1696
+
1697
+ it "raises an error when release cannot be cancelled" do
1698
+ transaction = create_escrowed_transcation
1699
+
1700
+ expect {
1701
+ transaction = Braintree::Transaction.cancel_release!(transaction.id)
1702
+ }.to raise_error(Braintree::ValidationsFailed)
1703
+ end
1704
+ end
1705
+
1475
1706
  describe "self.credit" do
1476
1707
  it "returns a successful result with type=credit if successful" do
1477
1708
  result = Braintree::Transaction.credit(
@@ -1530,6 +1761,22 @@ describe Braintree::Transaction do
1530
1761
  result.success?.should == true
1531
1762
  result.transaction.merchant_account_id.should == SpecHelper::DefaultMerchantAccountId
1532
1763
  end
1764
+
1765
+ it "disallows service fee on a credit" do
1766
+ params = {
1767
+ :transaction => {
1768
+ :amount => Braintree::Test::TransactionAmounts::Authorize,
1769
+ :credit_card => {
1770
+ :number => Braintree::Test::CreditCardNumbers::Visa,
1771
+ :expiration_date => "05/2009"
1772
+ },
1773
+ :service_fee_amount => "1.00"
1774
+ }
1775
+ }
1776
+ result = Braintree::Transaction.credit(params[:transaction])
1777
+ result.success?.should == false
1778
+ result.errors.for(:transaction).on(:base).map(&:code).should include(Braintree::ErrorCodes::Transaction::ServiceFeeIsNotAllowedOnCredits)
1779
+ end
1533
1780
  end
1534
1781
 
1535
1782
  describe "self.credit!" do
@@ -1792,6 +2039,76 @@ describe Braintree::Transaction do
1792
2039
  end
1793
2040
  end
1794
2041
 
2042
+ describe "self.hold_in_escrow" do
2043
+ it "returns the transaction if successful" do
2044
+ result = Braintree::Transaction.create(
2045
+ :type => "sale",
2046
+ :amount => Braintree::Test::TransactionAmounts::Authorize,
2047
+ :merchant_account_id => SpecHelper::NonDefaultSubMerchantAccountId,
2048
+ :credit_card => {
2049
+ :number => Braintree::Test::CreditCardNumbers::Visa,
2050
+ :expiration_date => "12/12",
2051
+ },
2052
+ :service_fee_amount => "10.00"
2053
+ )
2054
+
2055
+ result.transaction.escrow_status.should be_nil
2056
+ result = Braintree::Transaction.hold_in_escrow(result.transaction.id)
2057
+
2058
+ result.success?.should be_true
2059
+ result.transaction.escrow_status.should == Braintree::Transaction::EscrowStatus::HoldPending
2060
+ end
2061
+
2062
+ it "returns an error result if the transaction cannot be held in escrow" do
2063
+ transaction = Braintree::Transaction.sale!(
2064
+ :amount => Braintree::Test::TransactionAmounts::Authorize,
2065
+ :merchant_account_id => SpecHelper::NonDefaultMerchantAccountId,
2066
+ :credit_card => {
2067
+ :number => Braintree::Test::CreditCardNumbers::Visa,
2068
+ :expiration_date => "05/2009"
2069
+ }
2070
+ )
2071
+
2072
+ result = Braintree::Transaction.hold_in_escrow(transaction.id)
2073
+ result.errors.for(:transaction).on(:base)[0].code.should == Braintree::ErrorCodes::Transaction::CannotHoldInEscrow
2074
+ end
2075
+ end
2076
+
2077
+ describe "self.hold_in_escrow!" do
2078
+ it "returns the transaction if successful" do
2079
+ result = Braintree::Transaction.create(
2080
+ :type => "sale",
2081
+ :amount => Braintree::Test::TransactionAmounts::Authorize,
2082
+ :merchant_account_id => SpecHelper::NonDefaultSubMerchantAccountId,
2083
+ :credit_card => {
2084
+ :number => Braintree::Test::CreditCardNumbers::Visa,
2085
+ :expiration_date => "12/12",
2086
+ },
2087
+ :service_fee_amount => "10.00"
2088
+ )
2089
+
2090
+ result.transaction.escrow_status.should be_nil
2091
+ transaction = Braintree::Transaction.hold_in_escrow!(result.transaction.id)
2092
+
2093
+ transaction.escrow_status.should == Braintree::Transaction::EscrowStatus::HoldPending
2094
+ end
2095
+
2096
+ it "raises an error if the transaction cannot be held in escrow" do
2097
+ transaction = Braintree::Transaction.sale!(
2098
+ :amount => Braintree::Test::TransactionAmounts::Authorize,
2099
+ :merchant_account_id => SpecHelper::NonDefaultMerchantAccountId,
2100
+ :credit_card => {
2101
+ :number => Braintree::Test::CreditCardNumbers::Visa,
2102
+ :expiration_date => "05/2009"
2103
+ }
2104
+ )
2105
+
2106
+ expect do
2107
+ Braintree::Transaction.hold_in_escrow!(transaction.id)
2108
+ end.to raise_error(Braintree::ValidationsFailed)
2109
+ end
2110
+ end
2111
+
1795
2112
  describe "self.void" do
1796
2113
  it "returns a successful result if successful" do
1797
2114
  transaction = Braintree::Transaction.sale!(
@@ -2137,4 +2454,76 @@ describe Braintree::Transaction do
2137
2454
  response = Braintree::Configuration.instantiate.http.put "/transactions/#{transaction.id}/settle"
2138
2455
  Braintree::Transaction.find(transaction.id)
2139
2456
  end
2457
+
2458
+ def create_escrowed_transcation
2459
+ transaction = Braintree::Transaction.sale!(
2460
+ :amount => Braintree::Test::TransactionAmounts::Authorize,
2461
+ :merchant_account_id => SpecHelper::NonDefaultSubMerchantAccountId,
2462
+ :credit_card => {
2463
+ :number => Braintree::Test::CreditCardNumbers::Visa,
2464
+ :expiration_date => "05/2009"
2465
+ },
2466
+ :service_fee_amount => '1.00',
2467
+ :options => { :hold_in_escrow => true }
2468
+ )
2469
+
2470
+ response = Braintree::Configuration.instantiate.http.put "/transactions/#{transaction.id}/settle"
2471
+ response = Braintree::Configuration.instantiate.http.put "/transactions/#{transaction.id}/escrow"
2472
+ Braintree::Transaction.find(transaction.id)
2473
+ end
2474
+
2475
+ context "venmo sdk" do
2476
+ describe "venmo_sdk_payment_method_code" do
2477
+ it "can create a transaction with venmo_sdk_payment_method_code" do
2478
+ result = Braintree::Transaction.sale(
2479
+ :amount => "10.00",
2480
+ :venmo_sdk_payment_method_code => Braintree::Test::VenmoSDK.generate_test_payment_method_code(Braintree::Test::CreditCardNumbers::Visa)
2481
+ )
2482
+ result.success?.should == true
2483
+ result.transaction.credit_card_details.venmo_sdk?.should == true
2484
+ end
2485
+
2486
+ it "errors when an invalid payment method code is passed" do
2487
+ result = Braintree::Transaction.sale(
2488
+ :amount => "10.00",
2489
+ :venmo_sdk_payment_method_code => Braintree::Test::VenmoSDK::InvalidPaymentMethodCode
2490
+ )
2491
+ result.success?.should == false
2492
+ result.message.should == "Invalid VenmoSDK payment method code"
2493
+ result.errors.first.code.should == "91727"
2494
+ end
2495
+ end
2496
+
2497
+ describe "venmo_sdk_session" do
2498
+ it "can create a transaction and vault a card when a venmo_sdk_session is present" do
2499
+ result = Braintree::Transaction.sale(
2500
+ :amount => "10.00",
2501
+ :credit_card => {
2502
+ :number => Braintree::Test::CreditCardNumbers::Visa,
2503
+ :expiration_date => "05/2009"
2504
+ },
2505
+ :options => {
2506
+ :venmo_sdk_session => Braintree::Test::VenmoSDK::Session
2507
+ }
2508
+ )
2509
+ result.success?.should == true
2510
+ result.transaction.credit_card_details.venmo_sdk?.should == true
2511
+ end
2512
+
2513
+ it "venmo_sdk boolean is false when an invalid session is passed" do
2514
+ result = Braintree::Transaction.sale(
2515
+ :amount => "10.00",
2516
+ :credit_card => {
2517
+ :number => Braintree::Test::CreditCardNumbers::Visa,
2518
+ :expiration_date => "05/2009"
2519
+ },
2520
+ :options => {
2521
+ :venmo_sdk_session => Braintree::Test::VenmoSDK::InvalidSession
2522
+ }
2523
+ )
2524
+ result.success?.should == true
2525
+ result.transaction.credit_card_details.venmo_sdk?.should == false
2526
+ end
2527
+ end
2528
+ end
2140
2529
  end