braintree 2.10.0 → 4.26.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 (353) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +1 -1
  3. data/braintree.gemspec +24 -0
  4. data/lib/braintree/account_updater_daily_report.rb +20 -0
  5. data/lib/braintree/ach_mandate.rb +14 -0
  6. data/lib/braintree/add_on.rb +4 -0
  7. data/lib/braintree/add_on_gateway.rb +17 -0
  8. data/lib/braintree/address/country_names.rb +4 -1
  9. data/lib/braintree/address.rb +35 -43
  10. data/lib/braintree/address_gateway.rb +24 -11
  11. data/lib/braintree/advanced_search.rb +26 -11
  12. data/lib/braintree/apple_pay.rb +29 -0
  13. data/lib/braintree/apple_pay_card.rb +66 -0
  14. data/lib/braintree/apple_pay_gateway.rb +37 -0
  15. data/lib/braintree/apple_pay_options.rb +19 -0
  16. data/lib/braintree/authorization_adjustment.rb +23 -0
  17. data/lib/braintree/base_module.rb +11 -1
  18. data/lib/braintree/bin_data.rb +34 -0
  19. data/lib/braintree/client_token.rb +11 -0
  20. data/lib/braintree/client_token_gateway.rb +45 -0
  21. data/lib/braintree/configuration.rb +231 -39
  22. data/lib/braintree/connected_merchant_paypal_status_changed.rb +22 -0
  23. data/lib/braintree/connected_merchant_status_transitioned.rb +22 -0
  24. data/lib/braintree/credentials_parser.rb +44 -0
  25. data/lib/braintree/credit_card.rb +107 -132
  26. data/lib/braintree/credit_card_gateway.rb +90 -41
  27. data/lib/braintree/credit_card_verification.rb +90 -17
  28. data/lib/braintree/credit_card_verification_gateway.rb +87 -0
  29. data/lib/braintree/credit_card_verification_search.rb +20 -0
  30. data/lib/braintree/customer.rb +80 -118
  31. data/lib/braintree/customer_gateway.rb +66 -39
  32. data/lib/braintree/customer_search.rb +5 -2
  33. data/lib/braintree/customer_session_gateway.rb +194 -0
  34. data/lib/braintree/descriptor.rb +4 -2
  35. data/lib/braintree/digest.rb +16 -3
  36. data/lib/braintree/disbursement.rb +63 -0
  37. data/lib/braintree/discount.rb +4 -0
  38. data/lib/braintree/discount_gateway.rb +17 -0
  39. data/lib/braintree/dispute/evidence.rb +24 -0
  40. data/lib/braintree/dispute/paypal_message.rb +15 -0
  41. data/lib/braintree/dispute/status_history.rb +18 -0
  42. data/lib/braintree/dispute/transaction.rb +20 -0
  43. data/lib/braintree/dispute/transaction_details.rb +15 -0
  44. data/lib/braintree/dispute.rb +165 -0
  45. data/lib/braintree/dispute_gateway.rb +142 -0
  46. data/lib/braintree/dispute_search.rb +31 -0
  47. data/lib/braintree/document_upload.rb +37 -0
  48. data/lib/braintree/document_upload_gateway.rb +38 -0
  49. data/lib/braintree/enriched_customer_data.rb +21 -0
  50. data/lib/braintree/error_codes.rb +680 -15
  51. data/lib/braintree/error_result.rb +13 -6
  52. data/lib/braintree/errors.rb +5 -5
  53. data/lib/braintree/exceptions.rb +14 -20
  54. data/lib/braintree/exchange_rate.rb +13 -0
  55. data/lib/braintree/exchange_rate_quote.rb +24 -0
  56. data/lib/braintree/exchange_rate_quote_gateway.rb +35 -0
  57. data/lib/braintree/exchange_rate_quote_input.rb +21 -0
  58. data/lib/braintree/exchange_rate_quote_request.rb +18 -0
  59. data/lib/braintree/exchange_rate_quote_response.rb +18 -0
  60. data/lib/braintree/facilitated_details.rb +21 -0
  61. data/lib/braintree/facilitator_details.rb +20 -0
  62. data/lib/braintree/gateway.rb +104 -6
  63. data/lib/braintree/google_pay_card.rb +62 -0
  64. data/lib/braintree/granted_payment_instrument_update.rb +23 -0
  65. data/lib/braintree/graphql/enums/recommendations.rb +7 -0
  66. data/lib/braintree/graphql/enums/recommended_payment_option.rb +8 -0
  67. data/lib/braintree/graphql/inputs/create_customer_session_input.rb +35 -0
  68. data/lib/braintree/graphql/inputs/customer_recommendations_input.rb +41 -0
  69. data/lib/braintree/graphql/inputs/customer_session_input.rb +39 -0
  70. data/lib/braintree/graphql/inputs/phone_input.rb +32 -0
  71. data/lib/braintree/graphql/inputs/update_customer_session_input.rb +37 -0
  72. data/lib/braintree/graphql/types/customer_recommendations_payload.rb +32 -0
  73. data/lib/braintree/graphql/types/payment_options.rb +33 -0
  74. data/lib/braintree/graphql/unions/customer_recommendations.rb +34 -0
  75. data/lib/braintree/graphql_client.rb +50 -0
  76. data/lib/braintree/http.rb +119 -25
  77. data/lib/braintree/local_payment_completed/blik_alias.rb +23 -0
  78. data/lib/braintree/local_payment_completed.rb +28 -0
  79. data/lib/braintree/local_payment_expired.rb +21 -0
  80. data/lib/braintree/local_payment_funded.rb +22 -0
  81. data/lib/braintree/local_payment_reversed.rb +19 -0
  82. data/lib/braintree/merchant.rb +34 -0
  83. data/lib/braintree/merchant_account/address_details.rb +16 -0
  84. data/lib/braintree/merchant_account/business_details.rb +17 -0
  85. data/lib/braintree/merchant_account/funding_details.rb +18 -0
  86. data/lib/braintree/merchant_account/individual_details.rb +20 -0
  87. data/lib/braintree/merchant_account.rb +76 -0
  88. data/lib/braintree/merchant_account_gateway.rb +130 -0
  89. data/lib/braintree/merchant_gateway.rb +38 -0
  90. data/lib/braintree/meta_checkout_card.rb +90 -0
  91. data/lib/braintree/meta_checkout_token.rb +89 -0
  92. data/lib/braintree/modification.rb +16 -5
  93. data/lib/braintree/oauth_credentials.rb +22 -0
  94. data/lib/braintree/oauth_gateway.rb +73 -0
  95. data/lib/braintree/paginated_collection.rb +25 -0
  96. data/lib/braintree/paginated_result.rb +15 -0
  97. data/lib/braintree/payment_instrument_type.rb +18 -0
  98. data/lib/braintree/payment_method.rb +37 -0
  99. data/lib/braintree/payment_method_customer_data_updated_metadata.rb +24 -0
  100. data/lib/braintree/payment_method_gateway.rb +221 -0
  101. data/lib/braintree/payment_method_nonce.rb +49 -0
  102. data/lib/braintree/payment_method_nonce_details.rb +40 -0
  103. data/lib/braintree/payment_method_nonce_details_payer_info.rb +32 -0
  104. data/lib/braintree/payment_method_nonce_gateway.rb +38 -0
  105. data/lib/braintree/payment_method_parser.rb +29 -0
  106. data/lib/braintree/paypal_account.rb +60 -0
  107. data/lib/braintree/paypal_account_gateway.rb +73 -0
  108. data/lib/braintree/paypal_payment_resource.rb +22 -0
  109. data/lib/braintree/paypal_payment_resource_gateway.rb +36 -0
  110. data/lib/braintree/plan.rb +62 -0
  111. data/lib/braintree/plan_gateway.rb +117 -0
  112. data/lib/braintree/processor_response_types.rb +7 -0
  113. data/lib/braintree/resource_collection.rb +12 -5
  114. data/lib/braintree/revoked_payment_method_metadata.rb +22 -0
  115. data/lib/braintree/risk_data/liability_shift.rb +22 -0
  116. data/lib/braintree/risk_data.rb +29 -0
  117. data/lib/braintree/samsung_pay_card.rb +85 -0
  118. data/lib/braintree/sepa_direct_debit_account.rb +60 -0
  119. data/lib/braintree/sepa_direct_debit_account_gateway.rb +25 -0
  120. data/lib/braintree/sepa_direct_debit_account_nonce_details.rb +28 -0
  121. data/lib/braintree/settlement_batch_summary.rb +25 -0
  122. data/lib/braintree/settlement_batch_summary_gateway.rb +27 -0
  123. data/lib/braintree/sha256_digest.rb +13 -0
  124. data/lib/braintree/signature_service.rb +19 -0
  125. data/lib/braintree/subscription/status_details.rb +20 -0
  126. data/lib/braintree/subscription.rb +67 -47
  127. data/lib/braintree/subscription_gateway.rb +43 -16
  128. data/lib/braintree/subscription_search.rb +2 -2
  129. data/lib/braintree/successful_result.rb +30 -10
  130. data/lib/braintree/test/authentication_id.rb +21 -0
  131. data/lib/braintree/test/credit_card.rb +76 -0
  132. data/lib/braintree/test/merchant_account.rb +12 -0
  133. data/lib/braintree/test/nonce.rb +81 -0
  134. data/lib/braintree/test/transaction_amounts.rb +3 -5
  135. data/lib/braintree/test/venmo_sdk.rb +21 -0
  136. data/lib/braintree/test_transaction.rb +19 -0
  137. data/lib/braintree/testing_gateway.rb +43 -0
  138. data/lib/braintree/three_d_secure_info.rb +53 -0
  139. data/lib/braintree/transaction/address_details.rb +27 -5
  140. data/lib/braintree/transaction/apple_pay_details.rb +35 -0
  141. data/lib/braintree/transaction/credit_card_details.rb +55 -4
  142. data/lib/braintree/transaction/customer_details.rb +11 -3
  143. data/lib/braintree/transaction/disbursement_details.rb +28 -0
  144. data/lib/braintree/transaction/google_pay_details.rb +42 -0
  145. data/lib/braintree/transaction/installment/adjustment.rb +33 -0
  146. data/lib/braintree/transaction/installment.rb +28 -0
  147. data/lib/braintree/transaction/local_payment_details.rb +27 -0
  148. data/lib/braintree/transaction/meta_checkout_card_details.rb +55 -0
  149. data/lib/braintree/transaction/meta_checkout_token_details.rb +58 -0
  150. data/lib/braintree/transaction/package_details.rb +19 -0
  151. data/lib/braintree/transaction/payment_receipt/card_present_data.rb +36 -0
  152. data/lib/braintree/transaction/payment_receipt/merchant_address.rb +19 -0
  153. data/lib/braintree/transaction/payment_receipt.rb +31 -0
  154. data/lib/braintree/transaction/paypal_details.rb +40 -0
  155. data/lib/braintree/transaction/paypal_here_details.rb +23 -0
  156. data/lib/braintree/transaction/samsung_pay_card_details.rb +50 -0
  157. data/lib/braintree/transaction/sepa_direct_debit_account_details.rb +27 -0
  158. data/lib/braintree/transaction/status_details.rb +6 -2
  159. data/lib/braintree/transaction/subscription_details.rb +5 -2
  160. data/lib/braintree/transaction/us_bank_account_details.rb +22 -0
  161. data/lib/braintree/transaction/venmo_account_details.rb +17 -0
  162. data/lib/braintree/transaction/visa_checkout_card_details.rb +51 -0
  163. data/lib/braintree/transaction.rb +293 -150
  164. data/lib/braintree/transaction_gateway.rb +303 -59
  165. data/lib/braintree/transaction_line_item.rb +43 -0
  166. data/lib/braintree/transaction_line_item_gateway.rb +19 -0
  167. data/lib/braintree/transaction_review.rb +18 -0
  168. data/lib/braintree/transaction_search.rb +17 -9
  169. data/lib/braintree/unknown_payment_method.rb +26 -0
  170. data/lib/braintree/us_bank_account.rb +57 -0
  171. data/lib/braintree/us_bank_account_gateway.rb +17 -0
  172. data/lib/braintree/us_bank_account_verification.rb +89 -0
  173. data/lib/braintree/us_bank_account_verification_gateway.rb +51 -0
  174. data/lib/braintree/us_bank_account_verification_search.rb +19 -0
  175. data/lib/braintree/util.rb +91 -11
  176. data/lib/braintree/validation_error.rb +14 -5
  177. data/lib/braintree/validation_error_collection.rb +5 -5
  178. data/lib/braintree/venmo_account.rb +34 -0
  179. data/lib/braintree/venmo_profile_data.rb +25 -0
  180. data/lib/braintree/version.rb +2 -2
  181. data/lib/braintree/visa_checkout_card.rb +90 -0
  182. data/lib/braintree/webhook_notification.rb +146 -0
  183. data/lib/braintree/webhook_notification_gateway.rb +46 -0
  184. data/lib/braintree/webhook_testing.rb +7 -0
  185. data/lib/braintree/webhook_testing_gateway.rb +1239 -0
  186. data/lib/braintree/xml/generator.rb +18 -10
  187. data/lib/braintree/xml/libxml.rb +1 -1
  188. data/lib/braintree/xml/parser.rb +24 -36
  189. data/lib/braintree/xml/rexml.rb +6 -7
  190. data/lib/braintree/xml.rb +1 -2
  191. data/lib/braintree.rb +186 -52
  192. data/lib/ssl/api_braintreegateway_com.ca.crt +474 -0
  193. data/spec/fixtures/files/bt_logo.png +0 -0
  194. data/spec/fixtures/files/gif_extension_bt_logo.gif +0 -0
  195. data/spec/fixtures/files/malformed_pdf.pdf +1 -0
  196. data/spec/fixtures/files/too_long.pdf +0 -0
  197. data/spec/integration/braintree/add_on_spec.rb +46 -0
  198. data/spec/integration/braintree/address_spec.rb +101 -178
  199. data/spec/integration/braintree/advanced_search_spec.rb +100 -97
  200. data/spec/integration/braintree/apple_pay_spec.rb +63 -0
  201. data/spec/integration/braintree/braintree_gateway_spec.rb +72 -0
  202. data/spec/integration/braintree/client_api/client_token_spec.rb +407 -0
  203. data/spec/integration/braintree/client_api/spec_helper.rb +300 -0
  204. data/spec/integration/braintree/credit_card_spec.rb +885 -654
  205. data/spec/integration/braintree/credit_card_verification_search_spec.rb +193 -0
  206. data/spec/integration/braintree/credit_card_verification_spec.rb +473 -0
  207. data/spec/integration/braintree/customer_search_spec.rb +69 -21
  208. data/spec/integration/braintree/customer_session_spec.rb +143 -0
  209. data/spec/integration/braintree/customer_spec.rb +1437 -518
  210. data/spec/integration/braintree/disbursement_spec.rb +31 -0
  211. data/spec/integration/braintree/discount_spec.rb +34 -0
  212. data/spec/integration/braintree/dispute_search_spec.rb +165 -0
  213. data/spec/integration/braintree/dispute_spec.rb +333 -0
  214. data/spec/integration/braintree/document_upload_spec.rb +87 -0
  215. data/spec/integration/braintree/error_codes_spec.rb +3 -3
  216. data/spec/integration/braintree/exchange_rate_quote_spec.rb +97 -0
  217. data/spec/integration/braintree/graphql_client_spec.rb +72 -0
  218. data/spec/integration/braintree/http_spec.rb +106 -56
  219. data/spec/integration/braintree/merchant_account_spec.rb +603 -0
  220. data/spec/integration/braintree/merchant_spec.rb +227 -0
  221. data/spec/integration/braintree/oauth_spec.rb +225 -0
  222. data/spec/integration/braintree/package_tracking_spec.rb +130 -0
  223. data/spec/integration/braintree/payment_method_nonce_spec.rb +345 -0
  224. data/spec/integration/braintree/payment_method_spec.rb +2146 -0
  225. data/spec/integration/braintree/payment_method_us_bank_account_spec.rb +325 -0
  226. data/spec/integration/braintree/paypal_account_spec.rb +299 -0
  227. data/spec/integration/braintree/paypal_payment_resource_spec.rb +141 -0
  228. data/spec/integration/braintree/plan_spec.rb +136 -0
  229. data/spec/integration/braintree/samsung_pay_card_spec.rb +146 -0
  230. data/spec/integration/braintree/sepa_direct_debit_account_spec.rb +194 -0
  231. data/spec/integration/braintree/settlement_batch_summary_spec.rb +62 -0
  232. data/spec/integration/braintree/subscription_spec.rb +785 -431
  233. data/spec/integration/braintree/test/transaction_amounts_spec.rb +4 -4
  234. data/spec/integration/braintree/test_transaction_spec.rb +145 -0
  235. data/spec/integration/braintree/transaction_line_item_spec.rb +44 -0
  236. data/spec/integration/braintree/transaction_search_spec.rb +745 -156
  237. data/spec/integration/braintree/transaction_spec.rb +7452 -1288
  238. data/spec/integration/braintree/transaction_us_bank_account_spec.rb +196 -0
  239. data/spec/integration/braintree/us_bank_account_spec.rb +113 -0
  240. data/spec/integration/braintree/us_bank_account_verification_search_spec.rb +178 -0
  241. data/spec/integration/braintree/us_bank_account_verification_spec.rb +240 -0
  242. data/spec/integration/braintree/visa_checkout_card_spec.rb +110 -0
  243. data/spec/integration/spec_helper.rb +61 -5
  244. data/spec/oauth_test_helper.rb +17 -0
  245. data/spec/script/httpsd.rb +6 -6
  246. data/spec/spec.opts +4 -0
  247. data/spec/spec_helper.rb +77 -21
  248. data/spec/ssl/certificate.crt +17 -0
  249. data/spec/ssl/geotrust_global.crt +20 -0
  250. data/spec/ssl/privateKey.key +15 -0
  251. data/spec/unit/braintree/address_spec.rb +27 -16
  252. data/spec/unit/braintree/apple_pay_card_spec.rb +133 -0
  253. data/spec/unit/braintree/base_module_spec.rb +4 -4
  254. data/spec/unit/braintree/client_token_spec.rb +37 -0
  255. data/spec/unit/braintree/configuration_spec.rb +293 -43
  256. data/spec/unit/braintree/credentials_parser_spec.rb +81 -0
  257. data/spec/unit/braintree/credit_card_spec.rb +138 -47
  258. data/spec/unit/braintree/credit_card_verification_gateway_spec.rb +56 -0
  259. data/spec/unit/braintree/credit_card_verification_search_spec.rb +79 -0
  260. data/spec/unit/braintree/credit_card_verification_spec.rb +132 -6
  261. data/spec/unit/braintree/customer_session_gateway_spec.rb +120 -0
  262. data/spec/unit/braintree/customer_spec.rb +196 -40
  263. data/spec/unit/braintree/digest_spec.rb +16 -3
  264. data/spec/unit/braintree/disbursement_spec.rb +131 -0
  265. data/spec/unit/braintree/dispute_search_spec.rb +66 -0
  266. data/spec/unit/braintree/dispute_spec.rb +479 -0
  267. data/spec/unit/braintree/document_upload_spec.rb +35 -0
  268. data/spec/unit/braintree/enriched_customer_data_spec.rb +41 -0
  269. data/spec/unit/braintree/error_result_spec.rb +11 -11
  270. data/spec/unit/braintree/errors_spec.rb +17 -17
  271. data/spec/unit/braintree/exchange_rate_quote_input_spec.rb +42 -0
  272. data/spec/unit/braintree/exchange_rate_quote_request_spec.rb +82 -0
  273. data/spec/unit/braintree/exchange_rate_quote_response_spec.rb +52 -0
  274. data/spec/unit/braintree/exchange_rate_quote_spec.rb +42 -0
  275. data/spec/unit/braintree/exchange_rate_spec.rb +23 -0
  276. data/spec/unit/braintree/google_pay_card_spec.rb +8 -0
  277. data/spec/unit/braintree/graphql/create_customer_session_input_spec.rb +81 -0
  278. data/spec/unit/braintree/graphql/customer_recommendations_input_spec.rb +110 -0
  279. data/spec/unit/braintree/graphql/customer_session_input_spec.rb +81 -0
  280. data/spec/unit/braintree/graphql/phone_input_spec.rb +51 -0
  281. data/spec/unit/braintree/graphql/update_customer_session_input_spec.rb +93 -0
  282. data/spec/unit/braintree/graphql_client_spec.rb +37 -0
  283. data/spec/unit/braintree/http_spec.rb +164 -2
  284. data/spec/unit/braintree/local_payment_completed_spec.rb +90 -0
  285. data/spec/unit/braintree/local_payment_expired_spec.rb +24 -0
  286. data/spec/unit/braintree/local_payment_funded_spec.rb +34 -0
  287. data/spec/unit/braintree/merchant_account_spec.rb +33 -0
  288. data/spec/unit/braintree/meta_checkout_card_details_spec.rb +60 -0
  289. data/spec/unit/braintree/meta_checkout_card_spec.rb +62 -0
  290. data/spec/unit/braintree/meta_checkout_token_details_spec.rb +63 -0
  291. data/spec/unit/braintree/meta_checkout_token_spec.rb +65 -0
  292. data/spec/unit/braintree/modification_spec.rb +7 -0
  293. data/spec/unit/braintree/package_tracking_spec.rb +77 -0
  294. data/spec/unit/braintree/payment_method_customer_data_updated_metadata_spec.rb +45 -0
  295. data/spec/unit/braintree/payment_method_nonce_details_payer_info_spec.rb +31 -0
  296. data/spec/unit/braintree/payment_method_nonce_details_spec.rb +51 -0
  297. data/spec/unit/braintree/payment_method_nonce_spec.rb +43 -0
  298. data/spec/unit/braintree/payment_method_spec.rb +135 -0
  299. data/spec/unit/braintree/paypal_account_spec.rb +53 -0
  300. data/spec/unit/braintree/paypal_payment_resource_spec.rb +125 -0
  301. data/spec/unit/braintree/resource_collection_spec.rb +44 -3
  302. data/spec/unit/braintree/risk_data/liability_shift.rb +26 -0
  303. data/spec/unit/braintree/risk_data_spec.rb +55 -0
  304. data/spec/unit/braintree/sepa_debit_account_nonce_details_spec.rb +29 -0
  305. data/spec/unit/braintree/sepa_debit_account_spec.rb +86 -0
  306. data/spec/unit/braintree/sha256_digest_spec.rb +11 -0
  307. data/spec/unit/braintree/signature_service_spec.rb +23 -0
  308. data/spec/unit/braintree/subscription_search_spec.rb +25 -18
  309. data/spec/unit/braintree/subscription_spec.rb +34 -7
  310. data/spec/unit/braintree/successful_result_spec.rb +9 -9
  311. data/spec/unit/braintree/three_d_secure_info_spec.rb +69 -0
  312. data/spec/unit/braintree/transaction/apple_pay_details_spec.rb +8 -0
  313. data/spec/unit/braintree/transaction/credit_card_details_spec.rb +34 -6
  314. data/spec/unit/braintree/transaction/customer_details_spec.rb +3 -2
  315. data/spec/unit/braintree/transaction/deposit_details_spec.rb +18 -0
  316. data/spec/unit/braintree/transaction/google_pay_details_spec.rb +8 -0
  317. data/spec/unit/braintree/transaction/installment_spec.rb +25 -0
  318. data/spec/unit/braintree/transaction/local_payment_details_spec.rb +36 -0
  319. data/spec/unit/braintree/transaction/meta_checkout_card_details_spec.rb +8 -0
  320. data/spec/unit/braintree/transaction/meta_checkout_token_details_spec.rb +8 -0
  321. data/spec/unit/braintree/transaction/payment_receipt_spec.rb +73 -0
  322. data/spec/unit/braintree/transaction/paypal_details_spec.rb +68 -0
  323. data/spec/unit/braintree/transaction/sepa_direct_debit_account_details_spec.rb +33 -0
  324. data/spec/unit/braintree/transaction/visa_checkout_card_details_spec.rb +8 -0
  325. data/spec/unit/braintree/transaction_gateway_spec.rb +150 -0
  326. data/spec/unit/braintree/transaction_search_spec.rb +12 -12
  327. data/spec/unit/braintree/transaction_spec.rb +366 -52
  328. data/spec/unit/braintree/unknown_payment_method_spec.rb +33 -0
  329. data/spec/unit/braintree/us_bank_account_spec.rb +13 -0
  330. data/spec/unit/braintree/us_bank_account_verification_search_spec.rb +60 -0
  331. data/spec/unit/braintree/us_bank_account_verification_spec.rb +98 -0
  332. data/spec/unit/braintree/util_spec.rb +199 -39
  333. data/spec/unit/braintree/validation_error_collection_spec.rb +336 -133
  334. data/spec/unit/braintree/validation_error_spec.rb +4 -4
  335. data/spec/unit/braintree/venmo_profile_data_spec.rb +43 -0
  336. data/spec/unit/braintree/visa_checkout_card_spec.rb +8 -0
  337. data/spec/unit/braintree/webhook_notification_spec.rb +977 -0
  338. data/spec/unit/braintree/xml/libxml_spec.rb +5 -5
  339. data/spec/unit/braintree/xml/parser_spec.rb +39 -5
  340. data/spec/unit/braintree/xml/rexml_spec.rb +5 -5
  341. data/spec/unit/braintree/xml_spec.rb +39 -32
  342. data/spec/unit/braintree_spec.rb +3 -2
  343. data/spec/unit/credit_card_details_spec.rb +8 -0
  344. metadata +314 -73
  345. data/README.rdoc +0 -80
  346. data/lib/braintree/test/credit_card_numbers.rb +0 -52
  347. data/lib/braintree/transparent_redirect.rb +0 -51
  348. data/lib/braintree/transparent_redirect_gateway.rb +0 -105
  349. data/lib/ssl/sandbox_braintreegateway_com.ca.crt +0 -19
  350. data/lib/ssl/www_braintreegateway_com.ca.crt +0 -202
  351. data/spec/hacks/tcp_socket.rb +0 -18
  352. data/spec/integration/braintree/transparent_redirect_spec.rb +0 -236
  353. data/spec/unit/braintree/transparent_redirect_spec.rb +0 -160
@@ -1,20 +1,28 @@
1
1
  module Braintree
2
- # See http://www.braintreepaymentsolutions.com/docs/ruby/general/result_objects
3
2
  class ErrorResult
4
3
 
5
- attr_reader :credit_card_verification, :transaction, :subscription, :errors, :params, :message
4
+ attr_reader :credit_card_verification
5
+ attr_reader :errors
6
+ attr_reader :merchant_account
7
+ attr_reader :message
8
+ attr_reader :params
9
+ attr_reader :subscription
10
+ attr_reader :transaction
11
+ attr_reader :verification
6
12
 
7
- def initialize(gateway, data) # :nodoc:
13
+ def initialize(gateway, data)
8
14
  @gateway = gateway
9
- @params = data[:params]
15
+ @params = data[:params] if data[:params]
10
16
  @credit_card_verification = CreditCardVerification._new(data[:verification]) if data[:verification]
17
+ @merchant_account = MerchantAccount._new(gateway, data[:merchant_account]) if data[:merchant_account]
11
18
  @message = data[:message]
12
19
  @transaction = Transaction._new(gateway, data[:transaction]) if data[:transaction]
20
+ @verification = CreditCardVerification._new(data[:verification]) if data[:verification]
13
21
  @subscription = Subscription._new(gateway, data[:subscription]) if data[:subscription]
14
22
  @errors = Errors.new(data[:errors])
15
23
  end
16
24
 
17
- def inspect # :nodoc:
25
+ def inspect
18
26
  if @credit_card_verification
19
27
  verification_inspect = " credit_card_verification: #{@credit_card_verification.inspect}"
20
28
  end
@@ -24,7 +32,6 @@ module Braintree
24
32
  "#<#{self.class} params:{...} errors:<#{@errors._inner_inspect}>#{verification_inspect}#{transaction_inspect}>"
25
33
  end
26
34
 
27
- # Always returns false.
28
35
  def success?
29
36
  false
30
37
  end
@@ -1,10 +1,10 @@
1
1
  module Braintree
2
- # See http://www.braintreepaymentsolutions.com/docs/ruby/general/validation_errors
3
2
  class Errors
4
3
  include Enumerable
5
4
 
6
- def initialize(data = {}) # :nodoc:
7
- @errors = ValidationErrorCollection.new(data.merge(:errors => []))
5
+ def initialize(data = {})
6
+ data = data.merge(:errors => []) unless data
7
+ @errors = ValidationErrorCollection.new(data)
8
8
  end
9
9
 
10
10
  def each(&block)
@@ -15,7 +15,7 @@ module Braintree
15
15
  @errors.for(scope)
16
16
  end
17
17
 
18
- def inspect # :nodoc:
18
+ def inspect
19
19
  "#<#{self.class} #{_inner_inspect}>"
20
20
  end
21
21
 
@@ -26,7 +26,7 @@ module Braintree
26
26
  @errors.deep_size
27
27
  end
28
28
 
29
- def _inner_inspect # :nodoc:
29
+ def _inner_inspect
30
30
  @errors._inner_inspect
31
31
  end
32
32
  end
@@ -1,42 +1,34 @@
1
- module Braintree # :nodoc:
2
- # Super class for all Braintree exceptions.
1
+ module Braintree
3
2
  class BraintreeError < ::StandardError; end
4
3
 
5
- # See http://www.braintreepaymentsolutions.com/docs/ruby/general/exceptions
6
4
  class AuthenticationError < BraintreeError; end
7
5
 
8
- # See http://www.braintreepaymentsolutions.com/docs/ruby/general/exceptions
9
6
  class AuthorizationError < BraintreeError; end
10
7
 
11
- # See http://www.braintreepaymentsolutions.com/docs/ruby/general/exceptions
12
- class ConfigurationError < BraintreeError
13
- def initialize(setting, message) # :nodoc:
14
- super "Braintree::Configuration.#{setting} #{message}"
15
- end
16
- end
8
+ class ConfigurationError < BraintreeError; end
17
9
 
18
- # See http://www.braintreepaymentsolutions.com/docs/ruby/general/exceptions
19
- class DownForMaintenanceError < BraintreeError; end
10
+ class GatewayTimeoutError < BraintreeError; end
20
11
 
21
- # See http://www.braintreepaymentsolutions.com/docs/ruby/general/exceptions
22
- class ForgedQueryString < BraintreeError; end
12
+ class InvalidSignature < BraintreeError; end
13
+
14
+ class InvalidChallenge < BraintreeError; end
23
15
 
24
- # See http://www.braintreepaymentsolutions.com/docs/ruby/general/exceptions
25
16
  class NotFoundError < BraintreeError; end
26
17
 
27
- # See http://www.braintreepaymentsolutions.com/docs/ruby/general/exceptions
18
+ class RequestTimeoutError < BraintreeError; end
19
+
28
20
  class ServerError < BraintreeError; end
29
21
 
30
- # See http://www.braintreepaymentsolutions.com/docs/ruby/general/exceptions
22
+ class ServiceUnavailableError < BraintreeError; end
23
+
31
24
  class SSLCertificateError < BraintreeError; end
32
25
 
33
- # See http://www.braintreepaymentsolutions.com/docs/ruby/general/exceptions
26
+ class TooManyRequestsError < BraintreeError; end
27
+
34
28
  class UnexpectedError < BraintreeError; end
35
29
 
36
- # See http://www.braintreepaymentsolutions.com/docs/ruby/general/exceptions
37
30
  class UpgradeRequiredError < BraintreeError; end
38
31
 
39
- # See http://www.braintreepaymentsolutions.com/docs/ruby/general/exceptions
40
32
  class ValidationsFailed < BraintreeError
41
33
  attr_reader :error_result
42
34
 
@@ -52,5 +44,7 @@ module Braintree # :nodoc:
52
44
  inspect
53
45
  end
54
46
  end
47
+
48
+ class TestOperationPerformedInProduction < BraintreeError; end
55
49
  end
56
50
 
@@ -0,0 +1,13 @@
1
+ module Braintree
2
+ class ExchangeRate
3
+ include BaseModule
4
+
5
+ def initialize(gateway, attributes)
6
+ set_instance_variables_from_hash(attributes)
7
+ end
8
+
9
+ def self.generate(exchange_rate_quote_request)
10
+ Configuration.gateway.exchange_rate_quote.generate(exchange_rate_quote_request)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,24 @@
1
+ module Braintree
2
+ class ExchangeRateQuote
3
+ include BaseModule
4
+
5
+ attr_reader :attrs
6
+ attr_reader :base_amount
7
+ attr_reader :exchange_rate
8
+ attr_reader :expires_at
9
+ attr_reader :id
10
+ attr_reader :quote_amount
11
+ attr_reader :refreshes_at
12
+ attr_reader :trade_rate
13
+
14
+ def initialize(attributes)
15
+ @attrs = attributes.keys
16
+ set_instance_variables_from_hash(attributes)
17
+ end
18
+
19
+ def inspect
20
+ inspected_attributes = @attrs.map { |attr| "#{attr}:#{send(attr).inspect}" }
21
+ "#<#{self.class} #{inspected_attributes.join(" ")}>"
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,35 @@
1
+ module Braintree
2
+ class ExchangeRateQuoteGateway
3
+ def initialize(gateway)
4
+ @gateway = gateway
5
+ end
6
+
7
+ DEFINITION = <<-GRAPHQL
8
+ mutation GenerateExchangeRateQuoteInput($input: GenerateExchangeRateQuoteInput!) {
9
+ generateExchangeRateQuote(input: $input) {
10
+ quotes {
11
+ id
12
+ baseAmount {value, currencyCode}
13
+ quoteAmount {value, currencyCode}
14
+ exchangeRate
15
+ tradeRate
16
+ expiresAt
17
+ refreshesAt
18
+ }
19
+ }
20
+ }
21
+ GRAPHQL
22
+
23
+ def generate(params)
24
+ response = @gateway.config.graphql_client.query(DEFINITION, {input: params})
25
+
26
+ if response.has_key?(:data) && response[:data][:generateExchangeRateQuote]
27
+ response[:data][:generateExchangeRateQuote]
28
+ elsif response[:errors]
29
+ ErrorResult.new(@gateway, response[:errors])
30
+ else
31
+ raise UnexpectedError, "expected :generateExchangeRateQuote or :api_error_response in GraphQL response"
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,21 @@
1
+ module Braintree
2
+ class ExchangeRateQuoteInput
3
+ include BaseModule
4
+
5
+ attr_reader :attrs
6
+ attr_reader :base_currency
7
+ attr_reader :base_amount
8
+ attr_reader :markup
9
+ attr_reader :quote_currency
10
+
11
+ def initialize(attributes)
12
+ @attrs = attributes.keys
13
+ set_instance_variables_from_hash(attributes)
14
+ end
15
+
16
+ def inspect
17
+ inspected_attributes = @attrs.map { |attr| "#{attr}:#{send(attr).inspect}" }
18
+ "#<#{self.class} #{inspected_attributes.join(" ")}>"
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,18 @@
1
+ module Braintree
2
+ class ExchangeRateQuoteRequest
3
+ include BaseModule
4
+
5
+ attr_reader :quotes
6
+
7
+ def initialize(attributes)
8
+ @attrs = attributes.keys
9
+ set_instance_variables_from_hash(attributes)
10
+ @quotes = (@quotes || []).map { |quote_hash| ExchangeRateQuoteInput.new(quote_hash) }
11
+ end
12
+
13
+ def inspect
14
+ inspected_attributes = @attrs.map { |attr| "#{attr}:#{send(attr).inspect}" }
15
+ "#<#{self.class} #{inspected_attributes.join(" ")}>"
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ module Braintree
2
+ class ExchangeRateQuoteResponse
3
+ include BaseModule
4
+
5
+ attr_reader :quotes
6
+
7
+ def initialize(attributes)
8
+ @attrs = attributes.keys
9
+ set_instance_variables_from_hash(attributes)
10
+ @quotes = (@quotes || []).map { |quote_hash| ExchangeRateQuote.new(quote_hash) }
11
+ end
12
+
13
+ def inspect
14
+ inspected_attributes = @attrs.map { |attr| "#{attr}:#{send(attr).inspect}" }
15
+ "#<#{self.class} #{inspected_attributes.join(" ")}>"
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,21 @@
1
+ module Braintree
2
+ class FacilitatedDetails
3
+ include BaseModule
4
+
5
+ attr_reader :merchant_id
6
+ attr_reader :merchant_name
7
+ attr_reader :payment_method_nonce
8
+
9
+ def initialize(attributes)
10
+ set_instance_variables_from_hash attributes unless attributes.nil?
11
+ end
12
+
13
+ def inspect
14
+ attr_order = [:merchant_id, :merchant_name, :payment_method_nonce]
15
+ formatted_attrs = attr_order.map do |attr|
16
+ "#{attr}: #{send(attr).inspect}"
17
+ end
18
+ "#<FacilitatorDetails #{formatted_attrs.join(", ")}>"
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,20 @@
1
+ module Braintree
2
+ class FacilitatorDetails
3
+ include BaseModule
4
+
5
+ attr_reader :oauth_application_client_id
6
+ attr_reader :oauth_application_name
7
+
8
+ def initialize(attributes)
9
+ set_instance_variables_from_hash attributes unless attributes.nil?
10
+ end
11
+
12
+ def inspect
13
+ attr_order = [:oauth_application_client_id, :oauth_application_name]
14
+ formatted_attrs = attr_order.map do |attr|
15
+ "#{attr}: #{send(attr).inspect}"
16
+ end
17
+ "#<FacilitatorDetails #{formatted_attrs.join(", ")}>"
18
+ end
19
+ end
20
+ end
@@ -1,6 +1,6 @@
1
1
  module Braintree
2
- class Gateway # :nodoc:
3
- attr_reader :config
2
+ class Gateway
3
+ attr_reader :config, :graphql_client
4
4
 
5
5
  def initialize(config)
6
6
  if config.is_a?(Hash)
@@ -10,12 +10,26 @@ module Braintree
10
10
  else
11
11
  raise ArgumentError, "config is an invalid type"
12
12
  end
13
+
14
+ @graphql_client = GraphQLClient.new(@config)
15
+ end
16
+
17
+ def add_on
18
+ AddOnGateway.new(self)
13
19
  end
14
20
 
15
21
  def address
16
22
  AddressGateway.new(self)
17
23
  end
18
24
 
25
+ def apple_pay
26
+ ApplePayGateway.new(self)
27
+ end
28
+
29
+ def client_token
30
+ ClientTokenGateway.new(self)
31
+ end
32
+
19
33
  def credit_card
20
34
  CreditCardGateway.new(self)
21
35
  end
@@ -24,16 +38,100 @@ module Braintree
24
38
  CustomerGateway.new(self)
25
39
  end
26
40
 
27
- def subscription
28
- SubscriptionGateway.new(self)
41
+ def customer_session
42
+ CustomerSessionGateway.new(self, graphql_client)
43
+ end
44
+
45
+ def discount
46
+ DiscountGateway.new(self)
47
+ end
48
+
49
+ def dispute
50
+ DisputeGateway.new(self)
51
+ end
52
+
53
+ def document_upload
54
+ DocumentUploadGateway.new(self)
55
+ end
56
+
57
+ def exchange_rate_quote
58
+ ExchangeRateQuoteGateway.new(self)
59
+ end
60
+
61
+ def oauth
62
+ OAuthGateway.new(self)
63
+ end
64
+
65
+ def plan
66
+ PlanGateway.new(self)
67
+ end
68
+
69
+ def payment_method
70
+ PaymentMethodGateway.new(self)
71
+ end
72
+
73
+ def payment_method_nonce
74
+ PaymentMethodNonceGateway.new(self)
75
+ end
76
+
77
+ def paypal_account
78
+ PayPalAccountGateway.new(self)
29
79
  end
30
80
 
31
- def transparent_redirect
32
- TransparentRedirectGateway.new(self)
81
+ def paypal_payment_resource
82
+ PayPalPaymentResourceGateway.new(self)
83
+ end
84
+
85
+ def us_bank_account
86
+ UsBankAccountGateway.new(self)
87
+ end
88
+
89
+ def sepa_direct_debit_account
90
+ SepaDirectDebitAccountGateway.new(self)
91
+ end
92
+
93
+ def merchant
94
+ MerchantGateway.new(self)
95
+ end
96
+
97
+ def merchant_account
98
+ MerchantAccountGateway.new(self)
99
+ end
100
+
101
+ def settlement_batch_summary
102
+ SettlementBatchSummaryGateway.new(self)
103
+ end
104
+
105
+ def subscription
106
+ SubscriptionGateway.new(self)
33
107
  end
34
108
 
35
109
  def transaction
36
110
  TransactionGateway.new(self)
37
111
  end
112
+
113
+ def transaction_line_item
114
+ TransactionLineItemGateway.new(self)
115
+ end
116
+
117
+ def testing
118
+ TestingGateway.new(self)
119
+ end
120
+
121
+ def us_bank_account_verification
122
+ UsBankAccountVerificationGateway.new(self)
123
+ end
124
+
125
+ def verification
126
+ CreditCardVerificationGateway.new(self)
127
+ end
128
+
129
+ def webhook_notification
130
+ WebhookNotificationGateway.new(self)
131
+ end
132
+
133
+ def webhook_testing
134
+ WebhookTestingGateway.new(self)
135
+ end
38
136
  end
39
137
  end
@@ -0,0 +1,62 @@
1
+ module Braintree
2
+ class GooglePayCard
3
+ include BaseModule
4
+
5
+ attr_reader :bin
6
+ attr_reader :commercial
7
+ attr_reader :country_of_issuance
8
+ attr_reader :created_at
9
+ attr_reader :customer_id
10
+ attr_reader :debit
11
+ attr_reader :default
12
+ attr_reader :durbin_regulated
13
+ attr_reader :expiration_month
14
+ attr_reader :expiration_year
15
+ attr_reader :google_transaction_id
16
+ attr_reader :healthcare
17
+ attr_reader :image_url
18
+ attr_reader :issuing_bank
19
+ attr_reader :payroll
20
+ attr_reader :prepaid
21
+ attr_reader :prepaid_reloadable
22
+ attr_reader :product_id
23
+ attr_reader :source_card_last_4
24
+ attr_reader :source_card_type
25
+ attr_reader :source_description
26
+ attr_reader :subscriptions
27
+ attr_reader :token
28
+ attr_reader :updated_at
29
+ attr_reader :virtual_card_last_4
30
+ attr_reader :virtual_card_type
31
+
32
+ def initialize(gateway, attributes)
33
+ @gateway = gateway
34
+ set_instance_variables_from_hash(attributes)
35
+ @subscriptions = (@subscriptions || []).map { |subscription_hash| Subscription._new(@gateway, subscription_hash) }
36
+ end
37
+
38
+ def default?
39
+ @default
40
+ end
41
+
42
+ def is_network_tokenized?
43
+ @is_network_tokenized
44
+ end
45
+
46
+ def card_type
47
+ virtual_card_type
48
+ end
49
+
50
+ def last_4
51
+ virtual_card_last_4
52
+ end
53
+
54
+ class << self
55
+ protected :new
56
+ end
57
+
58
+ def self._new(*args)
59
+ self.new(*args)
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,23 @@
1
+ module Braintree
2
+ class GrantedPaymentInstrumentUpdate
3
+ include BaseModule
4
+
5
+ attr_reader :grant_owner_merchant_id
6
+ attr_reader :grant_recipient_merchant_id
7
+ attr_reader :payment_method_nonce
8
+ attr_reader :token
9
+ attr_reader :updated_fields
10
+
11
+ def initialize(attributes)
12
+ set_instance_variables_from_hash(attributes)
13
+ @payment_method_nonce = attributes[:payment_method_nonce][:nonce]
14
+ end
15
+
16
+ class << self
17
+ protected :new
18
+ def _new(*args)
19
+ self.new(*args)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,7 @@
1
+ # Represents available types of customer recommendations that can be retrieved using a PayPal customer session.
2
+
3
+ module Braintree
4
+ module Recommendations
5
+ PAYMENT_RECOMMENDATIONS = "PAYMENT_RECOMMENDATIONS"
6
+ end
7
+ end
@@ -0,0 +1,8 @@
1
+ # Represents available payment options related to PayPal customer session recommendations.
2
+
3
+ module Braintree
4
+ module RecommendedPaymentOption
5
+ PAYPAL = "PAYPAL"
6
+ VENMO = "VENMO"
7
+ end
8
+ end
@@ -0,0 +1,35 @@
1
+ # Represents the input to request the creation of a PayPal customer session.
2
+
3
+ module Braintree
4
+ class CreateCustomerSessionInput
5
+ include BaseModule
6
+
7
+ attr_reader :attrs
8
+ attr_reader :merchant_account_id
9
+ attr_reader :session_id
10
+ attr_reader :customer
11
+ attr_reader :domain
12
+
13
+ def initialize(attributes)
14
+ @attrs = attributes.keys
15
+ set_instance_variables_from_hash(attributes)
16
+ @customer = attributes[:customer] ? CustomerSessionInput.new(attributes[:customer]) : nil
17
+ end
18
+
19
+ def inspect
20
+ inspected_attributes = @attrs.map { |attr| "#{attr}:#{send(attr).inspect}" }
21
+ "#<#{self.class} #{inspected_attributes.join(" ")}>"
22
+ end
23
+
24
+ def to_graphql_variables
25
+ variables = {}
26
+ variables["merchantAccountId"] = merchant_account_id if merchant_account_id
27
+ variables["sessionId"] = session_id if session_id
28
+ variables["domain"] = domain if domain
29
+ variables["customer"] = customer.to_graphql_variables if customer
30
+ variables
31
+ end
32
+ end
33
+ end
34
+
35
+
@@ -0,0 +1,41 @@
1
+ # Represents the input to request PayPal customer session recommendations.
2
+
3
+ module Braintree
4
+ class CustomerRecommendationsInput
5
+ include BaseModule
6
+
7
+ attr_reader :attrs
8
+ attr_reader :merchant_account_id
9
+ attr_reader :session_id
10
+ attr_reader :recommendations
11
+ attr_reader :customer
12
+
13
+ def initialize(attributes)
14
+ unless attributes[:session_id]
15
+ raise ArgumentError, "Expected hash to contain a :session_id"
16
+ end
17
+ unless attributes[:recommendations]
18
+ raise ArgumentError, "Expected hash to contain a :recommendations"
19
+ end
20
+ @attrs = attributes.keys
21
+ set_instance_variables_from_hash(attributes)
22
+ @customer = attributes[:customer] ? CustomerSessionInput.new(attributes[:customer]) : nil
23
+ end
24
+
25
+ def inspect
26
+ inspected_attributes = @attrs.map { |attr| "#{attr}:#{send(attr).inspect}" }
27
+ "#<#{self.class} #{inspected_attributes.join(" ")}>"
28
+ end
29
+
30
+ def to_graphql_variables
31
+ variables = {}
32
+ variables["merchantAccountId"] = merchant_account_id if merchant_account_id
33
+ variables["sessionId"] = session_id if session_id
34
+ variables["recommendations"] = recommendations if recommendations
35
+ variables["customer"] = customer.to_graphql_variables if customer
36
+ variables
37
+ end
38
+ end
39
+ end
40
+
41
+
@@ -0,0 +1,39 @@
1
+ # Customer identifying information for a PayPal customer session.
2
+
3
+ module Braintree
4
+ class CustomerSessionInput
5
+ include BaseModule
6
+
7
+ attr_reader :attrs
8
+ attr_reader :email
9
+ attr_reader :phone
10
+ attr_reader :device_fingerprint_id
11
+ attr_reader :paypal_app_installed
12
+ attr_reader :venmo_app_installed
13
+ attr_reader :user_agent
14
+
15
+ def initialize(attributes)
16
+ @attrs = attributes.keys
17
+ set_instance_variables_from_hash(attributes)
18
+ @phone = attributes[:phone] ? PhoneInput.new(attributes[:phone]) : nil
19
+ end
20
+
21
+ def inspect
22
+ inspected_attributes = @attrs.map { |attr| "#{attr}:#{send(attr).inspect}" }
23
+ "#<#{self.class} #{inspected_attributes.join(" ")}>"
24
+ end
25
+
26
+ def to_graphql_variables
27
+ variables = {}
28
+ variables["email"] = email if email
29
+ variables["phone"] = phone.to_graphql_variables if phone
30
+ variables["deviceFingerprintId"] = device_fingerprint_id if device_fingerprint_id
31
+ variables["paypalAppInstalled"] = paypal_app_installed if paypal_app_installed
32
+ variables["venmoAppInstalled"] = venmo_app_installed if venmo_app_installed
33
+ variables["userAgent"] = user_agent if user_agent
34
+ variables
35
+ end
36
+ end
37
+ end
38
+
39
+