opentrust 0.1.1
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/LICENSE +21 -0
- data/README.md +42 -0
- data/lib/opentrust/client.rb +100 -0
- data/lib/opentrust/errors.rb +21 -0
- data/lib/opentrust/version.rb +3 -0
- data/lib/opentrust.rb +3 -0
- metadata +51 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: de6fbba7e9eebcb29951eabb63212c48696ed16e3ee6c0fbf98e4be0f3b8ba9f
|
|
4
|
+
data.tar.gz: 273707de6ff17306d6a8284d02a1d1fef3e85459d5be6f27a81991f5ebfb0571
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 0e60f2550129dace80d3e3e5ae14db4f178301bacea8e470544a3cc6f3b98dbf29e0c1a7c1a8a124a76b1ff5317b64afb8106724560d398aca8e5e2a3f5302a6
|
|
7
|
+
data.tar.gz: e7e639909140db87ebd47b66df031e5805218420a9a5be1ded69c48d0b8c4eb1b75d2f4f346d5924ac35e7aa32df39242edd6483752c765da7abd4632c9d102f
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Parity Cube Pvt Ltd and OpenTrust contributors
|
|
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 all
|
|
13
|
+
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 THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# opentrust (Ruby)
|
|
2
|
+
|
|
3
|
+
Check mobile numbers against experiences shared by other Indian merchants, and share your own. No dependencies.
|
|
4
|
+
|
|
5
|
+
```ruby
|
|
6
|
+
# Gemfile
|
|
7
|
+
gem "opentrust"
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
```ruby
|
|
11
|
+
require "opentrust"
|
|
12
|
+
|
|
13
|
+
client = OpenTrust::Client.new(api_key: ENV["OPENTRUST_API_KEY"]) # or just set OPENTRUST_API_KEY
|
|
14
|
+
|
|
15
|
+
result = client.lookup("98765 43210") # uses 1 check
|
|
16
|
+
result.reported? # => true / false
|
|
17
|
+
result.report_count # => 2
|
|
18
|
+
result.reports.each { |r| puts "#{r.reason_code}: #{r.notes}" }
|
|
19
|
+
result.notice # keep this in front of anyone who sees the result
|
|
20
|
+
result.checks_remaining
|
|
21
|
+
|
|
22
|
+
client.lookup_many(%w[9876543210 9000000001]) # up to 100, all or nothing
|
|
23
|
+
client.report(phone: "9876543210", reason_code: "cod_refusal", notes: "Refused 3 COD orders in March")
|
|
24
|
+
client.me # => { "checks_remaining" => ..., ... }
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Please don't block checkout on OpenTrust
|
|
28
|
+
|
|
29
|
+
Results are unverified observations from individual merchants. Use them as one input, have a person review decisions that matter, and fail open if OpenTrust is unreachable:
|
|
30
|
+
|
|
31
|
+
```ruby
|
|
32
|
+
begin
|
|
33
|
+
result = client.lookup(order.phone)
|
|
34
|
+
flag_for_review(order) if result.reported?
|
|
35
|
+
rescue OpenTrust::Error => e
|
|
36
|
+
Rails.logger.warn("OpenTrust unavailable: #{e.message}") # carry on
|
|
37
|
+
end
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Errors: `AuthenticationError` (401), `NoChecksError` (402, write to us and we'll raise your limit), `NotApprovedError` (403), `DuplicateReportError` (409), `InvalidRequestError` (422), `RateLimitError` (429), `ServerError` (5xx), `ConnectionError` (network/timeout). All inherit from `OpenTrust::Error` and carry `status` and `code`.
|
|
41
|
+
|
|
42
|
+
Timeout defaults to 5 seconds (`timeout:`). Docs: https://opentrust.in/docs
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
require "net/http"
|
|
3
|
+
require "uri"
|
|
4
|
+
|
|
5
|
+
module OpenTrust
|
|
6
|
+
Report = Struct.new(:reason_code, :reason, :notes, :reported_at, :reporter_verified, keyword_init: true)
|
|
7
|
+
|
|
8
|
+
# The result of checking one number. Reports are unverified observations from individual merchants,
|
|
9
|
+
# not a finding about anyone. Use them as one input among others.
|
|
10
|
+
Lookup = Struct.new(:phone_last4, :report_count, :reports, :notice, :checks_remaining, keyword_init: true) do
|
|
11
|
+
def reported?
|
|
12
|
+
report_count.to_i.positive?
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
Receipt = Struct.new(:id, :phone_last4, keyword_init: true)
|
|
17
|
+
|
|
18
|
+
class Client
|
|
19
|
+
DEFAULT_BASE_URL = "https://opentrust.in/api/v1".freeze
|
|
20
|
+
|
|
21
|
+
# adapter: optional callable (method, url, headers, body) -> [status, body_string], mainly for tests.
|
|
22
|
+
def initialize(api_key: ENV["OPENTRUST_API_KEY"], base_url: ENV.fetch("OPENTRUST_BASE_URL", DEFAULT_BASE_URL), timeout: 5, adapter: nil)
|
|
23
|
+
raise ArgumentError, "api_key is required (or set OPENTRUST_API_KEY)" if api_key.to_s.empty?
|
|
24
|
+
|
|
25
|
+
@api_key = api_key
|
|
26
|
+
@base_url = base_url.chomp("/")
|
|
27
|
+
@timeout = timeout
|
|
28
|
+
@adapter = adapter
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Check one number. Uses one check from your allowance.
|
|
32
|
+
def lookup(phone)
|
|
33
|
+
lookup_from(request(:post, "/lookups", phone: phone))
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Check up to 100 numbers. All or nothing: if you don't have enough checks, none are used.
|
|
37
|
+
def lookup_many(phones)
|
|
38
|
+
body = request(:post, "/lookups/batch", phones: Array(phones))
|
|
39
|
+
body.fetch("results").map { |r| lookup_from(r.merge("notice" => body["notice"], "checks_remaining" => body["checks_remaining"])) }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Share an experience with a number. Optional, and doesn't change your allowance.
|
|
43
|
+
def report(phone:, reason_code:, notes: nil)
|
|
44
|
+
body = request(:post, "/reports", { phone: phone, reason_code: reason_code, notes: notes }.compact)
|
|
45
|
+
Receipt.new(id: body["id"], phone_last4: body["phone_last4"])
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Your remaining checks and usage, as a Hash.
|
|
49
|
+
def me
|
|
50
|
+
request(:get, "/me")
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def lookup_from(h)
|
|
56
|
+
Lookup.new(
|
|
57
|
+
phone_last4: h["phone_last4"], report_count: h["report_count"], notice: h["notice"], checks_remaining: h["checks_remaining"],
|
|
58
|
+
reports: Array(h["reports"]).map { |r| Report.new(reason_code: r["reason_code"], reason: r["reason"], notes: r["notes"], reported_at: r["reported_at"], reporter_verified: r["reporter_verified"]) }
|
|
59
|
+
)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def request(verb, path, payload = nil)
|
|
63
|
+
headers = { "Authorization" => "Bearer #{@api_key}", "Accept" => "application/json", "User-Agent" => "opentrust-ruby/#{VERSION}" }
|
|
64
|
+
headers["Content-Type"] = "application/json" if payload
|
|
65
|
+
status, text = perform(verb, "#{@base_url}#{path}", headers, payload && JSON.generate(payload))
|
|
66
|
+
body = begin JSON.parse(text.to_s) rescue {} end
|
|
67
|
+
raise_for(status, body) unless (200..299).cover?(status)
|
|
68
|
+
body
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def perform(verb, url, headers, body)
|
|
72
|
+
return @adapter.call(verb, url, headers, body) if @adapter
|
|
73
|
+
|
|
74
|
+
uri = URI(url)
|
|
75
|
+
klass = verb == :get ? Net::HTTP::Get : Net::HTTP::Post
|
|
76
|
+
req = klass.new(uri, headers)
|
|
77
|
+
req.body = body if body
|
|
78
|
+
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https", open_timeout: @timeout, read_timeout: @timeout) { |http| http.request(req) }
|
|
79
|
+
[res.code.to_i, res.body]
|
|
80
|
+
rescue Timeout::Error, SystemCallError, SocketError, OpenSSL::SSL::SSLError, EOFError => e
|
|
81
|
+
raise ConnectionError.new("could not reach OpenTrust: #{e.class}: #{e.message}")
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def raise_for(status, body)
|
|
85
|
+
err = body["error"].is_a?(Hash) ? body["error"] : {}
|
|
86
|
+
message = err["message"] || "OpenTrust returned HTTP #{status}"
|
|
87
|
+
klass = case status
|
|
88
|
+
when 401 then AuthenticationError
|
|
89
|
+
when 402 then NoChecksError
|
|
90
|
+
when 403 then NotApprovedError
|
|
91
|
+
when 409 then DuplicateReportError
|
|
92
|
+
when 422 then InvalidRequestError
|
|
93
|
+
when 429 then RateLimitError
|
|
94
|
+
when 500..599 then ServerError
|
|
95
|
+
else Error
|
|
96
|
+
end
|
|
97
|
+
raise klass.new(message, status: status, code: err["code"])
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module OpenTrust
|
|
2
|
+
# Base class. Rescue this to handle anything that goes wrong talking to OpenTrust.
|
|
3
|
+
class Error < StandardError
|
|
4
|
+
attr_reader :status, :code
|
|
5
|
+
|
|
6
|
+
def initialize(message = nil, status: nil, code: nil)
|
|
7
|
+
super(message)
|
|
8
|
+
@status = status
|
|
9
|
+
@code = code
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
class ConnectionError < Error; end # network failure or timeout
|
|
14
|
+
class AuthenticationError < Error; end # 401: missing or invalid API key
|
|
15
|
+
class NoChecksError < Error; end # 402: allowance used up
|
|
16
|
+
class NotApprovedError < Error; end # 403: account not approved or suspended
|
|
17
|
+
class DuplicateReportError < Error; end # 409: you already shared this number
|
|
18
|
+
class InvalidRequestError < Error; end # 422: bad number, reason code or note
|
|
19
|
+
class RateLimitError < Error; end # 429
|
|
20
|
+
class ServerError < Error; end # 5xx
|
|
21
|
+
end
|
data/lib/opentrust.rb
ADDED
metadata
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: opentrust
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- OpenTrust
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-10-09 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
13
|
+
description: Check mobile numbers against experiences shared by other Indian merchants,
|
|
14
|
+
and share your own. A small, dependency-free client for https://opentrust.in.
|
|
15
|
+
email:
|
|
16
|
+
executables: []
|
|
17
|
+
extensions: []
|
|
18
|
+
extra_rdoc_files: []
|
|
19
|
+
files:
|
|
20
|
+
- LICENSE
|
|
21
|
+
- README.md
|
|
22
|
+
- lib/opentrust.rb
|
|
23
|
+
- lib/opentrust/client.rb
|
|
24
|
+
- lib/opentrust/errors.rb
|
|
25
|
+
- lib/opentrust/version.rb
|
|
26
|
+
homepage: https://opentrust.in/docs
|
|
27
|
+
licenses:
|
|
28
|
+
- MIT
|
|
29
|
+
metadata:
|
|
30
|
+
documentation_uri: https://opentrust.in/docs
|
|
31
|
+
rubygems_mfa_required: 'true'
|
|
32
|
+
post_install_message:
|
|
33
|
+
rdoc_options: []
|
|
34
|
+
require_paths:
|
|
35
|
+
- lib
|
|
36
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '2.7'
|
|
41
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
42
|
+
requirements:
|
|
43
|
+
- - ">="
|
|
44
|
+
- !ruby/object:Gem::Version
|
|
45
|
+
version: '0'
|
|
46
|
+
requirements: []
|
|
47
|
+
rubygems_version: 3.4.10
|
|
48
|
+
signing_key:
|
|
49
|
+
specification_version: 4
|
|
50
|
+
summary: Ruby client for the OpenTrust API
|
|
51
|
+
test_files: []
|