Skip to content
Merged
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
73 changes: 71 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,77 @@ First time using Mercado Pago? Create your [Mercado Pago account](https://www.me

Copy your `Access Token` in the [credentials panel](https://www.mercadopago.com/developers/panel/credentials) and replace the text `YOUR_ACCESS_TOKEN` with it.

### Simple usage

### Simple usage — Orders API

The [Orders API](https://www.mercadopago.com/developers/en/reference/online-payments/checkout-api/create-order/post) (`/v1/orders`) is the recommended way to accept payments. `sdk.order().create()` accepts either a plain `dict` or the optional typed request dataclasses (`OrderCreateRequest` and friends). Both routes produce the exact same JSON body.

```python
import mercadopago

sdk = mercadopago.SDK("YOUR_ACCESS_TOKEN")

request_options = mercadopago.config.RequestOptions()
request_options.custom_headers = {
'x-idempotency-key': '<SOME_UNIQUE_VALUE>'
}

order_data = {
"type": "online",
"total_amount": "100.00",
"external_reference": "ext_ref_1234",
"transactions": {
"payments": [
{
"amount": "100.00",
"payment_method": {
"id": "master",
"type": "credit_card",
"token": "CARD_TOKEN",
"installments": 1,
},
}
]
},
"payer": {
"email": "test_user_123456@testuser.com"
},
}
result = sdk.order().create(order_data, request_options)
order = result["response"]

print(order)
```

#### Typed request classes (optional)

Instead of a `dict`, you can build the request with the typed dataclasses. `None`
fields are omitted from the JSON body automatically, matching the `dict` route.

```python
import mercadopago
from mercadopago.resources.order_create import OrderCreateRequest, OrderPayerRequest
from mercadopago.resources.order_item import OrderItemRequest

sdk = mercadopago.SDK("YOUR_ACCESS_TOKEN")

order = OrderCreateRequest(
type="online",
total_amount="100.00",
external_reference="ext_ref_1234",
payer=OrderPayerRequest(email="test_user_123456@testuser.com"),
items=[OrderItemRequest(title="A book", unit_price="100.00", quantity=1)],
)

result = sdk.order().create(order)
print(result["response"])
```

For a complete recurring / Automatic Payments example (stored credential,
subscription data, integration data), see
[`examples/order/create_order_automatic_payment.py`](examples/order/create_order_automatic_payment.py).

### Creating a payment (legacy Payments API)

```python
import mercadopago

Expand Down
2 changes: 1 addition & 1 deletion examples/order/create_order_automatic_payment.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@
reason="recurring",
store_payment_method=False,
first_payment=False,
prev_transaction_ref=first_transaction_id, # required
previous_transaction_reference=first_transaction_id, # required
)
),
"subscription_data": dataclasses.asdict(
Expand Down
54 changes: 54 additions & 0 deletions mercadopago/resources/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from mercadopago.resources.merchant_order import MerchantOrder
from mercadopago.resources.oauth import OAuth
from mercadopago.resources.order import Order
from mercadopago.resources.order_automatic_payments import OrderAutomaticPayments
from mercadopago.resources.order_checkout_pro import (
OrderCheckoutProConfig,
OrderCheckoutProInstallments,
Expand All @@ -26,6 +27,38 @@
OrderCheckoutProTrack,
OrderCheckoutProDict,
)
from mercadopago.resources.order_create import (
OrderCreateRequest,
order_request_to_dict,
)
from mercadopago.resources.order_integration_data import (
OrderIntegrationData,
OrderSponsor,
)
from mercadopago.resources.item import ItemRequest
from mercadopago.resources.payer import (
PayerAddress,
PayerIdentification,
PayerPhone,
PayerRequest,
)
from mercadopago.resources.shipment import (
ShipmentAddress,
ShipmentFreeMethod,
ShipmentRequest,
)
from mercadopago.resources.order_stored_credential import OrderStoredCredential
from mercadopago.resources.order_subscription_data import (
OrderInvoicePeriod,
OrderSubscriptionData,
OrderSubscriptionSequence,
)
from mercadopago.resources.order_transaction import (
OrderPaymentMethodRequest,
OrderPaymentRequest,
OrderTransactionRequest,
)
from mercadopago.resources.order_transaction_security import OrderTransactionSecurity
from mercadopago.resources.payment import Payment
from mercadopago.resources.payment_methods import PaymentMethods
from mercadopago.resources.plan import Plan
Expand All @@ -50,13 +83,33 @@
'MerchantOrder',
'OAuth',
'Order',
'OrderAutomaticPayments',
'OrderCheckoutProConfig',
'OrderCheckoutProInstallments',
'OrderCheckoutProInterestFree',
'OrderCheckoutProOnlineConfig',
'OrderCheckoutProPaymentMethod',
'OrderCheckoutProTrack',
'OrderCheckoutProDict',
'OrderCreateRequest',
'OrderIntegrationData',
'OrderInvoicePeriod',
'ItemRequest',
'PayerAddress',
'PayerIdentification',
'PayerPhone',
'PayerRequest',
'ShipmentAddress',
'ShipmentFreeMethod',
'ShipmentRequest',
'OrderSponsor',
'OrderStoredCredential',
'OrderSubscriptionData',
'OrderSubscriptionSequence',
'OrderPaymentMethodRequest',
'OrderPaymentRequest',
'OrderTransactionRequest',
'OrderTransactionSecurity',
'Payment',
'PaymentMethods',
'Plan',
Expand All @@ -67,4 +120,5 @@
'RequestOptions',
'Subscription',
'User',
'order_request_to_dict',
)
39 changes: 39 additions & 0 deletions mercadopago/resources/item.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"""Dataclass for line items in API requests."""
from dataclasses import dataclass
from typing import Optional


# pylint: disable=too-many-instance-attributes # DTO: fields mirror the API items contract
@dataclass
class ItemRequest:
"""A single line item within an API request.

Use this dataclass to build an entry of the ``items`` array when creating
an order. Convert to dict with ``dataclasses.asdict()`` (``None`` fields are
filtered out before sending, per the omit-empty behavior of the API).

Attributes:
title: Display name of the item. Type: str.
type: Item type/category classifier. Type: str.
warranty: Whether the item includes a warranty. Type: bool.
event_date: ISO 8601 date associated with the item (e.g. event tickets).
Type: str.
unit_price: Price per unit as a decimal string (e.g. ``"100.00"``).
Type: str.
external_code: Merchant-side external identifier for the item. Type: str.
category_id: MercadoPago category identifier. Type: str.
description: Free-text description of the item. Type: str.
picture_url: URL of an image representing the item. Type: str.
quantity: Number of units. Type: int.
"""

title: Optional[str] = None
type: Optional[str] = None
warranty: Optional[bool] = None
event_date: Optional[str] = None
unit_price: Optional[str] = None
external_code: Optional[str] = None
category_id: Optional[str] = None
description: Optional[str] = None
picture_url: Optional[str] = None
quantity: Optional[int] = None
20 changes: 16 additions & 4 deletions mercadopago/resources/order.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@
`API reference
<https://www.mercadopago.com/developers/en/reference/online-payments/checkout-api/create-order/post>`_
"""
from dataclasses import is_dataclass

from mercadopago.core import MPBase
from mercadopago.pagination.iterator import search_auto_paging_iter as _paging_iter
from mercadopago.resources.order_create import order_request_to_dict

class Order(MPBase):
"""Manages orders and their associated transactions.
Expand Down Expand Up @@ -89,21 +92,30 @@ def search(self, filters=None, request_options=None):
def create(self, order_object, request_options=None):
"""Creates a new order.

Accepts either a plain ``dict`` (the historical, dynamic route) or an
:class:`~mercadopago.resources.order_create.OrderCreateRequest` typed
dataclass. When a dataclass is passed it is converted to a ``dict`` with
``None`` fields omitted, producing the same JSON body as the dict route
(DD-3). The dict route is unchanged and fully backward compatible.

Args:
order_object: Dict describing the order (items, transactions,
payer, etc.).
payer, etc.), or an ``OrderCreateRequest`` dataclass instance.
request_options: Per-call configuration overrides.

Raises:
ValueError: If *order_object* is not a ``dict``.
ValueError: If *order_object* is neither a ``dict`` nor a dataclass
instance.

Returns:
dict: Created order including its ``id``.

Reference: https://www.mercadopago.com/developers/en/reference/online-payments/checkout-api/create-order/post
"""
if not isinstance(order_object, dict):
raise ValueError("Param order_object must be a Dictionary")
if is_dataclass(order_object) and not isinstance(order_object, type):
order_object = order_request_to_dict(order_object)
elif not isinstance(order_object, dict):
raise ValueError("Param order_object must be a Dictionary or an OrderCreateRequest")

return self._post(uri="/v1/orders", data=order_object, request_options=request_options)

Expand Down
112 changes: 112 additions & 0 deletions mercadopago/resources/order_create.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
"""Root request dataclasses for the MercadoPago Orders API.

These dataclasses model the ``POST /v1/orders`` request body. They are an
optional, typed alternative to passing a plain ``dict`` to
:meth:`~mercadopago.resources.order.Order.create`. Build the request with the
dataclasses and convert it to a ``dict`` with ``dataclasses.asdict()``; ``None``
fields are filtered out before serialization so the resulting JSON matches the
dict path exactly.

The dict path continues to work unchanged; these dataclasses are purely additive.
"""
from dataclasses import (
asdict,
dataclass,
field,
is_dataclass,
)
from typing import (
List,
Optional,
Union,
)

from mercadopago.resources.item import ItemRequest
from mercadopago.resources.order_integration_data import OrderIntegrationData
from mercadopago.resources.shipment import ShipmentRequest
from mercadopago.resources.order_transaction import OrderTransactionRequest
from mercadopago.resources.payer import PayerRequest


def _filter_none(value):
"""Recursively drop ``None`` values from dicts/lists (DD-3, omit-empty)."""
if isinstance(value, dict):
return {k: _filter_none(v) for k, v in value.items() if v is not None}
if isinstance(value, list):
return [_filter_none(v) for v in value]
return value


def order_request_to_dict(request):
"""Convert a request dataclass into a ``dict`` with ``None`` fields omitted.

This is the canonical way to turn any of the Orders API request dataclasses
(``OrderCreateRequest`` and its nested objects) into the ``dict`` accepted by
:meth:`~mercadopago.resources.order.Order.create`. It runs
``dataclasses.asdict()`` and then recursively strips keys whose value is
``None`` so the resulting JSON matches the plain-dict path exactly (DD-3).

Args:
request: A request dataclass instance (or any dataclass instance).

Returns:
dict: The request as a plain ``dict`` with ``None`` fields removed.

Raises:
TypeError: If *request* is not a dataclass instance.
"""
if not is_dataclass(request) or isinstance(request, type):
raise TypeError("request must be a dataclass instance")
return _filter_none(asdict(request))


# pylint: disable=too-many-instance-attributes # DTO: fields mirror the Orders API root request contract
@dataclass
class OrderCreateRequest:
"""Root request body for creating an order.

Optional typed alternative to a plain ``dict``. Convert with
``dataclasses.asdict()``; ``None`` fields are filtered out before sending.

Attributes:
type: Order type (e.g. ``"online"``). Type: str.
external_reference: Merchant-side reference for the order. Type: str.
total_amount: Total order amount as a decimal string. Type: str.
currency: Currency identifier (e.g. ``"BRL"``). Type: str.
capture_mode: Capture mode (e.g. ``"automatic_async"``). Type: str.
processing_mode: Processing mode (e.g. ``"automatic"``). Type: str.
description: Free-text order description. Type: str.
marketplace: Marketplace identifier. Type: str.
marketplace_fee: Marketplace fee as a decimal string. Type: str.
expiration_time: Order expiration time (ISO 8601 / duration). Type: str.
checkout_available_at: When the checkout becomes available. Type: str.
transactions: Typed transactions payload. Accepts an
:class:`~mercadopago.resources.order_transaction.OrderTransactionRequest`
for a fully typed AP chain, or a plain ``dict`` for backward
compatibility.
payer: Payer information.
items: Line items in the order.
config: Order configuration payload.
shipment: Shipment configuration.
integration_data: Integration metadata.
additional_info: Free-form additional information (kept as-is).
"""

type: Optional[str] = None
external_reference: Optional[str] = None
total_amount: Optional[str] = None
currency: Optional[str] = None
capture_mode: Optional[str] = None
processing_mode: Optional[str] = None
description: Optional[str] = None
marketplace: Optional[str] = None
marketplace_fee: Optional[str] = None
expiration_time: Optional[str] = None
checkout_available_at: Optional[str] = None
transactions: Optional[Union[OrderTransactionRequest, dict]] = None
payer: Optional[PayerRequest] = None
items: Optional[List[ItemRequest]] = field(default=None)
config: Optional[dict] = None
shipment: Optional[ShipmentRequest] = None
integration_data: Optional[OrderIntegrationData] = None
additional_info: Optional[dict] = None
8 changes: 4 additions & 4 deletions mercadopago/resources/order_stored_credential.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ class OrderStoredCredential:
Type: bool.
first_payment: ``True`` for the initial authorization; ``False`` for
subsequent recurring charges. Type: bool.
prev_transaction_ref: Identifier of the previous transaction in the recurring
series. Required from the second charge onwards to link this payment to the
original card-network authorization. Type: str.
previous_transaction_reference: Identifier of the previous transaction in the
recurring series. Required from the second charge onwards to link this payment
to the original card-network authorization. Type: str.
"""

payment_initiator: Optional[str] = None
reason: Optional[str] = None
store_payment_method: Optional[bool] = None
first_payment: Optional[bool] = None
prev_transaction_ref: Optional[str] = None
previous_transaction_reference: Optional[str] = None
Loading