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.
@@ -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