diff --git a/request_params.py b/request_params.py index d0bf614..8c7ba95 100644 --- a/request_params.py +++ b/request_params.py @@ -2,38 +2,133 @@ from typing import Any +@dataclass(frozen=True) +class TransactionType: + """ + TransactionType.Auth - Authorize only + TransactionType.Sale - Auth & Capture + """ + Auth = "A" + Sale = "S" + + +@dataclass(frozen=True) +class DurationUnit: + Day = "D" + Week = "W" + Month = "M" + Year = "Y" + + +@dataclass(frozen=True) +class ScheduleType: + """ + ScheduleType.Each - Each specific day + ScheduleType.Every - Every X days + """ + Each = "E" + Every = "D" + + @dataclass -class PaymentRequestParams: +class BaseParamsClass: + def __to_dict_nested(self, field: Any) -> Any: + match field: + case BaseParamsClass(): return field.to_dict() + case list(): return [self.__to_dict_nested(i) for i in field if i is not None] + case _: return field + + def _get(self, field: str) -> Any: + return getattr(self, field) + + def to_dict(self) -> dict: + """ + Converts this dataclass to a dict. + Optional 'None' params are excluded. + """ + return dict((field.name, self.__to_dict_nested(self._get(field.name))) + for field in fields(self) if getattr(self, field.name) is not None) + + +@dataclass +class Address(BaseParamsClass): + firstname: str + address: str + city: str + state: str + zipcode: str + country: str + lastname: str | None = None + zip4: str | None = None + company: str | None = None + phone: str | None = None + fax: str | None = None + + +@dataclass +class SubscriptionSchedule(BaseParamsClass): + type: ScheduleType | None = None + number: int | None = None + reverse: bool | None = None + period: DurationUnit | None = None + periods: int | None = None + + +@dataclass +class SubscriptionPlan(BaseParamsClass): + subscriptionSchedule: SubscriptionSchedule | None = None + callbackUrl: str | None = None + recurringAmount: float | None = None + description: str | None = None + uniqueOrderItemId: str | None = None + trialDuration: int | None = None + trialDurationUnit: DurationUnit | None = None + + +@dataclass +class CartItem(BaseParamsClass): + sku: str + name: str + price: float + quantity: int | None = None + isSubscription: bool | None = None + subscriptionPlan: SubscriptionPlan | None = None + + +@dataclass +class Cart(BaseParamsClass): + billingAddress: Address + merchantEmail: str + currency: str + items: list[CartItem] + totalCost: float + shippingAddress: Address | None = None + login: str | None = None + shippingCost: float | None = None + taxCost: float | None = None + discount: float | None = None + description: str | None = None + + +@dataclass +class PaymentRequestParams(BaseParamsClass): token: str refId: str - customerId: str returnUrl: str - callbackUrl: str - cart: list + cart: Cart + callbackUrl: str | None = None + customerId: str | None = None forceSaveCard: bool | None = None - forceTransactionType: str | None = None + forceTransactionType: TransactionType | None = None confId: int | None = None - def __get(self, field: str) -> Any: + def _get(self, field: str) -> Any: + """ + Boolean values are replaced: True->Y / False->N + """ val = getattr(self, field) if type(val) is bool: return "Y" if val is True else "N" return val - def to_dict(self) -> dict: - """ - Converts this dataclass to a dict. - 'None' params are excluded. Boolean values are replaced: True->Y / False->N - """ - return dict((field.name, self.__get(field.name)) - for field in fields(self) if getattr(self, field.name) is not None) - -@dataclass(frozen=True) -class TransactionType: - """ - TransactionType.A - Authorize only - TransactionType.S - Sale (Auth & Capture) - """ - A = "A" - S = "S" diff --git a/xpayments_cloud.py b/xpayments_cloud.py index d6ecd9d..a0b1dd9 100644 --- a/xpayments_cloud.py +++ b/xpayments_cloud.py @@ -7,7 +7,9 @@ from exceptions import IllegalArgumentError, JSONProcessingError, UnicodeProcessingError from request_params import PaymentRequestParams from dotenv import dotenv_values +from typing import TypeAlias +JSON: TypeAlias = dict[str, 'JSON'] | list['JSON'] | str | int | float | bool | None config = dotenv_values(".env") @@ -32,7 +34,7 @@ def __init__(self, account: str, api_key: str, secret_key: str) -> None: self.secret_key = str(secret_key) self.TEST_SERVER_HOST = config.get('TEST_SERVER_HOST', '') - def send(self, controller: str, action: str, request_data: dict) -> str: + def send(self, controller: str, action: str, request_data: dict) -> JSON: """ Send API request to X-Payments Cloud """ @@ -49,7 +51,6 @@ def send(self, controller: str, action: str, request_data: dict) -> str: raise HTTPError response.raise_for_status() try: - # print(response.json()) return response.json() except JSONDecodeError as ex: raise JSONProcessingError(ex.msg) @@ -106,19 +107,19 @@ def __init__(self, account: str, api_key: str, secret_key: str) -> None: self.secret_key = str(secret_key) self.request = Request(account=self.account, api_key=self.api_key, secret_key=self.secret_key) - def do_pay(self, params: PaymentRequestParams) -> str: + def do_pay(self, params: PaymentRequestParams) -> JSON: """ Process payment """ return self.request.send(controller='payment', action='pay', request_data=params.to_dict()) - def do_tokenize_card(self, params: PaymentRequestParams) -> str: + def do_tokenize_card(self, params: PaymentRequestParams) -> JSON: """ Tokenize card """ return self.request.send(controller='payment', action='tokenize_card', request_data=params.to_dict()) - def do_rebill(self, xpid: str, ref_id: str, customer_id: str, cart: list, callback_url: str) -> str: + def do_rebill(self, xpid: str, ref_id: str, customer_id: str, cart: list, callback_url: str) -> JSON: """ Rebill payment (process payment using the saved card) """ @@ -131,7 +132,7 @@ def do_rebill(self, xpid: str, ref_id: str, customer_id: str, cart: list, callba } return self.request.send(controller='payment', action='rebill', request_data=params) - def do_action(self, action: str, xpid: str, amount: int | None = None) -> str: + def do_action(self, action: str, xpid: str, amount: int | None = None) -> JSON: """ Execute secondary payment action """ @@ -140,49 +141,49 @@ def do_action(self, action: str, xpid: str, amount: int | None = None) -> str: params.amount = amount return self.request.send(controller='payment', action=action, request_data=params) - def do_capture(self, xpid: str, amount: int) -> str: + def do_capture(self, xpid: str, amount: int) -> JSON: """ Capture payment """ return self.do_action(action='capture', xpid=xpid, amount=amount) - def do_void(self, xpid: str, amount: int) -> str: + def do_void(self, xpid: str, amount: int) -> JSON: """ Void payment """ return self.do_action(action='void', xpid=xpid, amount=amount) - def do_refund(self, xpid: str, amount: int) -> str: + def do_refund(self, xpid: str, amount: int) -> JSON: """ Refund payment """ return self.do_action(action='refund', xpid=xpid, amount=amount) - def do_continue(self, xpid: str) -> str: + def do_continue(self, xpid: str) -> JSON: """ Continue payment """ return self.do_action(action='continue', xpid=xpid) - def do_accept(self, xpid: str) -> str: + def do_accept(self, xpid: str) -> JSON: """ Accept payment """ return self.do_action(action='accept', xpid=xpid) - def do_decline(self, xpid: str) -> str: + def do_decline(self, xpid: str) -> JSON: """ Decline payment """ return self.do_action(action='decline', xpid=xpid) - def do_get_info(self, xpid: str) -> str: + def do_get_info(self, xpid: str) -> JSON: """ Get detailed payment information """ return self.do_action(action='get_info', xpid=xpid) - def do_get_customer_cards(self, customer_id: str, status: str = 'any') -> str: + def do_get_customer_cards(self, customer_id: str, status: str = 'any') -> JSON: """ Get customer's cards """ @@ -192,7 +193,7 @@ def do_get_customer_cards(self, customer_id: str, status: str = 'any') -> str: } return self.request.send(controller='customer', action='get_cards', request_data=params) - def do_add_bulk_operation(self, operation: str, xpids: list[str]) -> str: + def do_add_bulk_operation(self, operation: str, xpids: list[str]) -> JSON: """ Add bulk operation """ @@ -202,28 +203,28 @@ def do_add_bulk_operation(self, operation: str, xpids: list[str]) -> str: } return self.request.send(controller='bulk_operation', action='add', request_data=params) - def do_start_bulk_operation(self, batch_id: str) -> str: + def do_start_bulk_operation(self, batch_id: str) -> JSON: """ Start bulk operation """ params = {"batch_id": batch_id} return self.request.send(controller='bulk_operation', action='start', request_data=params) - def do_stop_bulk_operation(self, batch_id: str) -> str: + def do_stop_bulk_operation(self, batch_id: str) -> JSON: """ Stop bulk operation """ params = {"batch_id": batch_id} return self.request.send(controller='bulk_operation', action='stop', request_data=params) - def do_get_bulk_operation(self, batch_id: str) -> str: + def do_get_bulk_operation(self, batch_id: str) -> JSON: """ Get bulk operation """ params = {"batch_id": batch_id} return self.request.send(controller='bulk_operation', action='get', request_data=params) - def do_delete_bulk_operation(self, batch_id: str) -> str: + def do_delete_bulk_operation(self, batch_id: str) -> JSON: """ Delete bulk operation """