stripe 3.3.1 → 5.38.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (205) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +962 -0
  3. data/CODE_OF_CONDUCT.md +77 -0
  4. data/Gemfile +24 -22
  5. data/History.txt +1 -689
  6. data/README.md +202 -55
  7. data/Rakefile +18 -12
  8. data/VERSION +1 -1
  9. data/bin/stripe-console +5 -3
  10. data/lib/stripe/api_operations/create.rb +4 -2
  11. data/lib/stripe/api_operations/delete.rb +31 -3
  12. data/lib/stripe/api_operations/list.rb +4 -13
  13. data/lib/stripe/api_operations/nested_resource.rb +73 -0
  14. data/lib/stripe/api_operations/request.rb +84 -17
  15. data/lib/stripe/api_operations/save.rb +25 -13
  16. data/lib/stripe/api_resource.rb +70 -11
  17. data/lib/stripe/connection_manager.rb +179 -0
  18. data/lib/stripe/error_object.rb +93 -0
  19. data/lib/stripe/errors.rb +47 -19
  20. data/lib/stripe/instrumentation.rb +84 -0
  21. data/lib/stripe/list_object.rb +54 -22
  22. data/lib/stripe/multipart_encoder.rb +131 -0
  23. data/lib/stripe/oauth.rb +29 -20
  24. data/lib/stripe/object_types.rb +107 -0
  25. data/lib/stripe/{account.rb → resources/account.rb} +70 -36
  26. data/lib/stripe/resources/account_link.rb +10 -0
  27. data/lib/stripe/resources/alipay_account.rb +34 -0
  28. data/lib/stripe/{apple_pay_domain.rb → resources/apple_pay_domain.rb} +5 -2
  29. data/lib/stripe/resources/application_fee.rb +14 -0
  30. data/lib/stripe/resources/application_fee_refund.rb +31 -0
  31. data/lib/stripe/resources/balance.rb +8 -0
  32. data/lib/stripe/resources/balance_transaction.rb +10 -0
  33. data/lib/stripe/resources/bank_account.rb +43 -0
  34. data/lib/stripe/resources/billing_portal/configuration.rb +14 -0
  35. data/lib/stripe/resources/billing_portal/session.rb +12 -0
  36. data/lib/stripe/{bitcoin_receiver.rb → resources/bitcoin_receiver.rb} +7 -5
  37. data/lib/stripe/resources/bitcoin_transaction.rb +16 -0
  38. data/lib/stripe/resources/capability.rb +34 -0
  39. data/lib/stripe/resources/card.rb +38 -0
  40. data/lib/stripe/resources/charge.rb +23 -0
  41. data/lib/stripe/resources/checkout/session.rb +16 -0
  42. data/lib/stripe/resources/country_spec.rb +10 -0
  43. data/lib/stripe/{coupon.rb → resources/coupon.rb} +5 -2
  44. data/lib/stripe/resources/credit_note.rb +33 -0
  45. data/lib/stripe/resources/credit_note_line_item.rb +8 -0
  46. data/lib/stripe/resources/customer.rb +41 -0
  47. data/lib/stripe/resources/customer_balance_transaction.rb +31 -0
  48. data/lib/stripe/resources/discount.rb +7 -0
  49. data/lib/stripe/resources/dispute.rb +22 -0
  50. data/lib/stripe/resources/ephemeral_key.rb +20 -0
  51. data/lib/stripe/resources/event.rb +10 -0
  52. data/lib/stripe/resources/exchange_rate.rb +10 -0
  53. data/lib/stripe/resources/file.rb +36 -0
  54. data/lib/stripe/resources/file_link.rb +12 -0
  55. data/lib/stripe/resources/identity/verification_report.rb +12 -0
  56. data/lib/stripe/resources/identity/verification_session.rb +35 -0
  57. data/lib/stripe/resources/invoice.rb +74 -0
  58. data/lib/stripe/{invoice_item.rb → resources/invoice_item.rb} +5 -2
  59. data/lib/stripe/resources/invoice_line_item.rb +8 -0
  60. data/lib/stripe/resources/issuing/authorization.rb +34 -0
  61. data/lib/stripe/resources/issuing/card.rb +25 -0
  62. data/lib/stripe/resources/issuing/card_details.rb +9 -0
  63. data/lib/stripe/resources/issuing/cardholder.rb +14 -0
  64. data/lib/stripe/resources/issuing/dispute.rb +25 -0
  65. data/lib/stripe/resources/issuing/transaction.rb +13 -0
  66. data/lib/stripe/resources/line_item.rb +8 -0
  67. data/lib/stripe/resources/login_link.rb +15 -0
  68. data/lib/stripe/resources/mandate.rb +8 -0
  69. data/lib/stripe/resources/order.rb +33 -0
  70. data/lib/stripe/resources/order_return.rb +10 -0
  71. data/lib/stripe/resources/payment_intent.rb +43 -0
  72. data/lib/stripe/resources/payment_method.rb +33 -0
  73. data/lib/stripe/resources/payout.rb +33 -0
  74. data/lib/stripe/resources/person.rb +32 -0
  75. data/lib/stripe/{plan.rb → resources/plan.rb} +4 -1
  76. data/lib/stripe/resources/price.rb +12 -0
  77. data/lib/stripe/{product.rb → resources/product.rb} +6 -3
  78. data/lib/stripe/resources/promotion_code.rb +12 -0
  79. data/lib/stripe/resources/quote.rb +105 -0
  80. data/lib/stripe/resources/radar/early_fraud_warning.rb +12 -0
  81. data/lib/stripe/resources/radar/value_list.rb +15 -0
  82. data/lib/stripe/resources/radar/value_list_item.rb +14 -0
  83. data/lib/stripe/resources/recipient.rb +14 -0
  84. data/lib/stripe/resources/recipient_transfer.rb +7 -0
  85. data/lib/stripe/{refund.rb → resources/refund.rb} +4 -1
  86. data/lib/stripe/resources/reporting/report_run.rb +13 -0
  87. data/lib/stripe/resources/reporting/report_type.rb +13 -0
  88. data/lib/stripe/resources/reversal.rb +30 -0
  89. data/lib/stripe/resources/review.rb +21 -0
  90. data/lib/stripe/resources/setup_attempt.rb +10 -0
  91. data/lib/stripe/resources/setup_intent.rb +33 -0
  92. data/lib/stripe/resources/sigma/scheduled_query_run.rb +16 -0
  93. data/lib/stripe/{sku.rb → resources/sku.rb} +6 -3
  94. data/lib/stripe/resources/source.rb +47 -0
  95. data/lib/stripe/resources/source_transaction.rb +7 -0
  96. data/lib/stripe/resources/subscription.rb +26 -0
  97. data/lib/stripe/resources/subscription_item.rb +26 -0
  98. data/lib/stripe/resources/subscription_schedule.rb +33 -0
  99. data/lib/stripe/resources/tax_code.rb +10 -0
  100. data/lib/stripe/resources/tax_id.rb +27 -0
  101. data/lib/stripe/resources/tax_rate.rb +12 -0
  102. data/lib/stripe/resources/terminal/connection_token.rb +12 -0
  103. data/lib/stripe/resources/terminal/location.rb +15 -0
  104. data/lib/stripe/resources/terminal/reader.rb +15 -0
  105. data/lib/stripe/{three_d_secure.rb → resources/three_d_secure.rb} +4 -1
  106. data/lib/stripe/resources/token.rb +10 -0
  107. data/lib/stripe/resources/topup.rb +23 -0
  108. data/lib/stripe/resources/transfer.rb +27 -0
  109. data/lib/stripe/resources/usage_record.rb +8 -0
  110. data/lib/stripe/resources/usage_record_summary.rb +8 -0
  111. data/lib/stripe/{recipient.rb → resources/webhook_endpoint.rb} +6 -7
  112. data/lib/stripe/resources.rb +90 -0
  113. data/lib/stripe/singleton_api_resource.rb +10 -4
  114. data/lib/stripe/stripe_client.rb +798 -346
  115. data/lib/stripe/stripe_configuration.rb +194 -0
  116. data/lib/stripe/stripe_object.rb +271 -126
  117. data/lib/stripe/stripe_response.rb +89 -27
  118. data/lib/stripe/util.rb +134 -194
  119. data/lib/stripe/version.rb +3 -1
  120. data/lib/stripe/webhook.rb +57 -18
  121. data/lib/stripe.rb +74 -186
  122. data/stripe.gemspec +35 -16
  123. metadata +110 -165
  124. data/.gitattributes +0 -4
  125. data/.github/ISSUE_TEMPLATE.md +0 -5
  126. data/.gitignore +0 -5
  127. data/.travis.yml +0 -38
  128. data/lib/stripe/alipay_account.rb +0 -22
  129. data/lib/stripe/application_fee.rb +0 -22
  130. data/lib/stripe/application_fee_refund.rb +0 -20
  131. data/lib/stripe/balance.rb +0 -5
  132. data/lib/stripe/balance_transaction.rb +0 -11
  133. data/lib/stripe/bank_account.rb +0 -30
  134. data/lib/stripe/bitcoin_transaction.rb +0 -11
  135. data/lib/stripe/card.rb +0 -27
  136. data/lib/stripe/charge.rb +0 -82
  137. data/lib/stripe/country_spec.rb +0 -11
  138. data/lib/stripe/customer.rb +0 -79
  139. data/lib/stripe/dispute.rb +0 -17
  140. data/lib/stripe/ephemeral_key.rb +0 -18
  141. data/lib/stripe/event.rb +0 -7
  142. data/lib/stripe/file_upload.rb +0 -33
  143. data/lib/stripe/invoice.rb +0 -29
  144. data/lib/stripe/invoice_line_item.rb +0 -5
  145. data/lib/stripe/login_link.rb +0 -9
  146. data/lib/stripe/order.rb +0 -29
  147. data/lib/stripe/order_return.rb +0 -11
  148. data/lib/stripe/payout.rb +0 -18
  149. data/lib/stripe/recipient_transfer.rb +0 -6
  150. data/lib/stripe/reversal.rb +0 -20
  151. data/lib/stripe/source.rb +0 -23
  152. data/lib/stripe/subscription.rb +0 -33
  153. data/lib/stripe/subscription_item.rb +0 -14
  154. data/lib/stripe/token.rb +0 -7
  155. data/lib/stripe/transfer.rb +0 -18
  156. data/test/api_stub_helpers.rb +0 -0
  157. data/test/stripe/account_test.rb +0 -202
  158. data/test/stripe/alipay_account_test.rb +0 -17
  159. data/test/stripe/api_operations_test.rb +0 -31
  160. data/test/stripe/api_resource_test.rb +0 -558
  161. data/test/stripe/apple_pay_domain_test.rb +0 -31
  162. data/test/stripe/application_fee_refund_test.rb +0 -35
  163. data/test/stripe/application_fee_test.rb +0 -12
  164. data/test/stripe/balance_test.rb +0 -11
  165. data/test/stripe/bank_account_test.rb +0 -36
  166. data/test/stripe/bitcoin_receiver_test.rb +0 -67
  167. data/test/stripe/bitcoin_transaction_test.rb +0 -19
  168. data/test/stripe/charge_test.rb +0 -57
  169. data/test/stripe/country_spec_test.rb +0 -18
  170. data/test/stripe/coupon_test.rb +0 -42
  171. data/test/stripe/customer_card_test.rb +0 -46
  172. data/test/stripe/customer_test.rb +0 -114
  173. data/test/stripe/dispute_test.rb +0 -40
  174. data/test/stripe/ephemeral_key_test.rb +0 -84
  175. data/test/stripe/errors_test.rb +0 -18
  176. data/test/stripe/file_upload_test.rb +0 -66
  177. data/test/stripe/invoice_item_test.rb +0 -53
  178. data/test/stripe/invoice_line_item_test.rb +0 -6
  179. data/test/stripe/invoice_test.rb +0 -110
  180. data/test/stripe/list_object_test.rb +0 -170
  181. data/test/stripe/login_link_test.rb +0 -35
  182. data/test/stripe/oauth_test.rb +0 -85
  183. data/test/stripe/order_return_test.rb +0 -19
  184. data/test/stripe/order_test.rb +0 -57
  185. data/test/stripe/payout_test.rb +0 -48
  186. data/test/stripe/plan_test.rb +0 -50
  187. data/test/stripe/product_test.rb +0 -45
  188. data/test/stripe/recipient_card_test.rb +0 -44
  189. data/test/stripe/recipient_test.rb +0 -48
  190. data/test/stripe/refund_test.rb +0 -37
  191. data/test/stripe/reversal_test.rb +0 -41
  192. data/test/stripe/sku_test.rb +0 -48
  193. data/test/stripe/source_test.rb +0 -68
  194. data/test/stripe/stripe_client_test.rb +0 -750
  195. data/test/stripe/stripe_object_test.rb +0 -398
  196. data/test/stripe/stripe_response_test.rb +0 -46
  197. data/test/stripe/subscription_item_test.rb +0 -52
  198. data/test/stripe/subscription_test.rb +0 -58
  199. data/test/stripe/three_d_secure_test.rb +0 -21
  200. data/test/stripe/transfer_test.rb +0 -41
  201. data/test/stripe/util_test.rb +0 -414
  202. data/test/stripe/webhook_test.rb +0 -92
  203. data/test/stripe_test.rb +0 -59
  204. data/test/test_data.rb +0 -59
  205. data/test/test_helper.rb +0 -56
@@ -1,14 +1,54 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Stripe
2
- # StripeResponse encapsulates some vitals of a response that came back from
3
- # the Stripe API.
4
- class StripeResponse
5
- # The data contained by the HTTP body of the response deserialized from
6
- # JSON.
7
- attr_accessor :data
4
+ # Headers provides an access wrapper to an API response's header data. It
5
+ # mainly exists so that we don't need to expose the entire
6
+ # `Net::HTTPResponse` object while still getting some of its benefits like
7
+ # case-insensitive access to header names and flattening of header values.
8
+ class StripeResponseHeaders
9
+ # Initializes a Headers object from a Net::HTTP::HTTPResponse object.
10
+ def self.from_net_http(resp)
11
+ new(resp.to_hash)
12
+ end
8
13
 
9
- # The raw HTTP body of the response.
10
- attr_accessor :http_body
14
+ # `hash` is expected to be a hash mapping header names to arrays of
15
+ # header values. This is the default format generated by calling
16
+ # `#to_hash` on a `Net::HTTPResponse` object because headers can be
17
+ # repeated multiple times. Using `#[]` will collapse values down to just
18
+ # the first.
19
+ def initialize(hash)
20
+ if !hash.is_a?(Hash) ||
21
+ !hash.keys.all? { |n| n.is_a?(String) } ||
22
+ !hash.values.all? { |a| a.is_a?(Array) } ||
23
+ !hash.values.all? { |a| a.all? { |v| v.is_a?(String) } }
24
+ raise ArgumentError,
25
+ "expect hash to be a map of string header names to arrays of " \
26
+ "header values"
27
+ end
28
+
29
+ @hash = {}
11
30
 
31
+ # This shouldn't be strictly necessary because `Net::HTTPResponse` will
32
+ # produce a hash with all headers downcased, but do it anyway just in
33
+ # case an object of this class was constructed manually.
34
+ #
35
+ # Also has the effect of duplicating the hash, which is desirable for a
36
+ # little extra object safety.
37
+ hash.each do |k, v|
38
+ @hash[k.downcase] = v
39
+ end
40
+ end
41
+
42
+ def [](name)
43
+ values = @hash[name.downcase]
44
+ if values && values.count > 1
45
+ warn("Duplicate header values for `#{name}`; returning only first")
46
+ end
47
+ values ? values.first : nil
48
+ end
49
+ end
50
+
51
+ module StripeResponseBase
12
52
  # A Hash of the HTTP headers of the response.
13
53
  attr_accessor :http_headers
14
54
 
@@ -18,30 +58,52 @@ module Stripe
18
58
  # The Stripe request ID of the response.
19
59
  attr_accessor :request_id
20
60
 
21
- # Initializes a StripeResponse object from a Hash like the kind returned as
22
- # part of a Faraday exception.
23
- #
24
- # This may throw JSON::ParserError if the response body is not valid JSON.
25
- def self.from_faraday_hash(http_resp)
26
- resp = StripeResponse.new
27
- resp.data = JSON.parse(http_resp[:body], symbolize_names: true)
28
- resp.http_body = http_resp[:body]
29
- resp.http_headers = http_resp[:headers]
30
- resp.http_status = http_resp[:status]
31
- resp.request_id = http_resp[:headers]["Request-Id"]
32
- resp
61
+ def self.populate_for_net_http(resp, http_resp)
62
+ resp.http_headers = StripeResponseHeaders.from_net_http(http_resp)
63
+ resp.http_status = http_resp.code.to_i
64
+ resp.request_id = http_resp["request-id"]
33
65
  end
66
+ end
34
67
 
35
- # Initializes a StripeResponse object from a Faraday HTTP response object.
36
- #
37
- # This may throw JSON::ParserError if the response body is not valid JSON.
38
- def self.from_faraday_response(http_resp)
68
+ # StripeResponse encapsulates some vitals of a response that came back from
69
+ # the Stripe API.
70
+ class StripeResponse
71
+ include StripeResponseBase
72
+ # The data contained by the HTTP body of the response deserialized from
73
+ # JSON.
74
+ attr_accessor :data
75
+
76
+ # The raw HTTP body of the response.
77
+ attr_accessor :http_body
78
+
79
+ # Initializes a StripeResponse object from a Net::HTTP::HTTPResponse
80
+ # object.
81
+ def self.from_net_http(http_resp)
39
82
  resp = StripeResponse.new
40
83
  resp.data = JSON.parse(http_resp.body, symbolize_names: true)
41
84
  resp.http_body = http_resp.body
42
- resp.http_headers = http_resp.headers
43
- resp.http_status = http_resp.status
44
- resp.request_id = http_resp.headers["Request-Id"]
85
+ StripeResponseBase.populate_for_net_http(resp, http_resp)
86
+ resp
87
+ end
88
+ end
89
+
90
+ # We have to alias StripeResponseHeaders to StripeResponse::Headers, as this
91
+ # class used to be embedded within StripeResponse and we want to be backwards
92
+ # compatible.
93
+ StripeResponse::Headers = StripeResponseHeaders
94
+
95
+ # StripeHeadersOnlyResponse includes only header-related vitals of the
96
+ # response. This is used for streaming requests where the response was read
97
+ # directly in a block and we explicitly don't want to store the body of the
98
+ # response in memory.
99
+ class StripeHeadersOnlyResponse
100
+ include StripeResponseBase
101
+
102
+ # Initializes a StripeHeadersOnlyResponse object from a
103
+ # Net::HTTP::HTTPResponse object.
104
+ def self.from_net_http(http_resp)
105
+ resp = StripeHeadersOnlyResponse.new
106
+ StripeResponseBase.populate_for_net_http(resp, http_resp)
45
107
  resp
46
108
  end
47
109
  end
data/lib/stripe/util.rb CHANGED
@@ -1,68 +1,50 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "cgi"
2
4
 
3
5
  module Stripe
4
6
  module Util
5
- def self.objects_to_ids(h)
6
- case h
7
+ # Options that a user is allowed to specify.
8
+ OPTS_USER_SPECIFIED = Set[
9
+ :api_key,
10
+ :idempotency_key,
11
+ :stripe_account,
12
+ :stripe_version
13
+ ].freeze
14
+
15
+ # Options that should be copyable from one StripeObject to another
16
+ # including options that may be internal.
17
+ OPTS_COPYABLE = (
18
+ OPTS_USER_SPECIFIED + Set[:api_base]
19
+ ).freeze
20
+
21
+ # Options that should be persisted between API requests. This includes
22
+ # client, which is an object containing an HTTP client to reuse.
23
+ OPTS_PERSISTABLE = (
24
+ OPTS_USER_SPECIFIED + Set[:client] - Set[:idempotency_key]
25
+ ).freeze
26
+
27
+ def self.objects_to_ids(obj)
28
+ case obj
7
29
  when APIResource
8
- h.id
30
+ obj.id
9
31
  when Hash
10
32
  res = {}
11
- h.each { |k, v| res[k] = objects_to_ids(v) unless v.nil? }
33
+ obj.each { |k, v| res[k] = objects_to_ids(v) unless v.nil? }
12
34
  res
13
35
  when Array
14
- h.map { |v| objects_to_ids(v) }
36
+ obj.map { |v| objects_to_ids(v) }
15
37
  else
16
- h
38
+ obj
17
39
  end
18
40
  end
19
41
 
20
42
  def self.object_classes
21
- @object_classes ||= {
22
- # data structures
23
- ListObject::OBJECT_NAME => ListObject,
24
-
25
- # business objects
26
- Account::OBJECT_NAME => Account,
27
- AlipayAccount::OBJECT_NAME => AlipayAccount,
28
- ApplePayDomain::OBJECT_NAME => ApplePayDomain,
29
- ApplicationFee::OBJECT_NAME => ApplicationFee,
30
- ApplicationFeeRefund::OBJECT_NAME => ApplicationFeeRefund,
31
- Balance::OBJECT_NAME => Balance,
32
- BalanceTransaction::OBJECT_NAME => BalanceTransaction,
33
- BankAccount::OBJECT_NAME => BankAccount,
34
- BitcoinReceiver::OBJECT_NAME => BitcoinReceiver,
35
- BitcoinTransaction::OBJECT_NAME => BitcoinTransaction,
36
- Card::OBJECT_NAME => Card,
37
- Charge::OBJECT_NAME => Charge,
38
- CountrySpec::OBJECT_NAME => CountrySpec,
39
- Coupon::OBJECT_NAME => Coupon,
40
- Customer::OBJECT_NAME => Customer,
41
- Dispute::OBJECT_NAME => Dispute,
42
- EphemeralKey::OBJECT_NAME => EphemeralKey,
43
- Event::OBJECT_NAME => Event,
44
- FileUpload::OBJECT_NAME => FileUpload,
45
- Invoice::OBJECT_NAME => Invoice,
46
- InvoiceItem::OBJECT_NAME => InvoiceItem,
47
- InvoiceLineItem::OBJECT_NAME => InvoiceLineItem,
48
- LoginLink::OBJECT_NAME => LoginLink,
49
- Order::OBJECT_NAME => Order,
50
- OrderReturn::OBJECT_NAME => OrderReturn,
51
- Payout::OBJECT_NAME => Payout,
52
- Plan::OBJECT_NAME => Plan,
53
- Product::OBJECT_NAME => Product,
54
- Recipient::OBJECT_NAME => Recipient,
55
- RecipientTransfer::OBJECT_NAME => RecipientTransfer,
56
- Refund::OBJECT_NAME => Refund,
57
- Reversal::OBJECT_NAME => Reversal,
58
- SKU::OBJECT_NAME => SKU,
59
- Source::OBJECT_NAME => Source,
60
- Subscription::OBJECT_NAME => Subscription,
61
- SubscriptionItem::OBJECT_NAME => SubscriptionItem,
62
- ThreeDSecure::OBJECT_NAME => ThreeDSecure,
63
- Token::OBJECT_NAME => Token,
64
- Transfer::OBJECT_NAME => Transfer,
65
- }
43
+ @object_classes ||= Stripe::ObjectTypes.object_names_to_classes
44
+ end
45
+
46
+ def self.object_name_matches_class?(object_name, klass)
47
+ Util.object_classes[object_name] == klass
66
48
  end
67
49
 
68
50
  # Converts a hash of fields or an array of hashes into a +StripeObject+ or
@@ -78,51 +60,48 @@ module Stripe
78
60
  # * +opts+ - Options for +StripeObject+ like an API key that will be reused
79
61
  # on subsequent API calls.
80
62
  def self.convert_to_stripe_object(data, opts = {})
63
+ opts = normalize_opts(opts)
64
+
81
65
  case data
82
66
  when Array
83
67
  data.map { |i| convert_to_stripe_object(i, opts) }
84
68
  when Hash
85
- # Try converting to a known object class. If none available, fall back to generic StripeObject
86
- object_classes.fetch(data[:object], StripeObject).construct_from(data, opts)
69
+ # Try converting to a known object class. If none available, fall back
70
+ # to generic StripeObject
71
+ object_classes.fetch(data[:object], StripeObject)
72
+ .construct_from(data, opts)
87
73
  else
88
74
  data
89
75
  end
90
76
  end
91
77
 
92
78
  def self.log_error(message, data = {})
93
- if !Stripe.logger.nil? ||
94
- !Stripe.log_level.nil? && Stripe.log_level <= Stripe::LEVEL_ERROR
95
- log_internal(message, data, color: :cyan,
96
- level: Stripe::LEVEL_ERROR, logger: Stripe.logger, out: $stderr)
79
+ config = data.delete(:config) || Stripe.config
80
+ logger = config.logger || Stripe.logger
81
+ if !logger.nil? ||
82
+ !config.log_level.nil? && config.log_level <= Stripe::LEVEL_ERROR
83
+ log_internal(message, data, color: :cyan, level: Stripe::LEVEL_ERROR,
84
+ logger: Stripe.logger, out: $stderr)
97
85
  end
98
86
  end
99
87
 
100
88
  def self.log_info(message, data = {})
101
- if !Stripe.logger.nil? ||
102
- !Stripe.log_level.nil? && Stripe.log_level <= Stripe::LEVEL_INFO
103
- log_internal(message, data, color: :cyan,
104
- level: Stripe::LEVEL_INFO, logger: Stripe.logger, out: $stdout)
89
+ config = data.delete(:config) || Stripe.config
90
+ logger = config.logger || Stripe.logger
91
+ if !logger.nil? ||
92
+ !config.log_level.nil? && config.log_level <= Stripe::LEVEL_INFO
93
+ log_internal(message, data, color: :cyan, level: Stripe::LEVEL_INFO,
94
+ logger: Stripe.logger, out: $stdout)
105
95
  end
106
96
  end
107
97
 
108
98
  def self.log_debug(message, data = {})
109
- if !Stripe.logger.nil? ||
110
- !Stripe.log_level.nil? && Stripe.log_level <= Stripe::LEVEL_DEBUG
111
- log_internal(message, data, color: :blue,
112
- level: Stripe::LEVEL_DEBUG, logger: Stripe.logger, out: $stdout)
113
- end
114
- end
115
-
116
- def self.file_readable(file)
117
- # This is nominally equivalent to File.readable?, but that can
118
- # report incorrect results on some more oddball filesystems
119
- # (such as AFS)
120
- begin
121
- File.open(file) { |f| }
122
- rescue
123
- false
124
- else
125
- true
99
+ config = data.delete(:config) || Stripe.config
100
+ logger = config.logger || Stripe.logger
101
+ if !logger.nil? ||
102
+ !config.log_level.nil? && config.log_level <= Stripe::LEVEL_DEBUG
103
+ log_internal(message, data, color: :blue, level: Stripe::LEVEL_DEBUG,
104
+ logger: Stripe.logger, out: $stdout)
126
105
  end
127
106
  end
128
107
 
@@ -131,7 +110,11 @@ module Stripe
131
110
  when Hash
132
111
  new_hash = {}
133
112
  object.each do |key, value|
134
- key = (key.to_sym rescue key) || key
113
+ key = (begin
114
+ key.to_sym
115
+ rescue StandardError
116
+ key
117
+ end) || key
135
118
  new_hash[key] = symbolize_names(value)
136
119
  end
137
120
  new_hash
@@ -147,22 +130,8 @@ module Stripe
147
130
  # involves escaping special characters from parameter keys and values (e.g.
148
131
  # `&`).
149
132
  def self.encode_parameters(params)
150
- Util.flatten_params(params).
151
- map { |k,v| "#{url_encode(k)}=#{url_encode(v)}" }.join('&')
152
- end
153
-
154
- # Transforms an array into a hash with integer keys. Used for a small
155
- # number of API endpoints. If the argument is not an Array, return it
156
- # unchanged. Example: [{foo: 'bar'}] => {"0" => {foo: "bar"}}
157
- def self.array_to_hash(array)
158
- case array
159
- when Array
160
- hash = {}
161
- array.each_with_index { |v,i| hash[i.to_s] = v }
162
- hash
163
- else
164
- array
165
- end
133
+ Util.flatten_params(params)
134
+ .map { |k, v| "#{url_encode(k)}=#{url_encode(v)}" }.join("&")
166
135
  end
167
136
 
168
137
  # Encodes a string in a way that makes it suitable for use in a set of
@@ -173,20 +142,19 @@ module Stripe
173
142
  # Don't use strict form encoding by changing the square bracket control
174
143
  # characters back to their literals. This is fine by the server, and
175
144
  # makes these parameter strings easier to read.
176
- gsub('%5B', '[').gsub('%5D', ']')
145
+ gsub("%5B", "[").gsub("%5D", "]")
177
146
  end
178
147
 
179
- def self.flatten_params(params, parent_key=nil)
148
+ def self.flatten_params(params, parent_key = nil)
180
149
  result = []
181
150
 
182
151
  # do not sort the final output because arrays (and arrays of hashes
183
152
  # especially) can be order sensitive, but do sort incoming parameters
184
153
  params.each do |key, value|
185
- calculated_key = parent_key ? "#{parent_key}[#{key}]" : "#{key}"
154
+ calculated_key = parent_key ? "#{parent_key}[#{key}]" : key.to_s
186
155
  if value.is_a?(Hash)
187
156
  result += flatten_params(value, calculated_key)
188
157
  elsif value.is_a?(Array)
189
- check_array_of_maps_start_keys!(value)
190
158
  result += flatten_params_array(value, calculated_key)
191
159
  else
192
160
  result << [calculated_key, value]
@@ -198,20 +166,32 @@ module Stripe
198
166
 
199
167
  def self.flatten_params_array(value, calculated_key)
200
168
  result = []
201
- value.each do |elem|
169
+ value.each_with_index do |elem, i|
202
170
  if elem.is_a?(Hash)
203
- result += flatten_params(elem, "#{calculated_key}[]")
171
+ result += flatten_params(elem, "#{calculated_key}[#{i}]")
204
172
  elsif elem.is_a?(Array)
205
173
  result += flatten_params_array(elem, calculated_key)
206
174
  else
207
- result << ["#{calculated_key}[]", elem]
175
+ result << ["#{calculated_key}[#{i}]", elem]
208
176
  end
209
177
  end
210
178
  result
211
179
  end
212
180
 
181
+ # `Time.now` can be unstable in cases like an administrator manually
182
+ # updating its value or a reconcilation via NTP. For this reason, prefer
183
+ # the use of the system's monotonic clock especially where comparing times
184
+ # to calculate an elapsed duration.
185
+ #
186
+ # Shortcut for getting monotonic time, mostly for purposes of line length
187
+ # and test stubbing. Returns time in seconds since the event used for
188
+ # monotonic reference purposes by the platform (e.g. system boot time).
189
+ def self.monotonic_time
190
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
191
+ end
192
+
213
193
  def self.normalize_id(id)
214
- if id.kind_of?(Hash) # overloaded id
194
+ if id.is_a?(Hash) # overloaded id
215
195
  params_hash = id.dup
216
196
  id = params_hash.delete(:id)
217
197
  else
@@ -225,22 +205,26 @@ module Stripe
225
205
  def self.normalize_opts(opts)
226
206
  case opts
227
207
  when String
228
- {:api_key => opts}
208
+ { api_key: opts }
229
209
  when Hash
230
- check_api_key!(opts.fetch(:api_key)) if opts.has_key?(:api_key)
231
- opts.clone
210
+ check_api_key!(opts.fetch(:api_key)) if opts.key?(:api_key)
211
+ # Explicitly use dup here instead of clone to avoid preserving freeze
212
+ # state on input params.
213
+ opts.dup
232
214
  else
233
- raise TypeError.new('normalize_opts expects a string or a hash')
215
+ raise TypeError, "normalize_opts expects a string or a hash"
234
216
  end
235
217
  end
236
218
 
237
219
  def self.check_string_argument!(key)
238
- raise TypeError.new("argument must be a string") unless key.is_a?(String)
220
+ raise TypeError, "argument must be a string" unless key.is_a?(String)
221
+
239
222
  key
240
223
  end
241
224
 
242
225
  def self.check_api_key!(key)
243
- raise TypeError.new("api_key must be a string") unless key.is_a?(String)
226
+ raise TypeError, "api_key must be a string" unless key.is_a?(String)
227
+
244
228
  key
245
229
  end
246
230
 
@@ -250,15 +234,11 @@ module Stripe
250
234
  # certain key values when the user could have set them with a variety of
251
235
  # diffent naming schemes.
252
236
  def self.normalize_headers(headers)
253
- headers.inject({}) do |new_headers, (k, v)|
254
- if k.is_a?(Symbol)
255
- k = titlecase_parts(k.to_s.gsub("_", "-"))
256
- elsif k.is_a?(String)
257
- k = titlecase_parts(k)
258
- end
237
+ headers.each_with_object({}) do |(k, v), new_headers|
238
+ k = k.to_s.tr("_", "-") if k.is_a?(Symbol)
239
+ k = k.split("-").reject(&:empty?).map(&:capitalize).join("-")
259
240
 
260
241
  new_headers[k] = v
261
- new_headers
262
242
  end
263
243
  end
264
244
 
@@ -272,69 +252,33 @@ module Stripe
272
252
 
273
253
  # Constant time string comparison to prevent timing attacks
274
254
  # Code borrowed from ActiveSupport
275
- def self.secure_compare(a, b)
276
- return false unless a.bytesize == b.bytesize
255
+ def self.secure_compare(str_a, str_b)
256
+ return false unless str_a.bytesize == str_b.bytesize
277
257
 
278
- l = a.unpack "C#{a.bytesize}"
258
+ l = str_a.unpack "C#{str_a.bytesize}"
279
259
 
280
260
  res = 0
281
- b.each_byte { |byte| res |= byte ^ l.shift }
282
- res == 0
261
+ str_b.each_byte { |byte| res |= byte ^ l.shift }
262
+ res.zero?
283
263
  end
284
264
 
285
- private
265
+ #
266
+ # private
267
+ #
286
268
 
287
269
  COLOR_CODES = {
288
- :black => 0, :light_black => 60,
289
- :red => 1, :light_red => 61,
290
- :green => 2, :light_green => 62,
291
- :yellow => 3, :light_yellow => 63,
292
- :blue => 4, :light_blue => 64,
293
- :magenta => 5, :light_magenta => 65,
294
- :cyan => 6, :light_cyan => 66,
295
- :white => 7, :light_white => 67,
296
- :default => 9
297
- }
270
+ black: 0, light_black: 60,
271
+ red: 1, light_red: 61,
272
+ green: 2, light_green: 62,
273
+ yellow: 3, light_yellow: 63,
274
+ blue: 4, light_blue: 64,
275
+ magenta: 5, light_magenta: 65,
276
+ cyan: 6, light_cyan: 66,
277
+ white: 7, light_white: 67,
278
+ default: 9,
279
+ }.freeze
298
280
  private_constant :COLOR_CODES
299
281
 
300
- # We use a pretty janky version of form encoding (Rack's) that supports
301
- # more complex data structures like maps and arrays through the use of
302
- # specialized syntax. To encode an array of maps like:
303
- #
304
- # [{a: 1, b: 2}, {a: 3, b: 4}]
305
- #
306
- # We have to produce something that looks like this:
307
- #
308
- # arr[][a]=1&arr[][b]=2&arr[][a]=3&arr[][b]=4
309
- #
310
- # The only way for the server to recognize that this is a two item array is
311
- # that it notices the repetition of element "a", so it's key that these
312
- # repeated elements are encoded first.
313
- #
314
- # This method is invoked for any arrays being encoded and checks that if
315
- # the array contains all non-empty maps, that each of those maps must start
316
- # with the same key so that their boundaries can be properly encoded.
317
- def self.check_array_of_maps_start_keys!(arr)
318
- expected_key = nil
319
- arr.each do |item|
320
- return if !item.is_a?(Hash)
321
- return if item.count == 0
322
-
323
- first_key = item.first[0]
324
-
325
- if expected_key
326
- if expected_key != first_key
327
- raise ArgumentError,
328
- "All maps nested in an array should start with the same key " +
329
- "(expected starting key '#{expected_key}', got '#{first_key}')"
330
- end
331
- else
332
- expected_key = first_key
333
- end
334
- end
335
- end
336
- private_class_method :check_array_of_maps_start_keys!
337
-
338
282
  # Uses an ANSI escape code to colorize text if it's going to be sent to a
339
283
  # TTY.
340
284
  def self.colorize(val, color, isatty)
@@ -359,40 +303,36 @@ module Stripe
359
303
  end
360
304
  private_class_method :level_name
361
305
 
362
- # TODO: Make these named required arguments when we drop support for Ruby
363
- # 2.0.
364
- def self.log_internal(message, data = {}, color: nil, level: nil, logger: nil, out: nil)
365
- data_str = data.select { |k,v| !v.nil? }.
366
- map { |(k,v)|
367
- "%s=%s" % [
368
- colorize(k, color, !out.nil? && out.isatty),
369
- wrap_logfmt_value(v)
370
- ]
371
- }.join(" ")
306
+ def self.log_internal(message, data = {}, color:, level:, logger:, out:)
307
+ data_str = data.reject { |_k, v| v.nil? }
308
+ .map do |(k, v)|
309
+ format("%<key>s=%<value>s",
310
+ key: colorize(k, color, logger.nil? && !out.nil? && out.isatty),
311
+ value: wrap_logfmt_value(v))
312
+ end.join(" ")
372
313
 
373
314
  if !logger.nil?
374
315
  # the library's log levels are mapped to the same values as the
375
316
  # standard library's logger
376
317
  logger.log(level,
377
- "message=%s %s" % [wrap_logfmt_value(message), data_str])
318
+ format("message=%<message>s %<data_str>s",
319
+ message: wrap_logfmt_value(message),
320
+ data_str: data_str))
378
321
  elsif out.isatty
379
- out.puts "%s %s %s" %
380
- [colorize(level_name(level)[0, 4].upcase, color, out.isatty), message, data_str]
322
+ out.puts format("%<level>s %<message>s %<data_str>s",
323
+ level: colorize(level_name(level)[0, 4].upcase,
324
+ color, out.isatty),
325
+ message: message,
326
+ data_str: data_str)
381
327
  else
382
- out.puts "message=%s level=%s %s" %
383
- [wrap_logfmt_value(message), level_name(level), data_str]
328
+ out.puts format("message=%<message>s level=%<level>s %<data_str>s",
329
+ message: wrap_logfmt_value(message),
330
+ level: level_name(level),
331
+ data_str: data_str)
384
332
  end
385
333
  end
386
334
  private_class_method :log_internal
387
335
 
388
- def self.titlecase_parts(s)
389
- s.split("-").
390
- select { |p| p != "" }.
391
- map { |p| p[0].upcase + p[1..-1].downcase }.
392
- join("-")
393
- end
394
- private_class_method :titlecase_parts
395
-
396
336
  # Wraps a value in double quotes if it looks sufficiently complex so that
397
337
  # it can be read by logfmt parsers.
398
338
  def self.wrap_logfmt_value(val)
@@ -406,7 +346,7 @@ module Stripe
406
346
  if %r{[^\w\-/]} =~ val
407
347
  # If the string contains any special characters, escape any double
408
348
  # quotes it has, remove newlines, and wrap the whole thing in quotes.
409
- %{"%s"} % val.gsub('"', '\"').gsub("\n", "")
349
+ format(%("%<value>s"), value: val.gsub('"', '\"').delete("\n"))
410
350
  else
411
351
  # Otherwise use the basic value if it looks like a standard set of
412
352
  # characters (and allow a few special characters like hyphens, and
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Stripe
2
- VERSION = '3.3.1'
4
+ VERSION = "5.38.0"
3
5
  end