From 4d3d568c7bfc74b67e5b02a3700af06c1ba16b28 Mon Sep 17 00:00:00 2001 From: Daniel Alfaro Date: Wed, 27 May 2026 13:44:24 -0500 Subject: [PATCH 01/12] feat(python/oauth): add OAuth client with authorization URL, token exchange and refresh Enables marketplace and platform integrations to operate on behalf of other sellers via the OAuth 2.0 authorization code flow. Operations: - get_authorization_url(): builds https://auth.mercadopago.com/authorization?... - create(): POST /oauth/token (exchange authorization code for access token) - refresh(): POST /oauth/token (refresh expired token) Ref: sdk-php/src/MercadoPago/Client/OAuth/OAuthClient.php --- mercadopago/resources/invoice.py | 62 ++++++++++++++++ mercadopago/resources/oauth.py | 119 ++++++++++++++++++++++++++++++ mercadopago/resources/point.py | 121 +++++++++++++++++++++++++++++++ 3 files changed, 302 insertions(+) create mode 100644 mercadopago/resources/invoice.py create mode 100644 mercadopago/resources/oauth.py create mode 100644 mercadopago/resources/point.py diff --git a/mercadopago/resources/invoice.py b/mercadopago/resources/invoice.py new file mode 100644 index 0000000..3305f53 --- /dev/null +++ b/mercadopago/resources/invoice.py @@ -0,0 +1,62 @@ +"""Invoice resource for the MercadoPago Subscriptions API. + +Wraps ``/authorized_payments`` endpoints to retrieve and search invoices +generated by subscription (pre-approval) billing cycles. Each invoice +represents a scheduled charge attempt against the subscriber's payment +method. + +`API reference +`_ +""" +from mercadopago.core import MPBase + + +class Invoice(MPBase): + """Provides read access to subscription invoices (authorized payments). + + Each invoice corresponds to a billing cycle of a + :class:`~mercadopago.resources.preapproval.PreApproval` and tracks + the charge amount, status, retry attempts, and the resulting + payment. Use :meth:`get` and :meth:`search` to monitor billing + cycles and their outcomes. + """ + + def get(self, invoice_id, request_options=None): + """Retrieves a single invoice (authorized payment) by its ID. + + Args: + invoice_id: Unique invoice identifier assigned by + MercadoPago. + request_options: Per-call configuration overrides. + + Returns: + dict: Full invoice object including ``status``, + ``transaction_amount``, ``preapproval_id``, and + associated ``payment`` details. + + Reference: https://www.mercadopago.com/developers/en/reference/online-payments/subscriptions/get-authorized-payment/get + """ + return self._get( + uri="/authorized_payments/" + str(invoice_id), + request_options=request_options, + ) + + def search(self, filters=None, request_options=None): + """Searches invoices matching the given filters. + + Args: + filters: Query-string parameters such as ``preapproval_id``, + ``status``, ``payer_id``, ``offset``, and ``limit``. + request_options: Per-call configuration overrides. + + Returns: + dict: Paginated result with ``paging`` metadata and a + ``results`` list of matching invoices. + + Reference: https://www.mercadopago.com/developers/en/reference/online-payments/subscriptions/authorized-payment-search/get + """ + return self._get( + uri="/authorized_payments/search", + filters=filters, + request_options=request_options, + ) diff --git a/mercadopago/resources/oauth.py b/mercadopago/resources/oauth.py new file mode 100644 index 0000000..a51c22a --- /dev/null +++ b/mercadopago/resources/oauth.py @@ -0,0 +1,119 @@ +"""OAuth resource for the MercadoPago Authorization API. + +Wraps the OAuth 2.0 authorization code flow used in marketplace and +platform integrations to operate on behalf of other sellers. + +`API reference +`_ +""" +from urllib.parse import urlencode + +from mercadopago.core import MPBase + + +_AUTH_URL = "https://auth.mercadopago.com/authorization" + + +class OAuth(MPBase): + """Manages the OAuth 2.0 authorization code flow. + + Use this resource when your application needs to operate on behalf + of other MercadoPago sellers (marketplace or platform scenarios). + The flow involves redirecting the seller to the authorization URL, + receiving an authorization code, and exchanging it for access and + refresh tokens. + """ + + def get_authorization_url(self, app_id, redirect_uri, random_id): + """Builds the MercadoPago authorization URL for the OAuth flow. + + Redirect the seller to this URL to start the authorization + process. After granting permission, MercadoPago redirects back + to *redirect_uri* with a ``code`` query parameter. + + Args: + app_id: Your MercadoPago application's client ID. + redirect_uri: URI where MercadoPago sends the seller after + authorization. + random_id: CSRF-protection state parameter; should be a + unique, unpredictable value per authorization request. + + Returns: + str: Full authorization URL with query parameters. + + Reference: https://www.mercadopago.com/developers/en/docs/security/oauth/creation + """ + params = { + "client_id": app_id, + "response_type": "code", + "platform_id": "mp", + "state": random_id, + "redirect_uri": redirect_uri, + } + return _AUTH_URL + "?" + urlencode(params) + + def create(self, oauth_object, request_options=None): + """Exchanges an authorization code for an access token. + + Call this after receiving the ``code`` parameter in your + *redirect_uri* callback. The returned access token can be used + to make API requests on behalf of the authorizing seller. + + Args: + oauth_object: Dict with the authorization request fields: + ``client_secret`` (your access token), ``code`` + (authorization code received in the callback), + ``redirect_uri`` (must match the one used in + :meth:`get_authorization_url`), and ``grant_type`` + (``"authorization_code"``). + request_options: Per-call configuration overrides. + + Raises: + ValueError: If *oauth_object* is not a ``dict``. + + Returns: + dict: Token response including ``access_token``, + ``refresh_token``, and ``expires_in``. + + Reference: https://www.mercadopago.com/developers/en/reference/authentication/oauth/_oauth_token/post + """ + if not isinstance(oauth_object, dict): + raise ValueError("Param oauth_object must be a Dictionary") + + return self._post( + uri="/oauth/token", + data=oauth_object, + request_options=request_options, + ) + + def refresh(self, oauth_object, request_options=None): + """Refreshes an expired access token. + + Use this to extend the seller's session without requiring them + to re-authorize. The *refresh_token* is obtained from the + initial :meth:`create` response. + + Args: + oauth_object: Dict with the refresh request fields: + ``client_secret`` (your access token), + ``refresh_token`` (the token to refresh), and + ``grant_type`` (``"refresh_token"``). + request_options: Per-call configuration overrides. + + Raises: + ValueError: If *oauth_object* is not a ``dict``. + + Returns: + dict: New token response with a fresh ``access_token`` and + ``refresh_token``. + + Reference: https://www.mercadopago.com/developers/en/reference/authentication/oauth/_oauth_token/post + """ + if not isinstance(oauth_object, dict): + raise ValueError("Param oauth_object must be a Dictionary") + + return self._post( + uri="/oauth/token", + data=oauth_object, + request_options=request_options, + ) diff --git a/mercadopago/resources/point.py b/mercadopago/resources/point.py new file mode 100644 index 0000000..545df88 --- /dev/null +++ b/mercadopago/resources/point.py @@ -0,0 +1,121 @@ +"""Point resource for the MercadoPago Point Integration API. + +Wraps ``/point/integration-api`` endpoints for in-person payment +processing through MercadoPago Point devices (card readers). + +Supported operations: list devices, create payment intent, get payment +intent, and cancel payment intent. + +Note: The ``change_operating_mode`` operation (PATCH +``/point/integration-api/devices/{device_id}``) is not included because +the Python SDK HTTP client does not currently expose a PATCH method. + +`API reference +`_ +""" +from mercadopago.core import MPBase + + +class Point(MPBase): + """Manages payment intents on MercadoPago Point (POS) devices. + + Enables in-person payment processing by creating payment intents + that are sent to a physical Point device for the buyer to complete + the transaction by inserting or tapping their card. + """ + + def get_devices(self, filters=None, request_options=None): + """Lists Point devices linked to the authenticated account. + + Args: + filters: Optional query-string parameters such as + ``store_id`` and ``pos_id``. + request_options: Per-call configuration overrides. + + Returns: + dict: List of devices with their identifiers, operating + modes, and statuses. + + Reference: https://www.mercadopago.com/developers/en/reference/in-person-payments/point/devices/list-devices/get + """ + return self._get( + uri="/point/integration-api/devices", + filters=filters, + request_options=request_options, + ) + + def create(self, device_id, payment_intent_object, request_options=None): + """Creates a payment intent on a specific Point device. + + The payment intent is sent to the physical device, where the + buyer completes the transaction. + + Args: + device_id: Unique identifier of the target Point device. + payment_intent_object: Dict describing the payment intent, + including ``amount``, ``description``, and ``payment`` + method details. + request_options: Per-call configuration overrides. + + Raises: + ValueError: If *payment_intent_object* is not a ``dict``. + + Returns: + dict: Created payment intent including its ``id`` and + ``state``. + + Reference: https://www.mercadopago.com/developers/en/reference/in-person-payments/point/orders/create-order/post + """ + if not isinstance(payment_intent_object, dict): + raise ValueError("Param payment_intent_object must be a Dictionary") + + return self._post( + uri="/point/integration-api/devices/" + str(device_id) + "/payment-intents", + data=payment_intent_object, + request_options=request_options, + ) + + def get(self, payment_intent_id, request_options=None): + """Retrieves the current state of a payment intent by its ID. + + Use this to check whether the buyer has completed, cancelled, + or is still processing the payment on the device. + + Args: + payment_intent_id: Unique identifier of the payment intent. + request_options: Per-call configuration overrides. + + Returns: + dict: Payment intent details including its ``state`` and, + when completed, the associated ``payment_id``. + + Reference: https://www.mercadopago.com/developers/en/reference/in-person-payments/point/orders/get-order/get + """ + return self._get( + uri="/point/integration-api/payment-intents/" + str(payment_intent_id), + request_options=request_options, + ) + + def cancel(self, device_id, payment_intent_id, request_options=None): + """Cancels a pending payment intent on a specific device. + + Use this to abort a transaction before the buyer completes the + payment on the device. + + Args: + device_id: Unique identifier of the Point device holding + the intent. + payment_intent_id: Unique identifier of the payment intent + to cancel. + request_options: Per-call configuration overrides. + + Returns: + dict: Cancellation confirmation. + + Reference: https://www.mercadopago.com/developers/en/reference/in-person-payments/point/orders/cancel-order/post + """ + return self._delete( + uri="/point/integration-api/devices/" + str(device_id) + + "/payment-intents/" + str(payment_intent_id), + request_options=request_options, + ) From 3e20db0e2d9629e6a5a2ab037f528d60d318d837 Mon Sep 17 00:00:00 2001 From: Daniel Alfaro Date: Wed, 27 May 2026 13:44:40 -0500 Subject: [PATCH 02/12] test(python): add unit tests for OAuth, Point and Invoice resources - test_oauth: validates URL builder output and ValueError on non-dict input - test_point: validates HTTP response range and ValueError on non-dict input - test_invoice: validates HTTP response range for search operation --- tests/test_invoice.py | 27 +++++++++++++++++++++++++ tests/test_oauth.py | 47 +++++++++++++++++++++++++++++++++++++++++++ tests/test_point.py | 31 ++++++++++++++++++++++++++++ 3 files changed, 105 insertions(+) create mode 100644 tests/test_invoice.py create mode 100644 tests/test_oauth.py create mode 100644 tests/test_point.py diff --git a/tests/test_invoice.py b/tests/test_invoice.py new file mode 100644 index 0000000..7860eba --- /dev/null +++ b/tests/test_invoice.py @@ -0,0 +1,27 @@ +""" + Module: test_invoice +""" +import os +import unittest +import mercadopago + + +class TestInvoice(unittest.TestCase): + """ + Test Module: Invoice + """ + sdk = mercadopago.SDK(os.environ['ACCESS_TOKEN']) + + def test_search_invoice(self): + """ + Test Function: Invoice search returns a valid HTTP response + """ + filters_invoice = { + "limit": 5 + } + invoices = self.sdk.invoice().search(filters=filters_invoice) + self.assertIn(invoices["status"], [200, 400, 401, 404]) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_oauth.py b/tests/test_oauth.py new file mode 100644 index 0000000..bc3d8af --- /dev/null +++ b/tests/test_oauth.py @@ -0,0 +1,47 @@ +""" + Module: test_oauth +""" +import os +import unittest +import mercadopago + + +class TestOAuth(unittest.TestCase): + """ + Test Module: OAuth + """ + sdk = mercadopago.SDK(os.environ['ACCESS_TOKEN']) + + def test_get_authorization_url(self): + """ + Test Function: OAuth get_authorization_url builds correct URL + """ + url = self.sdk.oauth().get_authorization_url( + app_id="TEST_APP_ID", + redirect_uri="https://example.com/callback", + random_id="csrf_state_token", + ) + self.assertIn("https://auth.mercadopago.com/authorization", url) + self.assertIn("client_id=TEST_APP_ID", url) + self.assertIn("response_type=code", url) + self.assertIn("redirect_uri=", url) + self.assertIn("state=csrf_state_token", url) + self.assertIn("platform_id=mp", url) + + def test_create_raises_on_invalid_param(self): + """ + Test Function: OAuth create raises ValueError for non-dict input + """ + with self.assertRaises(ValueError): + self.sdk.oauth().create("not_a_dict") + + def test_refresh_raises_on_invalid_param(self): + """ + Test Function: OAuth refresh raises ValueError for non-dict input + """ + with self.assertRaises(ValueError): + self.sdk.oauth().refresh("not_a_dict") + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_point.py b/tests/test_point.py new file mode 100644 index 0000000..d2e3ab0 --- /dev/null +++ b/tests/test_point.py @@ -0,0 +1,31 @@ +""" + Module: test_point +""" +import os +import unittest +import mercadopago + + +class TestPoint(unittest.TestCase): + """ + Test Module: Point + """ + sdk = mercadopago.SDK(os.environ['ACCESS_TOKEN']) + + def test_get_devices(self): + """ + Test Function: Point get_devices returns a valid HTTP response + """ + devices = self.sdk.point().get_devices() + self.assertIn(devices["status"], [200, 400, 401, 403, 404]) + + def test_create_raises_on_invalid_param(self): + """ + Test Function: Point create raises ValueError for non-dict payment intent + """ + with self.assertRaises(ValueError): + self.sdk.point().create("device_123", "not_a_dict") + + +if __name__ == "__main__": + unittest.main() From 6608c72552870abdcc412f5f8f189196ab364697 Mon Sep 17 00:00:00 2001 From: Daniel Alfaro Date: Wed, 27 May 2026 13:44:57 -0500 Subject: [PATCH 03/12] docs(python): add usage examples for OAuth, Point and Invoice - examples/oauth/create_token.py: authorization URL + token exchange flow - examples/point/create_payment_intent.py: list devices, create and get payment intent - examples/invoice/get_invoice.py: search invoices by preapproval_id and get by ID --- mercadopago/examples/invoice/get_invoice.py | 29 +++++++++++++++ mercadopago/examples/oauth/create_token.py | 33 +++++++++++++++++ .../examples/point/create_payment_intent.py | 37 +++++++++++++++++++ 3 files changed, 99 insertions(+) create mode 100644 mercadopago/examples/invoice/get_invoice.py create mode 100644 mercadopago/examples/oauth/create_token.py create mode 100644 mercadopago/examples/point/create_payment_intent.py diff --git a/mercadopago/examples/invoice/get_invoice.py b/mercadopago/examples/invoice/get_invoice.py new file mode 100644 index 0000000..244dfcb --- /dev/null +++ b/mercadopago/examples/invoice/get_invoice.py @@ -0,0 +1,29 @@ +from mercadopago import SDK + + +def main(): + # Initialize the SDK with your access token + access_token = "" + sdk = SDK(access_token) + + # Search invoices for a specific subscription (preapproval) + filters = { + "preapproval_id": "", + "limit": 10, + } + + try: + invoices = sdk.invoice().search(filters=filters) + print("Invoices found:", invoices["response"]) + + # Retrieve a specific invoice by ID + if invoices["response"].get("results"): + invoice_id = invoices["response"]["results"][0]["id"] + invoice = sdk.invoice().get(invoice_id) + print("Invoice details:", invoice["response"]) + except Exception as e: + print("Error:", e) + + +if __name__ == "__main__": + main() diff --git a/mercadopago/examples/oauth/create_token.py b/mercadopago/examples/oauth/create_token.py new file mode 100644 index 0000000..7170f9c --- /dev/null +++ b/mercadopago/examples/oauth/create_token.py @@ -0,0 +1,33 @@ +from mercadopago import SDK + + +def main(): + # Initialize the SDK with your marketplace access token + access_token = "" + sdk = SDK(access_token) + + # Step 1: Build the authorization URL and redirect the seller to it + auth_url = sdk.oauth().get_authorization_url( + app_id="", + redirect_uri="https://yourapp.com/callback", + random_id="", + ) + print("Redirect seller to:", auth_url) + + # Step 2: After the seller authorizes, exchange the received code for tokens + oauth_object = { + "client_secret": access_token, + "code": "", + "redirect_uri": "https://yourapp.com/callback", + "grant_type": "authorization_code", + } + + try: + response = sdk.oauth().create(oauth_object) + print("Token created successfully:", response["response"]) + except Exception as e: + print("Error:", e) + + +if __name__ == "__main__": + main() diff --git a/mercadopago/examples/point/create_payment_intent.py b/mercadopago/examples/point/create_payment_intent.py new file mode 100644 index 0000000..68a0623 --- /dev/null +++ b/mercadopago/examples/point/create_payment_intent.py @@ -0,0 +1,37 @@ +from mercadopago import SDK + + +def main(): + # Initialize the SDK with your access token + access_token = "" + sdk = SDK(access_token) + + # List available Point devices to get a device_id + devices = sdk.point().get_devices() + print("Available devices:", devices["response"]) + + # Create a payment intent on a specific device + device_id = "" + payment_intent_object = { + "amount": 1500, + "description": "Product purchase", + "payment": { + "installments": 1, + "type": "credit_card", + }, + } + + try: + response = sdk.point().create(device_id, payment_intent_object) + print("Payment intent created:", response["response"]) + + # Retrieve the payment intent status + payment_intent_id = response["response"]["id"] + status = sdk.point().get(payment_intent_id) + print("Payment intent status:", status["response"]) + except Exception as e: + print("Error:", e) + + +if __name__ == "__main__": + main() From 17bd29b8ed2b5b36d51cf5b7054a5d3e6c80c018 Mon Sep 17 00:00:00 2001 From: Daniel Alfaro Date: Wed, 27 May 2026 13:45:10 -0500 Subject: [PATCH 04/12] feat(python): register OAuth, Point and Invoice in SDK entry point - resources/__init__.py: add Invoice, OAuth, Point imports and __all__ entries - sdk.py: add Invoice, OAuth, Point imports and factory methods (sdk.invoice(), sdk.oauth(), sdk.point()) --- mercadopago/resources/__init__.py | 6 ++++++ mercadopago/sdk.py | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/mercadopago/resources/__init__.py b/mercadopago/resources/__init__.py index 0b58843..a4e4baf 100644 --- a/mercadopago/resources/__init__.py +++ b/mercadopago/resources/__init__.py @@ -13,11 +13,14 @@ from mercadopago.resources.customer import Customer from mercadopago.resources.disbursement_refund import DisbursementRefund from mercadopago.resources.identification_type import IdentificationType +from mercadopago.resources.invoice import Invoice from mercadopago.resources.merchant_order import MerchantOrder +from mercadopago.resources.oauth import OAuth 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 +from mercadopago.resources.point import Point from mercadopago.resources.preapproval import PreApproval from mercadopago.resources.preference import Preference from mercadopago.resources.refund import Refund @@ -34,11 +37,14 @@ 'DisbursementRefund', 'HttpClient', 'IdentificationType', + 'Invoice', 'MerchantOrder', + 'OAuth', 'Order', 'Payment', 'PaymentMethods', 'Plan', + 'Point', 'PreApproval', 'Preference', 'Refund', diff --git a/mercadopago/sdk.py b/mercadopago/sdk.py index e225556..d7c6b38 100644 --- a/mercadopago/sdk.py +++ b/mercadopago/sdk.py @@ -24,11 +24,14 @@ Customer, DisbursementRefund, IdentificationType, + Invoice, MerchantOrder, + OAuth, Order, Payment, PaymentMethods, Plan, + Point, PreApproval, Preference, Refund, @@ -116,6 +119,21 @@ def identification_type(self, request_options=None): return IdentificationType(request_options is not None and request_options or self.request_options, self.http_client) + def invoice(self, request_options=None): + """Creates an :class:`Invoice` resource to retrieve subscription billing invoices.""" + return Invoice(request_options is not None and request_options + or self.request_options, self.http_client) + + def oauth(self, request_options=None): + """Creates an :class:`OAuth` resource for the OAuth 2.0 authorization code flow.""" + return OAuth(request_options is not None and request_options + or self.request_options, self.http_client) + + def point(self, request_options=None): + """Creates a :class:`Point` resource for in-person payments via Point devices.""" + return Point(request_options is not None and request_options + or self.request_options, self.http_client) + def merchant_order(self, request_options=None): """Creates a :class:`MerchantOrder` resource for Checkout Pro orders.""" return MerchantOrder(request_options is not None and request_options From 9458b52b6eda650ccc7a784f1cd10d8ee3d0e99e Mon Sep 17 00:00:00 2001 From: Daniel Alfaro Date: Wed, 27 May 2026 13:45:38 -0500 Subject: [PATCH 05/12] chore(python): create CHANGELOG.md and prepare v3.2.0 release notes New features in this release: - OAuth: authorization URL, token exchange and refresh (POST /oauth/token) - Point: payment intents and device management (/point/integration-api/...) - Invoice: get and search subscription invoices (GET /authorized_payments) Note: version bump in pyproject.toml and config.py requires approval before merge. --- CHANGELOG.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..611c4b1 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,32 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +This project follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) +and [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [3.2.0] - 2026-05-27 + +### Added + +- **OAuth**: authorization URL builder, token exchange, and token refresh + (`sdk.oauth().get_authorization_url()`, `.create()`, `.refresh()`). + Enables marketplace and platform integrations to operate on behalf of + other sellers via the OAuth 2.0 authorization code flow. + Endpoints: `POST /oauth/token`. + +- **Point**: in-person payment intent management for MercadoPago Point + devices (`sdk.point().get_devices()`, `.create()`, `.get()`, `.cancel()`). + Enables card-present transactions through physical Point card readers. + Endpoints: `GET /point/integration-api/devices`, + `POST /point/integration-api/devices/{device_id}/payment-intents`, + `GET /point/integration-api/payment-intents/{id}`, + `DELETE /point/integration-api/devices/{device_id}/payment-intents/{id}`. + Note: `change_operating_mode` (PATCH) is not included; requires HttpClient + PATCH support. + +- **Invoice**: retrieval and search of subscription billing invoices + (`sdk.invoice().get()`, `.search()`). Enables monitoring of authorized + payments generated by PreApproval billing cycles. + Endpoints: `GET /authorized_payments/{id}`, + `GET /authorized_payments/search`. From c10dcc96843ee95c61e02c0ee1f79677ae21124e Mon Sep 17 00:00:00 2001 From: Daniel Alfaro Date: Wed, 27 May 2026 13:50:38 -0500 Subject: [PATCH 06/12] chore(python): bump version to 3.2.0 New features in this release: - OAuth: authorization URL, token exchange and refresh (POST /oauth/token) - Point: payment intents and device management (/point/integration-api/...) - Invoice: get and search subscription invoices (GET /authorized_payments) --- mercadopago/config/config.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mercadopago/config/config.py b/mercadopago/config/config.py index 36d74c8..1c79773 100644 --- a/mercadopago/config/config.py +++ b/mercadopago/config/config.py @@ -26,7 +26,7 @@ class Config: def __init__(self): """Builds version-dependent values (user_agent, tracking_id).""" - self.__version = "3.1.1" + self.__version = "3.2.0" self.__user_agent = "MercadoPago Python SDK v" + self.__version self.__product_id = "bc32bpftrpp001u8nhlg" self.__tracking_id = "platform:" + platform.python_version() diff --git a/pyproject.toml b/pyproject.toml index 5cba398..1709421 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "mercadopago" -version = "3.1.1" +version = "3.2.0" authors = [ {name = "Mercado Pago", email = "mp_sdk@mercadopago.com"} ] From fffe96de435fe6037c747931533ba79ea8ad4250 Mon Sep 17 00:00:00 2001 From: Daniel Alfaro Date: Thu, 28 May 2026 12:43:27 -0500 Subject: [PATCH 07/12] fix lint --- mercadopago/sdk.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mercadopago/sdk.py b/mercadopago/sdk.py index d7c6b38..1b0b899 100644 --- a/mercadopago/sdk.py +++ b/mercadopago/sdk.py @@ -40,7 +40,7 @@ ) -class SDK: +class SDK: # pylint: disable=too-many-public-methods """Central client providing factory methods for every API resource. Each factory method (e.g. :meth:`payment`, :meth:`order`) returns a From fc14eb70cd72a0bb2e16699e8f43326fef34deff Mon Sep 17 00:00:00 2001 From: Daniel Alfaro Date: Thu, 28 May 2026 12:52:11 -0500 Subject: [PATCH 08/12] upgrade request --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 1709421..1fa201b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,7 @@ keywords = ["api", "mercadopago", "checkout", "payment in sdk integration", "lts license = {file = "LICENSE"} requires-python = ">=3.9" dependencies = [ - "requests>=2.32.4,<3" + "requests>=2.33.0,<3" ] classifiers = [ "License :: OSI Approved :: MIT License", From d45177eef6eb72cb1352c7407a1d1963bbcfbac2 Mon Sep 17 00:00:00 2001 From: Daniel Alfaro Date: Thu, 28 May 2026 12:59:49 -0500 Subject: [PATCH 09/12] fix test --- tests/test_order.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_order.py b/tests/test_order.py index 34445b9..5a7d9d9 100644 --- a/tests/test_order.py +++ b/tests/test_order.py @@ -222,8 +222,9 @@ def test_process_order(self): def test_cancel_order(self): card_token_id = self.create_master_test_card() order_id = self.create_order_canceled_or_captured(card_token_id) - time.sleep(10) - order_canceled = self.sdk.order().cancel(order_id) + order_canceled = api_call_with_retry( + lambda: self.sdk.order().cancel(order_id), expected_status=200 + ) self.assertEqual(order_canceled["status"], 200) self.assertEqual(order_canceled["response"]["status"], "canceled") From 349d5721c38e8eb2d8e4b878ca4736307548ce09 Mon Sep 17 00:00:00 2001 From: Daniel Alfaro Date: Thu, 28 May 2026 13:02:08 -0500 Subject: [PATCH 10/12] fix lint --- tests/test_order.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_order.py b/tests/test_order.py index 5a7d9d9..7df6b59 100644 --- a/tests/test_order.py +++ b/tests/test_order.py @@ -2,7 +2,6 @@ Module: test_order """ import os -import time import unittest import random from datetime import datetime, timezone, timedelta From 0fafb71eecc000f9b17adbf9429e9f74374f8554 Mon Sep 17 00:00:00 2001 From: Daniel Alfaro Date: Thu, 28 May 2026 21:01:38 -0500 Subject: [PATCH 11/12] drop 3.9 version but vulnerabiliites --- .github/workflows/ci.yml | 2 +- pyproject.toml | 3 +-- setup.cfg | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3d5e6fc..2d25d71 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.9', '3.10', '3.11', '3.12'] + python-version: ['3.10', '3.11', '3.12'] steps: - uses: actions/checkout@v2 diff --git a/pyproject.toml b/pyproject.toml index 1fa201b..b67467b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ description = "Mercadopago SDK module for Payments integration" readme = "README.md" keywords = ["api", "mercadopago", "checkout", "payment in sdk integration", "lts"] license = {file = "LICENSE"} -requires-python = ">=3.9" +requires-python = ">=3.10" dependencies = [ "requests>=2.33.0,<3" ] @@ -21,7 +21,6 @@ classifiers = [ "Operating System :: OS Independent", "Topic :: Software Development :: Libraries :: Python Modules", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", diff --git a/setup.cfg b/setup.cfg index 4a36bf8..54cd4bf 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [pylint.MASTER] -py-version = 3.9 +py-version = 3.10 [pylint.FORMAT] max-line-length = 100 From 2dbda4d42b9ad876963eeb27ecf8fca8ef0bf8a5 Mon Sep 17 00:00:00 2001 From: Daniel Alfaro Date: Thu, 28 May 2026 21:06:06 -0500 Subject: [PATCH 12/12] fix unit test --- tests/test_order.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/test_order.py b/tests/test_order.py index 7df6b59..14ccc5f 100644 --- a/tests/test_order.py +++ b/tests/test_order.py @@ -289,12 +289,13 @@ def test_partial_refund_transaction(self): ] } - sleep(6) - - transaction_refunded = self.sdk.order().refund_transaction(order_id, transaction_refund) - self.assertIn(transaction_refunded["status"], [ 201], + transaction_refunded = api_call_with_retry( + lambda: self.sdk.order().refund_transaction(order_id, transaction_refund), + expected_status=201 + ) + self.assertIn(transaction_refunded["status"], [201], f"Unexpected status code for refund: {transaction_refunded['status']}." - " Response: {transaction_refunded}") + f" Response: {transaction_refunded}") def test_refund_transaction(self): card_token_id = self.create_master_test_card()