Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
70 changes: 67 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@ 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

Expand All @@ -32,6 +34,68 @@ 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

sdk = mercadopago.SDK("YOUR_ACCESS_TOKEN")

payment_data = {
"transaction_amount": 100,
"token": "CARD_TOKEN",
Expand All @@ -42,7 +106,7 @@ payment_data = {
"email": 'test_user_123456@testuser.com'
}
}
result = sdk.payment().create(payment_data, request_options)
result = sdk.payment().create(payment_data)
payment = result["response"]

print(payment)
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,
OrderIdentification,
OrderPayerRequest,
order_request_to_dict,
)
from mercadopago.resources.order_integration_data import (
OrderIntegrationData,
OrderSponsor,
)
from mercadopago.resources.order_item import OrderItemRequest
from mercadopago.resources.order_payer import (
OrderPayerAddress,
OrderPayerPhone,
)
from mercadopago.resources.order_shipment import (
OrderShipmentAddress,
OrderShipmentFreeMethod,
OrderShipmentRequest,
)
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',
'OrderIdentification',
'OrderIntegrationData',
'OrderInvoicePeriod',
'OrderItemRequest',
'OrderPayerAddress',
'OrderPayerPhone',
'OrderPayerRequest',
'OrderShipmentAddress',
'OrderShipmentFreeMethod',
'OrderShipmentRequest',
'OrderSponsor',
'OrderStoredCredential',
'OrderSubscriptionData',
'OrderSubscriptionSequence',
'OrderPaymentMethodRequest',
'OrderPaymentRequest',
'OrderTransactionRequest',
'OrderTransactionSecurity',
'Payment',
'PaymentMethods',
'Plan',
Expand All @@ -67,4 +120,5 @@
'RequestOptions',
'Subscription',
'User',
'order_request_to_dict',
)
20 changes: 16 additions & 4 deletions mercadopago/resources/order.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@
`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.resources.order_create import order_request_to_dict

class Order(MPBase):
"""Manages orders and their associated transactions.
Expand Down Expand Up @@ -88,21 +91,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
155 changes: 155 additions & 0 deletions mercadopago/resources/order_create.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
"""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.order_item import OrderItemRequest
from mercadopago.resources.order_integration_data import OrderIntegrationData
from mercadopago.resources.order_payer import (
OrderPayerAddress,
OrderPayerPhone,
)
from mercadopago.resources.order_shipment import OrderShipmentRequest
from mercadopago.resources.order_transaction import OrderTransactionRequest


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))


@dataclass
class OrderIdentification:
"""Payer identification document for an order request.

Attributes:
type: Identification document type (e.g. ``"CPF"``). Type: str.
number: Identification document number. Type: str.
"""

type: Optional[str] = None
number: Optional[str] = None


# pylint: disable=too-many-instance-attributes # DTO: fields mirror the Orders API payer contract
@dataclass
class OrderPayerRequest:
"""Payer information for an order request.

Attributes:
email: Payer email address. Type: str.
first_name: Payer first name. Type: str.
last_name: Payer last name. Type: str.
customer_id: Stored customer identifier. Type: str.
entity_type: Payer entity type (``"individual"`` | ``"association"``).
Type: str.
identification: Payer identification document.
phone: Payer phone number.
address: Payer address.
"""

email: Optional[str] = None
first_name: Optional[str] = None
last_name: Optional[str] = None
customer_id: Optional[str] = None
entity_type: Optional[str] = None
identification: Optional[OrderIdentification] = None
phone: Optional[OrderPayerPhone] = None
address: Optional[OrderPayerAddress] = None


# 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[OrderPayerRequest] = None
items: Optional[List[OrderItemRequest]] = field(default=None)
config: Optional[dict] = None
shipment: Optional[OrderShipmentRequest] = None
integration_data: Optional[OrderIntegrationData] = None
additional_info: Optional[dict] = None
Loading