Saltar al contenido principal

Integración con Ethereum y BNB Smart Chain

Esta guía explica cómo aceptar pagos cripto no custodiales en Ethereum y BNB Smart Chain con los contratos inteligentes de pago de Volet.com y ethers v6.

Los clientes conectan sus billeteras, aprueban las transacciones y pagan directamente a tu billetera receptora a través de un contrato inteligente. Los fondos no se acreditan en una cuenta de Volet.com.

Antes de continuar, revisa Integración con contratos inteligentes para ver una descripción general del flujo de pago.

La integración de un vistazo​

  1. Conecta la billetera del cliente y confirma la red blockchain elegida.
  2. Configura los contratos de pago y tu billetera receptora.
  3. Crea un pedido en tu backend antes de iniciar el pago.
  4. Llama a previewPayment() para calcular el monto total del pago.
  5. Llama a transfer() para iniciar el pago. En USDT, revisa la autorización de gasto (allowance) del token y solicita la aprobación si hace falta.
  6. Guarda el hash de la transacción de pago (txid) en el pedido.
  7. Verifica la transacción en tu backend antes de marcar el pedido como pagado.

Antes de empezar​

Necesitarás:

  • Billetera del cliente: MetaMask u otra billetera compatible con EIP-1193 para pagos en el navegador.
  • Biblioteca: ethers versión 6 para interactuar con billeteras y contratos inteligentes.
  • Billetera del comercio: una dirección de billetera en formato Ethereum que controles para recibir los pagos.
  • Backend y base de datos: para crear pedidos, guardar los datos de las transacciones y verificar los pagos.
  • Proveedor RPC: para obtener las transacciones de la blockchain y verificar su ejecución.

Para conectar billeteras móviles, también necesitarás un Project ID de Reown/WalletConnect.

Redes y activos compatibles​

RedChain IDActivos compatibles
Ethereum Mainnet1ETH, USDT (ERC-20)
BNB Smart Chain56BNB, USDT (BEP-20)

Cada activo usa su contrato procesador de pagos. Los ejemplos de código de abajo contienen las direcciones de los procesadores y tokens de la configuración de la integración.

Las direcciones de los contratos, los decimales de los tokens y los parámetros de las funciones están en la Referencia de contratos inteligentes.

Antes de usar los ejemplos en producción, confirma con Volet.com las direcciones de los procesadores desplegados y la interfaz del contrato.

Paso 1. Conecta la billetera del cliente​

Usa ethers v6 para conectar la billetera de navegador del cliente.

<script src="https://cdn.jsdelivr.net/npm/ethers@6/dist/ethers.umd.min.js"></script>

<button id="connect-wallet">Connect wallet</button>

<script>
let provider;
let signer;

document.getElementById('connect-wallet').addEventListener('click', async () => {
if (!window.ethereum) {
alert('Install a compatible wallet, such as MetaMask.');
return;
}

provider = new ethers.BrowserProvider(window.ethereum);
await provider.send('eth_requestAccounts', []);
signer = await provider.getSigner();

document.getElementById('connect-wallet').textContent =
await signer.getAddress();
});
</script>

El cliente debe conectarse a la red elegida en tu página de pago:

  • Ethereum Mainnet (chainId: 1)
  • BNB Smart Chain (chainId: 56)

Si la billetera está en otra red, solicita el cambio de red o pide al cliente que la cambie manualmente.

Verifica siempre el chain ID activo justo antes de enviar un pago, porque el cliente puede cambiar de red en su billetera.

Paso 2. Configura los contratos de pago​

Agrega a tu aplicación la ABI del procesador de pagos y las funciones del token.

const PROCESSOR_ABI = [
'function previewPayment(uint256 amount, bool buyerPaysCommission) view returns (uint256 commission, uint256 totalPay)',
'function transfer(address seller, uint256 amount, bool buyerPaysCommission, address optionalCommissionWallet) payable',
];

const ERC20_ABI = [
'function balanceOf(address owner) view returns (uint256)',
'function allowance(address owner, address spender) view returns (uint256)',
'function approve(address spender, uint256 value) returns (bool)',
];

const PROCESSORS = {
native: '0x2fceE245109Cf3c535489a6F52fe11b3BBd644FF',
usdt: '0xe4083ED0A1101615b9092E8cD2164e2f42B4571E',
};

const USDT_BY_CHAIN = {
1: {
address: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
decimals: 6,
},
56: {
address: '0x55d398326f99059fF775485246999027B3197955',
decimals: 18,
},
};

El procesador de pagos ofrece dos funciones principales:

  • previewPayment(amount, buyerPaysCommission) calcula la comisión de procesamiento y el monto total que debe pagar el cliente.
  • transfer(seller, amount, buyerPaysCommission, optionalCommissionWallet) ejecuta el pago y distribuye los fondos según la configuración de comisiones del contrato.

El parámetro amount usa las unidades mínimas del activo. Usa ethers.parseEther() para ETH y BNB, y ethers.parseUnits() con los decimales correctos del token para USDT.

El parámetro buyerPaysCommission determina quién paga la comisión de procesamiento:

  • true: la comisión se suma al pago del cliente.
  • false: la comisión se descuenta del monto que recibe el comercio.

Usa ethers.ZeroAddress en optionalCommissionWallet si no hay una billetera de comisión de socio.

Obtén siempre el monto total del pago de previewPayment(). No calcules la comisión de procesamiento manualmente.

Las definiciones completas de los parámetros están en la Referencia de contratos inteligentes.

Paso 3. Acepta ETH o BNB​

Los pagos en ETH y BNB nativos requieren una sola transacción en la billetera.

La misma función sirve en ambas redes cuando el cliente está conectado a la blockchain correcta.

async function payNative(signer, seller, humanAmount, buyerPaysCommission = true) {
if (!ethers.isAddress(seller)) {
throw new Error('Invalid merchant wallet');
}

const network = await signer.provider.getNetwork();

if (![1n, 56n].includes(network.chainId)) {
throw new Error('Unsupported network');
}

const processor = new ethers.Contract(
PROCESSORS.native,
PROCESSOR_ABI,
signer
);

const amount = ethers.parseEther(humanAmount);

const [, totalPay] = await processor.previewPayment(
amount,
buyerPaysCommission
);

const tx = await processor.transfer(
seller,
amount,
buyerPaysCommission,
ethers.ZeroAddress,
{ value: totalPay }
);

return tx.hash;
}

El value de la transacción debe ser igual al totalPay que devuelve previewPayment().

El cliente también debe tener suficiente ETH o BNB para cubrir la comisión de red de la blockchain.

La función devuelve el hash de la transacción de pago (txid). Guárdalo en el pedido existente y verifica el pago antes de entregar el pedido.

Paso 4. Acepta USDT​

Los pagos en USDT usan un contrato de token ERC-20 o BEP-20 y pueden requerir una transacción de aprobación adicional.

Antes de hacer un pago, tu aplicación debe:

  1. Revisar el saldo de USDT del cliente con balanceOf().
  2. Revisar la autorización de gasto del procesador con allowance().
  3. Si la autorización es insuficiente, solicitar la aprobación con approve().
  4. Llamar a transfer() para ejecutar el pago.
async function payUsdt(signer, seller, humanAmount, buyerPaysCommission = true) {
if (!ethers.isAddress(seller)) {
throw new Error('Invalid merchant wallet');
}

const chainId = Number((await signer.provider.getNetwork()).chainId);
const tokenConfig = USDT_BY_CHAIN[chainId];

if (!tokenConfig) {
throw new Error('Unsupported network');
}

const processor = new ethers.Contract(
PROCESSORS.usdt,
PROCESSOR_ABI,
signer
);

const token = new ethers.Contract(
tokenConfig.address,
ERC20_ABI,
signer
);

const amount = ethers.parseUnits(humanAmount, tokenConfig.decimals);

const [, totalPay] = await processor.previewPayment(
amount,
buyerPaysCommission
);

const buyer = await signer.getAddress();
const balance = await token.balanceOf(buyer);

if (balance < totalPay) {
throw new Error('Insufficient USDT');
}

const currentAllowance = await token.allowance(
buyer,
PROCESSORS.usdt
);

if (currentAllowance < totalPay) {
// Ethereum USDT may require resetting an existing allowance.
if (chainId === 1 && currentAllowance > 0n) {
const resetTx = await token.approve(PROCESSORS.usdt, 0n);
await resetTx.wait();
}

const approvalTx = await token.approve(
PROCESSORS.usdt,
totalPay
);

await approvalTx.wait();
}

const tx = await processor.transfer(
seller,
amount,
buyerPaysCommission,
ethers.ZeroAddress
);

return tx.hash;
}

Requisitos de aprobación de USDT​

Según la autorización existente, un pago en USDT puede requerir:

Autorización existenteTransacciones necesarias
SuficienteSolo el pago
Cero o insuficienteAprobación + pago
Autorización distinta de cero en USDT de EthereumRestablecer la autorización a cero + aprobación + pago

El USDT de Ethereum puede rechazar el cambio directo de una autorización distinta de cero a otro valor distinto de cero. El ejemplo lo resuelve restableciendo primero la autorización cuando es necesario.

Cada aprobación debe confirmarse antes de pasar a la siguiente transacción.

Importante:

  • Una transacción approve() no es un pago.
  • El cliente necesita suficiente ETH o BNB para cubrir las comisiones de red, incluso cuando paga en USDT.
  • Usa montos enteros de tokens calculados con ethers.parseUnits(). No uses aritmética de punto flotante para montos financieros.
  • Rastrea las transacciones de aprobación por separado de la transacción de pago final.

Paso 5. Guarda la transacción de pago​

Tu backend debe crear un pedido antes de que el cliente envíe un pago.

Cada pedido debe contener los valores esperados de:

  • ID del pedido
  • Red blockchain y chain ID
  • Activo y monto
  • Billetera receptora del comercio (seller)
  • Contrato procesador de pagos aprobado
  • Billetera del comprador, si tu página de pago lo requiere
  • Configuración de la comisión de procesamiento (buyerPaysCommission)

Después de que el cliente envíe el pago:

  1. Obtén el hash de la transacción de pago (txid) que devuelve la billetera.
  2. Envía el hash a tu backend.
  3. Guárdalo junto con el ID del pedido y el chain ID.
  4. Inicia la verificación del pago en el servidor.

Cada transacción de pago debe estar asociada a un solo pedido como máximo. Aplícalo con una restricción única (chainId, txid) en tu base de datos.

La función transfer() del contrato inteligente no acepta un ID de pedido. Tu aplicación debe mantener la relación entre el pedido y su transacción de pago.

Si un cliente reintenta un pago, conserva los intentos de pago por separado en lugar de sobrescribir registros de transacciones que aún pueden estar pendientes.

Paso 6. Verifica el pago​

La verificación del pago es obligatoria antes de completar un pedido.

Un hash de transacción devuelto solo indica que se envió una transacción. No prueba que el comercio haya recibido el pago correcto.

Tu backend debe, por su cuenta:

  1. Obtener la transacción y el recibo de ejecución de la blockchain.
  2. Confirmar la ejecución exitosa y la finalidad requerida en la blockchain.
  3. Verificar que la transacción usó la red y el procesador de pagos esperados.
  4. Comparar el activo, la billetera del comercio, el monto y la llamada al contrato con el pedido.
  5. Confirmar que el comercio recibió los fondos esperados.
  6. Asegurarse de que la transacción no se haya usado ya para otro pedido.
  7. Marcar el pedido como pagado y entregarlo una sola vez.

Un recibo de transacción exitoso no basta por sí solo. Una transacción puede ejecutarse correctamente sin pagar al comercio correcto o el monto correcto.

Sigue Verificación de pagos y seguimiento de pedidos para la implementación en el backend, el ciclo de vida del pedido, los requisitos de finalidad y la lista de verificación para producción.

Opcional: admite billeteras móviles​

Para admitir billeteras móviles mediante WalletConnect, integra @walletconnect/ethereum-provider y configúralo con un Project ID de Reown Cloud.

El ejemplo HTML de EVM muestra la integración de WalletConnect, incluida la selección y el cambio de red.

Un Project ID de WalletConnect identifica tu aplicación. No es una clave privada. Restringe los dominios u orígenes permitidos de la aplicación cuando sea posible.

Ejemplo de integración​

El ejemplo example-evm.html muestra:

  • La conexión de MetaMask y de billeteras compatibles con WalletConnect.
  • La selección de Ethereum o BNB Smart Chain.
  • El cálculo de los montos de pago con previewPayment().
  • El envío de pagos en ETH, BNB y USDT.
  • La visualización de los hashes de transacción y el seguimiento de las confirmaciones en la blockchain.

Puedes abrir el ejemplo en el navegador o descargar el archivo HTML para estudiar y adaptar el código:

  • Abrir el ejemplo en vivo: abre la demo interactiva. Usa la mainnet de Ethereum y BNB Smart Chain, así que confirmar un pago transfiere fondos reales.
  • Descargar example-evm.html: descarga el archivo HTML para ejecutarlo localmente o adaptarlo a tu aplicación.

Las extensiones de billetera del navegador, como MetaMask, pueden no funcionar correctamente con páginas abiertas directamente desde el disco (file://). Sirve el archivo descargado desde un servidor web local. Por ejemplo, ejecuta npx serve en la carpeta del archivo y abre la URL de localhost que muestra.

Prueba el ejemplo​

  1. Abre el ejemplo en vivo o sirve el archivo descargado localmente en un navegador con MetaMask instalado.
  2. Selecciona Ethereum Mainnet o BNB Smart Chain y haz clic en Connect Wallet (MetaMask). Para billeteras móviles, ingresa tu Project ID de WalletConnect y selecciona WalletConnect (QR).
  3. Selecciona el método de pago: ETH, BNB o USDT.
  4. Ingresa la billetera receptora en Seller address.
  5. Ingresa el monto del pago y elige quién paga la comisión de procesamiento.
  6. Haz clic en Preview para ver la comisión de procesamiento y el monto total del pago. Luego haz clic en Pay y confirma la transacción en tu billetera. Los pagos en USDT pueden requerir antes una aprobación aparte.
  7. El pedido y el hash de la transacción aparecen en la tabla Orders. La demo sigue hasta 15 confirmaciones en la blockchain.

Antes de pagar:

  • Usa dos billeteras distintas. La dirección del vendedor debe ser diferente de la billetera que paga. De lo contrario, el contrato rechaza el pago con SellerIsBuyer.
  • Las transacciones usan fondos reales. El ejemplo viene con 0.001 ETH o BNB precargado. Revisa el monto, la comisión de procesamiento y la comisión de red antes de confirmar.

Limitaciones de la demo: este ejemplo guarda el historial de transacciones en el localStorage del navegador y no es una página de pago de producción. Seguir 15 confirmaciones no garantiza la finalidad de Ethereum ni reemplaza la verificación de pagos en el backend. Implementa el proceso completo de verificación de pagos antes de aceptar pedidos de clientes.