diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ee2f572..3d5e6fc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12'] + python-version: ['3.9', '3.10', '3.11', '3.12'] steps: - uses: actions/checkout@v2 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..9f72a15 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,16 @@ +repos: + - repo: https://github.com/melisource/fury_websec-git-hooks + rev: v2.0.0 + hooks: + - id: pre_commit_hook + stages: [commit] + - id: post_commit_hook + stages: [post-commit] + + - repo: https://github.com/melisource/fury_datasec-git-hooks + rev: 1.2.2 + hooks: + - id: pre_commit_hook + stages: [commit] + - id: post_commit_hook + stages: [post-commit] diff --git a/mercadopago/config/config.py b/mercadopago/config/config.py index f457264..dacd786 100644 --- a/mercadopago/config/config.py +++ b/mercadopago/config/config.py @@ -10,7 +10,7 @@ class Config: """ def __init__(self): - self.__version = "2.2.3" + self.__version = "2.3.0" self.__user_agent = "MercadoPago Python SDK v" + self.__version self.__product_id = "bc32bpftrpp001u8nhlg" self.__tracking_id = "platform:" + platform.python_version() diff --git a/mercadopago/config/request_options.py b/mercadopago/config/request_options.py index 2cc436c..3516892 100644 --- a/mercadopago/config/request_options.py +++ b/mercadopago/config/request_options.py @@ -21,7 +21,8 @@ class RequestOptions: # pylint: disable=too-many-instance-attributes __integrator_id = None __platform_id = None - def __init__( # pylint: disable=too-many-arguments + def __init__( # pylint: disable=too-many-positional-arguments + # pylint: disable=too-many-arguments self, access_token=None, connection_timeout=60.0, diff --git a/mercadopago/examples/order/cancel_order.py b/mercadopago/examples/order/cancel_order.py new file mode 100644 index 0000000..c848e13 --- /dev/null +++ b/mercadopago/examples/order/cancel_order.py @@ -0,0 +1,67 @@ +import os +import time + +from mercadopago import SDK + +def main(): + # Define the authentication token + access_token = "" + + # Define the authentication token + sdk = SDK(access_token) + + # Create a test card token + def create_test_card(): + card_token_object = { + "card_number": "5031433215406351", + "security_code": "123", + "expiration_year": "2030", + "expiration_month": "11", + "cardholder": {"name": "APRO"} + } + card_token_created = sdk.card_token().create(card_token_object) + return card_token_created["response"]["id"] + + # Create an order object + card_token_id = create_test_card() + order_object = { + "type": "online", + "capture_mode": "manual", + "processing_mode": "automatic", + "total_amount": "880.00", + "external_reference": "ext_ref_1234", + "transactions": { + "payments": [ + { + "amount": "880.00", + "payment_method": { + "id": "master", + "type": "credit_card", + "token": card_token_id, + "installments": 2 + } + } + ] + }, + "payer": { + "email": "" + } + } + + try: + # Call the method to create the order + response = sdk.order().create(order_object) + print("Order created successfully") + + # Get the order ID from the response + order_id = response["response"]["id"] + + time.sleep(5) + # Call the method to CANCEL + order_details = sdk.order().cancel(order_id) + print("Order details:", order_details["response"]) + except Exception as e: + print("Error:", e) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/mercadopago/examples/order/capture_order.py b/mercadopago/examples/order/capture_order.py new file mode 100644 index 0000000..bbd5d8d --- /dev/null +++ b/mercadopago/examples/order/capture_order.py @@ -0,0 +1,64 @@ +import os +from mercadopago import SDK + +def main(): + # Define the authentication token + access_token = "" + + # Define the authentication token + sdk = SDK(access_token) + + # Create a test card token + def create_test_card(): + card_token_object = { + "card_number": "5031433215406351", + "security_code": "123", + "expiration_year": "2030", + "expiration_month": "11", + "cardholder": {"name": "APRO"} + } + card_token_created = sdk.card_token().create(card_token_object) + return card_token_created["response"]["id"] + + # Create an order object + card_token_id = create_test_card() + order_object = { + "type": "online", + "capture_mode": "manual", # Mode need to be Manual to use Capture Method. + "processing_mode": "automatic", + "total_amount": "880.00", + "external_reference": "ext_ref_1234", + "transactions": { + "payments": [ + { + "amount": "880.00", + "payment_method": { + "id": "master", + "type": "credit_card", + "token": card_token_id, + "installments": 2 + } + } + ] + }, + "payer": { + "email": "" + } + } + + try: + # Call the method to create the order + response = sdk.order().create(order_object) + print("Order created successfully") + + # Get the order ID from the response + order_id = response["response"]["id"] + + # Call the method to CAPTURE the order + order_details = sdk.order().capture(order_id) + print("Order details:", order_details["response"]) + except Exception as e: + print("Error:", e) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/mercadopago/examples/order/create_order.py b/mercadopago/examples/order/create_order.py new file mode 100644 index 0000000..01f55b9 --- /dev/null +++ b/mercadopago/examples/order/create_order.py @@ -0,0 +1,56 @@ +from mercadopago import SDK + + +def main(): + # Define the authentication token + access_token = "" + + # Define the authentication token + sdk = SDK(access_token) + + # Create a test card token + def create_test_card(): + card_token_object = { + "card_number": "5031433215406351", + "security_code": "123", + "expiration_year": "2030", + "expiration_month": "11", + "cardholder": {"name": "APRO"} + } + card_token_created = sdk.card_token().create(card_token_object) + return card_token_created["response"]["id"] + + # Create an order object + card_token_id = create_test_card() + order_object = { + "type": "online", + "total_amount": "880.00", + "external_reference": "ext_ref_1234", + "transactions": { + "payments": [ + { + "amount": "880.00", + "payment_method": { + "id": "master", + "type": "credit_card", + "token": card_token_id, + "installments": 2 + } + } + ] + }, + "payer": { + "email": "" + } + } + + try: + # Call the method to create the order + response = sdk.order().create(order_object) + print("Order created successfully:", response["response"]) + except Exception as e: + print("Error:", e) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/mercadopago/examples/order/create_transaction.py b/mercadopago/examples/order/create_transaction.py new file mode 100644 index 0000000..87018e7 --- /dev/null +++ b/mercadopago/examples/order/create_transaction.py @@ -0,0 +1,65 @@ +import os +import time + +from mercadopago import SDK + +def main(): + # Define the authentication token + access_token = "" + + # Define the authentication token + sdk = SDK(access_token) + + # Create a test card token + def create_test_card(): + card_token_object = { + "card_number": "5031433215406351", + "security_code": "123", + "expiration_year": "2030", + "expiration_month": "11", + "cardholder": {"name": "APRO"} + } + card_token_created = sdk.card_token().create(card_token_object) + return card_token_created["response"]["id"] + + # Create an order object + card_token_id = create_test_card() + order_object = { + "type": "online", + "processing_mode": "manual", + "total_amount": "200.00", + "external_reference": "ext_ref_1234", + "payer": { + "email": "" + } + } + + try: + # Call the method to create the order + response = sdk.order().create(order_object) + print("Order created successfully") + + # Get the order ID from the response + order_id = response["response"]["id"] + + transaction_object = { + "payments": [ + { + "amount": "200.00", + "payment_method": { + "id": "master", + "type": "credit_card", + "token": card_token_id, + "installments": 12 + } + } + ] + } + # Call the method to CREATE A TRANSACTION in the order + transaction_created = sdk.order().create_transaction(order_id, transaction_object) + print("Transaction created:", transaction_created["response"]) + except Exception as e: + print("Error:", e) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/mercadopago/examples/order/delete_transaction.py b/mercadopago/examples/order/delete_transaction.py new file mode 100644 index 0000000..9f31c2f --- /dev/null +++ b/mercadopago/examples/order/delete_transaction.py @@ -0,0 +1,66 @@ +import os +import time + +from mercadopago import SDK + +def main(): + # Define the authentication token + access_token = "" + + # Define the authentication token + sdk = SDK(access_token) + + # Create a test card token + def create_test_card(): + card_token_object = { + "card_number": "5031433215406351", + "security_code": "123", + "expiration_year": "2030", + "expiration_month": "11", + "cardholder": {"name": "APRO"} + } + card_token_created = sdk.card_token().create(card_token_object) + return card_token_created["response"]["id"] + + # Create an order object + card_token_id = create_test_card() + order_object = { + "type": "online", + "processing_mode": "manual", + "total_amount": "200.00", + "external_reference": "ext_ref_1234", + "transactions": { + "payments": [ + { + "amount": "200.00", + "payment_method": { + "id": "master", + "type": "credit_card", + "token": card_token_id, + "installments": 12 + } + } + ] + }, + "payer": { + "email": "" + } + } + + try: + # Call the method to create the order + response = sdk.order().create(order_object) + print("Order created successfully") + + # Get the order ID from the response + order_id = response["response"]["id"] + transaction_id = response["response"]["transactions"]["payments"][0]["id"] + + # Call the method to DELETE the transaction in the order + transaction_deleted = sdk.order().delete_transaction(order_id, transaction_id) + print("Transaction Successful Deleted.", transaction_deleted["response"]) + except Exception as e: + print("Error:", e) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/mercadopago/examples/order/get_order.py b/mercadopago/examples/order/get_order.py new file mode 100644 index 0000000..2c128ef --- /dev/null +++ b/mercadopago/examples/order/get_order.py @@ -0,0 +1,62 @@ +import os +from mercadopago import SDK + +def main(): + # Define the authentication token + access_token = "" + + # Define the authentication token + sdk = SDK(access_token) + + # Create a test card token + def create_test_card(): + card_token_object = { + "card_number": "5031433215406351", + "security_code": "123", + "expiration_year": "2030", + "expiration_month": "11", + "cardholder": {"name": "APRO"} + } + card_token_created = sdk.card_token().create(card_token_object) + return card_token_created["response"]["id"] + + # Create an order object + card_token_id = create_test_card() + order_object = { + "type": "online", + "total_amount": "880.00", + "external_reference": "ext_ref_1234", + "transactions": { + "payments": [ + { + "amount": "880.00", + "payment_method": { + "id": "master", + "type": "credit_card", + "token": card_token_id, + "installments": 2 + } + } + ] + }, + "payer": { + "email": "" + } + } + + try: + # Call the method to create the order + response = sdk.order().create(order_object) + print("Order created successfully!") + + # Get the order ID from the response + order_id = response["response"]["id"] + + # Call the method to get the order details + order_details = sdk.order().get(order_id) + print("Order details:", order_details["response"]) + except Exception as e: + print("Error:", e) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/mercadopago/examples/order/process_order.py b/mercadopago/examples/order/process_order.py new file mode 100644 index 0000000..a60a023 --- /dev/null +++ b/mercadopago/examples/order/process_order.py @@ -0,0 +1,64 @@ +import os +from mercadopago import SDK + +def main(): + # Define the authentication token + access_token = "" + + # Define the authentication token + sdk = SDK(access_token) + + # Create a test card token + def create_test_card(): + card_token_object = { + "card_number": "5031433215406351", + "security_code": "123", + "expiration_year": "2030", + "expiration_month": "11", + "cardholder": {"name": "APRO"} + } + card_token_created = sdk.card_token().create(card_token_object) + return card_token_created["response"]["id"] + + # Create an order object + card_token_id = create_test_card() + order_object = { + "type": "online", + "processing_mode": "manual", # Mode need to be Manual to use Process Method. + "total_amount": "880.00", + "external_reference": "ext_ref_1234", + "transactions": { + "payments": [ + { + "amount": "880.00", + "payment_method": { + "id": "master", + "type": "credit_card", + "token": card_token_id, + "installments": 2 + } + } + ] + }, + "payer": { + "email": "" + } + } + + try: + # Call the method to create the order + response = sdk.order().create(order_object) + print("Order created successfully!") + + # Get the order ID from the response + order_id = response["response"]["id"] + print("Order ID:", order_id) + + # Call the method to PROCESS the order created in Manual Mode + order_details = sdk.order().process(order_id) + print("Order details:", order_details["response"]) + except Exception as e: + print("Error:", e) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/mercadopago/examples/order/refund_partial.py b/mercadopago/examples/order/refund_partial.py new file mode 100644 index 0000000..c260f9a --- /dev/null +++ b/mercadopago/examples/order/refund_partial.py @@ -0,0 +1,74 @@ +import os +import time + +from mercadopago import SDK + +def main(): + # Define the authentication token + access_token = "" + + # Define the authentication token + sdk = SDK(access_token) + + # Create a test card token + def create_test_card(): + card_token_object = { + "card_number": "5031433215406351", + "security_code": "123", + "expiration_year": "2030", + "expiration_month": "11", + "cardholder": {"name": "APRO"} + } + card_token_created = sdk.card_token().create(card_token_object) + return card_token_created["response"]["id"] + + # Create an order object + card_token_id = create_test_card() + order_object = { + "type": "online", + "processing_mode": "automatic", + "total_amount": "500.00", + "external_reference": "ext_ref_1234", + "transactions": { + "payments": [ + { + "amount": "500.00", + "payment_method": { + "id": "master", + "type": "credit_card", + "token": card_token_id, + "installments": 2 + } + } + ] + }, + "payer": { + "email": "" + } + } + + try: + # Call the method to create the order + response = sdk.order().create(order_object) + print("Order created successfully.") + + # Get the order ID from the response + order_id = response["response"]["id"] + + refund = { + "transactions": [ + { + "id": response["response"]["transactions"]["payments"][0]["id"], + "amount": "25.00" + } + ] + } + + # Call the method to REFUND + transaction_refund = sdk.order().refund_transaction(order_id, refund) + print("Transaction Partially Refunded:", transaction_refund["response"]) + except Exception as e: + print("Error:", e) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/mercadopago/examples/order/refund_total.py b/mercadopago/examples/order/refund_total.py new file mode 100644 index 0000000..251b0ca --- /dev/null +++ b/mercadopago/examples/order/refund_total.py @@ -0,0 +1,65 @@ +import os +import time + +from mercadopago import SDK + +def main(): + # Define the authentication token + access_token = "" + + # Define the authentication token + sdk = SDK(access_token) + + # Create a test card token + def create_test_card(): + card_token_object = { + "card_number": "5031433215406351", + "security_code": "123", + "expiration_year": "2030", + "expiration_month": "11", + "cardholder": {"name": "APRO"} + } + card_token_created = sdk.card_token().create(card_token_object) + return card_token_created["response"]["id"] + + # Create an order object + card_token_id = create_test_card() + order_object = { + "type": "online", + "processing_mode": "automatic", + "total_amount": "500.00", + "external_reference": "ext_ref_1234", + "transactions": { + "payments": [ + { + "amount": "500.00", + "payment_method": { + "id": "master", + "type": "credit_card", + "token": card_token_id, + "installments": 2 + } + } + ] + }, + "payer": { + "email": "" + } + } + + try: + # Call the method to create the order + response = sdk.order().create(order_object) + print("Order created successfully.") + + # Get the order ID from the response + order_id = response["response"]["id"] + + # Call the method to REFUND TOTALLY the transaction in the order + transaction_refund = sdk.order().refund_transaction(order_id) + print("Transaction Total Refunded:", transaction_refund["response"]) + except Exception as e: + print("Error:", e) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/mercadopago/examples/order/update_transaction.py b/mercadopago/examples/order/update_transaction.py new file mode 100644 index 0000000..f552071 --- /dev/null +++ b/mercadopago/examples/order/update_transaction.py @@ -0,0 +1,73 @@ +import os +import time + +from mercadopago import SDK + +def main(): + # Define the authentication token + access_token = "" + + # Define the authentication token + sdk = SDK(access_token) + + # Create a test card token + def create_test_card(): + card_token_object = { + "card_number": "5031433215406351", + "security_code": "123", + "expiration_year": "2030", + "expiration_month": "11", + "cardholder": {"name": "APRO"} + } + card_token_created = sdk.card_token().create(card_token_object) + return card_token_created["response"]["id"] + + # Create an order object + card_token_id = create_test_card() + order_object = { + "type": "online", + "processing_mode": "manual", + "total_amount": "500.00", + "external_reference": "ext_ref_1234", + "transactions": { + "payments": [ + { + "amount": "500.00", + "payment_method": { + "id": "master", + "type": "credit_card", + "token": card_token_id, + "installments": 2 + } + } + ] + }, + "payer": { + "email": "" + } + } + + try: + # Call the method to create the order + response = sdk.order().create(order_object) + print("Order created successfully") + + # Get the order ID from the response + order_id = response["response"]["id"] + transaction_id = response["response"]["transactions"]["payments"][0]["id"] + + transaction_update = { + "payment_method": { + "type": "credit_card", + "installments": 5 + } + } + + # Call the method to UPDATE the transaction in the order + update = sdk.order().update_transaction(order_id, transaction_id, transaction_update) + print("Transaction Updated:", update["response"]) + except Exception as e: + print("Error:", e) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/mercadopago/http/http_client.py b/mercadopago/http/http_client.py index 81d7040..901f705 100644 --- a/mercadopago/http/http_client.py +++ b/mercadopago/http/http_client.py @@ -1,6 +1,7 @@ """ Module: http_client """ +# pylint: disable=too-many-arguments import requests from requests.adapters import HTTPAdapter from urllib3.util import Retry @@ -24,14 +25,19 @@ def request(self, method, url, maxretries=None, **kwargs): http.mount("https://", HTTPAdapter(max_retries=retry_strategy)) with http as session: api_result = session.request(method, url, **kwargs) - response = { - "status": api_result.status_code, - "response": api_result.json() - } + response = {"status": api_result.status_code, "response": None} - return response + if api_result.status_code != 204 and api_result.content: + try: + response["response"] = api_result.json() + except ValueError as e: + print(f"Failed to parse JSON: {str(e)}") + response["response"] = None + + return response def get(self, url, headers, params=None, timeout=None, maxretries=None): # pylint: disable=too-many-arguments + # pylint: disable=too-many-positional-arguments """Makes a GET request to the API""" return self.request( "GET", @@ -43,6 +49,7 @@ def get(self, url, headers, params=None, timeout=None, maxretries=None): # pyli ) def post(self, url, headers, data=None, params=None, timeout=None, maxretries=None): # pylint: disable=too-many-arguments + # pylint: disable=too-many-positional-arguments """Makes a POST request to the API""" return self.request( "POST", @@ -55,6 +62,7 @@ def post(self, url, headers, data=None, params=None, timeout=None, maxretries=No ) def put(self, url, headers, data=None, params=None, timeout=None, maxretries=None): # pylint: disable=too-many-arguments + # pylint: disable=too-many-positional-arguments """Makes a PUT request to the API""" return self.request( "PUT", @@ -67,6 +75,7 @@ def put(self, url, headers, data=None, params=None, timeout=None, maxretries=Non ) def delete(self, url, headers, params=None, timeout=None, maxretries=None): # pylint: disable=too-many-arguments + # pylint: disable=too-many-positional-arguments """Makes a DELETE request to the API""" return self.request( "DELETE", diff --git a/mercadopago/resources/__init__.py b/mercadopago/resources/__init__.py index ac555f8..407b471 100644 --- a/mercadopago/resources/__init__.py +++ b/mercadopago/resources/__init__.py @@ -11,6 +11,7 @@ from mercadopago.resources.disbursement_refund import DisbursementRefund from mercadopago.resources.identification_type import IdentificationType from mercadopago.resources.merchant_order import MerchantOrder +from mercadopago.resources.order import Order from mercadopago.resources.payment import Payment from mercadopago.resources.payment_methods import PaymentMethods from mercadopago.resources.plan import Plan @@ -31,6 +32,7 @@ 'HttpClient', 'IdentificationType', 'MerchantOrder', + 'Order', 'Payment', 'PaymentMethods', 'Plan', diff --git a/mercadopago/resources/order.py b/mercadopago/resources/order.py new file mode 100644 index 0000000..3386970 --- /dev/null +++ b/mercadopago/resources/order.py @@ -0,0 +1,207 @@ +""" + Module: order +""" +from mercadopago.core import MPBase + +class Order(MPBase): + """ + This class provides the methods to access the API that will allow you to create + your own order experience on your website. + + From basic to advanced configurations, you control the whole experience. + + [Click here for more info](https://www.mercadopago.com/developers/en/guides/online-payments/checkout-api/introduction/) # pylint: disable=line-too-long + """ + + def create(self, order_object, request_options=None): + """[Click here for more info](https://www.mercadopago.com/developers/en/reference/order/online-payments/create/post/) # pylint: disable=line-too-long + + Args: + order_object (dict): Order to be created + request_options (mercadopago.config.request_options, optional): An instance of + RequestOptions can be pass changing or adding custom options to the REST call. + Defaults to None. + + Raises: + ValueError: Param order_object must be a Dictionary + + Returns: + dict: Order creation response + """ + if not isinstance(order_object, dict): + raise ValueError("Param order_object must be a Dictionary") + + return self._post(uri="/v1/orders", data=order_object, request_options=request_options) + + def get(self, order_id, request_options=None): + """[Click here for more info](https://www.mercadopago.com/developers/en/reference/order/online-payments/get-order/get ) # pylint: disable=line-too-long + + Args: + order_id (str): The Order ID + request_options (mercadopago.config.request_options, optional): An instance of + RequestOptions can be pass changing or adding custom options to the REST call. + Defaults to None. + + Raises: + ValueError: Param order_id must be a string + + Returns: + dict: Order returned in the response to the request made for its creation. + """ + + if not isinstance(order_id, str): + raise ValueError("Param order_id must be a string") + + return self._get(uri="/v1/orders/" + str(order_id), request_options=request_options) + + def process(self, order_id, request_options=None): + """[Click here for more info](https://www.mercadopago.com/developers/en/reference/order/online/process-order/post) # pylint: disable=line-too-long + Args: + order_id (str): ID of the order to be processed. This value is returned in the response to the Create order request. + request_options (mercadopago.config.request_options, optional): An instance of + RequestOptions can be pass changing or adding custom options to the REST call. + Defaults to None. + + Raises: + ValueError: Param order_id must be a string + Returns: + dict: Order returned in the response to the request made for its creation. + """ + + if not isinstance(order_id, str): + raise ValueError("Param order_id must be a string") + + return self._post(uri="/v1/orders/" + str(order_id) + "/process", request_options=request_options) # pylint: disable=line-too-long + + def cancel(self, order_id, request_options=None): + """[Click here for more info](https://www.mercadopago.com/developers/en/reference/order/online-payments/cancel-order/post) # pylint: disable=line-too-long + Args: + order_id (str): Order ID + request_options (mercadopago.config.request_options, optional): An instance of + RequestOptions can be pass changing or adding custom options to the REST call. + Defaults to None. + + Raises: + ValueError: Param order_id must be a string + + Returns: + dict: Order cancellation response + """ + if not isinstance(order_id, str): + raise ValueError("Param order_id must be a string") + + return self._post(uri="/v1/orders/" + str(order_id) + "/cancel", request_options=request_options) # pylint: disable=line-too-long + + def capture(self, order_id, request_options=None): + """[Click here for more info](https://www.mercadopago.com/developers/en/reference/order/online-payments/capture/post) # pylint: disable=line-too-long + Args: + order_id (str): ID of the order to be captured. This value is returned in the response to the Create order request. + request_options (mercadopago.config.request_options, optional): An instance of + RequestOptions can be pass changing or adding custom options to the REST call. + Defaults to None. + Raises: + ValueError: Param order_id must be a string + Returns: + dict: Order returned in the response to the request made for its creation. + """ + + if not isinstance(order_id, str): + raise ValueError("Param order_id must be a string") + + return self._post(uri="/v1/orders/" + str(order_id) + "/capture", request_options=request_options) # pylint: disable=line-too-long + + def create_transaction(self, order_id, transaction_object, request_options=None): + """[Click here for more info](https://www.mercadopago.com/developers/en/reference/order/online-payments/add-transaction/post) # pylint: disable=line-too-long + + Args: + order_id (str): The ID of the order to which the transaction will be added + transaction_object (dict): Transaction to be added + request_options (mercadopago.config.request_options, optional): An instance of + RequestOptions can be pass changing or adding custom options to the REST call. + Defaults to None. + + Raises: + ValueError: Param transaction_object must be a Dictionary + + Returns: + dict: Transaction created response + """ + if not isinstance(transaction_object, dict): + raise ValueError("Param transaction_object must be a Dictionary") + + response = self._post(uri=f"/v1/orders/{order_id}/transactions", data=transaction_object, + request_options=request_options) + if response.get("status") != 201: + raise Exception(f"Failed to add transaction: {response}") # pylint: disable=broad-exception-raised + return response + + def update_transaction(self, order_id, transaction_id, transaction_object, request_options=None): # pylint: disable=line-too-long + """[Click here for more info](https://www.mercadopago.com/developers/en/reference/order/online-payments/update-transaction/put) # pylint: disable=line-too-long + + Args: + order_id (str): The ID of the order to which the transaction belongs + transaction_id (str): The ID of the transaction to be updated + transaction_object (dict): Transaction details to be updated + request_options (mercadopago.config.request_options, optional): An instance of + RequestOptions can be pass changing or adding custom options to the REST call. + Defaults to None. + + Raises: + ValueError: Param transaction_object must be a Dictionary + + Returns: + dict: Transaction update response + """ + if not isinstance(transaction_object, dict): + raise ValueError("Param transaction_object must be a Dictionary") + + response = self._put(uri=f"/v1/orders/{order_id}/transactions/{transaction_id}", + data=transaction_object, request_options=request_options) + if response.get("status") != 200: + raise Exception(f"Failed to update transaction: {response}") # pylint: disable=broad-exception-raised + return response + + def refund_transaction(self, order_id, transaction_object=None, request_options=None): + """[Click here for more info](https://www.mercadopago.com/developers/en/reference/order/online-payments/refund/post) # pylint: disable=line-too-long + Args: + order_id (str): The ID of the order to which the transaction belongs + transaction_object (dict, optional): Transaction details to be updated + request_options (mercadopago.config.request_options, optional): An instance of + RequestOptions can be pass changing or adding custom options to the REST call. + Defaults to None. + Raises: + ValueError: Param transaction_object must be a Dictionary + Returns: + dict: Order refunded response + """ + if transaction_object is not None and not isinstance(transaction_object, dict): + raise ValueError("Param transaction_object must be a Dictionary") + + response = self._post(uri=f"/v1/orders/{order_id}/refund", data=transaction_object, + request_options=request_options) + if response.get("status") != 201: + raise Exception(f"Failed to refund transaction: {response}") # pylint: disable=broad-exception-raised + return response + + def delete_transaction(self, order_id, transaction_id, request_options=None): + """[Click here for more info](https://www.mercadopago.com/developers/en/reference/order/online-payments/delete-transaction/delete) # pylint: disable=line-too-long + Args: + order_id (str): The ID of the order to which the transaction belongs + transaction_id (str): The ID of the transaction to be deleted + request_options (mercadopago.config.request_options, optional): An instance of + RequestOptions can be pass changing or adding custom options to the REST call. + Defaults to None. + Raises: + ValueError: Params order_id and transaction_id must be strings + Returns: + Status 204 - No Content - if deleted successfully + """ + if not isinstance(order_id, str) or not isinstance(transaction_id, str): + raise ValueError("Params order_id and transaction_id must be strings") + + response = self._delete(uri=f"/v1/orders/{order_id}/transactions/{transaction_id}", + request_options=request_options) + + if response.get("status") != 204: + raise Exception(f"Failed to delete transaction: {response}") # pylint: disable=broad-exception-raised + return response diff --git a/mercadopago/sdk.py b/mercadopago/sdk.py index 0536b58..6129307 100644 --- a/mercadopago/sdk.py +++ b/mercadopago/sdk.py @@ -12,6 +12,7 @@ DisbursementRefund, IdentificationType, MerchantOrder, + Order, Payment, PaymentMethods, Plan, @@ -33,15 +34,16 @@ class SDK: 5. Disbursement Refund 6. Identification Type 7. Merchant Order - 8. Payment Methods - 9. Payment - 10. Preapproval - 11. Preference - 12. Refund - 13. User - 14. Chargeback - 15. Subscription - 16. Plan + 8. Order + 9. Payment Methods + 10. Payment + 11. Preapproval + 12. Preference + 13. Refund + 14. User + 15. Chargeback + 16. Subscription + 17. Plan """ def __init__( @@ -121,6 +123,13 @@ def merchant_order(self, request_options=None): return MerchantOrder(request_options is not None and request_options or self.request_options, self.http_client) + def order(self, request_options=None): + """ + Returns the attribute value of the function + """ + return Order(request_options is not None and request_options + or self.request_options, self.http_client) + def payment(self, request_options=None): """ Returns the attribute value of the function diff --git a/pyproject.toml b/pyproject.toml index 350ee6f..7b0252a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "mercadopago" -version = "2.2.3" +version = "2.3.0" authors = [ {name = "Mercado Pago", email = "mp_sdk@mercadopago.com"} ] diff --git a/tests/test_merchant_order.py b/tests/test_merchant_order.py index 8142add..67229a3 100644 --- a/tests/test_merchant_order.py +++ b/tests/test_merchant_order.py @@ -25,14 +25,13 @@ def test_all(self): "picture_url": "http://product1.image.png", "quantity": 1, "title": "Item 1", - "currency_id": "R$", + "currency_id": "BRL", "unit_price": 20.5 } ] } preference_saved = self.sdk.preference().create(preference_object) - merchant_order_object = { "preference_id": preference_saved["response"]["id"], "site_id": "MLB", diff --git a/tests/test_order.py b/tests/test_order.py new file mode 100644 index 0000000..0b4c0d5 --- /dev/null +++ b/tests/test_order.py @@ -0,0 +1,305 @@ +""" + Module: test_order +""" +import os +import time +import unittest +import random +from time import sleep + +import mercadopago + + +class TestOrder(unittest.TestCase): + """ + Test Module: Order + """ + sdk = mercadopago.SDK(os.environ['ACCESS_TOKEN']) + + def create_test_card(self, status="APRO"): + card_token_object = { + "card_number": "5031433215406351", + "security_code": "123", + "expiration_year": "2030", + "expiration_month": "11", + "cardholder": {"name": status} + } + card_token_created = self.sdk.card_token().create(card_token_object) + return card_token_created["response"]["id"] + + def create_order_canceled_or_captured(self, card_token_id): + random_email_id = random.randint(100000, 999999) + order_object_cc = { + "type": "online", + "processing_mode": "automatic", + "total_amount": "200.00", + "external_reference": "ext_ref_1234", + "payer": { + "email": f"test_payer_{random_email_id}@testuser.com" + }, + "capture_mode": "manual", + "transactions": { + "payments": [ + { + "amount": "200.00", + "payment_method": { + "id": "master", + "type": "credit_card", + "token": card_token_id, + "installments": 1 + } + } + ] + } + } + order_created = self.sdk.order().create(order_object_cc) + if order_created.get("status") != 201 or not order_created.get("response"): + self.fail(f"Failed to create order: {order_created}") + return order_created["response"]["id"] + + def create_order_builder_mode(self): + random_email_id = random.randint(100000, 999999) + order_object_cc = { + "type": "online", + "processing_mode": "manual", + "total_amount": "200.00", + "external_reference": "ext_ref_1234", + "payer": { + "email": f"test_payer_{random_email_id}@testuser.com" + }, + } + order_created = self.sdk.order().create(order_object_cc) + if order_created.get("status") != 201 or not order_created.get("response"): + self.fail(f"Failed to create order: {order_created}") + return order_created["response"]["id"] + + def create_order_oneshot_mode_complete(self, card_token_id): + random_email_id = random.randint(100000, 999999) + order_mode_oneshot_complete = { + "type": "online", + "processing_mode": "automatic", + "total_amount": "200.00", + "external_reference": "ext_ref_1234", + "transactions": { + "payments": [ + { + "amount": "200.00", + "payment_method": { + "id": "master", + "type": "credit_card", + "token": card_token_id, + "installments": 1 + } + } + ] + }, + "payer": { + "email": f"test_payer_{random_email_id}@testuser.com" + } + } + + order_created = self.sdk.order().create(order_mode_oneshot_complete) + + + if order_created.get("status") != 201 or not order_created.get("response"): + self.fail(f"Failed to create order: {order_created}") + return order_created["response"] + + def create_order_builder_mode_complete(self, card_token_id): + random_email_id = random.randint(100000, 999999) + order_mode_builder_complete = { + "type": "online", + "processing_mode": "manual", + "total_amount": "200.00", + "external_reference": "ext_ref_1234", + "transactions": { + "payments": [ + { + "amount": "200.00", + "payment_method": { + "id": "master", + "type": "credit_card", + "token": card_token_id, + "installments": 12 + } + } + ] + }, + "payer": { + "email": f"test_payer_{random_email_id}@testuser.com" + } + } + + order_created = self.sdk.order().create(order_mode_builder_complete) + + + if order_created.get("status") != 201 or not order_created.get("response"): + self.fail(f"Failed to create order: {order_created}") + return order_created["response"] + + def test_create_order_and_get_by_id(self): + """ + Test Function: Create an Order and Get an Order by ID + """ + card_token_id = self.create_test_card() + random_email_id = random.randint(100000, 999999) + order_object = { + "type": "online", + "total_amount": "1000.00", + "external_reference": "ext_ref_1234", + "transactions": { + "payments": [ + { + "amount": "1000.00", + "payment_method": { + "id": "master", + "type": "credit_card", + "token": card_token_id, + "installments": 12 + } + } + ] + }, + "payer": { + "email": f"test_payer_{random_email_id}@testuser.com" + } + } + + order_created = self.sdk.order().create(order_object) + self.assertEqual(order_created["status"], 201) + self.assertEqual(order_created["response"]["status"], "processed") + + order_get = self.sdk.order().get(order_created["response"]["id"]) + self.assertEqual(order_get["status"], 200) + + def test_process_order(self): + card_token_id = self.create_test_card() + random_email_id = random.randint(100000, 999999) + order_object = { + "type": "online", + "processing_mode": "manual", + "external_reference": "ext_ref_1234", + "total_amount": "200.00", + "transactions": { + "payments": [ + { + "amount": "200.00", + "payment_method": { + "id": "master", + "type": "credit_card", + "token": card_token_id, + "installments": 1 + } + } + ] + }, + "payer": { + "email": f"test_payer_{random_email_id}@testuser.com" + } + } + + order_created = self.sdk.order().create(order_object) + order_id = order_created["response"]["id"] + process_response = self.sdk.order().process(order_id) + if process_response.get("status") != 200 or not process_response.get("response"): + self.fail(f"Failed to create an order: {process_response}") + self.assertEqual(process_response["status"], 200, + "Invalid HTTP status when processing the order") + + def test_cancel_order(self): + card_token_id = self.create_test_card() + order_id = self.create_order_canceled_or_captured(card_token_id) + time.sleep(4) + order_canceled = self.sdk.order().cancel(order_id) + self.assertEqual(order_canceled["status"], 200) + self.assertEqual(order_canceled["response"]["status"], "canceled") + + def test_capture_order(self): + card_token_id = self.create_test_card() + order_id = self.create_order_canceled_or_captured(card_token_id) + order_captured = self.sdk.order().capture(order_id) + self.assertEqual(order_captured["status"], 200) + self.assertEqual(order_captured["response"]["status"], "processed") + + def test_create_transaction(self): + card_token_id = self.create_test_card() + order_id = self.create_order_builder_mode() + transaction_object = { + "payments": [ + { + "amount": "200.00", + "payment_method": { + "id": "master", + "type": "credit_card", + "token": card_token_id, + "installments": 12 + } + } + ] + } + + transaction_created = self.sdk.order().create_transaction(order_id, transaction_object) + self.assertEqual(transaction_created["status"], 201) + + def test_update_transaction(self): + card_token_id = self.create_test_card() + order_created = self.create_order_builder_mode_complete(card_token_id) + order_id = order_created["id"] + transaction_id = order_created["transactions"]["payments"][0]["id"] + + transaction_update = { + "payment_method": { + "type": "credit_card", + "installments": 5 + } + } + + transaction_updated = self.sdk.order().update_transaction(order_id, transaction_id, + transaction_update) + self.assertEqual(transaction_updated["status"], 200) + + def test_partial_refund_transaction(self): + card_token_id = self.create_test_card() + order_created = self.create_order_oneshot_mode_complete(card_token_id) + order_id = order_created["id"] + transaction_id = order_created["transactions"]["payments"][0]["id"] + + transaction_refund = { + "transactions": [ + { + "id": transaction_id, + "amount": "25.00" + } + ] + } + + sleep(3) + + transaction_refunded = self.sdk.order().refund_transaction(order_id, transaction_refund) + self.assertIn(transaction_refunded["status"], [ 201], + f"Unexpected status code for refund: {transaction_refunded['status']}." + " Response: {transaction_refunded}") + + def test_refund_transaction(self): + card_token_id = self.create_test_card() + order_created = self.create_order_oneshot_mode_complete(card_token_id) + order_id = order_created["id"] + sleep(3) + transaction_refunded = self.sdk.order().refund_transaction(order_id) + self.assertIn(transaction_refunded["status"], [ 201], + f"Unexpected status code for refund: {transaction_refunded['status']}." + " Response: {transaction_refunded}") + + def test_delete_transaction(self): + card_token_id = self.create_test_card() + order_created = self.create_order_builder_mode_complete(card_token_id) + order_id = order_created["id"] + transaction_id = order_created["transactions"]["payments"][0]["id"] + sleep(3) + + transaction_deleted = self.sdk.order().delete_transaction(order_id, transaction_id) + self.assertEqual(transaction_deleted["status"], 204) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_plan.py b/tests/test_plan.py index 28d018b..6e3081d 100644 --- a/tests/test_plan.py +++ b/tests/test_plan.py @@ -30,7 +30,7 @@ def test_all(self): "frequency_type": "days" }, "transaction_amount": 60, - "currency_id": "BRL", + "currency_id": "ARS", }, "back_url": "https://www.mercadopago.com.co/subscriptions", "reason": f"Test Plan #{random_reason_number}", @@ -40,7 +40,7 @@ def test_all(self): "frequency": 1, "frequency_type": "months", "transaction_amount": 60, - "currency_id": "BRL", + "currency_id": "ARS", }, "back_url": "https://www.mercadopago.com.co/subscriptions", "reason": f"Test Plan (mandatory) #{random_reason_number}", diff --git a/tests/test_preference.py b/tests/test_preference.py index 862c1fa..c4d1662 100644 --- a/tests/test_preference.py +++ b/tests/test_preference.py @@ -32,7 +32,7 @@ def test_all(self): } preference_saved = self.sdk.preference().create(preference_object) self.assertEqual(preference_saved["status"], 201) - time.sleep(1) + time.sleep(3) preference_object["items"][0]["title"] = "Testando 1 2 3" @@ -40,7 +40,7 @@ def test_all(self): preference_update = self.sdk.preference().update(preference_id, preference_object) self.assertEqual(preference_update["status"], 200) - time.sleep(1) + time.sleep(3) preference_saved = self.sdk.preference().get(preference_id) self.assertEqual(preference_saved["status"], 200) self.assertEqual(preference_saved["response"]["items"][0]["title"], diff --git a/tests/test_subscription.py b/tests/test_subscription.py index c001f29..f72a0fb 100644 --- a/tests/test_subscription.py +++ b/tests/test_subscription.py @@ -44,7 +44,13 @@ def test_all(self): "payer_email": self._customer_email, "preapproval_plan_id": self._plan_id, "card_token_id": card_token_id, - "status": "authorized" + "status": "authorized", + "auto_recurring": { + "frequency": 1, + "frequency_type": "months", + "transaction_amount": 60, + "currency_id": "ARS" + } } subscription_response = self.sdk.subscription().create(subscription_payload) @@ -96,7 +102,7 @@ def test_create_subscriptions_without_a_plan(self): "frequency": 1, "frequency_type": "months", "transaction_amount": 60, - "currency_id": "BRL", + "currency_id": "ARS", }, "status": "authorized" } @@ -157,7 +163,7 @@ def create_plan(cls): "frequency": 1, "frequency_type": "months", "transaction_amount": 60, - "currency_id": "BRL", + "currency_id": "ARS", }, "back_url": "https://www.mercadopago.com.co/subscriptions", "reason": f"Test Plan #{random.randint(100000, 999999)}",