pyXRocketAPI 0.2.0__py3-none-any.whl → 0.3.0__py3-none-any.whl

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.
pyXRocketAPI/api.py CHANGED
@@ -4,23 +4,11 @@ from typing import Any
4
4
  import requests
5
5
 
6
6
  from .exceptions import xRocketAPIException
7
- from .models import App, Balance, Cheque, ChequesList, Currency, Invoice, InvoicePaymentAddress, InvoicePaymentsList, InvoicesList, MassPayouts, xPage, Payout, PayoutsList, Rate, Withdrawal, WithdrawalLink, WithdrawalQuotas, WithdrawalsList, xRocketObject
7
+ from .models import App, Balance, Cheque, ChequeCallback, ChequeUrl, ChequesList, Currency, Health, Invoice, InvoiceCallback, InvoiceCustomer, InvoicePaymentAddress, InvoicePaymentsList, InvoiceUrl, InvoicesList, MassPayout, MassPayouts, Payout, PayoutCallback, PayoutsList, Rate, Withdrawal, WithdrawalCallback, WithdrawalLink, WithdrawalQuotas, WithdrawalsList, xPage, xRocketObject
8
8
 
9
9
  PRODUCTION_API_URL = "https://pay.api.xrocket.exchange"
10
10
  TESTNET_API_URL = "https://pay.api.testnet.xrocket.exchange"
11
11
 
12
-
13
- def _without_none(**values: Any) -> dict[str, Any]:
14
- """Keep valid falsey API values while omitting arguments not supplied."""
15
- return {key: value for key, value in values.items() if value is not None}
16
-
17
-
18
- def _identifier(first_name: str, first_value: str | None, second_name: str, second_value: str | None) -> dict[str, str]:
19
- if first_value is None and second_value is None:
20
- raise ValueError(f"Specify {first_name} or {second_name}.")
21
- return _without_none(**{first_name: first_value, second_name: second_value})
22
-
23
-
24
12
  class xRocketPayAPI:
25
13
  """Client for the current xRocket Pay API.
26
14
 
@@ -28,20 +16,52 @@ class xRocketPayAPI:
28
16
  ----------
29
17
  token:
30
18
  App-specific Pay API Bearer token. Public calls such as
31
- ``health_check()`` and ``get_available_currencies()`` do not require
32
- one.
19
+ ``health_check()`` and ``get_available_currencies()`` do not require one.
33
20
  testnet:
34
21
  Select the xRocket Pay testnet URL. A testnet token is required.
35
22
  timeout:
36
- Timeout passed to every request, in seconds. ``None`` uses requests'
37
- default behaviour.
23
+ Timeout passed to every request, in seconds.
24
+ ``None`` uses requests' default behaviour.
25
+ Default is 30 seconds.
26
+ A tuple of ``(connect, read)`` timeouts can be passed to override the default for each phase.
38
27
  """
39
28
 
29
+ @staticmethod
30
+ def __without_none(**values: Any) -> dict[str, Any]:
31
+ """Keep valid falsey API values while omitting arguments not supplied."""
32
+ return {key: value for key, value in values.items() if value is not None}
33
+
34
+ @staticmethod
35
+ def __identifier(first_name: str, first_value: str | None,
36
+ second_name: str, second_value: str | None) -> dict[str, str]:
37
+ """Return identifier query parameters after validating their presence."""
38
+ if first_value is None and second_value is None:
39
+ raise ValueError(f"Specify {first_name} or {second_name}.")
40
+ return xRocketPayAPI.__without_none(**{first_name: first_value, second_name: second_value})
41
+
42
+ @staticmethod
43
+ def __request_model(value: Any, model_type: type, parameter_name: str) -> dict[str, Any] | None:
44
+ """Serialize one known nested request model or reject a plain mapping."""
45
+ if value is None:
46
+ return None
47
+ if not isinstance(value, model_type):
48
+ raise TypeError("{} must be an instance of {}.".format(parameter_name, model_type.__name__))
49
+ return value.to_dict()
50
+
51
+ @staticmethod
52
+ def __number_as_string(value: str | int | float | None, parameter_name: str) -> str | None:
53
+ """Encode one numeric API field whose wire type is string."""
54
+ if value is None:
55
+ return None
56
+ if isinstance(value, bool):
57
+ raise TypeError("{} must be a string or number, not bool.".format(parameter_name))
58
+ return str(value)
59
+
40
60
  def __init__(
41
61
  self,
42
62
  token: str | None = None,
43
63
  testnet: bool = False,
44
- timeout: float | tuple[float, float] | None = 10,
64
+ timeout: float | tuple[float, float] | None = 30,
45
65
  ) -> None:
46
66
  self.token = token
47
67
  self.timeout = timeout
@@ -113,14 +133,14 @@ class xRocketPayAPI:
113
133
  except ValueError as error:
114
134
  raise xRocketAPIException("xRocket Pay returned invalid JSON.", status=response.status_code) from error
115
135
 
116
- def health_check(self) -> xRocketObject:
136
+ def health_check(self) -> Health:
117
137
  """Run the public health check.
118
138
 
119
139
  API: https://docs.xrocket.exchange/api/pay/reference/http/health-controller-health
120
140
 
121
- :return: ``xRocketObject``.
141
+ :return: ``Health``.
122
142
  """
123
- return xRocketObject.de_json(self._request("GET", "/health", auth_required=False), process_mode=2)
143
+ return Health.de_json(self._request("GET", "/health", auth_required=False))
124
144
 
125
145
  def get_app_info(self) -> App:
126
146
  """Get information about your application.
@@ -150,7 +170,7 @@ class xRocketPayAPI:
150
170
  :return: ``list[Currency]``.
151
171
  """
152
172
  data = self._request(
153
- "GET", "/api/v1/currencies", params=_without_none(kind=kind), auth_required=False
173
+ "GET", "/api/v1/currencies", params=self.__without_none(kind=kind), auth_required=False
154
174
  )
155
175
  return [Currency.de_json(item) for item in data]
156
176
 
@@ -173,17 +193,17 @@ class xRocketPayAPI:
173
193
  def create_invoice(
174
194
  self,
175
195
  price_currency: str,
176
- price_amount: str | None = None,
177
- min_payment: str | None = None,
196
+ price_amount: str | int | float | None = None,
197
+ min_payment: str | int | float | None = None,
178
198
  num_payments: int | None = None,
179
199
  payout_currency: str | None = None,
180
200
  pay_currencies: Iterable[str] | None = None,
181
201
  client_invoice_id: str | None = None,
182
202
  description: str | None = None,
183
203
  expires_in: int | None = None,
184
- callback: Mapping[str, Any] | None = None,
185
- url: Mapping[str, Any] | None = None,
186
- customer: Mapping[str, Any] | None = None,
204
+ callback: InvoiceCallback | None = None,
205
+ url: InvoiceUrl | None = None,
206
+ customer: InvoiceCustomer | None = None,
187
207
  is_fee_paid_by_user: bool | None = None,
188
208
  data: Mapping[str, Any] | None = None,
189
209
  platform_id: str | None = None,
@@ -193,27 +213,32 @@ class xRocketPayAPI:
193
213
  API: https://docs.xrocket.exchange/api/pay/reference/http/invoice-controller-create-invoice
194
214
 
195
215
  :param price_currency: Invoice price currency (crypto or fiat).
196
- :param price_amount: Invoice price amount.
197
- :param min_payment: Minimum payment amount (for open-amount invoices).
216
+ :param price_amount: Invoice price amount. A numeric value is encoded as an API string.
217
+ :param min_payment: Minimum payment amount (for open-amount invoices). A numeric value is encoded as an API string.
198
218
  :param num_payments: Num payments for invoice.
199
219
  :param payout_currency: Invoice payout crypto currency.
200
220
  :param pay_currencies: Crypto currencies which can be used to pay the invoice.
201
221
  :param client_invoice_id: Client Invoice ID as assigned by the client.
202
222
  :param description: Description for invoice.
203
223
  :param expires_in: Payment expires in milliseconds.
204
- :param callback: Callback data.
205
- :param url: User redirect urls.
206
- :param customer: Customer info.
224
+ :param callback: Callback data as an ``InvoiceCallback`` instance.
225
+ :param url: User redirect urls as an ``InvoiceUrl`` instance.
226
+ :param customer: Customer info as an ``InvoiceCustomer`` instance.
207
227
  :param is_fee_paid_by_user: If true, the user pays a commission.
208
228
  :param data: Custom user data passed through and returned in callbacks/webhooks (max size 4KB).
209
229
  :param platform_id: Platform identifier.
210
230
  :return: ``Invoice``.
211
231
  """
212
- body = _without_none(
213
- priceCurrency=price_currency, priceAmount=price_amount, minPayment=min_payment, numPayments=num_payments,
232
+ body = self.__without_none(
233
+ priceCurrency=price_currency,
234
+ priceAmount=self.__number_as_string(price_amount, "price_amount"),
235
+ minPayment=self.__number_as_string(min_payment, "min_payment"), numPayments=num_payments,
214
236
  payoutCurrency=payout_currency, payCurrencies=list(pay_currencies) if pay_currencies is not None else None,
215
- clientInvoiceId=client_invoice_id, description=description, expiresIn=expires_in, callback=callback, url=url,
216
- customer=customer, isFeePaidByUser=is_fee_paid_by_user, data=data, platformId=platform_id,
237
+ clientInvoiceId=client_invoice_id, description=description, expiresIn=expires_in,
238
+ callback=self.__request_model(callback, InvoiceCallback, "callback"),
239
+ url=self.__request_model(url, InvoiceUrl, "url"),
240
+ customer=self.__request_model(customer, InvoiceCustomer, "customer"),
241
+ isFeePaidByUser=is_fee_paid_by_user, data=data, platformId=platform_id,
217
242
  )
218
243
  return Invoice.de_json(self._request("POST", "/api/v1/invoices", body=body))
219
244
 
@@ -231,7 +256,7 @@ class xRocketPayAPI:
231
256
  :param limit: No description is provided.
232
257
  :return: ``InvoicesList``.
233
258
  """
234
- params = _without_none(asset=asset, fiat=fiat, ids=list(ids) if ids is not None else None, status=status, cursor=cursor, limit=limit)
259
+ params = self.__without_none(asset=asset, fiat=fiat, ids=list(ids) if ids is not None else None, status=status, cursor=cursor, limit=limit)
235
260
  return InvoicesList.de_json(self._request("GET", "/api/v1/invoices", params=params))
236
261
 
237
262
  def get_invoice_info(self, invoice_id: str | None = None, client_invoice_id: str | None = None) -> Invoice:
@@ -244,7 +269,7 @@ class xRocketPayAPI:
244
269
  :param client_invoice_id: Client Invoice ID assigned by the client. Either invoiceId or clientInvoiceId is required.
245
270
  :return: ``Invoice``.
246
271
  """
247
- return Invoice.de_json(self._request("GET", "/api/v1/invoice", params=_identifier("invoiceId", invoice_id, "clientInvoiceId", client_invoice_id)))
272
+ return Invoice.de_json(self._request("GET", "/api/v1/invoice", params=self.__identifier("invoiceId", invoice_id, "clientInvoiceId", client_invoice_id)))
248
273
 
249
274
  def delete_invoice(self, invoice_id: str | None = None, client_invoice_id: str | None = None) -> None:
250
275
  """Delete invoice.
@@ -256,7 +281,7 @@ class xRocketPayAPI:
256
281
  :param client_invoice_id: Client Invoice ID assigned by the client. Either invoiceId or clientInvoiceId is required.
257
282
  :return: ``None``.
258
283
  """
259
- self._request("DELETE", "/api/v1/invoice", params=_identifier("invoiceId", invoice_id, "clientInvoiceId", client_invoice_id))
284
+ self._request("DELETE", "/api/v1/invoice", params=self.__identifier("invoiceId", invoice_id, "clientInvoiceId", client_invoice_id))
260
285
 
261
286
  def get_invoice_payments(self, invoice_id: str | None = None, client_invoice_id: str | None = None,
262
287
  cursor: str | None = None, limit: int | None = None) -> InvoicePaymentsList:
@@ -271,8 +296,8 @@ class xRocketPayAPI:
271
296
  :param limit: No description is provided.
272
297
  :return: ``InvoicePaymentsList``.
273
298
  """
274
- params = _identifier("invoiceId", invoice_id, "clientInvoiceId", client_invoice_id)
275
- params.update(_without_none(cursor=cursor, limit=limit))
299
+ params = self.__identifier("invoiceId", invoice_id, "clientInvoiceId", client_invoice_id)
300
+ params.update(self.__without_none(cursor=cursor, limit=limit))
276
301
  return InvoicePaymentsList.de_json(self._request("GET", "/api/v1/invoice/payments", params=params))
277
302
 
278
303
  def create_invoice_payment_address(self, pay_network: str, invoice_id: str | None = None,
@@ -288,11 +313,11 @@ class xRocketPayAPI:
288
313
  """
289
314
  return InvoicePaymentAddress.de_json(self._request(
290
315
  "POST", "/api/v1/invoices/payments/address",
291
- params=_identifier("invoiceId", invoice_id, "clientInvoiceId", client_invoice_id), body={"payNetwork": pay_network},
316
+ params=self.__identifier("invoiceId", invoice_id, "clientInvoiceId", client_invoice_id), body={"payNetwork": pay_network},
292
317
  ))
293
318
 
294
- def create_cheque(self, asset: str, amount: str, client_cheque_id: str | None = None, password: str | None = None,
295
- description: str | None = None, callback: Mapping[str, Any] | None = None, url: Mapping[str, Any] | None = None,
319
+ def create_cheque(self, asset: str, amount: str | int | float, client_cheque_id: str | None = None, password: str | None = None,
320
+ description: str | None = None, callback: ChequeCallback | None = None, url: ChequeUrl | None = None,
296
321
  target_type: str | None = None, target: str | None = None) -> Cheque:
297
322
  """Create cheque.
298
323
 
@@ -300,19 +325,20 @@ class xRocketPayAPI:
300
325
  Issue a personal cheque to perform an accept-type payout. The amount is reserved from the application balance and can be redeemed by the recipient or cancelled before redemption. When targetType and target are set, only the addressed user is allowed to redeem the cheque.
301
326
 
302
327
  :param asset: Currency of transfer.
303
- :param amount: Cheque amount.
328
+ :param amount: Cheque amount. A numeric value is encoded as an API string.
304
329
  :param client_cheque_id: Unique cheque ID in your system to prevent double spends.
305
330
  :param password: Cheque password, the recipient has to enter it to redeem the cheque.
306
331
  :param description: Description for cheque.
307
- :param callback: Webhook settings for cheque activation updates.
308
- :param url: User redirect urls after cheque activation.
332
+ :param callback: Webhook settings for cheque activation updates as a ``ChequeCallback`` instance.
333
+ :param url: User redirect urls after cheque activation as a ``ChequeUrl`` instance.
309
334
  :param target_type: Target type for cheque, has to be passed together with target.
310
335
  :param target: Target for cheque, has to be passed together with targetType.
311
336
  :return: ``Cheque``.
312
337
  """
313
- return Cheque.de_json(self._request("POST", "/api/v1/cheques", body=_without_none(
314
- asset=asset, amount=str(amount), clientChequeId=client_cheque_id, password=password, description=description,
315
- callback=callback, url=url, targetType=target_type, target=target,
338
+ return Cheque.de_json(self._request("POST", "/api/v1/cheques", body=self.__without_none(
339
+ asset=asset, amount=self.__number_as_string(amount, "amount"), clientChequeId=client_cheque_id, password=password, description=description,
340
+ callback=self.__request_model(callback, ChequeCallback, "callback"),
341
+ url=self.__request_model(url, ChequeUrl, "url"), targetType=target_type, target=target,
316
342
  )))
317
343
 
318
344
  def get_cheques_list(self, from_date: str | None = None, to_date: str | None = None, target_type: str | None = None,
@@ -331,7 +357,7 @@ class xRocketPayAPI:
331
357
  :param limit: No description is provided.
332
358
  :return: ``ChequesList``.
333
359
  """
334
- return ChequesList.de_json(self._request("GET", "/api/v1/cheques", params=_without_none(
360
+ return ChequesList.de_json(self._request("GET", "/api/v1/cheques", params=self.__without_none(
335
361
  fromDate=from_date, toDate=to_date, targetType=target_type, target=target, state=state, cursor=cursor, limit=limit,
336
362
  )))
337
363
 
@@ -345,7 +371,7 @@ class xRocketPayAPI:
345
371
  :param client_cheque_id: Unique cheque id in your system.
346
372
  :return: ``Cheque``.
347
373
  """
348
- return Cheque.de_json(self._request("GET", "/api/v1/cheque", params=_identifier("chequeId", cheque_id, "clientChequeId", client_cheque_id)))
374
+ return Cheque.de_json(self._request("GET", "/api/v1/cheque", params=self.__identifier("chequeId", cheque_id, "clientChequeId", client_cheque_id)))
349
375
 
350
376
  def update_cheque(self, description: str, cheque_id: str | None = None, client_cheque_id: str | None = None) -> Cheque:
351
377
  """Update cheque.
@@ -358,7 +384,7 @@ class xRocketPayAPI:
358
384
  :param client_cheque_id: Unique cheque id in your system.
359
385
  :return: ``Cheque``.
360
386
  """
361
- return Cheque.de_json(self._request("PUT", "/api/v1/cheques", params=_identifier("chequeId", cheque_id, "clientChequeId", client_cheque_id), body={"description": description}))
387
+ return Cheque.de_json(self._request("PUT", "/api/v1/cheques", params=self.__identifier("chequeId", cheque_id, "clientChequeId", client_cheque_id), body={"description": description}))
362
388
 
363
389
  def delete_cheque(self, cheque_id: str | None = None, client_cheque_id: str | None = None) -> None:
364
390
  """Delete cheque.
@@ -370,10 +396,10 @@ class xRocketPayAPI:
370
396
  :param client_cheque_id: Unique cheque id in your system.
371
397
  :return: ``None``.
372
398
  """
373
- self._request("DELETE", "/api/v1/cheques", params=_identifier("chequeId", cheque_id, "clientChequeId", client_cheque_id))
399
+ self._request("DELETE", "/api/v1/cheques", params=self.__identifier("chequeId", cheque_id, "clientChequeId", client_cheque_id))
374
400
 
375
- def payout_funds_to_user(self, target: str, target_type: str, asset: str, amount: str, client_payout_id: str | None = None,
376
- description: str | None = None, callback: Mapping[str, Any] | None = None) -> Payout:
401
+ def create_payout(self, target: str, target_type: str, asset: str, amount: str | int | float, client_payout_id: str | None = None,
402
+ description: str | None = None, callback: PayoutCallback | None = None) -> Payout:
377
403
  """Payout funds to user.
378
404
 
379
405
  API: https://docs.xrocket.exchange/api/pay/reference/http/payout-controller-payout
@@ -381,15 +407,15 @@ class xRocketPayAPI:
381
407
  :param target: Target.
382
408
  :param target_type: Target type.
383
409
  :param asset: Asset of transfer.
384
- :param amount: Payout amount.
410
+ :param amount: Payout amount. A numeric value is encoded as an API string.
385
411
  :param client_payout_id: Unique payout ID in your system to prevent double spends.
386
412
  :param description: Payout description.
387
- :param callback: Webhook settings for payout status updates.
413
+ :param callback: Webhook settings for payout status updates as a ``PayoutCallback`` instance.
388
414
  :return: ``Payout``.
389
415
  """
390
- return Payout.de_json(self._request("POST", "/api/v1/payouts", body=_without_none(
391
- target=target, targetType=target_type, asset=asset, amount=str(amount), clientPayoutId=client_payout_id,
392
- description=description, callback=callback,
416
+ return Payout.de_json(self._request("POST", "/api/v1/payouts", body=self.__without_none(
417
+ target=target, targetType=target_type, asset=asset, amount=self.__number_as_string(amount, "amount"), clientPayoutId=client_payout_id,
418
+ description=description, callback=self.__request_model(callback, PayoutCallback, "callback"),
393
419
  )))
394
420
 
395
421
  def get_payouts_list(self, from_date: str | None = None, to_date: str | None = None, cursor: str | None = None,
@@ -404,7 +430,7 @@ class xRocketPayAPI:
404
430
  :param limit: No description is provided.
405
431
  :return: ``PayoutsList``.
406
432
  """
407
- return PayoutsList.de_json(self._request("GET", "/api/v1/payouts", params=_without_none(fromDate=from_date, toDate=to_date, cursor=cursor, limit=limit)))
433
+ return PayoutsList.de_json(self._request("GET", "/api/v1/payouts", params=self.__without_none(fromDate=from_date, toDate=to_date, cursor=cursor, limit=limit)))
408
434
 
409
435
  def get_payout_info(self, payout_id: str | None = None, client_payout_id: str | None = None) -> Payout:
410
436
  """Get payout info.
@@ -415,21 +441,26 @@ class xRocketPayAPI:
415
441
  :param client_payout_id: Unique payout ID in your system to prevent double spends.
416
442
  :return: ``Payout``.
417
443
  """
418
- return Payout.de_json(self._request("GET", "/api/v1/payout", params=_identifier("payoutId", payout_id, "clientPayoutId", client_payout_id)))
444
+ return Payout.de_json(self._request("GET", "/api/v1/payout", params=self.__identifier("payoutId", payout_id, "clientPayoutId", client_payout_id)))
419
445
 
420
- def create_mass_payouts(self, asset: str, payouts: Iterable[Mapping[str, Any]]) -> MassPayouts:
446
+ def create_mass_payouts(self, asset: str, payouts: Iterable[MassPayout]) -> MassPayouts:
421
447
  """Create mass payouts (Telegram users only).
422
448
 
423
449
  API: https://docs.xrocket.exchange/api/pay/reference/http/mass-payouts-controller-create-mass-payouts
424
450
 
425
451
  :param asset: Asset of payouts.
426
- :param payouts: List of payouts to process.
452
+ :param payouts: List of ``MassPayout`` instances to process.
427
453
  :return: ``MassPayouts``.
428
454
  """
429
- return MassPayouts.de_json(self._request("POST", "/api/v1/mass-payouts", body={"asset": asset, "payouts": list(payouts)}))
430
-
431
- def withdrawal_funds(self, client_withdrawal_id: str, network: str, address: str, asset: str, amount: str,
432
- comment: str | None = None, callback: Mapping[str, Any] | None = None) -> Withdrawal:
455
+ payout_data = []
456
+ for payout in payouts:
457
+ if not isinstance(payout, MassPayout):
458
+ raise TypeError("payouts must contain only MassPayout instances.")
459
+ payout_data.append(payout.to_dict())
460
+ return MassPayouts.de_json(self._request("POST", "/api/v1/mass-payouts", body={"asset": asset, "payouts": payout_data}))
461
+
462
+ def create_withdrawal(self, client_withdrawal_id: str, network: str, address: str, asset: str, amount: str | int | float,
463
+ comment: str | None = None, callback: WithdrawalCallback | None = None) -> Withdrawal:
433
464
  """Withdrawal funds from application to external wallet.
434
465
 
435
466
  API: https://docs.xrocket.exchange/api/pay/reference/http/withdrawal-controller-create-withdrawal
@@ -438,14 +469,14 @@ class xRocketPayAPI:
438
469
  :param network: Network code.
439
470
  :param address: Withdrawal address.
440
471
  :param asset: Asset code.
441
- :param amount: Withdrawal amount.
472
+ :param amount: Withdrawal amount. A numeric value is encoded as an API string.
442
473
  :param comment: Withdrawal comment.
443
- :param callback: Webhook settings for withdrawal status updates.
474
+ :param callback: Webhook settings for withdrawal status updates as a ``WithdrawalCallback`` instance.
444
475
  :return: ``Withdrawal``.
445
476
  """
446
- return Withdrawal.de_json(self._request("POST", "/api/v1/withdrawals", body=_without_none(
447
- clientWithdrawalId=client_withdrawal_id, network=network, address=address, asset=asset, amount=str(amount),
448
- comment=comment, callback=callback,
477
+ return Withdrawal.de_json(self._request("POST", "/api/v1/withdrawals", body=self.__without_none(
478
+ clientWithdrawalId=client_withdrawal_id, network=network, address=address, asset=asset, amount=self.__number_as_string(amount, "amount"),
479
+ comment=comment, callback=self.__request_model(callback, WithdrawalCallback, "callback"),
449
480
  )))
450
481
 
451
482
  def get_withdrawals_list(self, from_date: str | None = None, to_date: str | None = None, status: str | None = None,
@@ -461,7 +492,7 @@ class xRocketPayAPI:
461
492
  :param limit: No description is provided.
462
493
  :return: ``WithdrawalsList``.
463
494
  """
464
- return WithdrawalsList.de_json(self._request("GET", "/api/v1/withdrawals", params=_without_none(
495
+ return WithdrawalsList.de_json(self._request("GET", "/api/v1/withdrawals", params=self.__without_none(
465
496
  fromDate=from_date, toDate=to_date, status=status, cursor=cursor, limit=limit,
466
497
  )))
467
498
 
@@ -474,7 +505,7 @@ class xRocketPayAPI:
474
505
  :param client_withdrawal_id: Unique withdrawal ID in your system to prevent double spends.
475
506
  :return: ``Withdrawal``.
476
507
  """
477
- return Withdrawal.de_json(self._request("GET", "/api/v1/withdrawal", params=_identifier("withdrawalId", withdrawal_id, "clientWithdrawalId", client_withdrawal_id)))
508
+ return Withdrawal.de_json(self._request("GET", "/api/v1/withdrawal", params=self.__identifier("withdrawalId", withdrawal_id, "clientWithdrawalId", client_withdrawal_id)))
478
509
 
479
510
  def get_withdrawal_quotas(self, network: str, asset: str) -> WithdrawalQuotas:
480
511
  """Get application withdrawal quotas.
@@ -487,7 +518,7 @@ class xRocketPayAPI:
487
518
  """
488
519
  return WithdrawalQuotas.de_json(self._request("GET", "/api/v1/withdrawal-quotas", params={"network": network, "asset": asset}))
489
520
 
490
- def create_withdrawal_link(self, network: str, address: str, asset: str, amount: str, comment: str | None = None,
521
+ def create_withdrawal_link(self, network: str, address: str, asset: str, amount: str | int | float, comment: str | None = None,
491
522
  platform: str | None = None) -> WithdrawalLink:
492
523
  """Create withdrawal link.
493
524
 
@@ -496,11 +527,11 @@ class xRocketPayAPI:
496
527
  :param network: Network code.
497
528
  :param address: Withdrawal address.
498
529
  :param asset: Asset code.
499
- :param amount: Withdrawal amount.
530
+ :param amount: Withdrawal amount. A numeric value is encoded as an API string.
500
531
  :param comment: Withdrawal comment.
501
532
  :param platform: Platform identifier (optional, use only if provided by xRocket).
502
533
  :return: ``WithdrawalLink``.
503
534
  """
504
- return WithdrawalLink.de_json(self._request("POST", "/api/v1/withdrawal-link", body=_without_none(
505
- network=network, address=address, asset=asset, amount=str(amount), comment=comment, platform=platform,
535
+ return WithdrawalLink.de_json(self._request("POST", "/api/v1/withdrawal-link", body=self.__without_none(
536
+ network=network, address=address, asset=asset, amount=self.__number_as_string(amount, "amount"), comment=comment, platform=platform,
506
537
  )))
pyXRocketAPI/models.py CHANGED
@@ -2,6 +2,31 @@ import json
2
2
  from abc import ABC
3
3
 
4
4
 
5
+ class Dictionaryable(ABC):
6
+ """Base class for models that can be converted to an API dictionary.
7
+
8
+ Subclasses implement :meth:`to_dict` to prepare their fields for use in a
9
+ JSON request body. ``None`` values are omitted; valid falsey values are
10
+ retained.
11
+ """
12
+
13
+ def to_dict(self):
14
+ """Return the model fields as a dictionary accepted by the API.
15
+
16
+ :return: API-ready dictionary.
17
+ """
18
+ raise NotImplementedError
19
+
20
+ @staticmethod
21
+ def _number_as_string(value):
22
+ """Encode one number for an API field whose schema type is string."""
23
+ if value is None:
24
+ return None
25
+ if isinstance(value, bool):
26
+ raise TypeError("Boolean values are not valid numeric API values.")
27
+ return str(value)
28
+
29
+
5
30
  class xRocketObject(ABC):
6
31
  """Base class for xRocket Pay API response models.
7
32
 
@@ -62,6 +87,54 @@ class xRocketObject(ABC):
62
87
  return str(data)
63
88
 
64
89
 
90
+ # noinspection method-overriding
91
+ class HealthComponent(xRocketObject):
92
+ """One component reported by the xRocket Pay health check.
93
+
94
+ API: https://docs.xrocket.exchange/api/pay/reference/http/health-controller-health
95
+
96
+ :param name: No description is provided.
97
+ :param status: No description is provided.
98
+ :param message: No description is provided.
99
+ """
100
+ def __init__(self):
101
+ self.name = None
102
+ self.status = None
103
+ self.message = None
104
+
105
+ @classmethod
106
+ def de_json(cls, json_dict):
107
+ data = cls.check_json(json_dict)
108
+ return super(HealthComponent, cls).de_json(data, process_mode=2)
109
+
110
+
111
+ # noinspection method-overriding
112
+ class Health(xRocketObject):
113
+ """Response returned by the xRocket Pay health-check endpoint.
114
+
115
+ API: https://docs.xrocket.exchange/api/pay/reference/http/health-controller-health
116
+
117
+ :param status: No description is provided.
118
+ :param info: No description is provided.
119
+ :param error: No description is provided.
120
+ :param details: No description is provided.
121
+ """
122
+ def __init__(self):
123
+ self.status = None
124
+ self.info = []
125
+ self.error = []
126
+ self.details = []
127
+
128
+ @classmethod
129
+ def de_json(cls, json_dict):
130
+ data = cls.check_json(json_dict)
131
+ instance = super(Health, cls).de_json(data, process_mode=2)
132
+ instance.info = [HealthComponent.de_json(item) for item in instance.info]
133
+ instance.error = [HealthComponent.de_json(item) for item in instance.error]
134
+ instance.details = [HealthComponent.de_json(item) for item in instance.details]
135
+ return instance
136
+
137
+
65
138
  # noinspection method-overriding
66
139
  class App(xRocketObject):
67
140
  """Current application returned by xRocket Pay.
@@ -101,7 +174,14 @@ class Balance(xRocketObject):
101
174
  @classmethod
102
175
  def de_json(cls, json_dict):
103
176
  data = cls.check_json(json_dict)
104
- return super(Balance, cls).de_json(data, process_mode=2)
177
+ instance = super(Balance, cls).de_json(data, process_mode=2)
178
+ if instance.balance is not None:
179
+ instance.balance = float(instance.balance)
180
+ if instance.available is not None:
181
+ instance.available = float(instance.available)
182
+ if instance.holds is not None:
183
+ instance.holds = float(instance.holds)
184
+ return instance
105
185
 
106
186
 
107
187
  # noinspection method-overriding
@@ -125,6 +205,86 @@ class InvoiceLinks(xRocketObject):
125
205
  return super(InvoiceLinks, cls).de_json(data, process_mode=2)
126
206
 
127
207
 
208
+ # noinspection method-overriding
209
+ class InvoiceCallback(Dictionaryable, xRocketObject):
210
+ """Webhook settings for an invoice.
211
+
212
+ API: https://docs.xrocket.exchange/api/pay/reference/http/invoice-controller-create-invoice
213
+
214
+ :param callbackUrl: Url for notify when order status is changed.
215
+ :param payload: Custom data sent to webhook.
216
+ """
217
+
218
+ def __init__(self, callbackUrl=None, payload=None):
219
+ self.callbackUrl = callbackUrl
220
+ self.payload = payload
221
+
222
+ @classmethod
223
+ def de_json(cls, json_dict):
224
+ data = cls.check_json(json_dict)
225
+ return super(InvoiceCallback, cls).de_json(data, process_mode=2)
226
+
227
+ def to_dict(self):
228
+ return {key: value for key, value in {
229
+ "callbackUrl": self.callbackUrl, "payload": self.payload,
230
+ }.items() if value is not None}
231
+
232
+
233
+ # noinspection method-overriding
234
+ class InvoiceUrl(Dictionaryable, xRocketObject):
235
+ """User redirect URLs for an invoice.
236
+
237
+ API: https://docs.xrocket.exchange/api/pay/reference/http/invoice-controller-create-invoice
238
+
239
+ :param successUrl: Redirect user to url after successful payment.
240
+ :param cancelUrl: Redirect user to url after cancel payment.
241
+ """
242
+
243
+ def __init__(self, successUrl=None, cancelUrl=None):
244
+ self.successUrl = successUrl
245
+ self.cancelUrl = cancelUrl
246
+
247
+ @classmethod
248
+ def de_json(cls, json_dict):
249
+ data = cls.check_json(json_dict)
250
+ return super(InvoiceUrl, cls).de_json(data, process_mode=2)
251
+
252
+ def to_dict(self):
253
+ return {key: value for key, value in {
254
+ "successUrl": self.successUrl, "cancelUrl": self.cancelUrl,
255
+ }.items() if value is not None}
256
+
257
+
258
+ # noinspection method-overriding
259
+ class InvoiceCustomer(Dictionaryable, xRocketObject):
260
+ """Customer information for an invoice.
261
+
262
+ API: https://docs.xrocket.exchange/api/pay/reference/http/invoice-controller-create-invoice
263
+
264
+ :param id: Customer id in your app.
265
+ :param email: Customer email.
266
+ :param telegramId: Customer telegram ID.
267
+ :param telegramUsername: Customer telegram username (without @).
268
+ """
269
+
270
+ def __init__(self, id=None, email=None, telegramId=None, telegramUsername=None):
271
+ self.id = id
272
+ self.email = email
273
+ self.telegramId = telegramId
274
+ self.telegramUsername = telegramUsername
275
+
276
+ @classmethod
277
+ def de_json(cls, json_dict):
278
+ data = cls.check_json(json_dict)
279
+ return super(InvoiceCustomer, cls).de_json(data, process_mode=2)
280
+
281
+ def to_dict(self):
282
+ return {key: value for key, value in {
283
+ "id": self.id, "email": self.email, "telegramId": self.telegramId,
284
+ "telegramUsername": self.telegramUsername,
285
+ }.items() if value is not None}
286
+
287
+
128
288
  # noinspection method-overriding
129
289
  class Invoice(xRocketObject):
130
290
  """Invoice created in xRocket Pay.
@@ -142,10 +302,10 @@ class Invoice(xRocketObject):
142
302
  :param createdAt: Invoice creation time.
143
303
  :param expiresAt: Invoice expiration time (null if no expiration).
144
304
  :param status: Invoice status. **WARNING**: This list may be extended in the future. Always use exact status comparison and handle unknown statuses gracefully.
145
- :param callback: No description is provided.
146
- :param url: No description is provided.
147
- :param customer: No description is provided.
148
- :param links: No description is provided.
305
+ :param callback: Invoice webhook settings.
306
+ :param url: Invoice user redirect URLs.
307
+ :param customer: Invoice customer information.
308
+ :param links: Invoice payment links.
149
309
  """
150
310
  def __init__(self):
151
311
  self.id = None
@@ -168,6 +328,18 @@ class Invoice(xRocketObject):
168
328
  def de_json(cls, json_dict):
169
329
  data = cls.check_json(json_dict)
170
330
  instance = super(Invoice, cls).de_json(data, process_mode=2)
331
+ if instance.priceAmount is not None:
332
+ instance.priceAmount = float(instance.priceAmount)
333
+ if instance.minPayment is not None:
334
+ instance.minPayment = float(instance.minPayment)
335
+ if instance.expiresIn is not None:
336
+ instance.expiresIn = int(instance.expiresIn)
337
+ if instance.callback is not None:
338
+ instance.callback = InvoiceCallback.de_json(instance.callback)
339
+ if instance.url is not None:
340
+ instance.url = InvoiceUrl.de_json(instance.url)
341
+ if instance.customer is not None:
342
+ instance.customer = InvoiceCustomer.de_json(instance.customer)
171
343
  if instance.links is not None:
172
344
  instance.links = InvoiceLinks.de_json(instance.links)
173
345
  return instance
@@ -194,6 +366,54 @@ class ChequeLinks(xRocketObject):
194
366
  return super(ChequeLinks, cls).de_json(data, process_mode=2)
195
367
 
196
368
 
369
+ # noinspection method-overriding
370
+ class ChequeCallback(Dictionaryable, xRocketObject):
371
+ """Webhook settings for a cheque.
372
+
373
+ API: https://docs.xrocket.exchange/api/pay/reference/http/cheque-controller-create-cheque
374
+
375
+ :param callbackUrl: Url for notifying when cheque status changes.
376
+ :param payload: Custom data sent to webhook along with cheque status updates.
377
+ """
378
+ def __init__(self, callbackUrl=None, payload=None):
379
+ self.callbackUrl = callbackUrl
380
+ self.payload = payload
381
+
382
+ @classmethod
383
+ def de_json(cls, json_dict):
384
+ data = cls.check_json(json_dict)
385
+ return super(ChequeCallback, cls).de_json(data, process_mode=2)
386
+
387
+ def to_dict(self):
388
+ return {key: value for key, value in {
389
+ "callbackUrl": self.callbackUrl, "payload": self.payload,
390
+ }.items() if value is not None}
391
+
392
+
393
+ # noinspection method-overriding
394
+ class ChequeUrl(Dictionaryable, xRocketObject):
395
+ """User redirect URLs for a cheque.
396
+
397
+ API: https://docs.xrocket.exchange/api/pay/reference/http/cheque-controller-create-cheque
398
+
399
+ :param successUrl: Redirect user to URL after successful cheque activation.
400
+ :param cancelUrl: Redirect user to URL after cheque activation is cancelled/failed.
401
+ """
402
+ def __init__(self, successUrl=None, cancelUrl=None):
403
+ self.successUrl = successUrl
404
+ self.cancelUrl = cancelUrl
405
+
406
+ @classmethod
407
+ def de_json(cls, json_dict):
408
+ data = cls.check_json(json_dict)
409
+ return super(ChequeUrl, cls).de_json(data, process_mode=2)
410
+
411
+ def to_dict(self):
412
+ return {key: value for key, value in {
413
+ "successUrl": self.successUrl, "cancelUrl": self.cancelUrl,
414
+ }.items() if value is not None}
415
+
416
+
197
417
  # noinspection method-overriding
198
418
  class Cheque(xRocketObject):
199
419
  """Cheque created in xRocket Pay.
@@ -209,8 +429,8 @@ class Cheque(xRocketObject):
209
429
  :param links: Cheque activation links.
210
430
  :param state: Cheque state.
211
431
  :param deleted: Cheque is cancelled and the reserved funds are returned to the application balance.
212
- :param callback: No description is provided.
213
- :param url: No description is provided.
432
+ :param callback: Cheque webhook settings.
433
+ :param url: Cheque user redirect URLs.
214
434
  """
215
435
  def __init__(self):
216
436
  self.chequeId = None
@@ -231,9 +451,37 @@ class Cheque(xRocketObject):
231
451
  instance = super(Cheque, cls).de_json(data, process_mode=2)
232
452
  if instance.links is not None:
233
453
  instance.links = ChequeLinks.de_json(instance.links)
454
+ if instance.callback is not None:
455
+ instance.callback = ChequeCallback.de_json(instance.callback)
456
+ if instance.url is not None:
457
+ instance.url = ChequeUrl.de_json(instance.url)
234
458
  return instance
235
459
 
236
460
 
461
+ # noinspection method-overriding
462
+ class PayoutCallback(Dictionaryable, xRocketObject):
463
+ """Webhook settings for a payout.
464
+
465
+ API: https://docs.xrocket.exchange/api/pay/reference/http/payout-controller-payout
466
+
467
+ :param callbackUrl: Url for notify when payout status is changed.
468
+ :param payload: Custom data sent to webhook.
469
+ """
470
+ def __init__(self, callbackUrl=None, payload=None):
471
+ self.callbackUrl = callbackUrl
472
+ self.payload = payload
473
+
474
+ @classmethod
475
+ def de_json(cls, json_dict):
476
+ data = cls.check_json(json_dict)
477
+ return super(PayoutCallback, cls).de_json(data, process_mode=2)
478
+
479
+ def to_dict(self):
480
+ return {key: value for key, value in {
481
+ "callbackUrl": self.callbackUrl, "payload": self.payload,
482
+ }.items() if value is not None}
483
+
484
+
237
485
  # noinspection method-overriding
238
486
  class Payout(xRocketObject):
239
487
  """Payout from the current application to a user.
@@ -264,7 +512,36 @@ class Payout(xRocketObject):
264
512
  @classmethod
265
513
  def de_json(cls, json_dict):
266
514
  data = cls.check_json(json_dict)
267
- return super(Payout, cls).de_json(data, process_mode=2)
515
+ instance = super(Payout, cls).de_json(data, process_mode=2)
516
+ if instance.amount is not None:
517
+ instance.amount = float(instance.amount)
518
+ if instance.callback is not None:
519
+ instance.callback = PayoutCallback.de_json(instance.callback)
520
+ return instance
521
+
522
+
523
+ # noinspection method-overriding
524
+ class WithdrawalCallback(Dictionaryable, xRocketObject):
525
+ """Webhook settings for a withdrawal.
526
+
527
+ API: https://docs.xrocket.exchange/api/pay/reference/http/withdrawal-controller-create-withdrawal
528
+
529
+ :param callbackUrl: Url for notify when withdrawal status is changed.
530
+ :param payload: Custom data sent to webhook.
531
+ """
532
+ def __init__(self, callbackUrl=None, payload=None):
533
+ self.callbackUrl = callbackUrl
534
+ self.payload = payload
535
+
536
+ @classmethod
537
+ def de_json(cls, json_dict):
538
+ data = cls.check_json(json_dict)
539
+ return super(WithdrawalCallback, cls).de_json(data, process_mode=2)
540
+
541
+ def to_dict(self):
542
+ return {key: value for key, value in {
543
+ "callbackUrl": self.callbackUrl, "payload": self.payload,
544
+ }.items() if value is not None}
268
545
 
269
546
 
270
547
  # noinspection method-overriding
@@ -299,7 +576,12 @@ class Withdrawal(xRocketObject):
299
576
  @classmethod
300
577
  def de_json(cls, json_dict):
301
578
  data = cls.check_json(json_dict)
302
- return super(Withdrawal, cls).de_json(data, process_mode=2)
579
+ instance = super(Withdrawal, cls).de_json(data, process_mode=2)
580
+ if instance.amount is not None:
581
+ instance.amount = float(instance.amount)
582
+ if instance.callback is not None:
583
+ instance.callback = WithdrawalCallback.de_json(instance.callback)
584
+ return instance
303
585
 
304
586
 
305
587
  # noinspection method-overriding
@@ -360,7 +642,10 @@ class Rate(xRocketObject):
360
642
  @classmethod
361
643
  def de_json(cls, json_dict):
362
644
  data = cls.check_json(json_dict)
363
- return super(Rate, cls).de_json(data, process_mode=2)
645
+ instance = super(Rate, cls).de_json(data, process_mode=2)
646
+ if instance.rate is not None:
647
+ instance.rate = float(instance.rate)
648
+ return instance
364
649
 
365
650
 
366
651
  # noinspection method-overriding
@@ -398,7 +683,10 @@ class CursorPagination(xRocketObject):
398
683
  @classmethod
399
684
  def de_json(cls, json_dict):
400
685
  data = cls.check_json(json_dict)
401
- return super(CursorPagination, cls).de_json(data, process_mode=2)
686
+ instance = super(CursorPagination, cls).de_json(data, process_mode=2)
687
+ if instance.total is not None:
688
+ instance.total = int(instance.total)
689
+ return instance
402
690
 
403
691
 
404
692
  # noinspection method-overriding
@@ -418,6 +706,147 @@ class InvoicePaymentsPagination(xRocketObject):
418
706
  return super(InvoicePaymentsPagination, cls).de_json(data, process_mode=2)
419
707
 
420
708
 
709
+ # noinspection method-overriding
710
+ class InvoicePaymentPayer(xRocketObject):
711
+ """Payer information for an invoice payment transaction.
712
+
713
+ API: https://docs.xrocket.exchange/api/pay/reference/http/invoice-controller-get-invoice-payments
714
+
715
+ :param email: Payer email.
716
+ :param telegramId: Payer telegram ID.
717
+ :param telegramUsername: Payer telegram username (without @).
718
+ """
719
+ def __init__(self):
720
+ self.email = None
721
+ self.telegramId = None
722
+ self.telegramUsername = None
723
+
724
+ @classmethod
725
+ def de_json(cls, json_dict):
726
+ data = cls.check_json(json_dict)
727
+ return super(InvoicePaymentPayer, cls).de_json(data, process_mode=2)
728
+
729
+
730
+ # noinspection method-overriding
731
+ class InvoicePaymentTransactionBlockchainDetails(xRocketObject):
732
+ """Blockchain-specific details of an invoice payment transaction.
733
+
734
+ API: https://docs.xrocket.exchange/api/pay/reference/http/invoice-controller-get-invoice-payments
735
+
736
+ :param network: Blockchain network code.
737
+ :param txHash: Transaction hash.
738
+ :param amount: Amount in this specific transaction.
739
+ :param currency: Currency of this transaction.
740
+ :param status: On-chain status. **WARNING**: This list may be extended in the future. Treat unknown statuses as "in progress".
741
+ """
742
+ def __init__(self):
743
+ self.network = None
744
+ self.txHash = None
745
+ self.amount = None
746
+ self.currency = None
747
+ self.status = None
748
+
749
+ @classmethod
750
+ def de_json(cls, json_dict):
751
+ data = cls.check_json(json_dict)
752
+ instance = super(InvoicePaymentTransactionBlockchainDetails, cls).de_json(data, process_mode=2)
753
+ if instance.amount is not None:
754
+ instance.amount = float(instance.amount)
755
+ return instance
756
+
757
+
758
+ # noinspection method-overriding
759
+ class InvoiceInternalTransaction(xRocketObject):
760
+ """Internal transaction belonging to an invoice payment.
761
+
762
+ API: https://docs.xrocket.exchange/api/pay/reference/http/invoice-controller-get-invoice-payments
763
+
764
+ :param id: Payment id.
765
+ :param status: Payment status. **WARNING**: This list may be extended in the future. Use exact string comparison and treat unknown statuses as "in progress".
766
+ :param payAmount: Amount payer sent (gross, before fees). Null when payment is created but amount is not yet known.
767
+ :param payCurrency: Currency payer used. Null when payment is created but currency is not yet known.
768
+ :param receiveAmount: Amount merchant receives after fees (in invoice priceCurrency). Null until payment is finalized.
769
+ :param receiveCurrency: Currency merchant receives (= invoice priceCurrency). Null until payment is finalized.
770
+ :param comment: Payer comment.
771
+ :param payer: Payer information.
772
+ :param createdAt: Payment creation time.
773
+ :param finalizedAt: When payment reached final state (null for in-progress payments).
774
+ :param type: Payment type.
775
+ """
776
+ def __init__(self):
777
+ self.id = None
778
+ self.status = None
779
+ self.payAmount = None
780
+ self.payCurrency = None
781
+ self.receiveAmount = None
782
+ self.receiveCurrency = None
783
+ self.comment = None
784
+ self.payer = None
785
+ self.createdAt = None
786
+ self.finalizedAt = None
787
+ self.type = None
788
+
789
+ @classmethod
790
+ def de_json(cls, json_dict):
791
+ data = cls.check_json(json_dict)
792
+ instance = super(InvoiceInternalTransaction, cls).de_json(data, process_mode=2)
793
+ if instance.payAmount is not None:
794
+ instance.payAmount = float(instance.payAmount)
795
+ if instance.receiveAmount is not None:
796
+ instance.receiveAmount = float(instance.receiveAmount)
797
+ if instance.payer is not None:
798
+ instance.payer = InvoicePaymentPayer.de_json(instance.payer)
799
+ return instance
800
+
801
+
802
+ # noinspection method-overriding
803
+ class InvoiceBlockchainTransaction(xRocketObject):
804
+ """Blockchain transaction belonging to an invoice payment.
805
+
806
+ API: https://docs.xrocket.exchange/api/pay/reference/http/invoice-controller-get-invoice-payments
807
+
808
+ :param id: Payment id.
809
+ :param status: Payment status. **WARNING**: This list may be extended in the future. Use exact string comparison and treat unknown statuses as "in progress".
810
+ :param payAmount: Amount payer sent (gross, before fees). Null when payment is created but amount is not yet known.
811
+ :param payCurrency: Currency payer used. Null when payment is created but currency is not yet known.
812
+ :param receiveAmount: Amount merchant receives after fees (in invoice priceCurrency). Null until payment is finalized.
813
+ :param receiveCurrency: Currency merchant receives (= invoice priceCurrency). Null until payment is finalized.
814
+ :param comment: Payer comment.
815
+ :param payer: Payer information.
816
+ :param createdAt: Payment creation time.
817
+ :param finalizedAt: When payment reached final state (null for in-progress payments).
818
+ :param type: Payment type.
819
+ :param tx: Blockchain-specific details. Present only for ``type=blockchain`` transactions.
820
+ """
821
+ def __init__(self):
822
+ self.id = None
823
+ self.status = None
824
+ self.payAmount = None
825
+ self.payCurrency = None
826
+ self.receiveAmount = None
827
+ self.receiveCurrency = None
828
+ self.comment = None
829
+ self.payer = None
830
+ self.createdAt = None
831
+ self.finalizedAt = None
832
+ self.type = None
833
+ self.tx = None
834
+
835
+ @classmethod
836
+ def de_json(cls, json_dict):
837
+ data = cls.check_json(json_dict)
838
+ instance = super(InvoiceBlockchainTransaction, cls).de_json(data, process_mode=2)
839
+ if instance.payAmount is not None:
840
+ instance.payAmount = float(instance.payAmount)
841
+ if instance.receiveAmount is not None:
842
+ instance.receiveAmount = float(instance.receiveAmount)
843
+ if instance.payer is not None:
844
+ instance.payer = InvoicePaymentPayer.de_json(instance.payer)
845
+ if instance.tx is not None:
846
+ instance.tx = InvoicePaymentTransactionBlockchainDetails.de_json(instance.tx)
847
+ return instance
848
+
849
+
421
850
  # noinspection method-overriding
422
851
  class InvoicePayment(xRocketObject):
423
852
  """A payment made for an invoice.
@@ -425,7 +854,7 @@ class InvoicePayment(xRocketObject):
425
854
  API: https://docs.xrocket.exchange/api/pay/reference/http/invoice-controller-get-invoice-payments
426
855
 
427
856
  :param id: Unique payment id. Use it to match webhook events with the invoice payments endpoint.
428
- :param status: Invoice status. **WARNING**: This list may be extended in the future. Always use exact status comparison and handle unknown statuses gracefully.
857
+ :param status: Payment status. **WARNING**: This list may be extended in the future. Use exact string comparison and treat unknown statuses as "in progress".
429
858
  :param finalizedAt: When payment reached final state (null for in-progress payments).
430
859
  :param payAmount: Gross amount payer sent in total across all transactions of this payment (before fees).
431
860
  :param payCurrency: Currency payer used.
@@ -446,7 +875,22 @@ class InvoicePayment(xRocketObject):
446
875
  @classmethod
447
876
  def de_json(cls, json_dict):
448
877
  data = cls.check_json(json_dict)
449
- return super(InvoicePayment, cls).de_json(data, process_mode=2)
878
+ instance = super(InvoicePayment, cls).de_json(data, process_mode=2)
879
+ if instance.payAmount is not None:
880
+ instance.payAmount = float(instance.payAmount)
881
+ if instance.receiveAmount is not None:
882
+ instance.receiveAmount = float(instance.receiveAmount)
883
+ transactions = []
884
+ for transaction in instance.transactions:
885
+ transaction_data = cls.check_json(transaction)
886
+ if transaction_data.get("type") == "internal":
887
+ transactions.append(InvoiceInternalTransaction.de_json(transaction_data))
888
+ elif transaction_data.get("type") == "blockchain":
889
+ transactions.append(InvoiceBlockchainTransaction.de_json(transaction_data))
890
+ else:
891
+ transactions.append(xRocketObject.de_json(transaction_data, process_mode=2))
892
+ instance.transactions = transactions
893
+ return instance
450
894
 
451
895
 
452
896
  # noinspection method-overriding
@@ -566,7 +1010,10 @@ class InvoicePaymentAddress(xRocketObject):
566
1010
  @classmethod
567
1011
  def de_json(cls, json_dict):
568
1012
  data = cls.check_json(json_dict)
569
- return super(InvoicePaymentAddress, cls).de_json(data, process_mode=2)
1013
+ instance = super(InvoicePaymentAddress, cls).de_json(data, process_mode=2)
1014
+ if instance.minAmount is not None:
1015
+ instance.minAmount = float(instance.minAmount)
1016
+ return instance
570
1017
 
571
1018
 
572
1019
  # noinspection method-overriding
@@ -589,7 +1036,14 @@ class WithdrawalQuotas(xRocketObject):
589
1036
  @classmethod
590
1037
  def de_json(cls, json_dict):
591
1038
  data = cls.check_json(json_dict)
592
- return super(WithdrawalQuotas, cls).de_json(data, process_mode=2)
1039
+ instance = super(WithdrawalQuotas, cls).de_json(data, process_mode=2)
1040
+ if instance.withdrawMinSize is not None:
1041
+ instance.withdrawMinSize = float(instance.withdrawMinSize)
1042
+ if instance.withdrawFee is not None:
1043
+ instance.withdrawFee = float(instance.withdrawFee)
1044
+ if instance.precision is not None:
1045
+ instance.precision = int(instance.precision)
1046
+ return instance
593
1047
 
594
1048
 
595
1049
  # noinspection method-overriding
@@ -613,6 +1067,42 @@ class WithdrawalLink(xRocketObject):
613
1067
  return super(WithdrawalLink, cls).de_json(data, process_mode=2)
614
1068
 
615
1069
 
1070
+ # noinspection method-overriding
1071
+ class MassPayout(Dictionaryable, xRocketObject):
1072
+ """One payout supplied to a mass-payout request.
1073
+
1074
+ API: https://docs.xrocket.exchange/api/pay/reference/http/mass-payouts-controller-create-mass-payouts
1075
+
1076
+ :param target: Target.
1077
+ :param targetType: Target type (only TelegramUserId is supported for mass payouts).
1078
+ :param amount: Payout amount.
1079
+ :param clientPayoutId: Unique payout ID in your system to prevent double spends.
1080
+ :param description: Payout description.
1081
+ :param callback: Webhook settings for payout status updates.
1082
+ """
1083
+ def __init__(self, target=None, targetType=None, amount=None, clientPayoutId=None,
1084
+ description=None, callback=None):
1085
+ self.target = target
1086
+ self.targetType = targetType
1087
+ self.amount = amount
1088
+ self.clientPayoutId = clientPayoutId
1089
+ self.description = description
1090
+ self.callback = callback
1091
+
1092
+ def to_dict(self):
1093
+ if self.callback is not None and not isinstance(self.callback, PayoutCallback):
1094
+ raise TypeError("callback must be an instance of PayoutCallback.")
1095
+ data = {
1096
+ "target": self.target,
1097
+ "targetType": self.targetType,
1098
+ "amount": self._number_as_string(self.amount),
1099
+ "clientPayoutId": self.clientPayoutId,
1100
+ "description": self.description,
1101
+ "callback": self.callback.to_dict() if self.callback is not None else None,
1102
+ }
1103
+ return {key: value for key, value in data.items() if value is not None}
1104
+
1105
+
616
1106
  # noinspection method-overriding
617
1107
  class MassPayoutReason(xRocketObject):
618
1108
  """Problem details for one failed mass payout.
@@ -665,6 +1155,8 @@ class MassPayoutError(xRocketObject):
665
1155
  def de_json(cls, json_dict):
666
1156
  data = cls.check_json(json_dict)
667
1157
  instance = super(MassPayoutError, cls).de_json(data, process_mode=2)
1158
+ if instance.amount is not None:
1159
+ instance.amount = float(instance.amount)
668
1160
  if instance.reason is not None:
669
1161
  instance.reason = MassPayoutReason.de_json(instance.reason)
670
1162
  return instance
pyXRocketAPI/version.py CHANGED
@@ -1,3 +1,3 @@
1
1
  """Package version, read by the build configuration."""
2
2
 
3
- __version__ = "0.2.0"
3
+ __version__ = "0.3.0"
pyXRocketAPI/webhooks.py CHANGED
@@ -58,8 +58,6 @@ track invoice completion only when ``event`` is
58
58
  ``invoice_status_changed`` and ``event.data.invoice.status`` is ``paid``.
59
59
  Payment finality is indicated by ``event.data.payment.finalizedAt`` not being
60
60
  ``None``, rather than by relying on one fixed status value.
61
-
62
- AI generated. Manually validated.
63
61
  """
64
62
 
65
63
  import hashlib
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyXRocketAPI
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Python client for the xRocket Pay API
5
5
  Author: Badiboy
6
6
  License-Expression: MIT
@@ -47,6 +47,40 @@ For integration testing, use the xRocket testnet instead of production:
47
47
  client = xRocketPayAPI(token="testnet-token", testnet=True)
48
48
  ```
49
49
 
50
+ ## Nested request models
51
+
52
+ Known nested API objects are passed as model instances, not dictionaries. The
53
+ same models deserialize API responses and implement `to_dict()` for request
54
+ bodies.
55
+
56
+ ```python
57
+ from pyXRocketAPI import InvoiceCallback, InvoiceCustomer, InvoiceUrl, xRocketPayAPI
58
+
59
+ client = xRocketPayAPI(token="your-pay-api-token")
60
+ invoice = client.create_invoice(
61
+ price_currency="USDT",
62
+ price_amount="10.00",
63
+ callback=InvoiceCallback(
64
+ callbackUrl="https://example.com/webhooks/invoice",
65
+ payload={"order_id": "123"},
66
+ ),
67
+ url=InvoiceUrl(
68
+ successUrl="https://example.com/success",
69
+ cancelUrl="https://example.com/cancel",
70
+ ),
71
+ customer=InvoiceCustomer(
72
+ id="customer-123",
73
+ email="customer@example.com",
74
+ telegramId="123456789",
75
+ ),
76
+ data={"internal_order_id": "123"},
77
+ )
78
+ ```
79
+
80
+ `payload` and `data` remain ordinary dictionaries because xRocket defines them
81
+ as arbitrary JSON objects without a fixed schema. Use `MassPayout` instances
82
+ for every item passed to `create_mass_payouts()`.
83
+
50
84
  ## Safety
51
85
 
52
86
  The token grants access to the application and must not be committed to source control. Financial POST operations are not retried automatically. Supply and retain the relevant client identifier (`client_invoice_id`, `client_payout_id`, `client_cheque_id`, or `client_withdrawal_id`) so that a timed-out operation can be reconciled safely.
@@ -0,0 +1,11 @@
1
+ pyXRocketAPI/__init__.py,sha256=3zwtjTkq3OUBFdzRbXDbPj7cw1T0FVEbq0ZpM9BBziE,105
2
+ pyXRocketAPI/api.py,sha256=QZ7f3i-j1Ku9v74avHE8FIIpePfkbLgZz-lRA4hMVZw,28920
3
+ pyXRocketAPI/exceptions.py,sha256=4In1-OCoLSzgw7O9XTvr7z3uLg7jtE31bvDijMcCeFg,1255
4
+ pyXRocketAPI/models.py,sha256=jiQJlz3YYe0dHlMo61mf3UKCJqBlLXIl0uBdRnk4lkc,43563
5
+ pyXRocketAPI/version.py,sha256=9qdh2HVo2HCzHUvRO0ud8rwcZZnchxg61QL5HU8HUSw,79
6
+ pyXRocketAPI/webhooks.py,sha256=Q11biIxchjwnWehb3-216hx4n1ItBMtoj-W2vsSS_7U,19184
7
+ pyxrocketapi-0.3.0.dist-info/licenses/LICENSE,sha256=iw-28FPO0DiYiTwy8rfgljZxjhA12F5hhqHe_BpYW-o,1064
8
+ pyxrocketapi-0.3.0.dist-info/METADATA,sha256=X66veBTUkkp3MM9UTnC4LW60lqpLsQn2zZJCBWOTvZY,3451
9
+ pyxrocketapi-0.3.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
10
+ pyxrocketapi-0.3.0.dist-info/top_level.txt,sha256=33Ju2GBtWw8-eJeVkNtL7TR1g7SV-g9_3dw_Y56KvmU,13
11
+ pyxrocketapi-0.3.0.dist-info/RECORD,,
@@ -1,11 +0,0 @@
1
- pyXRocketAPI/__init__.py,sha256=3zwtjTkq3OUBFdzRbXDbPj7cw1T0FVEbq0ZpM9BBziE,105
2
- pyXRocketAPI/api.py,sha256=5LJJf5q3o1xJS5uIRJrMQ-VQ_o-q5jWVtsnPqvkuW1M,26164
3
- pyXRocketAPI/exceptions.py,sha256=4In1-OCoLSzgw7O9XTvr7z3uLg7jtE31bvDijMcCeFg,1255
4
- pyXRocketAPI/models.py,sha256=8Fsw2fRsYJKIT6Sjqrvx-Zkm58TUaBgPshXZ54EtJxI,24154
5
- pyXRocketAPI/version.py,sha256=VvY0Say_zvbS0e6COcolPJITN1i-2rYEQsWDPBecsOs,79
6
- pyXRocketAPI/webhooks.py,sha256=wqACD1yQCaIPWHtwD1_HBcgqXdrKbpmAwtNCiOThZ84,19219
7
- pyxrocketapi-0.2.0.dist-info/licenses/LICENSE,sha256=iw-28FPO0DiYiTwy8rfgljZxjhA12F5hhqHe_BpYW-o,1064
8
- pyxrocketapi-0.2.0.dist-info/METADATA,sha256=zM421Y7nyXhFvXMFUMZ5QfZEVFpgn0kmQIbJhfi7ARE,2349
9
- pyxrocketapi-0.2.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
10
- pyxrocketapi-0.2.0.dist-info/top_level.txt,sha256=33Ju2GBtWw8-eJeVkNtL7TR1g7SV-g9_3dw_Y56KvmU,13
11
- pyxrocketapi-0.2.0.dist-info/RECORD,,