braintree 2.4.0 → 2.5.1

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.
Files changed (66) hide show
  1. data/README.rdoc +4 -0
  2. data/lib/braintree.rb +43 -32
  3. data/lib/braintree/add_on.rb +4 -0
  4. data/lib/braintree/address.rb +18 -72
  5. data/lib/braintree/address_gateway.rb +76 -0
  6. data/lib/braintree/advanced_search.rb +31 -13
  7. data/lib/braintree/base_module.rb +6 -0
  8. data/lib/braintree/configuration.rb +57 -39
  9. data/lib/braintree/credit_card.rb +75 -129
  10. data/lib/braintree/credit_card_gateway.rb +133 -0
  11. data/lib/braintree/credit_card_verification.rb +8 -0
  12. data/lib/braintree/customer.rb +70 -123
  13. data/lib/braintree/customer_gateway.rb +121 -0
  14. data/lib/braintree/digest.rb +2 -2
  15. data/lib/braintree/discount.rb +4 -0
  16. data/lib/braintree/error_codes.rb +50 -5
  17. data/lib/braintree/error_result.rb +4 -18
  18. data/lib/braintree/errors.rb +1 -2
  19. data/lib/braintree/exceptions.rb +11 -16
  20. data/lib/braintree/gateway.rb +39 -0
  21. data/lib/braintree/http.rb +30 -26
  22. data/lib/braintree/modification.rb +23 -0
  23. data/lib/braintree/resource_collection.rb +1 -1
  24. data/lib/braintree/subscription.rb +29 -129
  25. data/lib/braintree/subscription_gateway.rb +122 -0
  26. data/lib/braintree/subscription_search.rb +6 -7
  27. data/lib/braintree/successful_result.rb +1 -12
  28. data/lib/braintree/test/credit_card_numbers.rb +4 -2
  29. data/lib/braintree/test/transaction_amounts.rb +3 -0
  30. data/lib/braintree/transaction.rb +83 -243
  31. data/lib/braintree/transaction/credit_card_details.rb +4 -4
  32. data/lib/braintree/transaction_gateway.rb +124 -0
  33. data/lib/braintree/transaction_search.rb +5 -3
  34. data/lib/braintree/transparent_redirect.rb +19 -112
  35. data/lib/braintree/transparent_redirect_gateway.rb +105 -0
  36. data/lib/braintree/util.rb +4 -0
  37. data/lib/braintree/validation_error.rb +1 -0
  38. data/lib/braintree/validation_error_collection.rb +5 -23
  39. data/lib/braintree/version.rb +2 -2
  40. data/lib/braintree/xml/parser.rb +1 -1
  41. data/lib/braintree/xml/rexml.rb +2 -2
  42. data/spec/integration/braintree/advanced_search_spec.rb +532 -0
  43. data/spec/integration/braintree/credit_card_spec.rb +5 -8
  44. data/spec/integration/braintree/http_spec.rb +53 -39
  45. data/spec/integration/braintree/subscription_spec.rb +678 -213
  46. data/spec/integration/braintree/transaction_search_spec.rb +318 -43
  47. data/spec/integration/braintree/transaction_spec.rb +134 -3
  48. data/spec/integration/braintree/transparent_redirect_spec.rb +1 -1
  49. data/spec/spec_helper.rb +55 -4
  50. data/spec/unit/braintree/address_spec.rb +8 -8
  51. data/spec/unit/braintree/base_module_spec.rb +1 -1
  52. data/spec/unit/braintree/configuration_spec.rb +34 -29
  53. data/spec/unit/braintree/credit_card_spec.rb +14 -12
  54. data/spec/unit/braintree/credit_card_verification_spec.rb +16 -0
  55. data/spec/unit/braintree/customer_spec.rb +10 -8
  56. data/spec/unit/braintree/digest_spec.rb +8 -17
  57. data/spec/unit/braintree/error_result_spec.rb +12 -2
  58. data/spec/unit/braintree/http_spec.rb +2 -2
  59. data/spec/unit/braintree/subscription_search_spec.rb +77 -0
  60. data/spec/unit/braintree/subscription_spec.rb +16 -8
  61. data/spec/unit/braintree/transaction_spec.rb +17 -12
  62. data/spec/unit/braintree/transparent_redirect_spec.rb +12 -12
  63. data/spec/unit/braintree/util_spec.rb +24 -0
  64. data/spec/unit/braintree/xml/parser_spec.rb +1 -1
  65. data/spec/unit/braintree_spec.rb +1 -1
  66. metadata +16 -5
@@ -10,6 +10,10 @@ module Braintree
10
10
  set_instance_variables_from_hash attributes unless attributes.nil?
11
11
  end
12
12
 
13
+ def expiration_date
14
+ "#{expiration_month}/#{expiration_year}"
15
+ end
16
+
13
17
  def inspect
14
18
  attr_order = [:token, :bin, :last_4, :card_type, :expiration_date, :cardholder_name, :customer_location]
15
19
  formatted_attrs = attr_order.map do |attr|
@@ -18,10 +22,6 @@ module Braintree
18
22
  "#<#{formatted_attrs.join(", ")}>"
19
23
  end
20
24
 
21
- def expiration_date
22
- "#{expiration_month}/#{expiration_year}"
23
- end
24
-
25
25
  def masked_number
26
26
  "#{bin}******#{last_4}"
27
27
  end
@@ -0,0 +1,124 @@
1
+ module Braintree
2
+ class TransactionGateway # :nodoc:
3
+ def initialize(gateway)
4
+ @gateway = gateway
5
+ @config = gateway.config
6
+ end
7
+
8
+ def create(attributes)
9
+ Util.verify_keys(TransactionGateway._create_signature, attributes)
10
+ _do_create "/transactions", :transaction => attributes
11
+ end
12
+
13
+ # Deprecated
14
+ def create_from_transparent_redirect(query_string)
15
+ params = @gateway.transparent_redirect.parse_and_validate_query_string query_string
16
+ _do_create("/transactions/all/confirm_transparent_redirect_request", :id => params[:id])
17
+ end
18
+
19
+ def create_transaction_url
20
+ warn "[DEPRECATED] Transaction.create_transaction_url is deprecated. Please use TransparentRedirect.url"
21
+ "#{@config.base_merchant_url}/transactions/all/create_via_transparent_redirect_request"
22
+ end
23
+
24
+ def credit(attributes)
25
+ create(attributes.merge(:type => 'credit'))
26
+ end
27
+
28
+ def find(id)
29
+ response = @config.http.get "/transactions/#{id}"
30
+ Transaction._new(@gateway, response[:transaction])
31
+ rescue NotFoundError
32
+ raise NotFoundError, "transaction with id #{id.inspect} not found"
33
+ end
34
+
35
+ def refund(transaction_id, amount = nil)
36
+ response = @config.http.post "/transactions/#{transaction_id}/refund", :transaction => {:amount => amount}
37
+ if response[:transaction]
38
+ SuccessfulResult.new(:transaction => Transaction._new(@gateway, response[:transaction]))
39
+ elsif response[:api_error_response]
40
+ ErrorResult.new(@gateway, response[:api_error_response])
41
+ else
42
+ raise UnexpectedError, "expected :transaction or :api_error_response"
43
+ end
44
+ end
45
+
46
+ def retry_subscription_charge(subscription_id, amount=nil)
47
+ attributes = {
48
+ :amount => amount,
49
+ :subscription_id => subscription_id,
50
+ :type => Transaction::Type::Sale
51
+ }
52
+ _do_create "/transactions", :transaction => attributes
53
+ end
54
+
55
+ def sale(attributes)
56
+ create(attributes.merge(:type => 'sale'))
57
+ end
58
+
59
+ def search(&block)
60
+ search = TransactionSearch.new
61
+ block.call(search) if block
62
+
63
+ response = @config.http.post "/transactions/advanced_search_ids", {:search => search.to_hash}
64
+ ResourceCollection.new(response) { |ids| _fetch_transactions(search, ids) }
65
+ end
66
+
67
+ def submit_for_settlement(transaction_id, amount = nil)
68
+ raise ArgumentError, "transaction_id is invalid" unless transaction_id =~ /\A[0-9a-z]+\z/
69
+ response = @config.http.put "/transactions/#{transaction_id}/submit_for_settlement", :transaction => {:amount => amount}
70
+ if response[:transaction]
71
+ SuccessfulResult.new(:transaction => Transaction._new(@gateway, response[:transaction]))
72
+ elsif response[:api_error_response]
73
+ ErrorResult.new(@gateway, response[:api_error_response])
74
+ else
75
+ raise UnexpectedError, "expected :transaction or :response"
76
+ end
77
+ end
78
+
79
+ def void(transaction_id)
80
+ response = @config.http.put "/transactions/#{transaction_id}/void"
81
+ if response[:transaction]
82
+ SuccessfulResult.new(:transaction => Transaction._new(@gateway, response[:transaction]))
83
+ elsif response[:api_error_response]
84
+ ErrorResult.new(@gateway, response[:api_error_response])
85
+ else
86
+ raise UnexpectedError, "expected :transaction or :api_error_response"
87
+ end
88
+ end
89
+
90
+ def self._create_signature # :nodoc:
91
+ [
92
+ :amount, :customer_id, :merchant_account_id, :order_id, :payment_method_token, :type,
93
+ {:credit_card => [:token, :cardholder_name, :cvv, :expiration_date, :expiration_month, :expiration_year, :number]},
94
+ {:customer => [:id, :company, :email, :fax, :first_name, :last_name, :phone, :website]},
95
+ {
96
+ :billing => AddressGateway._shared_signature
97
+ },
98
+ {
99
+ :shipping => AddressGateway._shared_signature
100
+ },
101
+ {:options => [:store_in_vault, :submit_for_settlement, :add_billing_address_to_payment_method, :store_shipping_address_in_vault]},
102
+ {:custom_fields => :_any_key_}
103
+ ]
104
+ end
105
+
106
+ def _do_create(url, params=nil) # :nodoc:
107
+ response = @config.http.post url, params
108
+ if response[:transaction]
109
+ SuccessfulResult.new(:transaction => Transaction._new(@gateway, response[:transaction]))
110
+ elsif response[:api_error_response]
111
+ ErrorResult.new(@gateway, response[:api_error_response])
112
+ else
113
+ raise UnexpectedError, "expected :transaction or :api_error_response"
114
+ end
115
+ end
116
+
117
+ def _fetch_transactions(search, ids) # :nodoc:
118
+ search.ids.in ids
119
+ response = @config.http.post "/transactions/advanced_search", {:search => search.to_hash}
120
+ attributes = response[:credit_card_transactions]
121
+ Util.extract_attribute_as_array(attributes, :transaction).map { |attrs| Transaction._new(@gateway, attrs) }
122
+ end
123
+ end
124
+ end
@@ -1,6 +1,6 @@
1
1
  module Braintree
2
- class TransactionSearch < AdvancedSearch
3
- search_fields(
2
+ class TransactionSearch < AdvancedSearch # :nodoc:
3
+ text_fields(
4
4
  :billing_company,
5
5
  :billing_country_name,
6
6
  :billing_extended_address,
@@ -24,6 +24,7 @@ module Braintree
24
24
  :order_id,
25
25
  :payment_method_token,
26
26
  :processor_authorization_code,
27
+ :settlement_batch_id,
27
28
  :shipping_company,
28
29
  :shipping_country_name,
29
30
  :shipping_extended_address,
@@ -59,6 +60,7 @@ module Braintree
59
60
 
60
61
  key_value_fields :refund
61
62
 
62
- range_fields :amount, :created_at
63
+ range_fields :amount, :created_at, :authorized_at, :failed_at, :gateway_rejected_at, :processor_declined_at,
64
+ :settled_at, :submitted_for_settlement_at, :voided_at
63
65
  end
64
66
  end
@@ -1,32 +1,12 @@
1
1
  module Braintree
2
- # The TransparentRedirect module provides methods to build the tr_data param
3
- # that must be submitted when using the transparent redirect API. For more information
4
- # about transparent redirect, see (TODO).
5
- #
6
- # You must provide a redirect_url that the gateway will redirect the user to when the
7
- # action is complete.
8
- #
9
- # tr_data = Braintree::TransparentRedirect.create_customer_data(
10
- # :redirect_url => "http://example.com/redirect_back_to_merchant_site
11
- # )
12
- #
13
- # In addition to the redirect_url, any data that needs to be protected from user tampering
14
- # should be included in the tr_data. For example, to prevent the user from tampering with the transaction
15
- # amount, include the amount in the tr_data.
16
- #
17
- # tr_data = Braintree::TransparentRedirect.transaction_data(
18
- # :redirect_url => "http://example.com/complete_transaction",
19
- # :transaction => {:amount => "100.00"}
20
- # )
2
+ # See:
3
+ # * http://www.braintreepaymentsolutions.com/docs/ruby/transactions/create_tr
4
+ # * http://www.braintreepaymentsolutions.com/docs/ruby/customers/create_tr
5
+ # * http://www.braintreepaymentsolutions.com/docs/ruby/customers/update_tr
6
+ # * http://www.braintreepaymentsolutions.com/docs/ruby/credit_cards/create_tr
7
+ # * http://www.braintreepaymentsolutions.com/docs/ruby/credit_cards/update_tr
21
8
  module TransparentRedirect
22
- TransparentRedirectKeys = [:redirect_url] # :nodoc:
23
- CreateCustomerSignature = TransparentRedirectKeys + [{:customer => Customer._create_signature}] # :nodoc:
24
- UpdateCustomerSignature = TransparentRedirectKeys + [:customer_id, {:customer => Customer._update_signature}] # :nodoc:
25
- TransactionSignature = TransparentRedirectKeys + [{:transaction => Transaction._create_signature}] # :nodoc:
26
- CreateCreditCardSignature = TransparentRedirectKeys + [{:credit_card => CreditCard._create_signature}] # :nodoc:
27
- UpdateCreditCardSignature = TransparentRedirectKeys + [:payment_method_token, {:credit_card => CreditCard._update_signature}] # :nodoc:
28
-
29
- module Kind
9
+ module Kind # :nodoc:
30
10
  CreateCustomer = "create_customer"
31
11
  UpdateCustomer = "update_customer"
32
12
  CreatePaymentMethod = "create_payment_method"
@@ -35,110 +15,37 @@ module Braintree
35
15
  end
36
16
 
37
17
  def self.confirm(query_string)
38
- params = TransparentRedirect.parse_and_validate_query_string query_string
39
- confirmation_klass = {
40
- Kind::CreateCustomer => Braintree::Customer,
41
- Kind::UpdateCustomer => Braintree::Customer,
42
- Kind::CreatePaymentMethod => Braintree::CreditCard,
43
- Kind::UpdatePaymentMethod => Braintree::CreditCard,
44
- Kind::CreateTransaction => Braintree::Transaction
45
- }[params[:kind]]
46
-
47
- confirmation_klass._do_create("/transparent_redirect_requests/#{params[:id]}/confirm")
18
+ Configuration.gateway.transparent_redirect.confirm(query_string)
48
19
  end
49
20
 
50
- # Returns the tr_data string for creating a credit card.
21
+ # See http://www.braintreepaymentsolutions.com/docs/ruby/credit_cards/create_tr
51
22
  def self.create_credit_card_data(params)
52
- Util.verify_keys(CreateCreditCardSignature, params)
53
- params[:kind] = Kind::CreatePaymentMethod
54
- _data(params)
23
+ Configuration.gateway.transparent_redirect.create_credit_card_data(params)
55
24
  end
56
25
 
57
- # Returns the tr_data string for creating a customer.
26
+ # See http://www.braintreepaymentsolutions.com/docs/ruby/customers/create_tr
58
27
  def self.create_customer_data(params)
59
- Util.verify_keys(CreateCustomerSignature, params)
60
- params[:kind] = Kind::CreateCustomer
61
- _data(params)
62
- end
63
-
64
- def self.parse_and_validate_query_string(query_string) # :nodoc:
65
- params = Util.symbolize_keys(Util.parse_query_string(query_string))
66
- query_string_without_hash = query_string[/(.*)&hash=.*/, 1]
67
-
68
- if params[:http_status] == nil
69
- raise UnexpectedError, "expected query string to have an http_status param"
70
- elsif params[:http_status] != '200'
71
- Util.raise_exception_for_status_code(params[:http_status], params[:bt_message])
72
- end
73
-
74
- if _hash(query_string_without_hash) == params[:hash]
75
- params
76
- else
77
- raise ForgedQueryString
78
- end
28
+ Configuration.gateway.transparent_redirect.create_customer_data(params)
79
29
  end
80
30
 
81
- # Returns the tr_data string for creating a transaction.
31
+ # See http://www.braintreepaymentsolutions.com/docs/ruby/transactions/create_tr
82
32
  def self.transaction_data(params)
83
- Util.verify_keys(TransactionSignature, params)
84
- params[:kind] = Kind::CreateTransaction
85
- transaction_type = params[:transaction] && params[:transaction][:type]
86
- unless %w[sale credit].include?(transaction_type)
87
- raise ArgumentError, "expected transaction[type] of sale or credit, was: #{transaction_type.inspect}"
88
- end
89
- _data(params)
33
+ Configuration.gateway.transparent_redirect.transaction_data(params)
90
34
  end
91
35
 
92
- # Returns the tr_data string for updating a credit card.
93
- # The payment_method_token of the credit card to update is required.
94
- #
95
- # tr_data = Braintree::TransparentRedirect.update_credit_card_data(
96
- # :redirect_url => "http://example.com/redirect_here",
97
- # :payment_method_token => "token123"
98
- # )
36
+ # See http://www.braintreepaymentsolutions.com/docs/ruby/credit_cards/update_tr
99
37
  def self.update_credit_card_data(params)
100
- Util.verify_keys(UpdateCreditCardSignature, params)
101
- unless params[:payment_method_token]
102
- raise ArgumentError, "expected params to contain :payment_method_token of payment method to update"
103
- end
104
- params[:kind] = Kind::UpdatePaymentMethod
105
- _data(params)
38
+ Configuration.gateway.transparent_redirect.update_credit_card_data(params)
106
39
  end
107
40
 
108
- # Returns the tr_data string for updating a customer.
109
- # The customer_id of the customer to update is required.
110
- #
111
- # tr_data = Braintree::TransparentRedirect.update_customer_data(
112
- # :redirect_url => "http://example.com/redirect_here",
113
- # :customer_id => "customer123"
114
- # )
41
+ # See http://www.braintreepaymentsolutions.com/docs/ruby/customers/update_tr
115
42
  def self.update_customer_data(params)
116
- Util.verify_keys(UpdateCustomerSignature, params)
117
- unless params[:customer_id]
118
- raise ArgumentError, "expected params to contain :customer_id of customer to update"
119
- end
120
- params[:kind] = Kind::UpdateCustomer
121
- _data(params)
43
+ Configuration.gateway.transparent_redirect.update_customer_data(params)
122
44
  end
123
45
 
124
46
  # Returns the URL to which Transparent Redirect Requests should be posted
125
47
  def self.url
126
- "#{Braintree::Configuration.base_merchant_url}/transparent_redirect_requests"
127
- end
128
-
129
- def self._data(params) # :nodoc:
130
- raise ArgumentError, "expected params to contain :redirect_url" unless params[:redirect_url]
131
- tr_data_segment = Util.hash_to_query_string(params.merge(
132
- :api_version => Configuration::API_VERSION,
133
- :time => Time.now.utc.strftime("%Y%m%d%H%M%S"),
134
- :public_key => Configuration.public_key
135
- ))
136
- tr_data_hash = _hash(tr_data_segment)
137
- "#{tr_data_hash}|#{tr_data_segment}"
138
- end
139
-
140
- def self._hash(string) # :nodoc:
141
- ::Braintree::Digest.hexdigest(string)
48
+ Configuration.gateway.transparent_redirect.url
142
49
  end
143
50
  end
144
51
  end
@@ -0,0 +1,105 @@
1
+ module Braintree
2
+ class TransparentRedirectGateway # :nodoc
3
+ TransparentRedirectKeys = [:redirect_url] # :nodoc:
4
+ CreateCustomerSignature = TransparentRedirectKeys + [{:customer => CustomerGateway._create_signature}] # :nodoc:
5
+ UpdateCustomerSignature = TransparentRedirectKeys + [:customer_id, {:customer => CustomerGateway._update_signature}] # :nodoc:
6
+ TransactionSignature = TransparentRedirectKeys + [{:transaction => TransactionGateway._create_signature}] # :nodoc:
7
+ CreateCreditCardSignature = TransparentRedirectKeys + [{:credit_card => CreditCardGateway._create_signature}] # :nodoc:
8
+ UpdateCreditCardSignature = TransparentRedirectKeys + [:payment_method_token, {:credit_card => CreditCardGateway._update_signature}] # :nodoc:
9
+
10
+ def initialize(gateway)
11
+ @gateway = gateway
12
+ @config = gateway.config
13
+ end
14
+
15
+ def confirm(query_string)
16
+ params = @gateway.transparent_redirect.parse_and_validate_query_string query_string
17
+ confirmation_gateway = {
18
+ TransparentRedirect::Kind::CreateCustomer => :customer,
19
+ TransparentRedirect::Kind::UpdateCustomer => :customer,
20
+ TransparentRedirect::Kind::CreatePaymentMethod => :credit_card,
21
+ TransparentRedirect::Kind::UpdatePaymentMethod => :credit_card,
22
+ TransparentRedirect::Kind::CreateTransaction => :transaction
23
+ }[params[:kind]]
24
+
25
+ @gateway.send(confirmation_gateway)._do_create("/transparent_redirect_requests/#{params[:id]}/confirm")
26
+ end
27
+
28
+ def create_credit_card_data(params)
29
+ Util.verify_keys(CreateCreditCardSignature, params)
30
+ params[:kind] = TransparentRedirect::Kind::CreatePaymentMethod
31
+ _data(params)
32
+ end
33
+
34
+ def create_customer_data(params)
35
+ Util.verify_keys(CreateCustomerSignature, params)
36
+ params[:kind] = TransparentRedirect::Kind::CreateCustomer
37
+ _data(params)
38
+ end
39
+
40
+ def parse_and_validate_query_string(query_string) # :nodoc:
41
+ params = Util.symbolize_keys(Util.parse_query_string(query_string))
42
+ query_string_without_hash = query_string[/(.*)&hash=.*/, 1]
43
+
44
+ if params[:http_status] == nil
45
+ raise UnexpectedError, "expected query string to have an http_status param"
46
+ elsif params[:http_status] != '200'
47
+ Util.raise_exception_for_status_code(params[:http_status], params[:bt_message])
48
+ end
49
+
50
+ if _hash(query_string_without_hash) == params[:hash]
51
+ params
52
+ else
53
+ raise ForgedQueryString
54
+ end
55
+ end
56
+
57
+ def transaction_data(params)
58
+ Util.verify_keys(TransactionSignature, params)
59
+ params[:kind] = TransparentRedirect::Kind::CreateTransaction
60
+ transaction_type = params[:transaction] && params[:transaction][:type]
61
+ unless %w[sale credit].include?(transaction_type)
62
+ raise ArgumentError, "expected transaction[type] of sale or credit, was: #{transaction_type.inspect}"
63
+ end
64
+ _data(params)
65
+ end
66
+
67
+ def update_credit_card_data(params)
68
+ Util.verify_keys(UpdateCreditCardSignature, params)
69
+ unless params[:payment_method_token]
70
+ raise ArgumentError, "expected params to contain :payment_method_token of payment method to update"
71
+ end
72
+ params[:kind] = TransparentRedirect::Kind::UpdatePaymentMethod
73
+ _data(params)
74
+ end
75
+
76
+ def update_customer_data(params)
77
+ Util.verify_keys(UpdateCustomerSignature, params)
78
+ unless params[:customer_id]
79
+ raise ArgumentError, "expected params to contain :customer_id of customer to update"
80
+ end
81
+ params[:kind] = TransparentRedirect::Kind::UpdateCustomer
82
+ _data(params)
83
+ end
84
+
85
+ def url
86
+ "#{@config.base_merchant_url}/transparent_redirect_requests"
87
+ end
88
+
89
+ def _data(params) # :nodoc:
90
+ raise ArgumentError, "expected params to contain :redirect_url" unless params[:redirect_url]
91
+ tr_data_segment = Util.hash_to_query_string(params.merge(
92
+ :api_version => @config.api_version,
93
+ :time => Time.now.utc.strftime("%Y%m%d%H%M%S"),
94
+ :public_key => @config.public_key
95
+ ))
96
+ tr_data_hash = _hash(tr_data_segment)
97
+ "#{tr_data_hash}|#{tr_data_segment}"
98
+ end
99
+
100
+ def _hash(string) # :nodoc:
101
+ ::Braintree::Digest.hexdigest(@config.private_key, string)
102
+ end
103
+ end
104
+ end
105
+
@@ -104,6 +104,10 @@ module Braintree
104
104
  full_key = (namespace ? "#{namespace}[#{key}]" : key.to_s)
105
105
  if value.is_a?(Hash)
106
106
  result += _flatten_hash_keys(value, full_key)
107
+ elsif value.is_a?(Array)
108
+ value.each do |item|
109
+ result += _flatten_hash_keys(item, full_key)
110
+ end
107
111
  else
108
112
  result << full_key
109
113
  end