# 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:

| Campo       | Qué contiene                                  |
| ----------- | --------------------------------------------- |
| `data`      | base64url del JSON con los campos de la factura |
| `signature` | HMAC-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

```html
<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](./form-signature.md).

## Claves dentro de data

| Clave          | Obligatoria | Qué contiene                                                                                                                                                       |
| -------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `projectId`    | sí          | ID del proyecto desde la configuración en el panel, uuid                                                                                                           |
| `amountFiat`   | sí          | Importe de 1 a 100 000, con dos decimales como máximo. Como cadena `"10.50"` o como número `10.5`                                                                   |
| `currencyFiat` | sí          | Moneda fiat: USD, EUR o RUB                                                                                                                                        |
| `timeToPay`    | sí          | Plazo de pago en horas: 0.5, 1, 3, 6, 12. Como cadena o como número                                                                                                |
| `description`  | no          | Descripción para el cliente, hasta 1000 caracteres                                                                                                                 |
| `serviceData`  | no          | Identificador 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 |
| `output`       | no          | `errors` —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`:

```json
{
    "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](../../webhook-url/index.md) —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 existe              | Un error genérico, sin indicar el motivo                  |
| La firma coincide, pero un campo está mal rellenado                                                    | Un error genérico. Con `"output":"errors"` —errores detallados |
| Ambas comprobaciones pasaron                                                                           | La 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](./form-signature.md).

## Siguientes pasos

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

* **La confirmación del pago** llega al [Webhook URL](../../webhook-url/index.md). Verifica la [firma de la notificación](../../webhook-url/signature-verification.md) con el secreto del webhook y entrega los pedidos solo con base en ella.
* **El retorno del cliente a tu sitio** se configura con los campos URL de éxito (Successful URL) y URL de fallo (Unsuccessful URL) —consulta la sección «Retorno del cliente al sitio de la tienda» en la página [Ciclo de vida de la factura](../../invoice-lifecycle.md). Llegar a estas URL no confirma el pago.
* **Los estados de la factura y la ventana de búsqueda de pagos** se describen en [Ciclo de vida de la factura](../../invoice-lifecycle.md).
* **El cliente transfirió un importe equivocado** —consulta [Asociación de pagos y discrepancias de importes](../../payment-matching.md).
