mirror of
https://github.com/we-promise/sure.git
synced 2026-04-25 15:04:04 +00:00
206 lines
6.2 KiB
Plaintext
206 lines
6.2 KiB
Plaintext
# frozen_string_literal: true
|
|
|
|
class Provider::<%= class_name %>
|
|
include HTTParty
|
|
|
|
headers "User-Agent" => "Sure Finance <%= class_name %> Client"
|
|
default_options.merge!(verify: true, ssl_verify_mode: OpenSSL::SSL::VERIFY_PEER, timeout: 120)
|
|
|
|
class Error < StandardError
|
|
attr_reader :error_type
|
|
|
|
def initialize(message, error_type = :unknown)
|
|
super(message)
|
|
@error_type = error_type
|
|
end
|
|
end
|
|
|
|
class ConfigurationError < Error; end
|
|
class AuthenticationError < Error; end
|
|
|
|
<% secret_fields = parsed_fields.select { |f| f[:secret] } -%>
|
|
<% non_secret_fields = parsed_fields.reject { |f| f[:secret] } -%>
|
|
<% all_attrs = parsed_fields.map { |f| f[:name] } -%>
|
|
attr_reader <%= all_attrs.map { |f| ":#{f}" }.join(", ") %>
|
|
|
|
def initialize(<%= all_attrs.map { |f| "#{f}:" }.join(", ") %>)
|
|
<% all_attrs.each do |f| -%>
|
|
@<%= f %> = <%= f %>
|
|
<% end -%>
|
|
validate_configuration!
|
|
end
|
|
<% if investment_provider? -%>
|
|
|
|
# TODO: Implement provider-specific API methods
|
|
# Example methods for investment providers:
|
|
|
|
# def list_accounts
|
|
# with_retries("list_accounts") do
|
|
# response = self.class.get(
|
|
# "#{base_url}/accounts",
|
|
# headers: auth_headers
|
|
# )
|
|
# handle_response(response)
|
|
# end
|
|
# end
|
|
|
|
# def get_holdings(account_id:)
|
|
# with_retries("get_holdings") do
|
|
# response = self.class.get(
|
|
# "#{base_url}/accounts/#{account_id}/holdings",
|
|
# headers: auth_headers
|
|
# )
|
|
# handle_response(response)
|
|
# end
|
|
# end
|
|
|
|
# def get_activities(account_id:, start_date:, end_date: Date.current)
|
|
# with_retries("get_activities") do
|
|
# response = self.class.get(
|
|
# "#{base_url}/accounts/#{account_id}/activities",
|
|
# headers: auth_headers,
|
|
# query: { start_date: start_date.to_s, end_date: end_date.to_s }
|
|
# )
|
|
# handle_response(response)
|
|
# end
|
|
# end
|
|
|
|
# def delete_connection(authorization_id:)
|
|
# with_retries("delete_connection") do
|
|
# response = self.class.delete(
|
|
# "#{base_url}/authorizations/#{authorization_id}",
|
|
# headers: auth_headers
|
|
# )
|
|
# handle_response(response)
|
|
# end
|
|
# end
|
|
<% else -%>
|
|
|
|
# TODO: Implement provider-specific API methods
|
|
# Example methods for banking providers:
|
|
|
|
# def list_accounts
|
|
# with_retries("list_accounts") do
|
|
# response = self.class.get(
|
|
# "#{base_url}/accounts",
|
|
# headers: auth_headers
|
|
# )
|
|
# handle_response(response)
|
|
# end
|
|
# end
|
|
|
|
# def get_transactions(account_id:, start_date:, end_date: Date.current, include_pending: true)
|
|
# with_retries("get_transactions") do
|
|
# response = self.class.get(
|
|
# "#{base_url}/accounts/#{account_id}/transactions",
|
|
# headers: auth_headers,
|
|
# query: {
|
|
# start_date: start_date.to_s,
|
|
# end_date: end_date.to_s,
|
|
# include_pending: include_pending
|
|
# }
|
|
# )
|
|
# handle_response(response)
|
|
# end
|
|
# end
|
|
|
|
# def get_balance(account_id:)
|
|
# with_retries("get_balance") do
|
|
# response = self.class.get(
|
|
# "#{base_url}/accounts/#{account_id}/balance",
|
|
# headers: auth_headers
|
|
# )
|
|
# handle_response(response)
|
|
# end
|
|
# end
|
|
<% end -%>
|
|
|
|
private
|
|
|
|
RETRYABLE_ERRORS = [
|
|
SocketError, Net::OpenTimeout, Net::ReadTimeout,
|
|
Errno::ECONNRESET, Errno::ECONNREFUSED, Errno::ETIMEDOUT, EOFError
|
|
].freeze
|
|
|
|
MAX_RETRIES = 3
|
|
INITIAL_RETRY_DELAY = 2 # seconds
|
|
|
|
def validate_configuration!
|
|
<% secret_fields.each do |field| -%>
|
|
raise ConfigurationError, "<%= field[:name].humanize %> is required" if @<%= field[:name] %>.blank?
|
|
<% end -%>
|
|
end
|
|
|
|
def with_retries(operation_name, max_retries: MAX_RETRIES)
|
|
retries = 0
|
|
|
|
begin
|
|
yield
|
|
rescue *RETRYABLE_ERRORS => e
|
|
retries += 1
|
|
|
|
if retries <= max_retries
|
|
delay = calculate_retry_delay(retries)
|
|
Rails.logger.warn(
|
|
"<%= class_name %> API: #{operation_name} failed (attempt #{retries}/#{max_retries}): " \
|
|
"#{e.class}: #{e.message}. Retrying in #{delay}s..."
|
|
)
|
|
sleep(delay)
|
|
retry
|
|
else
|
|
Rails.logger.error(
|
|
"<%= class_name %> API: #{operation_name} failed after #{max_retries} retries: " \
|
|
"#{e.class}: #{e.message}"
|
|
)
|
|
raise Error.new("Network error after #{max_retries} retries: #{e.message}", :network_error)
|
|
end
|
|
end
|
|
end
|
|
|
|
def calculate_retry_delay(retry_count)
|
|
base_delay = INITIAL_RETRY_DELAY * (2 ** (retry_count - 1))
|
|
jitter = base_delay * rand * 0.25
|
|
[ base_delay + jitter, 30 ].min
|
|
end
|
|
|
|
def auth_headers
|
|
# TODO: Customize based on your provider's authentication method
|
|
{
|
|
<% if secret_fields.any? -%>
|
|
"Authorization" => "Bearer #{@<%= secret_fields.first[:name] %>}",
|
|
<% end -%>
|
|
"Content-Type" => "application/json",
|
|
"Accept" => "application/json"
|
|
}
|
|
end
|
|
|
|
def handle_response(response)
|
|
case response.code
|
|
when 200, 201
|
|
JSON.parse(response.body, symbolize_names: true)
|
|
when 400
|
|
Rails.logger.error "<%= class_name %> API: Bad request - #{response.body}"
|
|
raise Error.new("Bad request: #{response.body}", :bad_request)
|
|
when 401
|
|
raise AuthenticationError.new("Invalid credentials", :unauthorized)
|
|
when 403
|
|
raise AuthenticationError.new("Access forbidden - check your permissions", :access_forbidden)
|
|
when 404
|
|
raise Error.new("Resource not found", :not_found)
|
|
when 429
|
|
raise Error.new("Rate limit exceeded. Please try again later.", :rate_limited)
|
|
when 500..599
|
|
raise Error.new("<%= class_name %> server error (#{response.code}). Please try again later.", :server_error)
|
|
else
|
|
Rails.logger.error "<%= class_name %> API: Unexpected response - Code: #{response.code}, Body: #{response.body}"
|
|
raise Error.new("Unexpected error: #{response.code} - #{response.body}", :unknown)
|
|
end
|
|
end
|
|
<% if non_secret_fields.any? { |f| f[:name] == "base_url" } -%>
|
|
|
|
def base_url
|
|
@base_url.presence || "<%= "https://api.example.com/v1" %>" # TODO: Set your provider's default base URL
|
|
end
|
|
<% end -%>
|
|
end
|