Ai knowledge and logicHelpers

magento2_get_order / magento2_find_orders

Look up Magento orders by order number or email from Python, returning the raw Magento order.

magento2_get_order(context: dict, order_number: str) -> dict | None
magento2_find_orders(context: dict, email: str, limit: int = 10) -> list[dict]

Thin helpers for the most common Magento read: finding a customer's order. They read the business's Magento connection with get_integration_credentials, call the Magento REST API (/rest/V1/orders), signed with the connection's OAuth credentials or bearer token and sent through Octocom's proxy unless the connection skips it and return the orders as Magento returns them; see the Magento API reference for every field. Each order also carries its shipments under shipments; their tracks hold the carrier and tracking number. A few fields a support bot should never see are left out: the customer's IP address and card details on the payment.

magento2_get_order tries the number as given and, for a short number, zero-padded to Magento's 9 digits (123 finds 000000123). Both helpers stay within the connection's store ID when one is set. Both helpers raise ValueError if Magento isn't connected and requests.HTTPError if Magento rejects the request.

The full source is below. Copy it into your own action when you need something it doesn't do.


magento2_get_order

NameTypeDescription
contextdictThe context object passed to your action
order_numberstrThe increment ID as the customer gave it. Spaces and a leading # are ignored

Returns the matching order, or None.

magento2_find_orders

NameTypeDescription
contextdictThe context object passed to your action
emailstrThe customer's email address
limitintMaximum number of orders, newest first (10)

Returns a list of orders, newest first. Empty if there are none.


Example

def execute_action(context):
    order = magento2_get_order(context, context["args"]["orderId"])
    if order is None:
        return {"success": False, "message": "The order could not be found."}
    return {"success": True, "orders": [order]}

Businesses moved off the built-in order lookups get this as their getOrderDetailsByOrderId action, together with a getOrderDetailsByEmail action that calls magento2_find_orders. They are ordinary Python actions you can read and change.


Read-only sidebar

magento2_order_widget(context) renders the saved order ID and customer email lookups as a basic sidebar panel without order buttons. See Order widgets for setup, custom mappings and the shared presentation source.

Source

import base64
import hashlib
import hmac
import re
import secrets
import time
from urllib.parse import quote

import requests

# Never returned: the customer's IP address and card details on the payment.
_MAGENTO2_ORDER_HIDDEN = {"remote_ip", "x_forwarded_for"}
_MAGENTO2_PAYMENT_HIDDEN = {
    "cc_last4", "cc_exp_month", "cc_exp_year", "cc_owner", "cc_type",
    "cc_trans_id", "last_trans_id", "additional_information",
}


def _magento2_clean_order(order):
    order = {k: v for k, v in order.items() if k not in _MAGENTO2_ORDER_HIDDEN}
    if isinstance(order.get("payment"), dict):
        order["payment"] = {k: v for k, v in order["payment"].items() if k not in _MAGENTO2_PAYMENT_HIDDEN}
    return order


def _magento2_store(context):
    """The current business's Magento connection, as get_integration_credentials returns it."""
    business = context.get("business") or {}
    slug = business.get("slug") or (context.get("conversation") or {}).get("businessSlug")
    if not slug:
        raise ValueError("The business is not available in the context")
    store = (get_integration_credentials(context).get(slug) or {}).get("magento2")
    if not store:
        raise ValueError("Magento is not connected for this business")
    return store


def _magento2_get(store, resource, filters, extra_params=None):
    """GET /rest/V1/<resource> with AND-ed searchCriteria filters, signed like the connector."""
    url = store["storeUrl"].rstrip("/") + "/rest/V1/" + resource
    if store.get("storeId") is not None:
        filters = [("store_id", store["storeId"])] + filters
    params = {}
    for index, (field, value) in enumerate(filters):
        prefix = f"searchCriteria[filterGroups][{index}][filters][0]"
        params[prefix + "[field]"] = field
        params[prefix + "[value]"] = str(value)
        params[prefix + "[conditionType]"] = "eq"
    params.update(extra_params or {})
    headers = {"Accept": "application/json", "User-Agent": "mozilla/5.0"}
    if store.get("useAccessTokenAsBearerToken"):
        headers["Authorization"] = "Bearer " + store["accessToken"]
    else:
        oauth = {
            "oauth_consumer_key": store["consumerKey"],
            "oauth_token": store["accessToken"],
            "oauth_nonce": secrets.token_hex(16),
            "oauth_timestamp": str(int(time.time())),
            "oauth_signature_method": "HMAC-SHA256",
            "oauth_version": "1.0",
        }
        encode = lambda value: quote(str(value), safe="~")
        pairs = sorted((encode(k), encode(v)) for k, v in list(params.items()) + list(oauth.items()))
        base = "GET&" + encode(url) + "&" + encode("&".join(k + "=" + v for k, v in pairs))
        key = encode(store["consumerSecret"]) + "&" + encode(store["accessTokenSecret"])
        oauth["oauth_signature"] = base64.b64encode(
            hmac.new(key.encode(), base.encode(), hashlib.sha256).digest()
        ).decode()
        headers["Authorization"] = "OAuth " + ", ".join(
            encode(k) + '="' + encode(v) + '"' for k, v in sorted(oauth.items())
        )
    # Most stores allowlist Octocom's proxy IP; skipProxy stores are called directly.
    send = requests.request if store.get("skipProxy") else proxy_request
    response = send("GET", url, params=params, headers=headers, timeout=20, allow_redirects=False)
    response.raise_for_status()
    return response.json().get("items") or []


def _magento2_with_shipments(store, order):
    # Tracking numbers live on the order's shipments, not on the order.
    order["shipments"] = _magento2_get(store, "shipments", [("order_id", order["entity_id"])])
    return _magento2_clean_order(order)


def magento2_get_order(context, order_number):
    """The Magento order with this increment ID (plus its shipments), or None."""
    store = _magento2_store(context)
    number = re.sub(r"\s+", "", str(order_number)).lstrip("#")
    if not number:
        return None
    candidates = [number]
    # Customers often drop the leading zeros of Magento's 9-digit numbers.
    if number.isdigit() and len(number) < 9:
        candidates.append(number.zfill(9))
    for candidate in candidates:
        orders = _magento2_get(store, "orders", [("increment_id", candidate)])
        if orders:
            return _magento2_with_shipments(store, orders[0])
    return None


def magento2_find_orders(context, email, limit=10):
    """The customer's Magento orders by email, newest first."""
    store = _magento2_store(context)
    orders = _magento2_get(store, "orders", [("customer_email", email.strip())], {
        "searchCriteria[pageSize]": str(limit),
        "searchCriteria[currentPage]": "1",
        "searchCriteria[sortOrders][0][field]": "created_at",
        "searchCriteria[sortOrders][0][direction]": "DESC",
    })
    return [_magento2_with_shipments(store, order) for order in orders[:limit]]


def _magento2_widget_order(order):
    return {
        "id": order.get("entity_id"), "number": order.get("increment_id"), "status": order.get("status"),
        "email": order.get("customer_email"), "total": order.get("grand_total"),
        "currency": order.get("order_currency_code"), "placed_at": order.get("created_at"),
        "products": [{"name": p.get("name"), "quantity": p.get("qty_ordered"), "sku": p.get("sku"), "total": p.get("row_total_incl_tax")}
                     for p in order.get("items") or [] if not p.get("parent_item_id")],
        "shipments": [{"carrier": t.get("title") or t.get("carrier_code"), "tracking_number": t.get("track_number")}
                      for shipment in order.get("shipments") or [] for t in shipment.get("tracks") or []],
    }


def magento2_order_widget(context):
    """Read-only sidebar orders from the saved order ID and customer's email."""
    return order_widget(context, get_order=magento2_get_order, find_orders=magento2_find_orders, map_order=_magento2_widget_order)

On this page