Skip to content

Refactoring, part 2 #3

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 117 additions & 22 deletions request_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
39 changes: 20 additions & 19 deletions xpayments_cloud.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")


Expand All @@ -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
"""
Expand All @@ -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)
Expand Down Expand Up @@ -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)
"""
Expand All @@ -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
"""
Expand All @@ -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
"""
Expand All @@ -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
"""
Expand All @@ -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
"""
Expand Down