diff --git a/lib/stripe.rb b/lib/stripe.rb index 890c4a9bc..13e060bf7 100644 --- a/lib/stripe.rb +++ b/lib/stripe.rb @@ -25,6 +25,7 @@ require "stripe/api_operations/nested_resource" require "stripe/api_operations/request" require "stripe/api_operations/save" +require "stripe/api_operations/singleton_save" require "stripe/api_operations/search" # API resource support classes diff --git a/lib/stripe/api_operations/singleton_save.rb b/lib/stripe/api_operations/singleton_save.rb new file mode 100644 index 000000000..d1e367cd0 --- /dev/null +++ b/lib/stripe/api_operations/singleton_save.rb @@ -0,0 +1,86 @@ +# frozen_string_literal: true + +module Stripe + module APIOperations + module SingletonSave + module ClassMethods + # Updates a singleton API resource + # + # Updates the identified resource with the passed in parameters. + # + # ==== Attributes + # + # * +params+ - A hash of parameters to pass to the API + # * +opts+ - A Hash of additional options (separate from the params / + # object values) to be added to the request. E.g. to allow for an + # idempotency_key to be passed in the request headers, or for the + # api_key to be overwritten. See + # {APIOperations::Request.execute_resource_request}. + def update(params = {}, opts = {}) + params.each_key do |k| + raise ArgumentError, "Cannot update protected field: #{k}" if protected_fields.include?(k) + end + + request_stripe_object( + method: :post, + path: resource_url, + params: params, + opts: opts + ) + end + end + + # The `save` method is DEPRECATED and will be removed in a future major + # version of the library. Use the `update` method on the resource instead. + # + # Updates a singleton API resource. + # + # If the resource doesn't yet have an assigned ID and the resource is one + # that can be created, then the method attempts to create the resource. + # The resource is updated otherwise. + # + # ==== Attributes + # + # * +params+ - Overrides any parameters in the resource's serialized data + # and includes them in the create or update. If +:req_url:+ is included + # in the list, it overrides the update URL used for the create or + # update. + # * +opts+ - A Hash of additional options (separate from the params / + # object values) to be added to the request. E.g. to allow for an + # idempotency_key to be passed in the request headers, or for the + # api_key to be overwritten. See + # {APIOperations::Request.execute_resource_request}. + def save(params = {}, opts = {}) + # We started unintentionally (sort of) allowing attributes sent to + # +save+ to override values used during the update. So as not to break + # the API, this makes that official here. + update_attributes(params) + + # Now remove any parameters that look like object attributes. + params = params.reject { |k, _| respond_to?(k) } + + values = serialize_params(self).merge(params) + + resp, opts = execute_resource_request(:post, resource_url, values, opts, ["save"]) + initialize_from(resp.data, opts) + end + extend Gem::Deprecate + deprecate :save, "the `update` class method (for examples " \ + "see https://github.com/stripe/stripe-ruby" \ + "/wiki/Migration-guide-for-v8)", 2022, 11 + + def self.included(base) + # Set `metadata` as additive so that when it's set directly we remember + # to clear keys that may have been previously set by sending empty + # values for them. + # + # It's possible that not every object with `Save` has `metadata`, but + # it's a close enough heuristic, and having this option set when there + # is no `metadata` field is not harmful. + base.additive_object_param(:metadata) + + base.extend(ClassMethods) + end + end + end +end diff --git a/lib/stripe/resources/tax/settings.rb b/lib/stripe/resources/tax/settings.rb index 6a177a0a6..2a4e25531 100644 --- a/lib/stripe/resources/tax/settings.rb +++ b/lib/stripe/resources/tax/settings.rb @@ -7,7 +7,7 @@ module Tax # # Related guide: [Using the Settings API](https://stripe.com/docs/tax/settings-api) class Settings < SingletonAPIResource - include Stripe::APIOperations::Save + include Stripe::APIOperations::SingletonSave OBJECT_NAME = "tax.settings" end diff --git a/test/stripe/api_resource_test.rb b/test/stripe/api_resource_test.rb index a0b3a98fe..d4e6a4806 100644 --- a/test/stripe/api_resource_test.rb +++ b/test/stripe/api_resource_test.rb @@ -782,6 +782,59 @@ def say_hello(params = {}, opts = {}) end end + context "singleton resource" do + class TestSingletonResource < SingletonAPIResource # rubocop:todo Lint/ConstantDefinitionInBlock + include Stripe::APIOperations::SingletonSave + OBJECT_NAME = "test.singleton" + end + + setup do + Util.instance_variable_set( + :@object_classes, + Stripe::ObjectTypes.object_names_to_classes.merge( + "test.singleton" => TestSingletonResource + ) + ) + end + teardown do + Util.class.instance_variable_set(:@object_classes, Stripe::ObjectTypes.object_names_to_classes) + end + + should "be retrievable" do + stub_request(:get, "#{Stripe.api_base}/v1/test/singleton") + .with(query: { foo: "bar" }, headers: { "Stripe-Account" => "acct_hi" }) + .to_return(body: JSON.generate({ object: "test.singleton", result: "hi!" })) + + settings = TestSingletonResource.retrieve({ foo: "bar" }, { stripe_account: "acct_hi" }) + assert settings.is_a? TestSingletonResource + assert_equal "hi!", settings.result + end + + should "be updatable" do + stub_request(:post, "#{Stripe.api_base}/v1/test/singleton") + .with(body: { foo: "bar" }, headers: { "Stripe-Account" => "acct_hi" }) + .to_return(body: JSON.generate({ object: "test.singleton", result: "hi!" })) + + settings = TestSingletonResource.update({ foo: "bar" }, { stripe_account: "acct_hi" }) + assert settings.is_a? TestSingletonResource + assert_equal "hi!", settings.result + end + + should "be saveable" do + stub_request(:get, "#{Stripe.api_base}/v1/test/singleton") + .to_return(body: JSON.generate({ object: "test.singleton", result: "hi!" })) + + stub_request(:post, "#{Stripe.api_base}/v1/test/singleton") + .with(body: { result: "hello" }) + .to_return(body: JSON.generate({ object: "test.singleton", result: "hello" })) + + settings = TestSingletonResource.retrieve + settings.result = "hello" + settings.save + assert_requested :post, "#{Stripe.api_base}/v1/test/singleton", times: 1 + end + end + @@fixtures = {} # rubocop:disable Style/ClassVars setup do if @@fixtures.empty? diff --git a/test/stripe/generated_examples_test.rb b/test/stripe/generated_examples_test.rb index 457aef51c..96a9516e2 100644 --- a/test/stripe/generated_examples_test.rb +++ b/test/stripe/generated_examples_test.rb @@ -1571,6 +1571,14 @@ class CodegennedExampleTest < Test::Unit::TestCase Stripe::TaxRate.update("txr_xxxxxxxxxxxxx", { active: false }) assert_requested :post, "#{Stripe.api_base}/v1/tax_rates/txr_xxxxxxxxxxxxx" end + should "Test tax settings get" do + Stripe::Tax::Settings.retrieve + assert_requested :get, "#{Stripe.api_base}/v1/tax/settings?" + end + should "Test tax settings post" do + Stripe::Tax::Settings.update({ defaults: { tax_code: "txcd_10000000" } }) + assert_requested :post, "#{Stripe.api_base}/v1/tax/settings" + end should "Test tax transactions create from calculation post" do Stripe::Tax::Transaction.create_from_calculation({ calculation: "xxx",