braintree 2.69.1 → 2.70.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.
@@ -90,4 +90,4 @@ your system. To run unit tests use rake: <tt>rake test:unit</tt>.
90
90
 
91
91
  == License
92
92
 
93
- See the [LICENSE](LICENSE) file for more info.
93
+ See the LICENSE[https://github.com/braintree/braintree_ruby/blob/master/LICENSE] file for more info.
@@ -55,6 +55,9 @@ require "braintree/error_result"
55
55
  require "braintree/errors"
56
56
  require "braintree/gateway"
57
57
  require "braintree/http"
58
+ require "braintree/ideal_payment"
59
+ require "braintree/ideal_payment_gateway"
60
+ require "braintree/transaction/ideal_payment_details"
58
61
  require "braintree/merchant"
59
62
  require "braintree/merchant_gateway"
60
63
  require "braintree/merchant_account"
@@ -290,6 +290,8 @@ module Braintree
290
290
  CustomerDoesNotHaveCreditCard = "91511"
291
291
  CustomerIdIsInvalid = "91510"
292
292
  HasAlreadyBeenRefunded = "91512"
293
+ IdealPaymentNotComplete = "815141"
294
+ IdealPaymentAlreadyConsumed = "815142"
293
295
  MerchantAccountDoesNotMatch3DSecureMerchantAccount = "91584"
294
296
  MerchantAccountDoesNotSupportMOTO = "91558"
295
297
  MerchantAccountDoesNotSupportRefunds = "91547"
@@ -401,6 +403,12 @@ module Braintree
401
403
  InconsistentCountry = "93612"
402
404
  PaymentMethodsAreInvalid = "93613"
403
405
  PaymentMethodsAreNotAllowed = "93615"
406
+ MerchantAccountExistsForCurrency = "93616"
407
+ CurrencyIsRequired = "93617"
408
+ CurrencyIsInvalid = "93618"
409
+ NoMerchantAccounts = "93619"
410
+ MerchantAccountExistsForId = "93620"
411
+ MerchantAccountNotAuthOnboarded = "93621"
404
412
  end
405
413
 
406
414
  module MerchantAccount
@@ -60,6 +60,10 @@ module Braintree
60
60
  UsBankAccountGateway.new(self)
61
61
  end
62
62
 
63
+ def ideal_payment
64
+ IdealPaymentGateway.new(self)
65
+ end
66
+
63
67
  def merchant
64
68
  MerchantGateway.new(self)
65
69
  end
@@ -0,0 +1,46 @@
1
+ module Braintree
2
+ class IdealPayment
3
+ include BaseModule
4
+
5
+ attr_reader :id, :ideal_transaction_id, :currency, :amount, :status, :order_id, :issuer, :approval_url, :iban_bank_account
6
+
7
+ def initialize(gateway, attributes) # :nodoc:
8
+ @gateway = gateway
9
+ set_instance_variables_from_hash(attributes)
10
+ @iban_bank_account = IbanBankAccount.new(attributes[:iban_bank_account])
11
+ end
12
+
13
+ class << self
14
+ protected :new
15
+ end
16
+
17
+ def self._new(*args) # :nodoc:
18
+ self.new *args
19
+ end
20
+
21
+ def self.sale(ideal_payment_id, transaction_attributes)
22
+ Configuration.gateway.transaction.sale(transaction_attributes.merge(
23
+ :payment_method_nonce => ideal_payment_id,
24
+ :options => { :submit_for_settlement => true }
25
+ )
26
+ )
27
+ end
28
+
29
+ def self.sale!(ideal_payment_id, transaction_attributes)
30
+ return_object_or_raise(:transaction) { sale(ideal_payment_id, transaction_attributes) }
31
+ end
32
+
33
+ def self.find(ideal_payment_id)
34
+ Configuration.gateway.ideal_payment.find(ideal_payment_id)
35
+ end
36
+
37
+ class IbanBankAccount
38
+ include BaseModule
39
+ attr_reader :account_holder_name, :bic, :masked_iban, :iban_account_number_last_4, :iban_country, :description
40
+
41
+ def initialize(attributes) # :nodoc:
42
+ set_instance_variables_from_hash(attributes)
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,18 @@
1
+ module Braintree
2
+ class IdealPaymentGateway
3
+ def initialize(gateway)
4
+ @gateway = gateway
5
+ @config = gateway.config
6
+ @config.assert_has_access_token_or_keys
7
+ end
8
+
9
+ def find(ideal_payment_id)
10
+ raise ArgumentError if ideal_payment_id.nil? || ideal_payment_id.to_s.strip == ""
11
+ response = @config.http.get("#{@config.base_merchant_path}/ideal_payments/#{ideal_payment_id}")
12
+ IdealPayment._new(@gateway, response[:ideal_payment])
13
+ rescue NotFoundError
14
+ raise NotFoundError, "ideal payment with ideal_payment_id #{ideal_payment_id.inspect} not found"
15
+ end
16
+
17
+ end
18
+ end
@@ -25,6 +25,10 @@ module Braintree
25
25
  _do_update "/merchant_accounts/#{merchant_account_id}/update_via_api", :merchant_account => attributes
26
26
  end
27
27
 
28
+ def create_for_currency(params)
29
+ _create_for_currency(params)
30
+ end
31
+
28
32
  def _do_create(path, params=nil) # :nodoc:
29
33
  response = @config.http.post("#{@config.base_merchant_path}#{path}", params)
30
34
  if response[:api_error_response]
@@ -43,6 +47,20 @@ module Braintree
43
47
  end
44
48
  end
45
49
 
50
+ def _create_for_currency(params)
51
+ response = @config.http.post("#{@config.base_merchant_path}/merchant_accounts/create_for_currency", :merchant_account => params)
52
+
53
+ if response.has_key?(:response) && response[:response][:merchant_account]
54
+ Braintree::SuccessfulResult.new(
55
+ :merchant_account => MerchantAccount._new(@gateway, response[:response][:merchant_account])
56
+ )
57
+ elsif response[:api_error_response]
58
+ ErrorResult.new(@gateway, response[:api_error_response])
59
+ else
60
+ raise UnexpectedError, "expected :merchant or :api_error_response"
61
+ end
62
+ end
63
+
46
64
  def self._detect_signature(attributes)
47
65
  if attributes.has_key?(:applicant_details)
48
66
  warn "[DEPRECATED] Passing :applicant_details to create is deprecated. Please use :individual, :business, and :funding."
@@ -8,5 +8,6 @@ module Braintree
8
8
  AndroidPayCard = 'android_pay_card'
9
9
  VenmoAccount = 'venmo_account'
10
10
  UsBankAccount = 'us_bank_account'
11
+ IdealPayment = 'ideal_payment'
11
12
  end
12
13
  end
@@ -14,8 +14,8 @@ module Braintree
14
14
  Configuration.gateway.payment_method.update(token, attributes)
15
15
  end
16
16
 
17
- def self.delete(token)
18
- Configuration.gateway.payment_method.delete(token)
17
+ def self.delete(token, options = {})
18
+ Configuration.gateway.payment_method.delete(token, options)
19
19
  end
20
20
 
21
21
  def self.grant(token, options = {})
@@ -43,9 +43,11 @@ module Braintree
43
43
  raise UnexpectedError, "expected :payment_method or :api_error_response"
44
44
  end
45
45
  end
46
-
47
- def delete(token)
48
- @config.http.delete("#{@config.base_merchant_path}/payment_methods/any/#{token}")
46
+
47
+ def delete(token, options = {})
48
+ Util.verify_keys(PaymentMethodGateway._delete_signature, options)
49
+ query_param = "?" + Util.hash_to_query_string(options) if not options.empty?
50
+ @config.http.delete("#{@config.base_merchant_path}/payment_methods/any/#{token}#{query_param}")
49
51
  SuccessfulResult.new
50
52
  end
51
53
 
@@ -135,6 +137,10 @@ module Braintree
135
137
  def self._update_signature # :nodoc:
136
138
  _signature(:update)
137
139
  end
140
+
141
+ def self._delete_signature # :nodoc:
142
+ [:revoke_all_grants]
143
+ end
138
144
 
139
145
  def self._signature(type) # :nodoc:
140
146
  billing_address_params = AddressGateway._shared_signature
@@ -123,6 +123,7 @@ module Braintree
123
123
  attr_reader :facilitator_details
124
124
  attr_reader :three_d_secure_info
125
125
  attr_reader :us_bank_account_details
126
+ attr_reader :ideal_payment_details
126
127
 
127
128
  def self.create(attributes)
128
129
  Configuration.gateway.transaction.create(attributes)
@@ -269,6 +270,7 @@ module Braintree
269
270
  @facilitator_details = FacilitatorDetails.new(attributes[:facilitator_details]) if attributes[:facilitator_details]
270
271
  @three_d_secure_info = ThreeDSecureInfo.new(attributes[:three_d_secure_info]) if attributes[:three_d_secure_info]
271
272
  @us_bank_account_details = UsBankAccountDetails.new(attributes[:us_bank_account]) if attributes[:us_bank_account]
273
+ @ideal_payment_details = IdealPaymentDetails.new(attributes[:ideal_payment]) if attributes[:ideal_payment]
272
274
  end
273
275
 
274
276
  # True if <tt>other</tt> is a Braintree::Transaction with the same id.
@@ -0,0 +1,13 @@
1
+ module Braintree
2
+ class Transaction
3
+ class IdealPaymentDetails # :nodoc:
4
+ include BaseModule
5
+
6
+ attr_reader :ideal_payment_id, :ideal_transaction_id, :masked_iban, :bic, :image_url
7
+
8
+ def initialize(attributes)
9
+ set_instance_variables_from_hash attributes unless attributes.nil?
10
+ end
11
+ end
12
+ end
13
+ end
@@ -5,7 +5,7 @@ module Braintree
5
5
 
6
6
  attr_reader :custom_field, :payer_email, :payment_id, :authorization_id, :token,
7
7
  :image_url, :debug_id, :payee_email, :payer_id, :payer_first_name, :payer_last_name,
8
- :seller_protection_status, :capture_id, :refund_id, :transaction_fee_amount,
8
+ :payer_status, :seller_protection_status, :capture_id, :refund_id, :transaction_fee_amount,
9
9
  :transaction_fee_currency_iso_code, :description
10
10
 
11
11
  def initialize(attributes)
@@ -170,6 +170,7 @@ module Braintree
170
170
  :store_shipping_address_in_vault,
171
171
  :venmo_sdk_session,
172
172
  :payee_email,
173
+ :skip_advanced_fraud_checking,
173
174
  :skip_avs,
174
175
  :skip_cvv,
175
176
  {:paypal => [:custom_field, :payee_email, :description, {:supplementary_data => :_any_key_}]},
@@ -1,8 +1,8 @@
1
1
  module Braintree
2
2
  module Version
3
3
  Major = 2
4
- Minor = 69
5
- Tiny = 1
4
+ Minor = 70
5
+ Tiny = 0
6
6
 
7
7
  String = "#{Major}.#{Minor}.#{Tiny}"
8
8
  end
@@ -1 +1 @@
1
- 31391
1
+ 2857
@@ -59,6 +59,7 @@ end
59
59
  def generate_valid_us_bank_account_nonce()
60
60
  raw_client_token = Braintree::ClientToken.generate
61
61
  client_token = decode_client_token(raw_client_token)
62
+
62
63
  url = client_token["braintree_api"]["url"] + "/tokens"
63
64
  token = client_token["braintree_api"]["access_token"]
64
65
  payload = {
@@ -70,7 +71,7 @@ def generate_valid_us_bank_account_nonce()
70
71
  :postal_code => "94112"
71
72
  },
72
73
  :account_type => "checking",
73
- :routing_number => "123456789",
74
+ :routing_number => "021000021",
74
75
  :account_number => "567891234",
75
76
  :account_holder_name => "Dan Schulman",
76
77
  :account_description => "PayPal Checking - 1234",
@@ -79,20 +80,25 @@ def generate_valid_us_bank_account_nonce()
79
80
  }
80
81
  }
81
82
 
82
- uri = URI::parse(url)
83
- connection = Net::HTTP.new(uri.host, uri.port)
84
- connection.use_ssl = true
85
- connection.verify_mode = OpenSSL::SSL::VERIFY_PEER
86
- resp = connection.start do |http|
87
- request = Net::HTTP::Post.new(uri.path)
88
- request["Content-Type"] = "application/json"
89
- request["Braintree-Version"] = "2015-11-01"
90
- request["Authorization"] = "Bearer #{token}"
91
- request.body = payload.to_json
92
- http.request(request)
93
- end
83
+ json = _cosmos_post(token, url, payload)
84
+ json["data"]["id"]
85
+ end
94
86
 
95
- json = JSON.parse(resp.body)
87
+ def generate_valid_ideal_payment_nonce(amount = Braintree::Test::TransactionAmounts::Authorize)
88
+ raw_client_token = Braintree::ClientToken.generate
89
+ client_token = decode_client_token(raw_client_token)
90
+
91
+ token = client_token["braintree_api"]["access_token"]
92
+ url = client_token["braintree_api"]["url"] + "/ideal-payments"
93
+ payload = {
94
+ :issuer => "RABONL2U",
95
+ :order_id => SpecHelper::DefaultOrderId,
96
+ :amount => amount,
97
+ :currency => "EUR",
98
+ :redirect_url => "https://braintree-api.com",
99
+ }
100
+
101
+ json = _cosmos_post(token, url, payload)
96
102
  json["data"]["id"]
97
103
  end
98
104
 
@@ -107,6 +113,23 @@ def generate_invalid_us_bank_account_nonce
107
113
  nonce += "_xxx"
108
114
  end
109
115
 
116
+ def _cosmos_post(token, url, payload)
117
+ uri = URI::parse(url)
118
+ connection = Net::HTTP.new(uri.host, uri.port)
119
+ connection.use_ssl = true
120
+ connection.verify_mode = OpenSSL::SSL::VERIFY_PEER
121
+ resp = connection.start do |http|
122
+ request = Net::HTTP::Post.new(uri.path)
123
+ request["Content-Type"] = "application/json"
124
+ request["Braintree-Version"] = "2015-11-01"
125
+ request["Authorization"] = "Bearer #{token}"
126
+ request.body = payload.to_json
127
+ http.request(request)
128
+ end
129
+
130
+ JSON.parse(resp.body)
131
+ end
132
+
110
133
  class ClientApiHttp
111
134
  attr_reader :config, :options
112
135
 
@@ -910,12 +910,12 @@ describe Braintree::Customer do
910
910
 
911
911
  us_bank_account = found_customer.us_bank_accounts.first
912
912
  us_bank_account.should be_a(Braintree::UsBankAccount)
913
- us_bank_account.routing_number.should == "123456789"
913
+ us_bank_account.routing_number.should == "021000021"
914
914
  us_bank_account.last_4.should == "1234"
915
915
  us_bank_account.account_type.should == "checking"
916
916
  us_bank_account.account_description.should == "PayPal Checking - 1234"
917
917
  us_bank_account.account_holder_name.should == "Dan Schulman"
918
- us_bank_account.bank_name.should == "UNKNOWN"
918
+ us_bank_account.bank_name.should =~ /CHASE/
919
919
  end
920
920
 
921
921
  it "works for a blank customer" do
@@ -0,0 +1,87 @@
1
+ require File.expand_path(File.dirname(__FILE__) + "/../spec_helper")
2
+ require File.expand_path(File.dirname(__FILE__) + "/client_api/spec_helper")
3
+
4
+ describe Braintree::IdealPayment do
5
+ context "self.sale" do
6
+ it "creates a transaction using an Ideal payment token and returns a result object" do
7
+ ideal_payment_id = generate_valid_ideal_payment_nonce
8
+
9
+ result = Braintree::IdealPayment.sale(
10
+ ideal_payment_id,
11
+ :order_id => SpecHelper::DefaultOrderId,
12
+ :merchant_account_id => "ideal_merchant_account",
13
+ :amount => Braintree::Test::TransactionAmounts::Authorize
14
+ )
15
+
16
+ result.success?.should == true
17
+ result.transaction.amount.should == BigDecimal.new(Braintree::Test::TransactionAmounts::Authorize)
18
+ result.transaction.type.should == "sale"
19
+ ideal_payment_details = result.transaction.ideal_payment_details
20
+ ideal_payment_details.ideal_payment_id.should =~ /^idealpayment_\w{6,}$/
21
+ ideal_payment_details.ideal_transaction_id.should =~ /^\d{16,}$/
22
+ ideal_payment_details.image_url.should start_with("https://")
23
+ ideal_payment_details.masked_iban.should_not be_empty
24
+ ideal_payment_details.bic.should_not be_empty
25
+ end
26
+ end
27
+
28
+ context "self.sale!" do
29
+ it "creates a transaction using an ideal payment and returns a result object" do
30
+ ideal_payment_id = generate_valid_ideal_payment_nonce
31
+
32
+ transaction = Braintree::IdealPayment.sale!(
33
+ ideal_payment_id,
34
+ :order_id => SpecHelper::DefaultOrderId,
35
+ :merchant_account_id => "ideal_merchant_account",
36
+ :amount => Braintree::Test::TransactionAmounts::Authorize
37
+ )
38
+
39
+ transaction.amount.should == BigDecimal.new(Braintree::Test::TransactionAmounts::Authorize)
40
+ transaction.type.should == "sale"
41
+ ideal_payment_details = transaction.ideal_payment_details
42
+ ideal_payment_details.ideal_payment_id.should =~ /^idealpayment_\w{6,}$/
43
+ ideal_payment_details.ideal_transaction_id.should =~ /^\d{16,}$/
44
+ ideal_payment_details.image_url.should start_with("https://")
45
+ ideal_payment_details.masked_iban.should_not be_empty
46
+ ideal_payment_details.bic.should_not be_empty
47
+ end
48
+
49
+ it "does not create a transaction using an ideal payment and returns raises an exception" do
50
+ expect do
51
+ Braintree::IdealPayment.sale!(
52
+ "invalid_nonce",
53
+ :merchant_account_id => "ideal_merchant_account",
54
+ :amount => Braintree::Test::TransactionAmounts::Authorize
55
+ )
56
+ end.to raise_error(Braintree::ValidationsFailed)
57
+ end
58
+ end
59
+
60
+ context "self.find" do
61
+ it "finds the Ideal payment with an id" do
62
+ ideal_payment_id = generate_valid_ideal_payment_nonce
63
+ ideal_payment = Braintree::IdealPayment.find(ideal_payment_id)
64
+
65
+ ideal_payment.id.should =~ /^idealpayment_\w{6,}$/
66
+ ideal_payment.ideal_transaction_id.should =~ /^\d{16,}$/
67
+ ideal_payment.currency.should_not be_empty
68
+ ideal_payment.amount.should_not be_empty
69
+ ideal_payment.status.should_not be_empty
70
+ ideal_payment.order_id.should_not be_empty
71
+ ideal_payment.issuer.should_not be_empty
72
+ ideal_payment.approval_url.should start_with("https://")
73
+ ideal_payment.iban_bank_account.account_holder_name.should_not be_empty
74
+ ideal_payment.iban_bank_account.bic.should_not be_empty
75
+ ideal_payment.iban_bank_account.masked_iban.should_not be_empty
76
+ ideal_payment.iban_bank_account.iban_account_number_last_4.should =~ /^\d{4}$/
77
+ ideal_payment.iban_bank_account.iban_country.should_not be_empty
78
+ ideal_payment.iban_bank_account.description.should_not be_empty
79
+ end
80
+
81
+ it "raises if the Ideal payment is not found" do
82
+ expect do
83
+ Braintree::IdealPayment.find("idealpayment_nxyqkq_s654wq_92jr64_mnr4kr_yjz")
84
+ end.to raise_error(Braintree::NotFoundError)
85
+ end
86
+ end
87
+ end
@@ -135,6 +135,109 @@ describe Braintree::MerchantAccount do
135
135
  end
136
136
  end
137
137
 
138
+ describe "create_for_currency" do
139
+ it "creates a new merchant account for currency" do
140
+ result = SpecHelper::create_merchant
141
+ result.should be_success
142
+
143
+ gateway = Braintree::Gateway.new(
144
+ :access_token => result.credentials.access_token,
145
+ :logger => Logger.new("/dev/null")
146
+ )
147
+
148
+ result = gateway.merchant_account.create_for_currency(
149
+ :currency => "JPY"
150
+ )
151
+ result.should be_success
152
+ result.merchant_account.currency_iso_code.should == "JPY"
153
+ end
154
+
155
+ it "returns error if a merchant account already exists for that currency" do
156
+ result = SpecHelper::create_merchant
157
+ result.should be_success
158
+
159
+ gateway = Braintree::Gateway.new(
160
+ :access_token => result.credentials.access_token,
161
+ :logger => Logger.new("/dev/null")
162
+ )
163
+
164
+ result = gateway.merchant_account.create_for_currency(
165
+ :currency => "USD"
166
+ )
167
+ result.should be_success
168
+
169
+ result = gateway.merchant_account.create_for_currency(
170
+ :currency => "USD"
171
+ )
172
+ result.should_not be_success
173
+
174
+ errors = result.errors.for(:merchant).on(:currency)
175
+ errors[0].code.should == Braintree::ErrorCodes::Merchant::MerchantAccountExistsForCurrency
176
+ end
177
+
178
+ it "returns error if no currency is provided" do
179
+ result = SpecHelper::create_merchant
180
+ result.should be_success
181
+
182
+ gateway = Braintree::Gateway.new(
183
+ :access_token => result.credentials.access_token,
184
+ :logger => Logger.new("/dev/null")
185
+ )
186
+
187
+ result = gateway.merchant_account.create_for_currency(
188
+ :currency => nil
189
+ )
190
+ result.should_not be_success
191
+
192
+ errors = result.errors.for(:merchant).on(:currency)
193
+ errors[0].code.should == Braintree::ErrorCodes::Merchant::CurrencyIsRequired
194
+
195
+ result = gateway.merchant_account.create_for_currency({})
196
+ result.should_not be_success
197
+
198
+ errors = result.errors.for(:merchant).on(:currency)
199
+ errors[0].code.should == Braintree::ErrorCodes::Merchant::CurrencyIsRequired
200
+ end
201
+
202
+ it "returns error if a currency is not supported" do
203
+ result = SpecHelper::create_merchant
204
+ result.should be_success
205
+
206
+ gateway = Braintree::Gateway.new(
207
+ :access_token => result.credentials.access_token,
208
+ :logger => Logger.new("/dev/null")
209
+ )
210
+
211
+ result = gateway.merchant_account.create_for_currency(
212
+ :currency => "FAKE_CURRENCY"
213
+ )
214
+ result.should_not be_success
215
+
216
+ errors = result.errors.for(:merchant).on(:currency)
217
+ errors[0].code.should == Braintree::ErrorCodes::Merchant::CurrencyIsInvalid
218
+ end
219
+
220
+ it "returns error if id is passed and already taken" do
221
+ result = SpecHelper::create_merchant
222
+ result.should be_success
223
+
224
+ gateway = Braintree::Gateway.new(
225
+ :access_token => result.credentials.access_token,
226
+ :logger => Logger.new("/dev/null")
227
+ )
228
+
229
+ merchant = result.merchant
230
+ result = gateway.merchant_account.create_for_currency(
231
+ :currency => "USD",
232
+ :id => merchant.merchant_accounts.first.id
233
+ )
234
+ result.should_not be_success
235
+
236
+ errors = result.errors.for(:merchant).on(:id)
237
+ errors[0].code.should == Braintree::ErrorCodes::Merchant::MerchantAccountExistsForId
238
+ end
239
+ end
240
+
138
241
  describe "find" do
139
242
  it "finds the merchant account with the given token" do
140
243
  result = Braintree::MerchantAccount.create(VALID_APPLICATION_PARAMS)