From c2d3f100a79fe40d857e9640e1875aeb0c4312fa Mon Sep 17 00:00:00 2001 From: Mario Hernandez Date: Fri, 18 Sep 2020 22:22:49 -0300 Subject: [PATCH 1/8] Stash --- flow_client.py | 24 ++- pyflowcl/Customer.py | 370 +++++++++++++++++++++++++++++++++++++++++ pyflowcl/models.py | 387 ++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 777 insertions(+), 4 deletions(-) create mode 100644 pyflowcl/Customer.py diff --git a/flow_client.py b/flow_client.py index 747b708..5adb902 100755 --- a/flow_client.py +++ b/flow_client.py @@ -1,16 +1,33 @@ from typing import Any, Dict -from pyflowcl import Payment +from pyflowcl import Payment, Customer from pyflowcl.Clients import ApiClient import logging logging.basicConfig(level=logging.DEBUG) API_URL = "https://sandbox.flow.cl/api" -API_KEY = "your_key" -API_SECRET = "your_secret" +API_KEY = "5C627F95-4523-4AEB-9FBC-7883B1FL43E5" +API_SECRET = "43559f1ae777c3f4ff86fb752917356ebf6f2644" api = ApiClient(API_URL, API_KEY, API_SECRET) +cust_data: Dict[str, Any] = { + "start": 0 +} + +llamada = Customer.register(api, 4, "https://mariofix.com") +print(llamada) +""" +llamada = Customer.edit(api, cust_data) +print(llamada) + +llamada = Customer.get(api, "cus_asg7nznrfp") +print(llamada) + +llamada = Customer.get(api, "cus_kpiq2nvif0") +print(llamada) +""" +""" pago: Dict[str, Any] = { "subject": "Asunto Email", "commerceOrder": "1234", @@ -43,3 +60,4 @@ llamada = Payment.getPayments(api, data) print(llamada) del llamada +""" \ No newline at end of file diff --git a/pyflowcl/Customer.py b/pyflowcl/Customer.py new file mode 100644 index 0000000..20ca0e0 --- /dev/null +++ b/pyflowcl/Customer.py @@ -0,0 +1,370 @@ +from dataclasses import asdict +from typing import Any, Dict, Union, cast + +from .models import ( + Error, + Customer, + CustomerRequest, + CustomerList, + CustomerRegisterResponse, + CustomerRegisterStatusResponse, + PaymentStatus, + CustomerChargeRequest, + CollectRequest, + CollectResponse, + BatchCollectRequest, + BatchCollectResponse, +) +from .Clients import ApiClient +import logging + + +def create( + apiclient: ApiClient, customer_data: Dict[str, Any] +) -> Union[ + Customer, Error, +]: + """ + Permite crear clientes para efectuarles cargos recurrentes o suscribirlos + a un planes de suscripción. Una vez creado un cliente, Flow lo identificará + por un hash denominado customerId, ejemplo: + + customerId: cus_onoolldvec + """ + url = f"{apiclient.api_url}/customer/create" + + customer = CustomerRequest.from_dict(customer_data) + if customer.apiKey is None: + customer.apiKey = apiclient.api_key + customer.s = apiclient.make_signature(asdict(customer)) + logging.debug("Before Request:" + str(customer)) + + response = apiclient.post(url, asdict(customer)) + + if response.status_code == 200: + return Customer.from_dict(cast(Dict[str, Any], response.json())) + if response.status_code == 400: + return Error.from_dict(cast(Dict[str, Any], response.json())) + if response.status_code == 401: + return Error.from_dict(cast(Dict[str, Any], response.json())) + else: + raise Exception(response=response) + + +def edit( + apiclient: ApiClient, customer_data: Dict[str, Any] +) -> Union[ + Customer, Error, +]: + """ + Este servicio permite editar los datos de un cliente + """ + url = f"{apiclient.api_url}/customer/edit" + + customer = CustomerRequest.from_dict(customer_data) + if customer.apiKey is None: + customer.apiKey = apiclient.api_key + customer.s = apiclient.make_signature(asdict(customer)) + logging.debug("Before Request:" + str(customer)) + + response = apiclient.post(url, asdict(customer)) + + if response.status_code == 200: + return Customer.from_dict(cast(Dict[str, Any], response.json())) + if response.status_code == 400: + return Error.from_dict(cast(Dict[str, Any], response.json())) + if response.status_code == 401: + return Error.from_dict(cast(Dict[str, Any], response.json())) + else: + raise Exception(response=response) + + +def delete( + apiclient: ApiClient, customer_data: Dict[str, Any] +) -> Union[ + Customer, Error, +]: + """ + Este servicio permite editar los datos de un cliente + """ + url = f"{apiclient.api_url}/customer/delete" + + customer = CustomerRequest.from_dict(customer_data) + if customer.apiKey is None: + customer.apiKey = apiclient.api_key + customer.s = apiclient.make_signature(asdict(customer)) + logging.debug("Before Request:" + str(customer)) + + response = apiclient.post(url, asdict(customer)) + + if response.status_code == 200: + return Customer.from_dict(cast(Dict[str, Any], response.json())) + if response.status_code == 400: + return Error.from_dict(cast(Dict[str, Any], response.json())) + if response.status_code == 401: + return Error.from_dict(cast(Dict[str, Any], response.json())) + else: + raise Exception(response=response) + + +def get( + apiclient: ApiClient, cust_id: str, +) -> Union[ + Customer, Error, +]: + """ + Permite obtener los datos de un cliente en base a su customerId. + """ + url = f"{apiclient.api_url}/customer/get" + + params: Dict[str, Any] = {"apiKey": apiclient.api_key, "customerId": cust_id} + signature = apiclient.make_signature(params) + params["s"] = signature + logging.debug("Before Request:" + str(params)) + response = apiclient.get(url, params) + + if response.status_code == 200: + return Customer.from_dict(cast(Dict[str, Any], response.json())) + if response.status_code == 400: + return Error.from_dict(cast(Dict[str, Any], response.json())) + if response.status_code == 401: + return Error.from_dict(cast(Dict[str, Any], response.json())) + else: + raise Exception(response=response) + + +def list( + apiclient: ApiClient, filter_params: Dict[str, Any], +) -> Union[ + CustomerList, Error, +]: + """ + Permite obtener los datos de un cliente en base a su customerId. + """ + + url = f"{apiclient.api_url}/customer/list" + + params: Dict[str, Any] = {"apiKey": apiclient.api_key} + # params.update(filter_params) retorna None, Plan B: + req_params = {**params, **filter_params} + + signature = apiclient.make_signature(req_params) + req_params["s"] = signature + logging.debug("Before Request:" + str(req_params)) + response = apiclient.get(url, req_params) + + if response.status_code == 200: + return CustomerList.from_dict(cast(Dict[str, Any], response.json())) + if response.status_code == 400: + return Error.from_dict(cast(Dict[str, Any], response.json())) + if response.status_code == 401: + return Error.from_dict(cast(Dict[str, Any], response.json())) + else: + raise Exception(response=response) + + +def register( + apiclient: ApiClient, customerId: str, url_return: str +) -> Union[ + CustomerRegisterResponse, Error, +]: + """ + Envía a un cliente a registrar su tarjeta de crédito para poder + efectuarle cargos automáticos. El servicio responde con la URL para + redirigir el browser del pagador y el token que identifica la + transacción. La url de redirección se debe formar concatenando los + valores recibidos en la respuesta de la siguiente forma: + + url + "?token=" +token + + Una vez redirigido el browser del cliente, Flow responderá por medio de + una llamada POST a la url callback del comercio indicada en el parámetro + url_return pasando como parámetro token. El comercio debe implementar + una página y capturar el parámetro token enviado por Flow para luego + consumir el servicio "customer/getRegisterStatus" para obtener el + resultado del registro. + """ + + url = f"{apiclient.api_url}/customer/register" + + params: Dict[str, Any] = { + "apiKey": apiclient.api_key, + "customerId": customerId, + "url_return": url_return, + } + + signature = apiclient.make_signature(params) + params["s"] = signature + logging.debug("Before Request:" + str(params)) + response = apiclient.post(url, params) + + if response.status_code == 200: + return CustomerRegisterResponse.from_dict(cast(Dict[str, Any], response.json())) + if response.status_code == 400: + return Error.from_dict(cast(Dict[str, Any], response.json())) + if response.status_code == 401: + return Error.from_dict(cast(Dict[str, Any], response.json())) + else: + raise Exception(response=response) + + +def getRegisterStatus( + apiclient: ApiClient, token: str +) -> Union[ + CustomerRegisterStatusResponse, Error, +]: + """ + Elte servicio retorna el resultado del registro de la tarjeta de + crédito de un cliente. + """ + + url = f"{apiclient.api_url}/customer/getRegisterStatus" + + params: Dict[str, Any] = { + "apiKey": apiclient.api_key, + "token": token, + } + + signature = apiclient.make_signature(params) + params["s"] = signature + logging.debug("Before Request:" + str(params)) + response = apiclient.get(url, params) + + if response.status_code == 200: + return CustomerRegisterStatusResponse.from_dict( + cast(Dict[str, Any], response.json()) + ) + if response.status_code == 400: + return Error.from_dict(cast(Dict[str, Any], response.json())) + if response.status_code == 401: + return Error.from_dict(cast(Dict[str, Any], response.json())) + else: + raise Exception(response=response) + + +def unRegister( + apiclient: ApiClient, customerId: str +) -> Union[ + Customer, Error, +]: + """ + Este servicio permite eliminar el registro de la tarjeta de crédito + de un cliente. Al eliminar el registro no se podrá hacer cargos + automáticos y Flow enviará un cobro por email. + """ + + url = f"{apiclient.api_url}/customer/unRegister" + + params: Dict[str, Any] = { + "apiKey": apiclient.api_key, + "customerId": customerId, + } + + signature = apiclient.make_signature(params) + params["s"] = signature + logging.debug("Before Request:" + str(params)) + response = apiclient.post(url, params) + + if response.status_code == 200: + return Customer.from_dict(cast(Dict[str, Any], response.json())) + if response.status_code == 400: + return Error.from_dict(cast(Dict[str, Any], response.json())) + if response.status_code == 401: + return Error.from_dict(cast(Dict[str, Any], response.json())) + else: + raise Exception(response=response) + + +def charge( + apiclient: ApiClient, charge_data: Dict[str, Any] +) -> Union[ + PaymentStatus, Error, +]: + """ + Este servicio permite efectuar un cargo automático en la tarjeta de + crédito previamente registrada por el cliente. Si el cliente no + tiene registrada una tarjeta el metodo retornará error. + """ + + url = f"{apiclient.api_url}/customer/charge" + + charge = CustomerChargeRequest.from_dict(charge_data) + if charge.apiKey is None: + charge.apiKey = apiclient.api_key + charge.s = apiclient.make_signature(asdict(charge)) + logging.debug("Before Request:" + str(charge)) + + response = apiclient.post(url, asdict(charge)) + + if response.status_code == 200: + return PaymentStatus.from_dict(cast(Dict[str, Any], response.json())) + if response.status_code == 400: + return Error.from_dict(cast(Dict[str, Any], response.json())) + if response.status_code == 401: + return Error.from_dict(cast(Dict[str, Any], response.json())) + else: + raise Exception(response=response) + + +def collect( + apiclient: ApiClient, collect_data: Dict[str, Any] +) -> Union[ + CollectResponse, Error, +]: + """ + Este servicio envía un cobro a un cliente. Si el cliente tiene + registrada una tarjeta de crédito se le hace un cargo automático, si + no tiene registrada una tarjeta de credito se genera un cobro. Si se + envía el parámetro byEmail = 1, se genera un cobro por email. + """ + + url = f"{apiclient.api_url}/customer/collect" + + collect = CollectRequest.from_dict(collect_data) + if collect.apiKey is None: + collect.apiKey = apiclient.api_key + collect.s = apiclient.make_signature(asdict(collect)) + logging.debug("Before Request:" + str(collect)) + + response = apiclient.post(url, asdict(collect)) + + if response.status_code == 200: + return CollectResponse.from_dict(cast(Dict[str, Any], response.json())) + if response.status_code == 400: + return Error.from_dict(cast(Dict[str, Any], response.json())) + if response.status_code == 401: + return Error.from_dict(cast(Dict[str, Any], response.json())) + else: + raise Exception(response=response) + + +def batchCollect( + apiclient: ApiClient, collect_data: Dict[str, Any] +) -> Union[ + BatchCollectResponse, Error, +]: + """ + Este servicio envía de forma masiva un lote de cobros a clientes. + Similar al servicio collect pero masivo y asíncrono. Este servicio + responde con un token identificador del lote y el número de filas + recibidas. + """ + + url = f"{apiclient.api_url}/customer/batchCollect" + + batch_collect = BatchCollectRequest.from_dict(collect_data) + if batch_collect.apiKey is None: + batch_collect.apiKey = apiclient.api_key + batch_collect.s = apiclient.make_signature(asdict(batch_collect)) + logging.debug("Before Request:" + str(batch_collect)) + + response = apiclient.post(url, asdict(batch_collect)) + + if response.status_code == 200: + return BatchCollectResponse.from_dict(cast(Dict[str, Any], response.json())) + if response.status_code == 400: + return Error.from_dict(cast(Dict[str, Any], response.json())) + if response.status_code == 401: + return Error.from_dict(cast(Dict[str, Any], response.json())) + else: + raise Exception(response=response) diff --git a/pyflowcl/models.py b/pyflowcl/models.py index 56b5ee8..62b32de 100644 --- a/pyflowcl/models.py +++ b/pyflowcl/models.py @@ -1,6 +1,7 @@ from __future__ import annotations + from dataclasses import dataclass -from typing import Any, Dict, Optional, List +from typing import Any, Dict, Optional, List, cast @dataclass @@ -272,3 +273,387 @@ def from_dict(d: Dict[str, Any]) -> RefundStatus: # noqa: F821 amount=amount, fee=fee, ) + + +@dataclass +class Customer: + """ Customer Object """ + + created: str = "" + creditCardType: Optional[str] = None + customerId: str = "" + email: str = "" + externalId: Optional[str] = None + last4CardDigits: Optional[str] = None + name: str = "" + pay_mode: Optional[str] = None + registerDate: Optional[str] = None + status: int = 0 + + @staticmethod + def from_dict(d: Dict[str, Any]) -> Customer: # noqa: F821 + created = d.get("created") + creditCardType = d.get("creditCardType") + customerId = d.get("customerId") + email = d.get("email") + externalId = d.get("externalId") + last4CardDigits = d.get("last4CardDigits") + name = d.get("name") + pay_mode = d.get("pay_mode") + registerDate = d.get("registerDate") + status = d.get("status") + + return Customer( + created=created, + creditCardType=creditCardType, + customerId=customerId, + email=email, + externalId=externalId, + last4CardDigits=last4CardDigits, + name=name, + pay_mode=pay_mode, + registerDate=registerDate, + status=status, + ) + + +@dataclass +class CustomerRequest: + """ CustomerRequest Object """ + + apiKey: str = "" + customerId: str = "" + email: str = "" + externalId: str = "" + name: str = "" + s: str = "" + + @staticmethod + def from_dict(d: Dict[str, Any]) -> CustomerRequest: # noqa: F821 + apiKey = d.get("apiKey") + customerId = d.get("customerId") + email = d.get("email") + externalId = d.get("externalId") + name = d.get("name") + s = d.get("s") + + return CustomerRequest( + apiKey=apiKey, + customerId=customerId, + email=email, + externalId=externalId, + name=name, + s=s, + ) + + +@dataclass +class CustomerList: + """ Lista de Clientes """ + + total: Optional[float] = None + hasMore: Optional[bool] = None + data: Optional[List[Dict[Any, Any]]] = None + + @staticmethod + def from_dict(d: Dict[str, Any]) -> CustomerList: # noqa: F821 + total = d.get("total") + hasMore = d.get("hasMore") + data = d.get("data") + + return CustomerList(total=total, hasMore=hasMore, data=data,) + + +@dataclass +class CustomerRegisterResponse: + """ Objeto respuesta """ + + url: Optional[str] = None + token: Optional[str] = None + + @staticmethod + def from_dict(d: Dict[str, Any]) -> CustomerRegisterResponse: # noqa: F821 + url = d.get("url") + token = d.get("token") + + return CustomerRegisterResponse(url=url, token=token,) + + +@dataclass +class CustomerRegisterStatusResponse: + """ Objeto respuesta """ + + creditCardType: str = "" + customerId: str = "" + last4CardDigits: str = "" + status: int = 0 + + @staticmethod + def from_dict(d: Dict[str, Any]) -> CustomerRegisterStatusResponse: # noqa: F821 + creditCardType = d.get("creditCardType") + customerId = d.get("customerId") + last4CardDigits = d.get("last4CardDigits") + status = d.get("status") + + return CustomerRegisterStatusResponse( + creditCardType=creditCardType, + customerId=customerId, + last4CardDigits=last4CardDigits, + status=status, + ) + + +@dataclass +class CustomerChargeRequest: + """ Objeto para generar una URL de pago """ + + amount: float = 0 + apiKey: str = "API_KEY" + commerceOrder: str = "" + currency: Optional[str] = None + optionals: Optional[str] = None + subject: str = "" + s: str = "" + + @staticmethod + def from_dict(d: Dict[str, Any]) -> CustomerChargeRequest: # noqa: F821 + amount = d.get("amount") + apiKey = d.get("apiKey") + commerceOrder = d.get("commerceOrder") + currency = d.get("currency") + optionals = d.get("optionals") + subject = d.get("subject") + s = d.get("s") + + return CustomerChargeRequest( + amount=amount, + apiKey=apiKey, + commerceOrder=commerceOrder, + currency=currency, + optionals=optionals, + subject=subject, + s=s, + ) + + +@dataclass +class CollectResponse: + """ Objeto para CollectResponse """ + + commerce_order: Optional[str] = None + flow_order: Optional[float] = None + paymen_result: Optional[PaymentStatus] = None + status: Optional[int] = None + token: Optional[str] = None + type: Optional[float] = None + url: Optional[str] = None + + @staticmethod + def from_dict(d: Dict[str, Any]) -> CollectResponse: # noqa: F821 + type = d.get("type") + commerce_order = d.get("commerceOrder") + flow_order = d.get("flowOrder") + url = d.get("url") + token = d.get("token") + status = d.get("status") + paymen_result = None + if d.get("paymenResult") is not None: + paymen_result = PaymentStatus.from_dict( + cast(Dict[str, Any], d.get("paymenResult")) + ) + + return CollectResponse( + type=type, + commerce_order=commerce_order, + flow_order=flow_order, + url=url, + token=token, + status=status, + paymen_result=paymen_result, + ) + + +@dataclass +class CollectRequest: + """ Objeto para generar un correo electronico de pago """ + + amount: float = 0 + apiKey: str = "API_KEY" + byEmail: Optional[int] = None + commerceOrder: str = "" + currency: Optional[str] = None + customerId: str = "" + forward_days_after: Optional[int] = None + forward_times: Optional[int] = None + ignore_auto_charging: Optional[int] = None + merchantId: Optional[str] = None + optionals: Optional[str] = None + paymentMethod: Optional[int] = 9 + subject: Optional[str] = None + timeout: Optional[int] = None + urlConfirmation: str = "" + urlReturn: str = "" + s: str = "" + + @staticmethod + def from_dict(d: Dict[str, Any]) -> CollectRequest: # noqa: F821 + amount = d.get("amount") + apiKey = d.get("apiKey") + byEmail = d.get("byEmail") + commerceOrder = d.get("commerceOrder") + currency = d.get("currency") + forward_days_after = d.get("forward_days_after") + forward_times = d.get("forward_times") + ignore_auto_charging = d.get("ignore_auto_charging") + merchantId = d.get("merchantId") + optionals = d.get("optionals") + subject = d.get("subject") + timeout = d.get("timeout") + urlConfirmation = d.get("urlConfirmation") + urlReturn = d.get("urlReturn") + s = d.get("s") + + return CollectRequest( + amount=amount, + apiKey=apiKey, + byEmail=byEmail, + commerceOrder=commerceOrder, + currency=currency, + ignore_auto_charging=ignore_auto_charging, + forward_days_after=forward_days_after, + forward_times=forward_times, + merchantId=merchantId, + optionals=optionals, + subject=subject, + timeout=timeout, + urlConfirmation=urlConfirmation, + urlReturn=urlReturn, + s=s, + ) + + +@dataclass +class CollectObject: + """ Objeto de cobro para un lote de cobros """ + + customer_id: str + commerce_order: str + subject: str + amount: float + currency: Optional[str] = None + payment_method: Optional[float] = None + optional: Optional[str] = None + + @staticmethod + def from_dict(d: Dict[str, Any]) -> CollectObject: # noqa: F821 + customer_id = d.get("customerId") + commerce_order = d.get("commerceOrder") + subject = d.get("subject") + amount = d.get("amount") + currency = d.get("currency") + payment_method = d.get("paymentMethod") + optional = d.get("optional") + + return CollectObject( + customer_id=customer_id, + commerce_order=commerce_order, + subject=subject, + amount=amount, + currency=currency, + payment_method=payment_method, + optional=optional, + ) + + +@dataclass +class BatchCollectRequest: + apiKey: str = "API_KEY" + batchRows: str = "" + byEmail: int = 0 + forward_days_after: Optional[int] = None + forward_times: Optional[int] = None + timeout: Optional[int] = None + urlCallBack: str = "" + urlConfirmation: str = "" + urlReturn: str = "" + s: str = "" + + @staticmethod + def from_dict(d: Dict[str, Any]) -> BatchCollectRequest: # noqa: F821 + apiKey = d.get("apiKey") + batchRows = d.get("batchRows") + byEmail = d.get("byEmail") + forward_days_after = d.get("forward_days_after") + forward_times = d.get("forward_times") + timeout = d.get("timeout") + urlCallBack = d.get("urlCallBack") + urlConfirmation = d.get("urlConfirmation") + urlReturn = d.get("urlReturn") + s = d.get("s") + + return BatchCollectRequest( + apiKey=apiKey, + batchRows=batchRows, + byEmail=byEmail, + forward_days_after=forward_days_after, + forward_times=forward_times, + timeout=timeout, + urlCallBack=urlCallBack, + urlConfirmation=urlConfirmation, + urlReturn=urlReturn, + s=s, + ) + + +@dataclass +class BatchCollectRejectedRow: + customerId: Optional[str] = None + commerceOrder: Optional[str] = None + rowNumber: Optional[int] = None + parameter: Optional[str] = None + errorCode: Optional[int] = None + errorMsg: Optional[str] = None + + @staticmethod + def from_dict(d: Dict[str, Any]) -> BatchCollectRejectedRow: # noqa: F821 + customerId = d.get("customerId") + commerceOrder = d.get("commerceOrder") + rowNumber = d.get("rowNumber") + parameter = d.get("parameter") + errorCode = d.get("errorCode") + errorMsg = d.get("errorMsg") + + return BatchCollectRejectedRow( + customerId=customerId, + commerceOrder=commerceOrder, + rowNumber=rowNumber, + parameter=parameter, + errorCode=errorCode, + errorMsg=errorMsg, + ) + + +@dataclass +class BatchCollectResponse: + token: Optional[str] = None + receivedRows: Optional[int] = None + acceptedRows: Optional[int] = None + rejectedRows: Optional[List[BatchCollectRejectedRow]] = None + + @staticmethod + def from_dict(d: Dict[str, Any]) -> BatchCollectResponse: # noqa: F821 + token = d.get("token") + receivedRows = d.get("receivedRows") + acceptedRows = d.get("acceptedRows") + rejectedRows = [] + for rejected_row in d.get("rejectedRows") or []: + rejected_row_item = BatchCollectRejectedRow.from_dict(rejected_row) + + rejectedRows.append(rejected_row_item) + + return BatchCollectResponse( + token=token, + receivedRows=receivedRows, + acceptedRows=acceptedRows, + rejectedRows=rejectedRows, + ) From 2070cc98ec0b06e77af0787de3541b69c351ffa1 Mon Sep 17 00:00:00 2001 From: Mario Hernandez Date: Tue, 8 Jun 2021 22:47:58 -0400 Subject: [PATCH 2/8] Version Bump --- pyproject.toml | 13 +++++++------ requirements.txt | 21 ++++++--------------- tox.ini | 2 +- 3 files changed, 14 insertions(+), 22 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 63d9223..de479f4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,20 +12,21 @@ classifiers=[ "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", "Intended Audience :: Developers", "Topic :: Software Development :: Libraries :: Python Modules", ] [tool.poetry.dependencies] python = "^3.6" -requests = "^2.24.0" -dataclasses = { version = "*", python = "3.6" } +requests = "^2.25.1" +dataclasses = { version = "*", python = "~3.6" } [tool.poetry.dev-dependencies] -pytest = "^6.0" -pytest-pep8 = "^1.0.6" -black = "^19.10b0" -tox = "^3.20.0" +pytest = "*" +pytest-pep8 = "*" +black = "*" +tox = "*" [build-system] requires = ["poetry>=0.12"] diff --git a/requirements.txt b/requirements.txt index 48292e0..2688672 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,15 +1,6 @@ -certifi==2020.6.20 \ - --hash=sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41 \ - --hash=sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3 -chardet==3.0.4 \ - --hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691 \ - --hash=sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae -idna==2.10 \ - --hash=sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0 \ - --hash=sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6 -requests==2.24.0 \ - --hash=sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898 \ - --hash=sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b -urllib3==1.25.10 \ - --hash=sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461 \ - --hash=sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a +certifi==2021.5.30 +chardet==4.0.0 +dataclasses==0.8 +idna==2.10 +requests==2.25.1 +urllib3==1.26.5 diff --git a/tox.ini b/tox.ini index fd19ca2..1faa9e4 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py36,py37,py38 +envlist = py36,py37,py38,py39 isolated_build = True [testenv] From 41275839f73d9027b23af95ffa813c77acd81cb2 Mon Sep 17 00:00:00 2001 From: Mario Hernandez Date: Fri, 25 Jun 2021 20:26:38 -0400 Subject: [PATCH 3/8] Version Bump --- .github/workflows/pyflow.yml | 4 ++-- requirements.txt | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/pyflow.yml b/.github/workflows/pyflow.yml index 26676b7..43f95bd 100644 --- a/.github/workflows/pyflow.yml +++ b/.github/workflows/pyflow.yml @@ -11,8 +11,8 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.6, 3.7, 3.8] - poetry-version: [1.0.10] + python-version: [3.6, 3.7, 3.8, 3.9] + poetry-version: [1.1.7] max-parallel: 1 steps: - name: Checkout Repo diff --git a/requirements.txt b/requirements.txt index 2688672..aa47e38 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ -certifi==2021.5.30 -chardet==4.0.0 -dataclasses==0.8 -idna==2.10 -requests==2.25.1 -urllib3==1.26.5 +certifi==2021.5.30; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" +chardet==4.0.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" +dataclasses==0.8; python_version >= "3.6" and python_version < "3.7" +idna==2.10; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" +requests==2.25.1; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") +urllib3==1.26.6; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version < "4" From 04b8c86c0341958c2b1b5c087c6b349fda902311 Mon Sep 17 00:00:00 2001 From: Mario Hernandez Date: Fri, 25 Jun 2021 20:33:49 -0400 Subject: [PATCH 4/8] client --- flow_client.py | 23 ++--------------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/flow_client.py b/flow_client.py index 5adb902..989cfbf 100755 --- a/flow_client.py +++ b/flow_client.py @@ -6,28 +6,10 @@ logging.basicConfig(level=logging.DEBUG) API_URL = "https://sandbox.flow.cl/api" -API_KEY = "5C627F95-4523-4AEB-9FBC-7883B1FL43E5" -API_SECRET = "43559f1ae777c3f4ff86fb752917356ebf6f2644" +API_KEY = "key" +API_SECRET = "secret" api = ApiClient(API_URL, API_KEY, API_SECRET) -cust_data: Dict[str, Any] = { - "start": 0 -} - -llamada = Customer.register(api, 4, "https://mariofix.com") -print(llamada) -""" -llamada = Customer.edit(api, cust_data) -print(llamada) - -llamada = Customer.get(api, "cus_asg7nznrfp") -print(llamada) - -llamada = Customer.get(api, "cus_kpiq2nvif0") -print(llamada) -""" - -""" pago: Dict[str, Any] = { "subject": "Asunto Email", "commerceOrder": "1234", @@ -60,4 +42,3 @@ llamada = Payment.getPayments(api, data) print(llamada) del llamada -""" \ No newline at end of file From c61038c38751480731e8e21339725e3763d6d235 Mon Sep 17 00:00:00 2001 From: Mario Hernandez Date: Fri, 25 Jun 2021 21:16:31 -0400 Subject: [PATCH 5/8] Compatibility Changes, Stable Release --- CHANGELOG.md | 29 +++++++++++++++++++++++++++++ README.md | 2 +- flow_client.py | 2 +- pyflowcl/Clients.py | 2 -- pyflowcl/Refund.py | 8 ++++++-- pyflowcl/__init__.py | 2 +- pyflowcl/models.py | 40 ++++++++++++++++++++-------------------- pyproject.toml | 2 +- tests/test_pyflow.py | 2 +- 9 files changed, 60 insertions(+), 29 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..1d50167 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,29 @@ +# Changelog +Cliente API para operaciones con el servicio de pagos Flow.cl +[FlowAPI-3.0.1](https://www.flow.cl/docs/api.html) + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] +- Customer, Plans, Subscriptions: 1.1.0 +- Coupon, Invoice: 1.2.0 +- Settlement, Merchant: 1.3.0 + +## [1.0.3] - 2021-09-18 +### Added +- Stable + +## [1.0.2-beta] - 2021-09-18 +### Added +- Soporte python 3.6 +- Configuracion Tox + +## [1.0.1-beta] +### Added +- Cambios menores + +## [1.0.0-beta] +### Added +- Payment +- Refund \ No newline at end of file diff --git a/README.md b/README.md index b75cec7..9d48e24 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Cliente API para operaciones con el servicio de pagos Flow.cl ## Instalacion Este proyecto es administrado por Poetry. -Se entrega archivo requirements.txt +Se entrega archivo requirements.txt para PIP. --- diff --git a/flow_client.py b/flow_client.py index 989cfbf..fa3f178 100755 --- a/flow_client.py +++ b/flow_client.py @@ -1,5 +1,5 @@ from typing import Any, Dict -from pyflowcl import Payment, Customer +from pyflowcl import Payment from pyflowcl.Clients import ApiClient import logging diff --git a/pyflowcl/Clients.py b/pyflowcl/Clients.py index 26504b5..4ee7417 100644 --- a/pyflowcl/Clients.py +++ b/pyflowcl/Clients.py @@ -1,5 +1,3 @@ -from __future__ import annotations - from dataclasses import dataclass from typing import Any, Dict diff --git a/pyflowcl/Refund.py b/pyflowcl/Refund.py index c8dd54a..119d6fa 100644 --- a/pyflowcl/Refund.py +++ b/pyflowcl/Refund.py @@ -1,9 +1,13 @@ from dataclasses import asdict from typing import Any, Dict, Union, cast - -from .models import Error, RefundRequest, RefundStatus from .Clients import ApiClient import logging +import sys + +if sys.version_info[0] == 3 and sys.version_info[1] < 7: + from .models_36 import * +else: + from .models import * def create( diff --git a/pyflowcl/__init__.py b/pyflowcl/__init__.py index 7863915..976498a 100644 --- a/pyflowcl/__init__.py +++ b/pyflowcl/__init__.py @@ -1 +1 @@ -__version__ = "1.0.2" +__version__ = "1.0.3" diff --git a/pyflowcl/models.py b/pyflowcl/models.py index 62b32de..897d82c 100644 --- a/pyflowcl/models.py +++ b/pyflowcl/models.py @@ -12,7 +12,7 @@ class Error: message: Optional[str] = None @staticmethod - def from_dict(d: Dict[str, Any]) -> Error: # noqa: F821 + def from_dict(d: Dict[str, Any]) -> Error: code = d.get("code") message = d.get("message") @@ -37,7 +37,7 @@ class PaymentStatus: merchant_id: Optional[str] = None @staticmethod - def from_dict(d: Dict[str, Any]) -> PaymentStatus: # noqa: F821 + def from_dict(d: Dict[str, Any]) -> PaymentStatus: flow_order = d.get("flowOrder") commerce_order = d.get("commerceOrder") request_date = d.get("requestDate") @@ -87,7 +87,7 @@ class PaymentRequest: s: str = "" @staticmethod - def from_dict(d: Dict[str, Any]) -> PaymentRequest: # noqa: F821 + def from_dict(d: Dict[str, Any]) -> PaymentRequest: amount = d.get("amount") apiKey = d.get("apiKey") commerceOrder = d.get("commerceOrder") @@ -142,7 +142,7 @@ class PaymentRequestEmail: s: str = "" @staticmethod - def from_dict(d: Dict[str, Any]) -> PaymentRequestEmail: # noqa: F821 + def from_dict(d: Dict[str, Any]) -> PaymentRequestEmail: amount = d.get("amount") apiKey = d.get("apiKey") commerceOrder = d.get("commerceOrder") @@ -187,7 +187,7 @@ class PaymentResponse: flowOrder: Optional[float] = None @staticmethod - def from_dict(d: Dict[str, Any]) -> PaymentResponse: # noqa: F821 + def from_dict(d: Dict[str, Any]) -> PaymentResponse: url = d.get("url") token = d.get("token") flowOrder = d.get("flowOrder") @@ -204,7 +204,7 @@ class PaymentList: data: Optional[List[Dict[Any, Any]]] = None @staticmethod - def from_dict(d: Dict[str, Any]) -> PaymentList: # noqa: F821 + def from_dict(d: Dict[str, Any]) -> PaymentList: total = d.get("total") hasMore = d.get("hasMore") data = d.get("data") @@ -226,7 +226,7 @@ class RefundRequest: s: str = "" @staticmethod - def from_dict(d: Dict[str, Any]) -> RefundRequest: # noqa: F821 + def from_dict(d: Dict[str, Any]) -> RefundRequest: amount = d.get("amount") apiKey = d.get("apiKey") commerceTrxId = d.get("commerceTrxId") @@ -259,7 +259,7 @@ class RefundStatus: fee: float = 0 @staticmethod - def from_dict(d: Dict[str, Any]) -> RefundStatus: # noqa: F821 + def from_dict(d: Dict[str, Any]) -> RefundStatus: flowRefundOrder = d.get("flowRefundOrder") date = d.get("date") status = d.get("status") @@ -291,7 +291,7 @@ class Customer: status: int = 0 @staticmethod - def from_dict(d: Dict[str, Any]) -> Customer: # noqa: F821 + def from_dict(d: Dict[str, Any]) -> Customer: created = d.get("created") creditCardType = d.get("creditCardType") customerId = d.get("customerId") @@ -329,7 +329,7 @@ class CustomerRequest: s: str = "" @staticmethod - def from_dict(d: Dict[str, Any]) -> CustomerRequest: # noqa: F821 + def from_dict(d: Dict[str, Any]) -> CustomerRequest: apiKey = d.get("apiKey") customerId = d.get("customerId") email = d.get("email") @@ -356,7 +356,7 @@ class CustomerList: data: Optional[List[Dict[Any, Any]]] = None @staticmethod - def from_dict(d: Dict[str, Any]) -> CustomerList: # noqa: F821 + def from_dict(d: Dict[str, Any]) -> CustomerList: total = d.get("total") hasMore = d.get("hasMore") data = d.get("data") @@ -372,7 +372,7 @@ class CustomerRegisterResponse: token: Optional[str] = None @staticmethod - def from_dict(d: Dict[str, Any]) -> CustomerRegisterResponse: # noqa: F821 + def from_dict(d: Dict[str, Any]) -> CustomerRegisterResponse: url = d.get("url") token = d.get("token") @@ -389,7 +389,7 @@ class CustomerRegisterStatusResponse: status: int = 0 @staticmethod - def from_dict(d: Dict[str, Any]) -> CustomerRegisterStatusResponse: # noqa: F821 + def from_dict(d: Dict[str, Any]) -> CustomerRegisterStatusResponse: creditCardType = d.get("creditCardType") customerId = d.get("customerId") last4CardDigits = d.get("last4CardDigits") @@ -416,7 +416,7 @@ class CustomerChargeRequest: s: str = "" @staticmethod - def from_dict(d: Dict[str, Any]) -> CustomerChargeRequest: # noqa: F821 + def from_dict(d: Dict[str, Any]) -> CustomerChargeRequest: amount = d.get("amount") apiKey = d.get("apiKey") commerceOrder = d.get("commerceOrder") @@ -449,7 +449,7 @@ class CollectResponse: url: Optional[str] = None @staticmethod - def from_dict(d: Dict[str, Any]) -> CollectResponse: # noqa: F821 + def from_dict(d: Dict[str, Any]) -> CollectResponse: type = d.get("type") commerce_order = d.get("commerceOrder") flow_order = d.get("flowOrder") @@ -496,7 +496,7 @@ class CollectRequest: s: str = "" @staticmethod - def from_dict(d: Dict[str, Any]) -> CollectRequest: # noqa: F821 + def from_dict(d: Dict[str, Any]) -> CollectRequest: amount = d.get("amount") apiKey = d.get("apiKey") byEmail = d.get("byEmail") @@ -545,7 +545,7 @@ class CollectObject: optional: Optional[str] = None @staticmethod - def from_dict(d: Dict[str, Any]) -> CollectObject: # noqa: F821 + def from_dict(d: Dict[str, Any]) -> CollectObject: customer_id = d.get("customerId") commerce_order = d.get("commerceOrder") subject = d.get("subject") @@ -579,7 +579,7 @@ class BatchCollectRequest: s: str = "" @staticmethod - def from_dict(d: Dict[str, Any]) -> BatchCollectRequest: # noqa: F821 + def from_dict(d: Dict[str, Any]) -> BatchCollectRequest: apiKey = d.get("apiKey") batchRows = d.get("batchRows") byEmail = d.get("byEmail") @@ -615,7 +615,7 @@ class BatchCollectRejectedRow: errorMsg: Optional[str] = None @staticmethod - def from_dict(d: Dict[str, Any]) -> BatchCollectRejectedRow: # noqa: F821 + def from_dict(d: Dict[str, Any]) -> BatchCollectRejectedRow: customerId = d.get("customerId") commerceOrder = d.get("commerceOrder") rowNumber = d.get("rowNumber") @@ -641,7 +641,7 @@ class BatchCollectResponse: rejectedRows: Optional[List[BatchCollectRejectedRow]] = None @staticmethod - def from_dict(d: Dict[str, Any]) -> BatchCollectResponse: # noqa: F821 + def from_dict(d: Dict[str, Any]) -> BatchCollectResponse: token = d.get("token") receivedRows = d.get("receivedRows") acceptedRows = d.get("acceptedRows") diff --git a/pyproject.toml b/pyproject.toml index de479f4..a520192 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pyflowcl" -version = "1.0.2" +version = "1.0.3" description = "Cliente para comunicacion con flowAPI-3 de flow.cl" authors = ["Mario Hernandez "] license = "MIT" diff --git a/tests/test_pyflow.py b/tests/test_pyflow.py index b5e8ab5..6a0838c 100644 --- a/tests/test_pyflow.py +++ b/tests/test_pyflow.py @@ -2,4 +2,4 @@ def test_version(): - assert __version__ == "1.0.2" + assert __version__ == "1.0.3" From e482cf1517bbc109cc124b5a0c7bf170ac374649 Mon Sep 17 00:00:00 2001 From: Mario Hernandez Date: Fri, 25 Jun 2021 21:21:06 -0400 Subject: [PATCH 6/8] Blacked --- pyflowcl/Payment.py | 39 +++++++++++++++------------------------ pyflowcl/Refund.py | 11 ++++------- pyflowcl/models.py | 28 +++++++++++++++++++++++----- pyflowcl/models_36.py | 17 ++++++++++++++--- 4 files changed, 56 insertions(+), 39 deletions(-) diff --git a/pyflowcl/Payment.py b/pyflowcl/Payment.py index bd155b7..2b63176 100644 --- a/pyflowcl/Payment.py +++ b/pyflowcl/Payment.py @@ -11,11 +11,10 @@ def getStatus( - apiclient: ApiClient, token: str, -) -> Union[ - PaymentStatus, Error, -]: - """ Obtiene el estado de un pago previamente creado, el parametro token + apiclient: ApiClient, + token: str, +) -> Union[PaymentStatus, Error,]: + """Obtiene el estado de un pago previamente creado, el parametro token hace referencia a notification id, el cual se recibe luego de procesado un pago """ @@ -38,11 +37,10 @@ def getStatus( def getStatusByCommerceId( - apiclient: ApiClient, commerceId: str, -) -> Union[ - PaymentStatus, Error, -]: - """ Obtiene el estado de un pago previamente creado, el parametro token + apiclient: ApiClient, + commerceId: str, +) -> Union[PaymentStatus, Error,]: + """Obtiene el estado de un pago previamente creado, el parametro token hace referencia a notification id, el cual se recibe luego de procesado un pago """ @@ -65,11 +63,10 @@ def getStatusByCommerceId( def getStatusByFlowOrder( - apiclient: ApiClient, flowOrder: int, -) -> Union[ - PaymentStatus, Error, -]: - """ Obtiene el estado de un pago previamente creado, el parametro token + apiclient: ApiClient, + flowOrder: int, +) -> Union[PaymentStatus, Error,]: + """Obtiene el estado de un pago previamente creado, el parametro token hace referencia a notification id, el cual se recibe luego de procesado un pago """ @@ -93,9 +90,7 @@ def getStatusByFlowOrder( def getPayments( apiclient: ApiClient, payment_info: Dict[str, Any] -) -> Union[ - PaymentList, Error, -]: +) -> Union[PaymentList, Error,]: """ Este método permite obtener la lista paginada de pagos recibidos en un día.Los objetos pagos de la lista tienen la misma estructura de @@ -121,9 +116,7 @@ def getPayments( def create( apiclient: ApiClient, payment_data: Dict[str, Any] -) -> Union[ - PaymentResponse, Error, -]: +) -> Union[PaymentResponse, Error,]: """ Este método permite crear una orden de pago a Flow y recibe como respuesta la URL para redirigir el browser del pagador y el token que identifica la @@ -158,9 +151,7 @@ def create( def createEmail( apiclient: ApiClient, payment_data: Dict[str, Any] -) -> Union[ - PaymentResponse, Error, -]: +) -> Union[PaymentResponse, Error,]: """ Permite generar un cobro por email. Flow emite un email al pagador que contiene la información de la Orden de pago y el link de pago diff --git a/pyflowcl/Refund.py b/pyflowcl/Refund.py index 119d6fa..cefcf3c 100644 --- a/pyflowcl/Refund.py +++ b/pyflowcl/Refund.py @@ -12,9 +12,7 @@ def create( apiclient: ApiClient, refund_data: Dict[str, Any] -) -> Union[ - RefundStatus, Error, -]: +) -> Union[RefundStatus, Error,]: """ Este servicio permite crear una orden de reembolso. Una vez que el receptor del reembolso acepte o rechaze el reembolso, Flow @@ -44,10 +42,9 @@ def create( def getStatus( - apiclient: ApiClient, token: str, -) -> Union[ - RefundStatus, Error, -]: + apiclient: ApiClient, + token: str, +) -> Union[RefundStatus, Error,]: """ Permite obtener el estado de un reembolso solicitado. Este servicio se debe invocar desde la página del comercio que se señaló en el diff --git a/pyflowcl/models.py b/pyflowcl/models.py index 897d82c..2397ca4 100644 --- a/pyflowcl/models.py +++ b/pyflowcl/models.py @@ -16,7 +16,10 @@ def from_dict(d: Dict[str, Any]) -> Error: code = d.get("code") message = d.get("message") - return Error(code=code, message=message,) + return Error( + code=code, + message=message, + ) @dataclass @@ -192,7 +195,11 @@ def from_dict(d: Dict[str, Any]) -> PaymentResponse: token = d.get("token") flowOrder = d.get("flowOrder") - return PaymentResponse(url=url, token=token, flowOrder=flowOrder,) + return PaymentResponse( + url=url, + token=token, + flowOrder=flowOrder, + ) @dataclass @@ -209,7 +216,11 @@ def from_dict(d: Dict[str, Any]) -> PaymentList: hasMore = d.get("hasMore") data = d.get("data") - return PaymentList(total=total, hasMore=hasMore, data=data,) + return PaymentList( + total=total, + hasMore=hasMore, + data=data, + ) @dataclass @@ -361,7 +372,11 @@ def from_dict(d: Dict[str, Any]) -> CustomerList: hasMore = d.get("hasMore") data = d.get("data") - return CustomerList(total=total, hasMore=hasMore, data=data,) + return CustomerList( + total=total, + hasMore=hasMore, + data=data, + ) @dataclass @@ -376,7 +391,10 @@ def from_dict(d: Dict[str, Any]) -> CustomerRegisterResponse: url = d.get("url") token = d.get("token") - return CustomerRegisterResponse(url=url, token=token,) + return CustomerRegisterResponse( + url=url, + token=token, + ) @dataclass diff --git a/pyflowcl/models_36.py b/pyflowcl/models_36.py index 66886da..a24e842 100644 --- a/pyflowcl/models_36.py +++ b/pyflowcl/models_36.py @@ -14,7 +14,10 @@ def from_dict(d: Dict[str, Any]) -> "Error": code = d.get("code") message = d.get("message") - return Error(code=code, message=message,) + return Error( + code=code, + message=message, + ) @dataclass @@ -190,7 +193,11 @@ def from_dict(d: Dict[str, Any]) -> "PaymentResponse": token = d.get("token") flowOrder = d.get("flowOrder") - return PaymentResponse(url=url, token=token, flowOrder=flowOrder,) + return PaymentResponse( + url=url, + token=token, + flowOrder=flowOrder, + ) @dataclass @@ -207,7 +214,11 @@ def from_dict(d: Dict[str, Any]) -> "PaymentList": hasMore = d.get("hasMore") data = d.get("data") - return PaymentList(total=total, hasMore=hasMore, data=data,) + return PaymentList( + total=total, + hasMore=hasMore, + data=data, + ) @dataclass From 4475acb2c20c510c7d18eed0d02d2d2ae17ce239 Mon Sep 17 00:00:00 2001 From: Mario Hernandez Date: Sat, 10 Jul 2021 21:28:26 -0400 Subject: [PATCH 7/8] Black y Docstrings --- docs/Makefile | 20 +++++++++++++ docs/conf.py | 68 ++++++++++++++++++++++++++++++++++++++++++++ docs/index.rst | 19 +++++++++++++ docs/make.bat | 35 +++++++++++++++++++++++ docs/modules.rst | 7 +++++ docs/pyflowcl.rst | 53 ++++++++++++++++++++++++++++++++++ pyflowcl/models.py | 39 ++++++++++++++----------- pyproject.toml | 5 +++- tests/test_pyflow.py | 2 +- 9 files changed, 229 insertions(+), 19 deletions(-) create mode 100644 docs/Makefile create mode 100644 docs/conf.py create mode 100644 docs/index.rst create mode 100644 docs/make.bat create mode 100644 docs/modules.rst create mode 100644 docs/pyflowcl.rst diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..d4bb2cb --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..d2f1a3d --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,68 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +import os +import sys + +sys.path.insert(0, os.path.abspath("..")) + + +# -- Project information ----------------------------------------------------- + +project = "pyFlowCL" +copyright = "2021, mariofix" +author = "mariofix" + +# The full version, including alpha/beta/rc tags +release = "1.0.5" + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = ["sphinx.ext.autodoc"] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ["_templates"] + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = "es" + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +on_rtd = os.environ.get("READTHEDOCS", None) == "True" + +if not on_rtd: # only import and set the theme if we're building docs locally + import sphinx_theme + + html_theme = "stanford_theme" + html_theme_path = [sphinx_theme.get_html_theme_path("stanford_theme")] + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ["_static"] diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..947419b --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,19 @@ +.. pyFlowCL documentation master file, created by + sphinx-quickstart on Sat Jul 10 19:22:59 2021. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to pyFlowCL's documentation! +==================================== + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + modules + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..2119f51 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=. +set BUILDDIR=_build + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/docs/modules.rst b/docs/modules.rst new file mode 100644 index 0000000..87ac54d --- /dev/null +++ b/docs/modules.rst @@ -0,0 +1,7 @@ +pyflowcl +====== + +.. toctree:: + :maxdepth: 4 + + pyflowcl diff --git a/docs/pyflowcl.rst b/docs/pyflowcl.rst new file mode 100644 index 0000000..cc078bf --- /dev/null +++ b/docs/pyflowcl.rst @@ -0,0 +1,53 @@ +pyflowcl package +================ + +Submodules +---------- + +pyflowcl.Clients module +----------------------- + +.. automodule:: pyflowcl.Clients + :members: + :undoc-members: + :show-inheritance: + +pyflowcl.Customer module +------------------------ + +.. automodule:: pyflowcl.Customer + :members: + :undoc-members: + :show-inheritance: + +pyflowcl.Payment module +----------------------- + +.. automodule:: pyflowcl.Payment + :members: + :undoc-members: + :show-inheritance: + +pyflowcl.Refund module +---------------------- + +.. automodule:: pyflowcl.Refund + :members: + :undoc-members: + :show-inheritance: + +pyflowcl.models module +---------------------- + +.. automodule:: pyflowcl.models + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: pyflowcl + :members: + :undoc-members: + :show-inheritance: diff --git a/pyflowcl/models.py b/pyflowcl/models.py index 2397ca4..90c47eb 100644 --- a/pyflowcl/models.py +++ b/pyflowcl/models.py @@ -1,3 +1,8 @@ +""" +pyflowcl.models +~~~~~~~~~~~~~~~~ +Modelos de distintos objetos del paquete +""" from __future__ import annotations from dataclasses import dataclass @@ -6,7 +11,7 @@ @dataclass class Error: - """ Objeto para definir un error """ + """Objeto para definir un error""" code: Optional[float] = None message: Optional[str] = None @@ -24,7 +29,7 @@ def from_dict(d: Dict[str, Any]) -> Error: @dataclass class PaymentStatus: - """ Objeto para obtener el estado de un pago """ + """Objeto para obtener el estado de un pago""" flow_order: Optional[int] = None commerce_order: Optional[str] = None @@ -72,7 +77,7 @@ def from_dict(d: Dict[str, Any]) -> PaymentStatus: @dataclass class PaymentRequest: - """ Objeto para generar una URL de pago """ + """Objeto para generar una URL de pago""" amount: float = 0 apiKey: str = "API_KEY" @@ -126,7 +131,7 @@ def from_dict(d: Dict[str, Any]) -> PaymentRequest: @dataclass class PaymentRequestEmail: - """ Objeto para generar un correo electronico de pago """ + """Objeto para generar un correo electronico de pago""" amount: float = 0 apiKey: str = "API_KEY" @@ -183,7 +188,7 @@ def from_dict(d: Dict[str, Any]) -> PaymentRequestEmail: @dataclass class PaymentResponse: - """ Objeto respuesta de una creacion de pago """ + """Objeto respuesta de una creacion de pago""" url: Optional[str] = None token: Optional[str] = None @@ -204,7 +209,7 @@ def from_dict(d: Dict[str, Any]) -> PaymentResponse: @dataclass class PaymentList: - """ Lista de pagos """ + """Lista de pagos""" total: Optional[float] = None hasMore: Optional[bool] = None @@ -225,7 +230,7 @@ def from_dict(d: Dict[str, Any]) -> PaymentList: @dataclass class RefundRequest: - """ Refund Request object """ + """Refund Request object""" amount: float = 0 apiKey: str = "API_KEY" @@ -261,7 +266,7 @@ def from_dict(d: Dict[str, Any]) -> RefundRequest: @dataclass class RefundStatus: - """ Refund object """ + """Refund object""" flowRefundOrder: int = 0 date: str = "" @@ -288,7 +293,7 @@ def from_dict(d: Dict[str, Any]) -> RefundStatus: @dataclass class Customer: - """ Customer Object """ + """Customer Object""" created: str = "" creditCardType: Optional[str] = None @@ -330,7 +335,7 @@ def from_dict(d: Dict[str, Any]) -> Customer: @dataclass class CustomerRequest: - """ CustomerRequest Object """ + """CustomerRequest Object""" apiKey: str = "" customerId: str = "" @@ -360,7 +365,7 @@ def from_dict(d: Dict[str, Any]) -> CustomerRequest: @dataclass class CustomerList: - """ Lista de Clientes """ + """Lista de Clientes""" total: Optional[float] = None hasMore: Optional[bool] = None @@ -381,7 +386,7 @@ def from_dict(d: Dict[str, Any]) -> CustomerList: @dataclass class CustomerRegisterResponse: - """ Objeto respuesta """ + """Objeto respuesta""" url: Optional[str] = None token: Optional[str] = None @@ -399,7 +404,7 @@ def from_dict(d: Dict[str, Any]) -> CustomerRegisterResponse: @dataclass class CustomerRegisterStatusResponse: - """ Objeto respuesta """ + """Objeto respuesta""" creditCardType: str = "" customerId: str = "" @@ -423,7 +428,7 @@ def from_dict(d: Dict[str, Any]) -> CustomerRegisterStatusResponse: @dataclass class CustomerChargeRequest: - """ Objeto para generar una URL de pago """ + """Objeto para generar una URL de pago""" amount: float = 0 apiKey: str = "API_KEY" @@ -456,7 +461,7 @@ def from_dict(d: Dict[str, Any]) -> CustomerChargeRequest: @dataclass class CollectResponse: - """ Objeto para CollectResponse """ + """Objeto para CollectResponse""" commerce_order: Optional[str] = None flow_order: Optional[float] = None @@ -493,7 +498,7 @@ def from_dict(d: Dict[str, Any]) -> CollectResponse: @dataclass class CollectRequest: - """ Objeto para generar un correo electronico de pago """ + """Objeto para generar un correo electronico de pago""" amount: float = 0 apiKey: str = "API_KEY" @@ -552,7 +557,7 @@ def from_dict(d: Dict[str, Any]) -> CollectRequest: @dataclass class CollectObject: - """ Objeto de cobro para un lote de cobros """ + """Objeto de cobro para un lote de cobros""" customer_id: str commerce_order: str diff --git a/pyproject.toml b/pyproject.toml index a31dcd3..3e8351b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pyflowcl" -version = "1.0.4" +version = "1.0.5" description = "Cliente para comunicacion con flowAPI-3 de flow.cl" authors = ["Mario Hernandez "] license = "MIT" @@ -25,6 +25,9 @@ pytest = "*" pytest-pep8 = "*" black = "*" tox = "*" +thankyou = "^0.0.1" +sphinx = "*" +sphinx-theme = "*" [build-system] requires = ["poetry>=0.12"] diff --git a/tests/test_pyflow.py b/tests/test_pyflow.py index 6a0838c..861f5bd 100644 --- a/tests/test_pyflow.py +++ b/tests/test_pyflow.py @@ -2,4 +2,4 @@ def test_version(): - assert __version__ == "1.0.3" + assert __version__ == "1.0.5" From 853ba83b892cc0c5cd90e81eb17ef435a73100e4 Mon Sep 17 00:00:00 2001 From: Mario Hernandez Date: Sat, 10 Jul 2021 21:29:16 -0400 Subject: [PATCH 8/8] Docstrings --- pyflowcl/Clients.py | 36 ++++++++++++++- pyflowcl/Customer.py | 106 ++++++++++++++----------------------------- pyflowcl/__init__.py | 2 +- 3 files changed, 71 insertions(+), 73 deletions(-) diff --git a/pyflowcl/Clients.py b/pyflowcl/Clients.py index 4ee7417..a7d1611 100644 --- a/pyflowcl/Clients.py +++ b/pyflowcl/Clients.py @@ -1,3 +1,8 @@ +""" +pyflowcl.Clients +~~~~~~~~~~~~~~~~ +Este modulo implementa el Cliente API genérico. +""" from dataclasses import dataclass from typing import Any, Dict @@ -9,13 +14,30 @@ @dataclass class ApiClient: - """ Objeto para definir ApiClient """ + """Clase ApiClient con los objetos para realizar llamadas + + Implementa todos los métodos de ``dataclass``. + + se instancia con:: + + cliente = ApiClient(api_url, api_key, api_secret) + + el cliente luego debe ser entregado como primer parametro de + las clases incorporadas:: + + pay = Payment.create(cliente, [...]) + + """ api_url: str = "https://sandbox.flow.cl/api" api_key: str = "" api_secret: str = "" def make_signature(self, params: Dict[str, Any]) -> str: + """Crea el Hash de validacion para ser enviado con la informacion + + :rtype: str + """ string = "" for k, d in params.items(): if d is not None: @@ -28,10 +50,22 @@ def make_signature(self, params: Dict[str, Any]) -> str: return hash_string def get(self, url: str, query_string: Dict[str, Any]) -> Dict[str, Any]: + """Reimplementa get + + :rtype: dict + """ return requests.get(url, params=query_string) def post(self, url: str, post_data: Dict[str, Any]) -> Dict[str, Any]: + """Reimplementa post + + :rtype: dict + """ return requests.post(url, data=post_data) def put(self, url: str, put_data: Dict[str, Any]) -> Dict[str, Any]: + """Reimplementa put + + :rtype: dict + """ return requests.put(url, data=put_data) diff --git a/pyflowcl/Customer.py b/pyflowcl/Customer.py index 20ca0e0..bd964ba 100644 --- a/pyflowcl/Customer.py +++ b/pyflowcl/Customer.py @@ -1,31 +1,31 @@ +""" +pyflowcl.Customer +~~~~~~~~~~~~~~~~~ +Modulo para operaciones con Customer +""" from dataclasses import asdict from typing import Any, Dict, Union, cast +from .Clients import ApiClient +import logging from .models import ( - Error, Customer, CustomerRequest, + Error, CustomerList, CustomerRegisterResponse, CustomerRegisterStatusResponse, PaymentStatus, CustomerChargeRequest, - CollectRequest, CollectResponse, - BatchCollectRequest, BatchCollectResponse, ) -from .Clients import ApiClient -import logging def create( apiclient: ApiClient, customer_data: Dict[str, Any] -) -> Union[ - Customer, Error, -]: - """ - Permite crear clientes para efectuarles cargos recurrentes o suscribirlos +) -> Union[Customer, Error]: + """Permite crear clientes para efectuarles cargos recurrentes o suscribirlos a un planes de suscripción. Una vez creado un cliente, Flow lo identificará por un hash denominado customerId, ejemplo: @@ -51,14 +51,8 @@ def create( raise Exception(response=response) -def edit( - apiclient: ApiClient, customer_data: Dict[str, Any] -) -> Union[ - Customer, Error, -]: - """ - Este servicio permite editar los datos de un cliente - """ +def edit(apiclient: ApiClient, customer_data: Dict[str, Any]) -> Union[Customer, Error]: + """Este servicio permite editar los datos de un cliente""" url = f"{apiclient.api_url}/customer/edit" customer = CustomerRequest.from_dict(customer_data) @@ -81,12 +75,8 @@ def edit( def delete( apiclient: ApiClient, customer_data: Dict[str, Any] -) -> Union[ - Customer, Error, -]: - """ - Este servicio permite editar los datos de un cliente - """ +) -> Union[Customer, Error]: + """Este servicio permite editar los datos de un cliente""" url = f"{apiclient.api_url}/customer/delete" customer = CustomerRequest.from_dict(customer_data) @@ -108,13 +98,10 @@ def delete( def get( - apiclient: ApiClient, cust_id: str, -) -> Union[ - Customer, Error, -]: - """ - Permite obtener los datos de un cliente en base a su customerId. - """ + apiclient: ApiClient, + cust_id: str, +) -> Union[Customer, Error]: + """Permite obtener los datos de un cliente en base a su customerId.""" url = f"{apiclient.api_url}/customer/get" params: Dict[str, Any] = {"apiKey": apiclient.api_key, "customerId": cust_id} @@ -134,13 +121,10 @@ def get( def list( - apiclient: ApiClient, filter_params: Dict[str, Any], -) -> Union[ - CustomerList, Error, -]: - """ - Permite obtener los datos de un cliente en base a su customerId. - """ + apiclient: ApiClient, + filter_params: Dict[str, Any], +) -> Union[CustomerList, Error]: + """Permite obtener los datos de un cliente en base a su customerId.""" url = f"{apiclient.api_url}/customer/list" @@ -165,17 +149,14 @@ def list( def register( apiclient: ApiClient, customerId: str, url_return: str -) -> Union[ - CustomerRegisterResponse, Error, -]: - """ - Envía a un cliente a registrar su tarjeta de crédito para poder +) -> Union[CustomerRegisterResponse, Error]: + """Envía a un cliente a registrar su tarjeta de crédito para poder efectuarle cargos automáticos. El servicio responde con la URL para redirigir el browser del pagador y el token que identifica la transacción. La url de redirección se debe formar concatenando los valores recibidos en la respuesta de la siguiente forma: - url + "?token=" +token + url + "?token=" +token Una vez redirigido el browser del cliente, Flow responderá por medio de una llamada POST a la url callback del comercio indicada en el parámetro @@ -210,11 +191,8 @@ def register( def getRegisterStatus( apiclient: ApiClient, token: str -) -> Union[ - CustomerRegisterStatusResponse, Error, -]: - """ - Elte servicio retorna el resultado del registro de la tarjeta de +) -> Union[CustomerRegisterStatusResponse, Error]: + """Este servicio retorna el resultado del registro de la tarjeta de crédito de un cliente. """ @@ -242,13 +220,8 @@ def getRegisterStatus( raise Exception(response=response) -def unRegister( - apiclient: ApiClient, customerId: str -) -> Union[ - Customer, Error, -]: - """ - Este servicio permite eliminar el registro de la tarjeta de crédito +def unRegister(apiclient: ApiClient, customerId: str) -> Union[Customer, Error]: + """Este servicio permite eliminar el registro de la tarjeta de crédito de un cliente. Al eliminar el registro no se podrá hacer cargos automáticos y Flow enviará un cobro por email. """ @@ -277,11 +250,8 @@ def unRegister( def charge( apiclient: ApiClient, charge_data: Dict[str, Any] -) -> Union[ - PaymentStatus, Error, -]: - """ - Este servicio permite efectuar un cargo automático en la tarjeta de +) -> Union[PaymentStatus, Error]: + """Este servicio permite efectuar un cargo automático en la tarjeta de crédito previamente registrada por el cliente. Si el cliente no tiene registrada una tarjeta el metodo retornará error. """ @@ -308,11 +278,8 @@ def charge( def collect( apiclient: ApiClient, collect_data: Dict[str, Any] -) -> Union[ - CollectResponse, Error, -]: - """ - Este servicio envía un cobro a un cliente. Si el cliente tiene +) -> Union[CollectResponse, Error]: + """Este servicio envía un cobro a un cliente. Si el cliente tiene registrada una tarjeta de crédito se le hace un cargo automático, si no tiene registrada una tarjeta de credito se genera un cobro. Si se envía el parámetro byEmail = 1, se genera un cobro por email. @@ -340,11 +307,8 @@ def collect( def batchCollect( apiclient: ApiClient, collect_data: Dict[str, Any] -) -> Union[ - BatchCollectResponse, Error, -]: - """ - Este servicio envía de forma masiva un lote de cobros a clientes. +) -> Union[BatchCollectResponse, Error]: + """Este servicio envía de forma masiva un lote de cobros a clientes. Similar al servicio collect pero masivo y asíncrono. Este servicio responde con un token identificador del lote y el número de filas recibidas. diff --git a/pyflowcl/__init__.py b/pyflowcl/__init__.py index 976498a..68cdeee 100644 --- a/pyflowcl/__init__.py +++ b/pyflowcl/__init__.py @@ -1 +1 @@ -__version__ = "1.0.3" +__version__ = "1.0.5"