diff --git a/.rubocop.yml b/.rubocop.yml index b3b299b21..755642edd 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -16,9 +16,14 @@ Layout/FirstHashElementIndentation: Layout/LineLength: Exclude: - "lib/stripe/object_types.rb" + - "lib/stripe/stripe_client.rb" - "lib/stripe/resources/**/*.rb" + - "lib/stripe/services/**/*.rb" - "test/**/*.rb" +Metrics/AbcSize: + Enabled: false + Metrics/BlockLength: Max: 40 Exclude: @@ -29,11 +34,25 @@ Metrics/BlockLength: Metrics/ClassLength: Enabled: false +# There are several methods with many branches in api_requestor due to +# request logic. +Metrics/CyclomaticComplexity: + Exclude: + - "lib/stripe/api_requestor.rb" + - "lib/stripe/util.rb" + +Metrics/PerceivedComplexity: + Exclude: + - "lib/stripe/api_requestor.rb" + - "lib/stripe/util.rb" + Metrics/MethodLength: - # There's ~2 long methods in `StripeClient` and one in `NestedResource`. If + # There's ~2 long methods in `APIRequestor` and one in `NestedResource`. If # we want to truncate those a little, we could move this to be closer to ~30 # (but the default of 10 is probably too short). Max: 55 + Exclude: + - "lib/stripe/services/v1_services.rb" Metrics/ModuleLength: Enabled: false @@ -41,6 +60,11 @@ Metrics/ModuleLength: Metrics/ParameterLists: # There's 2 methods in `StripeClient` that have long parameter lists. Max: 8 + # Optional parameters should be consistent across libraries, we need not be + # concerned about this. Was introduced with adding `base_address` + Exclude: + - "lib/stripe/api_operations/request.rb" + - "lib/stripe/stripe_object.rb" Style/AccessModifierDeclarations: EnforcedStyle: inline diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 0ca1972be..44e0e3549 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -9,7 +9,7 @@ # Offense count: 2 Lint/HashCompareByIdentity: Exclude: - - 'lib/stripe/stripe_client.rb' + - 'lib/stripe/api_requestor.rb' # Offense count: 27 # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes. @@ -42,7 +42,7 @@ Style/CaseLikeIf: # This cop supports unsafe autocorrection (--autocorrect-all). Style/CombinableLoops: Exclude: - - 'lib/stripe/stripe_client.rb' + - 'lib/stripe/api_requestor.rb' # Offense count: 44 # Configuration parameters: AllowedConstants. @@ -54,7 +54,7 @@ Style/Documentation: # Configuration parameters: AllowSplatArgument. Style/HashConversion: Exclude: - - 'lib/stripe/stripe_client.rb' + - 'lib/stripe/api_requestor.rb' # Offense count: 3 # This cop supports unsafe autocorrection (--autocorrect-all). @@ -76,7 +76,7 @@ Style/StringConcatenation: - 'lib/stripe/oauth.rb' - 'lib/stripe/resources/bank_account.rb' - 'lib/stripe/resources/source.rb' - - 'lib/stripe/stripe_client.rb' + - 'lib/stripe/api_requestor.rb' - 'test/stripe/api_resource_test.rb' - 'test/stripe/stripe_client_test.rb' - 'test/stripe/webhook_test.rb' diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a6c4e3bc..d4a72fc5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,73 @@ # Changelog +## 13.0.0 - 2024-10-01 +* [#1458](https://github.com/stripe/stripe-ruby/pull/1458) Support for APIs in the new API version 2024-09-30.acacia + + This release changes the pinned API version to `2024-09-30.acacia`. Please read the [API Upgrade Guide](https://stripe.com/docs/upgrades#2024-09-30.acacia) and carefully review the API changes before upgrading. + + ### ⚠️ Breaking changes + + Please refer to our [migration guide for v13](https://github.com/stripe/stripe-ruby/wiki/Migration-guide-for-v13) for more information about the backwards incompatible changes. + + #### ❗ `StripeClient` and related changes + * Move `StripeClient` and requestor logic to `APIRequestor`. + * `StripeClient#request` is still available, but is deprecated and will be removed. We encourage `StripeClient#raw_request` as a replacement (see other breaking changes for more detail). + * Repurpose and introduce `StripeClient` as the the entry-point to the service-based pattern, a new interface for calling the Stripe API with many benefits over the existing resource-based paradigm. Services are available under the `v1` and `v2` accessors. + * No global config: you can simultaneously use multiple clients with different configuration options (such as API keys) + * No extra API calls. All API endpoints can be accessed with a single method call. You don't have to call `retrieve` before doing an `update`. + * No static methods. Much easier mocking. + + #### Other breaking changes + + * Adjust default values around retries for HTTP requests. You can use the old defaults by setting them explicitly. New values are: + - max retries: `0` -> `2` + - max retry delay (seconds) `2` -> `5` + * Remove `StripeClient#connection_manager`. This was a legacy method from years ago. + * Singleton `retrieve` method now requires `params` to be passed as the first argument. Existing calls to singleton `retrieve` method with only `opts` argument will have to be updated to account for the addition of `params` argument. + ```ruby + params = { expand: ["available"] } + opts = { stripe_account: "acct_123" } + + # ❌ No longer works + Stripe::Balance.retrieve(opts) + + # ✅ Correct way to call retrieve method + Stripe::Balance.retrieve(params, opts) + ``` + * Moved the `Stripe.raw_request()` method that was recently added to `StripeClient`. This will use the configuration set on the StripeClient instead of the global configuration used before. + * Remove `APIResource.request`. Instead, use `StripeClient#raw_request` now. + ```ruby + # Instead of + Stripe::APIResource.request(:get, "/v1/endpoint", params, opts) + + # do + client = Stripe::StripeClient.new(...) + resp = client.raw_request(:get, "/v1/endpoint", params: params, opts: opts) + ``` + * Add an additional parameter to `APIResource.execute_resource_request`. However, we discourage use of this in favor of `StripeClient#raw_request`. + ```ruby + APIResource.execute_resource_request(method, url, params = {}, opts = {}, usage = []) + # is now, with base_address being one of [:api, :files, :connect, :meter_events] + APIResource.execute_resource_request(method, url, base_address = :api, params = {}, opts = {}, usage = []) + ``` + * Change parameters to `APIRequestor.execute_request` (previously `StripeClient.execute_request`). It now returns all request options from our internal request framework as the second value in the returned tuple, instead of only the API key used: + ```ruby + # Before + obj, api_key = StripeClient.execute_request(method, path, api_base: nil, + api_key: nil, headers: {}, params: {}, usage: []) + + # is now, with base_address being one of [:api, :files, :connect, :meter_events] + + obj, opts = APIRequestor.execute_request(method, path, base_address, + params: {}, opts: {}, usage: []) + puts(opts) # will output {api_key: "sk_test_123", stripe_account: "acct_123"} + ``` + + + ### Additions + * Add support for new Usage Billing APIs `Billing.MeterEvent`, `Billing.MeterEventAdjustments`, `Billing.MeterEventSession`, `Billing.MeterEventStream` and the new Events API `Core.Events` in the [v2 namespace ](https://docs.corp.stripe.com/api-v2-overview) + * Add method `parse_thin_event()` on the `StripeClient` class to parse [thin events](https://docs.corp.stripe.com/event-destinations#events-overview). + ## 12.7.0-beta.2 - 2024-09-18 * [#1449](https://github.com/stripe/stripe-ruby/pull/1449) Update generated code for beta * Remove support for resource `QuotePhase` @@ -52,7 +120,7 @@ * [#1433](https://github.com/stripe/stripe-ruby/pull/1433) Add usage to raw_request call * [#1431](https://github.com/stripe/stripe-ruby/pull/1431) Add `raw_request` - + - Adds the ability to make raw requests to the Stripe API, by providing an HTTP method and url. This is an alternative to using `Stripe::APIResource.request(...)` to make custom requests, which is discouraged and will be broken in a future major version. ## 12.2.0-beta.1 - 2024-07-05 @@ -65,14 +133,14 @@ * [#1425](https://github.com/stripe/stripe-ruby/pull/1425) Update generated code * Add support for `add_lines`, `remove_lines`, and `update_lines` methods on resource `Invoice` * [#1420](https://github.com/stripe/stripe-ruby/pull/1420) Update static methods for delete/list on BankAccount/Card to throw NotImplementedError - * The below methods have been throwing `InvalidRequestError` because the urls used to make the requests have been buggy. Updating them to throw `NotImplementedError` instead just like their counterparts for update & retrieve because they cannot be implemented without the parent id. - - Methods affected | Use these instead in the context of payment method | Use these in the context of external accounts - ------ | ------ | ---- - Stripe:: BankAccount.delete | Stripe::Customer.delete_source | Stripe::Account.delete_external_account - Stripe:: BankAccount.list | Stripe::Customer.list_sources | Stripe::Customer.list_external_accounts - Stripe:: Card.delete | Stripe::Customer.delete_source | Stripe::Account.delete_external_account - Stripe:: Card.list | Stripe::Customer.list_sources | Stripe::Customer.list_external_accounts + * The below methods have been throwing `InvalidRequestError` because the urls used to make the requests have been buggy. Updating them to throw `NotImplementedError` instead just like their counterparts for update & retrieve because they cannot be implemented without the parent id. + + Methods affected | Use these instead in the context of payment method | Use these in the context of external accounts + ------ | ------ | ---- + Stripe:: BankAccount.delete | Stripe::Customer.delete_source | Stripe::Account.delete_external_account + Stripe:: BankAccount.list | Stripe::Customer.list_sources | Stripe::Customer.list_external_accounts + Stripe:: Card.delete | Stripe::Customer.delete_source | Stripe::Account.delete_external_account + Stripe:: Card.list | Stripe::Customer.list_sources | Stripe::Customer.list_external_accounts * [#1427](https://github.com/stripe/stripe-ruby/pull/1427) Regenerate rbis * [#1426](https://github.com/stripe/stripe-ruby/pull/1426) Remove coveralls and re-added JRuby diff --git a/OPENAPI_VERSION b/OPENAPI_VERSION index 5f5b31119..365c23e62 100644 --- a/OPENAPI_VERSION +++ b/OPENAPI_VERSION @@ -1 +1 @@ -v1267 \ No newline at end of file +v1267 diff --git a/README.md b/README.md index 8be82deaf..a62bc7def 100644 --- a/README.md +++ b/README.md @@ -346,13 +346,14 @@ If you: - prefer to bypass the method definitions in the library and specify your request details directly, - used the method `Stripe::APIResource.request(...)` to specify your own requests, which will soon be broken -you can now use the `raw_request` method on `Stripe`. +you can now use the `raw_request` method on `StripeClient`. ```ruby -resp = Stripe.raw_request(:post, "/v1/beta_endpoint", {param: 123}, {stripe_version: "2022-11-15; feature_beta=v3"}) +client = Stripe::StripeClient.new(...) +resp = client.raw_request(:post, "/v1/beta_endpoint", {param: 123}, {stripe_version: "2022-11-15; feature_beta=v3"}) # (Optional) resp is a StripeResponse. You can use `Stripe.deserialize` to get a StripeObject. -deserialized_resp = Stripe.deserialize(resp.http_body) +deserialized_resp = client.deserialize(resp.http_body) ``` ## Support diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 000000000..d0c3af87b --- /dev/null +++ b/examples/README.md @@ -0,0 +1,11 @@ +## Running an example + +From the examples folder, run: +`RUBYLIB=../lib ruby your_example.rb` + +## Adding a new example + +1. Clone new_example.rb +2. Implement your example +3. Run it (as per above) +4. 👍 diff --git a/examples/meter_event_stream.rb b/examples/meter_event_stream.rb new file mode 100644 index 000000000..3f75ad4fe --- /dev/null +++ b/examples/meter_event_stream.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require "stripe" +require "date" + +class MeterEventManager + attr_accessor :api_key, :meter_event_session + + def initialize(api_key) + @api_key = api_key + @meter_event_session = nil + end + + def refresh_meter_event_session + return unless @meter_event_session.nil? || DateTime.parse(@meter_event_session.expires_at) <= DateTime.now + + # Create a new meter event session in case the existing session expired + client = Stripe::StripeClient.new(api_key) + @meter_event_session = client.v2.billing.meter_event_session.create + end + + def send_meter_event(meter_event) + # Refresh the meter event session if necessary + refresh_meter_event_session + + # Create a meter event with the current session's authentication token + client = Stripe::StripeClient.new(meter_event_session.authentication_token) + client.v2.billing.meter_event_stream.create( + events: [meter_event] + ) + end +end + +# Send meter events +api_key = "{{API_KEY}}" +customer_id = "{{CUSTOMER_ID}}" + +manager = MeterEventManager.new(api_key) +manager.send_meter_event( + { + event_name: "alpaca_ai_tokens", + payload: { + "stripe_customer_id" => customer_id, + "value" => "25", + }, + } +) diff --git a/examples/new_example.rb b/examples/new_example.rb new file mode 100644 index 000000000..a358995f9 --- /dev/null +++ b/examples/new_example.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require "stripe" +require "date" + +class NewExample + attr_accessor :api_key + + def initialize(api_key) + @api_key = api_key + end + + def do_something_great + puts "Hello World" + # client = Stripe::StripeClient.new(api_key) + # client.v1 + end +end + +# Send meter events +api_key = "{{API_KEY}}" + +example = NewExample.new(api_key) +example.do_something_great diff --git a/examples/stripe_webhook_handler.rb b/examples/stripe_webhook_handler.rb new file mode 100644 index 000000000..94269ebfe --- /dev/null +++ b/examples/stripe_webhook_handler.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true +# typed: false + +require "stripe" +require "sinatra" + +api_key = ENV.fetch("STRIPE_API_KEY", nil) +# Retrieve the webhook secret from the environment variable +webhook_secret = ENV.fetch("WEBHOOK_SECRET", nil) + +client = Stripe::StripeClient.new(api_key) + +post "/webhook" do + webhook_body = request.body.read + sig_header = request.env["HTTP_STRIPE_SIGNATURE"] + thin_event = client.parse_thin_event(webhook_body, sig_header, webhook_secret) + + # Fetch the event data to understand the failure + event = client.v2.core.events.retrieve(thin_event.id) + if event.instance_of? Stripe::V1BillingMeterErrorReportTriggeredEvent + meter = event.fetch_related_object + meter_id = meter.id + puts "Success!", meter_id + end + + # Record the failures and alert your team + status 200 +end diff --git a/lib/stripe.rb b/lib/stripe.rb index c20040db4..91cdcd593 100644 --- a/lib/stripe.rb +++ b/lib/stripe.rb @@ -32,13 +32,18 @@ # API resource support classes require "stripe/errors" require "stripe/object_types" +require "stripe/event_types" +require "stripe/request_options" require "stripe/util" require "stripe/connection_manager" require "stripe/multipart_encoder" +require "stripe/api_requestor" +require "stripe/stripe_service" require "stripe/stripe_client" require "stripe/stripe_object" require "stripe/stripe_response" require "stripe/list_object" +require "stripe/v2_list_object" require "stripe/search_result_object" require "stripe/error_object" require "stripe/api_resource" @@ -47,12 +52,15 @@ require "stripe/webhook" require "stripe/stripe_configuration" require "stripe/request_signing_authenticator" +require "stripe/thin_event" # Named API resources require "stripe/resources" +require "stripe/services" # OAuth require "stripe/oauth" +require "stripe/services/oauth_service" module Stripe DEFAULT_CA_BUNDLE_PATH = __dir__ + "/data/ca-certificates.crt" @@ -62,6 +70,12 @@ module Stripe LEVEL_ERROR = Logger::ERROR LEVEL_INFO = Logger::INFO + # API base constants + DEFAULT_API_BASE = "https://api.stripe.com" + DEFAULT_CONNECT_BASE = "https://connect.stripe.com" + DEFAULT_UPLOAD_BASE = "https://files.stripe.com" + DEFAULT_METER_EVENTS_BASE = "https://meter-events.stripe.com" + @app_info = nil @config = Stripe::StripeConfiguration.setup @@ -79,6 +93,7 @@ class << self def_delegators :@config, :api_base, :api_base= def_delegators :@config, :uploads_base, :uploads_base= def_delegators :@config, :connect_base, :connect_base= + def_delegators :@config, :meter_events_base, :meter_events_base= def_delegators :@config, :open_timeout, :open_timeout= def_delegators :@config, :read_timeout, :read_timeout= def_delegators :@config, :write_timeout, :write_timeout= @@ -129,48 +144,37 @@ def self.add_beta_version(beta_name, version) self.api_version = "#{api_version}; #{beta_name}=#{version}" end - class Preview - def self._get_default_opts(opts) - { api_mode: :preview }.merge(opts) - end - - def self.get(url, opts = {}) - Stripe.raw_request(:get, url, {}, _get_default_opts(opts)) - end - - def self.post(url, params = {}, opts = {}) - Stripe.raw_request(:post, url, params, _get_default_opts(opts)) - end - - def self.delete(url, opts = {}) - Stripe.raw_request(:delete, url, {}, _get_default_opts(opts)) - end - end - class RawRequest - include Stripe::APIOperations::Request - def initialize @opts = {} end - def execute(method, url, params = {}, opts = {}, usage = []) - resp, = execute_resource_request(method, url, params, opts, usage) + def execute(method, url, base_address: :api, params: {}, opts: {}, usage: []) + opts = Util.normalize_opts(opts) + req_opts = RequestOptions.extract_opts_from_hash(opts) + + requestor = APIRequestor.active_requestor + resp, = requestor.send(:execute_request_internal, method, url, base_address, params, req_opts, + usage) - resp + requestor.interpret_response(resp) end end # Sends a request to Stripe REST API - def self.raw_request(method, url, params = {}, opts = {}) + def self.raw_request(method, url, params = {}, opts = {}, base_address: :api) req = RawRequest.new - req.execute(method, url, params, opts, ["raw_request"]) + req.execute(method, url, base_address: base_address, params: params, opts: opts, + usage: ["raw_request"]) end - def self.deserialize(data) + def self.deserialize(data, api_mode: :v1) data = JSON.parse(data) if data.is_a?(String) - Util.convert_to_stripe_object(data, {}) + Util.convert_to_stripe_object(data, {}, api_mode: api_mode) end + extend Gem::Deprecate + deprecate :raw_request, "StripeClient#raw_request", 2024, 9 + deprecate :deserialize, "StripeClient#deserialize", 2024, 9 end Stripe.log_level = ENV["STRIPE_LOG"] unless ENV["STRIPE_LOG"].nil? diff --git a/lib/stripe/api_operations/nested_resource.rb b/lib/stripe/api_operations/nested_resource.rb index 0efde45c2..c63761f51 100644 --- a/lib/stripe/api_operations/nested_resource.rb +++ b/lib/stripe/api_operations/nested_resource.rb @@ -35,7 +35,6 @@ def nested_resource_class_methods(resource, path: nil, operations: nil, end end - # rubocop:disable Metrics/MethodLength private def define_operation( resource, operation, @@ -54,26 +53,8 @@ def nested_resource_class_methods(resource, path: nil, operations: nil, ) end when :retrieve - # TODO: (Major) Split params_or_opts to params and opts and get rid of the complicated way to add params define_singleton_method(:"retrieve_#{resource}") \ - do |id, nested_id, params_or_opts = {}, definitely_opts = nil| - opts = nil - params = nil - if definitely_opts.nil? - unrecognized_key = params_or_opts.keys.find { |k| !Util::OPTS_USER_SPECIFIED.include?(k) } - if unrecognized_key - raise ArgumentError, - "Unrecognized request option: #{unrecognized_key}. Did you mean to specify this as " \ - "retrieve params? " \ - "If so, you must explicitly pass an opts hash as a fourth argument. " \ - "For example: .retrieve(#{id}, #{nested_id}, {#{unrecognized_key}: 'foo'}, {})" - end - - opts = params_or_opts - else - opts = definitely_opts - params = params_or_opts - end + do |id, nested_id, params = {}, opts = {}| request_stripe_object( method: :get, path: send(resource_url_method, id, nested_id), @@ -115,7 +96,6 @@ def nested_resource_class_methods(resource, path: nil, operations: nil, raise ArgumentError, "Unknown operation: #{operation.inspect}" end end - # rubocop:enable Metrics/MethodLength end end end diff --git a/lib/stripe/api_operations/request.rb b/lib/stripe/api_operations/request.rb index 4be1da954..fa0127e87 100644 --- a/lib/stripe/api_operations/request.rb +++ b/lib/stripe/api_operations/request.rb @@ -4,20 +4,21 @@ module Stripe module APIOperations module Request module ClassMethods - def execute_resource_request(method, url, + def execute_resource_request(method, url, base_address = :api, params = {}, opts = {}, usage = []) execute_resource_request_internal( - :execute_request, method, url, params, opts, usage + :execute_request, method, url, base_address, params, opts, usage ) end - def execute_resource_request_stream(method, url, + def execute_resource_request_stream(method, url, base_address = :api, params = {}, opts = {}, usage = [], &read_body_chunk_block) execute_resource_request_internal( :execute_request_stream, method, url, + base_address, params, opts, usage, @@ -25,13 +26,12 @@ def execute_resource_request_stream(method, url, ) end - private def request_stripe_object(method:, path:, params:, opts: {}, usage: []) - resp, opts = execute_resource_request(method, path, params, opts, usage) - Util.convert_to_stripe_object_with_params(resp.data, params, opts, resp) + private def request_stripe_object(method:, path:, base_address: :api, params: {}, opts: {}, usage: []) + execute_resource_request(method, path, base_address, params, opts, usage) end private def execute_resource_request_internal(client_request_method_sym, - method, url, + method, url, base_address, params, opts, usage, &read_body_chunk_block) params ||= {} @@ -40,60 +40,15 @@ def execute_resource_request_stream(method, url, warn_on_opts_in_params(params) opts = Util.normalize_opts(opts) - error_on_non_string_user_opts(opts) + req_opts = RequestOptions.extract_opts_from_hash(opts) - opts[:client] ||= StripeClient.active_client - - headers = opts.clone - api_key = headers.delete(:api_key) - api_base = headers.delete(:api_base) - client = headers.delete(:client) - api_mode = headers.delete(:api_mode) - # Assume all remaining opts must be headers - - resp, opts[:api_key] = client.send( + APIRequestor.active_requestor.send( client_request_method_sym, method, url, - api_base: api_base, api_key: api_key, - headers: headers, params: params, usage: usage, api_mode: api_mode, + base_address, + params: params, opts: req_opts, usage: usage, &read_body_chunk_block ) - - # Hash#select returns an array before 1.9 - opts_to_persist = {} - opts.each do |k, v| - opts_to_persist[k] = v if Util::OPTS_PERSISTABLE.include?(k) - end - - [resp, opts_to_persist] - end - - # TODO: (major) - # This method used to be called `request`, but it's such a short name - # that it eventually conflicted with the name of a field on an API - # resource (specifically, `Event#request`), so it was renamed to - # something more unique. - # - # The former name had been around for just about forever though, and - # although all internal uses have been renamed, I've left this alias in - # place for backwards compatibility. Consider removing it on the next - # major. - alias request execute_resource_request - extend Gem::Deprecate - deprecate :request, "Stripe.raw_request", 2024, 7 - - private def error_on_non_string_user_opts(opts) - Util::OPTS_USER_SPECIFIED.each do |opt| - next unless opts.key?(opt) - - val = opts[opt] - next if val.nil? - next if val.is_a?(String) - - raise ArgumentError, - "request option '#{opt}' should be a string value " \ - "(was a #{val.class})" - end end private def error_on_invalid_params(params) @@ -105,7 +60,7 @@ def execute_resource_request_stream(method, url, end private def warn_on_opts_in_params(params) - Util::OPTS_USER_SPECIFIED.each do |opt| + RequestOptions::OPTS_USER_SPECIFIED.each do |opt| warn("WARNING: '#{opt}' should be in opts instead of params.") if params.key?(opt) end end @@ -115,30 +70,24 @@ def self.included(base) base.extend(ClassMethods) end - protected def execute_resource_request(method, url, + protected def execute_resource_request(method, url, base_address = :api, params = {}, opts = {}, usage = []) opts = @opts.merge(Util.normalize_opts(opts)) - self.class.execute_resource_request(method, url, params, opts, usage) + self.class.execute_resource_request(method, url, base_address, params, opts, usage) end - protected def execute_resource_request_stream(method, url, - params = {}, opts = {}, + protected def execute_resource_request_stream(method, url, base_address = :api, + params = {}, opts = {}, usage = [], &read_body_chunk_block) opts = @opts.merge(Util.normalize_opts(opts)) self.class.execute_resource_request_stream( - method, url, params, opts, &read_body_chunk_block + method, url, base_address, params, opts, usage, &read_body_chunk_block ) end - private def request_stripe_object(method:, path:, params:, opts: {}, usage: []) - resp, opts = execute_resource_request(method, path, params, opts, usage) - Util.convert_to_stripe_object_with_params(resp.data, params, opts, resp) + private def request_stripe_object(method:, path:, params:, base_address: :api, opts: {}, usage: []) + execute_resource_request(method, path, base_address, params, opts, usage) end - - # See notes on `alias` above. - alias request execute_resource_request - extend Gem::Deprecate - deprecate :request, "Stripe.raw_request", 2024, 7 end end end diff --git a/lib/stripe/api_operations/save.rb b/lib/stripe/api_operations/save.rb index ecf1052cd..08b336ba1 100644 --- a/lib/stripe/api_operations/save.rb +++ b/lib/stripe/api_operations/save.rb @@ -65,9 +65,10 @@ def save(params = {}, opts = {}) # Please note that id gets removed here our call to #url above has already # generated a uri for this object with an identifier baked in values.delete(:id) - - resp, opts = execute_resource_request(:post, save_url, values, opts, ["save"]) - initialize_from(resp.data, opts, resp) + opts = Util.normalize_opts(opts) + APIRequestor.active_requestor.execute_request_initialize_from(:post, save_url, :api, self, + params: values, opts: opts, + usage: ["save"]) end extend Gem::Deprecate deprecate :save, "the `update` class method (for examples " \ diff --git a/lib/stripe/api_operations/singleton_save.rb b/lib/stripe/api_operations/singleton_save.rb index 0fe074588..9956eb3fd 100644 --- a/lib/stripe/api_operations/singleton_save.rb +++ b/lib/stripe/api_operations/singleton_save.rb @@ -61,8 +61,11 @@ def save(params = {}, opts = {}) values = serialize_params(self).merge(params) - resp, opts = execute_resource_request(:post, resource_url, values, opts, ["save"]) - initialize_from(resp.data, opts, resp) + opts = Util.normalize_opts(opts) + + APIRequestor.active_requestor.execute_request_initialize_from(:post, resource_url, :api, self, + params: values, opts: opts, + usage: ["save"]) end extend Gem::Deprecate deprecate :save, "the `update` class method (for examples " \ diff --git a/lib/stripe/api_requestor.rb b/lib/stripe/api_requestor.rb new file mode 100644 index 000000000..33eef40c5 --- /dev/null +++ b/lib/stripe/api_requestor.rb @@ -0,0 +1,1131 @@ +# frozen_string_literal: true + +require "stripe/instrumentation" + +module Stripe + # APIRequestor executes requests against the Stripe API and allows a user to + # recover both a resource a call returns as well as a response object that + # contains information on the HTTP call. + class APIRequestor + # A set of all known thread contexts across all threads and a mutex to + # synchronize global access to them. + @thread_contexts_with_connection_managers = Set.new + @thread_contexts_with_connection_managers_mutex = Mutex.new + @last_connection_manager_gc = Util.monotonic_time + + # Initializes a new APIRequestor + def initialize(config_arg = {}) + @system_profiler = SystemProfiler.new + @last_request_metrics = Queue.new + + @config = case config_arg + when Hash + StripeConfiguration.new.reverse_duplicate_merge(config_arg) + when Stripe::StripeConfiguration + config_arg + when String + StripeConfiguration.new.reverse_duplicate_merge( + { api_key: config_arg } + ) + else + raise ArgumentError, "Can't handle argument: #{config_arg}" + end + end + + attr_reader :config, :options + + # Gets a currently active `APIRequestor`. Set for the current thread when + # `APIRequestor#request` is being run so that API operations being executed + # inside of that block can find the currently active requestor. It's reset to + # the original value (hopefully `nil`) after the block ends. + # + # For internal use only. Does not provide a stable API and may be broken + # with future non-major changes. + def self.active_requestor + current_thread_context.active_requestor || default_requestor + end + + # Finishes any active connections by closing their TCP connection and + # clears them from internal tracking in all connection managers across all + # threads. + # + # If passed a `config` object, only clear connection managers for that + # particular configuration. + # + # For internal use only. Does not provide a stable API and may be broken + # with future non-major changes. + def self.clear_all_connection_managers(config: nil) + # Just a quick path for when configuration is being set for the first + # time before any connections have been opened. There is technically some + # potential for thread raciness here, but not in a practical sense. + return if @thread_contexts_with_connection_managers.empty? + + @thread_contexts_with_connection_managers_mutex.synchronize do + pruned_contexts = Set.new + + @thread_contexts_with_connection_managers.each do |thread_context| + # Note that the thread context itself is not destroyed, but we clear + # its connection manager and remove our reference to it. If it ever + # makes a new request we'll give it a new connection manager and + # it'll go back into `@thread_contexts_with_connection_managers`. + thread_context.default_connection_managers.reject! do |cm_config, cm| + if config.nil? || config.key == cm_config + cm.clear + true + end + end + + pruned_contexts << thread_context if thread_context.default_connection_managers.empty? + end + + @thread_contexts_with_connection_managers.subtract(pruned_contexts) + end + end + + # A default requestor for the current thread. + def self.default_requestor + current_thread_context.default_requestor ||= APIRequestor.new(Stripe.config) + end + + # A default connection manager for the current thread scoped to the + # configuration object that may be provided. + def self.default_connection_manager(config = Stripe.config) + current_thread_context.default_connection_managers[config.key] ||= begin + connection_manager = ConnectionManager.new(config) + + @thread_contexts_with_connection_managers_mutex.synchronize do + maybe_gc_connection_managers + @thread_contexts_with_connection_managers << current_thread_context + end + + connection_manager + end + end + + # Checks if an error is a problem that we should retry on. This includes + # both socket errors that may represent an intermittent problem and some + # special HTTP statuses. + def self.should_retry?(error, + num_retries:, config: Stripe.config) + return false if num_retries >= config.max_network_retries + + case error + when Net::OpenTimeout, Net::ReadTimeout + # Retry on timeout-related problems (either on open or read). + true + when EOFError, Errno::ECONNREFUSED, Errno::ECONNRESET, # rubocop:todo Lint/DuplicateBranch + Errno::EHOSTUNREACH, Errno::ETIMEDOUT, SocketError + # Destination refused the connection, the connection was reset, or a + # variety of other connection failures. This could occur from a single + # saturated server, so retry in case it's intermittent. + true + when Stripe::StripeError + # The API may ask us not to retry (e.g. if doing so would be a no-op), + # or advise us to retry (e.g. in cases of lock timeouts). Defer to + # those instructions if given. + return false if error.http_headers["stripe-should-retry"] == "false" + return true if error.http_headers["stripe-should-retry"] == "true" + + # 409 Conflict + return true if error.http_status == 409 + + # 429 Too Many Requests + # + # There are a few different problems that can lead to a 429. The most + # common is rate limiting, on which we *don't* want to retry because + # that'd likely contribute to more contention problems. However, some + # 429s are lock timeouts, which is when a request conflicted with + # another request or an internal process on some particular object. + # These 429s are safe to retry. + return true if error.http_status == 429 && error.code == "lock_timeout" + + # Retry on 500, 503, and other internal errors. + # + # Note that we expect the stripe-should-retry header to be false + # in most cases when a 500 is returned, since our idempotency framework + # would typically replay it anyway. + true if error.http_status >= 500 + else + false + end + end + + def self.sleep_time(num_retries, config: Stripe.config) + # Apply exponential backoff with initial_network_retry_delay on the + # number of num_retries so far as inputs. Do not allow the number to + # exceed max_network_retry_delay. + sleep_seconds = [ + config.initial_network_retry_delay * (2**(num_retries - 1)), + config.max_network_retry_delay, + ].min + + # Apply some jitter by randomizing the value in the range of + # (sleep_seconds / 2) to (sleep_seconds). + sleep_seconds *= (0.5 * (1 + rand)) + + # But never sleep less than the base sleep seconds. + [config.initial_network_retry_delay, sleep_seconds].max + end + + # Executes the API call within the given block. Usage looks like: + # + # client = APIRequestor.new + # charge, resp = client.request { Charge.create } + # + def request + old_api_requestor = self.class.current_thread_context.active_requestor + self.class.current_thread_context.active_requestor = self + + if self.class.current_thread_context.last_responses&.key?(object_id) + raise "calls to APIRequestor#request cannot be nested within a thread" + end + + self.class.current_thread_context.last_responses ||= {} + self.class.current_thread_context.last_responses[object_id] = nil + + begin + res = yield + [res, self.class.current_thread_context.last_responses[object_id]] + ensure + self.class.current_thread_context.active_requestor = old_api_requestor + self.class.current_thread_context.last_responses.delete(object_id) + end + end + extend Gem::Deprecate + deprecate :request, "StripeClient#raw_request", 2024, 9 + + def execute_request(method, path, base_address, + params: {}, opts: {}, usage: []) + http_resp, req_opts = execute_request_internal( + method, path, base_address, params, opts, usage + ) + req_opts = RequestOptions.extract_opts_from_hash(req_opts) + + resp = interpret_response(http_resp) + + # If being called from `APIRequestor#request`, put the last response in + # thread-local memory so that it can be returned to the user. Don't store + # anything otherwise so that we don't leak memory. + store_last_response(object_id, resp) + + api_mode = Util.get_api_mode(path) + Util.convert_to_stripe_object_with_params(resp.data, params, RequestOptions.persistable(req_opts), resp, + api_mode: api_mode, requestor: self) + end + + # Execute request without instantiating a new object if the relevant object's name matches the class + # + # For internal use only. Does not provide a stable API and may be broken + # with future non-major changes. + def execute_request_initialize_from(method, path, base_address, object, + params: {}, opts: {}, usage: []) + opts = RequestOptions.combine_opts(object.instance_variable_get(:@opts), opts) + opts = Util.normalize_opts(opts) + http_resp, req_opts = execute_request_internal( + method, path, base_address, params, opts, usage + ) + req_opts = RequestOptions.extract_opts_from_hash(req_opts) + + resp = interpret_response(http_resp) + + # If being called from `APIRequestor#request`, put the last response in + # thread-local memory so that it can be returned to the user. Don't store + # anything otherwise so that we don't leak memory. + store_last_response(object_id, resp) + + if Util.object_name_matches_class?(resp.data[:object], object.class) + object.send(:initialize_from, + resp.data, RequestOptions.persistable(req_opts), resp, + api_mode: :v1, requestor: self) + else + Util.convert_to_stripe_object_with_params(resp.data, params, + RequestOptions.persistable(req_opts), + resp, api_mode: :v1, requestor: self) + end + end + + def interpret_response(http_resp) + StripeResponse.from_net_http(http_resp) + rescue JSON::ParserError + raise general_api_error(http_resp.code.to_i, http_resp.body) + end + + # Executes a request and returns the body as a stream instead of converting + # it to a StripeObject. This should be used for any request where we expect + # an arbitrary binary response. + # + # A `read_body_chunk` block can be passed, which will be called repeatedly + # with the body chunks read from the socket. + # + # If a block is passed, a StripeHeadersOnlyResponse is returned as the + # block is expected to do all the necessary body processing. If no block is + # passed, then a StripeStreamResponse is returned containing an IO stream + # with the response body. + def execute_request_stream(method, path, + base_address, + params: {}, opts: {}, usage: [], + &read_body_chunk_block) + unless block_given? + raise ArgumentError, + "execute_request_stream requires a read_body_chunk_block" + end + + http_resp, api_key = execute_request_internal( + method, path, base_address, params, opts, usage, &read_body_chunk_block + ) + + # When the read_body_chunk_block is given, we no longer have access to the + # response body at this point and so return a response object containing + # only the headers. This is because the body was consumed by the block. + resp = StripeHeadersOnlyResponse.from_net_http(http_resp) + + [resp, api_key] + end + + def store_last_response(object_id, resp) + return unless last_response_has_key?(object_id) + + self.class.current_thread_context.last_responses[object_id] = resp + end + + def last_response_has_key?(object_id) + self.class.current_thread_context.last_responses&.key?(object_id) + end + + # + # private + # + + # Time (in seconds) that a connection manager has not been used before it's + # eligible for garbage collection. + CONNECTION_MANAGER_GC_LAST_USED_EXPIRY = 120 + + # How often to check (in seconds) for connection managers that haven't been + # used in a long time and which should be garbage collected. + CONNECTION_MANAGER_GC_PERIOD = 60 + + ERROR_MESSAGE_CONNECTION = + "Unexpected error communicating when trying to connect to " \ + "Stripe (%s). You may be seeing this message because your DNS is not " \ + "working or you don't have an internet connection. To check, try " \ + "running `host stripe.com` from the command line." + ERROR_MESSAGE_SSL = + "Could not establish a secure connection to Stripe (%s), you " \ + "may need to upgrade your OpenSSL version. To check, try running " \ + "`openssl s_client -connect api.stripe.com:443` from the command " \ + "line." + + # Common error suffix sared by both connect and read timeout messages. + ERROR_MESSAGE_TIMEOUT_SUFFIX = + "Please check your internet connection and try again. " \ + "If this problem persists, you should check Stripe's service " \ + "status at https://status.stripe.com, or let us know at " \ + "support@stripe.com." + + ERROR_MESSAGE_TIMEOUT_CONNECT = ( + "Timed out connecting to Stripe (%s). " + + ERROR_MESSAGE_TIMEOUT_SUFFIX + ).freeze + + ERROR_MESSAGE_TIMEOUT_READ = ( + "Timed out communicating with Stripe (%s). " + + ERROR_MESSAGE_TIMEOUT_SUFFIX + ).freeze + + # Maps types of exceptions that we're likely to see during a network + # request to more user-friendly messages that we put in front of people. + # The original error message is also appended onto the final exception for + # full transparency. + NETWORK_ERROR_MESSAGES_MAP = { + EOFError => ERROR_MESSAGE_CONNECTION, + Errno::ECONNREFUSED => ERROR_MESSAGE_CONNECTION, + Errno::ECONNRESET => ERROR_MESSAGE_CONNECTION, + Errno::EHOSTUNREACH => ERROR_MESSAGE_CONNECTION, + Errno::ETIMEDOUT => ERROR_MESSAGE_TIMEOUT_CONNECT, + SocketError => ERROR_MESSAGE_CONNECTION, + + Net::OpenTimeout => ERROR_MESSAGE_TIMEOUT_CONNECT, + Net::ReadTimeout => ERROR_MESSAGE_TIMEOUT_READ, + + OpenSSL::SSL::SSLError => ERROR_MESSAGE_SSL, + }.freeze + private_constant :NETWORK_ERROR_MESSAGES_MAP + + # A record representing any data that `APIRequestor` puts into + # `Thread.current`. Making it a class likes this gives us a little extra + # type safety and lets us document what each field does. + # + # For internal use only. Does not provide a stable API and may be broken + # with future non-major changes. + class ThreadContext + # A `APIRequestor` that's been flagged as currently active within a + # thread by `APIRequestor#request`. A requestor stays active until the + # completion of the request block. + attr_accessor :active_requestor + + # A default `APIRequestor` object for the thread. Used in all cases where + # the user hasn't specified their own. + attr_accessor :default_requestor + + # A temporary map of object IDs to responses from last executed API + # calls. Used to return a responses from calls to `APIRequestor#request`. + # + # Stored in the thread data to make the use of a single `APIRequestor` + # object safe across multiple threads. Stored as a map so that multiple + # `APIRequestor` objects can run concurrently on the same thread. + # + # Responses are only left in as long as they're needed, which means + # they're removed as soon as a call leaves `APIRequestor#request`, and + # because that's wrapped in an `ensure` block, they should never leave + # garbage in `Thread.current`. + attr_accessor :last_responses + + # A map of connection mangers for the thread. Normally shared between + # all `APIRequestor` objects on a particular thread, and created so as to + # minimize the number of open connections that an application needs. + def default_connection_managers + @default_connection_managers ||= {} + end + + def reset_connection_managers + @default_connection_managers = {} + end + end + + # Access data stored for `APIRequestor` within the thread's current + # context. Returns `ThreadContext`. + # + # For internal use only. Does not provide a stable API and may be broken + # with future non-major changes. + def self.current_thread_context + Thread.current[:api_requestor__internal_use_only] ||= ThreadContext.new + end + + # Garbage collects connection managers that haven't been used in some time, + # with the idea being that we want to remove old connection managers that + # belong to dead threads and the like. + # + # Prefixed with `maybe_` because garbage collection will only run + # periodically so that we're not constantly engaged in busy work. If + # connection managers live a little passed their useful age it's not + # harmful, so it's not necessary to get them right away. + # + # For testability, returns `nil` if it didn't run and the number of + # connection managers that were garbage collected otherwise. + # + # IMPORTANT: This method is not thread-safe and expects to be called inside + # a lock on `@thread_contexts_with_connection_managers_mutex`. + # + # For internal use only. Does not provide a stable API and may be broken + # with future non-major changes. + def self.maybe_gc_connection_managers + next_gc_time = @last_connection_manager_gc + CONNECTION_MANAGER_GC_PERIOD + return nil if next_gc_time > Util.monotonic_time + + last_used_threshold = + Util.monotonic_time - CONNECTION_MANAGER_GC_LAST_USED_EXPIRY + + pruned_contexts = [] + @thread_contexts_with_connection_managers.each do |thread_context| + thread_context + .default_connection_managers + .each do |config_key, connection_manager| + next if connection_manager.last_used > last_used_threshold + + connection_manager.clear + thread_context.default_connection_managers.delete(config_key) + end + end + + @thread_contexts_with_connection_managers.each do |thread_context| + next unless thread_context.default_connection_managers.empty? + + pruned_contexts << thread_context + end + + @thread_contexts_with_connection_managers -= pruned_contexts + @last_connection_manager_gc = Util.monotonic_time + + pruned_contexts.count + end + + private def execute_request_internal(method, path, + base_address, params, opts, usage, + &read_body_chunk_block) + api_mode = Util.get_api_mode(path) + opts = RequestOptions.merge_config_and_opts(config, opts) + + raise ArgumentError, "method should be a symbol" \ + unless method.is_a?(Symbol) + raise ArgumentError, "path should be a string" \ + unless path.is_a?(String) + + base_url ||= config.base_addresses[base_address] + + raise ArgumentError, "api_base cannot be empty" if base_url.nil? || base_url.empty? + + api_key ||= opts[:api_key] + params = Util.objects_to_ids(params) + + check_api_key!(api_key) + + body_params = nil + query_params = nil + case method + when :get, :head, :delete + query_params = params + else + body_params = params + end + + query_params, path = merge_query_params(query_params, path) + + headers = request_headers(method, api_mode, opts) + url = api_url(path, base_url) + + # Merge given query parameters with any already encoded in the path. + query = query_params ? Util.encode_parameters(query_params, api_mode) : nil + + # Encoding body parameters is a little more complex because we may have + # to send a multipart-encoded body. `body_log` is produced separately as + # a log-friendly variant of the encoded form. File objects are displayed + # as such instead of as their file contents. + body, body_log = + body_params ? encode_body(body_params, headers, api_mode) : [nil, nil] + + # stores information on the request we're about to make so that we don't + # have to pass as many parameters around for logging. + context = RequestLogContext.new + context.account = headers["Stripe-Account"] + context.api_key = api_key + context.api_version = headers["Stripe-Version"] + context.body = body_log + context.idempotency_key = headers["Idempotency-Key"] + context.method = method + context.path = path + context.query = query + + # A block can be passed in to read the content directly from the response. + # We want to execute this block only when the response was actually + # successful. When it wasn't, we defer to the standard error handling as + # we have to read the body and parse the error JSON. + response_block = + if block_given? + lambda do |response| + response.read_body(&read_body_chunk_block) unless should_handle_as_error(response.code.to_i) + end + end + + http_resp = + execute_request_with_rescues(base_url, headers, api_mode, usage, context) do + self.class + .default_connection_manager(config) + .execute_request(method, url, + body: body, + headers: headers, + query: query, + &response_block) + end + + [http_resp, opts] + end + + private def api_url(url, base_url) + base_url + url + end + + private def check_api_key!(api_key) + unless api_key + raise AuthenticationError, "No API key provided. " \ + 'Set your API key using "Stripe.api_key = ". ' \ + "You can generate API keys from the Stripe web interface. " \ + "See https://stripe.com/api for details, or email " \ + "support@stripe.com if you have any questions." + end + + return unless api_key =~ /\s/ + + raise AuthenticationError, "Your API key is invalid, as it contains " \ + "whitespace. (HINT: You can double-check your API key from the " \ + "Stripe web interface. See https://stripe.com/api for details, or " \ + "email support@stripe.com if you have any questions.)" + end + + # Encodes a set of body parameters using multipart if `Content-Type` is set + # for that, or standard form-encoding otherwise. Returns the encoded body + # and a version of the encoded body that's safe to be logged. + private def encode_body(body_params, headers, api_mode) + body = nil + flattened_params = Util.flatten_params(body_params, api_mode) + + if headers["Content-Type"] == MultipartEncoder::MULTIPART_FORM_DATA + body, content_type = MultipartEncoder.encode(flattened_params) + + # Set a new content type that also includes the multipart boundary. + # See `MultipartEncoder` for details. + headers["Content-Type"] = content_type + + # `#to_s` any complex objects like files and the like to build output + # that's more condusive to logging. + flattened_params = + flattened_params.map { |k, v| [k, v.is_a?(String) ? v : v.to_s] }.to_h + + elsif api_mode == :v2 + body = JSON.generate(body_params) + headers["Content-Type"] = "application/json" + else + body = Util.encode_parameters(body_params, api_mode) + headers["Content-Type"] = "application/x-www-form-urlencoded" + end + + body_log = if api_mode == :v2 + body + else + # We don't use `Util.encode_parameters` partly as an optimization (to + # not redo work we've already done), and partly because the encoded + # forms of certain characters introduce a lot of visual noise and it's + # nice to have a clearer format for logs. + flattened_params.map { |k, v| "#{k}=#{v}" }.join("&") + end + + [body, body_log] + end + + private def should_handle_as_error(http_status) + http_status >= 400 + end + + private def execute_request_with_rescues(base_url, headers, api_mode, usage, context) + num_retries = 0 + + begin + request_start = nil + user_data = nil + + log_request(context, num_retries) + user_data = notify_request_begin(context) + + request_start = Util.monotonic_time + resp = yield + request_duration = Util.monotonic_time - request_start + + http_status = resp.code.to_i + context = context.dup_from_response_headers(resp) + + handle_error_response(resp, context, api_mode) if should_handle_as_error(http_status) + + log_response(context, request_start, http_status, resp.body, resp) + notify_request_end(context, request_duration, http_status, + num_retries, user_data, resp, headers) + + if config.enable_telemetry? && context.request_id + request_duration_ms = (request_duration * 1000).to_i + @last_request_metrics << StripeRequestMetrics.new(context.request_id, request_duration_ms, usage: usage) + end + + # We rescue all exceptions from a request so that we have an easy spot to + # implement our retry logic across the board. We'll re-raise if it's a + # type of exception that we didn't expect to handle. + rescue StandardError => e + # If we modify context we copy it into a new variable so as not to + # taint the original on a retry. + error_context = context + http_status = nil + request_duration = Util.monotonic_time - request_start if request_start + + if e.is_a?(Stripe::StripeError) + error_context = context.dup_from_response_headers(e.http_headers) + http_status = resp.code.to_i + log_response(error_context, request_start, + e.http_status, e.http_body, resp) + else + log_response_error(error_context, request_start, e) + end + notify_request_end(context, request_duration, http_status, num_retries, + user_data, resp, headers) + + if self.class.should_retry?(e, + num_retries: num_retries, + config: config) + num_retries += 1 + sleep self.class.sleep_time(num_retries, config: config) + retry + end + + case e + when Stripe::StripeError + raise + when *NETWORK_ERROR_MESSAGES_MAP.keys + handle_network_error(e, error_context, num_retries, base_url) + + # Only handle errors when we know we can do so, and re-raise otherwise. + # This should be pretty infrequent. + else # rubocop:todo Lint/DuplicateBranch + raise + end + end + + resp + end + + private def notify_request_begin(context) + return unless Instrumentation.any_subscribers?(:request_begin) + + event = Instrumentation::RequestBeginEvent.new( + method: context.method, + path: context.path, + user_data: {} + ) + Stripe::Instrumentation.notify(:request_begin, event) + + # This field may be set in the `request_begin` callback. If so, we'll + # forward it onto `request_end`. + event.user_data + end + + private def notify_request_end(context, duration, http_status, num_retries, + user_data, resp, headers) + return if !Instrumentation.any_subscribers?(:request_end) && + !Instrumentation.any_subscribers?(:request) + + request_context = Stripe::Instrumentation::RequestContext.new( + duration: duration, + context: context, + header: headers + ) + response_context = Stripe::Instrumentation::ResponseContext.new( + http_status: http_status, + response: resp + ) + + event = Instrumentation::RequestEndEvent.new( + request_context: request_context, + response_context: response_context, + num_retries: num_retries, + user_data: user_data || {} + ) + Stripe::Instrumentation.notify(:request_end, event) + + # The name before `request_begin` was also added. Provided for backwards + # compatibility. + Stripe::Instrumentation.notify(:request, event) + end + + private def general_api_error(status, body) + APIError.new("Invalid response object from API: #{body.inspect} " \ + "(HTTP response code was #{status})", + http_status: status, http_body: body) + end + + # Formats a plugin "app info" hash into a string that we can tack onto the + # end of a User-Agent string where it'll be fairly prominent in places like + # the Dashboard. Note that this formatting has been implemented to match + # other libraries, and shouldn't be changed without universal consensus. + private def format_app_info(info) + str = info[:name] + str = "#{str}/#{info[:version]}" unless info[:version].nil? + str = "#{str} (#{info[:url]})" unless info[:url].nil? + str + end + + private def handle_error_response(http_resp, context, api_mode) + begin + resp = StripeResponse.from_net_http(http_resp) + error_data = resp.data[:error] + + raise StripeError, "Indeterminate error" unless error_data + rescue JSON::ParserError, StripeError + raise general_api_error(http_resp.code.to_i, http_resp.body) + end + + error = if error_data.is_a?(String) + specific_oauth_error(resp, error_data, context) + elsif api_mode == :v2 + specific_v2_api_error(resp, error_data, context) + else + specific_api_error(resp, error_data, context) + end + + error.response = resp + raise(error) + end + + # Works around an edge case where we end up with both query parameters from + # parameteers and query parameters that were appended onto the end of the + # given path. + # + # Decode any parameters that were added onto the end of a path and add them + # to a unified query parameter hash so that all parameters end up in one + # place and all of them are correctly included in the final request. + private def merge_query_params(query_params, path) + u = URI.parse(path) + + # Return original results if there was nothing to be found. + return query_params, path if u.query.nil? + + query_params ||= {} + query_params = Hash[URI.decode_www_form(u.query)].merge(query_params) + + # Reset the path minus any query parameters that were specified. + path = u.path + + [query_params, path] + end + + private def specific_api_error(resp, error_data, context) + Util.log_error("Stripe API error", + status: resp.http_status, + error_code: error_data[:code], + error_message: error_data[:message], + error_param: error_data[:param], + error_type: error_data[:type], + idempotency_key: context.idempotency_key, + request_id: context.request_id, + config: config) + + # The standard set of arguments that can be used to initialize most of + # the exceptions. + opts = { + http_body: resp.http_body, + http_headers: resp.http_headers, + http_status: resp.http_status, + json_body: resp.data, + code: error_data[:code], + } + + case resp.http_status + when 400, 404 + case error_data[:type] + when "idempotency_error" + IdempotencyError.new(error_data[:message], **opts) + else + InvalidRequestError.new( + error_data[:message], error_data[:param], + **opts + ) + end + when 401 + AuthenticationError.new(error_data[:message], **opts) + when 402 + CardError.new( + error_data[:message], error_data[:param], + **opts + ) + when 403 + PermissionError.new(error_data[:message], **opts) + when 429 + RateLimitError.new(error_data[:message], **opts) + else + APIError.new(error_data[:message], **opts) + end + end + + private def specific_v2_api_error(resp, error_data, context) + Util.log_error("Stripe v2 API error", + status: resp.http_status, + error_code: error_data[:code], + error_message: error_data[:message], + error_param: error_data[:param], + error_type: error_data[:type], + idempotency_key: context.idempotency_key, + request_id: context.request_id, + config: config) + + # The standard set of arguments that can be used to initialize most of + # the exceptions. + opts = { + http_body: resp.http_body, + http_headers: resp.http_headers, + http_status: resp.http_status, + json_body: resp.data, + code: error_data[:code], + } + + case error_data[:type] + when "idempotency_error" + IdempotencyError.new(error_data[:message], **opts) + # switch cases: The beginning of the section generated from our OpenAPI spec + when "temporary_session_expired" + TemporarySessionExpiredError.new(error_data[:message], **opts) + # switch cases: The end of the section generated from our OpenAPI spec + else + specific_api_error(resp, error_data, context) + end + end + + # Attempts to look at a response's error code and return an OAuth error if + # one matches. Will return `nil` if the code isn't recognized. + private def specific_oauth_error(resp, error_code, context) + description = resp.data[:error_description] || error_code + + Util.log_error("Stripe OAuth error", + status: resp.http_status, + error_code: error_code, + error_description: description, + idempotency_key: context.idempotency_key, + request_id: context.request_id, + config: config) + + args = { + http_status: resp.http_status, http_body: resp.http_body, + json_body: resp.data, http_headers: resp.http_headers, + } + + case error_code + when "invalid_client" + OAuth::InvalidClientError.new(error_code, description, **args) + when "invalid_grant" + OAuth::InvalidGrantError.new(error_code, description, **args) + when "invalid_request" + OAuth::InvalidRequestError.new(error_code, description, **args) + when "invalid_scope" + OAuth::InvalidScopeError.new(error_code, description, **args) + when "unsupported_grant_type" + OAuth::UnsupportedGrantTypeError.new(error_code, description, **args) + when "unsupported_response_type" + OAuth::UnsupportedResponseTypeError.new(error_code, description, **args) + else + # We'd prefer that all errors are typed, but we create a generic + # OAuthError in case we run into a code that we don't recognize. + OAuth::OAuthError.new(error_code, description, **args) + end + end + + private def handle_network_error(error, context, num_retries, + base_url) + Util.log_error("Stripe network error", + error_message: error.message, + idempotency_key: context.idempotency_key, + request_id: context.request_id, + config: config) + + errors, message = NETWORK_ERROR_MESSAGES_MAP.detect do |(e, _)| + error.is_a?(e) + end + + if errors.nil? + message = "Unexpected error #{error.class.name} communicating " \ + "with Stripe. Please let us know at support@stripe.com." + end + + message = message % base_url + + message += " Request was retried #{num_retries} times." if num_retries > 0 + + raise APIConnectionError, + message + "\n\n(Network error: #{error.message})" + end + + private def request_headers(method, api_mode, req_opts) + user_agent = "Stripe/#{api_mode} RubyBindings/#{Stripe::VERSION}" + user_agent += " " + format_app_info(Stripe.app_info) unless Stripe.app_info.nil? + + headers = { + "User-Agent" => user_agent, + "Authorization" => "Bearer #{req_opts[:api_key]}", + } + + if config.enable_telemetry? + begin + headers["X-Stripe-Client-Telemetry"] = JSON.generate( + last_request_metrics: @last_request_metrics.pop(true)&.payload + ) + rescue ThreadError + # last_request_metrics is best effort, ignore failures when queue + # is empty. should fail if this is the first request ever sent + # on a requestor + end + end + + headers["Idempotency-Key"] = req_opts[:idempotency_key] if req_opts[:idempotency_key] + # It is only safe to retry network failures on post and delete + # requests if we add an Idempotency-Key header + if %i[post delete].include?(method) && (api_mode == :v2 || config.max_network_retries > 0) + headers["Idempotency-Key"] ||= SecureRandom.uuid + end + + headers["Stripe-Version"] = req_opts[:stripe_version] if req_opts[:stripe_version] + headers["Stripe-Account"] = req_opts[:stripe_account] if req_opts[:stripe_account] + headers["Stripe-Context"] = req_opts[:stripe_context] if req_opts[:stripe_context] + + user_agent = @system_profiler.user_agent + begin + headers.update( + "X-Stripe-Client-User-Agent" => JSON.generate(user_agent) + ) + rescue StandardError => e + headers.update( + "X-Stripe-Client-Raw-User-Agent" => user_agent.inspect, + :error => "#{e} (#{e.class})" + ) + end + + headers.update(req_opts[:headers]) + end + + private def log_request(context, num_retries) + Util.log_info("Request to Stripe API", + account: context.account, + api_version: context.api_version, + idempotency_key: context.idempotency_key, + method: context.method, + num_retries: num_retries, + path: context.path, + config: config) + Util.log_debug("Request details", + body: context.body, + idempotency_key: context.idempotency_key, + query: context.query, + config: config, + process_id: Process.pid, + thread_object_id: Thread.current.object_id, + log_timestamp: Util.monotonic_time) + end + + private def log_response(context, request_start, status, body, resp) + Util.log_info("Response from Stripe API", + account: context.account, + api_version: context.api_version, + elapsed: Util.monotonic_time - request_start, + idempotency_key: context.idempotency_key, + method: context.method, + path: context.path, + request_id: context.request_id, + status: status, + config: config) + Util.log_debug("Response details", + body: body, + idempotency_key: context.idempotency_key, + request_id: context.request_id, + config: config, + process_id: Process.pid, + thread_object_id: Thread.current.object_id, + response_object_id: resp.object_id, + log_timestamp: Util.monotonic_time) + + return unless context.request_id + + Util.log_debug("Dashboard link for request", + idempotency_key: context.idempotency_key, + request_id: context.request_id, + url: Util.request_id_dashboard_url(context.request_id, + context.api_key), + config: config) + end + + private def log_response_error(context, request_start, error) + elapsed = request_start ? Util.monotonic_time - request_start : nil + Util.log_error("Request error", + elapsed: elapsed, + error_message: error.message, + idempotency_key: context.idempotency_key, + method: context.method, + path: context.path, + config: config) + end + + # RequestLogContext stores information about a request that's begin made so + # that we can log certain information. It's useful because it means that we + # don't have to pass around as many parameters. + class RequestLogContext + attr_accessor :body, :account, :api_key, :api_version, :idempotency_key, :method, :path, :query, :request_id + + # The idea with this method is that we might want to update some of + # context information because a response that we've received from the API + # contains information that's more authoritative than what we started + # with for a request. For example, we should trust whatever came back in + # a `Stripe-Version` header beyond what configuration information that we + # might have had available. + def dup_from_response_headers(headers) + context = dup + context.account = headers["Stripe-Account"] + context.api_version = headers["Stripe-Version"] + context.idempotency_key = headers["Idempotency-Key"] + context.request_id = headers["Request-Id"] + context + end + end + + # SystemProfiler extracts information about the system that we're running + # in so that we can generate a rich user agent header to help debug + # integrations. + class SystemProfiler + def self.uname + if ::File.exist?("/proc/version") + ::File.read("/proc/version").strip + else + case RbConfig::CONFIG["host_os"] + when /linux|darwin|bsd|sunos|solaris|cygwin/i + uname_from_system + when /mswin|mingw/i + uname_from_system_ver + else + "unknown platform" + end + end + end + + def self.uname_from_system + (`uname -a 2>/dev/null` || "").strip + rescue Errno::ENOENT + "uname executable not found" + rescue Errno::ENOMEM # couldn't create subprocess + "uname lookup failed" + end + + def self.uname_from_system_ver + (`ver` || "").strip + rescue Errno::ENOENT + "ver executable not found" + rescue Errno::ENOMEM # couldn't create subprocess + "uname lookup failed" + end + + def initialize + @uname = self.class.uname + end + + def user_agent + lang_version = "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL} " \ + "(#{RUBY_RELEASE_DATE})" + + { + application: Stripe.app_info, + bindings_version: Stripe::VERSION, + lang: "ruby", + lang_version: lang_version, + platform: RUBY_PLATFORM, + engine: defined?(RUBY_ENGINE) ? RUBY_ENGINE : "", + publisher: "stripe", + uname: @uname, + hostname: Socket.gethostname, + }.delete_if { |_k, v| v.nil? } + end + end + + # StripeRequestMetrics tracks metadata to be reported to stripe for metrics + # collection + class StripeRequestMetrics + # The Stripe request ID of the response. + attr_accessor :request_id + + # Request duration in milliseconds + attr_accessor :request_duration_ms + + # list of names of tracked behaviors associated with this request + attr_accessor :usage + + def initialize(request_id, request_duration_ms, usage: []) + self.request_id = request_id + self.request_duration_ms = request_duration_ms + self.usage = usage + end + + def payload + ret = { request_id: request_id, request_duration_ms: request_duration_ms } + ret[:usage] = usage if !usage.nil? && !usage.empty? + ret + end + end + end +end diff --git a/lib/stripe/api_resource.rb b/lib/stripe/api_resource.rb index 1af656123..7ac3f3cce 100644 --- a/lib/stripe/api_resource.rb +++ b/lib/stripe/api_resource.rb @@ -25,6 +25,12 @@ def self.class_name end def self.resource_url + if name.include?("Stripe::V2") + raise NotImplementedError, + "V2 resources do not have a defined URL. Please use the StripeClient " \ + "to make V2 requests" + end + if self == APIResource raise NotImplementedError, "APIResource is an abstract class. You should perform actions " \ @@ -87,33 +93,35 @@ def resource_url end def refresh - resp, opts = execute_resource_request(:get, resource_url, - @retrieve_params) - initialize_from(resp.data, opts, resp) + if self.class.name.include?("Stripe::V2") + raise NotImplementedError, + "It is not possible to refresh v2 objects. Please retrieve the object using the StripeClient instead." + end + + @requestor.execute_request_initialize_from(:get, resource_url, :api, self, params: @retrieve_params) end def self.retrieve(id, opts = {}) + if name.include?("Stripe::V2") + raise NotImplementedError, + "It is not possible to retrieve v2 objects on the resource. Please use the StripeClient instead." + end + opts = Util.normalize_opts(opts) instance = new(id, opts) instance.refresh instance end - def request_stripe_object(method:, path:, params:, opts: {}) - resp, opts = execute_resource_request(method, path, params, opts) - - # If we're getting back this thing, update; otherwise, instantiate. - if Util.object_name_matches_class?(resp.data[:object], self.class) - initialize_from(resp.data, opts, resp) - else - Util.convert_to_stripe_object_with_params(resp.data, params, opts, resp) - end + def request_stripe_object(method:, path:, params:, base_address: :api, opts: {}) + APIRequestor.active_requestor.execute_request_initialize_from(method, path, base_address, self, + params: params, opts: opts) end - protected def request_stream(method:, path:, params:, opts: {}, + protected def request_stream(method:, path:, params:, base_address: :api, opts: {}, &read_body_chunk_block) resp, = execute_resource_request_stream( - method, path, params, opts, &read_body_chunk_block + method, path, base_address, params, opts, &read_body_chunk_block ) resp end diff --git a/lib/stripe/api_version.rb b/lib/stripe/api_version.rb index 73331fa25..9d726d565 100644 --- a/lib/stripe/api_version.rb +++ b/lib/stripe/api_version.rb @@ -4,6 +4,5 @@ module Stripe module ApiVersion CURRENT = "2024-06-20" - PREVIEW = "cs_ubb_launch" end end diff --git a/lib/stripe/connection_manager.rb b/lib/stripe/connection_manager.rb index 0c0dd23a7..d0604a164 100644 --- a/lib/stripe/connection_manager.rb +++ b/lib/stripe/connection_manager.rb @@ -12,7 +12,7 @@ module Stripe class ConnectionManager # Timestamp (in seconds procured from the system's monotonic clock) # indicating when the connection manager last made a request. This is used - # by `StripeClient` to determine whether a connection manager should be + # by `APIRequestor` to determine whether a connection manager should be # garbage collected or not. attr_reader :last_used attr_reader :config diff --git a/lib/stripe/errors.rb b/lib/stripe/errors.rb index 3945e1468..3c6b3d363 100644 --- a/lib/stripe/errors.rb +++ b/lib/stripe/errors.rb @@ -27,7 +27,8 @@ def initialize(message = nil, http_status: nil, http_body: nil, # rubocop:todo L def construct_error_object return nil if @json_body.nil? || !@json_body.key?(:error) - ErrorObject.construct_from(@json_body[:error]) + # ErrorObject is shared between v1 and v2, so use original object_classes to find + ErrorObject.construct_from(@json_body[:error], {}, nil, :v1) end # Whether the error was the result of an idempotent replay, meaning that it @@ -130,7 +131,7 @@ def initialize(code, description, http_status: nil, http_body: nil, def construct_error_object return nil if @json_body.nil? - OAuthErrorObject.construct_from(@json_body) + OAuthErrorObject.construct_from(@json_body, {}, nil, :v1) end end @@ -166,4 +167,9 @@ class UnsupportedGrantTypeError < OAuthError class UnsupportedResponseTypeError < OAuthError end end + + # class definitions: The beginning of the section generated from our OpenAPI spec + class TemporarySessionExpiredError < StripeError + end + # class definitions: The end of the section generated from our OpenAPI spec end diff --git a/lib/stripe/event_types.rb b/lib/stripe/event_types.rb new file mode 100644 index 000000000..2b8a0568e --- /dev/null +++ b/lib/stripe/event_types.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module Stripe + module EventTypes + def self.thin_event_names_to_classes + { + # The beginning of the section generated from our OpenAPI spec + V1BillingMeterErrorReportTriggeredEvent.lookup_type => V1BillingMeterErrorReportTriggeredEvent, + V1BillingMeterNoMeterFoundEvent.lookup_type => V1BillingMeterNoMeterFoundEvent, + # The end of the section generated from our OpenAPI spec + } + end + end +end diff --git a/lib/stripe/events/v1_billing_meter_error_report_triggered_event.rb b/lib/stripe/events/v1_billing_meter_error_report_triggered_event.rb new file mode 100644 index 000000000..73c2344ee --- /dev/null +++ b/lib/stripe/events/v1_billing_meter_error_report_triggered_event.rb @@ -0,0 +1,23 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + # This event occurs when there are invalid async usage events for a given meter. + class V1BillingMeterErrorReportTriggeredEvent < Stripe::V2::Event + def self.lookup_type + "v1.billing.meter.error_report_triggered" + end + # There is additional data present for this event, accessible with the `data` property. + # See the Stripe API docs for more information. + + # Retrieves the related object from the API. Make an API request on every call. + def fetch_related_object + _request( + method: :get, + path: related_object.url, + base_address: :api, + opts: { stripe_account: context } + ) + end + end +end diff --git a/lib/stripe/events/v1_billing_meter_no_meter_found_event.rb b/lib/stripe/events/v1_billing_meter_no_meter_found_event.rb new file mode 100644 index 000000000..b9b780637 --- /dev/null +++ b/lib/stripe/events/v1_billing_meter_no_meter_found_event.rb @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + # This event occurs when async usage events have missing or invalid meter ids. + class V1BillingMeterNoMeterFoundEvent < Stripe::V2::Event + def self.lookup_type + "v1.billing.meter.no_meter_found" + end + # There is additional data present for this event, accessible with the `data` property. + # See the Stripe API docs for more information. + end +end diff --git a/lib/stripe/list_object.rb b/lib/stripe/list_object.rb index 79e785a69..7d0e01ff9 100644 --- a/lib/stripe/list_object.rb +++ b/lib/stripe/list_object.rb @@ -21,7 +21,7 @@ def self.object_name # there isn't a next page in order to replicate the behavior of the API # when it attempts to return a page beyond the last. def self.empty_list(opts = {}) - ListObject.construct_from({ data: [] }, opts) + ListObject.construct_from({ data: [] }, opts, nil, :v1) end def initialize(*args) @@ -96,8 +96,7 @@ def empty? def retrieve(id, opts = {}) id, retrieve_params = Util.normalize_id(id) url = "#{resource_url}/#{CGI.escape(id)}" - resp, opts = execute_resource_request(:get, url, retrieve_params, opts) - Util.convert_to_stripe_object(resp.data, opts) + execute_resource_request(:get, url, :api, retrieve_params, opts) end # Fetches the next page in the resource list (if there is one). diff --git a/lib/stripe/oauth.rb b/lib/stripe/oauth.rb index 0ac1d8561..b8e6780ef 100644 --- a/lib/stripe/oauth.rb +++ b/lib/stripe/oauth.rb @@ -5,12 +5,10 @@ module OAuth module OAuthOperations extend APIOperations::Request::ClassMethods - def self.execute_resource_request(method, url, params, opts) + def self.execute_resource_request(method, url, base_address, params, opts) opts = Util.normalize_opts(opts) - opts[:client] ||= opts[:client] || StripeClient.active_client - opts[:api_base] ||= opts[:client].config.connect_base - super(method, url, params, opts) + super(method, url, base_address, params, opts) end end @@ -29,15 +27,14 @@ def self.get_client_id(params = {}) end def self.authorize_url(params = {}, opts = {}) - client = opts[:client] || StripeClient.active_client - base = opts[:connect_base] || client.config.connect_base + base = opts[:connect_base] || APIRequestor.active_requestor.config.connect_base path = "/oauth/authorize" path = "/express" + path if opts[:express] params[:client_id] = get_client_id(params) params[:response_type] ||= "code" - query = Util.encode_parameters(params) + query = Util.encode_parameters(params, :v1) "#{base}#{path}?#{query}" end @@ -45,21 +42,17 @@ def self.authorize_url(params = {}, opts = {}) def self.token(params = {}, opts = {}) opts = Util.normalize_opts(opts) opts[:api_key] = params[:client_secret] if params[:client_secret] - resp, opts = OAuthOperations.execute_resource_request( - :post, "/oauth/token", params, opts + OAuthOperations.execute_resource_request( + :post, "/oauth/token", :connect, params, opts ) - # This is just going to return a generic StripeObject, but that's okay - Util.convert_to_stripe_object(resp.data, opts) end def self.deauthorize(params = {}, opts = {}) opts = Util.normalize_opts(opts) params[:client_id] = get_client_id(params) - resp, opts = OAuthOperations.execute_resource_request( - :post, "/oauth/deauthorize", params, opts + OAuthOperations.execute_resource_request( + :post, "/oauth/deauthorize", :connect, params, opts ) - # This is just going to return a generic StripeObject, but that's okay - Util.convert_to_stripe_object(resp.data, opts) end end end diff --git a/lib/stripe/object_types.rb b/lib/stripe/object_types.rb index 74ec32978..f7d0fc2b1 100644 --- a/lib/stripe/object_types.rb +++ b/lib/stripe/object_types.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true # rubocop:disable Metrics/MethodLength -# rubocop:disable Metrics/AbcSize module Stripe module ObjectTypes @@ -163,6 +162,19 @@ def self.object_names_to_classes # object classes: The end of the section generated from our OpenAPI spec } end + + def self.v2_object_names_to_classes + { + V2::ListObject.object_name => V2::ListObject, + + # v2 object classes: The beginning of the section generated from our OpenAPI spec + V2::Billing::MeterEvent.object_name => V2::Billing::MeterEvent, + V2::Billing::MeterEventAdjustment.object_name => V2::Billing::MeterEventAdjustment, + V2::Billing::MeterEventSession.object_name => V2::Billing::MeterEventSession, + V2::Event.object_name => V2::Event, + # v2 object classes: The end of the section generated from our OpenAPI spec + } + end end end diff --git a/lib/stripe/request_options.rb b/lib/stripe/request_options.rb new file mode 100644 index 000000000..56c29ec87 --- /dev/null +++ b/lib/stripe/request_options.rb @@ -0,0 +1,128 @@ +# frozen_string_literal: true +# typed: true + +module Stripe + # RequestOptions is a class that encapsulates configurable options + # for requests made to the Stripe API. It is used by the APIRequestor + # to set per-request options. + # + # For internal use only. Does not provide a stable API and may be broken + # with future non-major changes. + module RequestOptions + # Options that a user is allowed to specify. + OPTS_USER_SPECIFIED = Set[ + :api_key, + :idempotency_key, + :stripe_account, + :stripe_context, + :stripe_version + ].freeze + + # Options that should be copyable from one StripeObject to another + # including options that may be internal. + OPTS_COPYABLE = ( + OPTS_USER_SPECIFIED + Set[:api_base] + ).freeze + + # Options that should be persisted between API requests. + OPTS_PERSISTABLE = ( + OPTS_USER_SPECIFIED - Set[:idempotency_key, :stripe_context] + ).freeze + + # Merges requestor options on a StripeConfiguration object + # with a per-request options hash, giving precedence + # to the per-request options. Expects StripeConfiguration and hash. + def self.merge_config_and_opts(config, req_opts) + # Raise an error if config is not a StripeConfiguration object + unless config.is_a?(StripeConfiguration) + raise ArgumentError, "config must be a Stripe::StripeConfiguration object" + end + + merged_opts = { + api_key: req_opts[:api_key] || config.api_key, + idempotency_key: req_opts[:idempotency_key], + stripe_account: req_opts[:stripe_account] || config.stripe_account, + stripe_context: req_opts[:stripe_context] || config.stripe_context, + stripe_version: req_opts[:stripe_version] || config.api_version, + headers: req_opts[:headers] || {}, + } + + # Remove nil values from headers + merged_opts.delete_if { |_, v| v.nil? } + + merged_opts + end + + # Merges requestor options hash on a StripeObject + # with a per-request options hash, giving precedence + # to the per-request options. Returns the merged request options. + # Expects two hashes. + def self.combine_opts(object_opts, req_opts) + merged_opts = { + api_key: req_opts[:api_key] || object_opts[:api_key], + idempotency_key: req_opts[:idempotency_key], + stripe_account: req_opts[:stripe_account] || object_opts[:stripe_account], + stripe_context: req_opts[:stripe_context] || object_opts[:stripe_context], + stripe_version: req_opts[:stripe_version] || object_opts[:api_version], + } + + # Remove nil values from headers + merged_opts.delete_if { |_, v| v.nil? } + + merged_opts + end + + # Extracts options from a user-provided hash, returning a new request options hash + # containing the recognized request options and a `headers` entry for the remaining options. + def self.extract_opts_from_hash(opts) + req_opts = {} + normalized_opts = Util.normalize_opts(opts.clone) + + RequestOptions.error_on_non_string_user_opts(normalized_opts) + + OPTS_USER_SPECIFIED.each do |opt| + req_opts[opt] = normalized_opts[opt] if normalized_opts.key?(opt) + normalized_opts.delete(opt) + end + + # Remaining user-provided opts should be treated as headers + req_opts[:headers] = Util.normalize_headers(normalized_opts) if normalized_opts.any? + + req_opts + end + + # Validates a normalized opts hash. + def self.error_on_non_string_user_opts(normalized_opts) + OPTS_USER_SPECIFIED.each do |opt| + next unless normalized_opts.key?(opt) + + val = normalized_opts[opt] + next if val.nil? + next if val.is_a?(String) + + raise ArgumentError, + "request option '#{opt}' should be a string value " \ + "(was a #{val.class})" + end + end + + # Get options that persist between requests + def self.persistable(req_opts) + opts_to_persist = {} + + # Hash#select returns an array before 1.9 + req_opts.each do |k, v| + opts_to_persist[k] = v if RequestOptions::OPTS_PERSISTABLE.include?(k) + end + + opts_to_persist + end + + # 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 + end + end +end diff --git a/lib/stripe/resources.rb b/lib/stripe/resources.rb index 89f76ecdd..a449a6523 100644 --- a/lib/stripe/resources.rb +++ b/lib/stripe/resources.rb @@ -146,4 +146,10 @@ require "stripe/resources/treasury/transaction_entry" require "stripe/resources/usage_record" require "stripe/resources/usage_record_summary" +require "stripe/resources/v2/billing/meter_event" +require "stripe/resources/v2/billing/meter_event_adjustment" +require "stripe/resources/v2/billing/meter_event_session" +require "stripe/resources/v2/event" require "stripe/resources/webhook_endpoint" +require "stripe/events/v1_billing_meter_error_report_triggered_event" +require "stripe/events/v1_billing_meter_no_meter_found_event" diff --git a/lib/stripe/resources/file.rb b/lib/stripe/resources/file.rb index 4ac61fad1..ced211941 100644 --- a/lib/stripe/resources/file.rb +++ b/lib/stripe/resources/file.rb @@ -22,17 +22,19 @@ def self.object_name # # All of Stripe's officially supported Client libraries support sending multipart/form-data. def self.create(params = {}, opts = {}) - config = opts[:client]&.config || Stripe.config - upload_base = config.uploads_base - opts = { api_base: upload_base }.merge(Util.normalize_opts(opts)) - if params[:file] && !params[:file].is_a?(String) && !params[:file].respond_to?(:read) raise ArgumentError, "file must respond to `#read`" end opts = { content_type: MultipartEncoder::MULTIPART_FORM_DATA }.merge(Util.normalize_opts(opts)) - request_stripe_object(method: :post, path: "/v1/files", params: params, opts: opts) + request_stripe_object( + method: :post, + path: "/v1/files", + params: params, + opts: opts, + base_address: :files + ) end # Returns a list of the files that your account has access to. Stripe sorts and returns the files by their creation dates, placing the most recently created files at the top. diff --git a/lib/stripe/resources/quote.rb b/lib/stripe/resources/quote.rb index a3501e030..ca8f340c0 100644 --- a/lib/stripe/resources/quote.rb +++ b/lib/stripe/resources/quote.rb @@ -210,24 +210,24 @@ def self.mark_stale(quote, params = {}, opts = {}) # Download the PDF for a finalized quote. Explanation for special handling can be found [here](https://docs.stripe.com/quotes/overview#quote_pdf) def pdf(params = {}, opts = {}, &read_body_chunk_block) - config = opts[:client]&.config || Stripe.config - opts = { api_base: config.uploads_base }.merge(opts) + opts = { api_base: APIRequestor.active_requestor.config.uploads_base }.merge(opts) request_stream( method: :get, path: format("/v1/quotes/%s/pdf", { quote: CGI.escape(self["id"]) }), params: params, opts: opts, + base_address: :files, &read_body_chunk_block ) end # Download the PDF for a finalized quote. Explanation for special handling can be found [here](https://docs.stripe.com/quotes/overview#quote_pdf) def self.pdf(quote, params = {}, opts = {}, &read_body_chunk_block) - config = opts[:client]&.config || Stripe.config - opts = { api_base: config.uploads_base }.merge(opts) + opts = { api_base: APIRequestor.active_requestor.config.uploads_base }.merge(opts) execute_resource_request_stream( :get, format("/v1/quotes/%s/pdf", { quote: CGI.escape(quote) }), + :files, params, opts, &read_body_chunk_block diff --git a/lib/stripe/resources/source.rb b/lib/stripe/resources/source.rb index ba5e442ba..0ba0009e8 100644 --- a/lib/stripe/resources/source.rb +++ b/lib/stripe/resources/source.rb @@ -70,8 +70,9 @@ def detach(params = {}, opts = {}) url = "#{Customer.resource_url}/#{CGI.escape(customer)}/sources" \ "/#{CGI.escape(id)}" - resp, opts = execute_resource_request(:delete, url, params, opts) - initialize_from(resp.data, opts) + opts = Util.normalize_opts(opts) + APIRequestor.active_requestor.execute_request_initialize_from(:delete, url, :api, self, + params: params, opts: opts) end def source_transactions(params = {}, opts = {}) diff --git a/lib/stripe/resources/tax/form.rb b/lib/stripe/resources/tax/form.rb index 6d15b95dd..58bd83f9f 100644 --- a/lib/stripe/resources/tax/form.rb +++ b/lib/stripe/resources/tax/form.rb @@ -21,24 +21,24 @@ def self.list(filters = {}, opts = {}) # Download the PDF for a tax form. def pdf(params = {}, opts = {}, &read_body_chunk_block) - config = opts[:client]&.config || Stripe.config - opts = { api_base: config.uploads_base }.merge(opts) + opts = { api_base: APIRequestor.active_requestor.config.uploads_base }.merge(opts) request_stream( method: :get, path: format("/v1/tax/forms/%s/pdf", { id: CGI.escape(self["id"]) }), params: params, opts: opts, + base_address: :files, &read_body_chunk_block ) end # Download the PDF for a tax form. def self.pdf(id, params = {}, opts = {}, &read_body_chunk_block) - config = opts[:client]&.config || Stripe.config - opts = { api_base: config.uploads_base }.merge(opts) + opts = { api_base: APIRequestor.active_requestor.config.uploads_base }.merge(opts) execute_resource_request_stream( :get, format("/v1/tax/forms/%s/pdf", { id: CGI.escape(id) }), + :files, params, opts, &read_body_chunk_block diff --git a/lib/stripe/resources/v2/billing/meter_event.rb b/lib/stripe/resources/v2/billing/meter_event.rb new file mode 100644 index 000000000..a7ddc2bfa --- /dev/null +++ b/lib/stripe/resources/v2/billing/meter_event.rb @@ -0,0 +1,16 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + module V2 + module Billing + # Fix me empty_doc_string. + class MeterEvent < APIResource + OBJECT_NAME = "billing.meter_event" + def self.object_name + "billing.meter_event" + end + end + end + end +end diff --git a/lib/stripe/resources/v2/billing/meter_event_adjustment.rb b/lib/stripe/resources/v2/billing/meter_event_adjustment.rb new file mode 100644 index 000000000..1e89b1802 --- /dev/null +++ b/lib/stripe/resources/v2/billing/meter_event_adjustment.rb @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + module V2 + module Billing + class MeterEventAdjustment < APIResource + OBJECT_NAME = "billing.meter_event_adjustment" + def self.object_name + "billing.meter_event_adjustment" + end + end + end + end +end diff --git a/lib/stripe/resources/v2/billing/meter_event_session.rb b/lib/stripe/resources/v2/billing/meter_event_session.rb new file mode 100644 index 000000000..cc6ced827 --- /dev/null +++ b/lib/stripe/resources/v2/billing/meter_event_session.rb @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + module V2 + module Billing + class MeterEventSession < APIResource + OBJECT_NAME = "billing.meter_event_session" + def self.object_name + "billing.meter_event_session" + end + end + end + end +end diff --git a/lib/stripe/resources/v2/event.rb b/lib/stripe/resources/v2/event.rb new file mode 100644 index 000000000..df657b403 --- /dev/null +++ b/lib/stripe/resources/v2/event.rb @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + module V2 + class Event < APIResource + OBJECT_NAME = "v2.core.event" + def self.object_name + "v2.core.event" + end + end + end +end diff --git a/lib/stripe/search_result_object.rb b/lib/stripe/search_result_object.rb index bc8586655..52a4c5e35 100644 --- a/lib/stripe/search_result_object.rb +++ b/lib/stripe/search_result_object.rb @@ -20,7 +20,7 @@ def self.object_name # that there isn't a next page in order to replicate the behavior of the API # when it attempts to return a page beyond the last. def self.empty_search_result(opts = {}) - SearchResultObject.construct_from({ data: [] }, opts) + SearchResultObject.construct_from({ data: [] }, opts, :v1) end def initialize(*args) diff --git a/lib/stripe/services.rb b/lib/stripe/services.rb new file mode 100644 index 000000000..05ad6773f --- /dev/null +++ b/lib/stripe/services.rb @@ -0,0 +1,200 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +require "stripe/services/account_capability_service" +require "stripe/services/account_external_account_service" +require "stripe/services/account_link_service" +require "stripe/services/account_login_link_service" +require "stripe/services/account_notice_service" +require "stripe/services/account_person_service" +require "stripe/services/account_service" +require "stripe/services/account_session_service" +require "stripe/services/apple_pay_domain_service" +require "stripe/services/application_fee_refund_service" +require "stripe/services/application_fee_service" +require "stripe/services/apps/secret_service" +require "stripe/services/apps_service" +require "stripe/services/balance_service" +require "stripe/services/balance_transaction_service" +require "stripe/services/billing/alert_service" +require "stripe/services/billing/meter_event_adjustment_service" +require "stripe/services/billing/meter_event_service" +require "stripe/services/billing/meter_event_summary_service" +require "stripe/services/billing/meter_service" +require "stripe/services/billing_portal/configuration_service" +require "stripe/services/billing_portal/session_service" +require "stripe/services/billing_portal_service" +require "stripe/services/billing_service" +require "stripe/services/capital/financing_offer_service" +require "stripe/services/capital/financing_summary_service" +require "stripe/services/capital/financing_transaction_service" +require "stripe/services/capital_service" +require "stripe/services/charge_service" +require "stripe/services/checkout/session_line_item_service" +require "stripe/services/checkout/session_service" +require "stripe/services/checkout_service" +require "stripe/services/climate/order_service" +require "stripe/services/climate/product_service" +require "stripe/services/climate/supplier_service" +require "stripe/services/climate_service" +require "stripe/services/confirmation_token_service" +require "stripe/services/country_spec_service" +require "stripe/services/coupon_service" +require "stripe/services/credit_note_line_item_service" +require "stripe/services/credit_note_preview_lines_service" +require "stripe/services/credit_note_service" +require "stripe/services/customer_balance_transaction_service" +require "stripe/services/customer_cash_balance_service" +require "stripe/services/customer_cash_balance_transaction_service" +require "stripe/services/customer_funding_instructions_service" +require "stripe/services/customer_payment_method_service" +require "stripe/services/customer_payment_source_service" +require "stripe/services/customer_service" +require "stripe/services/customer_session_service" +require "stripe/services/customer_tax_id_service" +require "stripe/services/dispute_service" +require "stripe/services/entitlements/active_entitlement_service" +require "stripe/services/entitlements/feature_service" +require "stripe/services/entitlements_service" +require "stripe/services/ephemeral_key_service" +require "stripe/services/event_service" +require "stripe/services/exchange_rate_service" +require "stripe/services/file_link_service" +require "stripe/services/file_service" +require "stripe/services/financial_connections/account_inferred_balance_service" +require "stripe/services/financial_connections/account_owner_service" +require "stripe/services/financial_connections/account_service" +require "stripe/services/financial_connections/institution_service" +require "stripe/services/financial_connections/session_service" +require "stripe/services/financial_connections/transaction_service" +require "stripe/services/financial_connections_service" +require "stripe/services/forwarding/request_service" +require "stripe/services/forwarding_service" +require "stripe/services/gift_cards/card_service" +require "stripe/services/gift_cards/transaction_service" +require "stripe/services/gift_cards_service" +require "stripe/services/identity/verification_report_service" +require "stripe/services/identity/verification_session_service" +require "stripe/services/identity_service" +require "stripe/services/invoice_item_service" +require "stripe/services/invoice_line_item_service" +require "stripe/services/invoice_payment_service" +require "stripe/services/invoice_rendering_template_service" +require "stripe/services/invoice_service" +require "stripe/services/invoice_upcoming_lines_service" +require "stripe/services/issuing/authorization_service" +require "stripe/services/issuing/card_service" +require "stripe/services/issuing/cardholder_service" +require "stripe/services/issuing/credit_underwriting_record_service" +require "stripe/services/issuing/dispute_service" +require "stripe/services/issuing/dispute_settlement_detail_service" +require "stripe/services/issuing/personalization_design_service" +require "stripe/services/issuing/physical_bundle_service" +require "stripe/services/issuing/token_service" +require "stripe/services/issuing/transaction_service" +require "stripe/services/issuing_service" +require "stripe/services/mandate_service" +require "stripe/services/margin_service" +require "stripe/services/order_line_item_service" +require "stripe/services/order_service" +require "stripe/services/payment_intent_service" +require "stripe/services/payment_link_line_item_service" +require "stripe/services/payment_link_service" +require "stripe/services/payment_method_configuration_service" +require "stripe/services/payment_method_domain_service" +require "stripe/services/payment_method_service" +require "stripe/services/payout_service" +require "stripe/services/plan_service" +require "stripe/services/price_service" +require "stripe/services/product_feature_service" +require "stripe/services/product_service" +require "stripe/services/promotion_code_service" +require "stripe/services/quote_computed_upfront_line_items_service" +require "stripe/services/quote_line_item_service" +require "stripe/services/quote_line_service" +require "stripe/services/quote_preview_invoice_service" +require "stripe/services/quote_preview_subscription_schedule_service" +require "stripe/services/quote_service" +require "stripe/services/radar/early_fraud_warning_service" +require "stripe/services/radar/value_list_item_service" +require "stripe/services/radar/value_list_service" +require "stripe/services/radar_service" +require "stripe/services/refund_service" +require "stripe/services/reporting/report_run_service" +require "stripe/services/reporting/report_type_service" +require "stripe/services/reporting_service" +require "stripe/services/review_service" +require "stripe/services/setup_attempt_service" +require "stripe/services/setup_intent_service" +require "stripe/services/shipping_rate_service" +require "stripe/services/sigma/scheduled_query_run_service" +require "stripe/services/sigma_service" +require "stripe/services/source_service" +require "stripe/services/source_transaction_service" +require "stripe/services/subscription_item_service" +require "stripe/services/subscription_item_usage_record_service" +require "stripe/services/subscription_item_usage_record_summary_service" +require "stripe/services/subscription_schedule_service" +require "stripe/services/subscription_service" +require "stripe/services/tax/association_service" +require "stripe/services/tax/calculation_line_item_service" +require "stripe/services/tax/calculation_service" +require "stripe/services/tax/form_service" +require "stripe/services/tax/registration_service" +require "stripe/services/tax/settings_service" +require "stripe/services/tax/transaction_line_item_service" +require "stripe/services/tax/transaction_service" +require "stripe/services/tax_code_service" +require "stripe/services/tax_id_service" +require "stripe/services/tax_rate_service" +require "stripe/services/tax_service" +require "stripe/services/terminal/configuration_service" +require "stripe/services/terminal/connection_token_service" +require "stripe/services/terminal/location_service" +require "stripe/services/terminal/reader_collected_data_service" +require "stripe/services/terminal/reader_service" +require "stripe/services/terminal_service" +require "stripe/services/test_helpers/confirmation_token_service" +require "stripe/services/test_helpers/customer_service" +require "stripe/services/test_helpers/issuing/authorization_service" +require "stripe/services/test_helpers/issuing/card_service" +require "stripe/services/test_helpers/issuing/personalization_design_service" +require "stripe/services/test_helpers/issuing/transaction_service" +require "stripe/services/test_helpers/issuing_service" +require "stripe/services/test_helpers/refund_service" +require "stripe/services/test_helpers/terminal/reader_service" +require "stripe/services/test_helpers/terminal_service" +require "stripe/services/test_helpers/test_clock_service" +require "stripe/services/test_helpers/treasury/inbound_transfer_service" +require "stripe/services/test_helpers/treasury/outbound_payment_service" +require "stripe/services/test_helpers/treasury/outbound_transfer_service" +require "stripe/services/test_helpers/treasury/received_credit_service" +require "stripe/services/test_helpers/treasury/received_debit_service" +require "stripe/services/test_helpers/treasury_service" +require "stripe/services/test_helpers_service" +require "stripe/services/token_service" +require "stripe/services/topup_service" +require "stripe/services/transfer_reversal_service" +require "stripe/services/transfer_service" +require "stripe/services/treasury/credit_reversal_service" +require "stripe/services/treasury/debit_reversal_service" +require "stripe/services/treasury/financial_account_features_service" +require "stripe/services/treasury/financial_account_service" +require "stripe/services/treasury/inbound_transfer_service" +require "stripe/services/treasury/outbound_payment_service" +require "stripe/services/treasury/outbound_transfer_service" +require "stripe/services/treasury/received_credit_service" +require "stripe/services/treasury/received_debit_service" +require "stripe/services/treasury/transaction_entry_service" +require "stripe/services/treasury/transaction_service" +require "stripe/services/treasury_service" +require "stripe/services/v1_services" +require "stripe/services/v2/billing/meter_event_adjustment_service" +require "stripe/services/v2/billing/meter_event_service" +require "stripe/services/v2/billing/meter_event_session_service" +require "stripe/services/v2/billing/meter_event_stream_service" +require "stripe/services/v2/billing_service" +require "stripe/services/v2/core/event_service" +require "stripe/services/v2/core_service" +require "stripe/services/v2_services" +require "stripe/services/webhook_endpoint_service" diff --git a/lib/stripe/services/account_capability_service.rb b/lib/stripe/services/account_capability_service.rb new file mode 100644 index 000000000..61b958699 --- /dev/null +++ b/lib/stripe/services/account_capability_service.rb @@ -0,0 +1,39 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + class AccountCapabilityService < StripeService + # Returns a list of capabilities associated with the account. The capabilities are returned sorted by creation date, with the most recent capability appearing first. + def list(account, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/accounts/%s/capabilities", { account: CGI.escape(account) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Retrieves information about the specified Account Capability. + def retrieve(account, capability, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/accounts/%s/capabilities/%s", { account: CGI.escape(account), capability: CGI.escape(capability) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Updates an existing Account Capability. Request or remove a capability by updating its requested parameter. + def update(account, capability, params = {}, opts = {}) + request( + method: :post, + path: format("/v1/accounts/%s/capabilities/%s", { account: CGI.escape(account), capability: CGI.escape(capability) }), + params: params, + opts: opts, + base_address: :api + ) + end + end +end diff --git a/lib/stripe/services/account_external_account_service.rb b/lib/stripe/services/account_external_account_service.rb new file mode 100644 index 000000000..98ceee1e4 --- /dev/null +++ b/lib/stripe/services/account_external_account_service.rb @@ -0,0 +1,68 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + class AccountExternalAccountService < StripeService + # Create an external account for a given account. + def create(account, params = {}, opts = {}) + request( + method: :post, + path: format("/v1/accounts/%s/external_accounts", { account: CGI.escape(account) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Delete a specified external account for a given account. + def delete(account, id, params = {}, opts = {}) + request( + method: :delete, + path: format("/v1/accounts/%s/external_accounts/%s", { account: CGI.escape(account), id: CGI.escape(id) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # List external accounts for an account. + def list(account, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/accounts/%s/external_accounts", { account: CGI.escape(account) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Retrieve a specified external account for a given account. + def retrieve(account, id, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/accounts/%s/external_accounts/%s", { account: CGI.escape(account), id: CGI.escape(id) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Updates the metadata, account holder name, account holder type of a bank account belonging to + # a connected account and optionally sets it as the default for its currency. Other bank account + # details are not editable by design. + # + # You can only update bank accounts when [account.controller.requirement_collection is application, which includes Custom accounts](https://stripe.com/api/accounts/object#account_object-controller-requirement_collection). + # + # You can re-enable a disabled bank account by performing an update call without providing any + # arguments or changes. + def update(account, id, params = {}, opts = {}) + request( + method: :post, + path: format("/v1/accounts/%s/external_accounts/%s", { account: CGI.escape(account), id: CGI.escape(id) }), + params: params, + opts: opts, + base_address: :api + ) + end + end +end diff --git a/lib/stripe/services/account_link_service.rb b/lib/stripe/services/account_link_service.rb new file mode 100644 index 000000000..f5318f1a8 --- /dev/null +++ b/lib/stripe/services/account_link_service.rb @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + class AccountLinkService < StripeService + # Creates an AccountLink object that includes a single-use Stripe URL that the platform can redirect their user to in order to take them through the Connect Onboarding flow. + def create(params = {}, opts = {}) + request( + method: :post, + path: "/v1/account_links", + params: params, + opts: opts, + base_address: :api + ) + end + end +end diff --git a/lib/stripe/services/account_login_link_service.rb b/lib/stripe/services/account_login_link_service.rb new file mode 100644 index 000000000..a4370ac5a --- /dev/null +++ b/lib/stripe/services/account_login_link_service.rb @@ -0,0 +1,19 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + class AccountLoginLinkService < StripeService + # Creates a single-use login link for a connected account to access the Express Dashboard. + # + # You can only create login links for accounts that use the [Express Dashboard](https://stripe.com/connect/express-dashboard) and are connected to your platform. + def create(account, params = {}, opts = {}) + request( + method: :post, + path: format("/v1/accounts/%s/login_links", { account: CGI.escape(account) }), + params: params, + opts: opts, + base_address: :api + ) + end + end +end diff --git a/lib/stripe/services/account_notice_service.rb b/lib/stripe/services/account_notice_service.rb new file mode 100644 index 000000000..d67d766bc --- /dev/null +++ b/lib/stripe/services/account_notice_service.rb @@ -0,0 +1,39 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + class AccountNoticeService < StripeService + # Retrieves a list of AccountNotice objects. The objects are sorted in descending order by creation date, with the most-recently-created object appearing first. + def list(params = {}, opts = {}) + request( + method: :get, + path: "/v1/account_notices", + params: params, + opts: opts, + base_address: :api + ) + end + + # Retrieves an AccountNotice object. + def retrieve(account_notice, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/account_notices/%s", { account_notice: CGI.escape(account_notice) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Updates an AccountNotice object. + def update(account_notice, params = {}, opts = {}) + request( + method: :post, + path: format("/v1/account_notices/%s", { account_notice: CGI.escape(account_notice) }), + params: params, + opts: opts, + base_address: :api + ) + end + end +end diff --git a/lib/stripe/services/account_person_service.rb b/lib/stripe/services/account_person_service.rb new file mode 100644 index 000000000..cdee2fed3 --- /dev/null +++ b/lib/stripe/services/account_person_service.rb @@ -0,0 +1,61 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + class AccountPersonService < StripeService + # Creates a new person. + def create(account, params = {}, opts = {}) + request( + method: :post, + path: format("/v1/accounts/%s/persons", { account: CGI.escape(account) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Deletes an existing person's relationship to the account's legal entity. Any person with a relationship for an account can be deleted through the API, except if the person is the account_opener. If your integration is using the executive parameter, you cannot delete the only verified executive on file. + def delete(account, person, params = {}, opts = {}) + request( + method: :delete, + path: format("/v1/accounts/%s/persons/%s", { account: CGI.escape(account), person: CGI.escape(person) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Returns a list of people associated with the account's legal entity. The people are returned sorted by creation date, with the most recent people appearing first. + def list(account, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/accounts/%s/persons", { account: CGI.escape(account) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Retrieves an existing person. + def retrieve(account, person, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/accounts/%s/persons/%s", { account: CGI.escape(account), person: CGI.escape(person) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Updates an existing person. + def update(account, person, params = {}, opts = {}) + request( + method: :post, + path: format("/v1/accounts/%s/persons/%s", { account: CGI.escape(account), person: CGI.escape(person) }), + params: params, + opts: opts, + base_address: :api + ) + end + end +end diff --git a/lib/stripe/services/account_service.rb b/lib/stripe/services/account_service.rb new file mode 100644 index 000000000..8b546c369 --- /dev/null +++ b/lib/stripe/services/account_service.rb @@ -0,0 +1,100 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + class AccountService < StripeService + attr_reader :capabilities, :external_accounts, :login_links, :persons + + def initialize(requestor) + super(requestor) + @capabilities = Stripe::AccountCapabilityService.new(@requestor) + @external_accounts = Stripe::AccountExternalAccountService.new(@requestor) + @login_links = Stripe::AccountLoginLinkService.new(@requestor) + @persons = Stripe::AccountPersonService.new(@requestor) + end + + # With [Connect](https://stripe.com/docs/connect), you can create Stripe accounts for your users. + # To do this, you'll first need to [register your platform](https://dashboard.stripe.com/account/applications/settings). + # + # If you've already collected information for your connected accounts, you [can prefill that information](https://stripe.com/docs/connect/best-practices#onboarding) when + # creating the account. Connect Onboarding won't ask for the prefilled information during account onboarding. + # You can prefill any information on the account. + def create(params = {}, opts = {}) + request(method: :post, path: "/v1/accounts", params: params, opts: opts, base_address: :api) + end + + # With [Connect](https://stripe.com/connect), you can delete accounts you manage. + # + # Test-mode accounts can be deleted at any time. + # + # Live-mode accounts where Stripe is responsible for negative account balances cannot be deleted, which includes Standard accounts. Live-mode accounts where your platform is liable for negative account balances, which includes Custom and Express accounts, can be deleted when all [balances](https://stripe.com/api/balance/balance_object) are zero. + # + # If you want to delete your own account, use the [account information tab in your account settings](https://dashboard.stripe.com/settings/account) instead. + def delete(account, params = {}, opts = {}) + request( + method: :delete, + path: format("/v1/accounts/%s", { account: CGI.escape(account) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Returns a list of accounts connected to your platform via [Connect](https://stripe.com/docs/connect). If you're not a platform, the list is empty. + def list(params = {}, opts = {}) + request(method: :get, path: "/v1/accounts", params: params, opts: opts, base_address: :api) + end + + # With [Connect](https://stripe.com/connect), you can reject accounts that you have flagged as suspicious. + # + # Only accounts where your platform is liable for negative account balances, which includes Custom and Express accounts, can be rejected. Test-mode accounts can be rejected at any time. Live-mode accounts can only be rejected after all balances are zero. + def reject(account, params = {}, opts = {}) + request( + method: :post, + path: format("/v1/accounts/%s/reject", { account: CGI.escape(account) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Retrieves the details of an account. + def retrieve(account, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/accounts/%s", { account: CGI.escape(account) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Retrieves the details of an account. + def retrieve_current(params = {}, opts = {}) + request(method: :get, path: "/v1/account", params: params, opts: opts, base_address: :api) + end + + # Updates a [connected account](https://stripe.com/connect/accounts) by setting the values of the parameters passed. Any parameters not provided are + # left unchanged. + # + # For accounts where [controller.requirement_collection](https://stripe.com/api/accounts/object#account_object-controller-requirement_collection) + # is application, which includes Custom accounts, you can update any information on the account. + # + # For accounts where [controller.requirement_collection](https://stripe.com/api/accounts/object#account_object-controller-requirement_collection) + # is stripe, which includes Standard and Express accounts, you can update all information until you create + # an [Account Link or Account Session](https://stripe.com/api/account_links) to start Connect onboarding, + # after which some properties can no longer be updated. + # + # To update your own account, use the [Dashboard](https://dashboard.stripe.com/settings/account). Refer to our + # [Connect](https://stripe.com/docs/connect/updating-accounts) documentation to learn more about updating accounts. + def update(account, params = {}, opts = {}) + request( + method: :post, + path: format("/v1/accounts/%s", { account: CGI.escape(account) }), + params: params, + opts: opts, + base_address: :api + ) + end + end +end diff --git a/lib/stripe/services/account_session_service.rb b/lib/stripe/services/account_session_service.rb new file mode 100644 index 000000000..2947d1f00 --- /dev/null +++ b/lib/stripe/services/account_session_service.rb @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + class AccountSessionService < StripeService + # Creates a AccountSession object that includes a single-use token that the platform can use on their front-end to grant client-side API access. + def create(params = {}, opts = {}) + request( + method: :post, + path: "/v1/account_sessions", + params: params, + opts: opts, + base_address: :api + ) + end + end +end diff --git a/lib/stripe/services/apple_pay_domain_service.rb b/lib/stripe/services/apple_pay_domain_service.rb new file mode 100644 index 000000000..8adbb1e96 --- /dev/null +++ b/lib/stripe/services/apple_pay_domain_service.rb @@ -0,0 +1,50 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + class ApplePayDomainService < StripeService + # Create an apple pay domain. + def create(params = {}, opts = {}) + request( + method: :post, + path: "/v1/apple_pay/domains", + params: params, + opts: opts, + base_address: :api + ) + end + + # Delete an apple pay domain. + def delete(domain, params = {}, opts = {}) + request( + method: :delete, + path: format("/v1/apple_pay/domains/%s", { domain: CGI.escape(domain) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # List apple pay domains. + def list(params = {}, opts = {}) + request( + method: :get, + path: "/v1/apple_pay/domains", + params: params, + opts: opts, + base_address: :api + ) + end + + # Retrieve an apple pay domain. + def retrieve(domain, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/apple_pay/domains/%s", { domain: CGI.escape(domain) }), + params: params, + opts: opts, + base_address: :api + ) + end + end +end diff --git a/lib/stripe/services/application_fee_refund_service.rb b/lib/stripe/services/application_fee_refund_service.rb new file mode 100644 index 000000000..962ea39b9 --- /dev/null +++ b/lib/stripe/services/application_fee_refund_service.rb @@ -0,0 +1,60 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + class ApplicationFeeRefundService < StripeService + # Refunds an application fee that has previously been collected but not yet refunded. + # Funds will be refunded to the Stripe account from which the fee was originally collected. + # + # You can optionally refund only part of an application fee. + # You can do so multiple times, until the entire fee has been refunded. + # + # Once entirely refunded, an application fee can't be refunded again. + # This method will raise an error when called on an already-refunded application fee, + # or when trying to refund more money than is left on an application fee. + def create(id, params = {}, opts = {}) + request( + method: :post, + path: format("/v1/application_fees/%s/refunds", { id: CGI.escape(id) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # You can see a list of the refunds belonging to a specific application fee. Note that the 10 most recent refunds are always available by default on the application fee object. If you need more than those 10, you can use this API method and the limit and starting_after parameters to page through additional refunds. + def list(id, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/application_fees/%s/refunds", { id: CGI.escape(id) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # By default, you can see the 10 most recent refunds stored directly on the application fee object, but you can also retrieve details about a specific refund stored on the application fee. + def retrieve(fee, id, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/application_fees/%s/refunds/%s", { fee: CGI.escape(fee), id: CGI.escape(id) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Updates the specified application fee refund by setting the values of the parameters passed. Any parameters not provided will be left unchanged. + # + # This request only accepts metadata as an argument. + def update(fee, id, params = {}, opts = {}) + request( + method: :post, + path: format("/v1/application_fees/%s/refunds/%s", { fee: CGI.escape(fee), id: CGI.escape(id) }), + params: params, + opts: opts, + base_address: :api + ) + end + end +end diff --git a/lib/stripe/services/application_fee_service.rb b/lib/stripe/services/application_fee_service.rb new file mode 100644 index 000000000..75e842564 --- /dev/null +++ b/lib/stripe/services/application_fee_service.rb @@ -0,0 +1,35 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + class ApplicationFeeService < StripeService + attr_reader :refunds + + def initialize(requestor) + super(requestor) + @refunds = Stripe::ApplicationFeeRefundService.new(@requestor) + end + + # Returns a list of application fees you've previously collected. The application fees are returned in sorted order, with the most recent fees appearing first. + def list(params = {}, opts = {}) + request( + method: :get, + path: "/v1/application_fees", + params: params, + opts: opts, + base_address: :api + ) + end + + # Retrieves the details of an application fee that your account has collected. The same information is returned when refunding the application fee. + def retrieve(id, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/application_fees/%s", { id: CGI.escape(id) }), + params: params, + opts: opts, + base_address: :api + ) + end + end +end diff --git a/lib/stripe/services/apps/secret_service.rb b/lib/stripe/services/apps/secret_service.rb new file mode 100644 index 000000000..c08d5657c --- /dev/null +++ b/lib/stripe/services/apps/secret_service.rb @@ -0,0 +1,52 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + module Apps + class SecretService < StripeService + # Create or replace a secret in the secret store. + def create(params = {}, opts = {}) + request( + method: :post, + path: "/v1/apps/secrets", + params: params, + opts: opts, + base_address: :api + ) + end + + # Deletes a secret from the secret store by name and scope. + def delete_where(params = {}, opts = {}) + request( + method: :post, + path: "/v1/apps/secrets/delete", + params: params, + opts: opts, + base_address: :api + ) + end + + # Finds a secret in the secret store by name and scope. + def find(params = {}, opts = {}) + request( + method: :get, + path: "/v1/apps/secrets/find", + params: params, + opts: opts, + base_address: :api + ) + end + + # List all secrets stored on the given scope. + def list(params = {}, opts = {}) + request( + method: :get, + path: "/v1/apps/secrets", + params: params, + opts: opts, + base_address: :api + ) + end + end + end +end diff --git a/lib/stripe/services/apps_service.rb b/lib/stripe/services/apps_service.rb new file mode 100644 index 000000000..6dc1b1f8a --- /dev/null +++ b/lib/stripe/services/apps_service.rb @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + class AppsService < StripeService + attr_reader :secrets + + def initialize(requestor) + super(requestor) + @secrets = Stripe::Apps::SecretService.new(@requestor) + end + end +end diff --git a/lib/stripe/services/balance_service.rb b/lib/stripe/services/balance_service.rb new file mode 100644 index 000000000..787b4c38b --- /dev/null +++ b/lib/stripe/services/balance_service.rb @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + class BalanceService < StripeService + # Retrieves the current account balance, based on the authentication that was used to make the request. + # For a sample request, see [Accounting for negative balances](https://stripe.com/docs/connect/account-balances#accounting-for-negative-balances). + def retrieve(params = {}, opts = {}) + request(method: :get, path: "/v1/balance", params: params, opts: opts, base_address: :api) + end + end +end diff --git a/lib/stripe/services/balance_transaction_service.rb b/lib/stripe/services/balance_transaction_service.rb new file mode 100644 index 000000000..7f856eb8b --- /dev/null +++ b/lib/stripe/services/balance_transaction_service.rb @@ -0,0 +1,32 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + class BalanceTransactionService < StripeService + # Returns a list of transactions that have contributed to the Stripe account balance (e.g., charges, transfers, and so forth). The transactions are returned in sorted order, with the most recent transactions appearing first. + # + # Note that this endpoint was previously called “Balance history” and used the path /v1/balance/history. + def list(params = {}, opts = {}) + request( + method: :get, + path: "/v1/balance_transactions", + params: params, + opts: opts, + base_address: :api + ) + end + + # Retrieves the balance transaction with the given ID. + # + # Note that this endpoint previously used the path /v1/balance/history/:id. + def retrieve(id, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/balance_transactions/%s", { id: CGI.escape(id) }), + params: params, + opts: opts, + base_address: :api + ) + end + end +end diff --git a/lib/stripe/services/billing/alert_service.rb b/lib/stripe/services/billing/alert_service.rb new file mode 100644 index 000000000..e076a8037 --- /dev/null +++ b/lib/stripe/services/billing/alert_service.rb @@ -0,0 +1,74 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + module Billing + class AlertService < StripeService + # Reactivates this alert, allowing it to trigger again. + def activate(id, params = {}, opts = {}) + request( + method: :post, + path: format("/v1/billing/alerts/%s/activate", { id: CGI.escape(id) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Archives this alert, removing it from the list view and APIs. This is non-reversible. + def archive(id, params = {}, opts = {}) + request( + method: :post, + path: format("/v1/billing/alerts/%s/archive", { id: CGI.escape(id) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Creates a billing alert + def create(params = {}, opts = {}) + request( + method: :post, + path: "/v1/billing/alerts", + params: params, + opts: opts, + base_address: :api + ) + end + + # Deactivates this alert, preventing it from triggering. + def deactivate(id, params = {}, opts = {}) + request( + method: :post, + path: format("/v1/billing/alerts/%s/deactivate", { id: CGI.escape(id) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Lists billing active and inactive alerts + def list(params = {}, opts = {}) + request( + method: :get, + path: "/v1/billing/alerts", + params: params, + opts: opts, + base_address: :api + ) + end + + # Retrieves a billing alert given an ID + def retrieve(id, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/billing/alerts/%s", { id: CGI.escape(id) }), + params: params, + opts: opts, + base_address: :api + ) + end + end + end +end diff --git a/lib/stripe/services/billing/meter_event_adjustment_service.rb b/lib/stripe/services/billing/meter_event_adjustment_service.rb new file mode 100644 index 000000000..147b8c0d0 --- /dev/null +++ b/lib/stripe/services/billing/meter_event_adjustment_service.rb @@ -0,0 +1,19 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + module Billing + class MeterEventAdjustmentService < StripeService + # Creates a billing meter event adjustment + def create(params = {}, opts = {}) + request( + method: :post, + path: "/v1/billing/meter_event_adjustments", + params: params, + opts: opts, + base_address: :api + ) + end + end + end +end diff --git a/lib/stripe/services/billing/meter_event_service.rb b/lib/stripe/services/billing/meter_event_service.rb new file mode 100644 index 000000000..d12445ce0 --- /dev/null +++ b/lib/stripe/services/billing/meter_event_service.rb @@ -0,0 +1,19 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + module Billing + class MeterEventService < StripeService + # Creates a billing meter event + def create(params = {}, opts = {}) + request( + method: :post, + path: "/v1/billing/meter_events", + params: params, + opts: opts, + base_address: :api + ) + end + end + end +end diff --git a/lib/stripe/services/billing/meter_event_summary_service.rb b/lib/stripe/services/billing/meter_event_summary_service.rb new file mode 100644 index 000000000..0bdcfb378 --- /dev/null +++ b/lib/stripe/services/billing/meter_event_summary_service.rb @@ -0,0 +1,19 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + module Billing + class MeterEventSummaryService < StripeService + # Retrieve a list of billing meter event summaries. + def list(id, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/billing/meters/%s/event_summaries", { id: CGI.escape(id) }), + params: params, + opts: opts, + base_address: :api + ) + end + end + end +end diff --git a/lib/stripe/services/billing/meter_service.rb b/lib/stripe/services/billing/meter_service.rb new file mode 100644 index 000000000..dbb2d2f49 --- /dev/null +++ b/lib/stripe/services/billing/meter_service.rb @@ -0,0 +1,81 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + module Billing + class MeterService < StripeService + attr_reader :event_summaries + + def initialize(requestor) + super(requestor) + @event_summaries = Stripe::Billing::MeterEventSummaryService.new(@requestor) + end + + # Creates a billing meter + def create(params = {}, opts = {}) + request( + method: :post, + path: "/v1/billing/meters", + params: params, + opts: opts, + base_address: :api + ) + end + + # Deactivates a billing meter + def deactivate(id, params = {}, opts = {}) + request( + method: :post, + path: format("/v1/billing/meters/%s/deactivate", { id: CGI.escape(id) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Retrieve a list of billing meters. + def list(params = {}, opts = {}) + request( + method: :get, + path: "/v1/billing/meters", + params: params, + opts: opts, + base_address: :api + ) + end + + # Reactivates a billing meter + def reactivate(id, params = {}, opts = {}) + request( + method: :post, + path: format("/v1/billing/meters/%s/reactivate", { id: CGI.escape(id) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Retrieves a billing meter given an ID + def retrieve(id, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/billing/meters/%s", { id: CGI.escape(id) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Updates a billing meter + def update(id, params = {}, opts = {}) + request( + method: :post, + path: format("/v1/billing/meters/%s", { id: CGI.escape(id) }), + params: params, + opts: opts, + base_address: :api + ) + end + end + end +end diff --git a/lib/stripe/services/billing_portal/configuration_service.rb b/lib/stripe/services/billing_portal/configuration_service.rb new file mode 100644 index 000000000..eeca9e991 --- /dev/null +++ b/lib/stripe/services/billing_portal/configuration_service.rb @@ -0,0 +1,52 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + module BillingPortal + class ConfigurationService < StripeService + # Creates a configuration that describes the functionality and behavior of a PortalSession + def create(params = {}, opts = {}) + request( + method: :post, + path: "/v1/billing_portal/configurations", + params: params, + opts: opts, + base_address: :api + ) + end + + # Returns a list of configurations that describe the functionality of the customer portal. + def list(params = {}, opts = {}) + request( + method: :get, + path: "/v1/billing_portal/configurations", + params: params, + opts: opts, + base_address: :api + ) + end + + # Retrieves a configuration that describes the functionality of the customer portal. + def retrieve(configuration, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/billing_portal/configurations/%s", { configuration: CGI.escape(configuration) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Updates a configuration that describes the functionality of the customer portal. + def update(configuration, params = {}, opts = {}) + request( + method: :post, + path: format("/v1/billing_portal/configurations/%s", { configuration: CGI.escape(configuration) }), + params: params, + opts: opts, + base_address: :api + ) + end + end + end +end diff --git a/lib/stripe/services/billing_portal/session_service.rb b/lib/stripe/services/billing_portal/session_service.rb new file mode 100644 index 000000000..7bb21bfd5 --- /dev/null +++ b/lib/stripe/services/billing_portal/session_service.rb @@ -0,0 +1,19 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + module BillingPortal + class SessionService < StripeService + # Creates a session of the customer portal. + def create(params = {}, opts = {}) + request( + method: :post, + path: "/v1/billing_portal/sessions", + params: params, + opts: opts, + base_address: :api + ) + end + end + end +end diff --git a/lib/stripe/services/billing_portal_service.rb b/lib/stripe/services/billing_portal_service.rb new file mode 100644 index 000000000..8b1f36da8 --- /dev/null +++ b/lib/stripe/services/billing_portal_service.rb @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + class BillingPortalService < StripeService + attr_reader :configurations, :sessions + + def initialize(requestor) + super(requestor) + @configurations = Stripe::BillingPortal::ConfigurationService.new(@requestor) + @sessions = Stripe::BillingPortal::SessionService.new(@requestor) + end + end +end diff --git a/lib/stripe/services/billing_service.rb b/lib/stripe/services/billing_service.rb new file mode 100644 index 000000000..b29154b6c --- /dev/null +++ b/lib/stripe/services/billing_service.rb @@ -0,0 +1,16 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + class BillingService < StripeService + attr_reader :alerts, :meters, :meter_events, :meter_event_adjustments + + def initialize(requestor) + super(requestor) + @alerts = Stripe::Billing::AlertService.new(@requestor) + @meters = Stripe::Billing::MeterService.new(@requestor) + @meter_events = Stripe::Billing::MeterEventService.new(@requestor) + @meter_event_adjustments = Stripe::Billing::MeterEventAdjustmentService.new(@requestor) + end + end +end diff --git a/lib/stripe/services/capital/financing_offer_service.rb b/lib/stripe/services/capital/financing_offer_service.rb new file mode 100644 index 000000000..ca9aaecfb --- /dev/null +++ b/lib/stripe/services/capital/financing_offer_service.rb @@ -0,0 +1,42 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + module Capital + class FinancingOfferService < StripeService + # Retrieves the financing offers available for Connected accounts that belong to your platform. + def list(params = {}, opts = {}) + request( + method: :get, + path: "/v1/capital/financing_offers", + params: params, + opts: opts, + base_address: :api + ) + end + + # Acknowledges that platform has received and delivered the financing_offer to + # the intended merchant recipient. + def mark_delivered(financing_offer, params = {}, opts = {}) + request( + method: :post, + path: format("/v1/capital/financing_offers/%s/mark_delivered", { financing_offer: CGI.escape(financing_offer) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Get the details of the financing offer + def retrieve(financing_offer, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/capital/financing_offers/%s", { financing_offer: CGI.escape(financing_offer) }), + params: params, + opts: opts, + base_address: :api + ) + end + end + end +end diff --git a/lib/stripe/services/capital/financing_summary_service.rb b/lib/stripe/services/capital/financing_summary_service.rb new file mode 100644 index 000000000..e291c21b9 --- /dev/null +++ b/lib/stripe/services/capital/financing_summary_service.rb @@ -0,0 +1,19 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + module Capital + class FinancingSummaryService < StripeService + # Retrieve the financing state for the account that was authenticated in the request. + def retrieve(params = {}, opts = {}) + request( + method: :get, + path: "/v1/capital/financing_summary", + params: params, + opts: opts, + base_address: :api + ) + end + end + end +end diff --git a/lib/stripe/services/capital/financing_transaction_service.rb b/lib/stripe/services/capital/financing_transaction_service.rb new file mode 100644 index 000000000..466fb2d92 --- /dev/null +++ b/lib/stripe/services/capital/financing_transaction_service.rb @@ -0,0 +1,31 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + module Capital + class FinancingTransactionService < StripeService + # Returns a list of financing transactions. The transactions are returned in sorted order, + # with the most recent transactions appearing first. + def list(params = {}, opts = {}) + request( + method: :get, + path: "/v1/capital/financing_transactions", + params: params, + opts: opts, + base_address: :api + ) + end + + # Retrieves a financing transaction for a financing offer. + def retrieve(financing_transaction, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/capital/financing_transactions/%s", { financing_transaction: CGI.escape(financing_transaction) }), + params: params, + opts: opts, + base_address: :api + ) + end + end + end +end diff --git a/lib/stripe/services/capital_service.rb b/lib/stripe/services/capital_service.rb new file mode 100644 index 000000000..a4b3659ec --- /dev/null +++ b/lib/stripe/services/capital_service.rb @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + class CapitalService < StripeService + attr_reader :financing_offers, :financing_summary, :financing_transactions + + def initialize(requestor) + super(requestor) + @financing_offers = Stripe::Capital::FinancingOfferService.new(@requestor) + @financing_summary = Stripe::Capital::FinancingSummaryService.new(@requestor) + @financing_transactions = Stripe::Capital::FinancingTransactionService.new(@requestor) + end + end +end diff --git a/lib/stripe/services/charge_service.rb b/lib/stripe/services/charge_service.rb new file mode 100644 index 000000000..4a236f210 --- /dev/null +++ b/lib/stripe/services/charge_service.rb @@ -0,0 +1,69 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + class ChargeService < StripeService + # Capture the payment of an existing, uncaptured charge that was created with the capture option set to false. + # + # Uncaptured payments expire a set number of days after they are created ([7 by default](https://stripe.com/docs/charges/placing-a-hold)), after which they are marked as refunded and capture attempts will fail. + # + # Don't use this method to capture a PaymentIntent-initiated charge. Use [Capture a PaymentIntent](https://stripe.com/docs/api/payment_intents/capture). + def capture(charge, params = {}, opts = {}) + request( + method: :post, + path: format("/v1/charges/%s/capture", { charge: CGI.escape(charge) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # This method is no longer recommended—use the [Payment Intents API](https://stripe.com/docs/api/payment_intents) + # to initiate a new payment instead. Confirmation of the PaymentIntent creates the Charge + # object used to request payment. + def create(params = {}, opts = {}) + request(method: :post, path: "/v1/charges", params: params, opts: opts, base_address: :api) + end + + # Returns a list of charges you've previously created. The charges are returned in sorted order, with the most recent charges appearing first. + def list(params = {}, opts = {}) + request(method: :get, path: "/v1/charges", params: params, opts: opts, base_address: :api) + end + + # Retrieves the details of a charge that has previously been created. Supply the unique charge ID that was returned from your previous request, and Stripe will return the corresponding charge information. The same information is returned when creating or refunding the charge. + def retrieve(charge, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/charges/%s", { charge: CGI.escape(charge) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Search for charges you've previously created using Stripe's [Search Query Language](https://stripe.com/docs/search#search-query-language). + # Don't use search in read-after-write flows where strict consistency is necessary. Under normal operating + # conditions, data is searchable in less than a minute. Occasionally, propagation of new or updated data can be up + # to an hour behind during outages. Search functionality is not available to merchants in India. + def search(params = {}, opts = {}) + request( + method: :get, + path: "/v1/charges/search", + params: params, + opts: opts, + base_address: :api + ) + end + + # Updates the specified charge by setting the values of the parameters passed. Any parameters not provided will be left unchanged. + def update(charge, params = {}, opts = {}) + request( + method: :post, + path: format("/v1/charges/%s", { charge: CGI.escape(charge) }), + params: params, + opts: opts, + base_address: :api + ) + end + end +end diff --git a/lib/stripe/services/checkout/session_line_item_service.rb b/lib/stripe/services/checkout/session_line_item_service.rb new file mode 100644 index 000000000..f1a5a56b7 --- /dev/null +++ b/lib/stripe/services/checkout/session_line_item_service.rb @@ -0,0 +1,19 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + module Checkout + class SessionLineItemService < StripeService + # When retrieving a Checkout Session, there is an includable line_items property containing the first handful of those items. There is also a URL where you can retrieve the full (paginated) list of line items. + def list(session, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/checkout/sessions/%s/line_items", { session: CGI.escape(session) }), + params: params, + opts: opts, + base_address: :api + ) + end + end + end +end diff --git a/lib/stripe/services/checkout/session_service.rb b/lib/stripe/services/checkout/session_service.rb new file mode 100644 index 000000000..bfe755025 --- /dev/null +++ b/lib/stripe/services/checkout/session_service.rb @@ -0,0 +1,72 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + module Checkout + class SessionService < StripeService + attr_reader :line_items + + def initialize(requestor) + super(requestor) + @line_items = Stripe::Checkout::SessionLineItemService.new(@requestor) + end + + # Creates a Session object. + def create(params = {}, opts = {}) + request( + method: :post, + path: "/v1/checkout/sessions", + params: params, + opts: opts, + base_address: :api + ) + end + + # A Session can be expired when it is in one of these statuses: open + # + # After it expires, a customer can't complete a Session and customers loading the Session see a message saying the Session is expired. + def expire(session, params = {}, opts = {}) + request( + method: :post, + path: format("/v1/checkout/sessions/%s/expire", { session: CGI.escape(session) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Returns a list of Checkout Sessions. + def list(params = {}, opts = {}) + request( + method: :get, + path: "/v1/checkout/sessions", + params: params, + opts: opts, + base_address: :api + ) + end + + # Retrieves a Session object. + def retrieve(session, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/checkout/sessions/%s", { session: CGI.escape(session) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Updates a Session object. + def update(session, params = {}, opts = {}) + request( + method: :post, + path: format("/v1/checkout/sessions/%s", { session: CGI.escape(session) }), + params: params, + opts: opts, + base_address: :api + ) + end + end + end +end diff --git a/lib/stripe/services/checkout_service.rb b/lib/stripe/services/checkout_service.rb new file mode 100644 index 000000000..6e8379137 --- /dev/null +++ b/lib/stripe/services/checkout_service.rb @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + class CheckoutService < StripeService + attr_reader :sessions + + def initialize(requestor) + super(requestor) + @sessions = Stripe::Checkout::SessionService.new(@requestor) + end + end +end diff --git a/lib/stripe/services/climate/order_service.rb b/lib/stripe/services/climate/order_service.rb new file mode 100644 index 000000000..ee37d3a8b --- /dev/null +++ b/lib/stripe/services/climate/order_service.rb @@ -0,0 +1,68 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + module Climate + class OrderService < StripeService + # Cancels a Climate order. You can cancel an order within 24 hours of creation. Stripe refunds the + # reservation amount_subtotal, but not the amount_fees for user-triggered cancellations. Frontier + # might cancel reservations if suppliers fail to deliver. If Frontier cancels the reservation, Stripe + # provides 90 days advance notice and refunds the amount_total. + def cancel(order, params = {}, opts = {}) + request( + method: :post, + path: format("/v1/climate/orders/%s/cancel", { order: CGI.escape(order) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Creates a Climate order object for a given Climate product. The order will be processed immediately + # after creation and payment will be deducted your Stripe balance. + def create(params = {}, opts = {}) + request( + method: :post, + path: "/v1/climate/orders", + params: params, + opts: opts, + base_address: :api + ) + end + + # Lists all Climate order objects. The orders are returned sorted by creation date, with the + # most recently created orders appearing first. + def list(params = {}, opts = {}) + request( + method: :get, + path: "/v1/climate/orders", + params: params, + opts: opts, + base_address: :api + ) + end + + # Retrieves the details of a Climate order object with the given ID. + def retrieve(order, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/climate/orders/%s", { order: CGI.escape(order) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Updates the specified order by setting the values of the parameters passed. + def update(order, params = {}, opts = {}) + request( + method: :post, + path: format("/v1/climate/orders/%s", { order: CGI.escape(order) }), + params: params, + opts: opts, + base_address: :api + ) + end + end + end +end diff --git a/lib/stripe/services/climate/product_service.rb b/lib/stripe/services/climate/product_service.rb new file mode 100644 index 000000000..0b01b8533 --- /dev/null +++ b/lib/stripe/services/climate/product_service.rb @@ -0,0 +1,30 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + module Climate + class ProductService < StripeService + # Lists all available Climate product objects. + def list(params = {}, opts = {}) + request( + method: :get, + path: "/v1/climate/products", + params: params, + opts: opts, + base_address: :api + ) + end + + # Retrieves the details of a Climate product with the given ID. + def retrieve(product, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/climate/products/%s", { product: CGI.escape(product) }), + params: params, + opts: opts, + base_address: :api + ) + end + end + end +end diff --git a/lib/stripe/services/climate/supplier_service.rb b/lib/stripe/services/climate/supplier_service.rb new file mode 100644 index 000000000..362caf107 --- /dev/null +++ b/lib/stripe/services/climate/supplier_service.rb @@ -0,0 +1,30 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + module Climate + class SupplierService < StripeService + # Lists all available Climate supplier objects. + def list(params = {}, opts = {}) + request( + method: :get, + path: "/v1/climate/suppliers", + params: params, + opts: opts, + base_address: :api + ) + end + + # Retrieves a Climate supplier object. + def retrieve(supplier, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/climate/suppliers/%s", { supplier: CGI.escape(supplier) }), + params: params, + opts: opts, + base_address: :api + ) + end + end + end +end diff --git a/lib/stripe/services/climate_service.rb b/lib/stripe/services/climate_service.rb new file mode 100644 index 000000000..7404a2e8d --- /dev/null +++ b/lib/stripe/services/climate_service.rb @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + class ClimateService < StripeService + attr_reader :orders, :products, :suppliers + + def initialize(requestor) + super(requestor) + @orders = Stripe::Climate::OrderService.new(@requestor) + @products = Stripe::Climate::ProductService.new(@requestor) + @suppliers = Stripe::Climate::SupplierService.new(@requestor) + end + end +end diff --git a/lib/stripe/services/confirmation_token_service.rb b/lib/stripe/services/confirmation_token_service.rb new file mode 100644 index 000000000..18f8e56eb --- /dev/null +++ b/lib/stripe/services/confirmation_token_service.rb @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + class ConfirmationTokenService < StripeService + # Retrieves an existing ConfirmationToken object + def retrieve(confirmation_token, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/confirmation_tokens/%s", { confirmation_token: CGI.escape(confirmation_token) }), + params: params, + opts: opts, + base_address: :api + ) + end + end +end diff --git a/lib/stripe/services/country_spec_service.rb b/lib/stripe/services/country_spec_service.rb new file mode 100644 index 000000000..39ff260c5 --- /dev/null +++ b/lib/stripe/services/country_spec_service.rb @@ -0,0 +1,28 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + class CountrySpecService < StripeService + # Lists all Country Spec objects available in the API. + def list(params = {}, opts = {}) + request( + method: :get, + path: "/v1/country_specs", + params: params, + opts: opts, + base_address: :api + ) + end + + # Returns a Country Spec for a given Country code. + def retrieve(country, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/country_specs/%s", { country: CGI.escape(country) }), + params: params, + opts: opts, + base_address: :api + ) + end + end +end diff --git a/lib/stripe/services/coupon_service.rb b/lib/stripe/services/coupon_service.rb new file mode 100644 index 000000000..23544cbd5 --- /dev/null +++ b/lib/stripe/services/coupon_service.rb @@ -0,0 +1,51 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + class CouponService < StripeService + # You can create coupons easily via the [coupon management](https://dashboard.stripe.com/coupons) page of the Stripe dashboard. Coupon creation is also accessible via the API if you need to create coupons on the fly. + # + # A coupon has either a percent_off or an amount_off and currency. If you set an amount_off, that amount will be subtracted from any invoice's subtotal. For example, an invoice with a subtotal of 100 will have a final total of 0 if a coupon with an amount_off of 200 is applied to it and an invoice with a subtotal of 300 will have a final total of 100 if a coupon with an amount_off of 200 is applied to it. + def create(params = {}, opts = {}) + request(method: :post, path: "/v1/coupons", params: params, opts: opts, base_address: :api) + end + + # You can delete coupons via the [coupon management](https://dashboard.stripe.com/coupons) page of the Stripe dashboard. However, deleting a coupon does not affect any customers who have already applied the coupon; it means that new customers can't redeem the coupon. You can also delete coupons via the API. + def delete(coupon, params = {}, opts = {}) + request( + method: :delete, + path: format("/v1/coupons/%s", { coupon: CGI.escape(coupon) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Returns a list of your coupons. + def list(params = {}, opts = {}) + request(method: :get, path: "/v1/coupons", params: params, opts: opts, base_address: :api) + end + + # Retrieves the coupon with the given ID. + def retrieve(coupon, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/coupons/%s", { coupon: CGI.escape(coupon) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Updates the metadata of a coupon. Other coupon details (currency, duration, amount_off) are, by design, not editable. + def update(coupon, params = {}, opts = {}) + request( + method: :post, + path: format("/v1/coupons/%s", { coupon: CGI.escape(coupon) }), + params: params, + opts: opts, + base_address: :api + ) + end + end +end diff --git a/lib/stripe/services/credit_note_line_item_service.rb b/lib/stripe/services/credit_note_line_item_service.rb new file mode 100644 index 000000000..1424621fc --- /dev/null +++ b/lib/stripe/services/credit_note_line_item_service.rb @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + class CreditNoteLineItemService < StripeService + # When retrieving a credit note, you'll get a lines property containing the first handful of those items. There is also a URL where you can retrieve the full (paginated) list of line items. + def list(credit_note, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/credit_notes/%s/lines", { credit_note: CGI.escape(credit_note) }), + params: params, + opts: opts, + base_address: :api + ) + end + end +end diff --git a/lib/stripe/services/credit_note_preview_lines_service.rb b/lib/stripe/services/credit_note_preview_lines_service.rb new file mode 100644 index 000000000..392dd619a --- /dev/null +++ b/lib/stripe/services/credit_note_preview_lines_service.rb @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + class CreditNotePreviewLinesService < StripeService + # When retrieving a credit note preview, you'll get a lines property containing the first handful of those items. This URL you can retrieve the full (paginated) list of line items. + def list(params = {}, opts = {}) + request( + method: :get, + path: "/v1/credit_notes/preview/lines", + params: params, + opts: opts, + base_address: :api + ) + end + end +end diff --git a/lib/stripe/services/credit_note_service.rb b/lib/stripe/services/credit_note_service.rb new file mode 100644 index 000000000..f0a612814 --- /dev/null +++ b/lib/stripe/services/credit_note_service.rb @@ -0,0 +1,93 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + class CreditNoteService < StripeService + attr_reader :line_items, :preview_lines + + def initialize(requestor) + super(requestor) + @line_items = Stripe::CreditNoteLineItemService.new(@requestor) + @preview_lines = Stripe::CreditNotePreviewLinesService.new(@requestor) + end + + # Issue a credit note to adjust the amount of a finalized invoice. For a status=open invoice, a credit note reduces + # its amount_due. For a status=paid invoice, a credit note does not affect its amount_due. Instead, it can result + # in any combination of the following: + # + # + # Refund: create a new refund (using refund_amount) or link an existing refund (using refund). + # Customer balance credit: credit the customer's balance (using credit_amount) which will be automatically applied to their next invoice when it's finalized. + # Outside of Stripe credit: record the amount that is or will be credited outside of Stripe (using out_of_band_amount). + # + # + # For post-payment credit notes the sum of the refund, credit and outside of Stripe amounts must equal the credit note total. + # + # You may issue multiple credit notes for an invoice. Each credit note will increment the invoice's pre_payment_credit_notes_amount + # or post_payment_credit_notes_amount depending on its status at the time of credit note creation. + def create(params = {}, opts = {}) + request( + method: :post, + path: "/v1/credit_notes", + params: params, + opts: opts, + base_address: :api + ) + end + + # Returns a list of credit notes. + def list(params = {}, opts = {}) + request( + method: :get, + path: "/v1/credit_notes", + params: params, + opts: opts, + base_address: :api + ) + end + + # Get a preview of a credit note without creating it. + def preview(params = {}, opts = {}) + request( + method: :get, + path: "/v1/credit_notes/preview", + params: params, + opts: opts, + base_address: :api + ) + end + + # Retrieves the credit note object with the given identifier. + def retrieve(id, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/credit_notes/%s", { id: CGI.escape(id) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Updates an existing credit note. + def update(id, params = {}, opts = {}) + request( + method: :post, + path: format("/v1/credit_notes/%s", { id: CGI.escape(id) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Marks a credit note as void. Learn more about [voiding credit notes](https://stripe.com/docs/billing/invoices/credit-notes#voiding). + def void_credit_note(id, params = {}, opts = {}) + request( + method: :post, + path: format("/v1/credit_notes/%s/void", { id: CGI.escape(id) }), + params: params, + opts: opts, + base_address: :api + ) + end + end +end diff --git a/lib/stripe/services/customer_balance_transaction_service.rb b/lib/stripe/services/customer_balance_transaction_service.rb new file mode 100644 index 000000000..ad76a3fde --- /dev/null +++ b/lib/stripe/services/customer_balance_transaction_service.rb @@ -0,0 +1,50 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + class CustomerBalanceTransactionService < StripeService + # Creates an immutable transaction that updates the customer's credit [balance](https://stripe.com/docs/billing/customer/balance). + def create(customer, params = {}, opts = {}) + request( + method: :post, + path: format("/v1/customers/%s/balance_transactions", { customer: CGI.escape(customer) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Returns a list of transactions that updated the customer's [balances](https://stripe.com/docs/billing/customer/balance). + def list(customer, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/customers/%s/balance_transactions", { customer: CGI.escape(customer) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Retrieves a specific customer balance transaction that updated the customer's [balances](https://stripe.com/docs/billing/customer/balance). + def retrieve(customer, transaction, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/customers/%s/balance_transactions/%s", { customer: CGI.escape(customer), transaction: CGI.escape(transaction) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Most credit balance transaction fields are immutable, but you may update its description and metadata. + def update(customer, transaction, params = {}, opts = {}) + request( + method: :post, + path: format("/v1/customers/%s/balance_transactions/%s", { customer: CGI.escape(customer), transaction: CGI.escape(transaction) }), + params: params, + opts: opts, + base_address: :api + ) + end + end +end diff --git a/lib/stripe/services/customer_cash_balance_service.rb b/lib/stripe/services/customer_cash_balance_service.rb new file mode 100644 index 000000000..ad87c9b76 --- /dev/null +++ b/lib/stripe/services/customer_cash_balance_service.rb @@ -0,0 +1,28 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + class CustomerCashBalanceService < StripeService + # Retrieves a customer's cash balance. + def retrieve(customer, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/customers/%s/cash_balance", { customer: CGI.escape(customer) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Changes the settings on a customer's cash balance. + def update(customer, params = {}, opts = {}) + request( + method: :post, + path: format("/v1/customers/%s/cash_balance", { customer: CGI.escape(customer) }), + params: params, + opts: opts, + base_address: :api + ) + end + end +end diff --git a/lib/stripe/services/customer_cash_balance_transaction_service.rb b/lib/stripe/services/customer_cash_balance_transaction_service.rb new file mode 100644 index 000000000..e1d3be5bb --- /dev/null +++ b/lib/stripe/services/customer_cash_balance_transaction_service.rb @@ -0,0 +1,28 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + class CustomerCashBalanceTransactionService < StripeService + # Returns a list of transactions that modified the customer's [cash balance](https://stripe.com/docs/payments/customer-balance). + def list(customer, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/customers/%s/cash_balance_transactions", { customer: CGI.escape(customer) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Retrieves a specific cash balance transaction, which updated the customer's [cash balance](https://stripe.com/docs/payments/customer-balance). + def retrieve(customer, transaction, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/customers/%s/cash_balance_transactions/%s", { customer: CGI.escape(customer), transaction: CGI.escape(transaction) }), + params: params, + opts: opts, + base_address: :api + ) + end + end +end diff --git a/lib/stripe/services/customer_funding_instructions_service.rb b/lib/stripe/services/customer_funding_instructions_service.rb new file mode 100644 index 000000000..310b92503 --- /dev/null +++ b/lib/stripe/services/customer_funding_instructions_service.rb @@ -0,0 +1,19 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + class CustomerFundingInstructionsService < StripeService + # Retrieve funding instructions for a customer cash balance. If funding instructions do not yet exist for the customer, new + # funding instructions will be created. If funding instructions have already been created for a given customer, the same + # funding instructions will be retrieved. In other words, we will return the same funding instructions each time. + def create(customer, params = {}, opts = {}) + request( + method: :post, + path: format("/v1/customers/%s/funding_instructions", { customer: CGI.escape(customer) }), + params: params, + opts: opts, + base_address: :api + ) + end + end +end diff --git a/lib/stripe/services/customer_payment_method_service.rb b/lib/stripe/services/customer_payment_method_service.rb new file mode 100644 index 000000000..344d0a029 --- /dev/null +++ b/lib/stripe/services/customer_payment_method_service.rb @@ -0,0 +1,28 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + class CustomerPaymentMethodService < StripeService + # Returns a list of PaymentMethods for a given Customer + def list(customer, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/customers/%s/payment_methods", { customer: CGI.escape(customer) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Retrieves a PaymentMethod object for a given Customer. + def retrieve(customer, payment_method, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/customers/%s/payment_methods/%s", { customer: CGI.escape(customer), payment_method: CGI.escape(payment_method) }), + params: params, + opts: opts, + base_address: :api + ) + end + end +end diff --git a/lib/stripe/services/customer_payment_source_service.rb b/lib/stripe/services/customer_payment_source_service.rb new file mode 100644 index 000000000..a623acc20 --- /dev/null +++ b/lib/stripe/services/customer_payment_source_service.rb @@ -0,0 +1,76 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + class CustomerPaymentSourceService < StripeService + # When you create a new credit card, you must specify a customer or recipient on which to create it. + # + # If the card's owner has no default card, then the new card will become the default. + # However, if the owner already has a default, then it will not change. + # To change the default, you should [update the customer](https://stripe.com/docs/api#update_customer) to have a new default_source. + def create(customer, params = {}, opts = {}) + request( + method: :post, + path: format("/v1/customers/%s/sources", { customer: CGI.escape(customer) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Delete a specified source for a given customer. + def delete(customer, id, params = {}, opts = {}) + request( + method: :delete, + path: format("/v1/customers/%s/sources/%s", { customer: CGI.escape(customer), id: CGI.escape(id) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # List sources for a specified customer. + def list(customer, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/customers/%s/sources", { customer: CGI.escape(customer) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Retrieve a specified source for a given customer. + def retrieve(customer, id, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/customers/%s/sources/%s", { customer: CGI.escape(customer), id: CGI.escape(id) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Update a specified source for a given customer. + def update(customer, id, params = {}, opts = {}) + request( + method: :post, + path: format("/v1/customers/%s/sources/%s", { customer: CGI.escape(customer), id: CGI.escape(id) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Verify a specified bank account for a given customer. + def verify(customer, id, params = {}, opts = {}) + request( + method: :post, + path: format("/v1/customers/%s/sources/%s/verify", { customer: CGI.escape(customer), id: CGI.escape(id) }), + params: params, + opts: opts, + base_address: :api + ) + end + end +end diff --git a/lib/stripe/services/customer_service.rb b/lib/stripe/services/customer_service.rb new file mode 100644 index 000000000..a9dbf1aa8 --- /dev/null +++ b/lib/stripe/services/customer_service.rb @@ -0,0 +1,89 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + class CustomerService < StripeService + attr_reader :cash_balance, :balance_transactions, :cash_balance_transactions, :payment_sources, :tax_ids, :payment_methods, :funding_instructions + + def initialize(requestor) + super(requestor) + @cash_balance = Stripe::CustomerCashBalanceService.new(@requestor) + @balance_transactions = Stripe::CustomerBalanceTransactionService.new(@requestor) + @cash_balance_transactions = Stripe::CustomerCashBalanceTransactionService.new(@requestor) + @payment_sources = Stripe::CustomerPaymentSourceService.new(@requestor) + @tax_ids = Stripe::CustomerTaxIdService.new(@requestor) + @payment_methods = Stripe::CustomerPaymentMethodService.new(@requestor) + @funding_instructions = Stripe::CustomerFundingInstructionsService.new(@requestor) + end + + # Creates a new customer object. + def create(params = {}, opts = {}) + request(method: :post, path: "/v1/customers", params: params, opts: opts, base_address: :api) + end + + # Permanently deletes a customer. It cannot be undone. Also immediately cancels any active subscriptions on the customer. + def delete(customer, params = {}, opts = {}) + request( + method: :delete, + path: format("/v1/customers/%s", { customer: CGI.escape(customer) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Removes the currently applied discount on a customer. + def delete_discount(customer, params = {}, opts = {}) + request( + method: :delete, + path: format("/v1/customers/%s/discount", { customer: CGI.escape(customer) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Returns a list of your customers. The customers are returned sorted by creation date, with the most recent customers appearing first. + def list(params = {}, opts = {}) + request(method: :get, path: "/v1/customers", params: params, opts: opts, base_address: :api) + end + + # Retrieves a Customer object. + def retrieve(customer, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/customers/%s", { customer: CGI.escape(customer) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Search for customers you've previously created using Stripe's [Search Query Language](https://stripe.com/docs/search#search-query-language). + # Don't use search in read-after-write flows where strict consistency is necessary. Under normal operating + # conditions, data is searchable in less than a minute. Occasionally, propagation of new or updated data can be up + # to an hour behind during outages. Search functionality is not available to merchants in India. + def search(params = {}, opts = {}) + request( + method: :get, + path: "/v1/customers/search", + params: params, + opts: opts, + base_address: :api + ) + end + + # Updates the specified customer by setting the values of the parameters passed. Any parameters not provided will be left unchanged. For example, if you pass the source parameter, that becomes the customer's active source (e.g., a card) to be used for all charges in the future. When you update a customer to a new valid card source by passing the source parameter: for each of the customer's current subscriptions, if the subscription bills automatically and is in the past_due state, then the latest open invoice for the subscription with automatic collection enabled will be retried. This retry will not count as an automatic retry, and will not affect the next regularly scheduled payment for the invoice. Changing the default_source for a customer will not trigger this behavior. + # + # This request accepts mostly the same arguments as the customer creation call. + def update(customer, params = {}, opts = {}) + request( + method: :post, + path: format("/v1/customers/%s", { customer: CGI.escape(customer) }), + params: params, + opts: opts, + base_address: :api + ) + end + end +end diff --git a/lib/stripe/services/customer_session_service.rb b/lib/stripe/services/customer_session_service.rb new file mode 100644 index 000000000..064c4c927 --- /dev/null +++ b/lib/stripe/services/customer_session_service.rb @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + class CustomerSessionService < StripeService + # Creates a Customer Session object that includes a single-use client secret that you can use on your front-end to grant client-side API access for certain customer resources. + def create(params = {}, opts = {}) + request( + method: :post, + path: "/v1/customer_sessions", + params: params, + opts: opts, + base_address: :api + ) + end + end +end diff --git a/lib/stripe/services/customer_tax_id_service.rb b/lib/stripe/services/customer_tax_id_service.rb new file mode 100644 index 000000000..7ba64d8d1 --- /dev/null +++ b/lib/stripe/services/customer_tax_id_service.rb @@ -0,0 +1,50 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + class CustomerTaxIdService < StripeService + # Creates a new tax_id object for a customer. + def create(customer, params = {}, opts = {}) + request( + method: :post, + path: format("/v1/customers/%s/tax_ids", { customer: CGI.escape(customer) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Deletes an existing tax_id object. + def delete(customer, id, params = {}, opts = {}) + request( + method: :delete, + path: format("/v1/customers/%s/tax_ids/%s", { customer: CGI.escape(customer), id: CGI.escape(id) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Returns a list of tax IDs for a customer. + def list(customer, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/customers/%s/tax_ids", { customer: CGI.escape(customer) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Retrieves the tax_id object with the given identifier. + def retrieve(customer, id, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/customers/%s/tax_ids/%s", { customer: CGI.escape(customer), id: CGI.escape(id) }), + params: params, + opts: opts, + base_address: :api + ) + end + end +end diff --git a/lib/stripe/services/dispute_service.rb b/lib/stripe/services/dispute_service.rb new file mode 100644 index 000000000..b9748bbf2 --- /dev/null +++ b/lib/stripe/services/dispute_service.rb @@ -0,0 +1,48 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + class DisputeService < StripeService + # Closing the dispute for a charge indicates that you do not have any evidence to submit and are essentially dismissing the dispute, acknowledging it as lost. + # + # The status of the dispute will change from needs_response to lost. Closing a dispute is irreversible. + def close(dispute, params = {}, opts = {}) + request( + method: :post, + path: format("/v1/disputes/%s/close", { dispute: CGI.escape(dispute) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Returns a list of your disputes. + def list(params = {}, opts = {}) + request(method: :get, path: "/v1/disputes", params: params, opts: opts, base_address: :api) + end + + # Retrieves the dispute with the given ID. + def retrieve(dispute, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/disputes/%s", { dispute: CGI.escape(dispute) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # When you get a dispute, contacting your customer is always the best first step. If that doesn't work, you can submit evidence to help us resolve the dispute in your favor. You can do this in your [dashboard](https://dashboard.stripe.com/disputes), but if you prefer, you can use the API to submit evidence programmatically. + # + # Depending on your dispute type, different evidence fields will give you a better chance of winning your dispute. To figure out which evidence fields to provide, see our [guide to dispute types](https://stripe.com/docs/disputes/categories). + def update(dispute, params = {}, opts = {}) + request( + method: :post, + path: format("/v1/disputes/%s", { dispute: CGI.escape(dispute) }), + params: params, + opts: opts, + base_address: :api + ) + end + end +end diff --git a/lib/stripe/services/entitlements/active_entitlement_service.rb b/lib/stripe/services/entitlements/active_entitlement_service.rb new file mode 100644 index 000000000..2481067eb --- /dev/null +++ b/lib/stripe/services/entitlements/active_entitlement_service.rb @@ -0,0 +1,30 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + module Entitlements + class ActiveEntitlementService < StripeService + # Retrieve a list of active entitlements for a customer + def list(params = {}, opts = {}) + request( + method: :get, + path: "/v1/entitlements/active_entitlements", + params: params, + opts: opts, + base_address: :api + ) + end + + # Retrieve an active entitlement + def retrieve(id, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/entitlements/active_entitlements/%s", { id: CGI.escape(id) }), + params: params, + opts: opts, + base_address: :api + ) + end + end + end +end diff --git a/lib/stripe/services/entitlements/feature_service.rb b/lib/stripe/services/entitlements/feature_service.rb new file mode 100644 index 000000000..05b0daed3 --- /dev/null +++ b/lib/stripe/services/entitlements/feature_service.rb @@ -0,0 +1,52 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + module Entitlements + class FeatureService < StripeService + # Creates a feature + def create(params = {}, opts = {}) + request( + method: :post, + path: "/v1/entitlements/features", + params: params, + opts: opts, + base_address: :api + ) + end + + # Retrieve a list of features + def list(params = {}, opts = {}) + request( + method: :get, + path: "/v1/entitlements/features", + params: params, + opts: opts, + base_address: :api + ) + end + + # Retrieves a feature + def retrieve(id, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/entitlements/features/%s", { id: CGI.escape(id) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Update a feature's metadata or permanently deactivate it. + def update(id, params = {}, opts = {}) + request( + method: :post, + path: format("/v1/entitlements/features/%s", { id: CGI.escape(id) }), + params: params, + opts: opts, + base_address: :api + ) + end + end + end +end diff --git a/lib/stripe/services/entitlements_service.rb b/lib/stripe/services/entitlements_service.rb new file mode 100644 index 000000000..05730c313 --- /dev/null +++ b/lib/stripe/services/entitlements_service.rb @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + class EntitlementsService < StripeService + attr_reader :active_entitlements, :features + + def initialize(requestor) + super(requestor) + @active_entitlements = Stripe::Entitlements::ActiveEntitlementService.new(@requestor) + @features = Stripe::Entitlements::FeatureService.new(@requestor) + end + end +end diff --git a/lib/stripe/services/ephemeral_key_service.rb b/lib/stripe/services/ephemeral_key_service.rb new file mode 100644 index 000000000..b58c18cf0 --- /dev/null +++ b/lib/stripe/services/ephemeral_key_service.rb @@ -0,0 +1,28 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + class EphemeralKeyService < StripeService + # Creates a short-lived API key for a given resource. + def create(params = {}, opts = {}) + request( + method: :post, + path: "/v1/ephemeral_keys", + params: params, + opts: opts, + base_address: :api + ) + end + + # Invalidates a short-lived API key for a given resource. + def delete(key, params = {}, opts = {}) + request( + method: :delete, + path: format("/v1/ephemeral_keys/%s", { key: CGI.escape(key) }), + params: params, + opts: opts, + base_address: :api + ) + end + end +end diff --git a/lib/stripe/services/event_service.rb b/lib/stripe/services/event_service.rb new file mode 100644 index 000000000..a6a75eb85 --- /dev/null +++ b/lib/stripe/services/event_service.rb @@ -0,0 +1,22 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + class EventService < StripeService + # List events, going back up to 30 days. Each event data is rendered according to Stripe API version at its creation time, specified in [event object](https://docs.stripe.com/api/events/object) api_version attribute (not according to your current Stripe API version or Stripe-Version header). + def list(params = {}, opts = {}) + request(method: :get, path: "/v1/events", params: params, opts: opts, base_address: :api) + end + + # Retrieves the details of an event if it was created in the last 30 days. Supply the unique identifier of the event, which you might have received in a webhook. + def retrieve(id, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/events/%s", { id: CGI.escape(id) }), + params: params, + opts: opts, + base_address: :api + ) + end + end +end diff --git a/lib/stripe/services/exchange_rate_service.rb b/lib/stripe/services/exchange_rate_service.rb new file mode 100644 index 000000000..86205a382 --- /dev/null +++ b/lib/stripe/services/exchange_rate_service.rb @@ -0,0 +1,28 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + class ExchangeRateService < StripeService + # Returns a list of objects that contain the rates at which foreign currencies are converted to one another. Only shows the currencies for which Stripe supports. + def list(params = {}, opts = {}) + request( + method: :get, + path: "/v1/exchange_rates", + params: params, + opts: opts, + base_address: :api + ) + end + + # Retrieves the exchange rates from the given currency to every supported currency. + def retrieve(rate_id, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/exchange_rates/%s", { rate_id: CGI.escape(rate_id) }), + params: params, + opts: opts, + base_address: :api + ) + end + end +end diff --git a/lib/stripe/services/file_link_service.rb b/lib/stripe/services/file_link_service.rb new file mode 100644 index 000000000..d06ca20be --- /dev/null +++ b/lib/stripe/services/file_link_service.rb @@ -0,0 +1,38 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + class FileLinkService < StripeService + # Creates a new file link object. + def create(params = {}, opts = {}) + request(method: :post, path: "/v1/file_links", params: params, opts: opts, base_address: :api) + end + + # Returns a list of file links. + def list(params = {}, opts = {}) + request(method: :get, path: "/v1/file_links", params: params, opts: opts, base_address: :api) + end + + # Retrieves the file link with the given ID. + def retrieve(link, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/file_links/%s", { link: CGI.escape(link) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Updates an existing file link object. Expired links can no longer be updated. + def update(link, params = {}, opts = {}) + request( + method: :post, + path: format("/v1/file_links/%s", { link: CGI.escape(link) }), + params: params, + opts: opts, + base_address: :api + ) + end + end +end diff --git a/lib/stripe/services/file_service.rb b/lib/stripe/services/file_service.rb new file mode 100644 index 000000000..7afc74229 --- /dev/null +++ b/lib/stripe/services/file_service.rb @@ -0,0 +1,35 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + class FileService < StripeService + # To upload a file to Stripe, you need to send a request of type multipart/form-data. Include the file you want to upload in the request, and the parameters for creating a file. + # + # All of Stripe's officially supported Client libraries support sending multipart/form-data. + def create(params = {}, opts = {}) + if params[:file] && !params[:file].is_a?(String) && !params[:file].respond_to?(:read) + raise ArgumentError, "file must respond to `#read`" + end + + opts = { content_type: MultipartEncoder::MULTIPART_FORM_DATA }.merge(Util.normalize_opts(opts)) + + request(method: :post, path: "/v1/files", params: params, opts: opts, base_address: :files) + end + + # Returns a list of the files that your account has access to. Stripe sorts and returns the files by their creation dates, placing the most recently created files at the top. + def list(params = {}, opts = {}) + request(method: :get, path: "/v1/files", params: params, opts: opts, base_address: :api) + end + + # Retrieves the details of an existing file object. After you supply a unique file ID, Stripe returns the corresponding file object. Learn how to [access file contents](https://stripe.com/docs/file-upload#download-file-contents). + def retrieve(file, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/files/%s", { file: CGI.escape(file) }), + params: params, + opts: opts, + base_address: :api + ) + end + end +end diff --git a/lib/stripe/services/financial_connections/account_inferred_balance_service.rb b/lib/stripe/services/financial_connections/account_inferred_balance_service.rb new file mode 100644 index 000000000..989639a8a --- /dev/null +++ b/lib/stripe/services/financial_connections/account_inferred_balance_service.rb @@ -0,0 +1,19 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + module FinancialConnections + class AccountInferredBalanceService < StripeService + # Lists the recorded inferred balances for a Financial Connections Account. + def list(account, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/financial_connections/accounts/%s/inferred_balances", { account: CGI.escape(account) }), + params: params, + opts: opts, + base_address: :api + ) + end + end + end +end diff --git a/lib/stripe/services/financial_connections/account_owner_service.rb b/lib/stripe/services/financial_connections/account_owner_service.rb new file mode 100644 index 000000000..5d9de8616 --- /dev/null +++ b/lib/stripe/services/financial_connections/account_owner_service.rb @@ -0,0 +1,19 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + module FinancialConnections + class AccountOwnerService < StripeService + # Lists all owners for a given Account + def list(account, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/financial_connections/accounts/%s/owners", { account: CGI.escape(account) }), + params: params, + opts: opts, + base_address: :api + ) + end + end + end +end diff --git a/lib/stripe/services/financial_connections/account_service.rb b/lib/stripe/services/financial_connections/account_service.rb new file mode 100644 index 000000000..e6a686868 --- /dev/null +++ b/lib/stripe/services/financial_connections/account_service.rb @@ -0,0 +1,83 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + module FinancialConnections + class AccountService < StripeService + attr_reader :inferred_balances, :owners + + def initialize(requestor) + super(requestor) + @inferred_balances = Stripe::FinancialConnections::AccountInferredBalanceService + .new(@requestor) + @owners = Stripe::FinancialConnections::AccountOwnerService.new(@requestor) + end + + # Disables your access to a Financial Connections Account. You will no longer be able to access data associated with the account (e.g. balances, transactions). + def disconnect(account, params = {}, opts = {}) + request( + method: :post, + path: format("/v1/financial_connections/accounts/%s/disconnect", { account: CGI.escape(account) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Returns a list of Financial Connections Account objects. + def list(params = {}, opts = {}) + request( + method: :get, + path: "/v1/financial_connections/accounts", + params: params, + opts: opts, + base_address: :api + ) + end + + # Refreshes the data associated with a Financial Connections Account. + def refresh(account, params = {}, opts = {}) + request( + method: :post, + path: format("/v1/financial_connections/accounts/%s/refresh", { account: CGI.escape(account) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Retrieves the details of an Financial Connections Account. + def retrieve(account, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/financial_connections/accounts/%s", { account: CGI.escape(account) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Subscribes to periodic refreshes of data associated with a Financial Connections Account. + def subscribe(account, params = {}, opts = {}) + request( + method: :post, + path: format("/v1/financial_connections/accounts/%s/subscribe", { account: CGI.escape(account) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Unsubscribes from periodic refreshes of data associated with a Financial Connections Account. + def unsubscribe(account, params = {}, opts = {}) + request( + method: :post, + path: format("/v1/financial_connections/accounts/%s/unsubscribe", { account: CGI.escape(account) }), + params: params, + opts: opts, + base_address: :api + ) + end + end + end +end diff --git a/lib/stripe/services/financial_connections/institution_service.rb b/lib/stripe/services/financial_connections/institution_service.rb new file mode 100644 index 000000000..03f6e85bc --- /dev/null +++ b/lib/stripe/services/financial_connections/institution_service.rb @@ -0,0 +1,30 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + module FinancialConnections + class InstitutionService < StripeService + # Returns a list of Financial Connections Institution objects. + def list(params = {}, opts = {}) + request( + method: :get, + path: "/v1/financial_connections/institutions", + params: params, + opts: opts, + base_address: :api + ) + end + + # Retrieves the details of a Financial Connections Institution. + def retrieve(institution, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/financial_connections/institutions/%s", { institution: CGI.escape(institution) }), + params: params, + opts: opts, + base_address: :api + ) + end + end + end +end diff --git a/lib/stripe/services/financial_connections/session_service.rb b/lib/stripe/services/financial_connections/session_service.rb new file mode 100644 index 000000000..89726e8c9 --- /dev/null +++ b/lib/stripe/services/financial_connections/session_service.rb @@ -0,0 +1,30 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + module FinancialConnections + class SessionService < StripeService + # To launch the Financial Connections authorization flow, create a Session. The session's client_secret can be used to launch the flow using Stripe.js. + def create(params = {}, opts = {}) + request( + method: :post, + path: "/v1/financial_connections/sessions", + params: params, + opts: opts, + base_address: :api + ) + end + + # Retrieves the details of a Financial Connections Session + def retrieve(session, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/financial_connections/sessions/%s", { session: CGI.escape(session) }), + params: params, + opts: opts, + base_address: :api + ) + end + end + end +end diff --git a/lib/stripe/services/financial_connections/transaction_service.rb b/lib/stripe/services/financial_connections/transaction_service.rb new file mode 100644 index 000000000..b28cc90e6 --- /dev/null +++ b/lib/stripe/services/financial_connections/transaction_service.rb @@ -0,0 +1,30 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + module FinancialConnections + class TransactionService < StripeService + # Returns a list of Financial Connections Transaction objects. + def list(params = {}, opts = {}) + request( + method: :get, + path: "/v1/financial_connections/transactions", + params: params, + opts: opts, + base_address: :api + ) + end + + # Retrieves the details of a Financial Connections Transaction + def retrieve(transaction, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/financial_connections/transactions/%s", { transaction: CGI.escape(transaction) }), + params: params, + opts: opts, + base_address: :api + ) + end + end + end +end diff --git a/lib/stripe/services/financial_connections_service.rb b/lib/stripe/services/financial_connections_service.rb new file mode 100644 index 000000000..c448caaab --- /dev/null +++ b/lib/stripe/services/financial_connections_service.rb @@ -0,0 +1,16 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + class FinancialConnectionsService < StripeService + attr_reader :accounts, :institutions, :sessions, :transactions + + def initialize(requestor) + super(requestor) + @accounts = Stripe::FinancialConnections::AccountService.new(@requestor) + @institutions = Stripe::FinancialConnections::InstitutionService.new(@requestor) + @sessions = Stripe::FinancialConnections::SessionService.new(@requestor) + @transactions = Stripe::FinancialConnections::TransactionService.new(@requestor) + end + end +end diff --git a/lib/stripe/services/forwarding/request_service.rb b/lib/stripe/services/forwarding/request_service.rb new file mode 100644 index 000000000..c9b3ec239 --- /dev/null +++ b/lib/stripe/services/forwarding/request_service.rb @@ -0,0 +1,41 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + module Forwarding + class RequestService < StripeService + # Creates a ForwardingRequest object. + def create(params = {}, opts = {}) + request( + method: :post, + path: "/v1/forwarding/requests", + params: params, + opts: opts, + base_address: :api + ) + end + + # Lists all ForwardingRequest objects. + def list(params = {}, opts = {}) + request( + method: :get, + path: "/v1/forwarding/requests", + params: params, + opts: opts, + base_address: :api + ) + end + + # Retrieves a ForwardingRequest object. + def retrieve(id, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/forwarding/requests/%s", { id: CGI.escape(id) }), + params: params, + opts: opts, + base_address: :api + ) + end + end + end +end diff --git a/lib/stripe/services/forwarding_service.rb b/lib/stripe/services/forwarding_service.rb new file mode 100644 index 000000000..69f3da702 --- /dev/null +++ b/lib/stripe/services/forwarding_service.rb @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + class ForwardingService < StripeService + attr_reader :requests + + def initialize(requestor) + super(requestor) + @requests = Stripe::Forwarding::RequestService.new(@requestor) + end + end +end diff --git a/lib/stripe/services/gift_cards/card_service.rb b/lib/stripe/services/gift_cards/card_service.rb new file mode 100644 index 000000000..d828be52c --- /dev/null +++ b/lib/stripe/services/gift_cards/card_service.rb @@ -0,0 +1,63 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + module GiftCards + class CardService < StripeService + # Creates a new gift card object. + def create(params = {}, opts = {}) + request( + method: :post, + path: "/v1/gift_cards/cards", + params: params, + opts: opts, + base_address: :api + ) + end + + # List gift cards for an account + def list(params = {}, opts = {}) + request( + method: :get, + path: "/v1/gift_cards/cards", + params: params, + opts: opts, + base_address: :api + ) + end + + # Retrieve a gift card by id + def retrieve(id, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/gift_cards/cards/%s", { id: CGI.escape(id) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Update a gift card + def update(id, params = {}, opts = {}) + request( + method: :post, + path: format("/v1/gift_cards/cards/%s", { id: CGI.escape(id) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Validates a gift card code, returning the matching gift card object if it exists. + def validate(params = {}, opts = {}) + request( + method: :post, + path: "/v1/gift_cards/cards/validate", + params: params, + opts: opts, + base_address: :api + ) + end + end + end +end diff --git a/lib/stripe/services/gift_cards/transaction_service.rb b/lib/stripe/services/gift_cards/transaction_service.rb new file mode 100644 index 000000000..277ad18fb --- /dev/null +++ b/lib/stripe/services/gift_cards/transaction_service.rb @@ -0,0 +1,74 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + module GiftCards + class TransactionService < StripeService + # Cancel a gift card transaction + def cancel(id, params = {}, opts = {}) + request( + method: :post, + path: format("/v1/gift_cards/transactions/%s/cancel", { id: CGI.escape(id) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Confirm a gift card transaction + def confirm(id, params = {}, opts = {}) + request( + method: :post, + path: format("/v1/gift_cards/transactions/%s/confirm", { id: CGI.escape(id) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Create a gift card transaction + def create(params = {}, opts = {}) + request( + method: :post, + path: "/v1/gift_cards/transactions", + params: params, + opts: opts, + base_address: :api + ) + end + + # List gift card transactions for a gift card + def list(params = {}, opts = {}) + request( + method: :get, + path: "/v1/gift_cards/transactions", + params: params, + opts: opts, + base_address: :api + ) + end + + # Retrieves the gift card transaction. + def retrieve(id, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/gift_cards/transactions/%s", { id: CGI.escape(id) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Update a gift card transaction + def update(id, params = {}, opts = {}) + request( + method: :post, + path: format("/v1/gift_cards/transactions/%s", { id: CGI.escape(id) }), + params: params, + opts: opts, + base_address: :api + ) + end + end + end +end diff --git a/lib/stripe/services/gift_cards_service.rb b/lib/stripe/services/gift_cards_service.rb new file mode 100644 index 000000000..9f7a2a0d3 --- /dev/null +++ b/lib/stripe/services/gift_cards_service.rb @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + class GiftCardsService < StripeService + attr_reader :cards, :transactions + + def initialize(requestor) + super(requestor) + @cards = Stripe::GiftCards::CardService.new(@requestor) + @transactions = Stripe::GiftCards::TransactionService.new(@requestor) + end + end +end diff --git a/lib/stripe/services/identity/verification_report_service.rb b/lib/stripe/services/identity/verification_report_service.rb new file mode 100644 index 000000000..6598452f3 --- /dev/null +++ b/lib/stripe/services/identity/verification_report_service.rb @@ -0,0 +1,30 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + module Identity + class VerificationReportService < StripeService + # List all verification reports. + def list(params = {}, opts = {}) + request( + method: :get, + path: "/v1/identity/verification_reports", + params: params, + opts: opts, + base_address: :api + ) + end + + # Retrieves an existing VerificationReport + def retrieve(report, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/identity/verification_reports/%s", { report: CGI.escape(report) }), + params: params, + opts: opts, + base_address: :api + ) + end + end + end +end diff --git a/lib/stripe/services/identity/verification_session_service.rb b/lib/stripe/services/identity/verification_session_service.rb new file mode 100644 index 000000000..c36805393 --- /dev/null +++ b/lib/stripe/services/identity/verification_session_service.rb @@ -0,0 +1,106 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + module Identity + class VerificationSessionService < StripeService + # A VerificationSession object can be canceled when it is in requires_input [status](https://stripe.com/docs/identity/how-sessions-work). + # + # Once canceled, future submission attempts are disabled. This cannot be undone. [Learn more](https://stripe.com/docs/identity/verification-sessions#cancel). + def cancel(session, params = {}, opts = {}) + request( + method: :post, + path: format("/v1/identity/verification_sessions/%s/cancel", { session: CGI.escape(session) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Creates a VerificationSession object. + # + # After the VerificationSession is created, display a verification modal using the session client_secret or send your users to the session's url. + # + # If your API key is in test mode, verification checks won't actually process, though everything else will occur as if in live mode. + # + # Related guide: [Verify your users' identity documents](https://stripe.com/docs/identity/verify-identity-documents) + def create(params = {}, opts = {}) + request( + method: :post, + path: "/v1/identity/verification_sessions", + params: params, + opts: opts, + base_address: :api + ) + end + + # Returns a list of VerificationSessions + def list(params = {}, opts = {}) + request( + method: :get, + path: "/v1/identity/verification_sessions", + params: params, + opts: opts, + base_address: :api + ) + end + + # Redact a VerificationSession to remove all collected information from Stripe. This will redact + # the VerificationSession and all objects related to it, including VerificationReports, Events, + # request logs, etc. + # + # A VerificationSession object can be redacted when it is in requires_input or verified + # [status](https://stripe.com/docs/identity/how-sessions-work). Redacting a VerificationSession in requires_action + # state will automatically cancel it. + # + # The redaction process may take up to four days. When the redaction process is in progress, the + # VerificationSession's redaction.status field will be set to processing; when the process is + # finished, it will change to redacted and an identity.verification_session.redacted event + # will be emitted. + # + # Redaction is irreversible. Redacted objects are still accessible in the Stripe API, but all the + # fields that contain personal data will be replaced by the string [redacted] or a similar + # placeholder. The metadata field will also be erased. Redacted objects cannot be updated or + # used for any purpose. + # + # [Learn more](https://stripe.com/docs/identity/verification-sessions#redact). + def redact(session, params = {}, opts = {}) + request( + method: :post, + path: format("/v1/identity/verification_sessions/%s/redact", { session: CGI.escape(session) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Retrieves the details of a VerificationSession that was previously created. + # + # When the session status is requires_input, you can use this method to retrieve a valid + # client_secret or url to allow re-submission. + def retrieve(session, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/identity/verification_sessions/%s", { session: CGI.escape(session) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Updates a VerificationSession object. + # + # When the session status is requires_input, you can use this method to update the + # verification check and options. + def update(session, params = {}, opts = {}) + request( + method: :post, + path: format("/v1/identity/verification_sessions/%s", { session: CGI.escape(session) }), + params: params, + opts: opts, + base_address: :api + ) + end + end + end +end diff --git a/lib/stripe/services/identity_service.rb b/lib/stripe/services/identity_service.rb new file mode 100644 index 000000000..d1a48008f --- /dev/null +++ b/lib/stripe/services/identity_service.rb @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + class IdentityService < StripeService + attr_reader :verification_reports, :verification_sessions + + def initialize(requestor) + super(requestor) + @verification_reports = Stripe::Identity::VerificationReportService.new(@requestor) + @verification_sessions = Stripe::Identity::VerificationSessionService.new(@requestor) + end + end +end diff --git a/lib/stripe/services/invoice_item_service.rb b/lib/stripe/services/invoice_item_service.rb new file mode 100644 index 000000000..9c69a6205 --- /dev/null +++ b/lib/stripe/services/invoice_item_service.rb @@ -0,0 +1,61 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + class InvoiceItemService < StripeService + # Creates an item to be added to a draft invoice (up to 250 items per invoice). If no invoice is specified, the item will be on the next invoice created for the customer specified. + def create(params = {}, opts = {}) + request( + method: :post, + path: "/v1/invoiceitems", + params: params, + opts: opts, + base_address: :api + ) + end + + # Deletes an invoice item, removing it from an invoice. Deleting invoice items is only possible when they're not attached to invoices, or if it's attached to a draft invoice. + def delete(invoiceitem, params = {}, opts = {}) + request( + method: :delete, + path: format("/v1/invoiceitems/%s", { invoiceitem: CGI.escape(invoiceitem) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Returns a list of your invoice items. Invoice items are returned sorted by creation date, with the most recently created invoice items appearing first. + def list(params = {}, opts = {}) + request( + method: :get, + path: "/v1/invoiceitems", + params: params, + opts: opts, + base_address: :api + ) + end + + # Retrieves the invoice item with the given ID. + def retrieve(invoiceitem, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/invoiceitems/%s", { invoiceitem: CGI.escape(invoiceitem) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Updates the amount or description of an invoice item on an upcoming invoice. Updating an invoice item is only possible before the invoice it's attached to is closed. + def update(invoiceitem, params = {}, opts = {}) + request( + method: :post, + path: format("/v1/invoiceitems/%s", { invoiceitem: CGI.escape(invoiceitem) }), + params: params, + opts: opts, + base_address: :api + ) + end + end +end diff --git a/lib/stripe/services/invoice_line_item_service.rb b/lib/stripe/services/invoice_line_item_service.rb new file mode 100644 index 000000000..e8edbbc54 --- /dev/null +++ b/lib/stripe/services/invoice_line_item_service.rb @@ -0,0 +1,31 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + class InvoiceLineItemService < StripeService + # When retrieving an invoice, you'll get a lines property containing the total count of line items and the first handful of those items. There is also a URL where you can retrieve the full (paginated) list of line items. + def list(invoice, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/invoices/%s/lines", { invoice: CGI.escape(invoice) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Updates an invoice's line item. Some fields, such as tax_amounts, only live on the invoice line item, + # so they can only be updated through this endpoint. Other fields, such as amount, live on both the invoice + # item and the invoice line item, so updates on this endpoint will propagate to the invoice item as well. + # Updating an invoice's line item is only possible before the invoice is finalized. + def update(invoice, line_item_id, params = {}, opts = {}) + request( + method: :post, + path: format("/v1/invoices/%s/lines/%s", { invoice: CGI.escape(invoice), line_item_id: CGI.escape(line_item_id) }), + params: params, + opts: opts, + base_address: :api + ) + end + end +end diff --git a/lib/stripe/services/invoice_payment_service.rb b/lib/stripe/services/invoice_payment_service.rb new file mode 100644 index 000000000..9a3fc99a9 --- /dev/null +++ b/lib/stripe/services/invoice_payment_service.rb @@ -0,0 +1,28 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + class InvoicePaymentService < StripeService + # When retrieving an invoice, there is an includable payments property containing the first handful of those items. There is also a URL where you can retrieve the full (paginated) list of payments. + def list(invoice, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/invoices/%s/payments", { invoice: CGI.escape(invoice) }), + params: params, + opts: opts, + base_address: :api + ) + end + + # Retrieves the invoice payment with the given ID. + def retrieve(invoice, invoice_payment, params = {}, opts = {}) + request( + method: :get, + path: format("/v1/invoices/%s/payments/%s", { invoice: CGI.escape(invoice), invoice_payment: CGI.escape(invoice_payment) }), + params: params, + opts: opts, + base_address: :api + ) + end + end +end diff --git a/lib/stripe/services/invoice_rendering_template_service.rb b/lib/stripe/services/invoice_rendering_template_service.rb new file mode 100644 index 000000000..d263f0a90 --- /dev/null +++ b/lib/stripe/services/invoice_rendering_template_service.rb @@ -0,0 +1,50 @@ +# File generated from our OpenAPI spec +# frozen_string_literal: true + +module Stripe + class InvoiceRenderingTemplateService < StripeService + # Updates the status of an invoice rendering template to ‘archived' so no new Stripe objects (customers, invoices, etc.) can reference it. The template can also no longer be updated. However, if the template is already set on a Stripe object, it will continue to be applied on invoices generated by it. + def archive(template, params = {}, opts = {}) + request( + method: :post, + path: format("/v1/invoice_rendering_templates/%