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
data/docs/API.md
ADDED
|
@@ -0,0 +1,922 @@
|
|
|
1
|
+
# NOWPayments Ruby SDK - API Reference
|
|
2
|
+
|
|
3
|
+
Complete API documentation with all available methods and usage examples.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Authentication](#authentication)
|
|
8
|
+
- [JWT Authentication](#jwt-authentication)
|
|
9
|
+
- [Status API](#status-api)
|
|
10
|
+
- [Payments API](#payments-api)
|
|
11
|
+
- [Invoices API](#invoices-api)
|
|
12
|
+
- [Estimates API](#estimates-api)
|
|
13
|
+
- [Recurring Payments API](#recurring-payments-api)
|
|
14
|
+
- [Mass Payouts API](#mass-payouts-api)
|
|
15
|
+
- [Conversions API](#conversions-api)
|
|
16
|
+
- [Custody API](#custody-api)
|
|
17
|
+
- [Error Handling](#error-handling)
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Authentication
|
|
22
|
+
|
|
23
|
+
### Initialize Client
|
|
24
|
+
|
|
25
|
+
```ruby
|
|
26
|
+
require 'nowpayments'
|
|
27
|
+
|
|
28
|
+
# Production
|
|
29
|
+
client = NOWPayments::Client.new(
|
|
30
|
+
api_key: ENV['NOWPAYMENTS_API_KEY'],
|
|
31
|
+
ipn_secret: ENV['NOWPAYMENTS_IPN_SECRET'] # Optional, for webhook verification
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
# Sandbox (for testing)
|
|
35
|
+
client = NOWPayments::Client.new(
|
|
36
|
+
api_key: ENV['NOWPAYMENTS_SANDBOX_API_KEY'],
|
|
37
|
+
ipn_secret: ENV['NOWPAYMENTS_SANDBOX_IPN_SECRET'],
|
|
38
|
+
sandbox: true
|
|
39
|
+
)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### JWT Authentication
|
|
43
|
+
|
|
44
|
+
Some endpoints require JWT authentication (token expires after 5 minutes):
|
|
45
|
+
|
|
46
|
+
**Required for:**
|
|
47
|
+
- Mass Payouts (create, verify, list)
|
|
48
|
+
- Conversions (all endpoints)
|
|
49
|
+
- Custody Operations (transfers, write-offs)
|
|
50
|
+
- Recurring Payments (delete)
|
|
51
|
+
|
|
52
|
+
#### `authenticate(email:, password:)`
|
|
53
|
+
|
|
54
|
+
Authenticate and obtain a JWT token (valid for 5 minutes).
|
|
55
|
+
|
|
56
|
+
```ruby
|
|
57
|
+
response = client.authenticate(
|
|
58
|
+
email: 'your_email@example.com',
|
|
59
|
+
password: 'your_password'
|
|
60
|
+
)
|
|
61
|
+
# => {"token" => "eyJhbGc..."}
|
|
62
|
+
|
|
63
|
+
# Token is automatically stored and injected in subsequent requests
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
#### `jwt_token(email:, password:)`
|
|
67
|
+
|
|
68
|
+
Get current JWT token, optionally refreshing if expired.
|
|
69
|
+
|
|
70
|
+
```ruby
|
|
71
|
+
# Get current token without refresh
|
|
72
|
+
token = client.jwt_token
|
|
73
|
+
# => "eyJhbGc..." or nil if expired/not authenticated
|
|
74
|
+
|
|
75
|
+
# Get token with auto-refresh if expired
|
|
76
|
+
token = client.jwt_token(
|
|
77
|
+
email: 'your_email@example.com',
|
|
78
|
+
password: 'your_password'
|
|
79
|
+
)
|
|
80
|
+
# => "eyJhbGc..."
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
#### `jwt_expired?`
|
|
84
|
+
|
|
85
|
+
Check if JWT token is expired or missing.
|
|
86
|
+
|
|
87
|
+
```ruby
|
|
88
|
+
client.jwt_expired? # => false
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
#### `jwt_time_remaining`
|
|
92
|
+
|
|
93
|
+
Get seconds until JWT token expires.
|
|
94
|
+
|
|
95
|
+
```ruby
|
|
96
|
+
client.jwt_time_remaining # => 287
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
#### `clear_jwt_token`
|
|
100
|
+
|
|
101
|
+
Manually clear stored JWT token (optional, for security).
|
|
102
|
+
|
|
103
|
+
```ruby
|
|
104
|
+
client.clear_jwt_token
|
|
105
|
+
client.jwt_expired? # => true
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**Auto-refresh pattern:**
|
|
109
|
+
|
|
110
|
+
```ruby
|
|
111
|
+
EMAIL = 'your_email@example.com'
|
|
112
|
+
PASSWORD = 'your_password'
|
|
113
|
+
|
|
114
|
+
def ensure_authenticated(client, email, password)
|
|
115
|
+
return unless client.jwt_expired?
|
|
116
|
+
client.authenticate(email: email, password: password)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Before each JWT-required operation
|
|
120
|
+
ensure_authenticated(client, EMAIL, PASSWORD)
|
|
121
|
+
payout = client.create_payout(...)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Status API
|
|
127
|
+
|
|
128
|
+
### `status`
|
|
129
|
+
|
|
130
|
+
Get API status (uptime, latency).
|
|
131
|
+
|
|
132
|
+
```ruby
|
|
133
|
+
status = client.status
|
|
134
|
+
# => {"message" => "OK"}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### `currencies`
|
|
138
|
+
|
|
139
|
+
Get list of available currencies.
|
|
140
|
+
|
|
141
|
+
```ruby
|
|
142
|
+
currencies = client.currencies
|
|
143
|
+
# => {"currencies" => ["btc", "eth", "usdt", ...]}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### `currencies_full`
|
|
147
|
+
|
|
148
|
+
Get detailed currency information including logos, networks, and limits.
|
|
149
|
+
|
|
150
|
+
```ruby
|
|
151
|
+
currencies = client.currencies_full
|
|
152
|
+
# => {"currencies" => [{"currency" => "btc", "logo_url" => "...", "network" => "BTC", ...}, ...]}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### `merchant_currencies`
|
|
156
|
+
|
|
157
|
+
Get currencies available to your merchant account.
|
|
158
|
+
|
|
159
|
+
```ruby
|
|
160
|
+
currencies = client.merchant_currencies
|
|
161
|
+
# => {"currencies" => ["btc", "eth", ...]}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### `selected_currencies`
|
|
165
|
+
|
|
166
|
+
Get currencies you've enabled in your account settings.
|
|
167
|
+
|
|
168
|
+
```ruby
|
|
169
|
+
currencies = client.selected_currencies
|
|
170
|
+
# => {"currencies" => ["btc", "eth", "usdt"]}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## Payments API
|
|
176
|
+
|
|
177
|
+
### `create_payment(price_amount:, price_currency:, pay_currency:, **params)`
|
|
178
|
+
|
|
179
|
+
Create a new payment.
|
|
180
|
+
|
|
181
|
+
```ruby
|
|
182
|
+
payment = client.create_payment(
|
|
183
|
+
price_amount: 100.0, # Amount in price_currency
|
|
184
|
+
price_currency: 'usd', # Fiat or crypto
|
|
185
|
+
pay_currency: 'btc', # Cryptocurrency customer pays with
|
|
186
|
+
order_id: 'order-123', # Your internal order ID
|
|
187
|
+
order_description: 'Pro Plan - Annual',
|
|
188
|
+
ipn_callback_url: 'https://example.com/webhooks/nowpayments',
|
|
189
|
+
success_url: 'https://example.com/success',
|
|
190
|
+
cancel_url: 'https://example.com/cancel'
|
|
191
|
+
)
|
|
192
|
+
# => {
|
|
193
|
+
# "payment_id" => "5729887098",
|
|
194
|
+
# "payment_status" => "waiting",
|
|
195
|
+
# "pay_address" => "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh",
|
|
196
|
+
# "pay_amount" => 0.00123456,
|
|
197
|
+
# "pay_currency" => "btc",
|
|
198
|
+
# "price_amount" => 100.0,
|
|
199
|
+
# "price_currency" => "usd",
|
|
200
|
+
# "order_id" => "order-123",
|
|
201
|
+
# "expiration_estimate_date" => "2025-11-01T12:30:00.000Z"
|
|
202
|
+
# }
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### `payment(payment_id)`
|
|
206
|
+
|
|
207
|
+
Get payment status and details.
|
|
208
|
+
|
|
209
|
+
```ruby
|
|
210
|
+
payment = client.payment('5729887098')
|
|
211
|
+
# => {
|
|
212
|
+
# "payment_id" => "5729887098",
|
|
213
|
+
# "payment_status" => "finished",
|
|
214
|
+
# "pay_address" => "bc1q...",
|
|
215
|
+
# "pay_amount" => 0.00123456,
|
|
216
|
+
# "actually_paid" => 0.00123456,
|
|
217
|
+
# "outcome_amount" => 99.5,
|
|
218
|
+
# "outcome_currency" => "usd"
|
|
219
|
+
# }
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
**Payment statuses:**
|
|
223
|
+
- `waiting` - Waiting for customer to send cryptocurrency
|
|
224
|
+
- `confirming` - Payment received, waiting for confirmations
|
|
225
|
+
- `confirmed` - Payment confirmed
|
|
226
|
+
- `sending` - Sending to your wallet
|
|
227
|
+
- `partially_paid` - Customer sent wrong amount
|
|
228
|
+
- `finished` - Payment complete
|
|
229
|
+
- `failed` - Payment failed
|
|
230
|
+
- `refunded` - Payment refunded
|
|
231
|
+
- `expired` - Payment expired
|
|
232
|
+
|
|
233
|
+
### `minimum_payment_amount(currency_from:, currency_to:, fiat_equivalent:)`
|
|
234
|
+
|
|
235
|
+
Get minimum payment amount for a currency pair.
|
|
236
|
+
|
|
237
|
+
```ruby
|
|
238
|
+
minimum = client.minimum_payment_amount(
|
|
239
|
+
currency_from: 'btc',
|
|
240
|
+
currency_to: 'eth',
|
|
241
|
+
fiat_equivalent: 'usd'
|
|
242
|
+
)
|
|
243
|
+
# => {
|
|
244
|
+
# "currency_from" => "btc",
|
|
245
|
+
# "currency_to" => "eth",
|
|
246
|
+
# "fiat_equivalent" => "usd",
|
|
247
|
+
# "min_amount" => 0.0001
|
|
248
|
+
# }
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### `balance`
|
|
252
|
+
|
|
253
|
+
Get your account balance.
|
|
254
|
+
|
|
255
|
+
```ruby
|
|
256
|
+
balance = client.balance
|
|
257
|
+
# => {
|
|
258
|
+
# "btc" => 0.5,
|
|
259
|
+
# "eth" => 10.0,
|
|
260
|
+
# "usdt" => 1000.0
|
|
261
|
+
# }
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
## Invoices API
|
|
267
|
+
|
|
268
|
+
Create hosted payment pages where customers can choose from 150+ cryptocurrencies.
|
|
269
|
+
|
|
270
|
+
### `create_invoice(price_amount:, price_currency:, order_id:, **params)`
|
|
271
|
+
|
|
272
|
+
Create a payment invoice with hosted page.
|
|
273
|
+
|
|
274
|
+
```ruby
|
|
275
|
+
invoice = client.create_invoice(
|
|
276
|
+
price_amount: 99.0,
|
|
277
|
+
price_currency: 'usd',
|
|
278
|
+
order_id: "inv-#{order.id}",
|
|
279
|
+
order_description: 'Pro Plan - Monthly',
|
|
280
|
+
success_url: 'https://example.com/thank-you',
|
|
281
|
+
cancel_url: 'https://example.com/checkout',
|
|
282
|
+
ipn_callback_url: 'https://example.com/webhooks/nowpayments'
|
|
283
|
+
)
|
|
284
|
+
# => {
|
|
285
|
+
# "id" => "5824448584",
|
|
286
|
+
# "order_id" => "inv-123",
|
|
287
|
+
# "order_description" => "Pro Plan - Monthly",
|
|
288
|
+
# "price_amount" => 99.0,
|
|
289
|
+
# "price_currency" => "usd",
|
|
290
|
+
# "invoice_url" => "https://nowpayments.io/payment/?iid=5824448584",
|
|
291
|
+
# "created_at" => "2025-11-01T12:00:00.000Z"
|
|
292
|
+
# }
|
|
293
|
+
|
|
294
|
+
# Redirect customer to invoice_url
|
|
295
|
+
redirect_to invoice['invoice_url']
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### `invoice(invoice_id)`
|
|
299
|
+
|
|
300
|
+
Get invoice details and payment status.
|
|
301
|
+
|
|
302
|
+
```ruby
|
|
303
|
+
invoice = client.invoice('5824448584')
|
|
304
|
+
# => {
|
|
305
|
+
# "id" => "5824448584",
|
|
306
|
+
# "payment_status" => "finished",
|
|
307
|
+
# "pay_currency" => "btc",
|
|
308
|
+
# "pay_amount" => 0.00234567,
|
|
309
|
+
# ...
|
|
310
|
+
# }
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
## Estimates API
|
|
316
|
+
|
|
317
|
+
### `estimate(amount:, currency_from:, currency_to:)`
|
|
318
|
+
|
|
319
|
+
Estimate exchange amount between currencies.
|
|
320
|
+
|
|
321
|
+
```ruby
|
|
322
|
+
estimate = client.estimate(
|
|
323
|
+
amount: 100,
|
|
324
|
+
currency_from: 'usd',
|
|
325
|
+
currency_to: 'btc'
|
|
326
|
+
)
|
|
327
|
+
# => {
|
|
328
|
+
# "currency_from" => "usd",
|
|
329
|
+
# "amount_from" => 100,
|
|
330
|
+
# "currency_to" => "btc",
|
|
331
|
+
# "estimated_amount" => 0.00234567
|
|
332
|
+
# }
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
## Recurring Payments API
|
|
338
|
+
|
|
339
|
+
Create and manage subscription billing.
|
|
340
|
+
|
|
341
|
+
### `create_recurring_payment(price_amount:, price_currency:, pay_currency:, **params)`
|
|
342
|
+
|
|
343
|
+
Create a recurring payment plan.
|
|
344
|
+
|
|
345
|
+
```ruby
|
|
346
|
+
subscription = client.create_recurring_payment(
|
|
347
|
+
price_amount: 29.99,
|
|
348
|
+
price_currency: 'usd',
|
|
349
|
+
pay_currency: 'btc',
|
|
350
|
+
order_id: "sub-#{subscription.id}",
|
|
351
|
+
order_description: 'Monthly Subscription',
|
|
352
|
+
period: 'month', # 'day', 'week', 'month', 'year'
|
|
353
|
+
ipn_callback_url: 'https://example.com/webhooks/nowpayments'
|
|
354
|
+
)
|
|
355
|
+
# => {
|
|
356
|
+
# "id" => "recurring_123",
|
|
357
|
+
# "order_id" => "sub-456",
|
|
358
|
+
# "price_amount" => 29.99,
|
|
359
|
+
# "price_currency" => "usd",
|
|
360
|
+
# "pay_currency" => "btc",
|
|
361
|
+
# "period" => "month",
|
|
362
|
+
# "status" => "active"
|
|
363
|
+
# }
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### `recurring_payment(recurring_payment_id)`
|
|
367
|
+
|
|
368
|
+
Get recurring payment details.
|
|
369
|
+
|
|
370
|
+
```ruby
|
|
371
|
+
subscription = client.recurring_payment('recurring_123')
|
|
372
|
+
# => {
|
|
373
|
+
# "id" => "recurring_123",
|
|
374
|
+
# "status" => "active",
|
|
375
|
+
# "next_payment_date" => "2025-12-01T00:00:00.000Z",
|
|
376
|
+
# ...
|
|
377
|
+
# }
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### `delete_recurring_payment(recurring_payment_id)`
|
|
381
|
+
|
|
382
|
+
**Requires JWT authentication.**
|
|
383
|
+
|
|
384
|
+
Cancel a recurring payment.
|
|
385
|
+
|
|
386
|
+
```ruby
|
|
387
|
+
# Authenticate first
|
|
388
|
+
client.authenticate(email: 'your@email.com', password: 'password')
|
|
389
|
+
|
|
390
|
+
# Delete recurring payment
|
|
391
|
+
result = client.delete_recurring_payment('recurring_123')
|
|
392
|
+
# => {"result" => true}
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
---
|
|
396
|
+
|
|
397
|
+
## Mass Payouts API
|
|
398
|
+
|
|
399
|
+
**All payout endpoints require JWT authentication.**
|
|
400
|
+
|
|
401
|
+
### `create_payout(withdrawals:, **params)`
|
|
402
|
+
|
|
403
|
+
**Requires JWT authentication.**
|
|
404
|
+
|
|
405
|
+
Create a batch payout to multiple addresses.
|
|
406
|
+
|
|
407
|
+
```ruby
|
|
408
|
+
# Authenticate first
|
|
409
|
+
client.authenticate(email: 'your@email.com', password: 'password')
|
|
410
|
+
|
|
411
|
+
# Create payout
|
|
412
|
+
payout = client.create_payout(
|
|
413
|
+
withdrawals: [
|
|
414
|
+
{
|
|
415
|
+
address: 'TEmGwPeRTPiLFLVfBxXkSP91yc5GMNQhfS',
|
|
416
|
+
currency: 'trx',
|
|
417
|
+
amount: 10,
|
|
418
|
+
extra_id: nil # Required for some currencies (XRP, XLM, etc.)
|
|
419
|
+
},
|
|
420
|
+
{
|
|
421
|
+
address: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb',
|
|
422
|
+
currency: 'eth',
|
|
423
|
+
amount: 0.1
|
|
424
|
+
}
|
|
425
|
+
],
|
|
426
|
+
payout_description: 'Weekly affiliate payouts'
|
|
427
|
+
)
|
|
428
|
+
# => {
|
|
429
|
+
# "id" => "batch_123",
|
|
430
|
+
# "withdrawals" => [
|
|
431
|
+
# {
|
|
432
|
+
# "id" => "withdrawal_456",
|
|
433
|
+
# "address" => "TEmGw...",
|
|
434
|
+
# "currency" => "trx",
|
|
435
|
+
# "amount" => 10,
|
|
436
|
+
# "status" => "pending"
|
|
437
|
+
# },
|
|
438
|
+
# ...
|
|
439
|
+
# ]
|
|
440
|
+
# }
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
### `verify_payout(batch_withdrawal_id:, verification_code:)`
|
|
444
|
+
|
|
445
|
+
**Requires JWT authentication.**
|
|
446
|
+
|
|
447
|
+
Verify payout with 2FA code (from Google Authenticator or email).
|
|
448
|
+
|
|
449
|
+
```ruby
|
|
450
|
+
# Authenticate first
|
|
451
|
+
client.authenticate(email: 'your@email.com', password: 'password')
|
|
452
|
+
|
|
453
|
+
# Verify with 2FA code
|
|
454
|
+
result = client.verify_payout(
|
|
455
|
+
batch_withdrawal_id: 'batch_123',
|
|
456
|
+
verification_code: '123456' # From Google Authenticator
|
|
457
|
+
)
|
|
458
|
+
# => {"result" => true}
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
### `list_payouts(limit:, offset:)`
|
|
462
|
+
|
|
463
|
+
**Requires JWT authentication.**
|
|
464
|
+
|
|
465
|
+
List all payouts.
|
|
466
|
+
|
|
467
|
+
```ruby
|
|
468
|
+
# Authenticate first
|
|
469
|
+
client.authenticate(email: 'your@email.com', password: 'password')
|
|
470
|
+
|
|
471
|
+
# List payouts
|
|
472
|
+
payouts = client.list_payouts(limit: 10, offset: 0)
|
|
473
|
+
# => {
|
|
474
|
+
# "count" => 5,
|
|
475
|
+
# "data" => [
|
|
476
|
+
# {
|
|
477
|
+
# "id" => "batch_123",
|
|
478
|
+
# "status" => "verified",
|
|
479
|
+
# "created_at" => "2025-11-01T12:00:00.000Z",
|
|
480
|
+
# ...
|
|
481
|
+
# },
|
|
482
|
+
# ...
|
|
483
|
+
# ]
|
|
484
|
+
# }
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
---
|
|
488
|
+
|
|
489
|
+
## Conversions API
|
|
490
|
+
|
|
491
|
+
**All conversion endpoints require JWT authentication.**
|
|
492
|
+
|
|
493
|
+
Convert between cryptocurrencies at market rates.
|
|
494
|
+
|
|
495
|
+
### `create_conversion(from_currency:, to_currency:, amount:)`
|
|
496
|
+
|
|
497
|
+
**Requires JWT authentication.**
|
|
498
|
+
|
|
499
|
+
Create a currency conversion.
|
|
500
|
+
|
|
501
|
+
```ruby
|
|
502
|
+
# Authenticate first
|
|
503
|
+
client.authenticate(email: 'your@email.com', password: 'password')
|
|
504
|
+
|
|
505
|
+
# Create conversion
|
|
506
|
+
conversion = client.create_conversion(
|
|
507
|
+
from_currency: 'btc',
|
|
508
|
+
to_currency: 'eth',
|
|
509
|
+
amount: 0.1
|
|
510
|
+
)
|
|
511
|
+
# => {
|
|
512
|
+
# "conversion_id" => "conversion_123",
|
|
513
|
+
# "from_currency" => "btc",
|
|
514
|
+
# "to_currency" => "eth",
|
|
515
|
+
# "from_amount" => 0.1,
|
|
516
|
+
# "to_amount" => 2.5,
|
|
517
|
+
# "status" => "processing"
|
|
518
|
+
# }
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
### `conversion_status(conversion_id)`
|
|
522
|
+
|
|
523
|
+
**Requires JWT authentication.**
|
|
524
|
+
|
|
525
|
+
Check conversion status.
|
|
526
|
+
|
|
527
|
+
```ruby
|
|
528
|
+
# Authenticate first
|
|
529
|
+
client.authenticate(email: 'your@email.com', password: 'password')
|
|
530
|
+
|
|
531
|
+
# Check status
|
|
532
|
+
status = client.conversion_status('conversion_123')
|
|
533
|
+
# => {
|
|
534
|
+
# "conversion_id" => "conversion_123",
|
|
535
|
+
# "status" => "completed",
|
|
536
|
+
# "from_currency" => "btc",
|
|
537
|
+
# "to_currency" => "eth",
|
|
538
|
+
# "from_amount" => 0.1,
|
|
539
|
+
# "to_amount" => 2.5
|
|
540
|
+
# }
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
### `list_conversions(limit:, offset:)`
|
|
544
|
+
|
|
545
|
+
**Requires JWT authentication.**
|
|
546
|
+
|
|
547
|
+
List all conversions.
|
|
548
|
+
|
|
549
|
+
```ruby
|
|
550
|
+
# Authenticate first
|
|
551
|
+
client.authenticate(email: 'your@email.com', password: 'password')
|
|
552
|
+
|
|
553
|
+
# List conversions
|
|
554
|
+
conversions = client.list_conversions(limit: 10, offset: 0)
|
|
555
|
+
# => {
|
|
556
|
+
# "count" => 3,
|
|
557
|
+
# "data" => [
|
|
558
|
+
# {
|
|
559
|
+
# "conversion_id" => "conversion_123",
|
|
560
|
+
# "status" => "completed",
|
|
561
|
+
# "created_at" => "2025-11-01T12:00:00.000Z",
|
|
562
|
+
# ...
|
|
563
|
+
# },
|
|
564
|
+
# ...
|
|
565
|
+
# ]
|
|
566
|
+
# }
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
---
|
|
570
|
+
|
|
571
|
+
## Custody API
|
|
572
|
+
|
|
573
|
+
Manage sub-accounts for users (marketplaces, casinos, exchanges).
|
|
574
|
+
|
|
575
|
+
### `create_sub_account(user_id:)`
|
|
576
|
+
|
|
577
|
+
Create a sub-account for a user.
|
|
578
|
+
|
|
579
|
+
```ruby
|
|
580
|
+
sub_account = client.create_sub_account(user_id: 'user_123')
|
|
581
|
+
# => {
|
|
582
|
+
# "result" => {
|
|
583
|
+
# "id" => 123456,
|
|
584
|
+
# "user_id" => "user_123",
|
|
585
|
+
# "created_at" => "2025-11-01T12:00:00.000Z"
|
|
586
|
+
# }
|
|
587
|
+
# }
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
### `create_sub_account_deposit(user_id:, currency:)`
|
|
591
|
+
|
|
592
|
+
Generate deposit address for user's wallet.
|
|
593
|
+
|
|
594
|
+
```ruby
|
|
595
|
+
deposit = client.create_sub_account_deposit(
|
|
596
|
+
user_id: 'user_123',
|
|
597
|
+
currency: 'btc'
|
|
598
|
+
)
|
|
599
|
+
# => {
|
|
600
|
+
# "result" => {
|
|
601
|
+
# "user_id" => "user_123",
|
|
602
|
+
# "currency" => "btc",
|
|
603
|
+
# "address" => "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh"
|
|
604
|
+
# }
|
|
605
|
+
# }
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
### `sub_account_balances(user_id:)`
|
|
609
|
+
|
|
610
|
+
Get user's sub-account balance.
|
|
611
|
+
|
|
612
|
+
```ruby
|
|
613
|
+
balances = client.sub_account_balances(user_id: 'user_123')
|
|
614
|
+
# => {
|
|
615
|
+
# "result" => {
|
|
616
|
+
# "balances" => {
|
|
617
|
+
# "btc" => 0.5,
|
|
618
|
+
# "eth" => 10.0,
|
|
619
|
+
# "usdt" => 1000.0
|
|
620
|
+
# }
|
|
621
|
+
# }
|
|
622
|
+
# }
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
### `transfer_to_sub_account(user_id:, currency:, amount:)`
|
|
626
|
+
|
|
627
|
+
Transfer funds from main account to sub-account.
|
|
628
|
+
|
|
629
|
+
```ruby
|
|
630
|
+
transfer = client.transfer_to_sub_account(
|
|
631
|
+
user_id: 'user_123',
|
|
632
|
+
currency: 'btc',
|
|
633
|
+
amount: 0.1
|
|
634
|
+
)
|
|
635
|
+
# => {
|
|
636
|
+
# "result" => {
|
|
637
|
+
# "id" => "transfer_456",
|
|
638
|
+
# "user_id" => "user_123",
|
|
639
|
+
# "currency" => "btc",
|
|
640
|
+
# "amount" => 0.1,
|
|
641
|
+
# "status" => "completed"
|
|
642
|
+
# }
|
|
643
|
+
# }
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
### `transfer_between_sub_accounts(currency:, amount:, from_id:, to_id:)`
|
|
647
|
+
|
|
648
|
+
**Requires JWT authentication.**
|
|
649
|
+
|
|
650
|
+
Transfer between two sub-accounts.
|
|
651
|
+
|
|
652
|
+
```ruby
|
|
653
|
+
# Authenticate first
|
|
654
|
+
client.authenticate(email: 'your@email.com', password: 'password')
|
|
655
|
+
|
|
656
|
+
# Transfer between users
|
|
657
|
+
transfer = client.transfer_between_sub_accounts(
|
|
658
|
+
currency: 'btc',
|
|
659
|
+
amount: 0.05,
|
|
660
|
+
from_id: 'user_123',
|
|
661
|
+
to_id: 'user_456'
|
|
662
|
+
)
|
|
663
|
+
# => {
|
|
664
|
+
# "result" => {
|
|
665
|
+
# "id" => "transfer_789",
|
|
666
|
+
# "from_id" => "user_123",
|
|
667
|
+
# "to_id" => "user_456",
|
|
668
|
+
# "currency" => "btc",
|
|
669
|
+
# "amount" => 0.05,
|
|
670
|
+
# "status" => "completed"
|
|
671
|
+
# }
|
|
672
|
+
# }
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
### `withdraw_from_sub_account(user_id:, currency:, amount:, address:)`
|
|
676
|
+
|
|
677
|
+
Withdraw funds from sub-account to external address.
|
|
678
|
+
|
|
679
|
+
```ruby
|
|
680
|
+
withdrawal = client.withdraw_from_sub_account(
|
|
681
|
+
user_id: 'user_123',
|
|
682
|
+
currency: 'btc',
|
|
683
|
+
amount: 0.05,
|
|
684
|
+
address: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh'
|
|
685
|
+
)
|
|
686
|
+
# => {
|
|
687
|
+
# "result" => {
|
|
688
|
+
# "id" => "withdrawal_789",
|
|
689
|
+
# "user_id" => "user_123",
|
|
690
|
+
# "currency" => "btc",
|
|
691
|
+
# "amount" => 0.05,
|
|
692
|
+
# "address" => "bc1q...",
|
|
693
|
+
# "status" => "processing"
|
|
694
|
+
# }
|
|
695
|
+
# }
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
### `write_off_sub_account_balance(user_id:, currency:, amount:, external_id:)`
|
|
699
|
+
|
|
700
|
+
**Requires JWT authentication.**
|
|
701
|
+
|
|
702
|
+
Write off (deduct) balance from sub-account.
|
|
703
|
+
|
|
704
|
+
```ruby
|
|
705
|
+
# Authenticate first
|
|
706
|
+
client.authenticate(email: 'your@email.com', password: 'password')
|
|
707
|
+
|
|
708
|
+
# Write off balance
|
|
709
|
+
result = client.write_off_sub_account_balance(
|
|
710
|
+
user_id: 'user_123',
|
|
711
|
+
currency: 'btc',
|
|
712
|
+
amount: 0.01,
|
|
713
|
+
external_id: 'fee_charge_789'
|
|
714
|
+
)
|
|
715
|
+
# => {
|
|
716
|
+
# "result" => {
|
|
717
|
+
# "user_id" => "user_123",
|
|
718
|
+
# "currency" => "btc",
|
|
719
|
+
# "amount" => 0.01,
|
|
720
|
+
# "external_id" => "fee_charge_789",
|
|
721
|
+
# "status" => "completed"
|
|
722
|
+
# }
|
|
723
|
+
# }
|
|
724
|
+
```
|
|
725
|
+
|
|
726
|
+
---
|
|
727
|
+
|
|
728
|
+
## Error Handling
|
|
729
|
+
|
|
730
|
+
The SDK raises specific exceptions for different error types:
|
|
731
|
+
|
|
732
|
+
```ruby
|
|
733
|
+
begin
|
|
734
|
+
payment = client.create_payment(...)
|
|
735
|
+
|
|
736
|
+
rescue NOWPayments::AuthenticationError => e
|
|
737
|
+
# 401 - Invalid API key
|
|
738
|
+
puts "Authentication failed: #{e.message}"
|
|
739
|
+
|
|
740
|
+
rescue NOWPayments::BadRequestError => e
|
|
741
|
+
# 400 - Invalid parameters
|
|
742
|
+
puts "Bad request: #{e.message}"
|
|
743
|
+
puts "Details: #{e.body}"
|
|
744
|
+
|
|
745
|
+
rescue NOWPayments::NotFoundError => e
|
|
746
|
+
# 404 - Resource not found
|
|
747
|
+
puts "Not found: #{e.message}"
|
|
748
|
+
|
|
749
|
+
rescue NOWPayments::UnprocessableEntityError => e
|
|
750
|
+
# 422 - Validation errors
|
|
751
|
+
puts "Validation failed: #{e.message}"
|
|
752
|
+
|
|
753
|
+
rescue NOWPayments::RateLimitError => e
|
|
754
|
+
# 429 - Too many requests
|
|
755
|
+
retry_after = e.headers['Retry-After']
|
|
756
|
+
puts "Rate limited. Retry after #{retry_after} seconds"
|
|
757
|
+
|
|
758
|
+
rescue NOWPayments::ServerError => e
|
|
759
|
+
# 500 - NOWPayments server error
|
|
760
|
+
puts "Server error: #{e.message}"
|
|
761
|
+
|
|
762
|
+
rescue NOWPayments::SecurityError => e
|
|
763
|
+
# Webhook signature verification failed
|
|
764
|
+
puts "Security error: #{e.message}"
|
|
765
|
+
|
|
766
|
+
rescue NOWPayments::ConnectionError => e
|
|
767
|
+
# Network/connection error
|
|
768
|
+
puts "Connection error: #{e.message}"
|
|
769
|
+
|
|
770
|
+
rescue NOWPayments::Error => e
|
|
771
|
+
# Generic API error
|
|
772
|
+
puts "API error: #{e.message}"
|
|
773
|
+
end
|
|
774
|
+
```
|
|
775
|
+
|
|
776
|
+
**Exception hierarchy:**
|
|
777
|
+
|
|
778
|
+
```
|
|
779
|
+
NOWPayments::Error
|
|
780
|
+
├── NOWPayments::AuthenticationError (401)
|
|
781
|
+
├── NOWPayments::BadRequestError (400)
|
|
782
|
+
├── NOWPayments::NotFoundError (404)
|
|
783
|
+
├── NOWPayments::UnprocessableEntityError (422)
|
|
784
|
+
├── NOWPayments::RateLimitError (429)
|
|
785
|
+
├── NOWPayments::ServerError (5xx)
|
|
786
|
+
├── NOWPayments::SecurityError (webhook verification)
|
|
787
|
+
└── NOWPayments::ConnectionError (network)
|
|
788
|
+
```
|
|
789
|
+
|
|
790
|
+
---
|
|
791
|
+
|
|
792
|
+
## Webhook Verification
|
|
793
|
+
|
|
794
|
+
**Critical:** Always verify webhook signatures to prevent fraud.
|
|
795
|
+
|
|
796
|
+
```ruby
|
|
797
|
+
# Rails controller
|
|
798
|
+
class WebhooksController < ApplicationController
|
|
799
|
+
skip_before_action :verify_authenticity_token
|
|
800
|
+
|
|
801
|
+
def nowpayments
|
|
802
|
+
# Verify signature - raises SecurityError if invalid
|
|
803
|
+
payload = NOWPayments::Rack.verify_webhook(
|
|
804
|
+
request,
|
|
805
|
+
ENV['NOWPAYMENTS_IPN_SECRET']
|
|
806
|
+
)
|
|
807
|
+
|
|
808
|
+
# Process payment
|
|
809
|
+
order = Order.find_by(id: payload['order_id'])
|
|
810
|
+
|
|
811
|
+
case payload['payment_status']
|
|
812
|
+
when 'finished'
|
|
813
|
+
order.mark_paid!
|
|
814
|
+
when 'failed', 'expired'
|
|
815
|
+
order.cancel!
|
|
816
|
+
end
|
|
817
|
+
|
|
818
|
+
head :ok
|
|
819
|
+
|
|
820
|
+
rescue NOWPayments::SecurityError => e
|
|
821
|
+
logger.error "Invalid webhook: #{e.message}"
|
|
822
|
+
head :forbidden
|
|
823
|
+
end
|
|
824
|
+
end
|
|
825
|
+
```
|
|
826
|
+
|
|
827
|
+
**Signature verification (low-level):**
|
|
828
|
+
|
|
829
|
+
```ruby
|
|
830
|
+
require 'openssl'
|
|
831
|
+
|
|
832
|
+
def verify_signature(payload, signature, secret)
|
|
833
|
+
hmac = OpenSSL::HMAC.hexdigest('SHA512', secret, payload)
|
|
834
|
+
|
|
835
|
+
# Use constant-time comparison to prevent timing attacks
|
|
836
|
+
secure_compare(hmac, signature)
|
|
837
|
+
end
|
|
838
|
+
|
|
839
|
+
def secure_compare(a, b)
|
|
840
|
+
return false unless a.bytesize == b.bytesize
|
|
841
|
+
|
|
842
|
+
l = a.unpack("C*")
|
|
843
|
+
r = 0
|
|
844
|
+
i = -1
|
|
845
|
+
|
|
846
|
+
b.each_byte { |byte| r |= byte ^ l[i += 1] }
|
|
847
|
+
r == 0
|
|
848
|
+
end
|
|
849
|
+
```
|
|
850
|
+
|
|
851
|
+
---
|
|
852
|
+
|
|
853
|
+
## Rate Limits
|
|
854
|
+
|
|
855
|
+
**Standard limits:**
|
|
856
|
+
- 60 requests per minute (standard endpoints)
|
|
857
|
+
- 10 requests per minute (payment creation)
|
|
858
|
+
|
|
859
|
+
**Best practices:**
|
|
860
|
+
- Implement exponential backoff on rate limit errors
|
|
861
|
+
- Cache currency lists and static data
|
|
862
|
+
- Use batch operations (payouts, invoices) when possible
|
|
863
|
+
- Monitor `Retry-After` header in rate limit responses
|
|
864
|
+
|
|
865
|
+
```ruby
|
|
866
|
+
def with_retry(max_retries: 3)
|
|
867
|
+
retries = 0
|
|
868
|
+
|
|
869
|
+
begin
|
|
870
|
+
yield
|
|
871
|
+
rescue NOWPayments::RateLimitError => e
|
|
872
|
+
retries += 1
|
|
873
|
+
if retries <= max_retries
|
|
874
|
+
retry_after = e.headers['Retry-After'].to_i
|
|
875
|
+
sleep retry_after
|
|
876
|
+
retry
|
|
877
|
+
else
|
|
878
|
+
raise
|
|
879
|
+
end
|
|
880
|
+
end
|
|
881
|
+
end
|
|
882
|
+
|
|
883
|
+
# Usage
|
|
884
|
+
with_retry do
|
|
885
|
+
client.create_payment(...)
|
|
886
|
+
end
|
|
887
|
+
```
|
|
888
|
+
|
|
889
|
+
---
|
|
890
|
+
|
|
891
|
+
## Testing
|
|
892
|
+
|
|
893
|
+
**Use sandbox for development:**
|
|
894
|
+
|
|
895
|
+
```ruby
|
|
896
|
+
client = NOWPayments::Client.new(
|
|
897
|
+
api_key: ENV['NOWPAYMENTS_SANDBOX_API_KEY'],
|
|
898
|
+
sandbox: true
|
|
899
|
+
)
|
|
900
|
+
```
|
|
901
|
+
|
|
902
|
+
**Get sandbox credentials:**
|
|
903
|
+
1. Create account at https://account-sandbox.nowpayments.io/
|
|
904
|
+
2. Generate API key from dashboard
|
|
905
|
+
3. Generate IPN secret for webhooks
|
|
906
|
+
4. Test with sandbox cryptocurrencies
|
|
907
|
+
|
|
908
|
+
**Sandbox test currencies:**
|
|
909
|
+
- All major cryptocurrencies available
|
|
910
|
+
- Instant confirmations (no waiting)
|
|
911
|
+
- Test payouts without real funds
|
|
912
|
+
- Test webhooks with ngrok
|
|
913
|
+
|
|
914
|
+
---
|
|
915
|
+
|
|
916
|
+
## Support
|
|
917
|
+
|
|
918
|
+
- [GitHub Issues](https://github.com/Sentia/nowpayments/issues)
|
|
919
|
+
- [NOWPayments Support](https://nowpayments.io/help)
|
|
920
|
+
- [API Documentation](https://documenter.getpostman.com/view/7907941/2s93JusNJt)
|
|
921
|
+
- [Sandbox Dashboard](https://account-sandbox.nowpayments.io/)
|
|
922
|
+
- [Production Dashboard](https://nowpayments.io/)
|