ROKIConnect
Conectores Empezar API SDK Migrar MCP Portal EN

Cobrar en Wix con ROKI

Wix no deja publicar una pasarela sin su aprobacion, asi que no hay nada que instalar de un click. Lo que si podes hacer hoy es pegar este codigo en tu sitio con Velo y cobrar con tarjeta.

Antes que nada: puede que no necesites esto

Si lo que vendes tiene un precio fijo -una entrada, una consulta, una membresia- el boton de pago te resuelve en cinco minutos y sin escribir una linea de codigo. Esta receta es para el otro caso: cuando el monto depende de lo que el visitante elige y hay que calcularlo en el momento.

Cual te toca

El monto es siempre el mismoBoton de pago. No escribis codigo.
El visitante elige cantidad u opcionesEsta receta. El monto se calcula en el backend.
Tenes una tienda de Wix con carritoNinguna de las dos todavia: el carrito de Wix solo acepta pasarelas aprobadas por Wix. Escribinos y te avisamos cuando se abra.

Por que el monto va en el backend y no en la pagina

Cualquiera puede abrir la consola del navegador y cambiar lo que quiera de tu pagina, incluido un numero. Si el precio se calcula ahi, se puede pagar L 1.00 por algo de L 1,000.00 y el cobro sale perfecto. Por eso el archivo del calculo es .jsw, que en Velo corre en el servidor de Wix: el visitante manda que eligio, no cuanto cuesta.

Antes de cobrar de verdad

Corre el flujo entero con una llave sk_test_ y un monto chico, y no lo des por bueno hasta ver que el webhook te llega: es lo unico que confirma un cobro. Si el import de crypto te da problema en tu plan de Wix, instala crypto-js desde el gestor de paquetes del editor y usa su HMAC - el resto del codigo no cambia.

Como se hace

  1. En el editor de Wix activa Velo (arriba a la derecha, "Dev Mode" o "Modo desarrollador").
  2. En el panel de la izquierda, en Backend, crea un archivo llamado roki.jsw y pega el primer bloque de codigo.
  3. En Backend tambien, crea http-functions.js y pega el segundo bloque: es el que recibe la confirmacion de ROKI.
  4. Guarda tu llave secreta en Configuracion → Secrets Manager con el nombre ROKI_SECRET_KEY, y el secreto de firma del webhook como ROKI_WEBHOOK_SECRET. Nunca la escribas dentro del codigo: el codigo de la pagina lo puede leer cualquiera.
  5. En la pagina donde va el boton, pega el tercer bloque en el panel de codigo de esa pagina, y ajusta los nombres de los elementos a los tuyos.
  6. Publica el sitio. En el portal de ROKI registra la URL del webhook, que va a ser https://TUSITIO.com/_functions/rokiWebhook, y copia el secreto de firma al Secrets Manager.
  7. Proba con una llave sk_test_ antes de cobrar de verdad.

1. El backend que crea el pago

Guardalo como backend/roki.jsw. El .jsw importa: es lo que hace que esta funcion corra en el servidor de Wix y no en el navegador de quien te compra.

// backend/roki.jsw
// Corre en el servidor de Wix. El navegador NUNCA manda el monto: manda que eligio.
import { fetch } from 'wix-fetch';
import { getSecret } from 'wix-secrets-backend';

// El catalogo vive aca, del lado del servidor. Si tenes muchos productos, esto puede salir
// de una coleccion de Wix - lo que no puede es salir del navegador.
const PRECIOS = {
  entrada_general: 250,
  entrada_vip: 600,
};

export async function crearPago(articulo, cantidad) {
  const precio = PRECIOS[articulo];
  if (!precio) throw new Error('Articulo desconocido');

  const unidades = Math.min(Math.max(parseInt(cantidad, 10) || 1, 1), 20);
  const monto = precio * unidades;

  const llave = await getSecret('ROKI_SECRET_KEY');
  const referencia = 'wix-' + Date.now() + '-' + Math.random().toString(36).slice(2, 8);

  // Vence en 48 horas. La API lee las fechas en hora de Honduras (UTC-6), no en UTC.
  const vence = new Date(Date.now() + 48 * 3600 * 1000 - 6 * 3600 * 1000)
    .toISOString().slice(0, 19).replace('T', ' ');

  const r = await fetch('https://aura.roki.systems/api/connect/v1/payments', {
    method: 'post',
    headers: {
      'Authorization': 'Bearer ' + llave,
      'Content-Type': 'application/json',
      'Accept': 'application/json',
      // Reintentar no crea un segundo cobro: con la misma llave, ROKI devuelve el mismo pago.
      'Idempotency-Key': referencia,
    },
    body: JSON.stringify({
      amount: monto,
      external_reference: referencia,
      name: articulo + ' x' + unidades,
      expires_at: vence,
      metadata: { articulo, unidades: String(unidades) },
    }),
  });

  const datos = await r.json();
  if (r.status !== 201 && r.status !== 200) {
    console.error('ROKI respondio ' + r.status, datos);
    throw new Error('No se pudo crear el pago');
  }

  // Si ROKI nombra campos que ignoro, es un nombre equivocado nuestro. Aparece en el log.
  if (datos.warnings && datos.warnings.length) {
    console.warn('ROKI ignoro campos:', datos.warnings);
  }

  return { url: datos.checkout_url, referencia };
}

2. El webhook, que es quien confirma

Guardalo como backend/http-functions.js. Que el visitante vuelva a tu pagina significa que su navegador volvio, no que el dinero se movio: lo unico que confirma un cobro es este webhook, con la firma verificada.

// backend/http-functions.js
// La URL queda en https://TUSITIO.com/_functions/rokiWebhook
import { ok, badRequest } from 'wix-http-functions';
import { getSecret } from 'wix-secrets-backend';
import { createHmac, timingSafeEqual } from 'crypto';

export async function post_rokiWebhook(request) {
  // El cuerpo CRUDO. Volver a serializar el JSON cambia el orden de las claves y la firma no
  // coincide nunca: es el error mas comun al escribir un webhook.
  const crudo = await request.body.text();
  const cabecera = request.headers['roki-signature'] || '';
  const secreto = await getSecret('ROKI_WEBHOOK_SECRET');

  const m = /t=(\d+)\s*,\s*v1=([a-f0-9]{64})/i.exec(cabecera);
  if (!m) return badRequest({ body: 'firma ausente' });

  // Una entrega firmada hace tres dias no sirve para repetir un cobro.
  if (Math.abs(Date.now() / 1000 - Number(m[1])) > 300) {
    return badRequest({ body: 'firma vencida' });
  }

  const esperada = createHmac('sha256', secreto).update(m[1] + '.' + crudo).digest('hex');
  // En tiempo constante: comparar byte a byte filtra la firma por lo que tarda en fallar.
  const a = Buffer.from(esperada), b = Buffer.from(m[2].toLowerCase());
  if (a.length !== b.length || !timingSafeEqual(a, b)) {
    return badRequest({ body: 'firma invalida' });
  }

  const evento = JSON.parse(crudo);
  // El campo es 'type'. El cuerpo es { id, type, created_at, data }.
  if (evento.type !== 'payment.approved') {
    return ok({ body: 'ignorado' });
  }

  const pago = evento.data || {};
  // Aca va lo tuyo: marcar la reserva, mandar el correo, apuntar la venta.
  // El identificador que conviene guardar es transaction_id (UUID), que es el que despues
  // aceptan anular y reembolsar - no el id numerico.
  console.log('Pagado', pago.external_reference, pago.transaction_id, pago.amount);

  // Siempre 200, y rapido: ROKI reintenta hasta que le contesten.
  return ok({ body: 'ok' });
}

3. El boton en tu pagina

Va en el panel de codigo de la pagina. Cambia #botonPagar y #cantidad por los nombres que tengan tus elementos en el editor.

// Panel de codigo de la pagina
import { crearPago } from 'backend/roki';
import wixLocation from 'wix-location';

$w.onReady(() => {
  $w('#botonPagar').onClick(async () => {
    $w('#botonPagar').disable();
    try {
      // Se manda QUE eligio, nunca cuanto cuesta.
      const { url } = await crearPago('entrada_general', $w('#cantidad').value);
      wixLocation.to(url);
    } catch (e) {
      console.error(e);
      $w('#botonPagar').enable();
    }
  });
});

Lo que conviene saber

Se instala en cada sitioNo es un plugin: es codigo tuyo dentro de tu sitio. Si manejas varios sitios de Wix, se pega en cada uno.
Wix cobra su parte aparteVelo viene en los planes Premium de Wix. En un sitio gratuito no hay backend, y sin backend esta receta no aplica: ahi va el boton de pago.
La llave secreta nunca va en el codigoEl codigo de una pagina de Wix lo puede leer cualquiera que sepa mirar. Por eso va en el Secrets Manager, que solo lee el backend.
El catalogo vive en el backendEn el ejemplo los precios estan en una constante del archivo .jsw. Si tenes muchos, guardalos en una coleccion de Wix y leelos desde ahi - lo que no cambia es que el precio sale del servidor.
Si Wix abre su APICuando la puerta se abra publicamos el conector de verdad, con instalacion de un click y sin codigo. Esta receta seguira funcionando igual.