braintree 3.2.0 → 4.2.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 (150) hide show
  1. checksums.yaml +4 -4
  2. data/braintree.gemspec +3 -3
  3. data/lib/braintree.rb +6 -1
  4. data/lib/braintree/account_updater_daily_report.rb +1 -1
  5. data/lib/braintree/address.rb +2 -1
  6. data/lib/braintree/apple_pay.rb +1 -1
  7. data/lib/braintree/apple_pay_card.rb +1 -1
  8. data/lib/braintree/apple_pay_options.rb +1 -1
  9. data/lib/braintree/authorization_adjustment.rb +1 -1
  10. data/lib/braintree/client_token.rb +1 -1
  11. data/lib/braintree/configuration.rb +11 -11
  12. data/lib/braintree/connected_merchant_paypal_status_changed.rb +1 -1
  13. data/lib/braintree/connected_merchant_status_transitioned.rb +1 -1
  14. data/lib/braintree/credit_card.rb +2 -2
  15. data/lib/braintree/credit_card_gateway.rb +14 -4
  16. data/lib/braintree/credit_card_verification.rb +5 -5
  17. data/lib/braintree/credit_card_verification_search.rb +1 -1
  18. data/lib/braintree/customer.rb +6 -4
  19. data/lib/braintree/customer_gateway.rb +2 -0
  20. data/lib/braintree/customer_search.rb +1 -1
  21. data/lib/braintree/disbursement.rb +1 -1
  22. data/lib/braintree/dispute.rb +15 -1
  23. data/lib/braintree/dispute/paypal_message.rb +15 -0
  24. data/lib/braintree/dispute_gateway.rb +2 -2
  25. data/lib/braintree/dispute_search.rb +3 -2
  26. data/lib/braintree/document_upload.rb +1 -1
  27. data/lib/braintree/error_codes.rb +10 -6
  28. data/lib/braintree/google_pay_card.rb +1 -1
  29. data/lib/braintree/granted_payment_instrument_update.rb +1 -1
  30. data/lib/braintree/graphql_client.rb +7 -7
  31. data/lib/braintree/http.rb +3 -3
  32. data/lib/braintree/local_payment_completed.rb +1 -1
  33. data/lib/braintree/local_payment_reversed.rb +19 -0
  34. data/lib/braintree/merchant.rb +1 -1
  35. data/lib/braintree/merchant_account.rb +1 -1
  36. data/lib/braintree/merchant_account_gateway.rb +1 -1
  37. data/lib/braintree/merchant_gateway.rb +1 -1
  38. data/lib/braintree/modification.rb +1 -1
  39. data/lib/braintree/oauth_credentials.rb +1 -1
  40. data/lib/braintree/oauth_gateway.rb +5 -5
  41. data/lib/braintree/payment_instrument_type.rb +10 -10
  42. data/lib/braintree/payment_method_gateway.rb +10 -7
  43. data/lib/braintree/payment_method_nonce.rb +7 -4
  44. data/lib/braintree/payment_method_nonce_details.rb +37 -0
  45. data/lib/braintree/payment_method_nonce_details_payer_info.rb +32 -0
  46. data/lib/braintree/payment_method_nonce_gateway.rb +1 -1
  47. data/lib/braintree/plan.rb +1 -1
  48. data/lib/braintree/processor_response_types.rb +3 -3
  49. data/lib/braintree/revoked_payment_method_metadata.rb +1 -1
  50. data/lib/braintree/risk_data.rb +3 -1
  51. data/lib/braintree/samsung_pay_card.rb +1 -1
  52. data/lib/braintree/settlement_batch_summary.rb +2 -2
  53. data/lib/braintree/subscription.rb +6 -6
  54. data/lib/braintree/three_d_secure_info.rb +22 -12
  55. data/lib/braintree/transaction.rb +32 -24
  56. data/lib/braintree/transaction_gateway.rb +24 -5
  57. data/lib/braintree/transaction_line_item.rb +1 -1
  58. data/lib/braintree/transaction_search.rb +3 -1
  59. data/lib/braintree/unknown_payment_method.rb +1 -1
  60. data/lib/braintree/us_bank_account.rb +3 -3
  61. data/lib/braintree/us_bank_account_verification.rb +1 -1
  62. data/lib/braintree/us_bank_account_verification_gateway.rb +1 -1
  63. data/lib/braintree/util.rb +4 -4
  64. data/lib/braintree/venmo_account.rb +1 -1
  65. data/lib/braintree/version.rb +1 -1
  66. data/lib/braintree/visa_checkout_card.rb +2 -2
  67. data/lib/braintree/webhook_notification.rb +30 -20
  68. data/lib/braintree/webhook_notification_gateway.rb +5 -5
  69. data/lib/braintree/webhook_testing_gateway.rb +30 -0
  70. data/lib/braintree/xml/generator.rb +5 -4
  71. data/lib/braintree/xml/libxml.rb +0 -1
  72. data/lib/braintree/xml/parser.rb +22 -12
  73. data/lib/braintree/xml/rexml.rb +70 -0
  74. data/spec/integration/braintree/add_on_spec.rb +1 -1
  75. data/spec/integration/braintree/address_spec.rb +28 -24
  76. data/spec/integration/braintree/advanced_search_spec.rb +45 -45
  77. data/spec/integration/braintree/apple_pay_spec.rb +3 -3
  78. data/spec/integration/braintree/braintree_gateway_spec.rb +2 -1
  79. data/spec/integration/braintree/client_api/client_token_spec.rb +14 -14
  80. data/spec/integration/braintree/client_api/spec_helper.rb +5 -5
  81. data/spec/integration/braintree/credit_card_spec.rb +213 -122
  82. data/spec/integration/braintree/credit_card_verification_search_spec.rb +2 -2
  83. data/spec/integration/braintree/credit_card_verification_spec.rb +1 -1
  84. data/spec/integration/braintree/customer_search_spec.rb +8 -8
  85. data/spec/integration/braintree/customer_spec.rb +282 -165
  86. data/spec/integration/braintree/dispute_search_spec.rb +28 -3
  87. data/spec/integration/braintree/dispute_spec.rb +6 -6
  88. data/spec/integration/braintree/error_codes_spec.rb +1 -1
  89. data/spec/integration/braintree/http_spec.rb +2 -2
  90. data/spec/integration/braintree/merchant_account_spec.rb +25 -26
  91. data/spec/integration/braintree/merchant_spec.rb +14 -14
  92. data/spec/integration/braintree/oauth_spec.rb +11 -11
  93. data/spec/integration/braintree/payment_method_nonce_spec.rb +28 -35
  94. data/spec/integration/braintree/payment_method_spec.rb +269 -165
  95. data/spec/integration/braintree/payment_method_us_bank_account_spec.rb +9 -9
  96. data/spec/integration/braintree/paypal_account_spec.rb +28 -28
  97. data/spec/integration/braintree/samsung_pay_card_spec.rb +9 -9
  98. data/spec/integration/braintree/settlement_batch_summary_spec.rb +8 -8
  99. data/spec/integration/braintree/subscription_spec.rb +133 -133
  100. data/spec/integration/braintree/test/transaction_amounts_spec.rb +2 -2
  101. data/spec/integration/braintree/test_transaction_spec.rb +10 -10
  102. data/spec/integration/braintree/transaction_search_spec.rb +93 -67
  103. data/spec/integration/braintree/transaction_spec.rb +574 -360
  104. data/spec/integration/braintree/transaction_us_bank_account_spec.rb +20 -20
  105. data/spec/integration/braintree/us_bank_account_spec.rb +6 -6
  106. data/spec/integration/braintree/us_bank_account_verification_search_spec.rb +7 -7
  107. data/spec/integration/braintree/us_bank_account_verification_spec.rb +8 -8
  108. data/spec/integration/braintree/visa_checkout_card_spec.rb +5 -5
  109. data/spec/integration/spec_helper.rb +9 -3
  110. data/spec/oauth_test_helper.rb +1 -1
  111. data/spec/script/httpsd.rb +6 -6
  112. data/spec/spec_helper.rb +6 -3
  113. data/spec/unit/braintree/address_spec.rb +1 -1
  114. data/spec/unit/braintree/apple_pay_card_spec.rb +1 -1
  115. data/spec/unit/braintree/client_token_spec.rb +2 -2
  116. data/spec/unit/braintree/configuration_spec.rb +42 -42
  117. data/spec/unit/braintree/credit_card_spec.rb +13 -13
  118. data/spec/unit/braintree/credit_card_verification_search_spec.rb +1 -1
  119. data/spec/unit/braintree/credit_card_verification_spec.rb +8 -4
  120. data/spec/unit/braintree/customer_spec.rb +20 -10
  121. data/spec/unit/braintree/disbursement_spec.rb +7 -7
  122. data/spec/unit/braintree/dispute_search_spec.rb +1 -0
  123. data/spec/unit/braintree/dispute_spec.rb +34 -9
  124. data/spec/unit/braintree/error_result_spec.rb +5 -5
  125. data/spec/unit/braintree/errors_spec.rb +8 -8
  126. data/spec/unit/braintree/http_spec.rb +5 -5
  127. data/spec/unit/braintree/merchant_account_spec.rb +1 -1
  128. data/spec/unit/braintree/payment_method_nonce_details_payer_info_spec.rb +31 -0
  129. data/spec/unit/braintree/payment_method_nonce_details_spec.rb +43 -0
  130. data/spec/unit/braintree/payment_method_nonce_spec.rb +40 -0
  131. data/spec/unit/braintree/payment_method_spec.rb +1 -1
  132. data/spec/unit/braintree/paypal_account_spec.rb +2 -2
  133. data/spec/unit/braintree/resource_collection_spec.rb +9 -9
  134. data/spec/unit/braintree/risk_data_spec.rb +9 -5
  135. data/spec/unit/braintree/subscription_search_spec.rb +1 -1
  136. data/spec/unit/braintree/successful_result_spec.rb +1 -1
  137. data/spec/unit/braintree/three_d_secure_info_spec.rb +32 -14
  138. data/spec/unit/braintree/transaction/credit_card_details_spec.rb +3 -3
  139. data/spec/unit/braintree/transaction/customer_details_spec.rb +1 -1
  140. data/spec/unit/braintree/transaction/deposit_details_spec.rb +2 -2
  141. data/spec/unit/braintree/transaction/paypal_details_spec.rb +1 -1
  142. data/spec/unit/braintree/transaction_search_spec.rb +12 -12
  143. data/spec/unit/braintree/transaction_spec.rb +25 -17
  144. data/spec/unit/braintree/util_spec.rb +18 -18
  145. data/spec/unit/braintree/validation_error_collection_spec.rb +36 -36
  146. data/spec/unit/braintree/webhook_notification_spec.rb +88 -56
  147. data/spec/unit/braintree/xml/rexml_spec.rb +51 -0
  148. data/spec/unit/braintree/xml_spec.rb +31 -31
  149. metadata +16 -8
  150. data/lib/braintree/settlement_batch.rb +0 -0
@@ -63,7 +63,7 @@ module Braintree
63
63
  end
64
64
 
65
65
  def self._new(*args) # :nodoc:
66
- self.new *args
66
+ self.new(*args)
67
67
  end
68
68
 
69
69
  def self.confirm_micro_transfer_amounts(*args)
@@ -18,7 +18,7 @@ module Braintree
18
18
  ErrorResult.new(@gateway, response[:api_error_response])
19
19
  else
20
20
  SuccessfulResult.new(
21
- :us_bank_account_verification => UsBankAccountVerification._new(response[:us_bank_account_verification])
21
+ :us_bank_account_verification => UsBankAccountVerification._new(response[:us_bank_account_verification]),
22
22
  )
23
23
  end
24
24
  rescue NotFoundError
@@ -14,13 +14,13 @@ module Braintree
14
14
  else
15
15
  url_encode(full_key) + "=" + url_encode(value)
16
16
  end
17
- end.sort * '&'
17
+ end.sort * "&"
18
18
  end
19
19
 
20
20
  def self.parse_query_string(qs)
21
- qs.split('&').inject({}) do |result, couplet|
22
- pair = couplet.split('=')
23
- result[CGI.unescape(pair[0]).to_sym] = CGI.unescape(pair[1] || '')
21
+ qs.split("&").inject({}) do |result, couplet|
22
+ pair = couplet.split("=")
23
+ result[CGI.unescape(pair[0]).to_sym] = CGI.unescape(pair[1] || "")
24
24
  result
25
25
  end
26
26
  end
@@ -28,7 +28,7 @@ module Braintree
28
28
  end
29
29
 
30
30
  def self._new(*args) # :nodoc:
31
- self.new *args
31
+ self.new(*args)
32
32
  end
33
33
  end
34
34
  end
@@ -1,6 +1,6 @@
1
1
  module Braintree
2
2
  module Version
3
- Major = 3
3
+ Major = 4
4
4
  Minor = 2
5
5
  Tiny = 0
6
6
 
@@ -39,7 +39,7 @@ module Braintree
39
39
  end
40
40
 
41
41
  def _most_recent_verification(attributes)
42
- verification = (attributes[:verifications] || []).sort_by{ |verification| verification[:created_at] }.reverse.first
42
+ verification = (attributes[:verifications] || []).sort_by { |verification| verification[:created_at] }.reverse.first
43
43
  CreditCardVerification._new(verification) if verification
44
44
  end
45
45
 
@@ -84,7 +84,7 @@ module Braintree
84
84
  end
85
85
 
86
86
  def self._new(*args) # :nodoc:
87
- self.new *args
87
+ self.new(*args)
88
88
  end
89
89
  end
90
90
  end
@@ -1,12 +1,17 @@
1
- require 'ostruct'
1
+ require "ostruct"
2
2
 
3
3
  module Braintree
4
4
  class WebhookNotification
5
5
  include BaseModule
6
6
 
7
7
  module Kind
8
+
9
+ AccountUpdaterDailyReport = "account_updater_daily_report"
10
+
8
11
  Check = "check"
9
12
 
13
+ ConnectedMerchantPayPalStatusChanged = "connected_merchant_paypal_status_changed"
14
+ ConnectedMerchantStatusTransitioned = "connected_merchant_status_transitioned"
10
15
  Disbursement = "disbursement"
11
16
  DisbursementException = "disbursement_exception"
12
17
 
@@ -17,6 +22,24 @@ module Braintree
17
22
  DisputeDisputed = "dispute_disputed"
18
23
  DisputeExpired = "dispute_expired"
19
24
 
25
+ GrantedPaymentInstrumentRevoked = "granted_payment_instrument_revoked"
26
+
27
+ GrantorUpdatedGrantedPaymentMethod = "grantor_updated_granted_payment_method"
28
+ GrantedPaymentMethodRevoked = "granted_payment_method_revoked"
29
+
30
+ LocalPaymentCompleted = "local_payment_completed"
31
+ LocalPaymentReversed = "local_payment_reversed"
32
+
33
+ OAuthAccessRevoked = "oauth_access_revoked"
34
+
35
+ PartnerMerchantConnected = "partner_merchant_connected"
36
+ PartnerMerchantDisconnected = "partner_merchant_disconnected"
37
+ PartnerMerchantDeclined = "partner_merchant_declined"
38
+
39
+ PaymentMethodRevokedByCustomer = "payment_method_revoked_by_customer"
40
+
41
+ RecipientUpdatedGrantedPaymentMethod = "recipient_updated_granted_payment_method"
42
+
20
43
  SubscriptionCanceled = "subscription_canceled"
21
44
  SubscriptionChargedSuccessfully = "subscription_charged_successfully"
22
45
  SubscriptionChargedUnsuccessfully = "subscription_charged_unsuccessfully"
@@ -27,25 +50,10 @@ module Braintree
27
50
 
28
51
  SubMerchantAccountApproved = "sub_merchant_account_approved"
29
52
  SubMerchantAccountDeclined = "sub_merchant_account_declined"
53
+
30
54
  TransactionDisbursed = "transaction_disbursed"
31
55
  TransactionSettlementDeclined = "transaction_settlement_declined"
32
56
  TransactionSettled = "transaction_settled"
33
- PartnerMerchantConnected = "partner_merchant_connected"
34
- PartnerMerchantDisconnected = "partner_merchant_disconnected"
35
- PartnerMerchantDeclined = "partner_merchant_declined"
36
-
37
- AccountUpdaterDailyReport = "account_updater_daily_report"
38
-
39
- OAuthAccessRevoked = "oauth_access_revoked"
40
- ConnectedMerchantStatusTransitioned = "connected_merchant_status_transitioned"
41
- ConnectedMerchantPayPalStatusChanged = "connected_merchant_paypal_status_changed"
42
-
43
- GrantorUpdatedGrantedPaymentMethod = "grantor_updated_granted_payment_method"
44
- RecipientUpdatedGrantedPaymentMethod = "recipient_updated_granted_payment_method"
45
- GrantedPaymentInstrumentRevoked = "granted_payment_instrument_revoked"
46
- PaymentMethodRevokedByCustomer = "payment_method_revoked_by_customer"
47
-
48
- LocalPaymentCompleted = "local_payment_completed"
49
57
  end
50
58
 
51
59
  attr_reader :account_updater_daily_report
@@ -57,6 +65,7 @@ module Braintree
57
65
  attr_reader :revoked_payment_method_metadata
58
66
  attr_reader :kind
59
67
  attr_reader :local_payment_completed
68
+ attr_reader :local_payment_reversed
60
69
  attr_reader :oauth_access_revocation
61
70
  attr_reader :partner_merchant
62
71
  attr_reader :source_merchant_id
@@ -87,8 +96,9 @@ module Braintree
87
96
  @connected_merchant_status_transitioned = ConnectedMerchantStatusTransitioned._new(@subject[:connected_merchant_status_transitioned]) if @subject.has_key?(:connected_merchant_status_transitioned)
88
97
  @connected_merchant_paypal_status_changed = ConnectedMerchantPayPalStatusChanged._new(@subject[:connected_merchant_paypal_status_changed]) if @subject.has_key?(:connected_merchant_paypal_status_changed)
89
98
  @granted_payment_instrument_update = GrantedPaymentInstrumentUpdate._new(@subject[:granted_payment_instrument_update]) if @subject.has_key?(:granted_payment_instrument_update)
90
- @revoked_payment_method_metadata = RevokedPaymentMethodMetadata._new(gateway, @subject) if [Kind::GrantedPaymentInstrumentRevoked, Kind::PaymentMethodRevokedByCustomer].include?(@kind)
91
- @local_payment_completed = LocalPaymentCompleted._new(@subject[:local_payment]) if @subject.has_key?(:local_payment)
99
+ @revoked_payment_method_metadata = RevokedPaymentMethodMetadata._new(gateway, @subject) if [Kind::GrantedPaymentInstrumentRevoked, Kind::PaymentMethodRevokedByCustomer, Kind::GrantedPaymentMethodRevoked].include?(@kind)
100
+ @local_payment_completed = LocalPaymentCompleted._new(@subject[:local_payment]) if @subject.has_key?(:local_payment) && Kind::LocalPaymentCompleted == @kind
101
+ @local_payment_reversed = LocalPaymentReversed._new(@subject[:local_payment_reversed]) if @subject.has_key?(:local_payment_reversed) && Kind::LocalPaymentReversed == @kind
92
102
  end
93
103
 
94
104
  def merchant_account
@@ -110,7 +120,7 @@ module Braintree
110
120
  class << self
111
121
  protected :new
112
122
  def _new(*args) # :nodoc:
113
- self.new *args
123
+ self.new(*args)
114
124
  end
115
125
  end
116
126
  end
@@ -7,8 +7,8 @@ module Braintree
7
7
  end
8
8
 
9
9
  def parse(signature_string, payload)
10
- raise InvalidSignature, 'signature cannot be nil' if signature_string.nil?
11
- raise InvalidSignature, 'payload cannot be nil' if payload.nil?
10
+ raise InvalidSignature, "signature cannot be nil" if signature_string.nil?
11
+ raise InvalidSignature, "payload cannot be nil" if payload.nil?
12
12
  if payload =~ /[^A-Za-z0-9+=\/\n]/
13
13
  raise InvalidSignature, "payload contains illegal characters"
14
14
  end
@@ -18,7 +18,7 @@ module Braintree
18
18
  end
19
19
 
20
20
  def verify(challenge)
21
- raise InvalidChallenge, 'challenge contains non-hex characters' unless challenge =~ /\A[a-f0-9]{20,32}\z/
21
+ raise InvalidChallenge, "challenge contains non-hex characters" unless challenge =~ /\A[a-f0-9]{20,32}\z/
22
22
  digest = Braintree::Digest.hexdigest(@config.private_key, challenge)
23
23
  "#{@config.public_key}|#{digest}"
24
24
  end
@@ -34,13 +34,13 @@ module Braintree
34
34
 
35
35
  def _verify_signature(signature_string, payload)
36
36
  public_key, signature = _matching_signature_pair(signature_string)
37
- raise InvalidSignature, 'no matching public key' if public_key.nil?
37
+ raise InvalidSignature, "no matching public key" if public_key.nil?
38
38
 
39
39
  signature_matches = [payload, payload + "\n"].any? do |payload|
40
40
  payload_signature = Braintree::Digest.hexdigest(@config.private_key, payload)
41
41
  Braintree::Digest.secure_compare(signature, payload_signature)
42
42
  end
43
- raise InvalidSignature, 'signature does not match payload - one has been modified' unless signature_matches
43
+ raise InvalidSignature, "signature does not match payload - one has been modified" unless signature_matches
44
44
  end
45
45
  end
46
46
  end
@@ -82,10 +82,14 @@ module Braintree
82
82
  _granted_payment_instrument_update_sample_xml(id)
83
83
  when Braintree::WebhookNotification::Kind::RecipientUpdatedGrantedPaymentMethod
84
84
  _granted_payment_instrument_update_sample_xml(id)
85
+ when Braintree::WebhookNotification::Kind::GrantedPaymentMethodRevoked
86
+ _granted_payment_method_revoked_xml(id)
85
87
  when Braintree::WebhookNotification::Kind::PaymentMethodRevokedByCustomer
86
88
  _payment_method_revoked_by_customer_sample_xml(id)
87
89
  when Braintree::WebhookNotification::Kind::LocalPaymentCompleted
88
90
  _local_payment_completed_sample_xml(id)
91
+ when Braintree::WebhookNotification::Kind::LocalPaymentReversed
92
+ _local_payment_reversed_sample_xml(id)
89
93
  else
90
94
  _subscription_sample_xml(id)
91
95
  end
@@ -875,6 +879,24 @@ module Braintree
875
879
  XML
876
880
  end
877
881
 
882
+ def _granted_payment_method_revoked_xml(id)
883
+ <<-XML
884
+ <venmo-account>
885
+ <created-at type='dateTime'>2018-10-11T21:28:37Z</created-at>
886
+ <updated-at type='dateTime'>2018-10-11T21:28:37Z</updated-at>
887
+ <default type='boolean'>true</default>
888
+ <image-url>https://assets.braintreegateway.com/payment_method_logo/venmo.png?environment=test</image-url>
889
+ <token>#{id}</token>
890
+ <source-description>Venmo Account: venmojoe</source-description>
891
+ <username>venmojoe</username>
892
+ <venmo-user-id>456</venmo-user-id>
893
+ <subscriptions type='array'/>
894
+ <customer-id>venmo_customer_id</customer-id>
895
+ <global-id>cGF5bWVudG1ldGhvZF92ZW5tb2FjY291bnQ</global-id>
896
+ </venmo-account>
897
+ XML
898
+ end
899
+
878
900
  def _payment_method_revoked_by_customer_sample_xml(id)
879
901
  <<-XML
880
902
  <paypal-account>
@@ -912,5 +934,13 @@ module Braintree
912
934
  </local-payment>
913
935
  XML
914
936
  end
937
+
938
+ def _local_payment_reversed_sample_xml(id)
939
+ <<-XML
940
+ <local-payment-reversed>
941
+ <payment-id>PAY-XYZ123</payment-id>
942
+ </local-payment-reversed>
943
+ XML
944
+ end
915
945
  end
916
946
  end
@@ -12,6 +12,7 @@ module Braintree
12
12
  "Date" => "datetime",
13
13
  "DateTime" => "datetime",
14
14
  "Time" => "datetime",
15
+ "ActiveSupport::TimeWithZone" => "datetime"
15
16
  }
16
17
 
17
18
  XML_FORMATTING_NAMES = {
@@ -25,7 +26,7 @@ module Braintree
25
26
  date_or_time.respond_to?(:xmlschema) ? date_or_time.xmlschema : date_or_time.to_s
26
27
  end,
27
28
  "bigdecimal" => Proc.new do |bigdecimal|
28
- str = bigdecimal.to_s('F')
29
+ str = bigdecimal.to_s("F")
29
30
  if str =~ /\.\d$/
30
31
  str += "0"
31
32
  end
@@ -61,15 +62,15 @@ module Braintree
61
62
  else
62
63
  type_name = XML_TYPE_NAMES[value.class.name]
63
64
 
64
- attributes = ((value.nil? || type_name.nil?) ? {} : { :type => type_name })
65
+ attributes = ((value.nil? || type_name.nil?) ? {} : {:type => type_name})
65
66
  if value.nil?
66
67
  attributes[:nil] = true
67
68
  end
68
69
 
69
- formatting_name = XML_FORMATTING_NAMES[value.class.name]
70
+ formatting_name = XML_FORMATTING_NAMES[value.class.name]
70
71
  options[:builder].tag!(_xml_escape(key),
71
72
  XML_FORMATTING[formatting_name] ? XML_FORMATTING[formatting_name].call(value) : value,
72
- attributes
73
+ attributes,
73
74
  )
74
75
  end
75
76
  end
@@ -1,4 +1,3 @@
1
- require 'libxml'
2
1
  # Portions of this code were copied and modified from Ruby on Rails, released
3
2
  # under the MIT license, copyright (c) 2005-2009 David Heinemeier Hansson
4
3
  module Braintree
@@ -2,7 +2,7 @@
2
2
  # under the MIT license, copyright (c) 2005-2009 David Heinemeier Hansson
3
3
  module Braintree
4
4
  module Xml # :nodoc:
5
- CONTENT_ROOT = '__content__'
5
+ CONTENT_ROOT = "__content__"
6
6
 
7
7
  module Parser # :nodoc:
8
8
  XML_PARSING = {
@@ -11,18 +11,28 @@ module Braintree
11
11
  "boolean" => Proc.new { |boolean| %w(1 true).include?(boolean.strip) },
12
12
  }
13
13
 
14
- def self.hash_from_xml(xml)
15
- standardized_hash_structure = ::Braintree::Xml::Libxml.parse(xml)
14
+ def self.hash_from_xml(xml, parser = _determine_parser)
15
+ standardized_hash_structure = parser.parse(xml)
16
16
  transformed_xml = _transform_xml(standardized_hash_structure)
17
17
  Util.symbolize_keys(transformed_xml)
18
18
  end
19
19
 
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
25
+ else
26
+ ::Braintree::Xml::Rexml
27
+ end
28
+ end
29
+
20
30
  # Transform into standard Ruby types and convert all keys to snake_case instead of dash-case
21
31
  def self._transform_xml(value)
22
32
  case value.class.to_s
23
- when 'Hash'
24
- if value['type'] == 'array'
25
- child_key, entries = value.detect { |k,v| k != 'type' } # child_key is throwaway
33
+ when "Hash"
34
+ if value["type"] == "array"
35
+ child_key, entries = value.detect { |k,v| k != "type" } # child_key is throwaway
26
36
  if entries.nil? || ((c = value[CONTENT_ROOT]) && c.strip.empty?)
27
37
  []
28
38
  else
@@ -37,21 +47,21 @@ module Braintree
37
47
  end
38
48
  elsif value.has_key?(CONTENT_ROOT)
39
49
  content = value[CONTENT_ROOT]
40
- if parser = XML_PARSING[value["type"]]
50
+ if (parser = XML_PARSING[value["type"]])
41
51
  XML_PARSING[value["type"]].call(content)
42
52
  else
43
53
  content
44
54
  end
45
- elsif value['type'] == 'string' && value['nil'] != 'true'
55
+ elsif value["type"] == "string" && value["nil"] != "true"
46
56
  ""
47
57
  elsif value == {}
48
58
  ""
49
- elsif value.nil? || value['nil'] == 'true'
59
+ elsif value.nil? || value["nil"] == "true"
50
60
  nil
51
61
  # If the type is the only element which makes it then
52
62
  # this still makes the value nil, except if type is
53
63
  # a XML node(where type['value'] is a Hash)
54
- elsif value['type'] && value.size == 1 && !value['type'].is_a?(::Hash)
64
+ elsif value["type"] && value.size == 1 && !value["type"].is_a?(::Hash)
55
65
  raise "is this needed?"
56
66
  nil
57
67
  else
@@ -61,14 +71,14 @@ module Braintree
61
71
  end
62
72
  xml_value
63
73
  end
64
- when 'Array'
74
+ when "Array"
65
75
  value.map! { |i| _transform_xml(i) }
66
76
  case value.length
67
77
  when 0 then nil
68
78
  when 1 then value.first
69
79
  else value
70
80
  end
71
- when 'String'
81
+ when "String"
72
82
  value
73
83
  else
74
84
  raise "can't transform #{value.class.name} - #{value.inspect}"
@@ -0,0 +1,70 @@
1
+ # Portions of this code were copied and modified from Ruby on Rails, released
2
+ # under the MIT license, copyright (c) 2005-2009 David Heinemeier Hansson
3
+ module Braintree
4
+ module Xml # :nodoc:
5
+ module Rexml # :nodoc:
6
+
7
+ CONTENT_KEY = "__content__".freeze
8
+
9
+ def self.parse(string)
10
+ require "rexml/document" unless defined?(REXML::Document)
11
+ doc = REXML::Document.new(string)
12
+ _merge_element!({}, doc.root)
13
+ end
14
+
15
+ def self._merge_element!(hash, element)
16
+ _merge!(hash, element.name, _collapse(element))
17
+ end
18
+
19
+ def self._collapse(element)
20
+ hash = _get_attributes(element)
21
+
22
+ if element.has_elements?
23
+ element.each_element { |child| _merge_element!(hash, child) }
24
+ _merge_texts!(hash, element) unless _empty_content?(element)
25
+ hash
26
+ else
27
+ _merge_texts!(hash, element)
28
+ end
29
+ end
30
+
31
+ def self._merge_texts!(hash, element)
32
+ unless element.has_text?
33
+ hash
34
+ else
35
+ # must use value to prevent double-escaping
36
+ _merge!(
37
+ hash,
38
+ CONTENT_KEY,
39
+ element.texts.map { |t| t.value }.join,
40
+ )
41
+ end
42
+ end
43
+
44
+ def self._merge!(hash, key, value)
45
+ if hash.has_key?(key)
46
+ if hash[key].instance_of?(Array)
47
+ hash[key] << value
48
+ else
49
+ hash[key] = [hash[key], value]
50
+ end
51
+ elsif value.instance_of?(Array)
52
+ hash[key] = [value]
53
+ else
54
+ hash[key] = value
55
+ end
56
+ hash
57
+ end
58
+
59
+ def self._get_attributes(element)
60
+ attributes = {}
61
+ element.attributes.each { |n,v| attributes[n] = v }
62
+ attributes
63
+ end
64
+
65
+ def self._empty_content?(element)
66
+ element.texts.join.strip == ""
67
+ end
68
+ end
69
+ end
70
+ end