tango-api-client 0.1.0
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/.rspec +3 -0
- data/CHANGELOG.md +11 -0
- data/LICENSE.txt +21 -0
- data/README.md +181 -0
- data/Rakefile +12 -0
- data/lib/tango/api/auth/basic.rb +22 -0
- data/lib/tango/api/auth/oauth2.rb +67 -0
- data/lib/tango/api/client/version.rb +9 -0
- data/lib/tango/api/client.rb +128 -0
- data/lib/tango/api/errors.rb +26 -0
- data/lib/tango/api/resources/accounts.rb +59 -0
- data/lib/tango/api/resources/base.rb +114 -0
- data/lib/tango/api/resources/catalogs.rb +50 -0
- data/lib/tango/api/resources/choice_products.rb +27 -0
- data/lib/tango/api/resources/customers.rb +30 -0
- data/lib/tango/api/resources/exchange_rates.rb +22 -0
- data/lib/tango/api/resources/funds.rb +43 -0
- data/lib/tango/api/resources/orders.rb +32 -0
- data/lib/tango/api/resources/status.rb +15 -0
- data/lib/tango/api.rb +36 -0
- data/sig/tango/api/client.rbs +20 -0
- metadata +125 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: becdf70f2093e91de7a7e7e1fbfd2c5dadbed25dffb8af5e00d330f4bdeebc3c
|
|
4
|
+
data.tar.gz: a0175048faf72fef7d89acf67e1fdf2d7649bff9a985d57ca88520e3e3f87c41
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 8f09484e472d1ec10e64019d00def0eed0821a4786b7fe11305ee7bcb1fcf5cae8d5a0f0521812176c1ff0a65b43cef25bec7afd6bec87a85d3b1fc4fd6a1f88
|
|
7
|
+
data.tar.gz: a2ed02aeec6a39e94a7be633e194a901c79d09eca09f253a3372b62a2b6792dc3513975bde325cf0fa189e667e7e88840df22edfab95c154e58ed48825b191ba
|
data/.rspec
ADDED
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
## [0.1.0] - 2025-11-07
|
|
6
|
+
- Initial release
|
|
7
|
+
- Basic and OAuth2 auth strategies
|
|
8
|
+
- Catalogs#get, Orders#create, Accounts#get, Funds (register/unregister/add), Customers#create, Accounts#create
|
|
9
|
+
- Faraday-based client with retries/timeouts
|
|
10
|
+
- Structured error mapping
|
|
11
|
+
- README and RSpec scaffolding
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Puneeth Kumar DN
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# tango-api-client
|
|
2
|
+
|
|
3
|
+
Ruby client for Tango (BHN) API with dual auth (Basic/OAuth2), flexible parameterization, retries/timeouts, and structured errors.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Install the gem and add to the application's Gemfile by executing:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
bundle add tango-api-client
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
gem install tango-api-client
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
require "tango/api/client"
|
|
23
|
+
|
|
24
|
+
Tango::Api.configure do |c|
|
|
25
|
+
c.base_url = ENV["TANGO_URL"]
|
|
26
|
+
c.timeout = 45
|
|
27
|
+
c.retries = { max: 2, base: 0.5, max_delay: 5 }
|
|
28
|
+
# Basic auth
|
|
29
|
+
c.auth = Tango::Api::Auth::Basic.new(platform: ENV["TANGO_PLATFORM"], key: ENV["TANGO_KEY"])
|
|
30
|
+
# or OAuth2
|
|
31
|
+
# c.auth = Tango::Api::Auth::OAuth2.new(
|
|
32
|
+
# token_url: ENV["TANGO_TOKEN_URL"],
|
|
33
|
+
# client_id: ENV["TANGO_CLIENT_ID"],
|
|
34
|
+
# client_secret: ENV["TANGO_CLIENT_SECRET"],
|
|
35
|
+
# scope: ENV["TANGO_SCOPE"]
|
|
36
|
+
# )
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
client = Tango::Api::Client.new
|
|
40
|
+
|
|
41
|
+
# Alternatively, pass a base URL string directly
|
|
42
|
+
# (convenient for scripts or isolated clients)
|
|
43
|
+
client = Tango::Api::Client.new("https://integration-api.tangocard.com/raas/v2")
|
|
44
|
+
|
|
45
|
+
# Or pass a configuration hash for explicit per-client settings
|
|
46
|
+
client = Tango::Api::Client.new(
|
|
47
|
+
base_url: "https://integration-api.tangocard.com/raas/v2",
|
|
48
|
+
timeout: 60,
|
|
49
|
+
retries: { max: 3, base: 0.5, max_delay: 8 },
|
|
50
|
+
default_headers: { "Accept-Language" => "en-US" }
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
# Catalogs
|
|
54
|
+
client.catalogs.get(country: 'US', status: 'active', verbose: true, rewardType: ['gift card','payment card'], categoryIds: 'uuid1,uuid2', currency: 'USD')
|
|
55
|
+
|
|
56
|
+
# Orders
|
|
57
|
+
client.orders.create(accountIdentifier: 'acc', amount: 1000, customerIdentifier: 'cust', utid: 'UT...', recipient: { email: 'a@b.com', firstName: 'A' }, sendEmail: true)
|
|
58
|
+
|
|
59
|
+
# Accounts
|
|
60
|
+
client.accounts.get('acc-identifier')
|
|
61
|
+
|
|
62
|
+
# Create Account for a Customer
|
|
63
|
+
client.accounts.create('customer-identifier', { accountIdentifier: 'new-acc', currency: 'USD' })
|
|
64
|
+
|
|
65
|
+
# Customers
|
|
66
|
+
client.customers.create({ customerIdentifier: 'cust', name: 'ACME Inc.' })
|
|
67
|
+
# List and filter customers
|
|
68
|
+
client.customers.list(displayName: 'ACME', status: 'active')
|
|
69
|
+
# Fetch a specific customer
|
|
70
|
+
client.customers.get('cust')
|
|
71
|
+
|
|
72
|
+
# Funds
|
|
73
|
+
client.funds.register_card({ accountIdentifier: 'acc', card: { number: '4111...', expiryMonth: '12', expiryYear: '2030' } })
|
|
74
|
+
client.funds.add_funds({ accountIdentifier: 'acc', amount: 5000 })
|
|
75
|
+
client.funds.unregister_card({ accountIdentifier: 'acc' })
|
|
76
|
+
|
|
77
|
+
# Error handling
|
|
78
|
+
begin
|
|
79
|
+
client.accounts.get('missing')
|
|
80
|
+
rescue Tango::Api::Errors::HttpError => e
|
|
81
|
+
puts "Error: #{e.class} status=#{e.status} request_id=#{e.request_id} message=#{e.message}"
|
|
82
|
+
end
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Notes
|
|
86
|
+
- Retries apply to idempotent HTTP methods (GET/HEAD/OPTIONS) with backoff and jitter.
|
|
87
|
+
- Ruby >= 3.0 is required.
|
|
88
|
+
- `base_url` is required (e.g., `https://api.tangocard.com/raas/v2`).
|
|
89
|
+
- Initialization options:
|
|
90
|
+
- Global config (recommended for Rails/apps): use `Tango::Api.configure { ... }` then `Tango::Api::Client.new`.
|
|
91
|
+
- Per-client overrides: pass a config hash to `Client.new(...)`.
|
|
92
|
+
- Convenience: passing a base URL string to `Client.new("https://...")` is supported for quick scripts.
|
|
93
|
+
|
|
94
|
+
### Examples
|
|
95
|
+
|
|
96
|
+
```ruby
|
|
97
|
+
# Catalogs convenience filters
|
|
98
|
+
client.catalogs.get_by_brand_key('AMAZON', verbose: true)
|
|
99
|
+
client.catalogs.get_by_brand_name('Amazon', country: 'US')
|
|
100
|
+
client.catalogs.get_by_utid('UT123456')
|
|
101
|
+
client.catalogs.get_by_reward_name('Gift Card')
|
|
102
|
+
|
|
103
|
+
# Choice Products
|
|
104
|
+
client.choice_products.get(rewardName: 'Choice', currencyCode: 'USD', countries: ['US'])
|
|
105
|
+
client.choice_products.get_for_utid('UT-CHOICE-123')
|
|
106
|
+
client.choice_products.catalog('UT-CHOICE-123', verbose: true, country: 'US')
|
|
107
|
+
|
|
108
|
+
# Orders with Idempotency-Key for safe retries
|
|
109
|
+
require 'securerandom'
|
|
110
|
+
idempotency = SecureRandom.uuid
|
|
111
|
+
client.orders.create(
|
|
112
|
+
{ accountIdentifier: 'acc', amount: 1000, customerIdentifier: 'cust', utid: 'UT123', recipient: { email: 'a@b.com' } },
|
|
113
|
+
idempotency_key: idempotency
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
# Resend with Idempotency-Key (prevents duplicate resends)
|
|
117
|
+
client.orders.resend('ORDER-123', idempotency_key: idempotency)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Resources overview
|
|
121
|
+
|
|
122
|
+
| Resource | Class | Methods (args) | HTTP / Path |
|
|
123
|
+
|-----------|-----------------------------------|----------------------------------------------------------------------------------------|----------------------------------------------------|
|
|
124
|
+
| Catalogs | `Tango::Api::Resources::Catalogs` | `get(params = {})` | GET `/catalogs` |
|
|
125
|
+
| Orders | `Tango::Api::Resources::Orders` | `create(body)`, `get(order_id)`, `list(params = {})`, `resend(order_id)` | POST `/orders`; GET `/orders/{id}`; GET `/orders`; POST `/orders/{id}/resend` |
|
|
126
|
+
| Accounts | `Tango::Api::Resources::Accounts` | `get(account_identifier)`, `list_for_customer(customer_identifier, params)`, `create(customer_identifier, body)`, `update_under_customer(customer_identifier, account_identifier, body)`, low balance alerts: `list_low_balance_alerts`, `set_low_balance_alert`, `get_low_balance_alert`, `update_low_balance_alert`, `delete_low_balance_alert` | GET `/accounts/{accountIdentifier}`; GET/POST/PATCH/DELETE under `/customers/{customerIdentifier}/accounts/...` |
|
|
127
|
+
| Customers | `Tango::Api::Resources::Customers`| `list(params = {})`, `get(customer_identifier)`, `create(body)`, `accounts(customer_identifier, params)` | GET `/customers`; GET `/customers/{customerIdentifier}`; POST `/customers`; GET `/customers/{customerIdentifier}/accounts` |
|
|
128
|
+
| Funds | `Tango::Api::Resources::Funds` | `register_card(body)`, `unregister_card(body)`, `add_funds(body)` | POST `/funds/registerCreditCard`; `/funds/unregisterCreditCard`; `/funds/add` |
|
|
129
|
+
| Status | `Tango::Api::Resources::Status` | `get` | GET `/status` |
|
|
130
|
+
| Exchange Rates | `Tango::Api::Resources::ExchangeRates` | `get(params = {})`, `get_for_utid(utid, params = {})` | GET `/exchangeRates`; GET `/exchangeRates/{utid}` |
|
|
131
|
+
| Choice Products | `Tango::Api::Resources::ChoiceProducts` | `get(params = {})`, `get_for_utid(utid)`, `catalog(choice_product_utid, params = {})` | GET `/choiceProducts`; GET `/choiceProducts/{utid}`; GET `/choiceProducts/{utid}/catalog` |
|
|
132
|
+
|
|
133
|
+
### Smoke test
|
|
134
|
+
|
|
135
|
+
Run a basic end-to-end check against your Tango environment.
|
|
136
|
+
|
|
137
|
+
Required:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
export TANGO_URL=https://integration-api.tangocard.com/raas/v2
|
|
141
|
+
export TANGO_PLATFORM=QAPlatform2
|
|
142
|
+
export TANGO_KEY=YOUR_BASIC_KEY
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Run:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
bin/smoke
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Optional flags:
|
|
152
|
+
|
|
153
|
+
- SMOKE_RUN_STATUS=true: call `/status` (may be 404 on RAAS v2)
|
|
154
|
+
- SMOKE_RUN_EXCHANGE_RATES=true: call `/exchangerates`
|
|
155
|
+
- TANGO_RUN_MUTATIONS=true: enable POST flows (create/update/delete)
|
|
156
|
+
|
|
157
|
+
Optional data variables (used when present):
|
|
158
|
+
|
|
159
|
+
- TANGO_ACCOUNT_IDENTIFIER, TANGO_CUSTOMER_IDENTIFIER
|
|
160
|
+
- TANGO_ORDER_IDENTIFIER, TANGO_ORDER_AMOUNT (default 100)
|
|
161
|
+
- TANGO_UTID or TANGO_CHOICE_PRODUCT_UTID for order/choice product flows
|
|
162
|
+
- TANGO_RECIPIENT_EMAIL
|
|
163
|
+
- Low balance alerts:
|
|
164
|
+
- TANGO_LOW_BALANCE_ALERT_BODY (JSON) or individual fields:
|
|
165
|
+
- TANGO_LOW_BALANCE_ALERT_DISPLAY_NAME, TANGO_LOW_BALANCE_ALERT_THRESHOLD, TANGO_LOW_BALANCE_ALERT_EMAIL
|
|
166
|
+
|
|
167
|
+
At the end, the script prints a summary of passed/failed checks with statuses and messages.
|
|
168
|
+
|
|
169
|
+
## Development
|
|
170
|
+
|
|
171
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
172
|
+
|
|
173
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
|
174
|
+
|
|
175
|
+
## Contributing
|
|
176
|
+
|
|
177
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/Engagedly-Inc/tango-api-client.
|
|
178
|
+
|
|
179
|
+
## License
|
|
180
|
+
|
|
181
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "base64"
|
|
4
|
+
|
|
5
|
+
module Tango
|
|
6
|
+
module Api
|
|
7
|
+
module Auth
|
|
8
|
+
# Basic authentication helper for Tango API.
|
|
9
|
+
class Basic
|
|
10
|
+
def initialize(platform:, key:)
|
|
11
|
+
@platform = platform
|
|
12
|
+
@key = key
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def apply(headers)
|
|
16
|
+
token = ::Base64.strict_encode64("#{@platform}:#{@key}")
|
|
17
|
+
headers["Authorization"] = "Basic #{token}"
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "uri"
|
|
5
|
+
require "json"
|
|
6
|
+
require "logger"
|
|
7
|
+
|
|
8
|
+
module Tango
|
|
9
|
+
module Api
|
|
10
|
+
module Auth
|
|
11
|
+
# OAuth2 client credentials flow helper that injects Bearer tokens.
|
|
12
|
+
class OAuth2
|
|
13
|
+
Token = Struct.new(:access_token, :expires_at)
|
|
14
|
+
|
|
15
|
+
def initialize(token_url:, client_id:, client_secret:, scope: nil, logger: nil)
|
|
16
|
+
@token_url = token_url
|
|
17
|
+
@client_id = client_id
|
|
18
|
+
@client_secret = client_secret
|
|
19
|
+
@scope = scope
|
|
20
|
+
@logger = logger || (defined?(Rails) ? Rails.logger : Logger.new($stdout))
|
|
21
|
+
@mutex = Mutex.new
|
|
22
|
+
@token = nil
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def apply(headers)
|
|
26
|
+
token = fetch_token
|
|
27
|
+
headers["Authorization"] = "Bearer #{token}"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def fetch_token
|
|
33
|
+
@mutex.synchronize do
|
|
34
|
+
return @token.access_token if @token && Time.now < (@token.expires_at - 60)
|
|
35
|
+
|
|
36
|
+
uri = URI(@token_url)
|
|
37
|
+
req = Net::HTTP::Post.new(uri)
|
|
38
|
+
req["Content-Type"] = "application/x-www-form-urlencoded"
|
|
39
|
+
body = {
|
|
40
|
+
grant_type: "client_credentials",
|
|
41
|
+
client_id: @client_id,
|
|
42
|
+
client_secret: @client_secret
|
|
43
|
+
}
|
|
44
|
+
body[:scope] = @scope if @scope
|
|
45
|
+
req.body = URI.encode_www_form(body)
|
|
46
|
+
|
|
47
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
48
|
+
http.use_ssl = uri.scheme == "https"
|
|
49
|
+
http.read_timeout = 10
|
|
50
|
+
http.open_timeout = 5
|
|
51
|
+
res = http.request(req)
|
|
52
|
+
|
|
53
|
+
unless res.is_a?(Net::HTTPSuccess)
|
|
54
|
+
@logger.error "OAuth2 token request failed: #{res.code} #{res.body}"
|
|
55
|
+
raise StandardError, "OAuth2 token request failed with #{res.code}"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
data = JSON.parse(res.body)
|
|
59
|
+
expires_in = (data["expires_in"] || 3600).to_i
|
|
60
|
+
@token = Token.new(data["access_token"], Time.now + expires_in)
|
|
61
|
+
@token.access_token
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "faraday"
|
|
4
|
+
require "faraday/follow_redirects"
|
|
5
|
+
require "faraday/retry"
|
|
6
|
+
require "json"
|
|
7
|
+
|
|
8
|
+
require_relative "client/version"
|
|
9
|
+
require_relative "../api"
|
|
10
|
+
require_relative "errors"
|
|
11
|
+
require_relative "auth/basic"
|
|
12
|
+
require_relative "auth/oauth2"
|
|
13
|
+
require_relative "resources/base"
|
|
14
|
+
require_relative "resources/catalogs"
|
|
15
|
+
require_relative "resources/accounts"
|
|
16
|
+
require_relative "resources/orders"
|
|
17
|
+
require_relative "resources/funds"
|
|
18
|
+
require_relative "resources/customers"
|
|
19
|
+
require_relative "resources/status"
|
|
20
|
+
require_relative "resources/exchange_rates"
|
|
21
|
+
require_relative "resources/choice_products"
|
|
22
|
+
|
|
23
|
+
module Tango
|
|
24
|
+
module Api
|
|
25
|
+
# Public client to interact with Tango API resources via Faraday.
|
|
26
|
+
class Client
|
|
27
|
+
def initialize(config = Tango::Api.configuration)
|
|
28
|
+
@config =
|
|
29
|
+
case config
|
|
30
|
+
when String
|
|
31
|
+
cfg = Tango::Api.configuration.dup
|
|
32
|
+
cfg.base_url = config
|
|
33
|
+
cfg
|
|
34
|
+
when Hash
|
|
35
|
+
cfg = Tango::Api.configuration.dup
|
|
36
|
+
config.each do |key, value|
|
|
37
|
+
writer = "#{key}="
|
|
38
|
+
cfg.public_send(writer, value) if cfg.respond_to?(writer)
|
|
39
|
+
end
|
|
40
|
+
cfg
|
|
41
|
+
else
|
|
42
|
+
config
|
|
43
|
+
end
|
|
44
|
+
raise ArgumentError, "Tango::Api.configuration.base_url is required" if @config.base_url.to_s.strip.empty?
|
|
45
|
+
|
|
46
|
+
@conn = build_connection
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def catalogs
|
|
50
|
+
Resources::Catalogs.new(@conn)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def accounts
|
|
54
|
+
Resources::Accounts.new(@conn)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def orders
|
|
58
|
+
Resources::Orders.new(@conn)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def funds
|
|
62
|
+
Resources::Funds.new(@conn)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def customers
|
|
66
|
+
Resources::Customers.new(@conn)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def status
|
|
70
|
+
Resources::Status.new(@conn)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def exchange_rates
|
|
74
|
+
Resources::ExchangeRates.new(@conn)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def choice_products
|
|
78
|
+
Resources::ChoiceProducts.new(@conn)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
|
|
83
|
+
def build_connection
|
|
84
|
+
Faraday.new(url: @config.base_url, headers: default_headers) do |f|
|
|
85
|
+
f.request :url_encoded
|
|
86
|
+
f.response :follow_redirects
|
|
87
|
+
f.options.timeout = @config.timeout
|
|
88
|
+
f.options.open_timeout = @config.open_timeout
|
|
89
|
+
|
|
90
|
+
f.request :retry, retry_options
|
|
91
|
+
|
|
92
|
+
f.response :raise_error
|
|
93
|
+
f.adapter Faraday.default_adapter
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def default_headers
|
|
98
|
+
headers = (@config.default_headers || {}).dup
|
|
99
|
+
apply_auth(headers)
|
|
100
|
+
headers
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def apply_auth(headers)
|
|
104
|
+
return unless @config.auth
|
|
105
|
+
|
|
106
|
+
@config.auth.apply(headers)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def retry_options
|
|
110
|
+
max = @config.retries[:max] || 2
|
|
111
|
+
base = @config.retries[:base] || 0.5
|
|
112
|
+
max_delay = @config.retries[:max_delay] || 5
|
|
113
|
+
{
|
|
114
|
+
max: max,
|
|
115
|
+
interval: base,
|
|
116
|
+
interval_randomness: 0.5,
|
|
117
|
+
backoff_factor: 2,
|
|
118
|
+
max_interval: max_delay,
|
|
119
|
+
retry_statuses: [429, 500, 502, 503, 504],
|
|
120
|
+
methods: %i[get head options]
|
|
121
|
+
}
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Namespace for resource-specific wrappers.
|
|
126
|
+
module Resources; end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tango
|
|
4
|
+
module Api
|
|
5
|
+
module Errors
|
|
6
|
+
# Base HTTP error carrying status, body, request id and optional i18n key.
|
|
7
|
+
class HttpError < StandardError
|
|
8
|
+
attr_reader :status, :body, :request_id, :i18n_key
|
|
9
|
+
|
|
10
|
+
def initialize(message, status:, body: nil, request_id: nil, i18n_key: nil)
|
|
11
|
+
super(message)
|
|
12
|
+
@status = status
|
|
13
|
+
@body = body
|
|
14
|
+
@request_id = request_id
|
|
15
|
+
@i18n_key = i18n_key
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
class Unauthorized < HttpError; end
|
|
20
|
+
class Forbidden < HttpError; end
|
|
21
|
+
class NotFound < HttpError; end
|
|
22
|
+
class RateLimited < HttpError; end
|
|
23
|
+
class ServerError < HttpError; end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tango
|
|
4
|
+
module Api
|
|
5
|
+
module Resources
|
|
6
|
+
# Client for Accounts endpoints.
|
|
7
|
+
class Accounts < Base
|
|
8
|
+
# GET /customers/{customerIdentifier}/accounts
|
|
9
|
+
def list_for_customer(customer_identifier, params = {})
|
|
10
|
+
get_json("customers/#{customer_identifier}/accounts", params)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# GET /accounts/{accountIdentifier}
|
|
14
|
+
def get(account_identifier)
|
|
15
|
+
get_json("accounts/#{account_identifier}")
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# POST /customers/{customerIdentifier}/accounts
|
|
19
|
+
def create(customer_identifier, body)
|
|
20
|
+
post_json("customers/#{customer_identifier}/accounts", body)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# PATCH /customers/{customerIdentifier}/accounts/{accountIdentifier}
|
|
24
|
+
def update_under_customer(customer_identifier, account_identifier, body)
|
|
25
|
+
patch_json("customers/#{customer_identifier}/accounts/#{account_identifier}", body)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Low balance alerts
|
|
29
|
+
# GET /customers/{customerIdentifier}/accounts/{accountIdentifier}/lowbalance
|
|
30
|
+
def list_low_balance_alerts(customer_identifier, account_identifier)
|
|
31
|
+
get_json("customers/#{customer_identifier}/accounts/#{account_identifier}/lowbalance")
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# POST /customers/{customerIdentifier}/accounts/{accountIdentifier}/lowbalance
|
|
35
|
+
def set_low_balance_alert(customer_identifier, account_identifier, body)
|
|
36
|
+
post_json("customers/#{customer_identifier}/accounts/#{account_identifier}/lowbalance", body)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# GET /customers/{customerIdentifier}/accounts/{accountIdentifier}/lowbalance/{alertIdentifier}
|
|
40
|
+
def get_low_balance_alert(customer_identifier, account_identifier, alert_identifier)
|
|
41
|
+
path = "customers/#{customer_identifier}/accounts/#{account_identifier}/lowbalance/#{alert_identifier}"
|
|
42
|
+
get_json(path)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# PATCH /customers/{customerIdentifier}/accounts/{accountIdentifier}/lowbalance/{alertIdentifier}
|
|
46
|
+
def update_low_balance_alert(customer_identifier, account_identifier, alert_identifier, body)
|
|
47
|
+
path = "customers/#{customer_identifier}/accounts/#{account_identifier}/lowbalance/#{alert_identifier}"
|
|
48
|
+
patch_json(path, body)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# DELETE /customers/{customerIdentifier}/accounts/{accountIdentifier}/lowbalance/{alertIdentifier}
|
|
52
|
+
def delete_low_balance_alert(customer_identifier, account_identifier, alert_identifier)
|
|
53
|
+
path = "customers/#{customer_identifier}/accounts/#{account_identifier}/lowbalance/#{alert_identifier}"
|
|
54
|
+
delete_json(path)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Tango
|
|
6
|
+
module Api
|
|
7
|
+
module Resources
|
|
8
|
+
# Base resource providing JSON parsing and Faraday error mapping.
|
|
9
|
+
class Base
|
|
10
|
+
def initialize(conn)
|
|
11
|
+
@conn = conn
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def get_json(path, params = nil)
|
|
17
|
+
response = params ? @conn.get(path, params) : @conn.get(path)
|
|
18
|
+
unless (200..299).cover?(response.status)
|
|
19
|
+
error = Struct.new(:response).new({ status: response.status, body: response.body,
|
|
20
|
+
headers: response.headers })
|
|
21
|
+
raise map_faraday_error(error)
|
|
22
|
+
end
|
|
23
|
+
parse_json_response(response)
|
|
24
|
+
rescue Faraday::Error => e
|
|
25
|
+
raise map_faraday_error(e)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def post_json(path, body, extra_headers = nil)
|
|
29
|
+
headers = { "Content-Type" => "application/json" }
|
|
30
|
+
headers.merge!(extra_headers) if extra_headers
|
|
31
|
+
response = @conn.post(path, body.to_json, headers)
|
|
32
|
+
unless (200..299).cover?(response.status)
|
|
33
|
+
error = Struct.new(:response).new({ status: response.status, body: response.body,
|
|
34
|
+
headers: response.headers })
|
|
35
|
+
raise map_faraday_error(error)
|
|
36
|
+
end
|
|
37
|
+
parse_json_response(response)
|
|
38
|
+
rescue Faraday::Error => e
|
|
39
|
+
raise map_faraday_error(e)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def patch_json(path, body, extra_headers = nil)
|
|
43
|
+
headers = { "Content-Type" => "application/json" }
|
|
44
|
+
headers.merge!(extra_headers) if extra_headers
|
|
45
|
+
response = @conn.patch(path, body.to_json, headers)
|
|
46
|
+
unless (200..299).cover?(response.status)
|
|
47
|
+
error = Struct.new(:response).new({ status: response.status, body: response.body,
|
|
48
|
+
headers: response.headers })
|
|
49
|
+
raise map_faraday_error(error)
|
|
50
|
+
end
|
|
51
|
+
parse_json_response(response)
|
|
52
|
+
rescue Faraday::Error => e
|
|
53
|
+
raise map_faraday_error(e)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def delete_json(path)
|
|
57
|
+
response = @conn.delete(path)
|
|
58
|
+
ensure_success_or_raise(response)
|
|
59
|
+
body_text = response.body.to_s
|
|
60
|
+
return {} if body_text.strip.empty?
|
|
61
|
+
|
|
62
|
+
parse_json_response(response)
|
|
63
|
+
rescue Faraday::Error => e
|
|
64
|
+
raise map_faraday_error(e)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def ensure_success_or_raise(response)
|
|
68
|
+
return if (200..299).cover?(response.status)
|
|
69
|
+
|
|
70
|
+
error = Struct.new(:response).new({ status: response.status, body: response.body,
|
|
71
|
+
headers: response.headers })
|
|
72
|
+
raise map_faraday_error(error)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def parse_json_response(res)
|
|
76
|
+
JSON.parse(res.body)
|
|
77
|
+
rescue JSON::ParserError
|
|
78
|
+
{ "raw" => res.body }
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def map_faraday_error(error)
|
|
82
|
+
status = error.response&.dig(:status)
|
|
83
|
+
body = error.response&.dig(:body)
|
|
84
|
+
request_id = error.response&.dig(:headers, "x-request-id")
|
|
85
|
+
message, i18n_key = extract_error_message(body)
|
|
86
|
+
|
|
87
|
+
case status
|
|
88
|
+
when 401 then Errors::Unauthorized.new(message, status: status, body: body, request_id: request_id,
|
|
89
|
+
i18n_key: i18n_key)
|
|
90
|
+
when 403 then Errors::Forbidden.new(message, status: status, body: body, request_id: request_id,
|
|
91
|
+
i18n_key: i18n_key)
|
|
92
|
+
when 404 then Errors::NotFound.new(message, status: status, body: body, request_id: request_id,
|
|
93
|
+
i18n_key: i18n_key)
|
|
94
|
+
when 429 then Errors::RateLimited.new(message, status: status, body: body, request_id: request_id,
|
|
95
|
+
i18n_key: i18n_key)
|
|
96
|
+
else Errors::ServerError.new(message, status: status || 500, body: body, request_id: request_id,
|
|
97
|
+
i18n_key: i18n_key)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def extract_error_message(body)
|
|
102
|
+
json = begin
|
|
103
|
+
JSON.parse(body)
|
|
104
|
+
rescue StandardError
|
|
105
|
+
nil
|
|
106
|
+
end
|
|
107
|
+
return ["API Error", nil] unless json.is_a?(Hash)
|
|
108
|
+
|
|
109
|
+
[json["message"] || "API Error", json["i18nKey"]]
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tango
|
|
4
|
+
module Api
|
|
5
|
+
module Resources
|
|
6
|
+
# Client for Catalogs endpoints.
|
|
7
|
+
class Catalogs < Base
|
|
8
|
+
# GET /catalogs with flexible params
|
|
9
|
+
# Arrays are encoded as repeated keys
|
|
10
|
+
def get(params = {})
|
|
11
|
+
query = encode_arrays(params)
|
|
12
|
+
get_json("catalogs", query)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Convenience: filter by brandKey
|
|
16
|
+
def get_by_brand_key(brand_key, params = {})
|
|
17
|
+
get(params.merge(brandKey: brand_key))
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Convenience: filter by brandName
|
|
21
|
+
def get_by_brand_name(brand_name, params = {})
|
|
22
|
+
get(params.merge(brandName: brand_name))
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Convenience: filter by utid
|
|
26
|
+
def get_by_utid(utid, params = {})
|
|
27
|
+
get(params.merge(utid: utid))
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Convenience: filter by rewardName
|
|
31
|
+
def get_by_reward_name(reward_name, params = {})
|
|
32
|
+
get(params.merge(rewardName: reward_name))
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def encode_arrays(params)
|
|
38
|
+
params.transform_values do |v|
|
|
39
|
+
if v.is_a?(Array)
|
|
40
|
+
# Faraday will expand arrays as repeated keys when params_encoder is nil
|
|
41
|
+
end
|
|
42
|
+
v
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# encode_arrays intentionally returns arrays as-is to let Faraday repeat keys
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tango
|
|
4
|
+
module Api
|
|
5
|
+
module Resources
|
|
6
|
+
# Resource for Choice Products catalog and details.
|
|
7
|
+
class ChoiceProducts < Base
|
|
8
|
+
# GET /choiceProducts
|
|
9
|
+
# Params may include: rewardName, currencyCode, countries: []
|
|
10
|
+
def get(params = {})
|
|
11
|
+
get_json("choiceProducts", params)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# GET /choiceProducts/{utid}
|
|
15
|
+
def get_for_utid(utid)
|
|
16
|
+
get_json("choiceProducts/#{utid}")
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# GET /choiceProducts/{choiceProductUtid}/catalog
|
|
20
|
+
# Supports typical catalog query params, including filters and verbosity flags.
|
|
21
|
+
def catalog(choice_product_utid, params = {})
|
|
22
|
+
get_json("choiceProducts/#{choice_product_utid}/catalog", params)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tango
|
|
4
|
+
module Api
|
|
5
|
+
module Resources
|
|
6
|
+
# Client for Customers endpoints.
|
|
7
|
+
class Customers < Base
|
|
8
|
+
# GET /customers
|
|
9
|
+
def list(params = {})
|
|
10
|
+
get_json("customers", params)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# GET /customers/{customerIdentifier}
|
|
14
|
+
def get(customer_identifier)
|
|
15
|
+
get_json("customers/#{customer_identifier}")
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# POST /customers
|
|
19
|
+
def create(body)
|
|
20
|
+
post_json("customers", body)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# GET /customers/{customerIdentifier}/accounts
|
|
24
|
+
def accounts(customer_identifier, params = {})
|
|
25
|
+
get_json("customers/#{customer_identifier}/accounts", params)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tango
|
|
4
|
+
module Api
|
|
5
|
+
module Resources
|
|
6
|
+
# Resource for exchange rates across catalog items and currencies.
|
|
7
|
+
class ExchangeRates < Base
|
|
8
|
+
# GET /exchangerates
|
|
9
|
+
# Example params: { currency: 'USD', country: 'US' }
|
|
10
|
+
def get(params = {})
|
|
11
|
+
get_json("exchangerates", params)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# GET /exchangerates/{utid}
|
|
15
|
+
# Fetch exchange rate info for a specific catalog item by UTID.
|
|
16
|
+
def get_for_utid(utid, params = {})
|
|
17
|
+
get_json("exchangerates/#{utid}", params)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tango
|
|
4
|
+
module Api
|
|
5
|
+
module Resources
|
|
6
|
+
# Client for Funds endpoints.
|
|
7
|
+
class Funds < Base
|
|
8
|
+
# GET /creditCards
|
|
9
|
+
# Optional params: { customerIdentifier:, accountIdentifier:, ... } (varies by tenant)
|
|
10
|
+
def list_cards(params = {})
|
|
11
|
+
get_json("creditCards", params)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# GET /creditCards/{creditCardToken}
|
|
15
|
+
def get_card(credit_card_token)
|
|
16
|
+
get_json("creditCards/#{credit_card_token}")
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# POST /creditCards
|
|
20
|
+
def register_card(body)
|
|
21
|
+
post_json("creditCards", body)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# POST /creditCardUnregisters
|
|
25
|
+
# Body: { customerIdentifier:, accountIdentifier:, creditCardToken: }
|
|
26
|
+
def unregister_card(body)
|
|
27
|
+
post_json("creditCardUnregisters", body)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# POST /creditCardDeposits
|
|
31
|
+
# Body requires: customerIdentifier, accountIdentifier, externalRefID, creditCardToken, amount
|
|
32
|
+
def create_credit_card_deposit(body)
|
|
33
|
+
post_json("creditCardDeposits", body)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# GET /creditCardDeposits/{externalRefID}
|
|
37
|
+
def get_credit_card_deposit(external_ref_id)
|
|
38
|
+
get_json("creditCardDeposits/#{external_ref_id}")
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tango
|
|
4
|
+
module Api
|
|
5
|
+
module Resources
|
|
6
|
+
# Client for Orders endpoints.
|
|
7
|
+
class Orders < Base
|
|
8
|
+
# POST /orders
|
|
9
|
+
def create(body, idempotency_key: nil)
|
|
10
|
+
headers = idempotency_key ? { "Idempotency-Key" => idempotency_key } : nil
|
|
11
|
+
post_json("orders", body, headers)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# GET /orders/{referenceOrderID}
|
|
15
|
+
def get(order_id)
|
|
16
|
+
get_json("orders/#{order_id}")
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# GET /orders
|
|
20
|
+
def list(params = {})
|
|
21
|
+
get_json("orders", params)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# POST /orders/{order_id}/resend
|
|
25
|
+
def resend(order_id, idempotency_key: nil)
|
|
26
|
+
headers = idempotency_key ? { "Idempotency-Key" => idempotency_key } : nil
|
|
27
|
+
post_json("orders/#{order_id}/resend", {}, headers)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
data/lib/tango/api.rb
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support"
|
|
4
|
+
require "active_support/core_ext/object/blank"
|
|
5
|
+
require "logger"
|
|
6
|
+
|
|
7
|
+
module Tango
|
|
8
|
+
# Root namespace for the Tango API Ruby client.
|
|
9
|
+
module Api
|
|
10
|
+
# Configuration entry point for the Tango API client.
|
|
11
|
+
class << self
|
|
12
|
+
def configure
|
|
13
|
+
yield(configuration)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def configuration
|
|
17
|
+
@configuration ||= Configuration.new
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Holds client-wide configuration such as timeouts, retries and auth.
|
|
22
|
+
class Configuration
|
|
23
|
+
attr_accessor :base_url, :timeout, :open_timeout, :retries, :logger, :default_headers, :auth
|
|
24
|
+
|
|
25
|
+
def initialize
|
|
26
|
+
@base_url = ENV.fetch("TANGO_URL", "https://integration-api.tangocard.com/raas/v2").to_s.presence
|
|
27
|
+
@timeout = 45
|
|
28
|
+
@open_timeout = 5
|
|
29
|
+
@retries = { max: 2, base: 0.5, max_delay: 5 }
|
|
30
|
+
@logger = defined?(Rails) ? Rails.logger : Logger.new($stdout)
|
|
31
|
+
@default_headers = { "Accept" => "application/json" }
|
|
32
|
+
@auth = nil
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Tango
|
|
2
|
+
module Api
|
|
3
|
+
module Client
|
|
4
|
+
VERSION: String
|
|
5
|
+
# See the writing guide of rbs: https://github.com/ruby/rbs#guides
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
class Client
|
|
9
|
+
def initialize: (?Configuration) -> void
|
|
10
|
+
def catalogs: Resources::Catalogs
|
|
11
|
+
def accounts: Resources::Accounts
|
|
12
|
+
def orders: Resources::Orders
|
|
13
|
+
def funds: Resources::Funds
|
|
14
|
+
def customers: Resources::Customers
|
|
15
|
+
def status: Resources::Status
|
|
16
|
+
def exchange_rates: Resources::ExchangeRates
|
|
17
|
+
def choice_products: Resources::ChoiceProducts
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: tango-api-client
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Puneeth Kumar DN
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2025-11-12 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: activesupport
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '6.0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '6.0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: faraday
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '2.7'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '2.7'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: faraday-follow_redirects
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '0.3'
|
|
48
|
+
type: :runtime
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ">="
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '0.3'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: faraday-retry
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '2.0'
|
|
62
|
+
type: :runtime
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">="
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '2.0'
|
|
69
|
+
description: Client for Tango API with Basic/OAuth2, flexible params, retries and
|
|
70
|
+
structured errors.
|
|
71
|
+
email:
|
|
72
|
+
- puneeth@engagedly.com
|
|
73
|
+
executables: []
|
|
74
|
+
extensions: []
|
|
75
|
+
extra_rdoc_files: []
|
|
76
|
+
files:
|
|
77
|
+
- ".rspec"
|
|
78
|
+
- CHANGELOG.md
|
|
79
|
+
- LICENSE.txt
|
|
80
|
+
- README.md
|
|
81
|
+
- Rakefile
|
|
82
|
+
- lib/tango/api.rb
|
|
83
|
+
- lib/tango/api/auth/basic.rb
|
|
84
|
+
- lib/tango/api/auth/oauth2.rb
|
|
85
|
+
- lib/tango/api/client.rb
|
|
86
|
+
- lib/tango/api/client/version.rb
|
|
87
|
+
- lib/tango/api/errors.rb
|
|
88
|
+
- lib/tango/api/resources/accounts.rb
|
|
89
|
+
- lib/tango/api/resources/base.rb
|
|
90
|
+
- lib/tango/api/resources/catalogs.rb
|
|
91
|
+
- lib/tango/api/resources/choice_products.rb
|
|
92
|
+
- lib/tango/api/resources/customers.rb
|
|
93
|
+
- lib/tango/api/resources/exchange_rates.rb
|
|
94
|
+
- lib/tango/api/resources/funds.rb
|
|
95
|
+
- lib/tango/api/resources/orders.rb
|
|
96
|
+
- lib/tango/api/resources/status.rb
|
|
97
|
+
- sig/tango/api/client.rbs
|
|
98
|
+
homepage: https://github.com/Engagedly-Inc/tango-api-client
|
|
99
|
+
licenses:
|
|
100
|
+
- MIT
|
|
101
|
+
metadata:
|
|
102
|
+
homepage_uri: https://github.com/Engagedly-Inc/tango-api-client
|
|
103
|
+
source_code_uri: https://github.com/Engagedly-Inc/tango-api-client
|
|
104
|
+
changelog_uri: https://github.com/Engagedly-Inc/tango-api-client/blob/master/CHANGELOG.md
|
|
105
|
+
rubygems_mfa_required: 'true'
|
|
106
|
+
post_install_message:
|
|
107
|
+
rdoc_options: []
|
|
108
|
+
require_paths:
|
|
109
|
+
- lib
|
|
110
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
111
|
+
requirements:
|
|
112
|
+
- - ">="
|
|
113
|
+
- !ruby/object:Gem::Version
|
|
114
|
+
version: '3.0'
|
|
115
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
116
|
+
requirements:
|
|
117
|
+
- - ">="
|
|
118
|
+
- !ruby/object:Gem::Version
|
|
119
|
+
version: '0'
|
|
120
|
+
requirements: []
|
|
121
|
+
rubygems_version: 3.3.7
|
|
122
|
+
signing_key:
|
|
123
|
+
specification_version: 4
|
|
124
|
+
summary: Ruby client for Tango (BHN) API with dual auth and robust errors.
|
|
125
|
+
test_files: []
|