Comprar
Crear una orden de compra. Descuenta el saldo del comprador y acredita al vendedor de forma atómica.
POST /market/order
Compra un producto. La operación es atómica: valida inventario y saldo, descuenta el total al comprador, acredita el neto al vendedor y la comisión a la plataforma, y crea la orden. Comprar es abierto para cualquier usuario autenticado.
El comprador paga el precio publicado (precio × cantidad); la comisión se descuenta del vendedor (ver modelo de comisión).
Autenticación
Bearer Token. Rate limit: 5 req/20s por usuario. No puedes comprar tu propio producto.
Request — producto digital o servicio
curl -X POST https://api.qvapay.com/market/order \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{ "product_uuid": "p1q2r3...", "quantity": 1 }'Request — producto con variantes
Si el producto define variantes (Color, Talla…), variant_uuid es obligatorio; sin él la API responde 400 con code: "VARIANT_REQUIRED". Las variantes disponibles se ven en la vitrina pública o vía GET /market/products/{uuid}/variants (dueño). El precio cobrado es el de la variante (o el del producto si la variante no define uno) y el stock se descuenta por variante.
curl -X POST https://api.qvapay.com/market/order \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{ "product_uuid": "p1q2r3...", "variant_uuid": "v1a2...", "quantity": 1 }'Request — producto físico
Los productos físicos requieren una dirección de envío estructurada (internacional): una dirección guardada (shipping_address_id) o una nueva (new_address). Si el producto define destinos (ship_to), la dirección debe estar dentro de ellos; si no, la API responde 400 con code: "SHIP_TO_BLOCKED".
Además, la tienda debe tener zonas de envío configuradas: la zona que cubra el destino determina el costo de la mensajería (shipping_amount, que se suma al total y va íntegro al vendedor, sin comisión) y el plazo estimado. Sin zonas configuradas la API responde 409 con code: "SHIPPING_NOT_CONFIGURED"; si ninguna zona cubre el destino, 400 con code: "NO_SHIPPING_ZONE". Una zona puede ofrecer envío gratis a partir de cierto subtotal.
Si varias zonas de la tienda cubren la dirección (por ejemplo, zonas por reparto o municipio dentro de una misma provincia), el comprador debe indicar cuál en shipping_zone_id (el id de la zona en shop.shipping.zones); sin ella la API responde 400 con code: "SHIPPING_ZONE_REQUIRED". Con una sola zona aplicable el campo es opcional.
curl -X POST https://api.qvapay.com/market/order \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"product_uuid": "p1q2r3...",
"quantity": 2,
"new_address": {
"recipient_name": "John Doe",
"line1": "1600 Amphitheatre Pkwy",
"city": "Mountain View",
"state": "CA",
"postal_code": "94043",
"phone": "+13105551234",
"save_as_default": true
},
"note": "Dejar en recepción"
}'Parámetros (body)
| Parámetro | Tipo | Requerido | Descripción |
|---|---|---|---|
product_uuid | string | Sí | UUID del producto |
variant_uuid | string | Condicional | UUID de la variante. Requerido si el producto tiene variantes activas |
quantity | number (≥1, ≤999) | No (default 1) | Cantidad |
shipping_address_id | string (uuid) | Condicional | Dirección guardada. Requerido para físicos si no se envía new_address |
shipping_zone_id | string | Condicional | Zona de envío elegida. Requerido para físicos cuando varias zonas de la tienda cubren la dirección |
new_address | object | Condicional | Dirección nueva (se guarda en el perfil). Ver campos abajo |
note | string (≤1200) | No | Instrucciones de entrega (opcional) |
idempotency_key | string (8–64, [A-Za-z0-9._-]) | No | Clave de idempotencia por intento de compra: un reintento con la misma clave devuelve la orden ya creada (200 con duplicate: true) en vez de duplicarla |
cart_key | string (8–64, [A-Za-z0-9._-]) | No | Batch de carrito: las órdenes que comparten cart_key cobran el envío una sola vez por tienda (lo cobra la primera orden física del batch; ventana de 1h) |
new_address (productos físicos): recipient_name (req), line1 (req), city (req), state (req, ≤64), postal_code, line2, phone, label, country (ISO-2, default US), save_as_default (boolean). Reglas por país: con country: "US", state debe ser un estado USPS de 2 letras y postal_code un ZIP válido (requerido); con country: "CU", state debe ser un código de provincia de 3 letras (LHA, MTZ…) y postal_code es opcional; en otros países state es texto libre y postal_code opcional.
Response 201
{
"order": {
"uuid": "o1r2d3...",
"status": "paid",
"total": 44.98,
"gift_card_amount": 0,
"shipping_amount": 5.00,
"cash_paid": 44.98,
"quantity": 2
},
"transaction_uuid": "a1b2c3d4-..."
}total= subtotal del producto +shipping_amount.cash_paides lo debitado del saldo (total − gift card aplicada).- Los productos digitales quedan en
status: "fulfilled"de inmediato; el entregable (digital_payload) se consulta en el detalle de la compra. - Físico/servicio quedan en
status: "paid"para que el vendedor los gestione.
Errores
| Código | Descripción |
|---|---|
400 | Saldo insuficiente |
400 | Falta dirección de envío para un producto físico |
400 | El producto requiere elegir una variante (code: "VARIANT_REQUIRED") |
400 | El vendedor no envía a ese destino (code: "SHIP_TO_BLOCKED") |
400 | La tienda no tiene una zona de envío para ese destino (code: "NO_SHIPPING_ZONE") |
400 | Varias zonas cubren la dirección y falta shipping_zone_id (o no cubre la dirección) (code: "SHIPPING_ZONE_REQUIRED") |
409 | La tienda aún no configuró sus envíos (code: "SHIPPING_NOT_CONFIGURED") |
400 | No puedes comprar tu propio producto |
400 | La tienda no está activa |
400 | Dirección de envío no encontrada |
404 | Producto o variante no disponible |
409 | No hay stock suficiente |
429 | Rate limit excedido |
500 | Error al procesar la compra |