nowpayments 0.2.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/.env.example +4 -0
- data/CHANGELOG.md +122 -0
- data/LICENSE.txt +21 -0
- data/README.md +578 -0
- data/Rakefile +12 -0
- data/docs/API.md +922 -0
- data/examples/jwt_authentication_example.rb +254 -0
- data/examples/simple_demo.rb +72 -0
- data/examples/webhook_server.rb +85 -0
- data/lib/nowpayments/api/authentication.rb +66 -0
- data/lib/nowpayments/api/conversions.rb +42 -0
- data/lib/nowpayments/api/currencies.rb +32 -0
- data/lib/nowpayments/api/custody.rb +147 -0
- data/lib/nowpayments/api/estimation.rb +34 -0
- data/lib/nowpayments/api/fiat_payouts.rb +150 -0
- data/lib/nowpayments/api/invoices.rb +88 -0
- data/lib/nowpayments/api/payments.rb +93 -0
- data/lib/nowpayments/api/payouts.rb +107 -0
- data/lib/nowpayments/api/status.rb +15 -0
- data/lib/nowpayments/api/subscriptions.rb +93 -0
- data/lib/nowpayments/client.rb +91 -0
- data/lib/nowpayments/errors.rb +60 -0
- data/lib/nowpayments/middleware/error_handler.rb +33 -0
- data/lib/nowpayments/rack.rb +25 -0
- data/lib/nowpayments/version.rb +5 -0
- data/lib/nowpayments/webhook.rb +62 -0
- data/lib/nowpayments.rb +12 -0
- data/sig/nowpayments.rbs +4 -0
- metadata +92 -0
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# JWT Authentication Example
|
|
4
|
+
|
|
5
|
+
require "nowpayments"
|
|
6
|
+
|
|
7
|
+
# Initialize client with API key
|
|
8
|
+
client = NOWPayments::Client.new(
|
|
9
|
+
api_key: "YOUR_API_KEY",
|
|
10
|
+
sandbox: true # Use sandbox for testing
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
# ============================================
|
|
14
|
+
# Example 1: Basic Authentication
|
|
15
|
+
# ============================================
|
|
16
|
+
|
|
17
|
+
puts "Example 1: Basic JWT Authentication"
|
|
18
|
+
puts "=" * 50
|
|
19
|
+
|
|
20
|
+
# Authenticate to get JWT token (expires in 5 minutes)
|
|
21
|
+
auth_response = client.authenticate(
|
|
22
|
+
email: "your_email@example.com",
|
|
23
|
+
password: "your_password"
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
puts "✅ Authenticated successfully!"
|
|
27
|
+
puts "Token: #{auth_response["token"][0..20]}..."
|
|
28
|
+
puts "Time remaining: #{client.jwt_time_remaining} seconds"
|
|
29
|
+
puts
|
|
30
|
+
|
|
31
|
+
# ============================================
|
|
32
|
+
# Example 2: Operations Requiring JWT Auth
|
|
33
|
+
# ============================================
|
|
34
|
+
|
|
35
|
+
puts "Example 2: Creating a Payout (Requires JWT)"
|
|
36
|
+
puts "=" * 50
|
|
37
|
+
|
|
38
|
+
# Create a payout (requires JWT Bearer token)
|
|
39
|
+
payout_response = client.create_payout(
|
|
40
|
+
withdrawals: [
|
|
41
|
+
{
|
|
42
|
+
address: "TEmGwPeRTPiLFLVfBxXkSP91yc5GMNQhfS",
|
|
43
|
+
currency: "trx",
|
|
44
|
+
amount: 10
|
|
45
|
+
}
|
|
46
|
+
],
|
|
47
|
+
payout_description: "Test payout"
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
puts "✅ Payout created!"
|
|
51
|
+
puts "Batch ID: #{payout_response["id"]}"
|
|
52
|
+
puts "Status: #{payout_response["withdrawals"].first["status"]}"
|
|
53
|
+
puts
|
|
54
|
+
|
|
55
|
+
# Verify payout with 2FA code
|
|
56
|
+
client.verify_payout(
|
|
57
|
+
batch_withdrawal_id: payout_response["id"],
|
|
58
|
+
verification_code: "123456" # From Google Authenticator or email
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
puts "✅ Payout verified!"
|
|
62
|
+
puts
|
|
63
|
+
|
|
64
|
+
# ============================================
|
|
65
|
+
# Example 3: Token Management
|
|
66
|
+
# ============================================
|
|
67
|
+
|
|
68
|
+
puts "Example 3: Token Lifecycle Management"
|
|
69
|
+
puts "=" * 50
|
|
70
|
+
|
|
71
|
+
# Check token status
|
|
72
|
+
puts "Token expired? #{client.jwt_expired?}"
|
|
73
|
+
puts "Time remaining: #{client.jwt_time_remaining} seconds"
|
|
74
|
+
puts
|
|
75
|
+
|
|
76
|
+
# Manual token refresh (if you have credentials stored)
|
|
77
|
+
if client.jwt_time_remaining < 60
|
|
78
|
+
puts "⚠️ Token expiring soon, re-authenticating..."
|
|
79
|
+
client.authenticate(
|
|
80
|
+
email: "your_email@example.com",
|
|
81
|
+
password: "your_password"
|
|
82
|
+
)
|
|
83
|
+
puts "✅ Token refreshed!"
|
|
84
|
+
end
|
|
85
|
+
puts
|
|
86
|
+
|
|
87
|
+
# ============================================
|
|
88
|
+
# Example 4: Conversions (Requires JWT)
|
|
89
|
+
# ============================================
|
|
90
|
+
|
|
91
|
+
puts "Example 4: Currency Conversions"
|
|
92
|
+
puts "=" * 50
|
|
93
|
+
|
|
94
|
+
# All conversion endpoints require JWT authentication
|
|
95
|
+
conversion = client.create_conversion(
|
|
96
|
+
from_currency: "btc",
|
|
97
|
+
to_currency: "eth",
|
|
98
|
+
amount: 0.1
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
puts "✅ Conversion created!"
|
|
102
|
+
puts "Conversion ID: #{conversion["conversion_id"]}"
|
|
103
|
+
puts
|
|
104
|
+
|
|
105
|
+
# Check conversion status
|
|
106
|
+
status = client.conversion_status(conversion["conversion_id"])
|
|
107
|
+
puts "Status: #{status["status"]}"
|
|
108
|
+
puts
|
|
109
|
+
|
|
110
|
+
# ============================================
|
|
111
|
+
# Example 5: Custody Operations (Requires JWT)
|
|
112
|
+
# ============================================
|
|
113
|
+
|
|
114
|
+
puts "Example 5: Custody/Sub-Account Operations"
|
|
115
|
+
puts "=" * 50
|
|
116
|
+
|
|
117
|
+
# Create user account
|
|
118
|
+
user = client.create_sub_account(user_id: "user_12345")
|
|
119
|
+
puts "✅ User account created: #{user["result"]["id"]}"
|
|
120
|
+
puts
|
|
121
|
+
|
|
122
|
+
# Transfer between accounts (requires JWT)
|
|
123
|
+
transfer = client.transfer_between_sub_accounts(
|
|
124
|
+
currency: "trx",
|
|
125
|
+
amount: 5,
|
|
126
|
+
from_id: "111111",
|
|
127
|
+
to_id: "222222"
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
puts "✅ Transfer initiated!"
|
|
131
|
+
puts "Transfer ID: #{transfer["result"]["id"]}"
|
|
132
|
+
puts "Status: #{transfer["result"]["status"]}"
|
|
133
|
+
puts
|
|
134
|
+
|
|
135
|
+
# ============================================
|
|
136
|
+
# Example 6: Recurring Payments (DELETE requires JWT)
|
|
137
|
+
# ============================================
|
|
138
|
+
|
|
139
|
+
puts "Example 6: Managing Recurring Payments"
|
|
140
|
+
puts "=" * 50
|
|
141
|
+
|
|
142
|
+
# Delete recurring payment (requires JWT)
|
|
143
|
+
result = client.delete_recurring_payment("subscription_id")
|
|
144
|
+
puts "✅ Recurring payment deleted: #{result["result"]}"
|
|
145
|
+
puts
|
|
146
|
+
|
|
147
|
+
# ============================================
|
|
148
|
+
# Example 7: Token Cleanup
|
|
149
|
+
# ============================================
|
|
150
|
+
|
|
151
|
+
puts "Example 7: Token Cleanup"
|
|
152
|
+
puts "=" * 50
|
|
153
|
+
|
|
154
|
+
# Clear token when done (optional, for security)
|
|
155
|
+
client.clear_jwt_token
|
|
156
|
+
puts "✅ JWT token cleared"
|
|
157
|
+
puts "Token expired? #{client.jwt_expired?}"
|
|
158
|
+
puts
|
|
159
|
+
|
|
160
|
+
# ============================================
|
|
161
|
+
# Example 8: Auto-Refresh Pattern
|
|
162
|
+
# ============================================
|
|
163
|
+
|
|
164
|
+
puts "Example 8: Auto-Refresh Pattern"
|
|
165
|
+
puts "=" * 50
|
|
166
|
+
|
|
167
|
+
# Store credentials for auto-refresh
|
|
168
|
+
EMAIL = "your_email@example.com"
|
|
169
|
+
PASSWORD = "your_password"
|
|
170
|
+
|
|
171
|
+
# Helper method to ensure authenticated
|
|
172
|
+
def ensure_authenticated(client, email, password)
|
|
173
|
+
return unless client.jwt_expired?
|
|
174
|
+
|
|
175
|
+
puts "🔄 Token expired, re-authenticating..."
|
|
176
|
+
client.authenticate(email: email, password: password)
|
|
177
|
+
puts "✅ Re-authenticated!"
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Before each JWT-required operation
|
|
181
|
+
ensure_authenticated(client, EMAIL, PASSWORD)
|
|
182
|
+
payout = client.list_payouts(limit: 10, offset: 0)
|
|
183
|
+
puts "✅ Listed #{payout["count"]} payouts"
|
|
184
|
+
puts
|
|
185
|
+
|
|
186
|
+
# ============================================
|
|
187
|
+
# Example 9: Multiple Operations Pattern
|
|
188
|
+
# ============================================
|
|
189
|
+
|
|
190
|
+
puts "Example 9: Efficient Multiple Operations"
|
|
191
|
+
puts "=" * 50
|
|
192
|
+
|
|
193
|
+
# Authenticate once at the beginning
|
|
194
|
+
client.authenticate(
|
|
195
|
+
email: "your_email@example.com",
|
|
196
|
+
password: "your_password"
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
# Perform multiple operations (token valid for 5 minutes)
|
|
200
|
+
operations = [
|
|
201
|
+
-> { client.balance },
|
|
202
|
+
-> { client.list_payouts(limit: 5, offset: 0) },
|
|
203
|
+
-> { client.list_conversions(limit: 5, offset: 0) },
|
|
204
|
+
-> { client.sub_account_balances }
|
|
205
|
+
]
|
|
206
|
+
|
|
207
|
+
operations.each_with_index do |operation, index|
|
|
208
|
+
# Check if token needs refresh before each operation
|
|
209
|
+
if client.jwt_expired?
|
|
210
|
+
puts "🔄 Refreshing token..."
|
|
211
|
+
client.authenticate(
|
|
212
|
+
email: "your_email@example.com",
|
|
213
|
+
password: "your_password"
|
|
214
|
+
)
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
result = operation.call
|
|
218
|
+
puts "✅ Operation #{index + 1} completed"
|
|
219
|
+
rescue StandardError => e
|
|
220
|
+
puts "❌ Operation #{index + 1} failed: #{e.message}"
|
|
221
|
+
end
|
|
222
|
+
puts
|
|
223
|
+
|
|
224
|
+
# ============================================
|
|
225
|
+
# Example 10: Error Handling
|
|
226
|
+
# ============================================
|
|
227
|
+
|
|
228
|
+
puts "Example 10: Error Handling"
|
|
229
|
+
puts "=" * 50
|
|
230
|
+
|
|
231
|
+
begin
|
|
232
|
+
# Try to create payout without authentication
|
|
233
|
+
client.clear_jwt_token
|
|
234
|
+
client.create_payout(
|
|
235
|
+
withdrawals: [{ address: "TEmGwPeRTPiLFLVfBxXkSP91yc5GMNQhfS", currency: "trx", amount: 10 }]
|
|
236
|
+
)
|
|
237
|
+
rescue StandardError => e
|
|
238
|
+
puts "❌ Expected error: #{e.class}"
|
|
239
|
+
puts "Message: #{e.message}"
|
|
240
|
+
puts "💡 Solution: Authenticate first!"
|
|
241
|
+
puts
|
|
242
|
+
|
|
243
|
+
# Authenticate and retry
|
|
244
|
+
client.authenticate(
|
|
245
|
+
email: "your_email@example.com",
|
|
246
|
+
password: "your_password"
|
|
247
|
+
)
|
|
248
|
+
puts "✅ Authenticated, retry succeeded!"
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
puts
|
|
252
|
+
puts "=" * 50
|
|
253
|
+
puts "🎉 All JWT authentication examples completed!"
|
|
254
|
+
puts "=" * 50
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "bundler/setup"
|
|
5
|
+
require "nowpayments"
|
|
6
|
+
require "dotenv"
|
|
7
|
+
|
|
8
|
+
Dotenv.load
|
|
9
|
+
|
|
10
|
+
# Initialize client
|
|
11
|
+
client = NOWPayments::Client.new(
|
|
12
|
+
api_key: ENV.fetch("NOWPAYMENTS_SANDBOX_API_KEY", nil),
|
|
13
|
+
sandbox: true
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
puts "=== NOWPayments API Demo ===\n\n"
|
|
17
|
+
|
|
18
|
+
# 1. Check API status
|
|
19
|
+
puts "1. Checking API status..."
|
|
20
|
+
status = client.status
|
|
21
|
+
puts " Status: #{status["message"]}\n\n"
|
|
22
|
+
|
|
23
|
+
# 2. Get available currencies
|
|
24
|
+
puts "2. Getting available currencies..."
|
|
25
|
+
currencies = client.currencies
|
|
26
|
+
puts " Available: #{currencies["currencies"].first(10).join(", ")}...\n\n"
|
|
27
|
+
|
|
28
|
+
# 3. Get minimum amount
|
|
29
|
+
puts "3. Checking minimum amount for USD -> BTC..."
|
|
30
|
+
min = client.min_amount(currency_from: "usd", currency_to: "btc")
|
|
31
|
+
puts " Minimum: #{min["min_amount"]} #{min["currency_to"]}\n\n"
|
|
32
|
+
|
|
33
|
+
# 4. Estimate price
|
|
34
|
+
puts "4. Estimating price for 100 USD in BTC..."
|
|
35
|
+
estimate = client.estimate(
|
|
36
|
+
amount: 100,
|
|
37
|
+
currency_from: "usd",
|
|
38
|
+
currency_to: "btc"
|
|
39
|
+
)
|
|
40
|
+
puts " Estimated: #{estimate["estimated_amount"]} BTC\n\n"
|
|
41
|
+
|
|
42
|
+
# 5. Create a payment
|
|
43
|
+
puts "5. Creating a payment..."
|
|
44
|
+
payment = client.create_payment(
|
|
45
|
+
price_amount: 100.0,
|
|
46
|
+
price_currency: "usd",
|
|
47
|
+
pay_currency: "btc",
|
|
48
|
+
order_id: "demo-#{Time.now.to_i}",
|
|
49
|
+
order_description: "Demo payment"
|
|
50
|
+
)
|
|
51
|
+
puts " Payment ID: #{payment["payment_id"]}"
|
|
52
|
+
puts " Pay Address: #{payment["pay_address"]}"
|
|
53
|
+
puts " Pay Amount: #{payment["pay_amount"]} #{payment["pay_currency"]}"
|
|
54
|
+
puts " Status: #{payment["payment_status"]}\n\n"
|
|
55
|
+
|
|
56
|
+
# 6. Check payment status
|
|
57
|
+
puts "6. Checking payment status..."
|
|
58
|
+
status = client.payment(payment["payment_id"])
|
|
59
|
+
puts " Current status: #{status["payment_status"]}\n\n"
|
|
60
|
+
|
|
61
|
+
# 7. Create an invoice
|
|
62
|
+
puts "7. Creating an invoice..."
|
|
63
|
+
invoice = client.create_invoice(
|
|
64
|
+
price_amount: 50.0,
|
|
65
|
+
price_currency: "usd",
|
|
66
|
+
order_id: "invoice-#{Time.now.to_i}"
|
|
67
|
+
)
|
|
68
|
+
puts " Invoice ID: #{invoice["id"]}"
|
|
69
|
+
puts " Invoice URL: #{invoice["invoice_url"]}\n\n"
|
|
70
|
+
|
|
71
|
+
puts "=== Demo Complete ===\n"
|
|
72
|
+
puts "All endpoints working correctly!"
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Example Sinatra webhook receiver
|
|
4
|
+
#
|
|
5
|
+
# Usage:
|
|
6
|
+
# ruby examples/webhook_server.rb
|
|
7
|
+
# Then use ngrok to expose: ngrok http 4567
|
|
8
|
+
# Configure the ngrok URL in NOWPayments dashboard
|
|
9
|
+
|
|
10
|
+
require "sinatra"
|
|
11
|
+
require "nowpayments"
|
|
12
|
+
require "dotenv"
|
|
13
|
+
require "json"
|
|
14
|
+
|
|
15
|
+
Dotenv.load
|
|
16
|
+
|
|
17
|
+
# Configure logging
|
|
18
|
+
set :logging, true
|
|
19
|
+
|
|
20
|
+
# Webhook endpoint
|
|
21
|
+
post "/webhooks/nowpayments" do
|
|
22
|
+
# Verify the webhook
|
|
23
|
+
payload = NOWPayments::Rack.verify_webhook(
|
|
24
|
+
request,
|
|
25
|
+
ENV.fetch("NOWPAYMENTS_SANDBOX_IPN_SECRET", nil)
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
logger.info "Received verified webhook: #{payload.inspect}"
|
|
29
|
+
|
|
30
|
+
# Handle different payment statuses
|
|
31
|
+
case payload["payment_status"]
|
|
32
|
+
when "finished"
|
|
33
|
+
logger.info "✅ Payment #{payload["payment_id"]} completed!"
|
|
34
|
+
logger.info " Order: #{payload["order_id"]}"
|
|
35
|
+
logger.info " Amount: #{payload["outcome_amount"]} #{payload["outcome_currency"]}"
|
|
36
|
+
|
|
37
|
+
# TODO: Fulfill order here
|
|
38
|
+
# Order.find_by(id: payload['order_id'])&.mark_paid!
|
|
39
|
+
|
|
40
|
+
when "failed"
|
|
41
|
+
logger.warn "❌ Payment #{payload["payment_id"]} failed"
|
|
42
|
+
|
|
43
|
+
# TODO: Cancel order here
|
|
44
|
+
# Order.find_by(id: payload['order_id'])&.cancel!
|
|
45
|
+
|
|
46
|
+
when "partially_paid"
|
|
47
|
+
logger.warn "⚠️ Payment #{payload["payment_id"]} partially paid"
|
|
48
|
+
logger.warn " Expected: #{payload["pay_amount"]} #{payload["pay_currency"]}"
|
|
49
|
+
logger.warn " Received: #{payload["actually_paid"]} #{payload["pay_currency"]}"
|
|
50
|
+
|
|
51
|
+
when "expired"
|
|
52
|
+
logger.info "⏱️ Payment #{payload["payment_id"]} expired"
|
|
53
|
+
|
|
54
|
+
else
|
|
55
|
+
logger.info "ℹ️ Payment #{payload["payment_id"]} status: #{payload["payment_status"]}"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Always return 200 OK to acknowledge receipt
|
|
59
|
+
status 200
|
|
60
|
+
{ success: true }.to_json
|
|
61
|
+
rescue NOWPayments::SecurityError => e
|
|
62
|
+
# Invalid signature - potential fraud
|
|
63
|
+
logger.error "🔒 Security Error: #{e.message}"
|
|
64
|
+
status 403
|
|
65
|
+
{ error: "Invalid signature" }.to_json
|
|
66
|
+
rescue StandardError => e
|
|
67
|
+
# Other errors
|
|
68
|
+
logger.error "❌ Error processing webhook: #{e.message}"
|
|
69
|
+
logger.error e.backtrace.join("\n")
|
|
70
|
+
status 500
|
|
71
|
+
{ error: "Internal server error" }.to_json
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Health check endpoint
|
|
75
|
+
get "/health" do
|
|
76
|
+
{ status: "ok", timestamp: Time.now.to_i }.to_json
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Start message
|
|
80
|
+
puts "\n=== NOWPayments Webhook Server ==="
|
|
81
|
+
puts "Listening on http://localhost:4567"
|
|
82
|
+
puts "Webhook URL: http://localhost:4567/webhooks/nowpayments"
|
|
83
|
+
puts "\nTo expose publicly, use ngrok:"
|
|
84
|
+
puts " ngrok http 4567"
|
|
85
|
+
puts "\nThen configure the ngrok URL in NOWPayments dashboard\n\n"
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module NOWPayments
|
|
4
|
+
module API
|
|
5
|
+
# JWT authentication endpoints
|
|
6
|
+
module Authentication
|
|
7
|
+
# Authenticate and obtain JWT token
|
|
8
|
+
# POST /v1/auth
|
|
9
|
+
# JWT tokens expire in 5 minutes for security reasons
|
|
10
|
+
# @param email [String] Your NOWPayments dashboard email (case-sensitive)
|
|
11
|
+
# @param password [String] Your NOWPayments dashboard password (case-sensitive)
|
|
12
|
+
# @return [Hash] Authentication response with JWT token
|
|
13
|
+
# @note Email and password are case-sensitive. test@gmail.com != Test@gmail.com
|
|
14
|
+
def authenticate(email:, password:)
|
|
15
|
+
response = post("auth", body: {
|
|
16
|
+
email: email,
|
|
17
|
+
password: password
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
# Store token and expiry time (5 minutes from now)
|
|
21
|
+
if response.body["token"]
|
|
22
|
+
@jwt_token = response.body["token"]
|
|
23
|
+
@jwt_expires_at = Time.now + 300 # 5 minutes = 300 seconds
|
|
24
|
+
|
|
25
|
+
# Reset connection to include new Bearer token
|
|
26
|
+
reset_connection! if respond_to?(:reset_connection!, true)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
response.body
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Get current JWT token (refreshes if expired)
|
|
33
|
+
# @param email [String, nil] Email for re-authentication if token expired
|
|
34
|
+
# @param password [String, nil] Password for re-authentication if token expired
|
|
35
|
+
# @return [String, nil] Current valid JWT token or nil
|
|
36
|
+
def jwt_token(email: nil, password: nil)
|
|
37
|
+
# Auto-refresh if expired and credentials provided
|
|
38
|
+
authenticate(email: email, password: password) if jwt_expired? && email && password
|
|
39
|
+
|
|
40
|
+
@jwt_token
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Check if JWT token is expired
|
|
44
|
+
# @return [Boolean] True if token is expired or not set
|
|
45
|
+
def jwt_expired?
|
|
46
|
+
!@jwt_token || !@jwt_expires_at || Time.now >= @jwt_expires_at
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Manually clear JWT token (e.g., for logout)
|
|
50
|
+
# @return [void]
|
|
51
|
+
def clear_jwt_token
|
|
52
|
+
@jwt_token = nil
|
|
53
|
+
@jwt_expires_at = nil
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Get time remaining until JWT token expires
|
|
57
|
+
# @return [Integer, nil] Seconds until expiry, or nil if no token
|
|
58
|
+
def jwt_time_remaining
|
|
59
|
+
return nil unless @jwt_token && @jwt_expires_at
|
|
60
|
+
|
|
61
|
+
remaining = (@jwt_expires_at - Time.now).to_i
|
|
62
|
+
remaining.positive? ? remaining : 0
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module NOWPayments
|
|
4
|
+
module API
|
|
5
|
+
# Conversion endpoints (requires JWT auth)
|
|
6
|
+
module Conversions
|
|
7
|
+
# Create a new conversion between currencies
|
|
8
|
+
# POST /v1/conversion
|
|
9
|
+
# Requires JWT authentication
|
|
10
|
+
# @param from_currency [String] Source currency code
|
|
11
|
+
# @param to_currency [String] Target currency code
|
|
12
|
+
# @param amount [Numeric] Amount to convert
|
|
13
|
+
# @return [Hash] Conversion details
|
|
14
|
+
def create_conversion(from_currency:, to_currency:, amount:)
|
|
15
|
+
post("conversion", body: {
|
|
16
|
+
from_currency: from_currency,
|
|
17
|
+
to_currency: to_currency,
|
|
18
|
+
amount: amount
|
|
19
|
+
}).body
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Get status of a specific conversion
|
|
23
|
+
# GET /v1/conversion/:conversion_id
|
|
24
|
+
# Requires JWT authentication
|
|
25
|
+
# @param conversion_id [String, Integer] Conversion ID
|
|
26
|
+
# @return [Hash] Conversion status details
|
|
27
|
+
def conversion_status(conversion_id)
|
|
28
|
+
get("conversion/#{conversion_id}").body
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# List all conversions with pagination
|
|
32
|
+
# GET /v1/conversion
|
|
33
|
+
# Requires JWT authentication
|
|
34
|
+
# @param limit [Integer] Results per page
|
|
35
|
+
# @param offset [Integer] Offset for pagination
|
|
36
|
+
# @return [Hash] List of conversions
|
|
37
|
+
def list_conversions(limit: 10, offset: 0)
|
|
38
|
+
get("conversion", params: { limit: limit, offset: offset }).body
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module NOWPayments
|
|
4
|
+
module API
|
|
5
|
+
# Currency-related endpoints
|
|
6
|
+
module Currencies
|
|
7
|
+
# Get list of available currencies
|
|
8
|
+
# GET /v1/currencies
|
|
9
|
+
# @param fixed_rate [Boolean, nil] Optional flag to get currencies with min/max exchange amounts
|
|
10
|
+
# @return [Hash] Available currencies
|
|
11
|
+
def currencies(fixed_rate: nil)
|
|
12
|
+
params = {}
|
|
13
|
+
params[:fixed_rate] = fixed_rate unless fixed_rate.nil?
|
|
14
|
+
get("currencies", params: params).body
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Get list of available currencies with full info
|
|
18
|
+
# GET /v1/full-currencies
|
|
19
|
+
# @return [Hash] Full currency information
|
|
20
|
+
def full_currencies
|
|
21
|
+
get("full-currencies").body
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Get list of available currencies checked by merchant
|
|
25
|
+
# GET /v1/merchant/coins
|
|
26
|
+
# @return [Hash] Merchant's checked currencies
|
|
27
|
+
def merchant_coins
|
|
28
|
+
get("merchant/coins").body
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module NOWPayments
|
|
4
|
+
module API
|
|
5
|
+
# Custody/sub-partner endpoints for managing customer accounts
|
|
6
|
+
module Custody
|
|
7
|
+
# Create a new sub-account (user account)
|
|
8
|
+
# POST /v1/sub-partner/balance
|
|
9
|
+
# @param user_id [String] Unique user identifier (your internal user ID)
|
|
10
|
+
# @return [Hash] Created sub-account details
|
|
11
|
+
def create_sub_account(user_id:)
|
|
12
|
+
post("sub-partner/balance", body: { Name: user_id }).body
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Get balance for a specific sub-account
|
|
16
|
+
# GET /v1/sub-partner/balance/:user_id
|
|
17
|
+
# @param user_id [String] User identifier (path parameter)
|
|
18
|
+
# @return [Hash] User balance details
|
|
19
|
+
def sub_account_balance(user_id)
|
|
20
|
+
get("sub-partner/balance/#{user_id}").body
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Get balance for all sub-accounts
|
|
24
|
+
# GET /v1/sub-partner/balance
|
|
25
|
+
# @return [Hash] Array of all user balances
|
|
26
|
+
def sub_account_balances
|
|
27
|
+
get("sub-partner/balance").body
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# List sub-accounts with filters
|
|
31
|
+
# GET /v1/sub-partner
|
|
32
|
+
# @param id [String, Integer, Array, nil] Filter by specific user ID(s)
|
|
33
|
+
# @param limit [Integer] Results per page
|
|
34
|
+
# @param offset [Integer] Offset for pagination
|
|
35
|
+
# @param order [String] Sort order (ASC or DESC)
|
|
36
|
+
# @return [Hash] List of sub-accounts
|
|
37
|
+
def list_sub_accounts(id: nil, limit: 10, offset: 0, order: "ASC")
|
|
38
|
+
params = { limit: limit, offset: offset, order: order }
|
|
39
|
+
params[:id] = id if id
|
|
40
|
+
|
|
41
|
+
get("sub-partner", params: params).body
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Transfer between sub-accounts
|
|
45
|
+
# POST /v1/sub-partner/transfer
|
|
46
|
+
# @param currency [String] Currency code
|
|
47
|
+
# @param amount [Numeric] Amount to transfer
|
|
48
|
+
# @param from_id [String, Integer] Source sub-account ID
|
|
49
|
+
# @param to_id [String, Integer] Destination sub-account ID
|
|
50
|
+
# @return [Hash] Transfer result
|
|
51
|
+
def transfer_between_sub_accounts(currency:, amount:, from_id:, to_id:)
|
|
52
|
+
post("sub-partner/transfer", body: {
|
|
53
|
+
currency: currency,
|
|
54
|
+
amount: amount,
|
|
55
|
+
from_id: from_id,
|
|
56
|
+
to_id: to_id
|
|
57
|
+
}).body
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Create deposit request for sub-account (external crypto deposit)
|
|
61
|
+
# POST /v1/sub-partner/deposit
|
|
62
|
+
# @param user_id [String] User identifier
|
|
63
|
+
# @param currency [String] Cryptocurrency code
|
|
64
|
+
# @param amount [Numeric, nil] Optional amount
|
|
65
|
+
# @return [Hash] Deposit address and details
|
|
66
|
+
def create_sub_account_deposit(user_id:, currency:, amount: nil)
|
|
67
|
+
params = {
|
|
68
|
+
Name: user_id,
|
|
69
|
+
currency: currency
|
|
70
|
+
}
|
|
71
|
+
params[:amount] = amount if amount
|
|
72
|
+
|
|
73
|
+
post("sub-partner/deposit", body: params).body
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Create payment deposit for sub-account
|
|
77
|
+
# POST /v1/sub-partner/payment
|
|
78
|
+
# @param sub_partner_id [String, Integer] Sub-account ID
|
|
79
|
+
# @param currency [String] Currency code
|
|
80
|
+
# @param amount [Numeric] Payment amount
|
|
81
|
+
# @param fixed_rate [Boolean, nil] Fixed rate flag
|
|
82
|
+
# @return [Hash] Payment deposit details
|
|
83
|
+
def create_sub_account_payment_deposit(sub_partner_id:, currency:, amount:, fixed_rate: nil)
|
|
84
|
+
params = {
|
|
85
|
+
sub_partner_id: sub_partner_id,
|
|
86
|
+
currency: currency,
|
|
87
|
+
amount: amount
|
|
88
|
+
}
|
|
89
|
+
params[:fixed_rate] = fixed_rate unless fixed_rate.nil?
|
|
90
|
+
|
|
91
|
+
post("sub-partner/payment", body: params).body
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Transfer funds from master account to sub-account
|
|
95
|
+
# POST /v1/sub-partner/deposit-from-master
|
|
96
|
+
# @param user_id [String] User identifier
|
|
97
|
+
# @param currency [String] Cryptocurrency code
|
|
98
|
+
# @param amount [Numeric] Amount to transfer
|
|
99
|
+
# @return [Hash] Transfer result
|
|
100
|
+
def transfer_to_sub_account(user_id:, currency:, amount:)
|
|
101
|
+
post("sub-partner/deposit-from-master", body: {
|
|
102
|
+
Name: user_id,
|
|
103
|
+
currency: currency,
|
|
104
|
+
amount: amount
|
|
105
|
+
}).body
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Write-off (withdraw) funds from sub-account to master account
|
|
109
|
+
# POST /v1/sub-partner/write-off
|
|
110
|
+
# @param user_id [String] User identifier
|
|
111
|
+
# @param currency [String] Cryptocurrency code
|
|
112
|
+
# @param amount [Numeric] Amount to withdraw
|
|
113
|
+
# @return [Hash] Write-off result
|
|
114
|
+
def withdraw_from_sub_account(user_id:, currency:, amount:)
|
|
115
|
+
post("sub-partner/write-off", body: {
|
|
116
|
+
Name: user_id,
|
|
117
|
+
currency: currency,
|
|
118
|
+
amount: amount
|
|
119
|
+
}).body
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Get details of a specific transfer
|
|
123
|
+
# GET /v1/sub-partner/transfer
|
|
124
|
+
# @param transfer_id [String, Integer] Transfer ID
|
|
125
|
+
# @return [Hash] Transfer details
|
|
126
|
+
def sub_account_transfer(transfer_id)
|
|
127
|
+
get("sub-partner/transfer", params: { id: transfer_id }).body
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Get list of all transfers
|
|
131
|
+
# GET /v1/sub-partner/transfers
|
|
132
|
+
# @param id [String, Integer, Array, nil] Filter by specific transfer ID(s)
|
|
133
|
+
# @param status [String, Array, nil] Filter by status (CREATED, WAITING, FINISHED, REJECTED)
|
|
134
|
+
# @param limit [Integer] Results per page
|
|
135
|
+
# @param offset [Integer] Offset for pagination
|
|
136
|
+
# @param order [String] Sort order (ASC or DESC)
|
|
137
|
+
# @return [Hash] List of transfers
|
|
138
|
+
def sub_account_transfers(id: nil, status: nil, limit: 10, offset: 0, order: "ASC")
|
|
139
|
+
params = { limit: limit, offset: offset, order: order }
|
|
140
|
+
params[:id] = id if id
|
|
141
|
+
params[:status] = status if status
|
|
142
|
+
|
|
143
|
+
get("sub-partner/transfers", params: params).body
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|