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.
data/README.md ADDED
@@ -0,0 +1,578 @@
1
+ # NOWPayments Ruby SDK
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/nowpayments.svg)](https://badge.fury.io/rb/nowpayments)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+
6
+ Production-ready Ruby wrapper for the [NOWPayments API](https://documenter.getpostman.com/view/7907941/2s93JusNJt). Accept cryptocurrency payments with minimal code.
7
+
8
+ ## Why NOWPayments?
9
+
10
+ - **150+ cryptocurrencies** - Bitcoin, Ethereum, USDT, and more
11
+ - **No KYC required** - Accept payments immediately
12
+ - **Instant settlement** - Real-time payment processing
13
+ - **Low fees** - Competitive transaction costs
14
+ - **Global reach** - Accept payments from anywhere
15
+
16
+ ## Installation
17
+
18
+ Add to your Gemfile:
19
+
20
+ ```ruby
21
+ gem 'nowpayments', git: 'https://github.com/Sentia/nowpayments'
22
+ ```
23
+
24
+ Or install directly:
25
+
26
+ ```bash
27
+ gem install nowpayments
28
+ ```
29
+
30
+ ## Quick Start
31
+
32
+ ```ruby
33
+ require 'nowpayments'
34
+
35
+ # Initialize client (sandbox for testing, production when ready)
36
+ client = NOWPayments::Client.new(
37
+ api_key: ENV['NOWPAYMENTS_API_KEY'],
38
+ sandbox: true
39
+ )
40
+
41
+ # Create a payment
42
+ payment = client.create_payment(
43
+ price_amount: 100.0,
44
+ price_currency: 'usd',
45
+ pay_currency: 'btc',
46
+ order_id: 'order-123',
47
+ ipn_callback_url: 'https://yourdomain.com/webhooks/nowpayments'
48
+ )
49
+
50
+ puts "Payment address: #{payment['pay_address']}"
51
+ puts "Amount: #{payment['pay_amount']} BTC"
52
+ puts "Status: #{payment['payment_status']}"
53
+ ```
54
+
55
+ ## Features
56
+
57
+ ### 🎉 Complete API Coverage - 57 Methods, 100% Coverage!
58
+
59
+ **11 API Modules with Full Implementation:**
60
+
61
+ 1. **Authentication (5 methods)** - JWT token management for protected endpoints
62
+ 2. **Status (1 method)** - API health checks
63
+ 3. **Currencies (3 methods)** - Available cryptocurrencies and details
64
+ 4. **Payments (4 methods)** - Create and track cryptocurrency payments
65
+ 5. **Invoices (3 methods)** - Hosted payment pages with status tracking
66
+ 6. **Estimates (2 methods)** - Price calculations and minimum amounts
67
+ 7. **Mass Payouts (8 methods)** - Batch withdrawals with 2FA verification
68
+ 8. **Conversions (3 methods)** - Currency conversions at market rates
69
+ 9. **Subscriptions (9 methods)** - Recurring payment plans and billing
70
+ 10. **Custody/Sub-accounts (11 methods)** - User wallet management for marketplaces
71
+ 11. **Fiat Payouts (8 methods)** - Beta: Crypto to fiat withdrawals
72
+
73
+ **Security & Production Ready:**
74
+ - **JWT Authentication** - Bearer token support for sensitive operations
75
+ - **Webhook Verification** - HMAC-SHA512 signature validation
76
+ - **Constant-time comparison** - Prevents timing attacks
77
+ - **Comprehensive error handling** - 8 exception classes with detailed messages
78
+ - **100% tested** - 23 passing tests, RuboCop clean
79
+
80
+ ### Complete Method List (57 Methods)
81
+
82
+ <details>
83
+ <summary><b>Authentication (5 methods)</b> - JWT token management</summary>
84
+
85
+ - `authenticate(email:, password:)` - Get JWT token (5-min expiry)
86
+ - `jwt_token(email:, password:)` - Get token with auto-refresh
87
+ - `jwt_expired?` - Check if token is expired
88
+ - `clear_jwt_token` - Clear stored token
89
+ - `jwt_time_remaining` - Seconds until expiry
90
+
91
+ </details>
92
+
93
+ <details>
94
+ <summary><b>Status & Currencies (4 methods)</b> - API health and currency info</summary>
95
+
96
+ - `status` - Check API status
97
+ - `currencies(fixed_rate: nil)` - Get available currencies
98
+ - `full_currencies` - Detailed currency information
99
+ - `merchant_coins` - Your enabled currencies
100
+
101
+ </details>
102
+
103
+ <details>
104
+ <summary><b>Payments (4 methods)</b> - Standard cryptocurrency payments</summary>
105
+
106
+ - `create_payment(...)` - Create new payment
107
+ - `payment(payment_id)` - Get payment status
108
+ - `payments(limit:, page:, ...)` - List payments with filters
109
+ - `update_payment_estimate(payment_id)` - Update exchange rate
110
+
111
+ </details>
112
+
113
+ <details>
114
+ <summary><b>Invoices (3 methods)</b> - Hosted payment pages</summary>
115
+
116
+ - `create_invoice(...)` - Create invoice with payment page
117
+ - `create_invoice_payment(...)` - Create payment by invoice ID
118
+ - `invoice(invoice_id)` - Get invoice status
119
+
120
+ </details>
121
+
122
+ <details>
123
+ <summary><b>Estimates (2 methods)</b> - Price calculations</summary>
124
+
125
+ - `estimate(amount:, currency_from:, currency_to:)` - Price estimate
126
+ - `min_amount(currency_from:, currency_to:)` - Minimum payment amount
127
+
128
+ </details>
129
+
130
+ <details>
131
+ <summary><b>Mass Payouts (8 methods)</b> - Batch withdrawals (JWT required)</summary>
132
+
133
+ - `balance` - Get account balance
134
+ - `create_payout(withdrawals:, ...)` - Create batch payout (JWT)
135
+ - `verify_payout(batch_withdrawal_id:, verification_code:)` - 2FA verify (JWT)
136
+ - `payout_status(payout_id)` - Get payout status
137
+ - `list_payouts(limit:, offset:)` - List all payouts (JWT)
138
+ - `validate_payout_address(address:, currency:, ...)` - Validate address
139
+ - `min_payout_amount(currency:)` - Minimum payout amount
140
+ - `payout_fee(currency:, amount:)` - Calculate payout fee
141
+
142
+ </details>
143
+
144
+ <details>
145
+ <summary><b>Conversions (3 methods)</b> - Currency conversions (JWT required)</summary>
146
+
147
+ - `create_conversion(from_currency:, to_currency:, amount:)` - Convert crypto (JWT)
148
+ - `conversion_status(conversion_id)` - Check conversion status (JWT)
149
+ - `list_conversions(limit:, offset:)` - List all conversions (JWT)
150
+
151
+ </details>
152
+
153
+ <details>
154
+ <summary><b>Subscriptions (9 methods)</b> - Recurring payments</summary>
155
+
156
+ - `subscription_plans` - List all subscription plans
157
+ - `create_subscription_plan(plan_data)` - Create new plan
158
+ - `update_subscription_plan(plan_id, plan_data)` - Update plan
159
+ - `subscription_plan(plan_id)` - Get plan details
160
+ - `create_subscription(plan_id:, email:)` - Create subscription
161
+ - `list_recurring_payments(...)` - List recurring payments with filters
162
+ - `recurring_payment(subscription_id)` - Get subscription details
163
+ - `delete_recurring_payment(subscription_id)` - Cancel subscription (JWT)
164
+ - `subscription_payments(subscription_id)` - List subscription payments
165
+
166
+ </details>
167
+
168
+ <details>
169
+ <summary><b>Custody/Sub-accounts (11 methods)</b> - User wallet management</summary>
170
+
171
+ - `create_sub_account(user_id:)` - Create user account
172
+ - `sub_account_balance(user_id)` - Get user balance
173
+ - `sub_account_balances` - Get all balances
174
+ - `list_sub_accounts(...)` - List all sub-accounts
175
+ - `transfer_between_sub_accounts(...)` - Transfer between users (JWT)
176
+ - `create_sub_account_deposit(user_id:, currency:, ...)` - Generate deposit address
177
+ - `create_sub_account_payment_deposit(...)` - Payment to sub-account
178
+ - `transfer_to_sub_account(user_id:, currency:, amount:)` - Deposit to user
179
+ - `withdraw_from_sub_account(user_id:, currency:, amount:)` - Withdraw from user (JWT)
180
+ - `sub_account_transfer(transfer_id)` - Get transfer details
181
+ - `sub_account_transfers(...)` - List all transfers
182
+
183
+ </details>
184
+
185
+ <details>
186
+ <summary><b>Fiat Payouts (8 methods)</b> - Beta: Crypto to fiat (JWT required)</summary>
187
+
188
+ - `fiat_payout_payment_methods(fiat_currency: nil)` - Available payment methods (JWT)
189
+ - `create_fiat_payout_account(...)` - Create payout account (JWT)
190
+ - `fiat_payout_accounts(...)` - List payout accounts (JWT)
191
+ - `update_fiat_payout_account(account_id:, ...)` - Update account (JWT)
192
+ - `create_fiat_payout(...)` - Create fiat payout (JWT)
193
+ - `fiat_payout_status(payout_id)` - Get payout status (JWT)
194
+ - `fiat_payouts(...)` - List all fiat payouts with filters (JWT)
195
+ - `fiat_payout_rates(...)` - Get conversion rates (JWT)
196
+
197
+ </details>
198
+
199
+ ### Built for Production
200
+
201
+ - **Comprehensive error handling** - 8 exception classes with detailed messages
202
+ - **Faraday middleware** - Automatic error mapping and retries
203
+ - **Tested** - 23 passing tests with VCR cassettes for integration
204
+ - **Rails-ready** - Drop-in Rack middleware for webhook verification
205
+ - **Type-safe** - All responses return Ruby Hashes from parsed JSON
206
+
207
+ ## Usage Examples
208
+
209
+ ### Accept Payment on Your Site
210
+
211
+ ```ruby
212
+ # 1. Create payment
213
+ payment = client.create_payment(
214
+ price_amount: 49.99,
215
+ price_currency: 'usd',
216
+ pay_currency: 'btc',
217
+ order_id: "order-#{order.id}",
218
+ order_description: 'Pro Plan - Annual',
219
+ ipn_callback_url: 'https://example.com/webhooks/nowpayments'
220
+ )
221
+
222
+ # 2. Show payment address to customer
223
+ @payment_address = payment['pay_address']
224
+ @payment_amount = payment['pay_amount']
225
+
226
+ # 3. Check status
227
+ status = client.payment(payment['payment_id'])
228
+ # => {"payment_status"=>"finished", ...}
229
+ ```
230
+
231
+ ### Hosted Invoice Page
232
+
233
+ ```ruby
234
+ # Create invoice with hosted payment page
235
+ invoice = client.create_invoice(
236
+ price_amount: 99.0,
237
+ price_currency: 'usd',
238
+ order_id: "inv-#{invoice.id}",
239
+ success_url: 'https://example.com/thank-you',
240
+ cancel_url: 'https://example.com/checkout'
241
+ )
242
+
243
+ # Redirect customer to payment page
244
+ redirect_to invoice['invoice_url']
245
+ # Customer can choose from 150+ cryptocurrencies
246
+ ```
247
+
248
+ ### Custody API - Sub-accounts (Marketplaces & Casinos)
249
+
250
+ ```ruby
251
+ # Create sub-account for a user
252
+ sub_account = client.create_sub_account(user_id: user.id)
253
+ # => {"id"=>123, "user_id"=>456, "created_at"=>"2025-11-01T..."}
254
+
255
+ # Generate deposit address for user's BTC wallet
256
+ deposit = client.create_sub_account_deposit(
257
+ user_id: user.id,
258
+ currency: 'btc'
259
+ )
260
+ # => {"address"=>"bc1q...", "currency"=>"btc"}
261
+
262
+ # Check user's balance
263
+ balances = client.sub_account_balances(user_id: user.id)
264
+ # => {"balances"=>{"btc"=>0.05, "eth"=>1.2}}
265
+
266
+ # Transfer funds to sub-account
267
+ transfer = client.transfer_to_sub_account(
268
+ user_id: user.id,
269
+ currency: 'btc',
270
+ amount: 0.01
271
+ )
272
+
273
+ # Process withdrawal
274
+ withdrawal = client.withdraw_from_sub_account(
275
+ user_id: user.id,
276
+ currency: 'btc',
277
+ amount: 0.005
278
+ )
279
+ ```
280
+
281
+ ### JWT Authentication (Required for Advanced Features)
282
+
283
+ **Some endpoints require JWT authentication (expires every 5 minutes):**
284
+
285
+ ```ruby
286
+ # Authenticate to get JWT token
287
+ client.authenticate(
288
+ email: 'your_email@example.com',
289
+ password: 'your_password'
290
+ )
291
+ # Token is automatically stored and injected in subsequent requests
292
+
293
+ # Check token status
294
+ client.jwt_expired? # => false
295
+ client.jwt_time_remaining # => 287 (seconds)
296
+
297
+ # JWT is required for these endpoints:
298
+ # - Mass Payouts (create_payout, verify_payout, list_payouts)
299
+ # - Conversions (create_conversion, conversion_status, list_conversions)
300
+ # - Custody Operations (transfer_between_sub_accounts, write_off_sub_account_balance)
301
+ # - Recurring Payments (delete_recurring_payment)
302
+
303
+ # Example: Create payout (requires JWT)
304
+ client.authenticate(email: 'your@email.com', password: 'password')
305
+ payout = client.create_payout(
306
+ withdrawals: [
307
+ {
308
+ address: 'TEmGwPeRTPiLFLVfBxXkSP91yc5GMNQhfS',
309
+ currency: 'trx',
310
+ amount: 10
311
+ }
312
+ ],
313
+ payout_description: 'Weekly payouts'
314
+ )
315
+
316
+ # Verify payout with 2FA code (from Google Authenticator)
317
+ client.verify_payout(
318
+ batch_withdrawal_id: payout['id'],
319
+ verification_code: '123456'
320
+ )
321
+
322
+ # Token auto-refresh pattern
323
+ def ensure_authenticated(client, email, password)
324
+ return unless client.jwt_expired?
325
+ client.authenticate(email: email, password: password)
326
+ end
327
+
328
+ # Before JWT-required operations
329
+ ensure_authenticated(client, EMAIL, PASSWORD)
330
+ payouts = client.list_payouts(limit: 10, offset: 0)
331
+
332
+ # Clear token when done (optional, for security)
333
+ client.clear_jwt_token
334
+ ```
335
+
336
+ **See [examples/jwt_authentication_example.rb](examples/jwt_authentication_example.rb) for complete usage patterns.**
337
+
338
+ ### Currency Conversions (JWT Required)
339
+
340
+ **Convert between cryptocurrencies at market rates:**
341
+
342
+ ```ruby
343
+ # Authenticate first
344
+ client.authenticate(email: 'your@email.com', password: 'password')
345
+
346
+ # Create conversion
347
+ conversion = client.create_conversion(
348
+ from_currency: 'btc',
349
+ to_currency: 'eth',
350
+ amount: 0.1
351
+ )
352
+ # => {"conversion_id" => "conv_123", "status" => "processing", ...}
353
+
354
+ # Check conversion status
355
+ status = client.conversion_status(conversion['conversion_id'])
356
+ # => {"status" => "completed", "from_amount" => 0.1, "to_amount" => 2.5, ...}
357
+
358
+ # List all conversions
359
+ conversions = client.list_conversions(limit: 10, offset: 0)
360
+ ```
361
+
362
+ ### Fiat Payouts (Beta - JWT Required)
363
+
364
+ **Withdraw cryptocurrency to fiat bank accounts:**
365
+
366
+ ```ruby
367
+ # Authenticate first
368
+ client.authenticate(email: 'your@email.com', password: 'password')
369
+
370
+ # Get available payment methods
371
+ methods = client.fiat_payout_payment_methods(fiat_currency: 'EUR')
372
+ # => {"result" => [{"provider" => "transfi", "methods" => [...]}]}
373
+
374
+ # Create payout account
375
+ account = client.create_fiat_payout_account(
376
+ provider: 'transfi',
377
+ fiat_currency: 'EUR',
378
+ account_data: {
379
+ accountHolderName: 'John Doe',
380
+ iban: 'DE89370400440532013000'
381
+ }
382
+ )
383
+ # => {"result" => {"id" => "acc_123", ...}}
384
+
385
+ # Get conversion rates
386
+ rates = client.fiat_payout_rates(
387
+ crypto_currency: 'btc',
388
+ fiat_currency: 'EUR',
389
+ crypto_amount: 0.1
390
+ )
391
+ # => {"result" => {"fiatAmount" => "2500.00", "rate" => "25000.00", ...}}
392
+
393
+ # Create fiat payout
394
+ payout = client.create_fiat_payout(
395
+ account_id: account['result']['id'],
396
+ crypto_currency: 'btc',
397
+ crypto_amount: 0.1
398
+ )
399
+ # => {"result" => {"id" => "payout_123", "status" => "PENDING", ...}}
400
+
401
+ # Check payout status
402
+ status = client.fiat_payout_status(payout['result']['id'])
403
+ # => {"result" => {"status" => "FINISHED", ...}}
404
+
405
+ # List all fiat payouts with filters
406
+ payouts = client.fiat_payouts(
407
+ status: 'FINISHED',
408
+ fiat_currency: 'EUR',
409
+ limit: 10,
410
+ page: 0
411
+ )
412
+ ```
413
+
414
+ ### Webhook Verification (Critical!)
415
+
416
+ **Always verify webhook signatures to prevent fraud:**
417
+
418
+ ```ruby
419
+ # app/controllers/webhooks_controller.rb
420
+ class WebhooksController < ApplicationController
421
+ skip_before_action :verify_authenticity_token
422
+
423
+ def nowpayments
424
+ # Verify signature - raises SecurityError if invalid
425
+ payload = NOWPayments::Rack.verify_webhook(
426
+ request,
427
+ ENV['NOWPAYMENTS_IPN_SECRET']
428
+ )
429
+
430
+ # Process payment status
431
+ order = Order.find_by(id: payload['order_id'])
432
+
433
+ case payload['payment_status']
434
+ when 'finished'
435
+ order.mark_paid!
436
+ OrderMailer.payment_received(order).deliver_later
437
+ when 'failed', 'expired'
438
+ order.cancel!
439
+ when 'partially_paid'
440
+ # Customer sent wrong amount
441
+ logger.warn "Underpaid: #{payload['actually_paid']} vs #{payload['pay_amount']}"
442
+ end
443
+
444
+ head :ok
445
+
446
+ rescue NOWPayments::SecurityError => e
447
+ logger.error "Invalid webhook signature: #{e.message}"
448
+ head :forbidden
449
+ end
450
+ end
451
+
452
+ # config/routes.rb
453
+ post '/webhooks/nowpayments', to: 'webhooks#nowpayments'
454
+ ```
455
+
456
+ ### Error Handling
457
+
458
+ ```ruby
459
+ begin
460
+ payment = client.create_payment(...)
461
+
462
+ rescue NOWPayments::AuthenticationError
463
+ # Invalid API key
464
+
465
+ rescue NOWPayments::BadRequestError => e
466
+ # Invalid parameters
467
+ puts "Error: #{e.message}"
468
+ puts "Details: #{e.body}"
469
+
470
+ rescue NOWPayments::RateLimitError => e
471
+ # Too many requests
472
+ retry_after = e.headers['Retry-After']
473
+
474
+ rescue NOWPayments::ServerError
475
+ # NOWPayments server error
476
+
477
+ rescue NOWPayments::ConnectionError
478
+ # Network error
479
+ end
480
+ ```
481
+
482
+ ## Documentation
483
+
484
+ - **[Complete API Reference](docs/API.md)** - All methods with examples
485
+ - **[Official API Docs](https://documenter.getpostman.com/view/7907941/2s93JusNJt)** - NOWPayments API documentation
486
+ - **[Dashboard](https://nowpayments.io/)** - Production environment
487
+ - **[Sandbox Dashboard](https://account-sandbox.nowpayments.io/)** - Testing environment
488
+
489
+ ## Testing with Sandbox
490
+
491
+ ```ruby
492
+ # Use sandbox for development
493
+ client = NOWPayments::Client.new(
494
+ api_key: ENV['NOWPAYMENTS_SANDBOX_API_KEY'],
495
+ ipn_secret: ENV['NOWPAYMENTS_SANDBOX_IPN_SECRET'],
496
+ sandbox: true
497
+ )
498
+
499
+ # All API calls go to sandbox environment
500
+ payment = client.create_payment(...)
501
+ ```
502
+
503
+ **Get sandbox credentials:**
504
+ 1. Create account at https://account-sandbox.nowpayments.io/
505
+ 2. Generate API key from dashboard
506
+ 3. Generate IPN secret for webhooks
507
+ 4. Add to `.env` file
508
+
509
+ ## Configuration
510
+
511
+ ```bash
512
+ # .env
513
+ NOWPAYMENTS_API_KEY=your_production_api_key
514
+ NOWPAYMENTS_IPN_SECRET=your_ipn_secret
515
+
516
+ # Testing
517
+ NOWPAYMENTS_SANDBOX_API_KEY=your_sandbox_api_key
518
+ NOWPAYMENTS_SANDBOX_IPN_SECRET=your_sandbox_ipn_secret
519
+ ```
520
+
521
+ ## Examples
522
+
523
+ See the `examples/` directory:
524
+
525
+ ```bash
526
+ # API usage demo
527
+ cp .env.example .env
528
+ # Add your sandbox credentials to .env
529
+ ruby examples/simple_demo.rb
530
+
531
+ # Webhook receiver (Sinatra)
532
+ ruby examples/webhook_server.rb
533
+ # Use ngrok to expose: ngrok http 4567
534
+ ```
535
+
536
+ ## Development
537
+
538
+ ```bash
539
+ # Install dependencies
540
+ bundle install
541
+
542
+ # Run tests
543
+ bundle exec rspec
544
+
545
+ # Run tests with coverage
546
+ COVERAGE=true bundle exec rspec
547
+
548
+ # Lint code
549
+ bundle exec rubocop
550
+
551
+ # Interactive console
552
+ bundle exec rake console
553
+ ```
554
+
555
+ ## Contributing
556
+
557
+ 1. Fork it
558
+ 2. Create your feature branch (`git checkout -b feature/my-feature`)
559
+ 3. Run tests (`bundle exec rspec`)
560
+ 4. Commit your changes (`git commit -am 'Add feature'`)
561
+ 5. Push to the branch (`git push origin feature/my-feature`)
562
+ 6. Create a Pull Request
563
+
564
+ ## Security
565
+
566
+ **Report security vulnerabilities to:** security@yourdomain.com
567
+
568
+ Never commit API keys or secrets. Always use environment variables.
569
+
570
+ ## License
571
+
572
+ MIT License - see [LICENSE.txt](LICENSE.txt)
573
+
574
+ ## Support
575
+
576
+ - [GitHub Issues](https://github.com/Sentia/nowpayments/issues)
577
+ - [NOWPayments Support](https://nowpayments.io/help)
578
+ - [API Documentation](https://documenter.getpostman.com/view/7907941/2s93JusNJt)
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]