Saltar al contenido principal
Centro de Ayuda

Creando Pedidos vía API: el estado Abierto (Created)

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.

Por qué esto importa

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.

Casos de uso

1. Ventas asistidas / fuera de línea (teléfono, WhatsApp, chat en vivo, tienda física)

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.

2. Conectores con marketplaces o canales externos

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.

3. Pre-ventas, backorders y flujos de cotización a pedido

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.

Requisitos antes de crear un pedido

  • El pedido requiere un 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.
  • Si 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.
  • Define 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.

Cómo se decide shipping_required cuando no lo envías

Si 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 false200 OK, sin necesidad de dirección
Solo 1 producto tipo cita (appointment) (omitido) Se deriva como false200 OK, sin necesidad de dirección
Solo 1 producto físico (omitido) Se deriva como true400 Customer without shipping address
1 físico + 1 digital (mixto) (omitido) Se deriva como true400 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.

  • Prefiere 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").
  • Los productos se referencian por id numérico (o variant_id para variantes), no por SKU.

Ejemplo completo: un cliente completamente nuevo

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.

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.

Ejemplo mínimo: crear un pedido Abierto

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.

Valores válidos de status, como referencia

Valor 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.

El efecto colateral del email

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:

  • Desactiva el toggle “New Manual Order” en Admin → Configuración → General → Emails. Esto desactiva el correo para todo pedido creado sin estado, incluyendo pedidos reales — hazlo solo si tu integración es la única fuente de este tipo de pedidos, o si gestionas la notificación por otro canal.
  • En desarrollo, dirige los pedidos de prueba a un cliente que controles (una bandeja real tuya, o un alias), nunca al correo de un cliente real, hasta haber verificado el comportamiento esperado.

Transicionar el pedido después

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.

Preguntas frecuentes

¿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.

Recursos

Si tienes más preguntas, no dudes en contactarnos.

¡Comenzá tu viaje con nosotros!

Probar gratis por 7 días. No se requiere tarjeta de crédito.