place_order
Submit the USDC transaction hash on Base, quote ID, variant ID, and shipping details to verify payment and place a physical hat order.
Instructions
Place an order after sending USDC payment on Base. Verifies the on-chain transaction and creates the order.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| quote_id | Yes | Quote ID from get_quote | |
| tx_hash | Yes | Transaction hash of USDC payment on Base | |
| variant_id | Yes | Shopify variant ID | |
| shipping_name | Yes | ||
| shipping_address1 | Yes | ||
| shipping_city | Yes | ||
| shipping_state | Yes | ||
| shipping_zip | Yes | ||
| shipping_country | No |
Implementation Reference
- main.py:354-428 (handler)The place_order handler function (FastAPI POST /tools/place_order). Receives quote_id, tx_hash, variant_id, quantity, shipping_address, and email. Validates quote, verifies USDC payment on Base via eth_getTransactionReceipt, then creates a Shopify order with financial_status=paid and a USDC transaction record. Returns order details including order_id and payment_verified status.
@app.post("/tools/place_order") async def place_order(request: Request): body = await request.json() quote_id = body.get("quote_id") tx_hash = body.get("tx_hash") variant_id = body.get("variant_id") quantity = body.get("quantity", 1) address = body.get("shipping_address", {}) email = body.get("email", "") # Validate quote quote = _quotes.get(quote_id) if not quote: raise HTTPException(400, "Quote not found. Call get_quote first.") if time.time() > quote["expires_at"]: raise HTTPException(400, "Quote expired. Call get_quote again.") expected_usdc = quote["total_usdc"] # Verify payment on Base verification = await verify_usdc_payment(tx_hash, expected_usdc, PAYMENT_WALLET) if not verification["verified"]: raise HTTPException(402, f"Payment not verified: {verification['reason']}") # Build Shopify order (already paid) name_parts = address.get("name", "Agent Buyer").split(" ", 1) order_payload = { "order": { "line_items": [{"variant_id": int(variant_id), "quantity": quantity}], "financial_status": "paid", "fulfillment_status": None, "send_receipt": bool(email), "send_fulfillment_receipt": True, "note": f"Paid via USDC on Base | tx: {tx_hash} | quote: {quote_id}", "shipping_address": { "first_name": name_parts[0], "last_name": name_parts[1] if len(name_parts) > 1 else "", "address1": address.get("address1", ""), "address2": address.get("address2", ""), "city": address.get("city", ""), "province": address.get("province", ""), "zip": address.get("zip", ""), "country_code": address.get("country_code", "US"), "phone": address.get("phone", ""), }, "transactions": [ { "kind": "sale", "status": "success", "amount": str(expected_usdc), "gateway": "USDC on Base", } ], } } if email: order_payload["order"]["email"] = email result = await shopify_post("orders.json", order_payload) order = result["order"] # Remove used quote _quotes.pop(quote_id, None) return { "success": True, "order_id": str(order["id"]), "order_number": order.get("order_number"), "status": order.get("fulfillment_status") or "unfulfilled", "payment_verified": True, "tx_hash": tx_hash, "amount_paid_usdc": verification.get("amount_usdc", expected_usdc), "shipping_to": address.get("name"), "message": "Order created. Printful will produce and ship your item. Use get_order_status to track.", } - main.py:119-152 (schema)First tool definition schema for place_order in MCP_TOOLS list (lines ~78-167). Declares name, description, and inputSchema with required fields: quote_id, tx_hash, variant_id, shipping_address, and optional email.
"name": "place_order", "description": ( "Place a fully paid order. " "Send the exact USDC amount from get_quote to the payment wallet on Base, " "then call this tool with the transaction hash. " "Server verifies payment on-chain and creates the order. " "Printful produces and ships the item." ), "price": "product price in USDC (see get_quote)", "inputSchema": { "type": "object", "properties": { "quote_id": {"type": "string", "description": "From get_quote"}, "tx_hash": {"type": "string", "description": "Base transaction hash of USDC payment"}, "variant_id": {"type": "string"}, "quantity": {"type": "integer", "default": 1}, "shipping_address": { "type": "object", "properties": { "name": {"type": "string"}, "address1": {"type": "string"}, "address2": {"type": "string"}, "city": {"type": "string"}, "province": {"type": "string"}, "zip": {"type": "string"}, "country_code": {"type": "string"}, "phone": {"type": "string"}, }, "required": ["name", "address1", "city", "zip", "country_code"], }, "email": {"type": "string", "description": "Optional — for order confirmation"}, }, "required": ["quote_id", "tx_hash", "variant_id", "shipping_address"], }, - main.py:507-524 (schema)Second tool definition schema for place_order (lines ~507-524). An alternative schema variant with flat shipping fields (shipping_name, shipping_address1, etc.) instead of a nested shipping_address object.
{ "name": "place_order", "description": "Place an order after sending USDC payment on Base. Verifies the on-chain transaction and creates the order.", "inputSchema": { "type": "object", "properties": { "quote_id": {"type": "string", "description": "Quote ID from get_quote"}, "tx_hash": {"type": "string", "description": "Transaction hash of USDC payment on Base"}, "variant_id": {"type": "integer", "description": "Shopify variant ID"}, "shipping_name": {"type": "string"}, "shipping_address1": {"type": "string"}, "shipping_city": {"type": "string"}, "shipping_state": {"type": "string"}, "shipping_zip": {"type": "string"}, "shipping_country": {"type": "string"} }, "required": ["quote_id", "tx_hash", "variant_id", "shipping_name", "shipping_address1", "shipping_city", "shipping_state", "shipping_zip"] } - main.py:574-578 (registration)Registration of place_order in the REST-to-MCP routing map. Maps the tool name 'place_order' to the REST endpoint '/tools/place_order' for the tools/call JSON-RPC method.
rest_map = { "search_products": "/tools/search_products", "get_product": "/tools/get_product", "get_quote": "/tools/get_quote", "place_order": "/tools/place_order", - main.py:184-226 (helper)The verify_usdc_payment helper function used by place_order. Verifies a USDC transfer on Base by decoding the transaction receipt logs, checking for a Transfer event to the payment wallet with the expected amount (allowing 1% slippage).
async def verify_usdc_payment(tx_hash: str, expected_amount_usdc: float, to_wallet: str) -> dict: """Verify a USDC transfer on Base via eth_getTransactionReceipt.""" # USDC Transfer event topic TRANSFER_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" async with httpx.AsyncClient(timeout=20) as client: # Get transaction receipt r = await client.post(BASE_RPC, json={ "jsonrpc": "2.0", "method": "eth_getTransactionReceipt", "params": [tx_hash], "id": 1, }) receipt = r.json().get("result") if not receipt: return {"verified": False, "reason": "Transaction not found or not confirmed yet"} if receipt.get("status") != "0x1": return {"verified": False, "reason": "Transaction failed on-chain"} # Check logs for USDC Transfer to our wallet expected_usdc_wei = int(expected_amount_usdc * 1_000_000) # USDC has 6 decimals to_wallet_padded = "0x000000000000000000000000" + to_wallet[2:].lower() for log in receipt.get("logs", []): if (log.get("address", "").lower() == USDC_CONTRACT.lower() and len(log.get("topics", [])) >= 3 and log["topics"][0].lower() == TRANSFER_TOPIC.lower() and log["topics"][2].lower() == to_wallet_padded.lower()): amount_hex = log.get("data", "0x0") amount = int(amount_hex, 16) # Allow up to 1% slippage if amount >= int(expected_usdc_wei * 0.99): return { "verified": True, "amount_usdc": amount / 1_000_000, "block": int(receipt.get("blockNumber", "0x0"), 16), } return {"verified": False, "reason": f"No USDC transfer found to {to_wallet} in this transaction"}