braintree 2.69.1 → 2.70.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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)