Creando un Pedido Manual
Añade y crea un pedido manual añadiendo productos al carrito de compra e introduce los datos de facturación y envío del cliente.
Esta es una guía avanzada, orientada a desarrolladores. Se asume que ya sabes hacer peticiones HTTP autenticadas y que estás construyendo una app o integración sobre la API de Jumpseller — no solo usando el Panel de Administración. Si buscas la versión “apunta y haz clic” de esta funcionalidad, revisa Pedidos Manuales en su lugar.
El endpoint POST /v1/orders.json te permite crear un pedido de forma programática, a nombre de un cliente. La mayoría de las integraciones definen status explícitamente (Paid, Pending Payment, Canceled, Abandoned). Existe un quinto estado que habilita un patrón muy útil: omitir por completo el campo status, con lo que el pedido se crea como Created — el mismo estado que el Panel de Administración etiqueta como “Open” (Abierto) en su filtro de Pedidos.
Un pedido en estado Created/Abierto no está pagado ni abandonado — es un carro de compra en vivo, asociado a un cliente real, que queda en Jumpseller esperando a completarse. Jumpseller le envía automáticamente al cliente un correo con un enlace de pago y un código QR, para que pueda terminar la compra por su cuenta a través del checkout de Jumpseller — no necesitas construir una interfaz de pago propia.
Ese único mecanismo es la base de varios patrones de integración.
Un vendedor o un chatbot arma el carro a nombre del cliente y crea el pedido vía API sin especificar status. El cliente recibe un correo con un botón “Completar el Pago” y un código QR — tu app no necesita construir un checkout propio. Es el equivalente vía API del flujo de “enviar un enlace de pago” de Pedidos Manuales en el Panel de Administración, pero automatizable y programable.
Cuando sincronizas pedidos desde un canal externo (marketplace, POS, plataforma de suscripciones) y el pago aún no está resuelto del lado de Jumpseller, crea el pedido como Created/Abierto primero. Una vez que tu integración confirme el pago externamente, transiciónalo con un PUT posterior a Paid o Pending Payment.
Crea el pedido apenas se acepte una cotización o se solicite un producto en backorder, déjalo Abierto, y solo cámbialo a Paid/Pending Payment cuando se confirme el stock o el comerciante lo apruebe. El cliente ya tiene un enlace de pago esperando en su bandeja de entrada para cuando llegue el momento de pagar.
customer.id — un email por sí solo no basta. Enviar customer: { "email": "..." } sin un id, incluso para un cliente que ya existe, devuelve "Account not found". No existe un atajo de auto-creación por email: busca primero al cliente (GET /v1/customers.json?email=...), créalo con POST /v1/customers.json si aún no existe, y siempre envía su id numérico en el payload del pedido.shipping_required es true, el cliente ya debe tener una dirección de envío guardada. Jumpseller valida esto al momento de crear el pedido — el bloque shipping_address que envías en línea se usa solo para mostrar información, pero el registro del cliente necesita al menos una dirección guardada, o la petición es rechazada. Agrega un shipping_address al crear o actualizar el cliente si no tiene ninguna.shipping_required: false para productos digitales/virtuales o citas, y el requisito de dirección desaparece por completo — confirmado con un cliente que no tenía ninguna dirección guardada. Esto no depende del type del producto (digital, appointment, etc.); es el flag shipping_required del propio pedido lo que Jumpseller revisa. Omite también shipping_method_name/shipping_price, ya que no hay nada que despachar.shipping_required cuando no lo envíasSi omites shipping_required por completo, Jumpseller lo deriva para todo el carrito, no producto por producto — probado contra un cliente sin ninguna dirección guardada:
| Contenido del carrito |
shipping_required enviado | Resultado |
|---|---|---|
| Solo 1 producto digital | (omitido) | Se deriva como false → 200 OK, sin necesidad de dirección |
| Solo 1 producto tipo cita (appointment) | (omitido) | Se deriva como false → 200 OK, sin necesidad de dirección |
| Solo 1 producto físico | (omitido) | Se deriva como true → 400 Customer without shipping address
|
| 1 físico + 1 digital (mixto) | (omitido) | Se deriva como true → 400 Customer without shipping address
|
| 1 producto digital |
true (forzado) |
400 Customer without shipping address, a pesar de ser digital |
| 1 producto físico |
false (forzado) | 200 OK, se salta el requisito de dirección incluso siendo físico |
Dos conclusiones: basta un solo ítem físico en el carrito para exigir dirección, aunque el resto sea digital — un flujo completamente libre de dirección solo funciona si el carrito no tiene ningún producto físico. Y el valor de shipping_required que envías explícitamente siempre gana sobre lo que el tipo de producto sugeriría por sí solo, en ambos sentidos.
shipping_method_name + shipping_price en vez de shipping_method_id. La forma con _id activa una validación de cobertura por comuna que suele rechazar pedidos perfectamente válidos. La forma basada en el nombre se salta esa validación por completo.region debe ser un código, no un nombre (por ejemplo "12", no "Metropolitana de Santiago").id numérico (o variant_id para variantes), no por SKU.Como el endpoint de pedidos necesita un customer.id (ver arriba), crear un pedido para alguien que nunca ha comprado en la tienda es un flujo de tres pasos: buscar, crear si no existe, y luego crear el pedido.
email en GET /v1/customers.json puede ser poco confiable — siempre verifica que el campo email del registro devuelto coincida realmente antes de reutilizar un id. Ante la duda, trata un resultado que no coincide como "no encontrado" y crea el cliente. cURL (tres peticiones)
# 1. Buscar al cliente por email
curl -s -u "$JUMPSELLER_LOGIN:$JUMPSELLER_AUTH_TOKEN" \
"https://api.jumpseller.com/v1/customers.json?email=nuevo.cliente@example.com&limit=1"
# 2. ¿No existe? Créalo con una dirección de envío
curl -s -u "$JUMPSELLER_LOGIN:$JUMPSELLER_AUTH_TOKEN" \
-X POST "https://api.jumpseller.com/v1/customers.json" \
-H "Content-Type: application/json" \
-d '{
"customer": {
"email": "nuevo.cliente@example.com",
"fullname": "Cliente Nuevo",
"status": "approved",
"shipping_address": {
"name": "Cliente", "surname": "Nuevo",
"address": "Calle Principal 123", "city": "Santiago",
"region": "12", "country": "CL", "municipality": "Santiago"
}
}
}'
# => devuelve el nuevo cliente, ej. {"customer": {"id": 20983999, ...}}
# 3. Crear el pedido usando el id del paso 1 o 2
curl -s -u "$JUMPSELLER_LOGIN:$JUMPSELLER_AUTH_TOKEN" \
-X POST "https://api.jumpseller.com/v1/orders.json" \
-H "Content-Type: application/json" \
-d '{
"order": {
"shipping_method_name": "Envío Estándar",
"shipping_price": 0,
"shipping_required": true,
"customer": { "id": 20983999 },
"products": [ { "id": 34745971, "qty": 1, "price": 2000.0 } ]
}
}'
Ruby (de principio a fin)
require 'net/http'
require 'json'
require 'uri'
def api(method, path, token_pair, body = nil)
uri = URI("https://api.jumpseller.com/v1#{path}")
request = Net::HTTP.const_get(method.to_s.capitalize).new(uri)
request.basic_auth(*token_pair)
if body
request['Content-Type'] = 'application/json'
request.body = body.to_json
end
JSON.parse(Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(request) }.body)
end
auth = [ENV['JUMPSELLER_LOGIN'], ENV['JUMPSELLER_AUTH_TOKEN']]
email = 'nuevo.cliente@example.com'
found = api(:get, "/customers.json?email=#{email}&limit=1", auth)
customer = found.find { |c| c['customer']['email'] == email }&.dig('customer')
customer ||= api(:post, '/customers.json', auth, {
customer: {
email: email,
fullname: 'Cliente Nuevo',
status: 'approved',
shipping_address: {
name: 'Cliente', surname: 'Nuevo',
address: 'Calle Principal 123', city: 'Santiago',
region: '12', country: 'CL', municipality: 'Santiago'
}
}
})['customer']
order = api(:post, '/orders.json', auth, {
order: {
shipping_method_name: 'Envío Estándar',
shipping_price: 0,
shipping_required: true,
customer: { id: customer['id'] },
products: [{ id: 34745971, qty: 1, price: 2000.0 }]
# sin la clave "status" => el pedido queda como "Created" / Abierto
}
})
puts order.dig('order', 'status_enum') # => "created"
Python (de principio a fin)
import os
import requests
AUTH = (os.environ["JUMPSELLER_LOGIN"], os.environ["JUMPSELLER_AUTH_TOKEN"])
BASE = "https://api.jumpseller.com/v1"
email = "nuevo.cliente@example.com"
found = requests.get(f"{BASE}/customers.json", auth=AUTH, params={"email": email, "limit": 1}).json()
customer = next((c["customer"] for c in found if c["customer"]["email"] == email), None)
if customer is None:
customer = requests.post(
f"{BASE}/customers.json",
auth=AUTH,
json={
"customer": {
"email": email,
"fullname": "Cliente Nuevo",
"status": "approved",
"shipping_address": {
"name": "Cliente", "surname": "Nuevo",
"address": "Calle Principal 123", "city": "Santiago",
"region": "12", "country": "CL", "municipality": "Santiago",
},
}
},
).json()["customer"]
order = requests.post(
f"{BASE}/orders.json",
auth=AUTH,
json={
"order": {
"shipping_method_name": "Envío Estándar",
"shipping_price": 0,
"shipping_required": True,
"customer": {"id": customer["id"]},
"products": [{"id": 34745971, "qty": 1, "price": 2000.0}],
# sin la clave "status" => el pedido queda como "Created" / Abierto
}
},
).json()["order"]
print(order["status_enum"]) # => "created"
La misma lógica de tres pasos aplica en Node.js y PHP — GET para buscar al cliente por email, POST para crearlo si no hay coincidencia, y luego POST el pedido con el id resuelto, siguiendo el mismo formato de petición mostrado en el ejemplo mínimo a continuación.
La única diferencia respecto a una creación de pedido normal es lo que omites: ninguna clave status en el payload.
cURL
curl -u "$JUMPSELLER_LOGIN:$JUMPSELLER_AUTH_TOKEN" \
-X POST "https://api.jumpseller.com/v1/orders.json" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"order": {
"shipping_method_name": "Envío Estándar",
"shipping_price": 0,
"shipping_required": true,
"customer": { "id": 20983951 },
"products": [
{ "id": 34745971, "qty": 1, "price": 2000.0 }
]
}
}'
Ruby
require 'net/http'
require 'json'
require 'uri'
uri = URI('https://api.jumpseller.com/v1/orders.json')
request = Net::HTTP::Post.new(uri)
request.basic_auth(ENV['JUMPSELLER_LOGIN'], ENV['JUMPSELLER_AUTH_TOKEN'])
request['Content-Type'] = 'application/json'
request.body = {
order: {
shipping_method_name: 'Envío Estándar',
shipping_price: 0,
shipping_required: true,
customer: { id: 20983951 },
products: [
{ id: 34745971, qty: 1, price: 2000.0 }
]
# sin la clave "status" => el pedido queda como "Created" / Abierto
}
}.to_json
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(request) }
order = JSON.parse(response.body)
puts order.dig('order', 'status_enum') # => "created"
Python
import os
import requests
response = requests.post(
"https://api.jumpseller.com/v1/orders.json",
auth=(os.environ["JUMPSELLER_LOGIN"], os.environ["JUMPSELLER_AUTH_TOKEN"]),
json={
"order": {
"shipping_method_name": "Envío Estándar",
"shipping_price": 0,
"shipping_required": True,
"customer": {"id": 20983951},
"products": [
{"id": 34745971, "qty": 1, "price": 2000.0}
],
# sin la clave "status" => el pedido queda como "Created" / Abierto
}
},
)
order = response.json()["order"]
print(order["status_enum"]) # => "created"
Node.js
const auth = Buffer.from(
`${process.env.JUMPSELLER_LOGIN}:${process.env.JUMPSELLER_AUTH_TOKEN}`
).toString('base64');
const response = await fetch('https://api.jumpseller.com/v1/orders.json', {
method: 'POST',
headers: {
Authorization: `Basic ${auth}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
order: {
shipping_method_name: 'Envío Estándar',
shipping_price: 0,
shipping_required: true,
customer: { id: 20983951 },
products: [{ id: 34745971, qty: 1, price: 2000.0 }],
// sin la clave "status" => el pedido queda como "Created" / Abierto
},
}),
});
const { order } = await response.json();
console.log(order.status_enum); // => "created"
PHP
<?php
$ch = curl_init('https://api.jumpseller.com/v1/orders.json');
$payload = [
'order' => [
'shipping_method_name' => 'Envío Estándar',
'shipping_price' => 0,
'shipping_required' => true,
'customer' => ['id' => 20983951],
'products' => [
['id' => 34745971, 'qty' => 1, 'price' => 2000.0],
],
// sin la clave "status" => el pedido queda como "Created" / Abierto
],
];
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_USERPWD => getenv('JUMPSELLER_LOGIN') . ':' . getenv('JUMPSELLER_AUTH_TOKEN'),
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode($payload),
]);
$order = json_decode(curl_exec($ch), true)['order'];
echo $order['status_enum']; // "created"
La respuesta incluye un checkout_url y un review_url — ambos utilizables en tu propia interfaz si prefieres mostrar tú mismo el enlace de pago, además de (o en vez de) el correo automático.
status, como referenciaValor de status que envías |
status_enum resultante | Filtro en el Admin |
|---|---|---|
| (omitido) | created | Open (Abierto) |
Pending Payment | pending_payment | Pendiente |
Paid | paid | Pagado |
Canceled | canceled | Cancelado |
Abandoned | abandoned | Abandonado |
Enviar "status": "Open" o "status": "Created" literalmente devuelve un error 400 Estado inválido. La omisión es la única forma de llegar a este estado a través de la API. Los valores de status distinguen mayúsculas y minúsculas.
Definir "send_email": false en la petición no suprime el correo de “pedido recibido” cuando el pedido se crea sin status. Es la misma notificación “New Manual Order” (Nuevo Pedido Manual) descrita en Emails y Notificaciones de Pedidos — está ligada al evento de creación del pedido manual en sí, no a los correos de transición de estado de pago que send_email fue pensado para controlar.
En la práctica: cualquier pedido que tu app cree como Created/Abierto llega a la bandeja de entrada del cliente, con un enlace de pago y código QR en vivo, sin importar el valor de send_email.
Si eso es exactamente lo que quieres (casos de uso 1–3 más arriba), no necesitas hacer nada. Si necesitas crear pedidos de forma silenciosa — para pruebas automatizadas, entornos de staging o ensayos — tienes dos opciones, ambas a nivel de tienda completa y no por petición individual:
Una vez confirmado el pago (por tu propio sistema, un marketplace, o el comerciante), avanza el pedido con un PUT:
curl -u "$JUMPSELLER_LOGIN:$JUMPSELLER_AUTH_TOKEN" \
-X PUT "https://api.jumpseller.com/v1/orders/2372.json" \
-H "Content-Type: application/json" \
-d '{ "order": { "status": "Paid" } }'
Combina esto con una suscripción a webhooks sobre eventos de pedidos si necesitas reaccionar también a cambios de estado hechos desde el Panel de Administración, y no solo desde tu propia app.
¿Puedo definir status como "Open" directamente? No — el string literal es rechazado. Omite el campo status para llegar a ese estado.
¿Funciona igual para pedidos creados desde la pantalla “Crear Pedido” del Panel de Administración? Sí. La interfaz de Pedidos Manuales produce el mismo estado Created cuando eliges “enviar un enlace de pago” en vez de “pago ya procesado”. Revisa Pedidos Manuales para el flujo apunta y haz clic.
¿Se le cobrará al cliente automáticamente? No. Los pedidos Created/Abiertos no están pagados; el cliente debe completar el checkout (vía el enlace del correo, el código QR, o el checkout_url) para que se procese el pago.
¿Hay alguna forma de evitar el correo de enlace de pago solo para una petición puntual? Actualmente no — send_email: false no aplica a esta notificación. Revisa el efecto colateral del email más arriba para conocer las opciones a nivel de tienda.
Si tienes más preguntas, no dudes en contactarnos.
Comienza tu prueba gratuita de 7 días. No se requiere tarjeta de crédito.