Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
32 changes: 32 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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`.
2 changes: 1 addition & 1 deletion mercadopago/config/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
29 changes: 29 additions & 0 deletions mercadopago/examples/invoice/get_invoice.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from mercadopago import SDK


def main():
# Initialize the SDK with your access token
access_token = "<YOUR_ACCESS_TOKEN>"
sdk = SDK(access_token)

# Search invoices for a specific subscription (preapproval)
filters = {
"preapproval_id": "<YOUR_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()
33 changes: 33 additions & 0 deletions mercadopago/examples/oauth/create_token.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from mercadopago import SDK


def main():
# Initialize the SDK with your marketplace access token
access_token = "<YOUR_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="<YOUR_APP_ID>",
redirect_uri="https://yourapp.com/callback",
random_id="<UNIQUE_CSRF_STATE>",
)
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": "<AUTHORIZATION_CODE_FROM_CALLBACK>",
"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()
37 changes: 37 additions & 0 deletions mercadopago/examples/point/create_payment_intent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from mercadopago import SDK


def main():
# Initialize the SDK with your access token
access_token = "<YOUR_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 = "<YOUR_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()
6 changes: 6 additions & 0 deletions mercadopago/resources/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -34,11 +37,14 @@
'DisbursementRefund',
'HttpClient',
'IdentificationType',
'Invoice',
'MerchantOrder',
'OAuth',
'Order',
'Payment',
'PaymentMethods',
'Plan',
'Point',
'PreApproval',
'Preference',
'Refund',
Expand Down
62 changes: 62 additions & 0 deletions mercadopago/resources/invoice.py
Original file line number Diff line number Diff line change
@@ -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
<https://www.mercadopago.com/developers/en/reference/online-payments/subscriptions/get-authorized-payment/get>`_
"""
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,
)
119 changes: 119 additions & 0 deletions mercadopago/resources/oauth.py
Original file line number Diff line number Diff line change
@@ -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
<https://www.mercadopago.com/developers/en/reference/authentication/oauth/_oauth_token/post>`_
"""
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,
)
Loading
Loading