diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 629f4c4a..0c0353a1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,7 +46,8 @@ jobs: runs-on: ubuntu-22.04 strategy: matrix: - ruby-version: [2.3, 2.4, 2.5, 2.6, 2.7, '3.0', 3.1, 3.2, 3.3, 3.4, jruby-9.4.0.0, truffleruby-head] + # following https://docs.stripe.com/sdks/versioning?lang=ruby#stripe-sdk-language-version-support-policy + ruby-version: [2.6, 2.7, '3.0', 3.1, 3.2, 3.3, 3.4, jruby-9.4.0.0, truffleruby-head] steps: - uses: extractions/setup-just@v2 - uses: actions/checkout@v3 diff --git a/.rubocop.yml b/.rubocop.yml index fecb84b3..a12b40ef 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -2,7 +2,8 @@ inherit_from: .rubocop_todo.yml AllCops: DisplayCopNames: true - TargetRubyVersion: 2.3 + TargetRubyVersion: 2.6 + SuggestExtensions: false Layout/CaseIndentation: EnforcedStyle: end @@ -72,14 +73,6 @@ Metrics/MethodLength: - initialize - inner_class_types -# TODO(xavdid): remove this once the first `basil` release is out -Naming/MethodName: - # these endpoints are removed soon so we pulled their overrides, meaning their names are wrong - # that won't make it out to users, but it's breaking linting/formatting in the meantime - Exclude: - - "lib/stripe/services/invoice_service.rb" - - "lib/stripe/resources/invoice.rb" - Metrics/ModuleLength: Enabled: false @@ -121,6 +114,9 @@ Style/HashTransformKeys: Style/HashTransformValues: Enabled: true + Exclude: + # RUN_DEVSDK-1956 + - "lib/stripe/api_requestor.rb" Style/NumericPredicate: Enabled: false @@ -265,6 +261,9 @@ Style/MapCompactWithConditionalBlock: # new in 1.30 Enabled: true Style/MapToHash: # new in 1.24 Enabled: true + Exclude: + # RUN_DEVSDK-1956 + - "lib/stripe/api_requestor.rb" Style/MapToSet: # new in 1.42 Enabled: true Style/MinMaxComparison: # new in 1.42 diff --git a/CHANGELOG.md b/CHANGELOG.md index bf5ab0ad..4a158e82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -69,7 +69,7 @@ This release changes the pinned API version to `2025-09-30.clover` and contains - Add the `StripeContext` class. Previously you could set the stripe_context to only a string value. Now you can use the new class as well - ⚠️ Change `EventNotification` (formerly known as `ThinEvent`)'s `context` property from `string` to `StripeContext` * [#1684](https://github.com/stripe/stripe-ruby/pull/1684) ⚠️ Drop support for Ruby < 2.6 & clarify version policy - - Read our new [language version support policy](https://docs.stripe.com/sdks/versioning?server=ruby#stripe-sdk-language-version-support-policy) + - Read our new [language version support policy](https://docs.stripe.com/sdks/versioning?lang=ruby#stripe-sdk-language-version-support-policy) - ⚠️ In this release, we drop support for Ruby 2.3, 2.4, and 2.5 - Ruby 2.6 support is deprecated and will be removed in the next scheduled major release (March 2026) * [#1651](https://github.com/stripe/stripe-ruby/pull/1651) ⚠️ Build SDK w/ V2 OpenAPI spec diff --git a/README.md b/README.md index bbbdb3cd..99cadaf7 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,9 @@ gem build stripe.gemspec ### Requirements -- Ruby 2.3+. +Per our [Language Version Support Policy](https://docs.stripe.com/sdks/versioning?lang=ruby#stripe-sdk-language-version-support-policy), we currently support **Ruby 2.6+**. + +Support for Ruby 2.6 and 2.7 is deprecated and will be removed in upcoming major versions. Read more and see the full schedule in the docs: https://docs.stripe.com/sdks/versioning?lang=ruby#stripe-sdk-language-version-support-policy ### Bundler @@ -55,8 +57,7 @@ gem 'stripe' ## Usage The library needs to be configured with your account's secret key which is -available in your [Stripe Dashboard][api-keys]. Set `Stripe.api_key` to its -value: +available in your [Stripe Dashboard][api-keys]. Initialize a new client with your API key: ```ruby require 'stripe' diff --git a/examples/event_notification_webhook_handler.rb b/examples/event_notification_webhook_handler.rb index 8b9cc83f..7728a469 100644 --- a/examples/event_notification_webhook_handler.rb +++ b/examples/event_notification_webhook_handler.rb @@ -7,10 +7,12 @@ # In this example, we: # - create a StripeClient called client # - use client.parse_event_notification to parse the received event notification webhook body -# - call client.v2.core.events.retrieve to retrieve the full event object +# - call event_notification.fetch_event to retrieve the full event object # - if it is a V1BillingMeterErrorReportTriggeredEvent event type, call -# event.fetchRelatedObject to retrieve the Billing Meter object associated +# event_notification.fetch_related_object to retrieve the Billing Meter object associated # with the event. +# - if it is an UnknownEventNotification, check the type property to see if it matches +# a known event type and handle it accordingly require "stripe" require "sinatra" @@ -27,9 +29,23 @@ post "/webhook" do event_notification = client.parse_event_notification(webhook_body, sig_header, webhook_secret) if event_notification.instance_of?(Stripe::Events::V1BillingMeterErrorReportTriggeredEventNotification) + # there's basic info about the related object in the notification + puts "Received event for meter", event_notification.related_object.id + + # but you can also fetch it if you need more info meter = event_notification.fetch_related_object - meter_id = meter.id - puts "Success!", meter_id + puts "Meter name:", meter.display_name + + # there's often more information on the actual event + event = event_notification.fetch_event + + puts "Meter had an error", event.data.developer_message_summary + elsif event_notification.instance_of?(Stripe::Events::UnknownEventNotification) + # this is a valid event type, but this SDK predates it + # we'll have to match on type instead + if event_notification.type == "some.new.event" + # your logic goes here + end end # Record the failures and alert your team diff --git a/lib/stripe.rb b/lib/stripe.rb index 4f1d3327..37cf3e59 100644 --- a/lib/stripe.rb +++ b/lib/stripe.rb @@ -160,18 +160,18 @@ module Stripe end def self.add_beta_version(beta_name, version) - unless version.start_with?("v") && version[1..-1].to_i.to_s == version[1..-1] + unless version.start_with?("v") && version[1..].to_i.to_s == version[1..] raise ArgumentError, "Version must be in the format 'v' followed by a number (e.g., 'v3')" end if (index = api_version.index("; #{beta_name}=")) start_index = index + "; #{beta_name}=".length end_index = api_version.index(";", start_index) || api_version.length - current_version = api_version[start_index...end_index][1..-1].to_i - new_version = version[1..-1].to_i + current_version = api_version[start_index...end_index][1..].to_i + new_version = version[1..].to_i return if new_version <= current_version # Keep the higher version, no update needed - self.api_version = api_version[0...index] + "; #{beta_name}=#{version}" + api_version[end_index..-1] + self.api_version = api_version[0...index] + "; #{beta_name}=#{version}" + api_version[end_index..] else self.api_version = "#{api_version}; #{beta_name}=#{version}" end diff --git a/lib/stripe/api_requestor.rb b/lib/stripe/api_requestor.rb index eed95ed6..c96ec174 100644 --- a/lib/stripe/api_requestor.rb +++ b/lib/stripe/api_requestor.rb @@ -572,8 +572,10 @@ module Stripe headers["Content-Type"] = content_type # `#to_s` any complex objects like files and the like to build output - # that's more condusive to logging. + # that's more conducive to logging. flattened_params = + # https://go/j/RUN_DEVSDK-1956 - this is probably a bug + # once fixed, you can remove the exclusions referencing this ticket in .rubocop.yml flattened_params.map { |k, v| [k, v.is_a?(String) ? v : v.to_s] }.to_h elsif api_mode == :v2 diff --git a/lib/stripe/request_options.rb b/lib/stripe/request_options.rb index beb596e0..ba2e2a2a 100644 --- a/lib/stripe/request_options.rb +++ b/lib/stripe/request_options.rb @@ -132,9 +132,7 @@ module Stripe # Get options that are copyable from StripeObject to StripeObject def self.copyable(req_opts) - req_opts.select do |k, _v| - RequestOptions::OPTS_COPYABLE.include?(k) - end + req_opts.slice(*RequestOptions::OPTS_COPYABLE) end end end diff --git a/lib/stripe/stripe_client.rb b/lib/stripe/stripe_client.rb index 228f8e12..5e4d7236 100644 --- a/lib/stripe/stripe_client.rb +++ b/lib/stripe/stripe_client.rb @@ -43,7 +43,7 @@ module Stripe connect_base: connect_base, meter_events_base: meter_events_base, client_id: client_id, - }.reject { |_k, v| v.nil? } + }.compact config = StripeConfiguration.client_init(config_opts) @requestor = APIRequestor.new(config) diff --git a/lib/stripe/stripe_configuration.rb b/lib/stripe/stripe_configuration.rb index bd5b7b2a..87e5979b 100644 --- a/lib/stripe/stripe_configuration.rb +++ b/lib/stripe/stripe_configuration.rb @@ -46,12 +46,7 @@ module Stripe imported_options = USER_CONFIGURABLE_GLOBAL_OPTIONS - StripeClient::CLIENT_OPTIONS client_config = StripeConfiguration.setup do |instance| imported_options.each do |key| - begin - instance.public_send("#{key}=", global_config.public_send(key)) if global_config.respond_to?(key) - rescue NotImplementedError => e - # In Ruby <= 2.5, we can't set write_timeout on Net::HTTP, log an error and continue - Util.log_error("Failed to set #{key} on client configuration: #{e}") - end + instance.public_send("#{key}=", global_config.public_send(key)) if global_config.respond_to?(key) end end client_config.reverse_duplicate_merge(config_opts) diff --git a/lib/stripe/stripe_object.rb b/lib/stripe/stripe_object.rb index 72608adb..7fbe2e14 100644 --- a/lib/stripe/stripe_object.rb +++ b/lib/stripe/stripe_object.rb @@ -202,13 +202,13 @@ module Stripe value.respond_to?(:to_hash) ? value.to_hash : value end - @values.each_with_object({}) do |(key, value), acc| - acc[key] = case value - when Array - value.map(&maybe_to_hash) - else - maybe_to_hash.call(value) - end + @values.transform_values do |value| + case value + when Array + value.map(&maybe_to_hash) + else + maybe_to_hash.call(value) + end end end @@ -273,7 +273,7 @@ module Stripe # a `nil` that makes it out of `#serialize_params_value` signals an empty # value that we shouldn't appear in the serialized form of the object - update_hash.reject! { |_, v| v.nil? } + update_hash.compact! update_hash end diff --git a/lib/stripe/util.rb b/lib/stripe/util.rb index fa61cb3b..2ef7dd8b 100644 --- a/lib/stripe/util.rb +++ b/lib/stripe/util.rb @@ -334,7 +334,7 @@ module Stripe def self.valid_variable_name?(key) return false if key.empty? || key[0] !~ LEGAL_FIRST_CHARACTER - key[1..-1].chars.all? { |char| char =~ LEGAL_VARIABLE_CHARACTER } + key[1..].chars.all? { |char| char =~ LEGAL_VARIABLE_CHARACTER } end def self.check_string_argument!(key) @@ -434,7 +434,7 @@ module Stripe private_class_method :level_name def self.log_internal(message, data = {}, color:, level:, logger:, out:) - data_str = data.reject { |_k, v| v.nil? } + data_str = data.compact .map do |(k, v)| format("%s=%s", key: colorize(k, color, logger.nil? && !out.nil? && out.isatty), diff --git a/stripe.gemspec b/stripe.gemspec index 2f84861f..d4cb3ecb 100644 --- a/stripe.gemspec +++ b/stripe.gemspec @@ -7,7 +7,7 @@ require "stripe/version" Gem::Specification.new do |s| s.name = "stripe" s.version = Stripe::VERSION - s.required_ruby_version = ">= 2.3.0" + s.required_ruby_version = ">= 2.6.0" s.summary = "Ruby bindings for the Stripe API" s.description = "Stripe is the easiest way to accept payments online. " \ "See https://stripe.com for details." diff --git a/test/stripe/api_requestor_test.rb b/test/stripe/api_requestor_test.rb index 855b06db..1a58c189 100644 --- a/test/stripe/api_requestor_test.rb +++ b/test/stripe/api_requestor_test.rb @@ -728,18 +728,16 @@ module Stripe context "Stripe-Account header" do should "use a globally set header" do - begin - old = Stripe.stripe_account - Stripe.stripe_account = "acct_1234" + old = Stripe.stripe_account + Stripe.stripe_account = "acct_1234" - stub_request(:post, "#{Stripe::DEFAULT_API_BASE}/v1/account") - .with(headers: { "Stripe-Account" => Stripe.stripe_account }) - .to_return(body: JSON.generate(object: "account")) + stub_request(:post, "#{Stripe::DEFAULT_API_BASE}/v1/account") + .with(headers: { "Stripe-Account" => Stripe.stripe_account }) + .to_return(body: JSON.generate(object: "account")) - Stripe::Account.create - ensure - Stripe.stripe_account = old - end + Stripe::Account.create + ensure + Stripe.stripe_account = old end should "use a local request set header" do @@ -798,41 +796,39 @@ module Stripe context "app_info" do should "send app_info if set" do - begin - old = Stripe.app_info - Stripe.set_app_info( - "MyAwesomePlugin", - partner_id: "partner_1234", - url: "https://myawesomeplugin.info", - version: "1.2.34" - ) + old = Stripe.app_info + Stripe.set_app_info( + "MyAwesomePlugin", + partner_id: "partner_1234", + url: "https://myawesomeplugin.info", + version: "1.2.34" + ) - stub_request(:post, "#{Stripe::DEFAULT_API_BASE}/v1/account") - .with do |req| - assert_equal \ - "Stripe/v1 RubyBindings/#{Stripe::VERSION} " \ - "MyAwesomePlugin/1.2.34 (https://myawesomeplugin.info)", - req.headers["User-Agent"] + stub_request(:post, "#{Stripe::DEFAULT_API_BASE}/v1/account") + .with do |req| + assert_equal \ + "Stripe/v1 RubyBindings/#{Stripe::VERSION} " \ + "MyAwesomePlugin/1.2.34 (https://myawesomeplugin.info)", + req.headers["User-Agent"] - data = JSON.parse(req.headers["X-Stripe-Client-User-Agent"], - symbolize_names: true) + data = JSON.parse(req.headers["X-Stripe-Client-User-Agent"], + symbolize_names: true) - assert_equal({ - name: "MyAwesomePlugin", - partner_id: "partner_1234", - url: "https://myawesomeplugin.info", - version: "1.2.34", - }, data[:application]) + assert_equal({ + name: "MyAwesomePlugin", + partner_id: "partner_1234", + url: "https://myawesomeplugin.info", + version: "1.2.34", + }, data[:application]) - true - end.to_return(body: JSON.generate(object: "account")) + true + end.to_return(body: JSON.generate(object: "account")) - client = APIRequestor.new("sk_test_123") - client.send(request_method, :post, "/v1/account", :api, - &@read_body_chunk_block) - ensure - Stripe.app_info = old - end + client = APIRequestor.new("sk_test_123") + client.send(request_method, :post, "/v1/account", :api, + &@read_body_chunk_block) + ensure + Stripe.app_info = old end end @@ -1361,17 +1357,15 @@ module Stripe end should "reset local thread state after a call" do - begin - APIRequestor.current_thread_context.active_requestor = :api_requestor + APIRequestor.current_thread_context.active_requestor = :api_requestor - client = APIRequestor.new("sk_test_123") - client.request { 0 } + client = APIRequestor.new("sk_test_123") + client.request { 0 } - assert_equal :api_requestor, - APIRequestor.current_thread_context.active_requestor - ensure - APIRequestor.current_thread_context.active_requestor = nil - end + assert_equal :api_requestor, + APIRequestor.current_thread_context.active_requestor + ensure + APIRequestor.current_thread_context.active_requestor = nil end should "correctly return last responses despite multiple clients" do @@ -1468,25 +1462,23 @@ module Stripe context "#proxy" do should "run the request through the proxy" do - begin - APIRequestor.clear_all_connection_managers + APIRequestor.clear_all_connection_managers - Stripe.proxy = "http://user:pass@localhost:8080" + Stripe.proxy = "http://user:pass@localhost:8080" - client = APIRequestor.new("sk_test_123") - client.request { 0 } + client = APIRequestor.new("sk_test_123") + client.request { 0 } - connection = Stripe::APIRequestor.default_connection_manager.connection_for(Stripe::DEFAULT_API_BASE) + connection = Stripe::APIRequestor.default_connection_manager.connection_for(Stripe::DEFAULT_API_BASE) - assert_equal "localhost", connection.proxy_address - assert_equal 8080, connection.proxy_port - assert_equal "user", connection.proxy_user - assert_equal "pass", connection.proxy_pass - ensure - Stripe.proxy = nil + assert_equal "localhost", connection.proxy_address + assert_equal 8080, connection.proxy_port + assert_equal "user", connection.proxy_user + assert_equal "pass", connection.proxy_pass + ensure + Stripe.proxy = nil - APIRequestor.clear_all_connection_managers - end + APIRequestor.clear_all_connection_managers end end diff --git a/test/stripe/generated_examples_test.rb b/test/stripe/generated_examples_test.rb index 6cbacf8c..6b108b59 100644 --- a/test/stripe/generated_examples_test.rb +++ b/test/stripe/generated_examples_test.rb @@ -3843,13 +3843,34 @@ module Stripe assert_requested :get, "#{Stripe::DEFAULT_API_BASE}/v1/promotion_codes/promo_xxxxxxxxxxxxx" end should "Test promotion codes post" do + promotion_code = Stripe::PromotionCode.create({ + promotion: { + type: "coupon", + coupon: "Z4OV52SU", + }, + }) + assert_requested :post, "#{Stripe.api_base}/v1/promotion_codes" + end + should "Test promotion codes post (service)" do + stub_request(:post, "#{Stripe::DEFAULT_API_BASE}/v1/promotion_codes").to_return(body: "{}") + client = Stripe::StripeClient.new("sk_test_123") + + promotion_code = client.v1.promotion_codes.create({ + promotion: { + type: "coupon", + coupon: "Z4OV52SU", + }, + }) + assert_requested :post, "#{Stripe::DEFAULT_API_BASE}/v1/promotion_codes" + end + should "Test promotion codes post 2" do promotion_code = Stripe::PromotionCode.update( "promo_xxxxxxxxxxxxx", { metadata: { order_id: "6735" } } ) assert_requested :post, "#{Stripe.api_base}/v1/promotion_codes/promo_xxxxxxxxxxxxx" end - should "Test promotion codes post (service)" do + should "Test promotion codes post 2 (service)" do stub_request( :post, "#{Stripe::DEFAULT_API_BASE}/v1/promotion_codes/promo_xxxxxxxxxxxxx" diff --git a/test/stripe/promotion_code_test.rb b/test/stripe/promotion_code_test.rb deleted file mode 100644 index 82e547ae..00000000 --- a/test/stripe/promotion_code_test.rb +++ /dev/null @@ -1,42 +0,0 @@ -# frozen_string_literal: true - -require File.expand_path("../test_helper", __dir__) - -module Stripe - class PromotionCodeTest < Test::Unit::TestCase - should "be listable" do - promotion_codes = Stripe::PromotionCode.list - assert_requested :get, "#{Stripe.api_base}/v1/promotion_codes" - assert promotion_codes.data.is_a?(Array) - assert promotion_codes.first.is_a?(Stripe::PromotionCode) - end - - should "be retrievable" do - coupon = Stripe::PromotionCode.retrieve("PROMO_123") - assert_requested :get, "#{Stripe.api_base}/v1/promotion_codes/PROMO_123" - assert coupon.is_a?(Stripe::PromotionCode) - end - - should "be creatable" do - coupon = Stripe::PromotionCode.create( - coupon: "co_123", - code: "MYCODE" - ) - assert_requested :post, "#{Stripe.api_base}/v1/promotion_codes" - assert coupon.is_a?(Stripe::PromotionCode) - end - - should "be saveable" do - coupon = Stripe::PromotionCode.retrieve("PROMO_123") - coupon.metadata["key"] = "value" - coupon.save - assert_requested :post, "#{Stripe.api_base}/v1/promotion_codes/#{coupon.id}" - end - - should "be updateable" do - coupon = Stripe::PromotionCode.update("PROMO_123", metadata: { key: "value" }) - assert_requested :post, "#{Stripe.api_base}/v1/promotion_codes/PROMO_123" - assert coupon.is_a?(Stripe::PromotionCode) - end - end -end diff --git a/test/stripe/stripe_configuration_test.rb b/test/stripe/stripe_configuration_test.rb index 31fd3724..a1ff5cae 100644 --- a/test/stripe/stripe_configuration_test.rb +++ b/test/stripe/stripe_configuration_test.rb @@ -126,7 +126,7 @@ module Stripe @client_opts[:api_key] = "client_test_123" @client_opts[:stripe_account] = "client_acct_123" @client_opts[:uploads_base] = "client_uploads_base.stripe.com" - @client_opts.reject! { |_k, v| v.nil? } + @client_opts.compact! client_config = Stripe::StripeConfiguration.client_init(@client_opts) diff --git a/test/stripe/stripe_object_test.rb b/test/stripe/stripe_object_test.rb index 8f763ad5..7a5b8d54 100644 --- a/test/stripe/stripe_object_test.rb +++ b/test/stripe/stripe_object_test.rb @@ -159,21 +159,19 @@ module Stripe context "#to_hash" do should "skip calling to_hash on nil" do - begin - module NilWithToHash - def to_hash - raise "Can't call to_hash on nil" - end + module NilWithToHash + def to_hash + raise "Can't call to_hash on nil" end - ::NilClass.include NilWithToHash - - hash_with_nil = { id: 3, foo: nil } - obj = StripeObject.construct_from(hash_with_nil) - expected_hash = { id: 3, foo: nil } - assert_equal expected_hash, obj.to_hash - ensure - ::NilClass.send(:undef_method, :to_hash) end + ::NilClass.include NilWithToHash + + hash_with_nil = { id: 3, foo: nil } + obj = StripeObject.construct_from(hash_with_nil) + expected_hash = { id: 3, foo: nil } + assert_equal expected_hash, obj.to_hash + ensure + ::NilClass.send(:undef_method, :to_hash) end should "recursively call to_hash on its values" do diff --git a/test/stripe_test.rb b/test/stripe_test.rb index 13d7b6e9..a23ae2df 100644 --- a/test/stripe_test.rb +++ b/test/stripe_test.rb @@ -4,23 +4,21 @@ require File.expand_path("test_helper", __dir__) class StripeTest < Test::Unit::TestCase should "allow app_info to be configured" do - begin - old = Stripe.app_info - Stripe.set_app_info( - "MyAwesomePlugin", - partner_id: "partner_1234", - url: "https://myawesomeplugin.info", - version: "1.2.34" - ) - assert_equal({ - name: "MyAwesomePlugin", - partner_id: "partner_1234", - url: "https://myawesomeplugin.info", - version: "1.2.34", - }, Stripe.app_info) - ensure - Stripe.app_info = old - end + old = Stripe.app_info + Stripe.set_app_info( + "MyAwesomePlugin", + partner_id: "partner_1234", + url: "https://myawesomeplugin.info", + version: "1.2.34" + ) + assert_equal({ + name: "MyAwesomePlugin", + partner_id: "partner_1234", + url: "https://myawesomeplugin.info", + version: "1.2.34", + }, Stripe.app_info) + ensure + Stripe.app_info = old end context "forwardable configurations" do @@ -78,14 +76,12 @@ class StripeTest < Test::Unit::TestCase end should "allow enable_telemetry to be configured" do - begin - old = Stripe.enable_telemetry? + old = Stripe.enable_telemetry? - Stripe.enable_telemetry = false - assert_equal false, Stripe.enable_telemetry? - ensure - Stripe.enable_telemetry = old - end + Stripe.enable_telemetry = false + assert_equal false, Stripe.enable_telemetry? + ensure + Stripe.enable_telemetry = old end should "allow log_level to be configured" do