Una nueva alternativa para integrar PayPhone WHMCS sin costos de licenciamiento

La integración entre PayPhone WHMCS ha sido una de las necesidades más frecuentes para empresas de hosting, proveedores de servicios digitales y desarrolladores web en Ecuador. Aunque PayPhone se ha consolidado como una de las plataformas de pago más utilizadas en el país, las opciones disponibles para conectarla con WHMCS han sido limitadas, costosas o difíciles de mantener.

Con el objetivo de aportar al ecosistema tecnológico ecuatoriano, en Marbust Technology Company hemos desarrollado y publicado de forma gratuita nuestro nuevo módulo PayPhone WHMCS, una solución de código abierto que permite aceptar pagos con PayPhone directamente desde las facturas de WHMCS.

¿Qué es PayPhone WHMCS?

PayPhone WHMCS es una pasarela de pago que conecta la plataforma de pagos PayPhone con el sistema de facturación y automatización WHMCS.

Gracias a esta integración, los clientes pueden pagar sus facturas utilizando tarjetas de crédito o débito a través de PayPhone, mientras que WHMCS registra automáticamente los pagos una vez que la transacción ha sido aprobada.

Todo el proceso se realiza utilizando el flujo oficial de PayPhone:

Prepare → Redirección → Confirmación

Esto garantiza compatibilidad con la infraestructura oficial de la plataforma.

¿Por qué desarrollamos este módulo PayPhone WHMCS?

Durante nuestras operaciones en MBHostCloud® y otros proyectos tecnológicos, identificamos una necesidad recurrente dentro de la comunidad de hosting ecuatoriana: disponer de una integración PayPhone WHMCS moderna, segura y accesible.

Muchos proveedores enfrentaban problemas como:

  • Módulos comerciales con costos elevados.
  • Integraciones abandonadas.
  • Falta de documentación técnica.
  • Limitaciones para personalizar el código.
  • Dependencia de desarrolladores externos.

Por esta razón decidimos crear una alternativa completamente gratuita y compartirla con toda la comunidad.

Principales características del módulo PayPhone WHMCS

Nuestro plugin fue desarrollado siguiendo las mejores prácticas de seguridad y la documentación oficial de PayPhone.

Integración nativa con WHMCS

Los clientes pueden realizar pagos directamente desde sus facturas sin abandonar el flujo habitual de WHMCS.

Seguridad mejorada

El token Bearer de PayPhone nunca se expone en el navegador del usuario.

Toda la comunicación con la API se realiza exclusivamente desde el servidor.

Registro automático de pagos

Cuando una transacción es aprobada, el módulo ejecuta automáticamente el registro del pago utilizando las funciones estándar de WHMCS.

Protección contra manipulación de datos

La validación de montos utiliza firmas HMAC-SHA256 para garantizar la integridad de la información.

Prevención de transacciones duplicadas

El sistema incorpora mecanismos de control para evitar registros duplicados de una misma transacción.

Registro detallado de eventos

Todas las operaciones importantes quedan almacenadas en el Gateway Log de WHMCS para facilitar auditorías y soporte técnico.

Compatibilidad del módulo PayPhone WHMCS

Actualmente el plugin ha sido probado exitosamente en entornos de producción con:

  • WHMCS 7.x
  • WHMCS 8.x
  • WHMCS 9.x
  • PHP 7.x
  • PHP 8.x

Además requiere:

  • HTTPS activo
  • cURL habilitado
  • Token de aplicación PayPhone
  • Facturas en dólares estadounidenses (USD)

Instalación de PayPhone WHMCS

La instalación es sencilla y puede completarse en pocos minutos.

Solo es necesario copiar los archivos del módulo dentro de las carpetas correspondientes de WHMCS y posteriormente activar la pasarela desde el panel administrativo.

public_html\modules\gateways\payphone.php:

<?php
/**
 * WHMCS PayPhone Payment Gateway — third-party, redirect server-side.
 * Pasarela de pago PayPhone (Ecuador) para WHMCS — gratuita y de código abierto.
 *
 * Flujo: Prepare (servidor) -> redirige a payWithCard -> el cliente paga ->
 * PayPhone regresa al callback -> Confirm (servidor) -> registra el pago.
 * El token Bearer NUNCA llega al navegador (solo se usa en el servidor).
 *
 * Repositorio: https://github.com/MarbustTechnologyCompany/whmcs-payphone-plugin
 * Autor:       Marco Antonio Bustillos (MarAntBQ) — https://marantbq.dev — https://github.com/MarAntBQ
 * Licencia:    MIT (c) Marbust Technology Company — https://marbust.com
 *              Uso libre y gratuito reconociendo a los autores.
 * Donaciones:  https://paypal.me/MarbustTechnology
 */

if (!defined('WHMCS')) {
    die('This file cannot be accessed directly');
}

/**
 * Metadatos del módulo.
 */
function payphone_MetaData()
{
    return [
        'DisplayName' => 'PayPhone (Pagos con Tarjeta de Crédito / Débito Visa, MasterCard, Diners, Discover)',
        'APIVersion' => '1.1',
        'DisableLocalCreditCardInput' => true,
        'TokenisedStorage' => false,
    ];
}

/**
 * Campos de configuración (Admin > Configuración > Pasarelas de pago).
 */
function payphone_config()
{
    return [
        'FriendlyName' => [
            'Type' => 'System',
            'Value' => 'PayPhone (Pagos con Tarjeta de Crédito / Débito Visa, MasterCard, Diners, Discover)',
        ],
        'token' => [
            'FriendlyName' => 'Token de Aplicación (Bearer)',
            'Type' => 'password',
            'Size' => '60',
            'Description' => 'Token de tu aplicación PayPhone (el mismo del Botón/API). Se guarda cifrado y solo se usa en el servidor.',
        ],
        'prepareUrl' => [
            'FriendlyName' => 'Prepare URL',
            'Type' => 'text',
            'Size' => '70',
            'Default' => 'https://pay.payphonetodoesposible.com/api/button/Prepare',
        ],
        'confirmUrl' => [
            'FriendlyName' => 'Confirm URL',
            'Type' => 'text',
            'Size' => '70',
            'Default' => 'https://pay.payphonetodoesposible.com/api/button/V2/Confirm',
        ],
    ];
}

/**
 * Botón de pago que se muestra en la factura.
 *
 * No llama a PayPhone aquí (para no crear una sesión de pago en cada carga
 * de la factura). En su lugar muestra un botón que, al hacer clic, hace POST
 * al callback con action=prepare; ese paso ejecuta Prepare y redirige.
 *
 * La firma HMAC evita que un tercero inicie un pago con un monto manipulado.
 */
function payphone_link($params)
{
    $invoiceId = $params['invoiceid'];
    $amount = number_format((float) $params['amount'], 2, '.', '');
    $currency = isset($params['currency']) ? $params['currency'] : '';
    $token = $params['token'];
    $systemUrl = rtrim($params['systemurl'], '/');
    $callbackUrl = $systemUrl . '/modules/gateways/callback/payphone.php';
    $langPayNow = !empty($params['langpaynow']) ? $params['langpaynow'] : 'Pagar con PayPhone';

    if ($currency && strtoupper($currency) !== 'USD') {
        return '<div class="alert alert-warning">PayPhone solo procesa pagos en <strong>USD</strong>. '
            . 'Esta factura está en ' . htmlspecialchars($currency) . '.</div>';
    }

    $hash = hash_hmac('sha256', $invoiceId . '|' . $amount, $token);

    $html = '<form method="post" action="' . htmlspecialchars($callbackUrl) . '" style="display:inline">';
    $html .= '<input type="hidden" name="action" value="prepare" />';
    $html .= '<input type="hidden" name="invoiceid" value="' . htmlspecialchars($invoiceId) . '" />';
    $html .= '<input type="hidden" name="amount" value="' . htmlspecialchars($amount) . '" />';
    $html .= '<input type="hidden" name="hash" value="' . htmlspecialchars($hash) . '" />';
    $html .= '<input type="submit" value="' . htmlspecialchars($langPayNow) . '" class="btn btn-success" />';
    $html .= '</form>';

    return $html;
}

public_html\modules\gateways\callback\payphone.php:

<?php
/**
 * WHMCS PayPhone — Callback (dos fases):
 *
 *  1) action=prepare  : ejecuta Prepare contra PayPhone y redirige a payWithCard.
 *  2) retorno PayPhone : ?id=<transactionId>&clientTransactionId=<clientTxId>
 *                        ejecuta Confirm, valida y registra el pago en WHMCS.
 *
 * Repositorio: https://github.com/MarbustTechnologyCompany/whmcs-payphone-plugin
 * Autor:       Marco Antonio Bustillos (MarAntBQ) — https://github.com/MarAntBQ
 * Licencia:    MIT (c) Marbust Technology Company — https://marbust.com
 * Donaciones:  https://paypal.me/MarbustTechnology
 */

require_once __DIR__ . '/../../../init.php';
require_once __DIR__ . '/../../../includes/gatewayfunctions.php';
require_once __DIR__ . '/../../../includes/invoicefunctions.php';

$gatewayModuleName = basename(__FILE__, '.php');
$gatewayParams = getGatewayVariables($gatewayModuleName);

// Módulo activado.
if (!$gatewayParams['type']) {
    die('Module Not Activated');
}

$token = $gatewayParams['token'];
$prepareUrl = !empty($gatewayParams['prepareUrl'])
    ? $gatewayParams['prepareUrl']
    : 'https://pay.payphonetodoesposible.com/api/button/Prepare';
$confirmUrl = !empty($gatewayParams['confirmUrl'])
    ? $gatewayParams['confirmUrl']
    : 'https://pay.payphonetodoesposible.com/api/button/V2/Confirm';

/**
 * URL base del sistema (defensivo: getGatewayVariables suele traer 'systemurl',
 * pero si faltara se obtiene de la configuración de WHMCS).
 */
function payphone_system_url($gatewayParams)
{
    if (!empty($gatewayParams['systemurl'])) {
        return rtrim($gatewayParams['systemurl'], '/');
    }
    if (class_exists('\\WHMCS\\Config\\Setting')) {
        $url = \WHMCS\Config\Setting::getValue('SystemURL');
        if ($url) {
            return rtrim($url, '/');
        }
    }
    return '';
}

$systemUrl = payphone_system_url($gatewayParams);

/**
 * POST JSON a PayPhone con cURL (libcurl es aceptado por su backend IIS).
 */
function payphone_http_post($url, $token, array $body)
{
    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POST => true,
        CURLOPT_POSTFIELDS => json_encode($body),
        CURLOPT_HTTPHEADER => [
            'Authorization: Bearer ' . $token,
            'Content-Type: application/json',
        ],
        CURLOPT_TIMEOUT => 30,
    ]);
    $raw = curl_exec($ch);
    $status = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $error = curl_error($ch);
    curl_close($ch);

    return [
        'status' => $status,
        'data' => json_decode((string) $raw, true),
        'raw' => $raw,
        'error' => $error,
    ];
}

$action = isset($_REQUEST['action']) ? $_REQUEST['action'] : '';

/* ----------------------------------------------------------------------- */
/* FASE 1 — Prepare + redirección                                          */
/* ----------------------------------------------------------------------- */
if ($action === 'prepare') {
    $invoiceId = (int) (isset($_POST['invoiceid']) ? $_POST['invoiceid'] : 0);
    $amount = number_format((float) (isset($_POST['amount']) ? $_POST['amount'] : 0), 2, '.', '');
    $hash = isset($_POST['hash']) ? $_POST['hash'] : '';

    $expected = hash_hmac('sha256', $invoiceId . '|' . $amount, $token);
    if (!$invoiceId || !hash_equals($expected, $hash)) {
        die('Solicitud inválida.');
    }

    $amountCents = (int) round(((float) $amount) * 100);
    if ($amountCents <= 0) {
        header('Location: ' . $systemUrl . '/viewinvoice.php?id=' . $invoiceId);
        exit;
    }

    $clientTxId = substr('WHMCS-' . $invoiceId . '-' . time() . '-' . rand(1000, 9999), 0, 50);
    $responseUrl = $systemUrl . '/modules/gateways/callback/payphone.php';

    $body = [
        'amount' => $amountCents,
        'amountWithoutTax' => $amountCents,
        'amountWithTax' => 0,
        'tax' => 0,
        'service' => 0,
        'tip' => 0,
        'clientTransactionId' => $clientTxId,
        'responseUrl' => $responseUrl,
        'currency' => 'USD',
        'reference' => 'Factura ' . $invoiceId,
    ];

    $resp = payphone_http_post($prepareUrl, $token, $body);
    $data = is_array($resp['data']) ? $resp['data'] : [];
    $redirectUrl = isset($data['payWithCard'])
        ? $data['payWithCard']
        : (isset($data['payWithPayPhone']) ? $data['payWithPayPhone'] : '');

    if ($resp['status'] !== 200 || !$redirectUrl) {
        logTransaction($gatewayParams['name'], ['request' => $body, 'response' => $resp], 'Prepare Failed');
        die('No se pudo iniciar el pago con PayPhone. Intenta nuevamente o contacta a soporte.');
    }

    logTransaction(
        $gatewayParams['name'],
        ['clientTxId' => $clientTxId, 'paymentId' => isset($data['paymentId']) ? $data['paymentId'] : null],
        'Prepare OK'
    );

    header('Location: ' . $redirectUrl);
    exit;
}

/* ----------------------------------------------------------------------- */
/* FASE 2 — Retorno de PayPhone + Confirm                                  */
/* ----------------------------------------------------------------------- */
$transactionId = isset($_REQUEST['id']) ? trim($_REQUEST['id']) : '';
$clientTxId = isset($_REQUEST['clientTransactionId']) ? trim($_REQUEST['clientTransactionId']) : '';

if ($transactionId === '' || $clientTxId === '') {
    die('Parámetros de retorno inválidos.');
}

// El invoiceId va codificado en el clientTransactionId: WHMCS-<invoiceId>-...
if (!preg_match('/^WHMCS-(\d+)-/', $clientTxId, $m)) {
    die('No se pudo identificar la factura.');
}
$invoiceId = (int) $m[1];

$confirm = payphone_http_post($confirmUrl, $token, [
    'id' => (int) $transactionId,
    'clientTxId' => $clientTxId,
]);
$result = is_array($confirm['data']) ? $confirm['data'] : [];

$approved = (
    (isset($result['statusCode']) && (int) $result['statusCode'] === 3) ||
    (isset($result['transactionStatus']) && $result['transactionStatus'] === 'Approved')
);

// Valida que la factura exista y no esté ya pagada; evita doble registro.
$invoiceId = checkCbInvoiceID($invoiceId, $gatewayParams['name']);
checkCbTransID($transactionId);

if (!$approved) {
    logTransaction($gatewayParams['name'], $confirm, 'Unsuccessful');
    header('Location: ' . $systemUrl . '/viewinvoice.php?id=' . $invoiceId . '&paymentfailed=true');
    exit;
}

// PayPhone devuelve el monto en centavos.
$paidAmount = isset($result['amount']) ? ((float) $result['amount']) / 100 : 0;
$fee = 0;

addInvoicePayment($invoiceId, $transactionId, $paidAmount, $fee, $gatewayModuleName);
logTransaction($gatewayParams['name'], $result, 'Success');

header('Location: ' . $systemUrl . '/viewinvoice.php?id=' . $invoiceId . '&paymentsuccess=true');
exit;

Una vez configurado el token de PayPhone, el sistema queda listo para comenzar a recibir pagos.

Abre «Apps & Integrations» => /index.php?rp=/panel/apps/browse/payments

Busca «Payphone» y Activalo:

Coloca el Token obtenido en https://appdeveloper.payphonetodoesposible.com/application

Código abierto para toda la comunidad

Uno de los aspectos más importantes de este proyecto es que ha sido publicado como software libre bajo licencia MIT.

Esto permite que cualquier empresa o desarrollador pueda:

  • Utilizar el módulo gratuitamente.
  • Modificar el código.
  • Adaptarlo a sus necesidades.
  • Compartir mejoras.
  • Contribuir al proyecto.

Creemos firmemente que el crecimiento del ecosistema tecnológico ecuatoriano se fortalece cuando las herramientas son accesibles para todos.

Beneficios para empresas de hosting en Ecuador

La integración PayPhone WHMCS permite que los proveedores de hosting y servicios digitales puedan:

  • Automatizar la recepción de pagos.
  • Reducir procesos manuales.
  • Mejorar la experiencia del cliente.
  • Aumentar las opciones de pago disponibles.
  • Integrar una plataforma ampliamente utilizada en Ecuador.
  • Evitar costos adicionales por licencias de terceros.

Descarga el módulo PayPhone WHMCS

El proyecto se encuentra disponible públicamente en GitHub y puede descargarse de forma gratuita.

Repositorio oficial: https://github.com/MarbustTechnologyCompany/whmcs-payphone-plugin

whmcs payphone

También invitamos a desarrolladores y empresas a reportar errores, sugerir mejoras y contribuir al crecimiento del proyecto.

Conclusión

La integración PayPhone WHMCS representa una solución práctica para empresas ecuatorianas que buscan automatizar sus cobros y ofrecer una experiencia de pago moderna a sus clientes.

Con esta iniciativa, Marbust Technology Company reafirma su compromiso con el software libre, la innovación tecnológica y el fortalecimiento de la comunidad de hosting y desarrollo web en Ecuador.

Si utilizas WHMCS y deseas aceptar pagos con PayPhone, este proyecto puede ayudarte a implementar una solución profesional, segura y completamente gratuita.