# Inicio rápido de la cuenta de prueba

Tu primer pago ficticio con estado real, firmado y con su webhook.

Al terminar habrás creado un pago ficticio en tu cuenta de prueba, lo habrás visto llegar a `completed` y sabrás forzar otros desenlaces. Nada mueve dinero. Si aún no tienes cuenta, empieza por [Tu primer pago de prueba](/docs/primer-pago/), que corre sin registro.

Necesitas acceso al panel, en [merchants.fital.xyz](https://merchants.fital.xyz): entran las empresas con KYB aprobado en [empresa.fital.xyz](https://empresa.fital.xyz). Si tu empresa aún no tiene cuenta, regístrala ahí y completa el KYB.

## 1. Crea tu llave

En el panel elige el ambiente **Sandbox** y, en la pantalla Llaves, **Crear llave de API**. Dale al menos estos scopes: `balances:read`, `beneficiaries:read`, `payments:read` y `payments:write`. Guarda la llave y su secreto de firma: se muestran una sola vez.

## 2. Prepara tus variables

```bash
export FITAL_API="https://widget.fital.xyz/sandbox/v1"
export FITAL_KEY="sk_test_fital_..."
export FITAL_SIGNING_SECRET="el secreto de firma de esa llave"
```

## 3. Mira tu saldo ficticio

```bash
curl -sS "$FITAL_API/balances" -H "Authorization: Bearer $FITAL_KEY"
```

```js
const r = await fetch(`${process.env.FITAL_API}/balances`, {
  headers: { Authorization: `Bearer ${process.env.FITAL_KEY}` },
});
console.log(r.status, await r.json());
```

```python
import json, os, urllib.error, urllib.request
pet = urllib.request.Request(os.environ["FITAL_API"] + "/balances",
    headers={"Authorization": "Bearer " + os.environ["FITAL_KEY"]})
try:
    with urllib.request.urlopen(pet) as r:
        print(r.status, json.load(r))
except urllib.error.HTTPError as e:
    print(e.code, e.read().decode())
```

```php
<?php
$c = curl_init(getenv('FITAL_API') . '/balances');
curl_setopt_array($c, [
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('FITAL_KEY')],
]);
echo curl_exec($c), "\n";
```

Tu cuenta nace con saldo ficticio en MXN y USD.

## 4. Elige un beneficiario

La cuenta ya trae beneficiarios aprobados. Cada uno termina sus pagos de una forma: el nombre lo dice.

```bash
curl -sS "$FITAL_API/beneficiaries" \
  -H "Authorization: Bearer $FITAL_KEY"
```

```js
const r = await fetch(`${process.env.FITAL_API}/beneficiaries`, {
  headers: { Authorization: `Bearer ${process.env.FITAL_KEY}` },
});
console.log(r.status, await r.json());
```

```python
import json, os, urllib.error, urllib.request
pet = urllib.request.Request(os.environ["FITAL_API"] + "/beneficiaries",
    headers={"Authorization": "Bearer " + os.environ["FITAL_KEY"]})
try:
    with urllib.request.urlopen(pet) as r:
        print(r.status, json.load(r))
except urllib.error.HTTPError as e:
    print(e.code, e.read().decode())
```

```php
<?php
$c = curl_init(getenv('FITAL_API') . '/beneficiaries');
curl_setopt_array($c, [
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('FITAL_KEY')],
]);
echo curl_exec($c), "\n";
```

| Titular | Sus pagos terminan en |
|---|---|
| `PROVEEDOR FICTICIO ÉXITO` | `completed` |
| `PROVEEDOR FICTICIO RECHAZO` | `failed` |
| `PROVEEDOR FICTICIO DEVOLUCIÓN` | `completed` y luego `returned` |
| `PROVEEDOR FICTICIO REVISIÓN` | `in_review` y luego `completed` |
| `PROVEEDOR FICTICIO INCIERTO` | `processing` sin confirmar y luego `completed`, unos 5 minutos después |

Mientras el pago a `PROVEEDOR FICTICIO INCIERTO` sigue sin confirmar, cualquier otro pago de tu empresa responde `409 payment_in_flight`. Para seguir sin esperar, llévalo a `completed` con [`POST /sandbox/v1/payments/{id}/advance`](/docs/operaciones/avanzar-pago-de-prueba/).

La respuesta da sólo el `id`: la pantalla Sandbox del panel muestra el nombre de cada uno. Guarda en `BENEFICIARIO` el `id` del de éxito:

```bash
export BENEFICIARIO="ben_..."
```

## 5. Crea un pago

```bash
REF="mi-primer-pago"
CUERPO=$(printf '{"beneficiary_id":"%s","amount":"100.00","currency":"MXN","external_ref":"%s"}' "$BENEFICIARIO" "$REF")
CLAVE="pago-$REF-$BENEFICIARIO"
TS=$(date +%s)
HASH=$(printf '%s' "$CUERPO" | openssl dgst -sha256 -r | cut -d' ' -f1)
FIRMA=$(printf '%s\n%s\n%s\n%s\n%s' "$TS" POST /v1/payments "$HASH" "$CLAVE" \
  | openssl dgst -sha256 -hmac "$FITAL_SIGNING_SECRET" -r | cut -d' ' -f1)
curl -sS -X POST "$FITAL_API/payments" \
  -H "Authorization: Bearer $FITAL_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $CLAVE" \
  -H "X-Fital-Timestamp: $TS" \
  -H "X-Fital-Signature: v1=$FIRMA" \
  -d "$CUERPO"
```

```js
import { createHash, createHmac } from 'node:crypto';
const ref = 'mi-primer-pago';
const cuerpo = JSON.stringify({ beneficiary_id: process.env.BENEFICIARIO, amount: '100.00', currency: 'MXN', external_ref: ref });
const clave = `pago-${ref}-${process.env.BENEFICIARIO}`;
const ts = String(Math.floor(Date.now() / 1000));
const hash = createHash('sha256').update(cuerpo).digest('hex');
const firma = createHmac('sha256', process.env.FITAL_SIGNING_SECRET)
  .update([ts, 'POST', '/v1/payments', hash, clave].join('\n')).digest('hex');
const r = await fetch(`${process.env.FITAL_API}/payments`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.FITAL_KEY}`,
    'Content-Type': 'application/json',
    'Idempotency-Key': clave,
    'X-Fital-Timestamp': ts,
    'X-Fital-Signature': `v1=${firma}`,
  },
  body: cuerpo,
});
console.log(r.status, await r.json());
```

```python
import hashlib, hmac, json, os, time, urllib.error, urllib.request
ref = "mi-primer-pago"
datos = {"beneficiary_id": os.environ["BENEFICIARIO"], "amount": "100.00", "currency": "MXN", "external_ref": ref}
cuerpo = json.dumps(datos).encode()
clave = "pago-" + ref + "-" + os.environ["BENEFICIARIO"]
ts = str(int(time.time()))
canon = "\n".join([ts, "POST", "/v1/payments", hashlib.sha256(cuerpo).hexdigest(), clave])
firma = hmac.new(os.environ["FITAL_SIGNING_SECRET"].encode(), canon.encode(), hashlib.sha256).hexdigest()
pet = urllib.request.Request(os.environ["FITAL_API"] + "/payments", method="POST", data=cuerpo,
    headers={"Authorization": "Bearer " + os.environ["FITAL_KEY"],
             "Content-Type": "application/json",
             "Idempotency-Key": clave,
             "X-Fital-Timestamp": ts,
             "X-Fital-Signature": "v1=" + firma})
try:
    with urllib.request.urlopen(pet) as r:
        print(r.status, json.load(r))
except urllib.error.HTTPError as e:
    print(e.code, e.read().decode())
```

```php
<?php
$ref = 'mi-primer-pago';
$cuerpo = json_encode(['beneficiary_id' => getenv('BENEFICIARIO'), 'amount' => '100.00', 'currency' => 'MXN', 'external_ref' => $ref]);
$clave = 'pago-' . $ref . '-' . getenv('BENEFICIARIO');
$ts = (string) time();
$canon = implode("\n", [$ts, 'POST', '/v1/payments', hash('sha256', $cuerpo), $clave]);
$firma = hash_hmac('sha256', $canon, getenv('FITAL_SIGNING_SECRET'));
$c = curl_init(getenv('FITAL_API') . '/payments');
curl_setopt_array($c, [
  CURLOPT_POST => true,
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => [
    'Authorization: Bearer ' . getenv('FITAL_KEY'),
    'Content-Type: application/json',
    'Idempotency-Key: ' . $clave,
    'X-Fital-Timestamp: ' . $ts,
    'X-Fital-Signature: v1=' . $firma,
  ],
  CURLOPT_POSTFIELDS => $cuerpo,
]);
echo curl_exec($c), "\n";
```

Responde `202` con `status: processing` y un `id` con prefijo `pay_`. La clave de idempotencia sale de tu referencia y del beneficiario: si corres esto otra vez, recibes el mismo pago, con `Idempotent-Replayed: true`, y no uno nuevo. Para crear otro pago, cambia la referencia. Ver [Idempotencia](/docs/idempotencia/).

Guarda el `id`:

```bash
export PAGO="pay_..."
```

## 6. Míralo terminar

Espera unos 30 segundos y consúltalo:

```bash
curl -sS "$FITAL_API/payments/$PAGO" \
  -H "Authorization: Bearer $FITAL_KEY"
```

```js
const r = await fetch(`${process.env.FITAL_API}/payments/${process.env.PAGO}`, {
  headers: { Authorization: `Bearer ${process.env.FITAL_KEY}` },
});
console.log(r.status, await r.json());
```

```python
import json, os, urllib.error, urllib.request
pet = urllib.request.Request(os.environ["FITAL_API"] + "/payments/" + os.environ["PAGO"],
    headers={"Authorization": "Bearer " + os.environ["FITAL_KEY"]})
try:
    with urllib.request.urlopen(pet) as r:
        print(r.status, json.load(r))
except urllib.error.HTTPError as e:
    print(e.code, e.read().decode())
```

```php
<?php
$c = curl_init(getenv('FITAL_API') . '/payments/' . getenv('PAGO'));
curl_setopt_array($c, [
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('FITAL_KEY')],
]);
echo curl_exec($c), "\n";
```

`status` llega en `completed`. Si sigue en `processing`, espera unos segundos y consulta otra vez, o llévalo a `completed` con el paso 7. Si tienes un destino de webhook, también recibes `payment.completed`.

## 7. Fuerza otro desenlace

Sin esperar su guion, lleva un pago a otro estado con [`POST /sandbox/v1/payments/{id}/advance`](/docs/operaciones/avanzar-pago-de-prueba/). O provoca un error al crear el pago con `X-Fital-Scenario`. La lista está en [Ambientes](/docs/ambientes/#escenarios).

## Errores comunes

- `401 wrong_environment`: usas una llave de otro ambiente. Aquí va tu llave `sk_test_fital_…`, no la llave pública del simulador.
- `401 invalid_signature`: firmaste la ruta con `/sandbox`. Se firma siempre `/v1/...`.
- `403 forbidden_scope`: a tu llave le falta un scope. `details.required_scope` lo nombra.
- `409 payment_in_flight`: otro pago de tu empresa sigue sin confirmarse. Termínalo con el paso 7 o espera a que se resuelva.
- `404 not_found`: el `id` no es de un beneficiario de tu cuenta.
- `422 invalid_beneficiary`: el beneficiario se dio de baja.

## Qué sigue

[Cuenta de prueba](/docs/sandbox/cuenta-de-prueba/) e [Inicio rápido de webhooks](/docs/webhooks/inicio-rapido/).
