Skip to content

Commit

Permalink
Make API consistent
Browse files Browse the repository at this point in the history
  • Loading branch information
oto-stefan committed Jul 5, 2024
1 parent 6097a9a commit 63d2851
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 100 deletions.
46 changes: 6 additions & 40 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ like ``payload`` or ``extensions``.

.. code-block:: python
r = c.payment_init(14, 1000000, 'http://twisto.dev/', 'Tesovaci nakup', customer_id='[email protected]',
return_method='GET', pay_operation='payment', merchant_data=[1, 2, 3])
r = c.payment_init(14, 1000000, 'http://twisto.dev/', 'Testovaci nakup', customer_id='[email protected]',
return_method=HttpMethod.GET, pay_operation=PayOperation.PAYMENT, merchant_data=[1, 2, 3])
r.payload
#[Out]# OrderedDict([('payId', 'b627c1e4e60fcBF'),
#[Out]# ('dttm', '20160615104254'),
Expand Down Expand Up @@ -98,7 +98,7 @@ appropriate enumerations are available.
"pay_method": PayMethod.CARD,
"currency": Currency.CZK,
"close_payment": True,
"return_method": ReturnMethod.POST,
"return_method": HttpMethod.POST,
"cart": [
CartItem(name="Wireless sluchátka", quantity=2, amount=123400),
CartItem(name="Doprava", quantity=1, amount=0, description="DPL"),
Expand Down Expand Up @@ -129,13 +129,13 @@ appropriate enumerations are available.
r = c.payment_init(16, 123400, "http://twisto.dev/", "Testovací nákup", **data)
Custom payments are initialized with ``c.payment_init(pay_operation='customPayment')``, you can optionally set
Custom payments are initialized with ``c.payment_init(pay_operation=PayOperation.CUSTOM_PAYMENT)``, you can optionally set
payment validity by ``custom_expiry='YYYYMMDDhhmmss'``.

.. code-block:: python
r = c.payment_init(14, 1000000, 'http://twisto.dev/', 'Testovaci nakup', return_method='POST',
pay_operation='customPayment', custom_expiry='20160630120000')
r = c.payment_init(14, 1000000, 'http://twisto.dev/', 'Testovaci nakup', return_method=HttpMethod.POST,
pay_operation=PayOperation.CUSTOM_PAYMENT, custom_expiry='20160630120000')
r.payload
#[Out]# OrderedDict([('payId', 'b627c1e4e60fcBF'),
#[Out]# ('dttm', '20160615104254'),
Expand All @@ -149,40 +149,6 @@ Send (by whatever means) obtained ``customerCode`` to customer who can then perf
on URL ``https://platebnibrana.csob.cz/payment/{customerCode}`` (``c.get_payment_process_url`` is not applicable
for custom payments).

You can also use one-click payment methods. For this you need
to call ``c.payment_init(pay_operation='oneclickPayment')``. After this transaction confirmed
you can use obtained ``payId`` as template for one-click payment.

.. code-block:: python
r = c.oneclick_init('1e058ff1d0d5aBF', 666, 10000)
r.payload
#[Out]# OrderedDict([('payId', 'ff7d3e7c6c4fdBF'),
#[Out]# ('dttm', '20160615104532'),
#[Out]# ('resultCode', 0),
#[Out]# ('resultMessage', 'OK'),
#[Out]# ('paymentStatus', 1),
#[Out]# ('dttime', datetime.datetime(2016, 6, 15, 10, 45, 32))])
r = c.oneclick_start('ff7d3e7c6c4fdBF')
r.payload
#[Out]# OrderedDict([('payId', 'ff7d3e7c6c4fdBF'),
#[Out]# ('dttm', '20160615104619'),
#[Out]# ('resultCode', 0),
#[Out]# ('resultMessage', 'OK'),
#[Out]# ('paymentStatus', 2),
#[Out]# ('dttime', datetime.datetime(2016, 6, 15, 10, 46, 19))])
r = c.payment_status('ff7d3e7c6c4fdBF')
r.payload
#[Out]# OrderedDict([('payId', 'ff7d3e7c6c4fdBF'),
#[Out]# ('dttm', '20160615104643'),
#[Out]# ('resultCode', 0),
#[Out]# ('resultMessage', 'OK'),
#[Out]# ('paymentStatus', 7),
#[Out]# ('authCode', '168164'),
#[Out]# ('dttime', datetime.datetime(2016, 6, 15, 10, 46, 43))])
Of course you can use standard requests's methods on ``response`` object.

.. code-block:: python
Expand Down
170 changes: 114 additions & 56 deletions pycsob/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ class PayOperation(Enum):
"""Pay operations allowed on card gateway."""

PAYMENT = "payment"
ONECLICK_PAYMENT = "oneclickPayment"
# ONECLICK_PAYMENT = "oneclickPayment"
CUSTOM_PAYMENT = "customPayment"


Expand All @@ -96,8 +96,8 @@ class PayMethod(Enum):


@unique
class ReturnMethod(Enum):
"""Available HTTP methods."""
class HttpMethod(Enum):
"""Available HTTP methods (for echo and redirection)."""

GET = "GET"
POST = "POST"
Expand Down Expand Up @@ -296,7 +296,7 @@ class CsobClient(object):

def __init__(self, merchant_id, base_url, private_key_file, csob_pub_key_file):
"""
Initialize Client
Initialize client
:param merchant_id: Your Merchant ID (you can find it in POSMerchant)
:param base_url: Base API url development / production
Expand Down Expand Up @@ -326,7 +326,7 @@ def payment_init(
currency: Currency = Currency.CZK,
language: Language = Language.CZECH,
close_payment: bool = True,
return_method: ReturnMethod = ReturnMethod.POST,
return_method: HttpMethod = HttpMethod.POST,
pay_operation: PayOperation = PayOperation.PAYMENT,
ttl_sec: int = 600,
logo_version: Optional[int] = None,
Expand All @@ -338,7 +338,7 @@ def payment_init(
pay_method: PayMethod = PayMethod.CARD,
) -> OrderedDict[str, Any]:
"""
Initialize transaction, sum of cart items must be equal to total amount
Initialize transaction, sum of cart items must be equal to total amount.
If cart is None, we create it for you from total_amount and description values.
Cart example::
Expand Down Expand Up @@ -369,6 +369,7 @@ def payment_init(
:param pay_method: 'card' = card payment, 'card#LVP' = card payment with low value payment
:return: response from gateway as OrderedDict
"""
# Documentation: https://github.com/csob/paymentgateway/wiki/Basic-methods#payment-init-operation

# fill cart if not set
if not cart:
Expand Down Expand Up @@ -400,78 +401,94 @@ def payment_init(
r = self._client.post(url, data=json.dumps(payload))
return utils.validate_response(r, self.f_pubkey)

def get_payment_process_url(self, pay_id):
def get_payment_process_url(self, pay_id: str) -> str:
"""
Get URL to process payment on gateway.
:param pay_id: pay_id obtained from payment_init()
:return: url to process payment
"""
# Documentation: https://github.com/csob/paymentgateway/wiki/Basic-methods#paymentprocess-method-
return utils.mk_url(
base_url=self.base_url,
endpoint_url='payment/process/',
payload=self.req_payload(pay_id=pay_id)
payload=self._req_payload(pay_id=pay_id)
)

def gateway_return(self, datadict):
def payment_status(self, pay_id: str) -> OrderedDict[str, Any]:
"""
Return from gateway as OrderedDict
Get current status of payment.
:param datadict: data from request in dict
:return: verified data or raise error
:param pay_id: pay_id obtained from payment_init()
:return: response from gateway as OrderedDict
"""
o = OrderedDict()
for k in conf.RESPONSE_KEYS:
if k in datadict:
o[k] = int(datadict[k]) if k in ('resultCode', 'paymentStatus') else datadict[k]
if not utils.verify(o, datadict['signature'], self.f_pubkey):
raise utils.CsobVerifyError('Unverified gateway return data')
if "dttm" in o:
o["dttime"] = utils.dttm_decode(o["dttm"])
if 'merchantData' in o:
o['merchantData'] = b64decode(o['merchantData'])
return o

def payment_status(self, pay_id):
# Documentation: https://github.com/csob/paymentgateway/wiki/Basic-methods#paymentstatus-method-
url = utils.mk_url(
base_url=self.base_url,
endpoint_url='payment/status/',
payload=self.req_payload(pay_id=pay_id)
payload=self._req_payload(pay_id=pay_id)
)
r = self._client.get(url=url)
return utils.validate_response(r, self.f_pubkey)

def payment_reverse(self, pay_id):
def payment_reverse(self, pay_id: str) -> OrderedDict[str, Any]:
"""
Reverse an already authorised payment (cancel it prior to sending into balancing).
:param pay_id: pay_id obtained from payment_init()
:return: response from gateway as OrderedDict
"""
# Documentation: https://github.com/csob/paymentgateway/wiki/Basic-methods#paymentreverse-method-
url = utils.mk_url(
base_url=self.base_url,
endpoint_url='payment/reverse/'
)
payload = self.req_payload(pay_id)
payload = self._req_payload(pay_id=pay_id)
r = self._client.put(url, data=json.dumps(payload))
return utils.validate_response(r, self.f_pubkey)

def payment_close(self, pay_id, total_amount=None):
def payment_close(self, pay_id: str, total_amount: Optional[int] = None) -> OrderedDict[str, Any]:
"""
Insert payment into settlement.
:param pay_id: pay_id obtained from payment_init()
:param total_amount: final price (less than or equal to original price)
:return: response from gateway as OrderedDict
"""
# Documentation: reversal of an already authorised payment (cancelled prior to sending into balancing)
url = utils.mk_url(
base_url=self.base_url,
endpoint_url='payment/close/'
)
payload = self.req_payload(pay_id, totalAmount=total_amount)
payload = self._req_payload(pay_id=pay_id, totalAmount=total_amount)
r = self._client.put(url, data=json.dumps(payload))
return utils.validate_response(r, self.f_pubkey)

def payment_refund(self, pay_id, amount=None):
def payment_refund(self, pay_id: str, amount: Optional[int] = None) -> OrderedDict[str, Any]:
"""
Requested to return back funds to buyer.
:param pay_id: pay_id obtained from payment_init()
:param amount: required amount for partial refund
:return: response from gateway as OrderedDict
"""
# Documentation: https://github.com/csob/paymentgateway/wiki/Basic-methods#paymentrefund-method-
url = utils.mk_url(
base_url=self.base_url,
endpoint_url='payment/refund/'
)

payload = self.req_payload(pay_id, amount=amount)
payload = self._req_payload(pay_id=pay_id, amount=amount)
r = self._client.put(url, data=json.dumps(payload))
return utils.validate_response(r, self.f_pubkey)

def customer_info(self, customer_id):
def customer_info(self, customer_id: str) -> OrderedDict[str, Any]:
"""
Retrieve saved customer information.
:param customer_id: e-shop customer ID
:return: data from JSON response or raise error
:return: response from gateway as OrderedDict
"""
# Documentation: https://github.com/csob/paymentgateway/wiki/Basic-methods#echocustomer-method-
url = utils.mk_url(
base_url=self.base_url,
endpoint_url='echo/customer'
Expand All @@ -484,18 +501,19 @@ def customer_info(self, customer_id):
r = self._client.post(url, data=json.dumps(payload))
return utils.validate_response(r, self.f_pubkey)

def echo(self, method='POST'):
def echo(self, method: HttpMethod = HttpMethod.POST) -> OrderedDict[str, Any]:
"""
Echo call for development purposes/gateway tests
Echo call for development purposes/gateway tests.
:param method: request method (GET/POST), default is POST
:return: data from JSON response or raise error
:return: response from gateway as OrderedDict
"""
# Documentation: https://github.com/csob/paymentgateway/wiki/Basic-methods#echo-method-
payload = utils.mk_payload(self.f_key, pairs=(
('merchantId', self.merchant_id),
('dttm', utils.dttm())
))
if method.lower() == 'post':
if method is HttpMethod.POST:
url = utils.mk_url(
base_url=self.base_url,
endpoint_url='echo/'
Expand All @@ -511,22 +529,29 @@ def echo(self, method='POST'):

return utils.validate_response(r, self.f_pubkey)

def req_payload(self, pay_id, **kwargs):
pairs = (
('merchantId', self.merchant_id),
('payId', pay_id),
('dttm', utils.dttm()),
)
for k, v in kwargs.items():
if v not in conf.EMPTY_VALUES:
pairs += ((k, v),)
return utils.mk_payload(keyfile=self.f_key, pairs=pairs)

def button_init(
self, order_no, total_amount, client_ip, return_url,
language='cs', return_method='POST', merchant_data=None):
"Get url to the button."

self,
order_no: str,
total_amount: int,
client_ip: str,
return_url: str,
return_method: HttpMethod = HttpMethod.POST,
language: Language = Language.CZECH,
merchant_data=None
) -> OrderedDict[str, Any]:
"""
Initialize payment with payment button.
:param order_no: order number
:param total_amount: total price
:param client_ip: IP address of customer (IPv4/IPv6)
:param return_url: URL to be returned to from payment gateway
:param return_method: method which be used for return to shop from gateway POST (default) or GET
:param language: supported languages: cs, en, de, fr, hu, it, ja, pl, pt, ro, ru, sk, es, tr, vi, hr, sl, sv
:param merchant_data: bytearray of merchant data
:return: response from gateway as OrderedDict
"""
# Documentation: https://github.com/csob/paymentgateway/wiki/Methods-for-%C4%8CSOB-Payment-Button#buttoninit-method-
payload = utils.mk_payload(self.f_key, pairs=(
('merchantId', self.merchant_id),
('orderNo', str(order_no)),
Expand All @@ -535,11 +560,44 @@ def button_init(
('totalAmount', total_amount),
('currency', 'CZK'),
('returnUrl', return_url),
('returnMethod', return_method),
('returnMethod', return_method.value),
('brand', 'csob'),
('merchantData', utils.encode_merchant_data(merchant_data)),
('language', language[:2]),
('language', language.value),
))
url = utils.mk_url(base_url=self.base_url, endpoint_url='button/init')
r = self._client.post(url, data=json.dumps(payload))
return utils.validate_response(r, self.f_pubkey)

def _req_payload(self, pay_id: str, **kwargs: Any) -> OrderedDict[str, Any]:
"""
Help to create request payload.
"""
pairs = (
('merchantId', self.merchant_id),
('payId', pay_id),
('dttm', utils.dttm()),
)
for k, v in kwargs.items():
if v not in conf.EMPTY_VALUES:
pairs += ((k, v),)
return utils.mk_payload(keyfile=self.f_key, pairs=pairs)

def _gateway_return(self, datadict: Dict[str, Any]) -> OrderedDict[str, Any]:
"""
Return from gateway as OrderedDict
:param datadict: data from request in dict
:return: verified data or raise error
"""
o = OrderedDict()
for k in conf.RESPONSE_KEYS:
if k in datadict:
o[k] = int(datadict[k]) if k in ('resultCode', 'paymentStatus') else datadict[k]
if not utils.verify(o, datadict['signature'], self.f_pubkey):
raise utils.CsobVerifyError('Unverified gateway return data')
if "dttm" in o:
o["dttime"] = utils.dttm_decode(o["dttm"])
if 'merchantData' in o:
o['merchantData'] = b64decode(o['merchantData'])
return o
Loading

0 comments on commit 63d2851

Please sign in to comment.