diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 524f04f..d93cd36 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -9,6 +9,6 @@ jobs: pre-commit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-python@v3 - uses: pre-commit/action@v3.0.1 diff --git a/khipu_tools/pykhipu/banks.py b/khipu_tools/pykhipu/banks.py index 710712a..4b7c047 100644 --- a/khipu_tools/pykhipu/banks.py +++ b/khipu_tools/pykhipu/banks.py @@ -1,14 +1,13 @@ -# -*- coding: utf-8 -*- import requests from pykhipu.responses import BanksResponse -class Banks(object): - ENDPOINT = '/banks' +class Banks: + ENDPOINT = "/banks" def __init__(self, client): self.client = client def get(self): - response = self.client.make_request('GET', self.ENDPOINT) + response = self.client.make_request("GET", self.ENDPOINT) return BanksResponse.from_response(response) diff --git a/khipu_tools/pykhipu/client.py b/khipu_tools/pykhipu/client.py index ca3a259..7dde509 100644 --- a/khipu_tools/pykhipu/client.py +++ b/khipu_tools/pykhipu/client.py @@ -1,26 +1,30 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals +import hmac +import logging import os import sys + import requests -import hmac -import logging + if sys.version_info.major < 3 or sys.version_info.minor < 9: from importlib_metadata import version else: from importlib.metadata import version + from hashlib import sha256 -from six.moves.urllib.parse import urlencode, quote -#from urllib.parse import urlencode, quote -from pykhipu.payments import Payments + from pykhipu.banks import Banks + +# from urllib.parse import urlencode, quote +from pykhipu.payments import Payments from pykhipu.receivers import Receivers +from urllib.parse import quote, urlencode + +API_BASE = "https://khipu.com/api/2.0" +TRUE_LIST = [True, "True", "true", "TRUE", 1, "1"] +FALSE_LIST = [False, "False", "false", "FALSE", 0, "0"] -API_BASE = 'https://khipu.com/api/2.0' -TRUE_LIST = [True, 'True', 'true', 'TRUE', 1, '1'] -FALSE_LIST = [False, 'False', 'false', 'FALSE', 0, '0'] -class Client(object): +class Client: def __init__(self, receiver_id=None, secret=None, debug=False): self.receiver_id = receiver_id self.secret = secret @@ -28,7 +32,7 @@ def __init__(self, receiver_id=None, secret=None, debug=False): @property def receiver_id(self): - return self._receiver_id or os.getenv('KHIPU_RECEIVER_ID') + return self._receiver_id or os.getenv("KHIPU_RECEIVER_ID") @receiver_id.setter def receiver_id(self, value): @@ -36,7 +40,7 @@ def receiver_id(self, value): @property def secret(self): - return self._secret or os.getenv('KHIPU_SECRET') + return self._secret or os.getenv("KHIPU_SECRET") @secret.setter def secret(self, value): @@ -44,55 +48,65 @@ def secret(self, value): @property def is_debug(self): - return self._debug or (os.getenv('KHIPU_DEBUG') in TRUE_LIST) + return self._debug or (os.getenv("KHIPU_DEBUG") in TRUE_LIST) @is_debug.setter def is_debug(self, value): - self._debug = ( - value in TRUE_LIST - and value not in FALSE_LIST - ) - + self._debug = value in TRUE_LIST and value not in FALSE_LIST + @property def payments(self): - if not hasattr(self, '_payments'): + if not hasattr(self, "_payments"): self._payments = Payments(self) return self._payments @property def banks(self): - if not hasattr(self, '_banks'): + if not hasattr(self, "_banks"): self._banks = Banks(self) return self._banks @property def receivers(self): - if not hasattr(self, '_receivers'): + if not hasattr(self, "_receivers"): self._receivers = Receivers(self) return self._receivers def __make_signature(self, method, url, params=None, data=None): method_name = method.upper() - to_sign = '&'.join([method_name, quote(url, safe='')]) + to_sign = "&".join([method_name, quote(url, safe="")]) def quote_items(tuples): - return ['='.join([quote(str(pair[0]), safe=''), - quote(str(pair[1]), safe='')]) for pair in tuples] + return [ + "=".join([quote(str(pair[0]), safe=""), quote(str(pair[1]), safe="")]) + for pair in tuples + ] if params: - sorted_items = sorted(params.items(), key = lambda item: item[0]) - to_sign = '&'.join([to_sign,] + quote_items(sorted_items)) + sorted_items = sorted(params.items(), key=lambda item: item[0]) + to_sign = "&".join( + [ + to_sign, + ] + + quote_items(sorted_items) + ) if data: - sorted_items = sorted(data.items(), key = lambda item: item[0]) - to_sign = '&'.join([to_sign,] + quote_items(sorted_items)) - - hasher = hmac.new(self.secret.encode(), to_sign.encode('UTF-8'), digestmod=sha256) - signature = "{id}:{hash}".format(id=self.receiver_id, - hash=hasher.hexdigest()) + sorted_items = sorted(data.items(), key=lambda item: item[0]) + to_sign = "&".join( + [ + to_sign, + ] + + quote_items(sorted_items) + ) + + hasher = hmac.new( + self.secret.encode(), to_sign.encode("UTF-8"), digestmod=sha256 + ) + signature = f"{self.receiver_id}:{hasher.hexdigest()}" if self.is_debug: - logging.debug('a firmar: %s', to_sign) - logging.debug('firma: %s', signature) + logging.debug("a firmar: %s", to_sign) + logging.debug("firma: %s", signature) return signature @@ -100,22 +114,22 @@ def make_request(self, method, endpoint, params=None, data=None): url = API_BASE + endpoint signature = self.__make_signature(method, url, params, data) payload = { - 'headers': { - 'Authorization': signature, - 'Content-Type': 'application/x-www-form-urlencoded', - 'User-Agent': 'pykhipu/{version}'.format(version=version('pykhipu')), - 'Accept': 'application/json' + "headers": { + "Authorization": signature, + "Content-Type": "application/x-www-form-urlencoded", + "User-Agent": "pykhipu/{version}".format(version=version("pykhipu")), + "Accept": "application/json", } } if params: - payload.update({ 'params': params }) + payload.update({"params": params}) if data: - payload.update({ 'data': data }) + payload.update({"data": data}) response = requests.request(method, url, **payload) if self.is_debug: - logging.debug('Response: %s', response.text) + logging.debug("Response: %s", response.text) return response diff --git a/khipu_tools/pykhipu/items.py b/khipu_tools/pykhipu/items.py index c205368..01b8e1c 100644 --- a/khipu_tools/pykhipu/items.py +++ b/khipu_tools/pykhipu/items.py @@ -1,5 +1,4 @@ -# -*- coding: utf-8 -*- -class BankItem(object): +class BankItem: def __init__(self, bank_id, name, message, min_amount, bank_type, parent): self._bank_id = bank_id self._name = name @@ -14,8 +13,14 @@ def from_data(cls, data): Constructor que permite obtener una instancia de BankItem a partir de un diccionario de datos. """ - return cls(data.get('bank_id'), data.get('name'), data.get('message'), - data.get('min_amount'), data.get('type'), data.get('parent')) + return cls( + data.get("bank_id"), + data.get("name"), + data.get("message"), + data.get("min_amount"), + data.get("type"), + data.get("parent"), + ) @property def bank_id(self): @@ -61,14 +66,14 @@ def parent(self): return self._parent -class ErrorItem(object): +class ErrorItem: def __init__(self, field, message): self._field = field self._message = message @classmethod def from_data(cls, data): - return cls(data.get('field'), data.get('message')) + return cls(data.get("field"), data.get("message")) @property def field(self): diff --git a/khipu_tools/pykhipu/payments.py b/khipu_tools/pykhipu/payments.py index 9a60c59..398523d 100644 --- a/khipu_tools/pykhipu/payments.py +++ b/khipu_tools/pykhipu/payments.py @@ -1,14 +1,10 @@ -# -*- coding: utf-8 -*- from datetime import datetime -from pykhipu.responses import ( - PaymentsResponse, - PaymentsCreateResponse, - SuccessResponse, -) +from pykhipu.responses import PaymentsCreateResponse, PaymentsResponse, SuccessResponse -class Payments(object): - ENDPOINT = '/payments' + +class Payments: + ENDPOINT = "/payments" def __init__(self, client): self.client = client @@ -19,8 +15,9 @@ def get(self, notification_token): actual del pago. Se obtiene del notification_token que envia khipu cuando el pago es conciliado. """ - response = self.client.make_request('GET', self.ENDPOINT, - params={ 'notification_token': notification_token }) + response = self.client.make_request( + "GET", self.ENDPOINT, params={"notification_token": notification_token} + ) return PaymentsResponse.from_response(response) def post(self, subject, currency, amount, **kwargs): @@ -28,14 +25,12 @@ def post(self, subject, currency, amount, **kwargs): Crea un pago en khipu y obtiene las URLs para redirección al usuario para que complete el pago. """ - data = {'subject': subject, - 'currency': currency, - 'amount': amount } + data = {"subject": subject, "currency": currency, "amount": amount} data.update(kwargs) - if hasattr(data, 'expires_date'): - if isinstance(data['expires_date'], datetime): - data['expires_date'] = data['expires_date'].isoformat() - response = self.client.make_request('POST', self.ENDPOINT, data=data) + if hasattr(data, "expires_date"): + if isinstance(data["expires_date"], datetime): + data["expires_date"] = data["expires_date"].isoformat() + response = self.client.make_request("POST", self.ENDPOINT, data=data) return PaymentsCreateResponse.from_response(response) def get_id(self, id): @@ -43,8 +38,8 @@ def get_id(self, id): Información completa del pago. Datos con los que fue creado y el estado actual del pago. """ - endpoint = "{0}/{1}/".format(self.ENDPOINT, id) - response = self.client.make_request('GET', endpoint) + endpoint = f"{self.ENDPOINT}/{id}/" + response = self.client.make_request("GET", endpoint) return PaymentsResponse.from_response(response) def delete(self, id): @@ -52,8 +47,8 @@ def delete(self, id): Solo se pueden borrar pagos que estén pendientes de pagar. Esta operación no puede deshacerse. """ - endpoint = "{0}/{1}/".format(self.ENDPOINT, id) - response = self.client.make_request('DELETE', endpoint) + endpoint = f"{self.ENDPOINT}/{id}/" + response = self.client.make_request("DELETE", endpoint) return SuccessResponse.from_response(response) def post_refunds(self, id, amount=None): @@ -64,7 +59,7 @@ def post_refunds(self, id, amount=None): """ data = None if amount: - data = { 'amount': amount } - endpoint = "{0}/{1}/refunds".format(self.ENDPOINT, id) - response = self.client.make_request('POST', endpoint, data=data) + data = {"amount": amount} + endpoint = f"{self.ENDPOINT}/{id}/refunds" + response = self.client.make_request("POST", endpoint, data=data) return SuccessResponse.from_response(response) diff --git a/khipu_tools/pykhipu/receivers.py b/khipu_tools/pykhipu/receivers.py index ea0e85a..99b2e08 100644 --- a/khipu_tools/pykhipu/receivers.py +++ b/khipu_tools/pykhipu/receivers.py @@ -1,6 +1,5 @@ -# -*- coding: utf-8 -*- -class Receivers(object): - ENDPOINT = '/receivers' +class Receivers: + ENDPOINT = "/receivers" def __init__(self, client): self.client = client @@ -11,5 +10,5 @@ def post(self, data): de la cuenta de usuario asociada, datos de facturación y datos de contacto. """ - response = self.client.make_request('POST', self.ENDPOINT, data=data) + response = self.client.make_request("POST", self.ENDPOINT, data=data) return ReceiversCreateResponse.from_response(response) diff --git a/khipu_tools/pykhipu/responses.py b/khipu_tools/pykhipu/responses.py index 82333a3..3862ef9 100644 --- a/khipu_tools/pykhipu/responses.py +++ b/khipu_tools/pykhipu/responses.py @@ -1,11 +1,10 @@ -# -*- coding: utf-8 -*- -import requests import dateutil.parser +import requests +from pykhipu.errors import AuthorizationError, ServiceError, ValidationError from pykhipu.items import BankItem -from pykhipu.errors import ValidationError, AuthorizationError, ServiceError -class BaseResponse(object): +class BaseResponse: @classmethod def from_response(cls, response): data = response.json() @@ -23,14 +22,49 @@ def from_response(cls, response): class PaymentsResponse(BaseResponse): - def __init__(self, payment_id, payment_url, simplified_transfer_url, - transfer_url, webpay_url, hites_url, payme_url, app_url, ready_for_terminal, - notification_token, receiver_id, conciliation_date, subject, amount, currency, - status, status_detail, body, picture_url, receipt_url, return_url, cancel_url, - notify_url, notify_api_version, expires_date, attachment_urls, bank, - bank_id, payer_name, payer_email, personal_identifier, - bank_account_number, out_of_date_conciliation, transaction_id, custom, - responsible_user_email, send_reminders, send_email, payment_method, funds_source): + def __init__( + self, + payment_id, + payment_url, + simplified_transfer_url, + transfer_url, + webpay_url, + hites_url, + payme_url, + app_url, + ready_for_terminal, + notification_token, + receiver_id, + conciliation_date, + subject, + amount, + currency, + status, + status_detail, + body, + picture_url, + receipt_url, + return_url, + cancel_url, + notify_url, + notify_api_version, + expires_date, + attachment_urls, + bank, + bank_id, + payer_name, + payer_email, + personal_identifier, + bank_account_number, + out_of_date_conciliation, + transaction_id, + custom, + responsible_user_email, + send_reminders, + send_email, + payment_method, + funds_source, + ): self._payment_id = payment_id self._payment_url = payment_url self._simplified_transfer_url = simplified_transfer_url @@ -74,30 +108,53 @@ def __init__(self, payment_id, payment_url, simplified_transfer_url, @classmethod def from_data(cls, data): - conciliation_date = data.get('conciliation_date') + conciliation_date = data.get("conciliation_date") if conciliation_date: conciliation_date = dateutil.parser.parse(conciliation_date) - expires_date = dateutil.parser.parse(data.get('expires_date')) - - return cls(data.get('payment_id'), data.get('payment_url'), - data.get('simplified_transfer_url'), data.get('transfer_url'), - data.get('webpay_url'), data.get('hites_url'), data.get('payme_url'), - data.get('app_url'), data.get('ready_for_terminal'), - data.get('notification_token'), data.get('receiver_id'), - conciliation_date, data.get('subject'), - data.get('amount'), data.get('currency'), data.get('status'), - data.get('status_detail'), data.get('body'), - data.get('picture_url'), data.get('receipt_url'), - data.get('return_url'), data.get('cancel_url'), - data.get('notify_url'), data.get('notify_api_version'), - expires_date, data.get('attachment_urls'), - data.get('bank'), data.get('bank_id'), data.get('payer_name'), - data.get('payer_email'), data.get('personal_identifier'), - data.get('bank_account_number'), - data.get('out_of_date_conciliation'), data.get('transaction_id'), - data.get('custom'), data.get('responsible_user_email'), - data.get('send_reminders'), data.get('send_email'), - data.get('payment_method'), data.get('funds_source')) + expires_date = dateutil.parser.parse(data.get("expires_date")) + + return cls( + data.get("payment_id"), + data.get("payment_url"), + data.get("simplified_transfer_url"), + data.get("transfer_url"), + data.get("webpay_url"), + data.get("hites_url"), + data.get("payme_url"), + data.get("app_url"), + data.get("ready_for_terminal"), + data.get("notification_token"), + data.get("receiver_id"), + conciliation_date, + data.get("subject"), + data.get("amount"), + data.get("currency"), + data.get("status"), + data.get("status_detail"), + data.get("body"), + data.get("picture_url"), + data.get("receipt_url"), + data.get("return_url"), + data.get("cancel_url"), + data.get("notify_url"), + data.get("notify_api_version"), + expires_date, + data.get("attachment_urls"), + data.get("bank"), + data.get("bank_id"), + data.get("payer_name"), + data.get("payer_email"), + data.get("personal_identifier"), + data.get("bank_account_number"), + data.get("out_of_date_conciliation"), + data.get("transaction_id"), + data.get("custom"), + data.get("responsible_user_email"), + data.get("send_reminders"), + data.get("send_email"), + data.get("payment_method"), + data.get("funds_source"), + ) @property def payment_id(self): @@ -197,8 +254,7 @@ def subject(self): @property def amount(self): - """ - """ + """ """ return self._amount @property @@ -403,8 +459,18 @@ def funds_source(self): class PaymentsCreateResponse(BaseResponse): - def __init__(self, payment_id, payment_url, simplified_transfer_url, - transfer_url, webpay_url, hites_url, payme_url, app_url, ready_for_terminal): + def __init__( + self, + payment_id, + payment_url, + simplified_transfer_url, + transfer_url, + webpay_url, + hites_url, + payme_url, + app_url, + ready_for_terminal, + ): self._payment_id = payment_id self._payment_url = payment_url self._simplified_transfer_url = simplified_transfer_url @@ -417,10 +483,17 @@ def __init__(self, payment_id, payment_url, simplified_transfer_url, @classmethod def from_data(cls, data): - return cls(data.get('payment_id'), data.get('payment_url'), - data.get('simplified_transfer_url'), data.get('transfer_url'), - data.get('webpay_url'), data.get('hites_url'), data.get('payme_url'), - data.get('app_url'), data.get('ready_for_terminal')) + return cls( + data.get("payment_id"), + data.get("payment_url"), + data.get("simplified_transfer_url"), + data.get("transfer_url"), + data.get("webpay_url"), + data.get("hites_url"), + data.get("payme_url"), + data.get("app_url"), + data.get("ready_for_terminal"), + ) @property def payment_id(self): @@ -497,7 +570,7 @@ def __init__(self, receiver_id, secret): @classmethod def from_data(cls, data): - return cls(data.get('receiver_id'), data.get('secret')) + return cls(data.get("receiver_id"), data.get("secret")) @property def receiver_id(self): @@ -514,13 +587,14 @@ def secret(self): """ return self._secret + class BanksResponse(BaseResponse): def __init__(self, banks): self._banks = banks @classmethod def from_data(cls, data): - banks = [BankItem.from_data(i) for i in data.get('banks')] + banks = [BankItem.from_data(i) for i in data.get("banks")] return cls(banks) @property @@ -537,7 +611,7 @@ def __init__(self, message): @classmethod def from_data(cls, data): - return cls(data.get('message')) + return cls(data.get("message")) @property def message(self):