Saltar al contenido principal

Instalar el SDK en React

Es el más completo de los cuatro. Trae la máquina de estados entera, hooks, prop getters y eventos — y sirve igual para React Native. En las otras plataformas la secuencia del flujo la orquesta tu aplicación; aquí la orquesta el SDK.

Paquete@spidi/spidi-react
Entorno mínimoReact 18+ · TypeScript
Registronpm, paquete privado de la organización @spidi

Lo que necesitas antes de empezar

  • Un token de lectura de npm para la organización @spidi, que entrega SPIDI.
  • Un session_id de prueba, creado por tu backend, por el monto mínimo.
  • Datos de un pagador real: cédula, teléfono y banco venezolanos, asociados entre sí.

Paso 1 · Configura la autenticación de npm

En desarrollo local:

npm login

En integración continua, o para terceros externos, en el .npmrc:

@spidi:registry=https://registry.npmjs.org/
//registry.npmjs.org/:_authToken=npm_tu_token_de_lectura

Paso 2 · Declara la dependencia

npm install @spidi/spidi-react

Paso 3 · Tu primera llamada

Cargar la sesión y comprobar que responde:

import { useSpidiSession } from "@spidi/spidi-react";

export function CheckoutInitializer({ sessionId }: { sessionId: string }) {
const { session, commerce, status, error } = useSpidiSession({
sessionId,
environment: "sandbox", // o "production"
enabledMethods: ["immediate_debit"],
});

if (status === "loading") return <p>Cargando información de pago...</p>;
if (status === "failed") return <p>Error al cargar: {error?.message}</p>;

return (
<div>
<h2>Comercio: {commerce?.name}</h2>
<p>Monto: Bs. {session?.bs_amount}</p>
</div>
);
}

Paso 4 · El flujo del débito inmediato

Todo el flujo vive en un solo hook, useSpidiCheckout, que expone el estado y los manejadores:

import { useSpidiCheckout } from "@spidi/spidi-react";

export function CheckoutForm({ sessionId }: { sessionId: string }) {
const spidi = useSpidiCheckout({
sessionId,
environment: "sandbox", // o "production"
});

if (spidi.status === "loading") return <p>Cargando...</p>;
if (spidi.status === "success")
return <p>¡Pago exitoso! Comprobante: {spidi.result?.id}</p>;

// Paso 2: Solicitar clave por SMS (OTP)
if (spidi.status === "awaiting_otp" || spidi.status === "confirming") {
return (
<form {...spidi.getFormProps()}>
<h3>Clave de pago</h3>
<p>Te enviamos una clave por SMS al teléfono {spidi.fields.phoneNumber}</p>
<input {...spidi.getKeyProps({ placeholder: "Clave de 8 dígitos" })} />
{spidi.keyValidationError && <p role="alert">{spidi.keyValidationError}</p>}
{spidi.error && <p role="alert">{spidi.error.message}</p>}
<button disabled={spidi.isBusy}>
{spidi.status === "confirming" ? "Procesando..." : `Pagar Bs. ${spidi.amounts?.bsFormatted}`}
</button>
<button type="button" onClick={spidi.resendKey} disabled={!spidi.canResend}>
Reenviar clave {spidi.resendIn > 0 && `(${spidi.resendIn}s)`}
</button>
</form>
);
}

// Paso 1: Formulario inicial de datos del pagador
return (
<form {...spidi.getFormProps()}>
<h2>Total a pagar: Bs. {spidi.amounts?.bsFormatted}</h2>

<label>Identificación</label>
<select {...spidi.getIdentificationTypeProps()}>
<option value="V">V</option>
<option value="E">E</option>
<option value="P">P</option>
</select>
<input {...spidi.getIdentificationProps({ placeholder: "Cédula" })} />
{spidi.validationErrors.identificationNumber && (
<p role="alert">{spidi.validationErrors.identificationNumber}</p>
)}

<label>Teléfono asociado</label>
<select {...spidi.getPhonePrefixProps()}>
{spidi.phonePrefixes.map((prefix) => (
<option key={prefix} value={prefix}>{prefix}</option>
))}
</select>
<input {...spidi.getPhoneProps({ placeholder: "Número" })} />
{spidi.validationErrors.phoneNumber && (
<p role="alert">{spidi.validationErrors.phoneNumber}</p>
)}

<label>Banco</label>
<select {...spidi.getBankProps()}>
<option value="">Seleccione banco...</option>
{spidi.banks.map((b) => (
<option key={b._id} value={b._id}>{b.name}</option>
))}
</select>
{spidi.validationErrors.bank && <p role="alert">{spidi.validationErrors.bank}</p>}

<button disabled={spidi.isBusy}>
{spidi.status === "creating" ? "Enviando clave..." : "Siguiente"}
</button>
{spidi.error && <p role="alert">{spidi.error.message}</p>}
</form>
);
}
La regla más importante del SDK

El botón de confirmar se deshabilita mientras la petición está en vuelo. Un doble toque es un doble débito, y la confirmación no se reintenta nunca.

Y si la petición falla por red o por tiempo de espera, no reintentes: consulta el estado, que es una lectura y se puede repetir sin riesgo:

{spidi.error?.code && ["network", "timeout", "http"].includes(spidi.error.code) && (
<button type="button" onClick={() => void spidi.checkStatus()}>
Verificar estado del pago
</button>
)}

Mensajes de validación propios

Los que no definas quedan en los de SPIDI:

const spidi = useSpidiCheckout({
sessionId,
messages: {
bankRequired: "Elige tu banco para continuar",
invalidIdentification: "La cédula proporcionada no es válida",
}
});

Cripto vía Binance Pay

import { useSpidiCrypto } from "@spidi/spidi-react";

export function CryptoCheckout({ sessionId }: { sessionId: string }) {
const crypto = useSpidiCrypto({ sessionId, environment: "sandbox" });

if (crypto.status === "loading") return <p>Generando orden...</p>;
if (crypto.status === "success") return <p>¡Pago cripto exitoso!</p>;

return (
<div>
<h3>Paga con Binance Pay</h3>
{crypto.order?.paymentQr?.img && (
<img src={crypto.order.paymentQr.img} alt="QR de Pago" />
)}
<a href={crypto.order?.paymentUrl} target="_blank" rel="noopener noreferrer">
Ir a pagar en Binance Pay
</a>
<button onClick={() => void crypto.checkStatus()} disabled={crypto.isBusy}>
Verificar acreditación
</button>
</div>
);
}

Cripto con Binance Pay

Las reglas que no cambian entre plataformas

Los formatos de monto, los dos identificadores del banco, la validación y —sobre todo— la regla de que la confirmación del débito no se reintenta nunca son idénticos en las cuatro. Viven en una sola página para que no se lean a medias:

El contrato y los errores · Antes de tocar dinero real

De dónde sale este código

Del documento de integración que entrega SPIDI, de agosto de 2026, copiado literal. No lo hemos ejecutado: el paquete es privado y no tenemos acceso. A diferencia del resto de este portal, aquí no hay una prueba que lo respalde.