Carrito
Cómo se identifica el carrito de cada visitante y de quién es, cómo añadir, ver y quitar productos, calcular el envío y el descuento de un cupón, y pagar para crear el pedido.
Cómo se identifica el carrito
Cada visitante tiene su carrito, aunque no haya iniciado sesión. Se identifica con la cabecera user-ip-address:
- Genera un identificador propio para cada visitante, difícil de adivinar, de al menos 16 caracteres. Un UUID sirve.
- Guárdalo en el navegador o en la app, en una cookie o en el almacenamiento local.
- Envíalo en todas las llamadas del carrito.
curl https://<dominio-de-la-empresa>/api/v1/ecommerce/carts/shipments/calculate?type=DOMICILIO \
-H "user-ip-address: 3f2b8c1e-9d4a-4e7b-8a1c-2b5d6e7f8a90" \
-H "Accept: application/json"
Si el visitante inicia sesión o se registra enviando esta cabecera, su carrito pasa a ser de su cuenta.
De quién es el carrito:
- Mientras nadie inicia sesión, el carrito es de visitante y solo lo protege el identificador. Por eso tiene que ser difícil de adivinar.
- Cuando el visitante inicia sesión o se registra, el carrito pasa a su cuenta. Desde ese momento solo esa cuenta lo ve, lo cambia o lo paga, aunque otro conozca el identificador.
- Con el identificador del carrito de otra cuenta, todas las rutas responden como si no existiera: sin carrito o 404
CART_NOT_FOUND. Iniciar sesión con ese identificador tampoco se lleva el carrito.
Todas las rutas del carrito, y también pagar, exigen que la tienda esté publicada. Con la tienda en privado responden igual que el catálogo: 404 STORE_NOT_FOUND si nunca se publicó, o 503 STORE_PAUSED si estuvo publicada y está cerrada un tiempo.
Añadir un producto
POST /api/v1/ecommerce/carts
Si el visitante aún no tiene carrito, se crea con este primer producto. No pide token; si se envía el del cliente, el carrito queda en su cuenta.
| Campo | Tipo | Obligatorio | Reglas |
|---|---|---|---|
item_id |
número | Sí | Un producto a la venta en la tienda. Los servicios no se venden por aquí |
quantity |
número | Sí | Entre 1 y 100000 |
variants |
lista | No | Solo si el producto tiene variantes (talla, color…). Hasta 100 |
variants[].id |
número | Con variants |
Una variante de ese mismo producto |
variants[].quantity |
número | Con variants |
Entre 1 y 100000 |
{ "item_id": 45, "quantity": 2, "variants": [ { "id": 7, "quantity": 2 } ] }
La cantidad reemplaza, no suma. Si el producto ya estaba en el carrito, su línea pasa a tener la cantidad enviada. Para quitarlo, usa «Quitar un producto», más abajo.
El precio lo calcula el ERP: precio de oferta si lo hay, y precios por cantidad si el producto los tiene.
Respuesta
El carrito completo, con los totales ya recalculados. Tiene la misma forma que al verlo (ver «Ver el carrito»).
Si algo sale mal
| HTTP | code | Cuándo | Qué hacer |
|---|---|---|---|
| 404 | STORE_NOT_FOUND |
La tienda no está publicada | Publícala en el ERP |
| 503 | STORE_PAUSED |
La tienda está pausada | Vuelve a intentarlo más tarde (Retry-After) |
| 400 | CART_IDENTIFIER_REQUIRED |
Falta la cabecera user-ip-address o tiene menos de 16 caracteres |
Envía el identificador del carrito |
| 422 | VALIDATION_ERROR |
El producto no existe o no está a la venta, la cantidad no es válida, o una variante no es de ese producto | Corrige el campo que indica errors |
| 503 | STORE_NOT_CONFIGURED |
La tienda no tiene almacén | Avisa a la tienda |
Ver el carrito
GET /api/v1/ecommerce/carts
Devuelve el carrito activo del visitante, con sus líneas y el envío elegido. Si no tiene, data llega en null: no es un error.
{ "success": true, "message": "Carrito de compras obtenido de forma correcta.", "data": null }
Cuando hay carrito, data tiene la misma forma que la respuesta de «Quitar un producto», más shipment con el envío elegido o null.
Si algo sale mal
| HTTP | code | Cuándo | Qué hacer |
|---|---|---|---|
| 404 | STORE_NOT_FOUND |
La tienda no está publicada | Publícala en el ERP |
| 503 | STORE_PAUSED |
La tienda está pausada | Vuelve a intentarlo más tarde (Retry-After) |
| 400 | CART_IDENTIFIER_REQUIRED |
Falta la cabecera user-ip-address o tiene menos de 16 caracteres |
Envía el identificador del carrito |
Quitar un producto
DELETE /api/v1/ecommerce/carts/{code}/items/{id}
| Parámetro | Dónde | Qué es |
|---|---|---|
code |
Ruta | Código del carrito, con formato UUID |
id |
Ruta | Identificador de la línea del carrito (items[].id), no del producto |
user-ip-address |
Cabecera | El identificador del carrito. Obligatorio si no se envía el token del cliente |
El carrito tiene que ser de quien llama: por el token del cliente, si el carrito es de su cuenta, o por el identificador de la cabecera. Un carrito ajeno responde igual que uno inexistente.
Respuesta
Devuelve el carrito actualizado, con los totales ya recalculados.
{
"success": true,
"message": "Producto eliminado del carrito correctamente.",
"data": {
"code": "3f2b8c1e-9d4a-4e7b-8a1c-2b5d6e7f8a90",
"order": "000123",
"status": "PROCESANDO PEDIDO",
"phase": null,
"can_cancel": true,
"payment_method": "",
"quantity": 2,
"subtotal": 84.75,
"total_taxes": 15.25,
"total_shipments": 0,
"discounts": 0,
"total": 100,
"items": [
{
"id": 88,
"quantity": 2,
"subtotal": 84.75,
"total_taxes": 15.25,
"discounts": 0,
"total": 100,
"item": { "id": 45, "description": "Polo algodón", "price": 50.0, "stock": 10.0 }
}
],
"cash_order_id": "0000"
}
}
| Campo | Qué es |
|---|---|
items[].id |
Identificador de la línea. Es el que se usa para quitarla |
items[].item |
Copia del producto en el momento de añadirlo |
total |
Envío + suma de las líneas − descuentos |
Si algo sale mal
| HTTP | code | Cuándo | Qué hacer |
|---|---|---|---|
| 404 | STORE_NOT_FOUND |
La tienda no está publicada | Publícala en el ERP |
| 503 | STORE_PAUSED |
La tienda está pausada | Vuelve a intentarlo más tarde (Retry-After) |
| 400 | CART_IDENTIFIER_REQUIRED |
Sin token y sin la cabecera user-ip-address, o con menos de 16 caracteres |
Envía el identificador del carrito |
| 404 | CART_NOT_FOUND |
El carrito no existe o no es de quien llama | Revisa el código y el identificador |
| 404 | CART_ITEM_NOT_FOUND |
Esa línea no está en el carrito | Vuelve a consultar el carrito |
| 409 | CART_ALREADY_PAID |
El carrito ya se pagó | Nada: un pedido pagado no se modifica |
| 409 | CART_NOT_EDITABLE |
El carrito ya no está activo, por ejemplo porque se anuló | Empieza un carrito nuevo |
Calcular el envío del carrito
GET /api/v1/ecommerce/carts/shipments/calculate
Calcula cuánto cuesta enviar el carrito del visitante. Solo calcula: no guarda nada en el carrito.
| Parámetro | Dónde | Obligatorio | Reglas |
|---|---|---|---|
user-ip-address |
Cabecera | Sí | El identificador del carrito |
type |
Query | Sí | Uno de los tipos de envío que la tienda tiene activos, en mayúsculas. Si no activó ninguno, vale DOMICILIO, AGENCIA o TIENDA |
ubigeo |
Query | No | Distrito de destino, 6 cifras. Si no se envía y el cliente tiene sesión, se usa su dirección por defecto |
agency_code |
Query | No | Solo con AGENCIA: devuelve solo esa agencia |
establishment_id |
Query | No | Solo con TIENDA: devuelve solo ese establecimiento. Tiene que estar visible en la tienda |
El peso es la suma del peso de cada producto por su cantidad. Hasta 1 kg se cobra el precio por kilo; a partir de ahí se suma el precio por kilo adicional. Si la tienda lo tiene configurado, al total se le suma el IGV (18 %), sin redondear.
Respuesta a domicilio
{
"success": true,
"message": "Información procesada de forma correcta.",
"data": { "type": { "price": 12, "lead_time": 1 }, "weight": 1.2, "subtotal": 12, "total": 14.16 }
}
Respuesta a agencia
Una lista con cada agencia que llega al destino, con su propio subtotal y total. Si ninguna llega, la lista viene vacía.
{
"success": true,
"message": "Información procesada de forma correcta.",
"data": {
"type": [ { "code": "OLVA", "name": "Olva Courier", "logo": "…", "price": 15.5, "lead_time": 3, "subtotal": 15.5, "total": 18.29 } ],
"weight": 2.5
}
}
Respuesta con recojo en tienda
Los establecimientos del mismo distrito, provincia o departamento. El subtotal y el total son 0.
{
"success": true,
"message": "Información procesada de forma correcta.",
"data": {
"type": [ { "id": 1, "description": "Tienda Centro", "address": "Av. Lima 123", "lead_time": 1, "logo": null, "latitude": "-12.04", "longitude": "-77.03", "department": "LIMA", "province": "LIMA", "district": "LIMA" } ],
"weight": 1.2,
"subtotal": 0,
"total": 0
}
}
Si algo sale mal
| HTTP | code | Cuándo | Qué hacer |
|---|---|---|---|
| 404 | STORE_NOT_FOUND |
La tienda no está publicada | Publícala en el ERP |
| 503 | STORE_PAUSED |
La tienda está pausada | Vuelve a intentarlo más tarde (Retry-After) |
| 422 | VALIDATION_ERROR |
Falta el tipo o no está activo en la tienda, el ubigeo no tiene 6 cifras o el establecimiento no existe | Corrige el parámetro que indica errors |
| 400 | CART_IDENTIFIER_REQUIRED |
Falta la cabecera user-ip-address |
Envía el identificador del carrito |
| 404 | CART_NOT_FOUND |
No hay un carrito activo con ese identificador | Añade un producto primero |
| 422 | UBIGEO_REQUIRED |
No se envió ubigeo y no hay sesión | Envía el ubigeo |
| 422 | DEFAULT_ADDRESS_MISSING |
No se envió ubigeo y el cliente no tiene dirección por defecto | Envía el ubigeo o pide al cliente una dirección |
| 422 | DESTINATION_NOT_FOUND |
La dirección por defecto no tiene ubigeo | Envía el ubigeo |
| 422 | SHIPPING_AREA_NOT_COVERED |
A domicilio: no hay tarifa para ese distrito | Ofrece otro tipo de envío |
Calcular el descuento de un cupón
GET /api/v1/ecommerce/carts/coupons/calculate
Comprueba un cupón y devuelve cuánto descontaría. No aplica el cupón al carrito.
| Parámetro | Dónde | Obligatorio | Reglas |
|---|---|---|---|
code |
Query | Sí | Código del cupón. Máximo 50 caracteres |
user-ip-address |
Cabecera | No | El identificador del carrito. Sin él se comprueba el cupón, pero no se puede calcular el importe |
Authorization |
Cabecera | No | El token del cliente. Hace falta para los cupones de un solo uso o reservados a ciertos clientes |
Esta ruta admite 10 peticiones por minuto, para que no se puedan probar códigos. Ver límites.
Respuesta
data es el importe del descuento.
{ "success": true, "message": "Información procesada de forma correcta.", "data": 15.0 }
- Si el cupón es de porcentaje, se aplica sobre el importe de los productos o sobre el envío, según cómo lo configuró la tienda.
- Si es de importe fijo, se devuelve ese importe, con un tope: nunca más que el importe sobre el que se aplica.
Si algo sale mal
| HTTP | code | Cuándo | Qué hacer |
|---|---|---|---|
| 404 | STORE_NOT_FOUND |
La tienda no está publicada | Publícala en el ERP |
| 503 | STORE_PAUSED |
La tienda está pausada | Vuelve a intentarlo más tarde (Retry-After) |
| 422 | VALIDATION_ERROR |
Falta el código o es demasiado largo | Corrige el código |
| 422 | COUPON_NOT_FOUND |
El cupón no existe o no está activo | Pide al cliente que revise el código |
| 422 | COUPON_EXPIRED |
El cupón aún no empieza o ya venció | — |
| 401 | COUPON_LOGIN_REQUIRED |
El cupón exige sesión | Pide al cliente que inicie sesión |
| 422 | COUPON_ALREADY_USED |
El cliente ya usó este cupón | — |
| 422 | COUPON_USER_NOT_ELIGIBLE |
El cupón no es para este cliente | — |
| 422 | COUPON_REQUIRES_CART |
El cupón es válido, pero hace falta un carrito para calcular el descuento | Envía el identificador del carrito, o añade productos |
| 422 | COUPON_PRODUCTS_NOT_IN_CART |
El carrito no tiene ningún producto de la promoción | Añade uno |
| 422 | COUPON_MIN_AMOUNT_NOT_REACHED |
El carrito no llega al importe mínimo | Añade productos |
| 422 | COUPON_MAX_AMOUNT_EXCEEDED |
El carrito supera el importe máximo | Quita productos |
| 429 | RATE_LIMITED |
Más de 10 peticiones por minuto | Espera lo que indica Retry-After |
COUPON_LOGIN_REQUIRED es un 401 distinto del de sesión: no trae error_code y no significa que el token haya caducado.
Pagar y crear el pedido
POST /api/v1/ecommerce/carts/pay
Convierte el carrito en un pedido del ERP. Es una solicitud de pedido: el cobro y el envío los gestiona después la tienda desde el ERP.
Pide el token del cliente y la cabecera user-ip-address con el identificador del carrito. El carrito tiene que ser de visitante (sin cuenta) o de la cuenta del cliente que paga: el de otra cuenta responde como si no existiera. Lo normal es registrarse o iniciar sesión enviando la cabecera, y pagar justo después.
Cuerpo
| Campo | Tipo | Obligatorio | Reglas |
|---|---|---|---|
contact.first_name |
texto | Sí | Nombre de quien recibe el aviso. Máximo 50 caracteres |
contact.last_name |
texto | No | Máximo 50 caracteres |
contact.country_code |
texto | Sí | Prefijo del país, por ejemplo +51. Máximo 5 caracteres |
contact.phone |
texto | Sí | WhatsApp de contacto. Exactamente 9 cifras |
origin |
texto | Sí | WEB o APP |
payment_method |
texto | No | Uno de los métodos de pago de la tienda. Sin él se usa EFECTIVO |
billing.document_type_id |
texto | No | 01 (factura) o 03 (boleta) |
billing.document_number |
texto | No | DNI de 8 cifras o RUC de 11. Una factura exige RUC |
billing.name |
texto | No | Nombre o razón social. Máximo 250 caracteres |
data |
objeto | No | Lo que devolvió la consulta de DNI o RUC de ese mismo documento: ruc o dni, nombre_o_razon_social o nombre_completo, direccion, condicion, estado, ubigeo |
{
"contact": { "first_name": "Ana", "last_name": "Pérez", "country_code": "+51", "phone": "987654321" },
"origin": "WEB",
"payment_method": "TRANSFERENCIA",
"billing": { "document_type_id": "03", "document_number": "45678912", "name": "Ana Pérez" }
}
El teléfono y el prefijo solo se guardan como dato de contacto del pedido: no se envía ningún código ni se comprueba el país.
Si se indica un documento de facturación que la empresa aún no tiene, se da de alta como cliente con los datos de data. Sin nombre, ni en data ni en billing.name, responde BILLING_NAME_REQUIRED.
Respuesta
{
"success": true,
"message": "Información procesada de forma correcta.",
"data": { "number": "PED P001-15", "order_id": 57 }
}
| Campo | Qué es |
|---|---|
number |
Número del pedido en el ERP, para mostrárselo al cliente |
order_id |
Identificador interno del pedido en el ERP |
Al pagar se reserva el stock de los productos y se descuenta el de sus variantes (talla, color…). Si el pedido se anula, ambos vuelven.
Si algo sale mal
| HTTP | code | Cuándo | Qué hacer |
|---|---|---|---|
| 404 | STORE_NOT_FOUND |
La tienda no está publicada | Publícala en el ERP |
| 503 | STORE_PAUSED |
La tienda está pausada | Vuelve a intentarlo más tarde (Retry-After) |
| 401 | SESSION_EXPIRED o TOKEN_INVALID |
Falta el token o no es válido | Pide al cliente que inicie sesión |
| 403 | NOT_A_STORE_USER |
El token no es de un cliente de la tienda | Usa el token de un cliente |
| 422 | VALIDATION_ERROR |
Falta el contacto u origin, el teléfono no tiene 9 cifras, el método de pago no está disponible o el documento no es válido |
Corrige el campo que indica errors |
| 400 | CART_IDENTIFIER_REQUIRED |
Falta la cabecera user-ip-address |
Envía el identificador del carrito |
| 404 | CART_NOT_FOUND |
No hay carrito activo con ese identificador, o es de otra cuenta | Añade un producto primero |
| 409 | CART_ALREADY_PAID |
El carrito ya se pagó | Consulta el pedido en el historial |
| 422 | CART_EMPTY |
El carrito no tiene productos | Añade uno |
| 422 | BILLING_NAME_REQUIRED |
Falta el nombre del documento de facturación | Envía billing.name o data |
| 409 | OUT_OF_STOCK |
No hay stock suficiente de algún producto | Quita o reduce ese producto |
| 503 | STORE_NOT_CONFIGURED |
Falta el almacén o la serie de pedidos en el ERP | Avisa a la tienda |