Saltar al contenido principal

Formularios HTML

Una de las formas de añadir pagos en cripto a un sitio, una tienda en línea o un bot. El cliente pulsa un botón y se abre la página de pago con la factura.

El formulario se envía solo por POST a https://dash.bitsby.app/invoices/form y contiene dos campos:

CampoQué contiene
database64url del JSON con los campos de la factura
signatureHMAC-SHA256 de la cadena data, 64 caracteres hexadecimales

Un proyecto puede tener como máximo 50 facturas sin pagar creadas de esta forma en un momento dado.

Ejemplo de formulario

<form method="post" action="https://dash.bitsby.app/invoices/form" target="_blank">
<input type="hidden" name="data" value="eyJwcm9qZWN0SWQiOiJhMWIyYzNkNC01ZTZm...">
<input type="hidden" name="signature" value="f77d6da1be35bd2900e0bfed9f202b04...">
<button type="submit">Pay</button>
</form>

El servidor de la tienda rellena ambos valores al generar la página. El secreto nunca aparece en el marcado.

El atributo target="_blank" es opcional: con él, el carrito permanece abierto en la pestaña original.

Cómo se construye el contenedor

  1. Construye un objeto JSON con los campos de la factura.
  2. Codifícalo como base64url. Esta es la cadena data.
  3. Calcula signature = HMAC-SHA256(data, formSecret).

La cadena data se firma como un todo, así que el orden de las claves del JSON, la sangría y el estilo de escape Unicode no importan. El receptor también acepta base64 estándar, con o sin relleno.

No reconstruyas data después de firmar: si cambia aunque sea un byte, la firma debe calcularse de nuevo.

Cómo calcular la firma, con ejemplos listos en cuatro lenguajes, se explica en Firma del formulario HTML.

Claves dentro de data

ClaveObligatoriaQué contiene
projectIdID del proyecto desde la configuración en el panel, uuid
amountFiatImporte de 1 a 100 000, con dos decimales como máximo. Como cadena "10.50" o como número 10.5
currencyFiatMoneda fiat: USD, EUR o RUB
timeToPayPlazo de pago en horas: 0.5, 1, 3, 6, 12. Como cadena o como número
descriptionnoDescripción para el cliente, hasta 1000 caracteres
serviceDatanoIdentificador del pedido en el lado de la tienda, hasta 1000 caracteres. No se muestra al cliente, pero es visible en el código fuente del formulario —no pongas aquí nada confidencial
outputnoerrors —mostrar los detalles de los errores de validación

Una clave opcional puede omitirse del JSON —equivale a una cadena vacía. Las claves pueden ir en cualquier orden; el receptor ignora las claves desconocidas.

Los valores son cadenas o números JSON. Los booleanos, los arrays y los objetos anidados no cuentan como valores de campo y llegan como una cadena vacía.

Ejemplo del contenido de data:

{
"projectId": "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
"amountFiat": 10.5,
"currencyFiat": "USD",
"timeToPay": 1,
"description": "Order #7, delivery",
"serviceData": "order-7"
}

Aquí el importe y el plazo de pago se pasan como números; también se aceptan cadenas. El opcional output no está definido.

Formato del importe

Solo dígitos y punto decimal, con dos decimales como máximo: 10, 10.00, 1000.50. Los espacios alrededor se recortan.

Cualquier otro formato se rechaza —separadores de miles, coma en lugar de punto, notación exponencial, signo delante del número, símbolo de moneda. No hay redondeo: un tercer decimal no se trunca, provoca un rechazo.

Secreto del formulario

El secreto vive en la configuración del proyecto en el panel, en el campo Secreto del formulario (Form secret). El servidor lo emite al crear el proyecto. No puedes establecer tu propio valor; el secreto solo puede volver a emitirse —por ejemplo, si se ve comprometido.

El secreto debe permanecer en el servidor de la tienda. Si acaba en el HTML público del sitio, la firma pierde su sentido.

El secreto del formulario y el secreto del webhook son claves distintas; no los confundas.

Identificador del pedido

Rellena siempre serviceData: pon ahí tu propio identificador del pedido. El valor vuelve en la notificación de pago —la tienda lo usa para encontrar el pedido.

Un serviceData relleno protege contra duplicados. Reenviar el formulario o hacer doble clic en el botón no crea una segunda factura: el cliente es dirigido a la factura sin pagar ya existente. La coincidencia se determina por los campos del pedido —proyecto, importe, moneda, descripción y serviceData—, no por los bytes de data.

El valor debe ser único por pedido. Con el mismo valor, un segundo cliente acaba en la factura sin pagar del primero.

Un serviceData vacío elimina esta protección: no hay nada por lo que reconocer una repetición, y cada envío crea una factura nueva. Los duplicados consumen el límite de 50 facturas sin pagar y, cuando una se paga, la tienda no tiene con qué vincular el pago a un pedido.

Errores y modo de depuración

El formulario pasa por dos comprobaciones: primero la firma y el contenedor, después los valores de los campos.

Qué ocurrióQué muestra el servicio
La firma no coincide, el contenedor es ilegible, el proyecto pertenece a otro o no existeUn error genérico, sin indicar el motivo
La firma coincide, pero un campo está mal rellenadoUn error genérico. Con "output":"errors" —errores detallados
Ambas comprobaciones pasaronLa página de pago con la factura

La primera comprobación nunca revela el motivo, con ninguna configuración.

El par "output":"errors" en el JSON activa los detalles de la segunda comprobación: el servicio nombra el campo, enumera los valores permitidos e informa cuando se alcanza el techo de 50 facturas sin pagar. La clave vive dentro del contenedor firmado, así que no puede inyectarse desde fuera.

Mientras configuras el formulario, pon "output":"errors" en el JSON; una vez configurado, elimina la clave y reconstruye el contenedor.

Si la propia firma no coincide, compara tu data y tu firma con el conjunto de referencia en Firma del formulario HTML.

Siguientes pasos

Una factura creada por el formulario se procesa igual que una creada por la API o en el panel: