Integraciones

Pagos cripto en WooCommerce

Cómo añadir la cripto como método de pago de WooCommerce: una clase de pasarela propia, crear el pago de Qvard, el webhook que completa el pedido y los escollos propios de WooCommerce.

WooCommerce tiene un punto de extensión limpio para los métodos de pago, y Qvard solo necesita dos cosas de él: una llamada que cree un pago cuando el cliente paga y un endpoint que complete el pedido cuando el pago termina. Todavía no hay un plugin de Qvard listo, así que esta guía muestra el código propio, pieza a pieza, y los detalles de WooCommerce que suelen salir mal.

Antes de empezar

Necesitas una cuenta de Qvard con un proyecto, una clave API y una callback URL guardada en el proyecto. Del lado de WooCommerce necesitas una forma de añadir código, idealmente un pequeño plugin propio o un plugin de fragmentos de código, no un archivo del tema que desaparece con la siguiente actualización.

Dos decisiones previas:

  • Moneda. Qvard fija los pagos en dólares estadounidenses. La pasarela de esta guía acepta pedidos en USD y rechaza otras monedas, que es la opción segura por defecto. Si tu tienda vende en otra moneda, convierte el total del pedido a dólares antes de crear el pago e indícalo en la caja.
  • Dónde vive la clave. Define la clave API como constante en wp-config.php (define( 'QVARD_API_KEY', '...' );), no en los ajustes de la base de datos, donde las exportaciones y otros plugins pueden leerla.

Si solo necesitas la idea básica sin la clase de WooCommerce, empieza con pagos cripto en WordPress.

Las piezas y cómo avanza un pedido

Tres piezas forman la integración:

  1. Una clase de pasarela de pago que aparece en la caja de WooCommerce como «Pagar con cripto».
  2. Una llamada a Qvard dentro de la pasarela, que crea un pago para el pedido y devuelve la dirección de la página de pago alojada.
  3. Un endpoint de webhook al que Qvard llama cuando el pago se completa.

Un pedido avanza así: el cliente elige el método y pulsa pagar; la pasarela crea el pago de Qvard, pone el pedido en espera y redirige al cliente a la página de pago; el cliente paga desde una billetera; Qvard llama a tu endpoint; el endpoint completa el pedido y WooCommerce envía sus correos habituales y lo hace avanzar. El id del pago que envías a Qvard es el número de pedido de WooCommerce, y así el webhook vuelve a encontrar el pedido.

La clase de la pasarela

La clase extiende WC_Payment_Gateway. Su constructor fija el id y el título del método y carga los ajustes, y process_payment hace el trabajo. Rechaza los pedidos que no están en USD y luego envía un POST a https://api.qvard.com/api/v1/payment con tu clave en la cabecera Authorization: Bearer y un JSON: id (el número de pedido como cadena) y price (el total en dólares estadounidenses). Una respuesta 201 trae un redirectUrl.

Si todo va bien, la pasarela guarda en el pedido el id del pago de Qvard, pone el estado en on-hold («pendiente de pago»), vacía el carrito y devuelve el redirectUrl como redirección. Si falla, añade un aviso de error y devuelve failure, de modo que el cliente se queda en la caja y puede elegir otro método.

Enviar el mismo id para el mismo método de pago devuelve el pago existente, así que un cliente que pulse el botón dos veces no crea dos pagos.

PHPqvard-gateway.php
<?php
// A minimal WooCommerce payment method that sends the customer to the Qvard payment page.
add_filter( 'woocommerce_payment_gateways', function ( $gateways ) {
    $gateways[] = 'WC_Gateway_Qvard';
    return $gateways;
} );

add_action( 'plugins_loaded', function () {
    if ( ! class_exists( 'WC_Payment_Gateway' ) ) {
        return;
    }

    class WC_Gateway_Qvard extends WC_Payment_Gateway {
        public function __construct() {
            $this->id                 = 'qvard';
            $this->method_title       = 'Qvard (crypto)';
            $this->method_description = 'Accept USDT and other crypto through Qvard.';
            $this->has_fields         = false;

            $this->init_form_fields();
            $this->init_settings();
            $this->title = $this->get_option( 'title', 'Pay with crypto' );

            add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, array( $this, 'process_admin_options' ) );
        }

        public function init_form_fields() {
            $this->form_fields = array(
                'enabled' => array( 'title' => 'Enable', 'type' => 'checkbox', 'default' => 'no' ),
                'title'   => array( 'title' => 'Title', 'type' => 'text', 'default' => 'Pay with crypto' ),
            );
        }

        public function process_payment( $order_id ) {
            $order = wc_get_order( $order_id );

            // Qvard prices payments in US dollars: refuse other shop currencies instead of charging a wrong amount.
            if ( 'USD' !== $order->get_currency() ) {
                wc_add_notice( 'Crypto payment is available for orders in USD only.', 'error' );
                return array( 'result' => 'failure' );
            }

            $response = wp_remote_post( 'https://api.qvard.com/api/v1/payment', array(
                'timeout' => 15,
                'headers' => array( 'Authorization' => 'Bearer ' . QVARD_API_KEY, 'Content-Type' => 'application/json' ),
                'body'    => wp_json_encode( array( 'id' => (string) $order->get_id(), 'price' => (float) $order->get_total() ) ),
            ) );

            if ( is_wp_error( $response ) || 201 !== wp_remote_retrieve_response_code( $response ) ) {
                wc_add_notice( 'Could not start the payment. Please try again.', 'error' );
                return array( 'result' => 'failure' );
            }

            $data = json_decode( wp_remote_retrieve_body( $response ), true );
            if ( empty( $data['redirectUrl'] ) ) {
                wc_add_notice( 'Could not start the payment. Please try again.', 'error' );
                return array( 'result' => 'failure' );
            }

            $order->update_meta_data( '_qvard_payment_id', sanitize_text_field( $data['id'] ?? '' ) );
            $order->update_status( 'on-hold', 'Waiting for the crypto payment.' );
            WC()->cart->empty_cart();

            return array( 'result' => 'success', 'redirect' => $data['redirectUrl'] );
        }
    }
} );

El webhook que completa el pedido

Registra una ruta REST y guarda su dirección en tu proyecto de Qvard como callback URL. Cuando un pago se completa, Qvard envía un POST con el id y el status, firmado en la cabecera X-Qvard-Signature. El manejador del ejemplo:

  • verifica la firma (HMAC-SHA256 con tu clave secreta) con el SDK de Qvard y responde 401 si no coincide;
  • busca el pedido por id e ignora los pedidos que no existen o que no se pagaron con esta pasarela, respondiendo igualmente 200 para que Qvard deje de reintentar;
  • llama a payment_complete() solo con el estado completed y solo si el pedido aún no está pagado, lo que lo hace seguro si se ejecuta dos veces;
  • responde exactamente HTTP 200. Cualquier otro estado, un tiempo de espera agotado o una redirección cuenta como entrega fallida y se reintenta, cinco intentos en unos seis minutos.

Permite la ruta en cualquier plugin de seguridad o cortafuegos y asegúrate de que la caché no toque /wp-json/. Las reglas generales están en cómo proteger los webhooks de pagos cripto.

PHPqvard-webhook.php
<?php
// Callback URL to save in the Qvard project: https://your-shop.com/wp-json/qvard/v1/webhook
add_action( 'rest_api_init', function () {
    register_rest_route( 'qvard/v1', '/webhook', array(
        'methods'             => 'POST',
        'permission_callback' => '__return_true', // authenticated below by the X-Qvard-Signature header
        'callback'            => function ( WP_REST_Request $request ) {
            try {
                $payload = ( new Qvard\Qvard( QVARD_API_KEY ) )->webhooks->constructEvent( $request->get_body(), $request->get_header( 'x-qvard-signature' ), QVARD_API_KEY );
            } catch ( \Exception $e ) {
                return new WP_REST_Response( null, 401 );
            }
            $order   = wc_get_order( (int) ( $payload['id'] ?? 0 ) );

            // Unknown order or not ours: nothing to retry, answer 200.
            if ( ! $order || 'qvard' !== $order->get_payment_method() ) {
                return new WP_REST_Response( null, 200 );
            }

            if ( 'completed' === ( $payload['status'] ?? '' ) && ! $order->is_paid() ) {
                $order->payment_complete( (string) $order->get_meta( '_qvard_payment_id' ) );
                $order->add_order_note( 'Qvard: payment completed.' );
            }

            return new WP_REST_Response( null, 200 ); // must be exactly 200
        },
    ) );
} );

Detalles de WooCommerce que muerden

  • Existencias. WooCommerce reduce las existencias cuando un pedido pasa a procesando o en espera, así que los pedidos cripto sin pagar pueden retener existencias. Cancela los pedidos en espera caducados tras el plazo que elijas, con una tarea programada propia (el ajuste de retención de existencias de WooCommerce se aplica a los pedidos pendientes de pago, no a los en espera); si no, las existencias quedan reservadas.
  • Pagos parciales. Si llega menos del importe completo, el estado del pago es partial y no se envía el webhook de finalización. El pedido sigue en espera; añade una nota, pide al cliente el resto o cancela y reembolsa.
  • Reembolsos. Un pago cripto no se puede retirar. Crea el registro de reembolso en WooCommerce para tu contabilidad, pero envía el dinero tú mismo a una dirección que te dé el cliente.
  • Correos del pedido. El correo de «en espera» sale al hacer el pedido; asegúrate de que su texto diga al cliente que el pago está pendiente y dónde pagar.
  • El regreso al sitio. Todavía no hay URL de retorno tras el pago, así que el cliente vuelve a tu tienda por su cuenta. La página de pedido estándar muestra el estado real, que es lo correcto.
  • Caja por bloques. La caja basada en bloques de WooCommerce necesita también una pasarela registrada para bloques; prueba el método en la caja que realmente uses.

Pruébalo antes de salir en vivo

  1. Abre una factura de demostración para ver la página de pago que recibirá tu cliente.
  2. Haz un pedido de prueba con el método cripto y comprueba que te redirige a la página de pago y que el pedido queda en espera.
  3. Paga un pequeño importe real y confirma que llega el webhook y que el pedido pasa a procesando.
  4. Haz que el endpoint falle una vez, confirma que Qvard reintenta y que el pedido no se completa dos veces.
  5. Retira un pequeño importe de tu saldo a tu billetera para saber que todo el recorrido funciona.

Todavía no hay un sandbox independiente, así que usa importes reales pequeños.

Qué cuesta y qué está listo

No hay cuota de suscripción por la pasarela. La comisión es individual para tu proyecto y, según la página de precios, parte del 1% y puede bajar hasta el 0,1%. Los fondos quedan en tu saldo de Qvard y los retiras a tu propia billetera por la comisión de red; el retiro a una cuenta bancaria en moneda fiat no está disponible.

Qvard funciona a través de su API, así que la integración requiere un desarrollador y pruebas; no hay un plugin de WooCommerce listo, y los enlaces de pago y un sandbox independiente están en desarrollo. La API se describe en API de pagos cripto para desarrolladores y el lado de la tienda en pagos cripto para el comercio electrónico.

FAQ

¿Hay un plugin de Qvard para WooCommerce?

Todavía no. La integración es por la API: una clase de pasarela que crea el pago y un webhook que completa el pedido. El código de esta guía es un punto de partida para un desarrollador.

Mi tienda usa euros. ¿Pueden los clientes pagar igualmente con cripto?

Qvard fija los pagos en dólares estadounidenses. Convierte el total del pedido a dólares antes de crear el pago e indica al cliente el importe en dólares en la caja; la pasarela del ejemplo rechaza los pedidos que no están en USD como opción segura por defecto.

¿Cuándo pasa el pedido a pagado?

Cuando llega el webhook con el estado completed. Que el cliente vuelva a la tienda no es prueba de pago, y el estado partial significa que llegó menos de lo requerido.

¿Y si mi sitio estaba caído cuando se completó el pago?

La notificación se reintenta cinco veces durante unos seis minutos. Después, reenvíala para el pago desde el panel de Qvard; el manejador es seguro si se ejecuta dos veces.

Artículos relacionados

Artículos relacionados