huntrecht-sdk 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +32 -0
- data/lib/huntrecht/client.rb +189 -0
- data/lib/huntrecht/errors.rb +36 -0
- data/lib/huntrecht/resources.rb +325 -0
- data/lib/huntrecht/version.rb +5 -0
- data/lib/huntrecht.rb +16 -0
- metadata +52 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 2e4418b148d7b59618906b0442001104999f4360ca3db315780d71a90091ff70
|
|
4
|
+
data.tar.gz: 47c6491f815275c4264bc80168bfe41b9be706efdeafbfd8ffecf63cdf5746ae
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: ecd9cb91da37da60a6c29796d3787257b8521d9f0ac8aa4b21652608c467ca3fbc6e2d1b1bb1aa39824e84632e6eb46adb6aed5bbee5c88a6e95d1f7e6ad31ee
|
|
7
|
+
data.tar.gz: d32a334f3e8dea757f1e782927d06eba5bafac6f58e56d2433fe63e860adb01f6d2eeabf8e9628e53b55df8f6d68df954b8afc0b70e9b6309a059bb3dd412b23
|
data/README.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Huntrecht SDK for Ruby
|
|
4
|
+
|
|
5
|
+
Official Ruby client for the Huntrecht Platform API v1.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
gem install huntrecht-sdk
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Or in your Gemfile:
|
|
14
|
+
|
|
15
|
+
```ruby
|
|
16
|
+
gem "huntrecht-sdk"
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
require "huntrecht"
|
|
23
|
+
|
|
24
|
+
client = Huntrecht::Client.new(
|
|
25
|
+
client_id: "hnt_your_client_id",
|
|
26
|
+
client_secret: "your_secret"
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
orders = client.orders.list(status: "pending")
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
See the [Ruby SDK docs](https://huntrecht.com/docs/sdks/ruby) for the full reference.
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "uri"
|
|
5
|
+
require "json"
|
|
6
|
+
|
|
7
|
+
require_relative "errors"
|
|
8
|
+
require_relative "version"
|
|
9
|
+
|
|
10
|
+
module Huntrecht
|
|
11
|
+
API_VERSION = "v1"
|
|
12
|
+
DEFAULT_BASE_URL = "https://api.huntrecht.com"
|
|
13
|
+
USER_AGENT = "huntrecht-sdk-ruby/#{VERSION}"
|
|
14
|
+
|
|
15
|
+
# Client for the Huntrecht Platform API v1.
|
|
16
|
+
#
|
|
17
|
+
# client = Huntrecht::Client.new(
|
|
18
|
+
# client_id: "hnt_abc123",
|
|
19
|
+
# client_secret: "secret"
|
|
20
|
+
# )
|
|
21
|
+
# client.auth.token # authenticate
|
|
22
|
+
# orders = client.orders.list
|
|
23
|
+
class Client
|
|
24
|
+
attr_reader :base_url, :client_id, :timeout, :max_retries, :retry_backoff
|
|
25
|
+
attr_accessor :access_token
|
|
26
|
+
|
|
27
|
+
def initialize(base_url: nil, client_id: nil, client_secret: nil,
|
|
28
|
+
access_token: nil, timeout: 30, max_retries: 3,
|
|
29
|
+
retry_backoff: 1.0)
|
|
30
|
+
@base_url = (base_url || ENV["HUNTRECHT_BASE_URL"] || DEFAULT_BASE_URL).sub(%r{/+\z}, "")
|
|
31
|
+
@client_id = client_id || ENV["HUNTRECHT_CLIENT_ID"] || ""
|
|
32
|
+
@client_secret = client_secret || ENV["HUNTRECHT_CLIENT_SECRET"] || ""
|
|
33
|
+
@access_token = access_token
|
|
34
|
+
@refresh_token = nil
|
|
35
|
+
@token_expires_at = Time.at(0)
|
|
36
|
+
@timeout = timeout
|
|
37
|
+
@max_retries = max_retries
|
|
38
|
+
@retry_backoff = retry_backoff
|
|
39
|
+
|
|
40
|
+
require_relative "resources"
|
|
41
|
+
@auth = Resources::AuthAPI.new(self)
|
|
42
|
+
@clients = Resources::ClientsAPI.new(self)
|
|
43
|
+
@orders = Resources::OrdersAPI.new(self)
|
|
44
|
+
@payments = Resources::PaymentsAPI.new(self)
|
|
45
|
+
@subscriptions = Resources::SubscriptionsAPI.new(self)
|
|
46
|
+
@credit = Resources::CreditAPI.new(self)
|
|
47
|
+
@credit_risk = Resources::CreditRiskAPI.new(self)
|
|
48
|
+
@kyc = Resources::KycAPI.new(self)
|
|
49
|
+
@quotes = Resources::QuotesAPI.new(self)
|
|
50
|
+
@users = Resources::UsersAPI.new(self)
|
|
51
|
+
@storefront = Resources::StorefrontAPI.new(self)
|
|
52
|
+
@price_drops = Resources::PriceDropsAPI.new(self)
|
|
53
|
+
@app_proxy = Resources::AppProxyAPI.new(self)
|
|
54
|
+
@linked_payments = Resources::LinkedPaymentsAPI.new(self)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
attr_reader :auth, :clients, :orders, :payments, :subscriptions,
|
|
58
|
+
:credit, :credit_risk, :kyc, :quotes, :users, :storefront,
|
|
59
|
+
:price_drops, :app_proxy, :linked_payments
|
|
60
|
+
|
|
61
|
+
def client_secret
|
|
62
|
+
@client_secret
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def refresh_token
|
|
66
|
+
@refresh_token
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def store_tokens(data)
|
|
70
|
+
@access_token = data["access_token"]
|
|
71
|
+
@refresh_token = data["refresh_token"]
|
|
72
|
+
@token_expires_at = Time.now + (data["expires_in"] || 1800).to_i - 60
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Make an API request with automatic auth, retry, and rate-limit handling.
|
|
76
|
+
def request(method, path, params: nil, json: nil, headers: nil, auth_required: true)
|
|
77
|
+
ensure_token if auth_required
|
|
78
|
+
|
|
79
|
+
last_error = nil
|
|
80
|
+
(0..@max_retries).each do |attempt|
|
|
81
|
+
begin
|
|
82
|
+
return perform(method, path, params: params, json: json,
|
|
83
|
+
headers: headers, auth_required: auth_required)
|
|
84
|
+
rescue RateLimitError => e
|
|
85
|
+
last_error = e
|
|
86
|
+
raise if attempt >= @max_retries
|
|
87
|
+
|
|
88
|
+
wait = e.retry_after.positive? ? e.retry_after : @retry_backoff * (2**attempt)
|
|
89
|
+
sleep(wait)
|
|
90
|
+
rescue APIError
|
|
91
|
+
raise
|
|
92
|
+
rescue StandardError => e
|
|
93
|
+
last_error = APIError.new("HTTP error: #{e.message}")
|
|
94
|
+
raise last_error if attempt >= @max_retries
|
|
95
|
+
|
|
96
|
+
sleep(@retry_backoff * (2**attempt))
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
raise(last_error || APIError.new("Request failed"))
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Build the full URI for a path + query params (no network).
|
|
103
|
+
def build_uri(path, params = nil)
|
|
104
|
+
uri = URI("#{@base_url}/api/#{API_VERSION}#{path}")
|
|
105
|
+
clean = (params || {}).reject { |_k, v| v.nil? }
|
|
106
|
+
unless clean.empty?
|
|
107
|
+
uri.query = URI.encode_www_form(clean.map { |k, v| [k.to_s, v.to_s] })
|
|
108
|
+
end
|
|
109
|
+
uri
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
private
|
|
113
|
+
|
|
114
|
+
def ensure_token
|
|
115
|
+
return if @access_token && Time.now < @token_expires_at
|
|
116
|
+
|
|
117
|
+
if !@client_id.empty? && !@client_secret.empty?
|
|
118
|
+
data = @auth.token
|
|
119
|
+
store_tokens(data)
|
|
120
|
+
else
|
|
121
|
+
raise AuthenticationError, "No access token and no client credentials. " \
|
|
122
|
+
"Set HUNTRECHT_CLIENT_ID and HUNTRECHT_CLIENT_SECRET, " \
|
|
123
|
+
"or pass them to Huntrecht::Client.new."
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def perform(method, path, params:, json:, headers:, auth_required:)
|
|
128
|
+
uri = build_uri(path, params)
|
|
129
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
130
|
+
http.use_ssl = uri.scheme == "https"
|
|
131
|
+
http.open_timeout = @timeout
|
|
132
|
+
http.read_timeout = @timeout
|
|
133
|
+
|
|
134
|
+
req_class = {
|
|
135
|
+
"GET" => Net::HTTP::Get, "POST" => Net::HTTP::Post,
|
|
136
|
+
"PUT" => Net::HTTP::Put, "PATCH" => Net::HTTP::Patch,
|
|
137
|
+
"DELETE" => Net::HTTP::Delete
|
|
138
|
+
}.fetch(method.upcase) { raise APIError, "Unsupported method #{method}" }
|
|
139
|
+
|
|
140
|
+
req = req_class.new(uri.request_uri)
|
|
141
|
+
req["Accept"] = "application/json"
|
|
142
|
+
req["User-Agent"] = USER_AGENT
|
|
143
|
+
req["Authorization"] = "Bearer #{@access_token}" if auth_required && @access_token
|
|
144
|
+
(headers || {}).each { |k, v| req[k] = v }
|
|
145
|
+
if json
|
|
146
|
+
req["Content-Type"] = "application/json"
|
|
147
|
+
req.body = JSON.generate(json)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
handle_response(http.request(req))
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def handle_response(resp)
|
|
154
|
+
return {} if resp.code == "204"
|
|
155
|
+
|
|
156
|
+
data = begin
|
|
157
|
+
JSON.parse(resp.body || "")
|
|
158
|
+
rescue JSON::ParserError
|
|
159
|
+
{ "raw" => resp.body.to_s }
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
case resp.code.to_i
|
|
163
|
+
when 200..299
|
|
164
|
+
data
|
|
165
|
+
when 401
|
|
166
|
+
@access_token = nil
|
|
167
|
+
raise AuthenticationError.new(data["error_description"] || "Authentication failed",
|
|
168
|
+
status_code: 401, response: data)
|
|
169
|
+
when 403
|
|
170
|
+
raise PermissionError.new(data["error_description"] || "Insufficient permissions",
|
|
171
|
+
status_code: 403, response: data)
|
|
172
|
+
when 404
|
|
173
|
+
raise NotFoundError.new(data["error_description"] || "Resource not found",
|
|
174
|
+
status_code: 404, response: data)
|
|
175
|
+
when 422
|
|
176
|
+
raise ValidationError.new(data["error_description"] || "Validation failed",
|
|
177
|
+
status_code: 422, response: data)
|
|
178
|
+
when 429
|
|
179
|
+
retry_after = resp["Retry-After"].to_i
|
|
180
|
+
retry_after = 60 if retry_after <= 0
|
|
181
|
+
raise RateLimitError.new(data["error_description"] || "Rate limit exceeded",
|
|
182
|
+
status_code: 429, retry_after: retry_after, response: data)
|
|
183
|
+
else
|
|
184
|
+
raise APIError.new(data["error_description"] || "HTTP #{resp.code}",
|
|
185
|
+
status_code: resp.code.to_i, response: data)
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Huntrecht
|
|
4
|
+
# Base error for all Huntrecht API failures.
|
|
5
|
+
class APIError < StandardError
|
|
6
|
+
attr_reader :status_code, :response
|
|
7
|
+
|
|
8
|
+
def initialize(message = nil, status_code: nil, response: nil)
|
|
9
|
+
super(message)
|
|
10
|
+
@status_code = status_code
|
|
11
|
+
@response = response
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# 401 — missing, expired, or invalid credentials.
|
|
16
|
+
class AuthenticationError < APIError; end
|
|
17
|
+
|
|
18
|
+
# 403 — authenticated but not permitted.
|
|
19
|
+
class PermissionError < APIError; end
|
|
20
|
+
|
|
21
|
+
# 404 — resource not found.
|
|
22
|
+
class NotFoundError < APIError; end
|
|
23
|
+
|
|
24
|
+
# 422 — request validation failed.
|
|
25
|
+
class ValidationError < APIError; end
|
|
26
|
+
|
|
27
|
+
# 429 — rate limited. Carries the Retry-After seconds.
|
|
28
|
+
class RateLimitError < APIError
|
|
29
|
+
attr_reader :retry_after
|
|
30
|
+
|
|
31
|
+
def initialize(message = nil, status_code: 429, retry_after: 60, response: nil)
|
|
32
|
+
super(message, status_code: status_code, response: response)
|
|
33
|
+
@retry_after = retry_after
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Huntrecht
|
|
4
|
+
module Resources
|
|
5
|
+
# Base class for all API resources.
|
|
6
|
+
class Base
|
|
7
|
+
def initialize(client)
|
|
8
|
+
@client = client
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def request(method, path, **kwargs)
|
|
14
|
+
@client.request(method, path, **kwargs)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def page_params(page, per_page, status = nil)
|
|
18
|
+
params = { page: page, per_page: per_page }
|
|
19
|
+
params[:status] = status if status
|
|
20
|
+
params
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Accept a body as positional hash, kwargs, or both (Ruby 3
|
|
24
|
+
# separates the two at call sites, so support each style).
|
|
25
|
+
def body_for(data = nil, **kwargs)
|
|
26
|
+
body = {}
|
|
27
|
+
body.merge!(data) if data
|
|
28
|
+
kwargs.each { |k, v| body[k.to_s] = v }
|
|
29
|
+
body
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# OAuth2 token lifecycle.
|
|
34
|
+
class AuthAPI < Base
|
|
35
|
+
def token(grant_type: "client_credentials", client_id: nil, client_secret: nil,
|
|
36
|
+
refresh_token: nil, scope: nil)
|
|
37
|
+
body = { "grant_type" => grant_type }
|
|
38
|
+
if grant_type == "client_credentials"
|
|
39
|
+
body["client_id"] = client_id || @client.client_id
|
|
40
|
+
body["client_secret"] = client_secret || @client.client_secret
|
|
41
|
+
body["scope"] = scope if scope
|
|
42
|
+
elsif grant_type == "refresh_token"
|
|
43
|
+
body["refresh_token"] = refresh_token || @client.refresh_token || ""
|
|
44
|
+
end
|
|
45
|
+
data = request("POST", "/auth/token", json: body, auth_required: false)
|
|
46
|
+
@client.store_tokens(data)
|
|
47
|
+
data
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def revoke(token = nil)
|
|
51
|
+
tok = token || @client.access_token || ""
|
|
52
|
+
request("POST", "/auth/revoke", params: { token: tok }, auth_required: false)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# OAuth2 API clients.
|
|
57
|
+
class ClientsAPI < Base
|
|
58
|
+
def list(user_id)
|
|
59
|
+
request("GET", "/clients", params: { user_id: user_id })
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def create(user_id, data = nil, **kwargs)
|
|
63
|
+
request("POST", "/clients", params: { user_id: user_id },
|
|
64
|
+
json: body_for(data, **kwargs))
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def update(user_id, client_id, data = nil, **kwargs)
|
|
68
|
+
request("PATCH", "/clients/#{client_id}", params: { user_id: user_id },
|
|
69
|
+
json: body_for(data, **kwargs))
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def rotate_secret(user_id, client_id)
|
|
73
|
+
request("POST", "/clients/#{client_id}/rotate", params: { user_id: user_id })
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def delete(user_id, client_id)
|
|
77
|
+
request("DELETE", "/clients/#{client_id}", params: { user_id: user_id })
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# B2B trade orders.
|
|
82
|
+
class OrdersAPI < Base
|
|
83
|
+
def list(page: 1, per_page: 20, status: nil)
|
|
84
|
+
request("GET", "/orders", params: page_params(page, per_page, status))
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def get(order_id)
|
|
88
|
+
request("GET", "/orders/#{order_id}")
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def create(commodity:, quantity:, delivery_terms: "FOB", destination: nil, currency: "USD")
|
|
92
|
+
params = { commodity: commodity, quantity: quantity,
|
|
93
|
+
delivery_terms: delivery_terms, currency: currency }
|
|
94
|
+
params[:destination] = destination if destination
|
|
95
|
+
request("POST", "/orders", params: params)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Payments.
|
|
100
|
+
class PaymentsAPI < Base
|
|
101
|
+
def list(page: 1, per_page: 20, status: nil)
|
|
102
|
+
request("GET", "/payments", params: page_params(page, per_page, status))
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def get(payment_id)
|
|
106
|
+
request("GET", "/payments/#{payment_id}")
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def create(data = nil, **kwargs)
|
|
110
|
+
request("POST", "/payments", json: body_for(data, **kwargs))
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# B2B subscriptions.
|
|
115
|
+
class SubscriptionsAPI < Base
|
|
116
|
+
def list(page: 1, per_page: 20, status: nil, include_payment_history: false)
|
|
117
|
+
params = page_params(page, per_page, status)
|
|
118
|
+
params[:include_payment_history] = include_payment_history
|
|
119
|
+
request("GET", "/subscriptions", params: params)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def get(subscription_id)
|
|
123
|
+
request("GET", "/subscriptions/#{subscription_id}")
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Basic credit scoring.
|
|
128
|
+
class CreditAPI < Base
|
|
129
|
+
def assess(data = nil, **kwargs)
|
|
130
|
+
request("POST", "/credit/assess", json: body_for(data, **kwargs))
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def score(customer_email)
|
|
134
|
+
request("GET", "/credit/score/#{customer_email}")
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Risk scoring, company assessments, improvement, credit history.
|
|
139
|
+
class CreditRiskAPI < Base
|
|
140
|
+
def get_score(customer_id)
|
|
141
|
+
request("GET", "/credit-risk/score", params: { customer_id: customer_id })
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def assess(data = nil, **kwargs)
|
|
145
|
+
request("POST", "/credit-risk/assess", json: body_for(data, **kwargs))
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def get_history(customer_id, limit: 20)
|
|
149
|
+
request("GET", "/data-connect/credit-history",
|
|
150
|
+
params: { customer_id: customer_id, limit: limit })
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def get_assessment(user_id)
|
|
154
|
+
request("GET", "/company/credit-assessment", params: { user_id: user_id })
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def request_assessment(data = nil, **kwargs)
|
|
158
|
+
request("POST", "/company/credit-assessment/request", json: body_for(data, **kwargs))
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def get_improvement_options
|
|
162
|
+
request("GET", "/credit-improvement/available-options")
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def connect_wallet(data = nil, **kwargs)
|
|
166
|
+
request("POST", "/credit-improvement/connect-wallet", json: body_for(data, **kwargs))
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def apply_boosts(data = nil, **kwargs)
|
|
170
|
+
request("POST", "/credit-improvement/apply-boosts", json: body_for(data, **kwargs))
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# KYC submissions.
|
|
175
|
+
class KycAPI < Base
|
|
176
|
+
def list(page: 1, per_page: 20, status: nil)
|
|
177
|
+
request("GET", "/kyc", params: page_params(page, per_page, status))
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def get(submission_id)
|
|
181
|
+
request("GET", "/kyc/#{submission_id}")
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def submit(data = nil, **kwargs)
|
|
185
|
+
request("POST", "/kyc", json: body_for(data, **kwargs))
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Commodity quotes.
|
|
190
|
+
class QuotesAPI < Base
|
|
191
|
+
def list(page: 1, per_page: 20)
|
|
192
|
+
request("GET", "/quotes", params: { page: page, per_page: per_page })
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def get(quote_id)
|
|
196
|
+
request("GET", "/quotes/#{quote_id}")
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def create(data = nil, **kwargs)
|
|
200
|
+
request("POST", "/quotes", json: body_for(data, **kwargs))
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# User profiles.
|
|
205
|
+
class UsersAPI < Base
|
|
206
|
+
def me
|
|
207
|
+
request("GET", "/users/me")
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def get(user_id)
|
|
211
|
+
request("GET", "/users/#{user_id}")
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# Shopify-backed catalog.
|
|
216
|
+
class StorefrontAPI < Base
|
|
217
|
+
def collections(first: 20, include_products: false)
|
|
218
|
+
first = 100 if first > 100
|
|
219
|
+
request("GET", "/storefront/collections",
|
|
220
|
+
params: { first: first, include_products: include_products })
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def collection(handle, products_first: 20)
|
|
224
|
+
products_first = 250 if products_first > 250
|
|
225
|
+
request("GET", "/storefront/collections/#{handle}",
|
|
226
|
+
params: { products_first: products_first })
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def products(first: 20, after: nil, b2b_only: false)
|
|
230
|
+
first = 100 if first > 100
|
|
231
|
+
params = { first: first, b2b_only: b2b_only }
|
|
232
|
+
params[:after] = after if after
|
|
233
|
+
request("GET", "/storefront/products", params: params)
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def product(handle)
|
|
237
|
+
request("GET", "/storefront/products/#{handle}")
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def search(query, first: 10, b2b_only: false)
|
|
241
|
+
first = 50 if first > 50
|
|
242
|
+
request("GET", "/storefront/search",
|
|
243
|
+
params: { query: query, first: first, b2b_only: b2b_only })
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# Price-drop events (public).
|
|
248
|
+
class PriceDropsAPI < Base
|
|
249
|
+
def list(limit: 10, min_discount: 5.0, days: 7)
|
|
250
|
+
limit = 50 if limit > 50
|
|
251
|
+
request("GET", "/price-drops",
|
|
252
|
+
params: { limit: limit, min_discount: min_discount, days: days },
|
|
253
|
+
auth_required: false)
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def featured(limit: 10)
|
|
257
|
+
limit = 10 if limit > 10
|
|
258
|
+
request("GET", "/price-drops/featured", params: { limit: limit },
|
|
259
|
+
auth_required: false)
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
# Theme-safe proxy endpoints (public).
|
|
264
|
+
class AppProxyAPI < Base
|
|
265
|
+
def collections(first: 20, signature: nil)
|
|
266
|
+
first = 100 if first > 100
|
|
267
|
+
params = { first: first }
|
|
268
|
+
params[:signature] = signature if signature
|
|
269
|
+
request("GET", "/app-proxy/collections", params: params, auth_required: false)
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def collection(handle, first: 20, signature: nil)
|
|
273
|
+
first = 250 if first > 250
|
|
274
|
+
params = { first: first }
|
|
275
|
+
params[:signature] = signature if signature
|
|
276
|
+
request("GET", "/app-proxy/collections/#{handle}", params: params,
|
|
277
|
+
auth_required: false)
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
def price_drops(limit: 10, min_discount: 5.0)
|
|
281
|
+
limit = 20 if limit > 20
|
|
282
|
+
request("GET", "/app-proxy/price-drops",
|
|
283
|
+
params: { limit: limit, min_discount: min_discount },
|
|
284
|
+
auth_required: false)
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def payment_methods(customer_id: nil, product_price: 0, b2b_exclusive: false,
|
|
288
|
+
signature: nil)
|
|
289
|
+
params = { product_price: product_price, b2b_exclusive: b2b_exclusive }
|
|
290
|
+
params[:customer_id] = customer_id if customer_id
|
|
291
|
+
params[:signature] = signature if signature
|
|
292
|
+
request("GET", "/app-proxy/payment-methods", params: params,
|
|
293
|
+
auth_required: false)
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
# Linked wallets and bank accounts.
|
|
298
|
+
class LinkedPaymentsAPI < Base
|
|
299
|
+
def check_eligibility(customer_id, product_price: 0, b2b_exclusive: false)
|
|
300
|
+
request("GET", "/linked-payments/check-eligibility",
|
|
301
|
+
params: { customer_id: customer_id, product_price: product_price,
|
|
302
|
+
b2b_exclusive: b2b_exclusive },
|
|
303
|
+
auth_required: false)
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
def link_wallet(customer_id, wallet_address: nil, wallet_provider: nil)
|
|
307
|
+
body = { "customer_id" => customer_id }
|
|
308
|
+
body["wallet_address"] = wallet_address if wallet_address
|
|
309
|
+
body["wallet_provider"] = wallet_provider if wallet_provider
|
|
310
|
+
request("POST", "/linked-payments/link-wallet", json: body)
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
def link_bank(customer_id, plaid_access_token: nil, account_id: nil)
|
|
314
|
+
body = { "customer_id" => customer_id }
|
|
315
|
+
body["plaid_access_token"] = plaid_access_token if plaid_access_token
|
|
316
|
+
body["account_id"] = account_id if account_id
|
|
317
|
+
request("POST", "/linked-payments/link-bank", json: body)
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
def linked_accounts(customer_id)
|
|
321
|
+
request("GET", "/linked-payments/linked-accounts/#{customer_id}")
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
end
|
data/lib/huntrecht.rb
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Huntrecht Platform SDK for Ruby.
|
|
4
|
+
#
|
|
5
|
+
# Official client for the Huntrecht Platform API v1.
|
|
6
|
+
#
|
|
7
|
+
# require "huntrecht"
|
|
8
|
+
#
|
|
9
|
+
# client = Huntrecht::Client.new(
|
|
10
|
+
# client_id: "hnt_your_client_id",
|
|
11
|
+
# client_secret: "your_secret"
|
|
12
|
+
# )
|
|
13
|
+
# orders = client.orders.list
|
|
14
|
+
require_relative "huntrecht/version"
|
|
15
|
+
require_relative "huntrecht/errors"
|
|
16
|
+
require_relative "huntrecht/client"
|
metadata
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: huntrecht-sdk
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.4
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Huntrecht
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-09-17 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
13
|
+
description: B2B commerce, credit risk, KYC, quotes, storefront, and payments for
|
|
14
|
+
the Huntrecht Platform API v1.
|
|
15
|
+
email:
|
|
16
|
+
- dev@huntrecht.com
|
|
17
|
+
executables: []
|
|
18
|
+
extensions: []
|
|
19
|
+
extra_rdoc_files: []
|
|
20
|
+
files:
|
|
21
|
+
- README.md
|
|
22
|
+
- lib/huntrecht.rb
|
|
23
|
+
- lib/huntrecht/client.rb
|
|
24
|
+
- lib/huntrecht/errors.rb
|
|
25
|
+
- lib/huntrecht/resources.rb
|
|
26
|
+
- lib/huntrecht/version.rb
|
|
27
|
+
homepage: https://github.com/huntrecht/sdks
|
|
28
|
+
licenses:
|
|
29
|
+
- MIT
|
|
30
|
+
metadata:
|
|
31
|
+
homepage_uri: https://github.com/huntrecht/sdks
|
|
32
|
+
source_code_uri: https://github.com/huntrecht/sdks
|
|
33
|
+
post_install_message:
|
|
34
|
+
rdoc_options: []
|
|
35
|
+
require_paths:
|
|
36
|
+
- lib
|
|
37
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
38
|
+
requirements:
|
|
39
|
+
- - ">="
|
|
40
|
+
- !ruby/object:Gem::Version
|
|
41
|
+
version: '3.0'
|
|
42
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '0'
|
|
47
|
+
requirements: []
|
|
48
|
+
rubygems_version: 3.5.22
|
|
49
|
+
signing_key:
|
|
50
|
+
specification_version: 4
|
|
51
|
+
summary: Official Ruby SDK for the Huntrecht Platform API v1
|
|
52
|
+
test_files: []
|