From 32520a340362661915bb11c80eb1957281fbe207 Mon Sep 17 00:00:00 2001 From: Armando Ibarra Date: Thu, 3 Nov 2022 20:39:57 -0600 Subject: [PATCH] Added support for custom http client --- .rubocop.yml | 3 + README.md | 57 +++++++++++++++++++ .../abstract_checkout_sdk_builder.rb | 29 ++++++++-- lib/checkout_sdk/api_client.rb | 29 ++++------ lib/checkout_sdk/checkout_api.rb | 4 +- lib/checkout_sdk/checkout_configuration.rb | 10 ++-- .../checkout_oauth_sdk_builder.rb | 4 +- .../checkout_static_keys_sdk_builder.rb | 3 +- lib/checkout_sdk/checkout_utils.rb | 15 +++++ lib/checkout_sdk/oauth_sdk_credentials.rb | 18 ++---- ...eckout_previous_static_keys_sdk_builder.rb | 3 +- .../request_apm_payments_integration_spec.rb | 12 +--- spec/checkout_sdk_spec.rb | 13 ----- 13 files changed, 133 insertions(+), 67 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 3e5af9f..98344e5 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -22,6 +22,9 @@ Metrics/ParameterLists: Metrics/ClassLength: Max: 110 +Metrics/CyclomaticComplexity: + Max: 8 + Style/Documentation: Enabled: false diff --git a/README.md b/README.md index c109d19..4843300 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,63 @@ All the API responses that do not fall in the 2** status codes will cause a `Che exception encapsulates the `http_metadata` and a map of `error_details`, if available. +## Custom Http Client + +Ruby SDK supports your own configuration for `http client` using `Faraday::Connection` object, you can pass +through the +SDK instantiation as follows: + +```ruby +http_client = Faraday.new do |conn| + conn.options.timeout = 10 + conn.proxy "http://localhost:8080" +end + +api = CheckoutSdk.builder + .static_keys + .with_secret_key('secret_key') + .with_public_key('public_key') # optional, only required for operations related with tokens + .with_environment(CheckoutSdk::Environment.sandbox) + .with_http_client(http_client) + .build +``` + +You don't need to specify the URL for Faraday constructor the SDK grabs the belong URI from `CheckoutSdk::Environment` +however if you want to +use specific URI's without a proxy you can create the `Environment` object as follows. + +```ruby +environment = CheckoutSdk::Environment.new('https://the.base.uri/', # the uri for all CKO operations + 'https://the.oauth.uri/connect/token', # the uri used for OAUTH authorization, only required for OAuth operations + 'https://the.files.uri/', # the uri used for Files operations, only required for Accounts module + 'https://the.transfers.uri/', # the uri used for Transfer operations, only required for Transfers module + 'https://the.balances.uri/', # the uri used for Balances operations, only required for Balances module + false) +``` + +If you want to provide your own `http client` and wants to use the `upload_file` functionality from `Disputes` or `Accounts` modules, you +also need to specify the custom `http client` for `multipart requests`: + +```ruby +http_client = Faraday.new do |conn| + conn.options.timeout = 10 + conn.proxy "http://localhost:8080" +end + +multipart_client = Faraday.new do |f| + f.request :multipart +end + +api = CheckoutSdk.builder + .static_keys + .with_secret_key('secret_key') + .with_public_key('public_key') # optional, only required for operations related with tokens + .with_environment(CheckoutSdk::Environment.sandbox) + .with_http_client(http_client) + .with_multipart_http_client(multipart_client) + .build +``` + ## Building from source Once you check out the code from GitHub, the project can be built using gem: diff --git a/lib/checkout_sdk/abstract_checkout_sdk_builder.rb b/lib/checkout_sdk/abstract_checkout_sdk_builder.rb index 7a2da9e..59f1063 100644 --- a/lib/checkout_sdk/abstract_checkout_sdk_builder.rb +++ b/lib/checkout_sdk/abstract_checkout_sdk_builder.rb @@ -5,8 +5,10 @@ class AbstractCheckoutSdkBuilder # @!attribute environment # @return [Environment] # @!attribute http_client - # @return [Object] - attr_accessor :environment, :http_client + # @return [Faraday::Connection] + # @!attribute multipart_http_client + # @return [Faraday::Connection] + attr_accessor :environment, :http_client, :multipart_http_client # @param [Environment] environment def with_environment(environment) @@ -14,15 +16,34 @@ def with_environment(environment) self end + # @param [Faraday::Connection] http_client def with_http_client(http_client) @http_client = http_client self end + # @param [Faraday::Connection] multipart_http_client + def with_multipart_http_client(multipart_http_client) + @multipart_http_client = multipart_http_client + self + end + def build with_environment(Environment.sandbox) if environment.nil? - raise CheckoutArgumentException, 'HttpClient must be an instance of Faraday::Connection' \ - if !http_client.nil? && !@http_client.instance_of?(Faraday::Connection) + if http_client.nil? + @http_client = CheckoutUtils.build_default_client + else + unless @http_client.instance_of?(Faraday::Connection) + raise CheckoutArgumentException, 'http_client must be an instance of Faraday::Connection' + end + end + if multipart_http_client.nil? + @multipart_http_client = CheckoutUtils.build_multipart_client + else + unless @multipart_http_client.instance_of?(Faraday::Connection) + raise CheckoutArgumentException, 'multipart_http_client must be an instance of Faraday::Connection' + end + end end end end diff --git a/lib/checkout_sdk/api_client.rb b/lib/checkout_sdk/api_client.rb index 1c34c86..5e89344 100644 --- a/lib/checkout_sdk/api_client.rb +++ b/lib/checkout_sdk/api_client.rb @@ -2,12 +2,15 @@ module CheckoutSdk class ApiClient - # @param [CheckoutConfiguration] _configuration + attr_accessor :client, :multipart_client + + # @param [CheckoutConfiguration] configuration # @param [String] uri - def initialize(_configuration, uri) - # TODO: expect given http client - @client = build_default_client(uri) - @multipart_client = build_multipart_client(uri) + def initialize(configuration, uri) + @client = configuration.http_client.clone + @client.url_prefix = uri + @multipart_client = configuration.multipart_http_client.clone + @multipart_client.url_prefix = uri end # @param [String] path @@ -51,19 +54,6 @@ def submit_file(path, private - def build_multipart_client(uri) - Faraday.new(uri) do |f| - f.request :multipart - f.response :raise_error - end - end - - def build_default_client(uri) - Faraday.new(uri) do |f| - f.response :raise_error - end - end - def invoke(method, path, authorization, body = nil, idempotency_key = nil, params: nil) path = append_params(path, params) unless params.nil? @@ -91,7 +81,6 @@ def get_default_headers(authorization) end def append_params(path, input_params) - # TODO: refactor this into a utils method hash = CheckoutSdk::JsonSerializer.to_custom_hash(input_params) params = URI.encode_www_form(hash) "#{path}?#{params}" @@ -128,6 +117,8 @@ def upload_file(path, authorization, file_request) end def parse_response(response) + raise CheckoutApiException, response if response.status < 200 || response.status >= 300 + metadata = CheckoutUtils.map_to_http_metadata(response) body = (JSON.parse(response.body, object_class: OpenStruct) if !response.body.nil? && response.body != '') body = OpenStruct.new if body.nil? diff --git a/lib/checkout_sdk/checkout_api.rb b/lib/checkout_sdk/checkout_api.rb index ba6e5c8..cca8124 100644 --- a/lib/checkout_sdk/checkout_api.rb +++ b/lib/checkout_sdk/checkout_api.rb @@ -65,13 +65,13 @@ def initialize(configuration) # @param [CheckoutConfiguration] configuration # @return [ApiClient] def base_api_client(configuration) - CheckoutSdk::ApiClient.new configuration, configuration.environment.base_uri + ApiClient.new(configuration, configuration.environment.base_uri) end # @param [CheckoutConfiguration] configuration # @return [ApiClient] def files_client(configuration) - CheckoutSdk::ApiClient.new configuration, configuration.environment.files_uri + ApiClient.new(configuration, configuration.environment.files_uri) end end end diff --git a/lib/checkout_sdk/checkout_configuration.rb b/lib/checkout_sdk/checkout_configuration.rb index 57bb1f4..b69bbd3 100644 --- a/lib/checkout_sdk/checkout_configuration.rb +++ b/lib/checkout_sdk/checkout_configuration.rb @@ -6,17 +6,19 @@ module CheckoutSdk # @!attribute environment # @return [Environment] # @!attribute http_client - # @return [Object] + # @return [Faraday::Connection] class CheckoutConfiguration - attr_accessor :credentials, :environment, :http_client + attr_accessor :credentials, :environment, :http_client, :multipart_http_client # @param [SdkCredentials] credentials # @param [Environment] environment - # @param [Object] http_client - def initialize(credentials, environment, http_client) + # @param [Faraday::Connection] http_client + # @param [Faraday::Connection] multipart_http_client + def initialize(credentials, environment, http_client, multipart_http_client) @credentials = credentials @environment = environment @http_client = http_client + @multipart_http_client = multipart_http_client end end end diff --git a/lib/checkout_sdk/checkout_oauth_sdk_builder.rb b/lib/checkout_sdk/checkout_oauth_sdk_builder.rb index 3f11df2..3dbb513 100644 --- a/lib/checkout_sdk/checkout_oauth_sdk_builder.rb +++ b/lib/checkout_sdk/checkout_oauth_sdk_builder.rb @@ -40,11 +40,13 @@ def with_scopes(scopes) # @return [CheckoutSdk::CheckoutApi] def build + super CheckoutSdk::CheckoutApi.new( CheckoutConfiguration.new( OAuthSdkCredentials.new(client_id, client_secret, scopes, http_client, environment, authorization_uri), environment, - http_client + http_client, + multipart_http_client ) ) end diff --git a/lib/checkout_sdk/checkout_static_keys_sdk_builder.rb b/lib/checkout_sdk/checkout_static_keys_sdk_builder.rb index 8cd0e65..3426a2a 100644 --- a/lib/checkout_sdk/checkout_static_keys_sdk_builder.rb +++ b/lib/checkout_sdk/checkout_static_keys_sdk_builder.rb @@ -15,7 +15,8 @@ def build CheckoutConfiguration.new( StaticKeysSdkCredentials.new(secret_key, public_key), environment, - http_client + http_client, + multipart_http_client ) ) end diff --git a/lib/checkout_sdk/checkout_utils.rb b/lib/checkout_sdk/checkout_utils.rb index a86ac98..35bc659 100644 --- a/lib/checkout_sdk/checkout_utils.rb +++ b/lib/checkout_sdk/checkout_utils.rb @@ -20,5 +20,20 @@ def self.map_to_http_metadata(response) end metadata end + + # @return [Faraday::Connection] + def self.build_multipart_client + Faraday.new do |f| + f.request :multipart + f.response :raise_error + end + end + + # @return [Faraday::Connection] + def self.build_default_client + Faraday.new do |f| + f.response :raise_error + end + end end end diff --git a/lib/checkout_sdk/oauth_sdk_credentials.rb b/lib/checkout_sdk/oauth_sdk_credentials.rb index 9bf9cc4..a585ee2 100644 --- a/lib/checkout_sdk/oauth_sdk_credentials.rb +++ b/lib/checkout_sdk/oauth_sdk_credentials.rb @@ -27,7 +27,7 @@ class OAuthSdkCredentials < SdkCredentials # @param [String] client_id # @param [String] client_secret # @param [Array(CheckoutSdk::OAuthScopes)] scopes - # @param [Faraday] http_client + # @param [Faraday::Connection] http_client # @param [CheckoutSdk::Environment] environment # @param [String, nil] auth_uri def initialize(client_id, client_secret, scopes, http_client, environment, auth_uri = nil) @@ -36,7 +36,8 @@ def initialize(client_id, client_secret, scopes, http_client, environment, auth_ @client_secret = client_secret @scopes = scopes @authorization_uri = auth_uri.nil? ? environment.authorization_uri : auth_uri - @http_client = http_client.nil? ? build_default_client(authorization_uri) : http_client + @oauth_http_client = http_client.clone + @oauth_http_client.url_prefix = @authorization_uri build_access_token end @@ -66,7 +67,7 @@ def build_access_token body = URI.encode_www_form data begin - response = @http_client.run_request(:post, @authorization_uri, body, headers) + response = @oauth_http_client.run_request(:post, @authorization_uri, body, headers) rescue Faraday::ClientError => e raise CheckoutAuthorizationException, e.response rescue Faraday::ConnectionFailed => e @@ -85,19 +86,10 @@ def build_access_token private - # TODO: refactor this into a utils method - # @param [String] authorization_uri - # @return [Faraday::Connection] - def build_default_client(authorization_uri) - Faraday.new(authorization_uri) do |f| - f.response(:raise_error) - end - end - # @param [String] client_id # @param [String] client_secret # @param [Environment] environment - # @param [String] authorization_uri + # @param [String, nil] authorization_uri def validate_arguments(client_id, client_secret, environment, authorization_uri) if client_id.nil? || client_secret.nil? raise CheckoutArgumentException, 'Invalid OAuth "client_id" or "client_secret"' diff --git a/lib/checkout_sdk/previous/checkout_previous_static_keys_sdk_builder.rb b/lib/checkout_sdk/previous/checkout_previous_static_keys_sdk_builder.rb index 41edeec..7b0ec6b 100644 --- a/lib/checkout_sdk/previous/checkout_previous_static_keys_sdk_builder.rb +++ b/lib/checkout_sdk/previous/checkout_previous_static_keys_sdk_builder.rb @@ -15,7 +15,8 @@ def build CheckoutConfiguration.new( PreviousStaticKeysSdkCredentials.new(secret_key, public_key), environment, - http_client + http_client, + multipart_http_client ) ) end diff --git a/spec/checkout_sdk/payments/request_apm_payments_integration_spec.rb b/spec/checkout_sdk/payments/request_apm_payments_integration_spec.rb index 67af636..63b8fea 100644 --- a/spec/checkout_sdk/payments/request_apm_payments_integration_spec.rb +++ b/spec/checkout_sdk/payments/request_apm_payments_integration_spec.rb @@ -265,7 +265,7 @@ end context 'when requesting Sofort source payment' do - it 'should request payment correctly' do + it 'raises an error (apm_service_unavailable)' do source = CheckoutSdk::Payments::SofortSource.new request = CheckoutSdk::Payments::PaymentRequest.new @@ -277,14 +277,8 @@ request.success_url = 'https://testing.checkout.com/sucess' request.failure_url = 'https://testing.checkout.com/failure' - response = default_sdk.payments.request_payment(request) - expect(response).not_to be nil - expect(response.id).not_to be nil - - payment_details = default_sdk.payments.get_payment_details(response.id) - expect(payment_details).not_to be nil - expect(payment_details.id).to eq response.id - expect(payment_details.source.type).to eq CheckoutSdk::Common::PaymentSourceType::SOFORT + expect { default_sdk.payments.request_payment(request) } + .to raise_error(CheckoutSdk::CheckoutApiException) { |e| expect(e.error_details[:error_codes].first).to eq 'apm_service_unavailable' } end end diff --git a/spec/checkout_sdk_spec.rb b/spec/checkout_sdk_spec.rb index 8c05da9..4382329 100644 --- a/spec/checkout_sdk_spec.rb +++ b/spec/checkout_sdk_spec.rb @@ -120,19 +120,6 @@ 'Invalid OAuth "client_id" or "client_secret"') end - it 'raises an error when environment and authorization_uri are not provided' do - expect do - CheckoutSdk.builder - .oauth - .with_client_credentials(ENV['CHECKOUT_DEFAULT_OAUTH_CLIENT_ID'], - ENV['CHECKOUT_DEFAULT_OAUTH_CLIENT_SECRET']) - .with_scopes([CheckoutSdk::OAuthScopes::VAULT, - CheckoutSdk::OAuthScopes::GATEWAY]) - .build - end.to raise_error(CheckoutSdk::CheckoutArgumentException, - 'Invalid configuration. Please specify an "environment" or a specific OAuth "authorization_uri"') - end - it 'raises an error when the authorization_uri provided is invalid' do expect do CheckoutSdk.builder