kortana-dev-sdk 1.0.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/lib/kortana/client.rb +131 -0
- data/lib/kortana/errors.rb +23 -0
- data/lib/kortana/modules/banking.rb +61 -0
- data/lib/kortana/modules/blockchain.rb +48 -0
- data/lib/kortana/modules/cards.rb +48 -0
- data/lib/kortana/modules/compliance.rb +61 -0
- data/lib/kortana/modules/contracts.rb +62 -0
- data/lib/kortana/modules/merchants.rb +62 -0
- data/lib/kortana/modules/payments.rb +60 -0
- data/lib/kortana/modules/settlement.rb +22 -0
- data/lib/kortana/modules/wallets.rb +39 -0
- data/lib/kortana/version.rb +3 -0
- data/lib/kortana/webhooks.rb +33 -0
- data/lib/kortana.rb +14 -0
- metadata +85 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 997d5b86ba941ebf76795917ab911e7db813ec8e761fb80f46ed06491d29f968
|
|
4
|
+
data.tar.gz: 10399a24ab1a9a28ab0797b548645cbcf8c5d56eccfc8f2554a34d50f725f048
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: e0f3ad4b65934e278e02de90558c7072a42fe39e4e620c5756e703273b68851fbea01a818e160810ee30ea2a2bba9de70753c512f805b3a35c82c247d39246c7
|
|
7
|
+
data.tar.gz: b2dc9cfd6344c0a9dcb8d7be738a343463340a5185bbfaf0d30dba9b0c1c055580786a76cbc7a32a5a51e91f0e9f36354154c860bb171cfec151152d58f5786b
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
module Kortana
|
|
2
|
+
class Client
|
|
3
|
+
attr_reader :blockchain, :wallets, :contracts, :payments, :merchants,
|
|
4
|
+
:banking, :cards, :compliance, :settlement
|
|
5
|
+
|
|
6
|
+
BASE_URLS = {
|
|
7
|
+
"mainnet" => "https://console.kortana.network/api/v1",
|
|
8
|
+
"testnet" => "https://console.kortana.network/api/v1"
|
|
9
|
+
}.freeze
|
|
10
|
+
|
|
11
|
+
def initialize(api_key:, environment: "mainnet", base_url: nil, timeout: 30, max_retries: 3)
|
|
12
|
+
raise ConfigurationError, "api_key must not be empty" if api_key.nil? || api_key.strip.empty?
|
|
13
|
+
raise ConfigurationError, "environment must be 'mainnet' or 'testnet'" unless %w[mainnet testnet].include?(environment)
|
|
14
|
+
|
|
15
|
+
@api_key = api_key.strip
|
|
16
|
+
@environment = environment
|
|
17
|
+
@base_url = base_url || BASE_URLS[environment]
|
|
18
|
+
@timeout = timeout
|
|
19
|
+
@max_retries = max_retries
|
|
20
|
+
|
|
21
|
+
setup_http_client
|
|
22
|
+
setup_modules
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def get(path, params = {}, headers = {})
|
|
26
|
+
request(:get, path, params, nil, headers)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def post(path, body = nil, headers = {})
|
|
30
|
+
request(:post, path, nil, body, headers)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def put(path, body = nil, headers = {})
|
|
34
|
+
request(:put, path, nil, body, headers)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def delete(path, headers = {})
|
|
38
|
+
request(:delete, path, nil, nil, headers)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def setup_http_client
|
|
44
|
+
@conn = Faraday.new(url: @base_url) do |f|
|
|
45
|
+
f.request :url_encoded
|
|
46
|
+
|
|
47
|
+
f.request :retry, {
|
|
48
|
+
max: @max_retries,
|
|
49
|
+
interval: 1.0,
|
|
50
|
+
backoff_factor: 2.0,
|
|
51
|
+
exceptions: [Faraday::ConnectionFailed, Faraday::TimeoutError]
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
f.options.timeout = @timeout
|
|
55
|
+
f.options.open_timeout = 5
|
|
56
|
+
f.adapter Faraday.default_adapter
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def setup_modules
|
|
61
|
+
require_relative 'modules/blockchain'
|
|
62
|
+
require_relative 'modules/wallets'
|
|
63
|
+
require_relative 'modules/contracts'
|
|
64
|
+
require_relative 'modules/payments'
|
|
65
|
+
require_relative 'modules/merchants'
|
|
66
|
+
require_relative 'modules/banking'
|
|
67
|
+
require_relative 'modules/cards'
|
|
68
|
+
require_relative 'modules/compliance'
|
|
69
|
+
require_relative 'modules/settlement'
|
|
70
|
+
|
|
71
|
+
@blockchain = Modules::Blockchain.new(self)
|
|
72
|
+
@wallets = Modules::Wallets.new(self)
|
|
73
|
+
@contracts = Modules::Contracts.new(self)
|
|
74
|
+
@payments = Modules::Payments.new(self)
|
|
75
|
+
@merchants = Modules::Merchants.new(self)
|
|
76
|
+
@banking = Modules::Banking.new(self)
|
|
77
|
+
@cards = Modules::Cards.new(self)
|
|
78
|
+
@compliance = Modules::Compliance.new(self)
|
|
79
|
+
@settlement = Modules::Settlement.new(self)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def request(method, path, params = nil, body = nil, headers = {})
|
|
83
|
+
# Auto-generate idempotency key for mutations if write operation
|
|
84
|
+
idempotency_key = headers["Idempotency-Key"]
|
|
85
|
+
if %i[post put patch].include?(method) && !idempotency_key
|
|
86
|
+
idempotency_key = "idem_#{SecureRandom.uuid}"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
res = @conn.send(method) do |req|
|
|
90
|
+
req.path = path
|
|
91
|
+
req.params = params if params
|
|
92
|
+
req.headers["Authorization"] = "Bearer #{@api_key}"
|
|
93
|
+
req.headers["User-Agent"] = "kortana-sdk-ruby/1.0.0"
|
|
94
|
+
req.headers["Idempotency-Key"] = idempotency_key if idempotency_key
|
|
95
|
+
req.headers["Content-Type"] = "application/json"
|
|
96
|
+
req.body = body.to_json if body
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
handle_response(res)
|
|
100
|
+
rescue Faraday::Error => e
|
|
101
|
+
raise ServerError.new("Network/Faraday connection failed: #{e.message}")
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def handle_response(res)
|
|
105
|
+
return JSON.parse(res.body) if res.status >= 200 && res.status < 300
|
|
106
|
+
|
|
107
|
+
begin
|
|
108
|
+
err_body = JSON.parse(res.body)
|
|
109
|
+
err_detail = err_body["error"] || {}
|
|
110
|
+
msg = err_detail["message"] || "API returned status #{res.status}"
|
|
111
|
+
code = err_detail["code"]
|
|
112
|
+
request_id = err_detail["request_id"]
|
|
113
|
+
rescue
|
|
114
|
+
msg = "API returned status #{res.status}"
|
|
115
|
+
code = nil
|
|
116
|
+
request_id = nil
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
error_class = case res.status
|
|
120
|
+
when 401 then AuthError
|
|
121
|
+
when 400 then ValidationError
|
|
122
|
+
when 429 then RateLimitError
|
|
123
|
+
when 404 then NotFoundError
|
|
124
|
+
when 409 then ConflictError
|
|
125
|
+
else ServerError
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
raise error_class.new(msg, code: code, request_id: request_id, http_status: res.status)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Kortana
|
|
2
|
+
class Error < StandardError; end
|
|
3
|
+
class ConfigurationError < Error; end
|
|
4
|
+
|
|
5
|
+
class APIError < Error
|
|
6
|
+
attr_reader :code, :request_id, :http_status
|
|
7
|
+
|
|
8
|
+
def initialize(message, code: nil, request_id: nil, http_status: nil)
|
|
9
|
+
super(message)
|
|
10
|
+
@code = code
|
|
11
|
+
@request_id = request_id
|
|
12
|
+
@http_status = http_status
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class AuthError < APIError; end
|
|
17
|
+
class ValidationError < APIError; end
|
|
18
|
+
class RateLimitError < APIError; end
|
|
19
|
+
class NotFoundError < APIError; end
|
|
20
|
+
class ConflictError < APIError; end
|
|
21
|
+
class ServerError < APIError; end
|
|
22
|
+
class WebhookSignatureError < Error; end
|
|
23
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
module Kortana
|
|
2
|
+
module Modules
|
|
3
|
+
class Banking
|
|
4
|
+
def initialize(client)
|
|
5
|
+
@client = client
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def create_customer(name:, email:, phone: nil, address: nil)
|
|
9
|
+
@client.post("/banking/customers", {
|
|
10
|
+
name: name,
|
|
11
|
+
email: email,
|
|
12
|
+
phone: phone,
|
|
13
|
+
address: address
|
|
14
|
+
})
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def get_customer(id)
|
|
18
|
+
@client.get("/banking/customers/#{id}")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def update_customer(id, params)
|
|
22
|
+
@client.put("/banking/customers/#{id}", params)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def list_customers(options = {})
|
|
26
|
+
@client.get("/banking/customers", options)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def create_bank_account(customer_id:, bank_name:, account_number:, routing_number:, account_type: "checking")
|
|
30
|
+
@client.post("/banking/accounts", {
|
|
31
|
+
customerId: customer_id,
|
|
32
|
+
bankName: bank_name,
|
|
33
|
+
accountNumber: account_number,
|
|
34
|
+
routingNumber: routing_number,
|
|
35
|
+
accountType: account_type
|
|
36
|
+
})
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def get_bank_account(id)
|
|
40
|
+
@client.get("/banking/accounts/#{id}")
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def list_bank_accounts(customer_id)
|
|
44
|
+
@client.get("/banking/accounts", { customerId: customer_id })
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def create_transfer(from_address:, to_address:, amount:, currency: "USD")
|
|
48
|
+
@client.post("/banking/transfers", {
|
|
49
|
+
fromAddress: from_address,
|
|
50
|
+
toAddress: to_address,
|
|
51
|
+
amount: amount.to_s,
|
|
52
|
+
currency: currency
|
|
53
|
+
})
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def get_transfer(id)
|
|
57
|
+
@client.get("/banking/transfers/#{id}")
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
module Kortana
|
|
2
|
+
module Modules
|
|
3
|
+
class Blockchain
|
|
4
|
+
def initialize(client)
|
|
5
|
+
@client = client
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def get_block(block_number_or_hash)
|
|
9
|
+
@client.get("/blockchain/blocks/#{block_number_or_hash}")
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def get_transaction(tx_hash)
|
|
13
|
+
@client.get("/blockchain/transactions/#{tx_hash}")
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def get_transaction_receipt(tx_hash)
|
|
17
|
+
@client.get("/blockchain/transactions/#{tx_hash}/receipt")
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def get_gas_price
|
|
21
|
+
res = @client.get("/blockchain/gas-price")
|
|
22
|
+
res["gasPrice"].to_i
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def estimate_gas(params)
|
|
26
|
+
res = @client.post("/blockchain/estimate-gas", params)
|
|
27
|
+
res["gasLimit"].to_i
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def get_network_status
|
|
31
|
+
@client.get("/blockchain/status")
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def get_logs(filter)
|
|
35
|
+
@client.post("/blockchain/logs", filter)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def get_validators
|
|
39
|
+
@client.get("/blockchain/validators")
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def get_token_balance(address, token_address)
|
|
43
|
+
res = @client.get("/blockchain/addresses/#{address}/token-balance", { token: token_address })
|
|
44
|
+
res["balance"].to_i
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
module Kortana
|
|
2
|
+
module Modules
|
|
3
|
+
class Cards
|
|
4
|
+
def initialize(client)
|
|
5
|
+
@client = client
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def create(customer_id: nil, merchant_id: nil, type: "virtual", currency: "DNR", spend_limit: nil, metadata: nil)
|
|
9
|
+
@client.post("/cards", {
|
|
10
|
+
customerId: customer_id,
|
|
11
|
+
merchantId: merchant_id,
|
|
12
|
+
type: type,
|
|
13
|
+
currency: currency,
|
|
14
|
+
spendLimit: spend_limit ? spend_limit.to_s : nil,
|
|
15
|
+
metadata: metadata
|
|
16
|
+
})
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def get(id)
|
|
20
|
+
@client.get("/cards/#{id}")
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def list(options = {})
|
|
24
|
+
@client.get("/cards", options)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def update_controls(id, controls)
|
|
28
|
+
@client.put("/cards/#{id}/controls", controls)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def disable(id)
|
|
32
|
+
@client.post("/cards/#{id}/disable")
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def enable(id)
|
|
36
|
+
@client.post("/cards/#{id}/enable")
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def simulate_transaction(card_id:, amount:, merchant: nil)
|
|
40
|
+
@client.post("/cards/simulate", {
|
|
41
|
+
cardId: card_id,
|
|
42
|
+
amount: amount.to_s,
|
|
43
|
+
merchant: merchant
|
|
44
|
+
})
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
module Kortana
|
|
2
|
+
module Modules
|
|
3
|
+
class Compliance
|
|
4
|
+
def initialize(client)
|
|
5
|
+
@client = client
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def create_kyc(customer_id: nil, merchant_id: nil, document_type:, document_country:, document_front:, document_back: nil, selfie: nil)
|
|
9
|
+
@client.post("/compliance/kyc", {
|
|
10
|
+
customerId: customer_id,
|
|
11
|
+
merchantId: merchant_id,
|
|
12
|
+
documentType: document_type,
|
|
13
|
+
documentCountry: document_country,
|
|
14
|
+
documentFront: document_front,
|
|
15
|
+
documentBack: document_back,
|
|
16
|
+
selfie: selfie
|
|
17
|
+
})
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def get_kyc(id)
|
|
21
|
+
@client.get("/compliance/kyc/#{id}")
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def create_kyb(merchant_id:, business_name:, business_type:, tax_id: nil, documents: nil)
|
|
25
|
+
@client.post("/compliance/kyb", {
|
|
26
|
+
merchantId: merchant_id,
|
|
27
|
+
businessName: business_name,
|
|
28
|
+
businessType: business_type,
|
|
29
|
+
taxId: tax_id,
|
|
30
|
+
documents: documents
|
|
31
|
+
})
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def get_kyb(id)
|
|
35
|
+
@client.get("/compliance/kyb/#{id}")
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def screen(entity_id: nil, transaction_id: nil, first_name: nil, last_name: nil, dob: nil)
|
|
39
|
+
@client.post("/compliance/screen", {
|
|
40
|
+
entityId: entity_id,
|
|
41
|
+
transactionId: transaction_id,
|
|
42
|
+
firstName: first_name,
|
|
43
|
+
lastName: last_name,
|
|
44
|
+
dob: dob
|
|
45
|
+
})
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def get_screening(id)
|
|
49
|
+
@client.get("/compliance/screen/#{id}")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def create_str(transaction_id:, reason:, evidence: nil)
|
|
53
|
+
@client.post("/compliance/str", {
|
|
54
|
+
transactionId: transaction_id,
|
|
55
|
+
reason: reason,
|
|
56
|
+
evidence: evidence
|
|
57
|
+
})
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
module Kortana
|
|
2
|
+
module Modules
|
|
3
|
+
class Contracts
|
|
4
|
+
def initialize(client)
|
|
5
|
+
@client = client
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def deploy_contract(abi:, bytecode:, args: nil)
|
|
9
|
+
@client.post("/contracts/deploy", {
|
|
10
|
+
abi: abi,
|
|
11
|
+
bytecode: bytecode,
|
|
12
|
+
args: args
|
|
13
|
+
})
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def read_contract(address, abi, method, args = [])
|
|
17
|
+
res = @client.post("/contracts/#{address}/read", {
|
|
18
|
+
abi: abi,
|
|
19
|
+
method: method,
|
|
20
|
+
args: args
|
|
21
|
+
})
|
|
22
|
+
res["result"]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def write_contract(address, abi, method, args = [], options = {})
|
|
26
|
+
@client.post("/contracts/#{address}/write", {
|
|
27
|
+
abi: abi,
|
|
28
|
+
method: method,
|
|
29
|
+
args: args,
|
|
30
|
+
options: options
|
|
31
|
+
})
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def estimate_contract_gas(address, abi, method, args = [], options = {})
|
|
35
|
+
res = @client.post("/contracts/#{address}/estimate-gas", {
|
|
36
|
+
abi: abi,
|
|
37
|
+
method: method,
|
|
38
|
+
args: args,
|
|
39
|
+
options: options
|
|
40
|
+
})
|
|
41
|
+
res["gasLimit"].to_i
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def get_contract_events(address, abi, event_name, filter = {})
|
|
45
|
+
@client.post("/contracts/#{address}/events", {
|
|
46
|
+
abi: abi,
|
|
47
|
+
eventName: event_name,
|
|
48
|
+
filter: filter
|
|
49
|
+
})
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def verify_contract(address, source_code:, compiler_version:, optimizer: false, optimizer_runs: 200)
|
|
53
|
+
@client.post("/contracts/#{address}/verify", {
|
|
54
|
+
sourceCode: source_code,
|
|
55
|
+
compilerVersion: compiler_version,
|
|
56
|
+
optimizer: optimizer,
|
|
57
|
+
optimizerRuns: optimizer_runs
|
|
58
|
+
})
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
module Kortana
|
|
2
|
+
module Modules
|
|
3
|
+
class Merchants
|
|
4
|
+
def initialize(client)
|
|
5
|
+
@client = client
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def create(name:, email:, business_name: nil, metadata: nil)
|
|
9
|
+
@client.post("/merchants", {
|
|
10
|
+
name: name,
|
|
11
|
+
email: email,
|
|
12
|
+
businessName: business_name,
|
|
13
|
+
metadata: metadata
|
|
14
|
+
})
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def get(id)
|
|
18
|
+
@client.get("/merchants/#{id}")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def update(id, params)
|
|
22
|
+
@client.put("/merchants/#{id}", params)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def list_stores(merchant_id)
|
|
26
|
+
@client.get("/merchants/#{merchant_id}/stores")
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def create_store(merchant_id, name:, currency: "DNR", settlement_currency: "DNR")
|
|
30
|
+
@client.post("/merchants/#{merchant_id}/stores", {
|
|
31
|
+
name: name,
|
|
32
|
+
currency: currency,
|
|
33
|
+
settlementCurrency: settlement_currency
|
|
34
|
+
})
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def list_terminals(merchant_id)
|
|
38
|
+
@client.get("/merchants/#{merchant_id}/terminals")
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def create_terminal(merchant_id, name: nil, store_id: nil, location: nil)
|
|
42
|
+
@client.post("/merchants/#{merchant_id}/terminals", {
|
|
43
|
+
name: name,
|
|
44
|
+
storeId: store_id,
|
|
45
|
+
location: location
|
|
46
|
+
})
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def create_api_key(merchant_id, name:, permissions:, expires_at: nil)
|
|
50
|
+
@client.post("/merchants/#{merchant_id}/api-keys", {
|
|
51
|
+
name: name,
|
|
52
|
+
permissions: permissions,
|
|
53
|
+
expiresAt: expires_at
|
|
54
|
+
})
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def list_api_keys(merchant_id)
|
|
58
|
+
@client.get("/merchants/#{merchant_id}/api-keys")
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
module Kortana
|
|
2
|
+
module Modules
|
|
3
|
+
class Payments
|
|
4
|
+
def initialize(client)
|
|
5
|
+
@client = client
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def create_intent(amount:, currency: "DNR", description: nil, customer_id: nil, merchant_id: nil, metadata: nil)
|
|
9
|
+
@client.post("/payments/intents", {
|
|
10
|
+
amount: amount.to_s,
|
|
11
|
+
currency: currency,
|
|
12
|
+
description: description,
|
|
13
|
+
customerId: customer_id,
|
|
14
|
+
merchantId: merchant_id,
|
|
15
|
+
metadata: metadata
|
|
16
|
+
})
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def get_intent(id)
|
|
20
|
+
@client.get("/payments/intents/#{id}")
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def confirm_intent(id, payment_method = nil)
|
|
24
|
+
@client.post("/payments/intents/#{id}/confirm", {
|
|
25
|
+
paymentMethod: payment_method
|
|
26
|
+
})
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def cancel_intent(id)
|
|
30
|
+
@client.post("/payments/intents/#{id}/cancel")
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def create_invoice(items:, customer: nil, due_date: nil, metadata: nil)
|
|
34
|
+
@client.post("/payments/invoices", {
|
|
35
|
+
items: items,
|
|
36
|
+
customer: customer,
|
|
37
|
+
dueDate: due_date,
|
|
38
|
+
metadata: metadata
|
|
39
|
+
})
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def get_invoice(id)
|
|
43
|
+
@client.get("/payments/invoices/#{id}")
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def create_payment_link(amount:, currency: "DNR", description: nil, expires_at: nil)
|
|
47
|
+
@client.post("/payments/links", {
|
|
48
|
+
amount: amount.to_s,
|
|
49
|
+
currency: currency,
|
|
50
|
+
description: description,
|
|
51
|
+
expiresAt: expires_at
|
|
52
|
+
})
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def get_payment_link(id)
|
|
56
|
+
@client.get("/payments/links/#{id}")
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module Kortana
|
|
2
|
+
module Modules
|
|
3
|
+
class Settlement
|
|
4
|
+
def initialize(client)
|
|
5
|
+
@client = client
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def get_treasury(currency: nil)
|
|
9
|
+
params = currency ? { currency: currency } : {}
|
|
10
|
+
@client.get("/settlement/treasury", params)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def list(options = {})
|
|
14
|
+
@client.get("/settlement/settlements", options)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def get(id)
|
|
18
|
+
@client.get("/settlement/settlements/#{id}")
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
module Kortana
|
|
2
|
+
module Modules
|
|
3
|
+
class Wallets
|
|
4
|
+
def initialize(client)
|
|
5
|
+
@client = client
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def get_balance
|
|
9
|
+
@client.get("/wallets/balance")
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def list_addresses
|
|
13
|
+
@client.get("/wallets/addresses")
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def get_address(index = nil)
|
|
17
|
+
params = index ? { index: index } : {}
|
|
18
|
+
@client.get("/wallets/address", params)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def transfer(to:, amount:, currency: "DNR", chain: nil)
|
|
22
|
+
@client.post("/wallets/transfers", {
|
|
23
|
+
to: to,
|
|
24
|
+
amount: amount.to_s,
|
|
25
|
+
currency: currency,
|
|
26
|
+
chain: chain
|
|
27
|
+
})
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def list_transactions(options = {})
|
|
31
|
+
@client.get("/wallets/transactions", options)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def get_transaction(id)
|
|
35
|
+
@client.get("/wallets/transactions/#{id}")
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module Kortana
|
|
2
|
+
class Webhook
|
|
3
|
+
def initialize(secret)
|
|
4
|
+
@secret = secret
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def verify(payload, signature)
|
|
8
|
+
raise WebhookSignatureError, "Signature must not be empty" if signature.nil? || signature.strip.empty?
|
|
9
|
+
raise WebhookSignatureError, "Webhook secret must not be empty" if @secret.nil? || @secret.strip.empty?
|
|
10
|
+
|
|
11
|
+
expected = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), @secret, payload)
|
|
12
|
+
|
|
13
|
+
unless secure_compare(expected, signature)
|
|
14
|
+
raise WebhookSignatureError, "Webhook signature verification failed"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
JSON.parse(payload)
|
|
18
|
+
rescue JSON::ParserError
|
|
19
|
+
raise WebhookSignatureError, "Invalid JSON payload"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
# Constant time comparison to avoid timing attacks
|
|
25
|
+
def secure_compare(a, b)
|
|
26
|
+
return false unless a.bytesize == b.bytesize
|
|
27
|
+
l = a.unpack("C*")
|
|
28
|
+
res = 0
|
|
29
|
+
b.each_byte { |val| res |= val ^ l.shift }
|
|
30
|
+
res.zero?
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
data/lib/kortana.rb
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
require 'faraday'
|
|
2
|
+
require 'faraday/retry'
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'openssl'
|
|
5
|
+
require 'securerandom'
|
|
6
|
+
|
|
7
|
+
require_relative 'kortana/version'
|
|
8
|
+
require_relative 'kortana/errors'
|
|
9
|
+
require_relative 'kortana/client'
|
|
10
|
+
require_relative 'kortana/webhooks'
|
|
11
|
+
|
|
12
|
+
module Kortana
|
|
13
|
+
# Entry point configuration module
|
|
14
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: kortana-dev-sdk
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Kortana Labs
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: faraday
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '2.8'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '2.8'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: faraday-retry
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '2.2'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '2.2'
|
|
40
|
+
description: Integrate with Kortana Pay, wallets, smart contracts, card issuance,
|
|
41
|
+
compliance, and ledger APIs directly from Ruby applications.
|
|
42
|
+
email:
|
|
43
|
+
- engineering@kortana.io
|
|
44
|
+
executables: []
|
|
45
|
+
extensions: []
|
|
46
|
+
extra_rdoc_files: []
|
|
47
|
+
files:
|
|
48
|
+
- lib/kortana.rb
|
|
49
|
+
- lib/kortana/client.rb
|
|
50
|
+
- lib/kortana/errors.rb
|
|
51
|
+
- lib/kortana/modules/banking.rb
|
|
52
|
+
- lib/kortana/modules/blockchain.rb
|
|
53
|
+
- lib/kortana/modules/cards.rb
|
|
54
|
+
- lib/kortana/modules/compliance.rb
|
|
55
|
+
- lib/kortana/modules/contracts.rb
|
|
56
|
+
- lib/kortana/modules/merchants.rb
|
|
57
|
+
- lib/kortana/modules/payments.rb
|
|
58
|
+
- lib/kortana/modules/settlement.rb
|
|
59
|
+
- lib/kortana/modules/wallets.rb
|
|
60
|
+
- lib/kortana/version.rb
|
|
61
|
+
- lib/kortana/webhooks.rb
|
|
62
|
+
homepage: https://docs.kortana.io/sdk/ruby
|
|
63
|
+
licenses:
|
|
64
|
+
- MIT
|
|
65
|
+
metadata:
|
|
66
|
+
homepage_uri: https://docs.kortana.io/sdk/ruby
|
|
67
|
+
source_code_uri: https://gitlab.com/emeka.iwuagwu/kortanablockchain-hub
|
|
68
|
+
rdoc_options: []
|
|
69
|
+
require_paths:
|
|
70
|
+
- lib
|
|
71
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - ">="
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '3.0'
|
|
76
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
77
|
+
requirements:
|
|
78
|
+
- - ">="
|
|
79
|
+
- !ruby/object:Gem::Version
|
|
80
|
+
version: '0'
|
|
81
|
+
requirements: []
|
|
82
|
+
rubygems_version: 4.0.10
|
|
83
|
+
specification_version: 4
|
|
84
|
+
summary: Official Ruby SDK for the Kortana Blockchain Platform
|
|
85
|
+
test_files: []
|