braintree 4.23.0 → 4.34.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.
Files changed (130) hide show
  1. checksums.yaml +4 -4
  2. data/lib/braintree/address_gateway.rb +5 -0
  3. data/lib/braintree/apple_pay_card.rb +6 -0
  4. data/lib/braintree/bank_account_instant_verification_gateway.rb +38 -0
  5. data/lib/braintree/bank_account_instant_verification_jwt.rb +23 -0
  6. data/lib/braintree/bank_account_instant_verification_jwt_request.rb +21 -0
  7. data/lib/braintree/bin_data.rb +8 -2
  8. data/lib/braintree/configuration.rb +1 -1
  9. data/lib/braintree/credit_card.rb +12 -5
  10. data/lib/braintree/credit_card_gateway.rb +1 -0
  11. data/lib/braintree/credit_card_verification_gateway.rb +5 -2
  12. data/lib/braintree/customer_session_gateway.rb +195 -0
  13. data/lib/braintree/dispute.rb +1 -0
  14. data/lib/braintree/error_codes.rb +58 -104
  15. data/lib/braintree/error_result.rb +1 -1
  16. data/lib/braintree/errors.rb +2 -1
  17. data/lib/braintree/gateway.rb +12 -0
  18. data/lib/braintree/google_pay_card.rb +5 -0
  19. data/lib/braintree/graphql/enums/recommendations.rb +9 -0
  20. data/lib/braintree/graphql/enums/recommended_payment_option.rb +10 -0
  21. data/lib/braintree/graphql/inputs/create_customer_session_input.rb +44 -0
  22. data/lib/braintree/graphql/inputs/customer_recommendations_input.rb +40 -0
  23. data/lib/braintree/graphql/inputs/customer_session_input.rb +45 -0
  24. data/lib/braintree/graphql/inputs/monetary_amount_input.rb +28 -0
  25. data/lib/braintree/graphql/inputs/paypal_payee_input.rb +28 -0
  26. data/lib/braintree/graphql/inputs/paypal_purchase_unit_input.rb +31 -0
  27. data/lib/braintree/graphql/inputs/phone_input.rb +34 -0
  28. data/lib/braintree/graphql/inputs/update_customer_session_input.rb +42 -0
  29. data/lib/braintree/graphql/types/customer_recommendations_payload.rb +129 -0
  30. data/lib/braintree/graphql/types/payment_options.rb +35 -0
  31. data/lib/braintree/graphql/types/payment_recommendations.rb +35 -0
  32. data/lib/braintree/graphql/unions/customer_recommendations.rb +47 -0
  33. data/lib/braintree/graphql_client.rb +16 -0
  34. data/lib/braintree/merchant_account.rb +1 -25
  35. data/lib/braintree/merchant_account_gateway.rb +0 -81
  36. data/lib/braintree/meta_checkout_card.rb +11 -6
  37. data/lib/braintree/meta_checkout_token.rb +11 -6
  38. data/lib/braintree/payment_method_gateway.rb +9 -0
  39. data/lib/braintree/paypal_payment_resource.rb +22 -0
  40. data/lib/braintree/paypal_payment_resource_gateway.rb +36 -0
  41. data/lib/braintree/successful_result.rb +3 -0
  42. data/lib/braintree/test/credit_card.rb +5 -0
  43. data/lib/braintree/transaction/apple_pay_details.rb +7 -0
  44. data/lib/braintree/transaction/credit_card_details.rb +22 -10
  45. data/lib/braintree/transaction/google_pay_details.rb +16 -10
  46. data/lib/braintree/transaction/meta_checkout_card_details.rb +11 -2
  47. data/lib/braintree/transaction/meta_checkout_token_details.rb +13 -3
  48. data/lib/braintree/transaction/paypal_details.rb +2 -0
  49. data/lib/braintree/transaction/visa_checkout_card_details.rb +10 -2
  50. data/lib/braintree/transaction.rb +3 -26
  51. data/lib/braintree/transaction_gateway.rb +52 -31
  52. data/lib/braintree/us_bank_account_verification.rb +3 -1
  53. data/lib/braintree/version.rb +1 -1
  54. data/lib/braintree/visa_checkout_card.rb +10 -5
  55. data/lib/braintree/webhook_notification.rb +1 -4
  56. data/lib/braintree/webhook_testing_gateway.rb +16 -33
  57. data/lib/braintree/xml/{libxml.rb → nokogiri.rb} +18 -16
  58. data/lib/braintree/xml/parser.rb +4 -4
  59. data/lib/braintree.rb +21 -4
  60. data/spec/integration/braintree/advanced_search_spec.rb +7 -4
  61. data/spec/integration/braintree/apple_pay_spec.rb +6 -7
  62. data/spec/integration/braintree/bank_account_instant_verification_spec.rb +191 -0
  63. data/spec/integration/braintree/client_api/spec_helper.rb +81 -0
  64. data/spec/integration/braintree/credit_card_spec.rb +115 -1
  65. data/spec/integration/braintree/credit_card_verification_spec.rb +149 -0
  66. data/spec/integration/braintree/customer_session_spec.rb +154 -0
  67. data/spec/integration/braintree/customer_spec.rb +94 -10
  68. data/spec/integration/braintree/disbursement_spec.rb +1 -1
  69. data/spec/integration/braintree/dispute_spec.rb +13 -2
  70. data/spec/integration/braintree/merchant_account_spec.rb +19 -359
  71. data/spec/integration/braintree/merchant_spec.rb +4 -4
  72. data/spec/integration/braintree/payment_method_nonce_spec.rb +55 -0
  73. data/spec/integration/braintree/payment_method_spec.rb +57 -7
  74. data/spec/integration/braintree/payment_method_us_bank_account_spec.rb +0 -3
  75. data/spec/integration/braintree/paypal_payment_resource_spec.rb +141 -0
  76. data/spec/integration/braintree/transaction_payment_facilitator_spec.rb +119 -0
  77. data/spec/integration/braintree/transaction_search_spec.rb +38 -33
  78. data/spec/integration/braintree/transaction_spec.rb +266 -350
  79. data/spec/integration/braintree/transaction_transfer_type_spec.rb +325 -0
  80. data/spec/integration/braintree/transaction_us_bank_account_spec.rb +0 -1
  81. data/spec/integration/braintree/us_bank_account_spec.rb +0 -3
  82. data/spec/integration/braintree/us_bank_account_verification_spec.rb +1 -1
  83. data/spec/spec_helper.rb +46 -12
  84. data/spec/unit/braintree/apple_pay_card_spec.rb +7 -0
  85. data/spec/unit/braintree/bank_account_instant_verification_gateway_spec.rb +98 -0
  86. data/spec/unit/braintree/bank_account_instant_verification_jwt_request_spec.rb +71 -0
  87. data/spec/unit/braintree/client_token_spec.rb +11 -0
  88. data/spec/unit/braintree/configuration_spec.rb +1 -1
  89. data/spec/unit/braintree/credit_card_spec.rb +27 -2
  90. data/spec/unit/braintree/credit_card_verification_gateway_spec.rb +1 -0
  91. data/spec/unit/braintree/credit_card_verification_spec.rb +17 -0
  92. data/spec/unit/braintree/customer_session_gateway_spec.rb +122 -0
  93. data/spec/unit/braintree/customer_spec.rb +2 -1
  94. data/spec/unit/braintree/dispute_spec.rb +6 -0
  95. data/spec/unit/braintree/error_result_spec.rb +28 -0
  96. data/spec/unit/braintree/google_pay_card_spec.rb +28 -0
  97. data/spec/unit/braintree/graphql/create_customer_session_input_spec.rb +102 -0
  98. data/spec/unit/braintree/graphql/customer_recommendations_input_spec.rb +80 -0
  99. data/spec/unit/braintree/graphql/customer_recommendations_spec.rb +40 -0
  100. data/spec/unit/braintree/graphql/customer_session_input_spec.rb +81 -0
  101. data/spec/unit/braintree/graphql/phone_input_spec.rb +51 -0
  102. data/spec/unit/braintree/graphql/update_customer_session_input_spec.rb +107 -0
  103. data/spec/unit/braintree/graphql_client_spec.rb +37 -0
  104. data/spec/unit/braintree/meta_checkout_card_spec.rb +61 -51
  105. data/spec/unit/braintree/meta_checkout_token_spec.rb +7 -1
  106. data/spec/unit/braintree/payment_method_nonce_spec.rb +11 -1
  107. data/spec/unit/braintree/payment_method_spec.rb +12 -0
  108. data/spec/unit/braintree/paypal_payment_resource_spec.rb +125 -0
  109. data/spec/unit/braintree/transaction/apple_pay_details_spec.rb +37 -0
  110. data/spec/unit/braintree/transaction/credit_card_details_spec.rb +15 -1
  111. data/spec/unit/braintree/transaction/google_pay_details_spec.rb +37 -0
  112. data/spec/unit/braintree/transaction/meta_checkout_card_details_spec.rb +28 -0
  113. data/spec/unit/braintree/transaction/meta_checkout_token_details_spec.rb +28 -0
  114. data/spec/unit/braintree/transaction/paypal_details_spec.rb +5 -0
  115. data/spec/unit/braintree/transaction/visa_checkout_card_details_spec.rb +28 -0
  116. data/spec/unit/braintree/transaction_ach_mandate_spec.rb +82 -0
  117. data/spec/unit/braintree/transaction_gateway_spec.rb +58 -11
  118. data/spec/unit/braintree/transaction_spec.rb +66 -0
  119. data/spec/unit/braintree/visa_checkout_card_spec.rb +28 -0
  120. data/spec/unit/braintree/webhook_notification_spec.rb +15 -51
  121. data/spec/unit/braintree/xml/{libxml_spec.rb → nokogiri_spec.rb} +7 -7
  122. data/spec/unit/braintree/xml/parser_spec.rb +57 -0
  123. data/spec/unit/credit_card_details_spec.rb +28 -0
  124. metadata +49 -10
  125. data/lib/braintree/merchant_account/business_details.rb +0 -17
  126. data/lib/braintree/merchant_account/funding_details.rb +0 -18
  127. data/lib/braintree/merchant_account/individual_details.rb +0 -20
  128. data/lib/ssl/securetrust_ca.crt +0 -44
  129. data/spec/unit/braintree/disbursement_spec.rb +0 -131
  130. data/spec/unit/braintree/merchant_account_spec.rb +0 -33
@@ -2,24 +2,24 @@
2
2
  # under the MIT license, copyright (c) 2005-2009 David Heinemeier Hansson
3
3
  module Braintree
4
4
  module Xml
5
- module Libxml
6
- LIB_XML_LIMIT = 30000000
5
+ module Nokogiri
6
+ NOKOGIRI_XML_LIMIT = 30000000
7
7
 
8
8
  def self.parse(xml_string)
9
- old_keep_blanks_setting = ::LibXML::XML.default_keep_blanks
10
- ::LibXML::XML.default_keep_blanks = false
11
- root_node = LibXML::XML::Parser.string(xml_string.strip).parse.root
12
- _node_to_hash(root_node)
13
- ensure
14
- ::LibXML::XML.default_keep_blanks = old_keep_blanks_setting
9
+ require "nokogiri" unless defined?(::Nokogiri)
10
+ doc = ::Nokogiri::XML(xml_string.strip)
11
+ _node_to_hash(doc.root)
15
12
  end
16
13
 
17
14
  def self._node_to_hash(node, hash = {})
18
- if node.text?
19
- raise ::LibXML::XML::Error if node.content.length >= LIB_XML_LIMIT
20
- hash[CONTENT_ROOT] = node.content
15
+ sub_hash = node.text? ? hash : _build_sub_hash(hash, node.name)
16
+
17
+ if node.text? || (node.children.size == 1 && node.children.first.text?)
18
+ content = node.text? ? node.content : node.children.first.content
19
+ raise "Content too large" if content.length >= NOKOGIRI_XML_LIMIT
20
+ sub_hash[CONTENT_ROOT] = content
21
+ _attributes_to_hash(node, sub_hash) unless node.text?
21
22
  else
22
- sub_hash = _build_sub_hash(hash, node.name)
23
23
  _attributes_to_hash(node, sub_hash)
24
24
  if _array?(node)
25
25
  _children_array_to_hash(node, sub_hash)
@@ -44,25 +44,27 @@ module Braintree
44
44
  end
45
45
 
46
46
  def self._children_to_hash(node, hash={})
47
- node.each { |child| _node_to_hash(child, hash) }
47
+ node.children.each { |child| _node_to_hash(child, hash) unless child.text? && child.content.strip.empty? }
48
48
  _attributes_to_hash(node, hash)
49
49
  hash
50
50
  end
51
51
 
52
52
  def self._attributes_to_hash(node, hash={})
53
- node.each_attr { |attr| hash[attr.name] = attr.value }
53
+ node.attributes.each { |name, attr| hash[name] = attr.value }
54
54
  hash
55
55
  end
56
56
 
57
57
  def self._children_array_to_hash(node, hash={})
58
- hash[node.child.name] = node.map do |child|
58
+ first_child = node.children.find { |child| !child.text? }
59
+ hash[first_child.name] = node.children.select { |child| !child.text? }.map do |child|
59
60
  _children_to_hash(child, {})
60
61
  end
61
62
  hash
62
63
  end
63
64
 
64
65
  def self._array?(node)
65
- node.child? && node.child.next? && node.child.name == node.child.next.name
66
+ non_text_children = node.children.select { |child| !child.text? }
67
+ non_text_children.size > 1 && non_text_children.first.name == non_text_children[1].name
66
68
  end
67
69
  end
68
70
  end
@@ -18,10 +18,10 @@ module Braintree
18
18
  end
19
19
 
20
20
  def self._determine_parser
21
- # If LibXML is not available, we fall back to REXML
22
- # This allows us to be compatible with JRuby, which LibXML does not support
23
- if defined?(::LibXML::XML) && ::LibXML::XML.respond_to?(:default_keep_blanks=)
24
- ::Braintree::Xml::Libxml
21
+ # If Nokogiri is available, use it for better performance
22
+ # Otherwise fall back to REXML for JRuby compatibility
23
+ if defined?(::Nokogiri)
24
+ ::Braintree::Xml::Nokogiri
25
25
  else
26
26
  ::Braintree::Xml::Rexml
27
27
  end
data/lib/braintree.rb CHANGED
@@ -35,6 +35,9 @@ require "braintree/apple_pay_card"
35
35
  require "braintree/apple_pay_gateway"
36
36
  require "braintree/apple_pay_options"
37
37
  require "braintree/authorization_adjustment"
38
+ require "braintree/bank_account_instant_verification_gateway"
39
+ require "braintree/bank_account_instant_verification_jwt"
40
+ require "braintree/bank_account_instant_verification_jwt_request"
38
41
  require "braintree/bin_data"
39
42
  require "braintree/client_token"
40
43
  require "braintree/client_token_gateway"
@@ -51,6 +54,7 @@ require "braintree/customer"
51
54
  require "braintree/customer_gateway"
52
55
  require "braintree/granted_payment_instrument_update"
53
56
  require "braintree/customer_search"
57
+ require "braintree/customer_session_gateway"
54
58
  require "braintree/descriptor"
55
59
  require "braintree/digest"
56
60
  require "braintree/discount"
@@ -75,6 +79,20 @@ require "braintree/exchange_rate_quote_input"
75
79
  require "braintree/exchange_rate_quote_response"
76
80
  require "braintree/exchange_rate_quote_request"
77
81
  require "braintree/gateway"
82
+ require "braintree/graphql/enums/recommendations"
83
+ require "braintree/graphql/enums/recommended_payment_option"
84
+ require "braintree/graphql/inputs/create_customer_session_input"
85
+ require "braintree/graphql/inputs/customer_recommendations_input"
86
+ require "braintree/graphql/inputs/customer_session_input"
87
+ require "braintree/graphql/inputs/phone_input"
88
+ require "braintree/graphql/inputs/update_customer_session_input"
89
+ require "braintree/graphql/inputs/paypal_purchase_unit_input"
90
+ require "braintree/graphql/inputs/paypal_payee_input"
91
+ require "braintree/graphql/inputs/monetary_amount_input"
92
+ require "braintree/graphql/types/customer_recommendations_payload"
93
+ require "braintree/graphql/types/payment_options"
94
+ require "braintree/graphql/types/payment_recommendations"
95
+ require "braintree/graphql/unions/customer_recommendations"
78
96
  require "braintree/graphql_client"
79
97
  require "braintree/google_pay_card"
80
98
  require "braintree/local_payment_completed"
@@ -87,9 +105,6 @@ require "braintree/merchant"
87
105
  require "braintree/merchant_gateway"
88
106
  require "braintree/merchant_account"
89
107
  require "braintree/merchant_account_gateway"
90
- require "braintree/merchant_account/individual_details"
91
- require "braintree/merchant_account/business_details"
92
- require "braintree/merchant_account/funding_details"
93
108
  require "braintree/merchant_account/address_details"
94
109
  require "braintree/meta_checkout_card"
95
110
  require "braintree/meta_checkout_token"
@@ -106,6 +121,8 @@ require "braintree/payment_method_nonce_gateway"
106
121
  require "braintree/payment_method_parser"
107
122
  require "braintree/paypal_account"
108
123
  require "braintree/paypal_account_gateway"
124
+ require "braintree/paypal_payment_resource"
125
+ require "braintree/paypal_payment_resource_gateway"
109
126
  require "braintree/plan"
110
127
  require "braintree/plan_gateway"
111
128
  require "braintree/processor_response_types"
@@ -186,6 +203,6 @@ require "braintree/webhook_testing"
186
203
  require "braintree/webhook_testing_gateway"
187
204
  require "braintree/xml"
188
205
  require "braintree/xml/generator"
189
- require "braintree/xml/libxml"
206
+ require "braintree/xml/nokogiri"
190
207
  require "braintree/xml/rexml"
191
208
  require "braintree/xml/parser"
@@ -33,7 +33,8 @@ describe Braintree::AdvancedSearch do
33
33
  expect(collection).not_to include(subscription2)
34
34
  end
35
35
 
36
- it "is_not" do
36
+ # we are temporarily skipping this test until we have a more stable CI env
37
+ xit "is_not" do
37
38
  id = rand(36**8).to_s(36)
38
39
  subscription1 = Braintree::Subscription.create(
39
40
  :payment_method_token => @credit_card.token,
@@ -80,7 +81,8 @@ describe Braintree::AdvancedSearch do
80
81
  expect(collection).not_to include(subscription2)
81
82
  end
82
83
 
83
- it "ends_with" do
84
+ # we are temporarily skipping this test until we have a more stable CI env
85
+ xit "ends_with" do
84
86
  id = rand(36**8).to_s(36)
85
87
  subscription1 = Braintree::Subscription.create(
86
88
  :payment_method_token => @credit_card.token,
@@ -175,7 +177,8 @@ describe Braintree::AdvancedSearch do
175
177
  expect(collection).not_to include(subscription2)
176
178
  end
177
179
 
178
- it "returns only matching results given an argument list" do
180
+ # ignore until more stable CI
181
+ xit "returns only matching results given an argument list" do
179
182
  subscription1 = Braintree::Subscription.create(
180
183
  :payment_method_token => @credit_card.token,
181
184
  :plan_id => SpecHelper::TriallessPlan[:id],
@@ -262,7 +265,7 @@ describe Braintree::AdvancedSearch do
262
265
 
263
266
  context "multiple_value_or_text_field" do
264
267
  describe "in" do
265
- it "works for the in operator" do
268
+ xit "works for the in operator(temporarily disabling until more stable CI)" do
266
269
  Braintree::Subscription.create(
267
270
  :payment_method_token => @credit_card.token,
268
271
  :plan_id => SpecHelper::TriallessPlan[:id],
@@ -2,20 +2,19 @@ require File.expand_path(File.dirname(__FILE__) + "/../../spec_helper")
2
2
 
3
3
  describe Braintree::ApplePayGateway do
4
4
  before(:each) do
5
- gateway = Braintree::Gateway.new(
5
+ oauth_gateway = Braintree::Gateway.new(
6
6
  :client_id => "client_id$#{Braintree::Configuration.environment}$integration_client_id",
7
7
  :client_secret => "client_secret$#{Braintree::Configuration.environment}$integration_client_secret",
8
8
  :logger => Logger.new("/dev/null"),
9
9
  )
10
10
 
11
- result = gateway.merchant.create(
12
- :email => "name@email.com",
13
- :country_code_alpha3 => "GBR",
14
- :payment_methods => ["credit_card", "paypal"],
15
- )
11
+ access_token = Braintree::OAuthTestHelper.create_token(oauth_gateway, {
12
+ :merchant_public_id => "integration_merchant_id",
13
+ :scope => "read_write"
14
+ }).credentials.access_token
16
15
 
17
16
  @gateway = Braintree::Gateway.new(
18
- :access_token => result.credentials.access_token,
17
+ :access_token => access_token,
19
18
  :logger => Logger.new("/dev/null"),
20
19
  )
21
20
  end
@@ -0,0 +1,191 @@
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::BankAccountInstantVerificationGateway do
5
+ before do
6
+ @gateway = Braintree::Gateway.new(
7
+ :environment => :development,
8
+ :merchant_id => "integration2_merchant_id",
9
+ :public_key => "integration2_public_key",
10
+ :private_key => "integration2_private_key",
11
+ )
12
+
13
+ @us_bank_gateway = Braintree::Gateway.new(
14
+ :environment => :development,
15
+ :merchant_id => "integration_merchant_id",
16
+ :public_key => "integration_public_key",
17
+ :private_key => "integration_private_key",
18
+ )
19
+ end
20
+
21
+ describe "create_jwt" do
22
+ it "creates a jwt with valid request" do
23
+ request = Braintree::BankAccountInstantVerificationJwtRequest.new(
24
+ :business_name => "15Ladders",
25
+ :return_url => "https://example.com/success",
26
+ :cancel_url => "https://example.com/cancel",
27
+ )
28
+
29
+ result = @gateway.bank_account_instant_verification.create_jwt(request)
30
+
31
+ unless result.success?
32
+ puts "DEBUG: Result failed!"
33
+ puts "DEBUG: Errors: #{result.errors.inspect}" if result.respond_to?(:errors)
34
+ end
35
+
36
+ expect(result.success?).to eq(true)
37
+ expect(result.bank_account_instant_verification_jwt).to have_attributes(
38
+ jwt: a_string_matching(/.+/),
39
+ )
40
+ end
41
+
42
+ it "fails with invalid business name" do
43
+ request = Braintree::BankAccountInstantVerificationJwtRequest.new(
44
+ :business_name => "", # Empty business name should cause validation error
45
+ :return_url => "https://example.com/return",
46
+ :cancel_url => "https://example.com/cancel",
47
+ )
48
+
49
+ result = @gateway.bank_account_instant_verification.create_jwt(request)
50
+
51
+ expect(result.success?).to eq(false)
52
+ expect(result.errors).not_to be_nil
53
+ end
54
+
55
+ it "fails with invalid URLs" do
56
+ request = Braintree::BankAccountInstantVerificationJwtRequest.new(
57
+ :business_name => "15Ladders",
58
+ :return_url => "not-a-valid-url",
59
+ :cancel_url => "also-not-valid",
60
+ )
61
+
62
+ result = @us_bank_gateway.bank_account_instant_verification.create_jwt(request)
63
+
64
+ expect(result.success?).to eq(false)
65
+ expect(result.errors).not_to be_nil
66
+ end
67
+ end
68
+
69
+ describe "charge US bank with ACH mandate" do
70
+ it "creates transaction directly with nonce and provides ACH mandate at transaction time (instant verification)" do
71
+ nonce = generate_us_bank_account_nonce_via_open_banking
72
+
73
+ mandate_accepted_at = Time.now - 300 # 5 minutes ago
74
+
75
+ # Create transaction directly with nonce and provide ACH mandate at transaction time (instant verification)
76
+ transaction_request = {
77
+ :amount => "12.34",
78
+ :payment_method_nonce => nonce,
79
+ :merchant_account_id => SpecHelper::UsBankMerchantAccountId, # could it be?
80
+ :us_bank_account => {
81
+ :ach_mandate_text => "I authorize this transaction and future debits",
82
+ :ach_mandate_accepted_at => mandate_accepted_at
83
+ },
84
+ :options => {
85
+ :submit_for_settlement => true
86
+ }
87
+ }
88
+
89
+ transaction_result = @us_bank_gateway.transaction.sale(transaction_request)
90
+
91
+ expect(transaction_result.success?).to eq(true), "Expected transaction success but got failure with validation errors (see console output)"
92
+ transaction = transaction_result.transaction
93
+
94
+ expected_transaction = {
95
+ id: a_string_matching(/.+/),
96
+ amount: BigDecimal("12.34"),
97
+ us_bank_account_details: have_attributes(
98
+ ach_mandate: have_attributes(
99
+ text: "I authorize this transaction and future debits",
100
+ accepted_at: be_a(Time),
101
+ ),
102
+ account_holder_name: "Dan Schulman",
103
+ last_4: "1234",
104
+ routing_number: "021000021",
105
+ account_type: "checking",
106
+ )
107
+ }
108
+
109
+ expect(transaction).to have_attributes(expected_transaction)
110
+ end
111
+ end
112
+
113
+ describe "Open Finance flow with INSTANT_VERIFICATION_ACCOUNT_VALIDATION" do
114
+ it "tokenizes bank account via Open Finance API, vaults with and charges" do
115
+
116
+ nonce = generate_us_bank_account_nonce_via_open_banking
117
+
118
+ customer_result = @us_bank_gateway.customer.create({})
119
+ expect(customer_result.success?).to eq(true)
120
+ customer = customer_result.customer
121
+
122
+ mandate_accepted_at = Time.now - 300
123
+
124
+ payment_method_request = {
125
+ :customer_id => customer.id,
126
+ :payment_method_nonce => nonce,
127
+ :us_bank_account => {
128
+ :ach_mandate_text => "I authorize this transaction and future debits",
129
+ :ach_mandate_accepted_at => mandate_accepted_at
130
+ },
131
+ :options => {
132
+ :verification_merchant_account_id => SpecHelper::UsBankMerchantAccountId,
133
+ :us_bank_account_verification_method => Braintree::UsBankAccountVerification::VerificationMethod::InstantVerificationAccountValidation
134
+ }
135
+ }
136
+
137
+ payment_method_result = @us_bank_gateway.payment_method.create(payment_method_request)
138
+ expect(payment_method_result.success?).to eq(true), "Expected payment method creation success but got failure with validation errors"
139
+
140
+ us_bank_account = payment_method_result.payment_method
141
+
142
+ expected_us_bank_account = {
143
+ verifications: a_collection_containing_exactly(
144
+ have_attributes(
145
+ verification_method: Braintree::UsBankAccountVerification::VerificationMethod::InstantVerificationAccountValidation,
146
+ status: "verified",
147
+ ),
148
+ ),
149
+ ach_mandate: have_attributes(
150
+ text: "I authorize this transaction and future debits",
151
+ accepted_at: be_a(Time),
152
+ )
153
+ }
154
+
155
+ expect(us_bank_account).to have_attributes(expected_us_bank_account)
156
+
157
+ verification = us_bank_account.verifications.first
158
+ expect(verification.verification_method).to eq(Braintree::UsBankAccountVerification::VerificationMethod::InstantVerificationAccountValidation)
159
+
160
+ transaction_request = {
161
+ :amount => "12.34",
162
+ :payment_method_token => us_bank_account.token,
163
+ :merchant_account_id => SpecHelper::UsBankMerchantAccountId,
164
+ :options => {
165
+ :submit_for_settlement => true
166
+ }
167
+ }
168
+
169
+ transaction_result = @us_bank_gateway.transaction.sale(transaction_request)
170
+ expect(transaction_result.success?).to eq(true), "Expected transaction success but got failure"
171
+ transaction = transaction_result.transaction
172
+
173
+ expected_transaction = {
174
+ id: a_string_matching(/.+/),
175
+ amount: BigDecimal("12.34"),
176
+ us_bank_account_details: have_attributes(
177
+ token: us_bank_account.token,
178
+ ach_mandate: have_attributes(
179
+ text: "I authorize this transaction and future debits",
180
+ accepted_at: be_a(Time),
181
+ ),
182
+ last_4: "1234",
183
+ routing_number: "021000021",
184
+ account_type: "checking",
185
+ )
186
+ }
187
+
188
+ expect(transaction).to have_attributes(expected_transaction)
189
+ end
190
+ end
191
+ end
@@ -147,6 +147,87 @@ def generate_invalid_us_bank_account_nonce
147
147
  nonce + "_xxx"
148
148
  end
149
149
 
150
+ def generate_us_bank_account_nonce_via_open_banking
151
+
152
+ config = Braintree::Configuration.new(
153
+ :environment => :development,
154
+ :merchant_id => "integration_merchant_id",
155
+ :public_key => "integration_public_key",
156
+ :private_key => "integration_private_key",
157
+ )
158
+
159
+ request_body = {
160
+ :account_details => {
161
+ :account_number => "567891234",
162
+ :account_type => "CHECKING",
163
+ :classification => "PERSONAL",
164
+ :tokenized_account => true,
165
+ :last_4 => "1234"
166
+ },
167
+ :institution_details => {
168
+ :bank_id => {
169
+ :bank_code => "021000021",
170
+ :country_code => "US"
171
+ }
172
+ },
173
+ :account_holders => [
174
+ {
175
+ :ownership => "PRIMARY",
176
+ :full_name => {
177
+ :name => "Dan Schulman"
178
+ },
179
+ :name => {
180
+ :given_name => "Dan",
181
+ :surname => "Schulman",
182
+ :full_name => "Dan Schulman"
183
+ }
184
+ }
185
+ ]
186
+ }
187
+
188
+ graphql_base_url = config.graphql_base_url
189
+ atmosphere_base_url = graphql_base_url.gsub("/graphql", "")
190
+ url = "#{atmosphere_base_url}/v1/open-finance/tokenize-bank-account-details"
191
+
192
+ uri = URI.parse(url)
193
+ connection = Net::HTTP.new(uri.host, uri.port)
194
+
195
+ if uri.scheme == "https"
196
+ connection.use_ssl = true
197
+ connection.verify_mode = OpenSSL::SSL::VERIFY_PEER
198
+ connection.ca_file = config.ca_file if config.ca_file
199
+ end
200
+
201
+ response = connection.start do |http|
202
+ request = Net::HTTP::Post.new(uri.path)
203
+ request["Content-Type"] = "application/json"
204
+ request["Accept"] = "application/json"
205
+ request["Braintree-Version"] = "2019-01-01"
206
+ request["User-Agent"] = "Braintree Ruby Library #{Braintree::Version::String}"
207
+ request["X-ApiVersion"] = config.api_version
208
+
209
+ # Basic auth like Node.js
210
+ auth_string = "#{config.public_key}:#{config.private_key}"
211
+ request["Authorization"] = "Basic #{Base64.strict_encode64(auth_string)}"
212
+
213
+ request.body = request_body.to_json
214
+
215
+ http.request(request)
216
+ end
217
+
218
+ if response.code.to_i != 200
219
+ raise "HTTP #{response.code}: #{response.body}"
220
+ end
221
+
222
+ result = JSON.parse(response.body)
223
+
224
+ unless result["tenant_token"]
225
+ raise "Open Banking tokenization failed: #{result.inspect}"
226
+ end
227
+
228
+ result["tenant_token"]
229
+ end
230
+
150
231
  def _cosmos_post(token, url, payload)
151
232
  uri = URI::parse(url)
152
233
  connection = Net::HTTP.new(uri.host, uri.port)
@@ -449,6 +449,66 @@ describe Braintree::CreditCard do
449
449
  expect(credit_card.prepaid).to eq(Braintree::CreditCard::Prepaid::Yes)
450
450
  end
451
451
 
452
+ it "sets the prepaid reloadable field if the card is prepaid reloadable" do
453
+ customer = Braintree::Customer.create!
454
+ result = Braintree::CreditCard.create(
455
+ :customer_id => customer.id,
456
+ :number => Braintree::Test::CreditCardNumbers::CardTypeIndicators::PrepaidReloadable,
457
+ :expiration_date => "05/2014",
458
+ :options => {:verify_card => true},
459
+ )
460
+ credit_card = result.credit_card
461
+ expect(credit_card.prepaid_reloadable).to eq(Braintree::CreditCard::PrepaidReloadable::Yes)
462
+ end
463
+
464
+ it "sets the business field if the card is business" do
465
+ customer = Braintree::Customer.create!
466
+ result = Braintree::CreditCard.create(
467
+ :customer_id => customer.id,
468
+ :number => Braintree::Test::CreditCardNumbers::CardTypeIndicators::Business,
469
+ :expiration_date => "05/2014",
470
+ :options => {:verify_card => true},
471
+ )
472
+ credit_card = result.credit_card
473
+ expect(credit_card.business).to eq(Braintree::CreditCard::Business::Yes)
474
+ end
475
+
476
+ it "sets the consumer field if the card is consumer" do
477
+ customer = Braintree::Customer.create!
478
+ result = Braintree::CreditCard.create(
479
+ :customer_id => customer.id,
480
+ :number => Braintree::Test::CreditCardNumbers::CardTypeIndicators::Consumer,
481
+ :expiration_date => "05/2014",
482
+ :options => {:verify_card => true},
483
+ )
484
+ credit_card = result.credit_card
485
+ expect(credit_card.consumer).to eq(Braintree::CreditCard::Consumer::Yes)
486
+ end
487
+
488
+ it "sets the corporate field if the card is corporate" do
489
+ customer = Braintree::Customer.create!
490
+ result = Braintree::CreditCard.create(
491
+ :customer_id => customer.id,
492
+ :number => Braintree::Test::CreditCardNumbers::CardTypeIndicators::Corporate,
493
+ :expiration_date => "05/2014",
494
+ :options => {:verify_card => true},
495
+ )
496
+ credit_card = result.credit_card
497
+ expect(credit_card.corporate).to eq(Braintree::CreditCard::Corporate::Yes)
498
+ end
499
+
500
+ it "sets the purchase field if the card is purchase" do
501
+ customer = Braintree::Customer.create!
502
+ result = Braintree::CreditCard.create(
503
+ :customer_id => customer.id,
504
+ :number => Braintree::Test::CreditCardNumbers::CardTypeIndicators::Purchase,
505
+ :expiration_date => "05/2014",
506
+ :options => {:verify_card => true},
507
+ )
508
+ credit_card = result.credit_card
509
+ expect(credit_card.purchase).to eq(Braintree::CreditCard::Purchase::Yes)
510
+ end
511
+
452
512
  it "sets the healthcare field if the card is healthcare" do
453
513
  customer = Braintree::Customer.create!
454
514
  result = Braintree::CreditCard.create(
@@ -1203,7 +1263,8 @@ describe Braintree::CreditCard do
1203
1263
  end
1204
1264
 
1205
1265
  describe "self.expiring_between" do
1206
- it "finds payment methods expiring between the given dates" do
1266
+ #Disabling this test until we have a more stable CI
1267
+ xit "finds payment methods expiring between the given dates" do
1207
1268
  next_year = Time.now.year + 1
1208
1269
  collection = Braintree::CreditCard.expiring_between(Time.mktime(next_year, 1), Time.mktime(next_year, 12))
1209
1270
  expect(collection.maximum_size).to be > 0
@@ -1437,4 +1498,57 @@ describe Braintree::CreditCard do
1437
1498
  expect(credit_card_vaulted.is_network_tokenized?).to eq(false)
1438
1499
  end
1439
1500
  end
1501
+
1502
+ describe "account information inquiry" do
1503
+ it "includes ani response when account information inquiry is sent in options" do
1504
+ customer = Braintree::Customer.create!
1505
+ result = Braintree::CreditCard.create(
1506
+ :cardholder_name => "John Doe",
1507
+ :customer_id => customer.id,
1508
+ :cvv => "123",
1509
+ :number => Braintree::Test::CreditCardNumbers::Visa,
1510
+ :expiration_date => "05/2027",
1511
+ :billing_address => {
1512
+ :first_name => "John",
1513
+ :last_name => "Doe",
1514
+ },
1515
+ :options => {
1516
+ :account_information_inquiry => "send_data",
1517
+ :verify_card => true,
1518
+ },
1519
+ )
1520
+
1521
+ expect(result).to be_success
1522
+ verification = result.credit_card.verification
1523
+ expect(verification.ani_first_name_response_code).not_to be_nil
1524
+ expect(verification.ani_last_name_response_code).not_to be_nil
1525
+ end
1526
+
1527
+ it "includes ani response after updating the options with account information inquiry" do
1528
+ customer = Braintree::Customer.create!
1529
+ credit_card = Braintree::CreditCard.create!(
1530
+ :cardholder_name => "Original Holder",
1531
+ :customer_id => customer.id,
1532
+ :cvv => "123",
1533
+ :number => Braintree::Test::CreditCardNumbers::Visa,
1534
+ :expiration_date => "05/2027",
1535
+ )
1536
+ updated_result = Braintree::CreditCard.update(credit_card.token,
1537
+ :options => {
1538
+ :verify_card => true,
1539
+ :account_information_inquiry => "send_data",
1540
+ },
1541
+ )
1542
+
1543
+ expect(updated_result).to be_success
1544
+ verification = updated_result.credit_card.verification
1545
+ expect(verification.ani_first_name_response_code).not_to be_nil
1546
+ expect(verification.ani_last_name_response_code).not_to be_nil
1547
+ end
1548
+ end
1440
1549
  end
1550
+
1551
+
1552
+
1553
+
1554
+