> ## Knowledge Base Index
> Fetch the complete knowledge base index at: https://help.fygaro.com/sitemap.xml
> Use this file to discover available pages before exploring further.
> Pure-Markdown content can be obtained by appending a '.md' suffix to the content URLs listed in the sitemap (without the trailing slash).

# Conectar Pagos de Fygaro con Wix


### Descargo de responsabilidad

Asegúrate de hacer una copia de seguridad de tu sitio antes de implementar esta integración y de probarla completamente antes de habilitarla en producción.

Conectar Fygaro con Wix para recibir pagos requiere:

* Un plan de suscripción de Fygaro con capacidades de Hooks.
    
* Una cuenta de Wix con una tienda en línea o función de comercio electrónico.

| ¿Buscas una mejor solución?  [Explora Fygaro Shops](https://fygaro.com/w/shops) – el constructor de tiendas web listo para usar, sin necesidad de código, con pagos y logística integrados para simplificar tu negocio.
|| Ultima actualización: 14 Enero 2026 (Incluye devoluciones y mejoras de seguridad)

### Paso 1: Accede al Editor de Sitio de Wix

1. Inicia sesión en tu panel de control de Wix.
    
2. Abre el editor de tu sitio web.
---

### Paso 2: Activa el Modo Desarrollador

1. En la barra superior del editor del sitio, haz clic en **Modo Dev**.
    
2. Sigue los pasos de activación para habilitar el Modo Desarrollador.
---

### Paso 3: Instala el Paquete NPM Requerido

1. Una vez que el Modo Desarrollador esté habilitado, haz clic en **Paquetes y Aplicaciones** en la barra de menú izquierda.
    
2. Haz clic en el signo **+** junto a npm e instala el paquete `jsonwebtoken`.
---

### Paso 4: Crea los Archivos Backend

1. En la barra de menú izquierda, selecciona **Backend y Público**.
    
2. Haz clic en el signo **+** bajo **Backend**, luego haz clic en **Agregar Archivo .js**.
    
3. Nombra el archivo `fygaro.web.js` e incluye el siguiente código:

```
// Working ample Code Block, update as needed.

import { Permissions, webMethod } from "wix-web-module";
import jwt from 'jsonwebtoken'; // Install "jsonwebtoken" package in Wix Velo settings
import crypto from "crypto";
import { fetch } from "wix-fetch";
import wixSecretsBackend from "wix-secrets-backend";


export const getFJWT = webMethod(
    Permissions.Anyone,
    async (api_public, api_secrete, cartTotal, taxes, currency, orderNumber) => {
        
        const SECRET_KEY = api_secrete;
        const KID = api_public; 


        var payload = {
            amount: cartTotal,
            tax: taxes,
            currency: currency,
            custom_reference: orderNumber,
        };

        if(taxes == 0){
          payload = {
            amount: cartTotal,
            currency: currency,
            custom_reference: orderNumber,
          };
        }

        const options = {
            header: { kid: KID },
            algorithm: 'HS256',
        };


        return jwt.sign(payload, SECRET_KEY, options);

    }
);

/* -----------------------------
   Refund API (external payment)
-------------------------------- */

const FYGARO_BASE = "https://api.fygaro.com/api/v1/external/payment";

function to2dp(amount) {
  // expects number like 12.3 -> "12.30"
  return Number(amount).toFixed(2);
}

//refund function
export async function fygaroRefund({
  apiKeyKid,       // Fygaro API key (UUID string) -> JWT header kid
  apiSecret,       // Fygaro shared secret -> HS256 signing key
  transactionId,   // original Fygaro tx id (UUID)
  amount,          // optional: number (major units). If undefined => full refund
}) {
  const iat = Math.floor(Date.now() / 1000);
  const exp = iat + 300; // 5 minutes

  const payload = {
    transactionId,
    iat,
    exp,
    ...(amount !== undefined ? { amount: to2dp(amount) } : {}),
  };

  const token = jwt.sign(payload, apiSecret, {
    algorithm: "HS256",
    header: { kid: apiKeyKid, typ: "JWT" },
  });

  const res = await fetch(`${FYGARO_BASE}/refund/`, {
    method: "post",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ token }),
  });

  const text = await res.text();
  let data;
  try { data = JSON.parse(text); } catch { data = { raw: text }; }

  if (!res.ok) {
    // Bubble up something useful for Wix logs
    throw new Error(`Fygaro refund failed (${res.status}): ${JSON.stringify(data)}`);
  }

  // Expected 200: { transactionId, amount } :contentReference[oaicite:7]{index=7}
  return data;
}


/* -----------------------------
   Secrets Manager helpers
-------------------------------- */

function hookSecretName(keyId) {
  return `fy_hk_scrt_${keyId}`;
}

export async function getFygaroHookSecret(keyId) {
  // returns the secret value by name
  return wixSecretsBackend.getSecret(hookSecretName(keyId));
}

export async function upsertFygaroHookSecret(keyId, secretValue) {
  const name = hookSecretName(keyId);

  // listSecretInfo() returns array of { id, name, description? }
  const infos = await wixSecretsBackend.listSecretInfo();
  const existing = (infos || []).find((s) => s.name === name);

  if (existing?.id) {
    // updateSecret(secretId, { value })
    await wixSecretsBackend.updateSecret(existing.id, { value: secretValue });
    return { action: "updated", name };
  }

  // createSecret({ name, value }) 
  await wixSecretsBackend.createSecret({ name, value: secretValue });
  return { action: "created", name };
}


/* -----------------------------
   Webhook signature helpers
-------------------------------- */

export function normalizeHeaders(h = {}) {
  const out = {};
  Object.keys(h).forEach((k) => (out[String(k).toLowerCase()] = h[k]));
  return out;
}

export function parseFygaroSignature(sigHeader = "") {
  const parts = String(sigHeader).split(",").map((p) => p.trim());
  let t = null;
  const v1 = [];
  for (const part of parts) {
    const idx = part.indexOf("=");
    if (idx === -1) continue;
    const k = part.slice(0, idx).trim();
    const v = part.slice(idx + 1).trim();
    if (k === "t") t = v;
    if (k === "v1") v1.push(v);
  }
  return { t, v1 };
}

export function constantTimeEqualHex(aHex, bHex) {
  if (typeof aHex !== "string" || typeof bHex !== "string") return false;
  if (aHex.length !== bHex.length) return false;
  let diff = 0;
  for (let i = 0; i < aHex.length; i++) diff |= aHex.charCodeAt(i) ^ bHex.charCodeAt(i);
  return diff === 0;
}

export function computeFygaroExpectedSignature({ secret, t, rawBody }) {
  const message = `${t}.${rawBody}`;
  return crypto.createHmac("sha256", secret).update(message, "utf8").digest("hex");
}
```

4. Haz clic nuevamente en el signo **+**, pero esta vez selecciona **Exponer API del Sitio**.
    
5. Incluye el siguiente código en el nuevo archivo:

```
// Working sample Code Block, update as needed
import { ok, badRequest, unauthorized, serverError } from 'wix-http-functions';
import crypto from "crypto";
import wixPaymentProviderBackend from "wix-payment-provider-backend";
import { getSecret } from "wix-secrets-backend";

function normalizeHeaders(h = {}) {
  const out = {};
  Object.keys(h).forEach((k) => (out[String(k).toLowerCase()] = h[k]));
  return out;
}

function parseFygaroSignature(sigHeader = "") {
  
  const parts = String(sigHeader).split(",").map((p) => p.trim());
  let t = null;
  const v1 = [];
  for (const part of parts) {
    const idx = part.indexOf("=");
    if (idx === -1) continue;
    const k = part.slice(0, idx).trim();
    const v = part.slice(idx + 1).trim();
    if (k === "t") t = v;
    if (k === "v1") v1.push(v);
  }
  return { t, v1 };
}

// Constant-time compare for hex strings (no timingSafeEqual dependency)
function constantTimeEqualHex(aHex, bHex) {
  if (typeof aHex !== "string" || typeof bHex !== "string") return false;
  if (aHex.length !== bHex.length) return false;
  let diff = 0;
  for (let i = 0; i < aHex.length; i++) {
    diff |= aHex.charCodeAt(i) ^ bHex.charCodeAt(i);
  }
  return diff === 0;
}



//Hook URL> https://www.mysite.com/_functions/updateTransaction
// or https://user.wix.com/_functions/updateTransaction

export async function post_updateTransaction(request) {
    
    try {
    const headers = normalizeHeaders(request.headers);

    // ✅ RAW BODY (must be exact bytes)
    const buf = await request.body.buffer();
    const rawBody = buf.toString("utf8");

    const sigHeader = headers["fygaro-signature"];
    const keyId = headers["fygaro-key-id"];

    if (!sigHeader || !keyId) {
      console.log("Missing headers:", { hasSig: !!sigHeader, hasKeyId: !!keyId });
      return badRequest({ body: { error: "Missing Fygaro-Signature or Fygaro-Key-ID" } });
    }

    // 🔐 Secrets Manager: create secrets like "fy_hk_scrt_<keyId>"
    const secretName = `fy_hk_scrt_${keyId}`;
    const secret = await getSecret(secretName);

    if (!secret) {
      console.log("Unknown keyId (no secret found):", { keyId, secretName });
      return unauthorized({ body: { error: "Unknown Fygaro-Key-ID (no matching secret configured)" } });
    }

    const { t, v1 } = parseFygaroSignature(sigHeader);
    if (!t || !v1.length) {
      console.log("Malformed signature header:", { sigHeaderPreview: sigHeader.slice(0, 40) });
      return badRequest({ body: { error: "Malformed Fygaro-Signature header" } });
    }

    // Replay window (recommended by Fygaro)
    const now = Math.floor(Date.now() / 1000);
    const ts = parseInt(t, 10);
    if (!Number.isFinite(ts) || Math.abs(now - ts) > 300) {
      console.log("Stale timestamp:", { ts, now, driftSec: Math.abs(now - ts) });
      return unauthorized({ body: { error: "Stale timestamp" } });
    }

    // Compute HMAC as per Fygaro doc: message = t + "." + raw_body
    const message = `${t}.${rawBody}`;
    const expected = crypto.createHmac("sha256", secret).update(message, "utf8").digest("hex");

    const valid = v1.some((sig) => constantTimeEqualHex(expected, sig));
    if (!valid) {
      console.log("Invalid signature:", {
        keyId,
        t,
        v1Count: v1.length,
        bodyLen: rawBody.length,
      });
      return unauthorized({ body: { error: "Invalid signature" } });
    }

    // ✅ Verified — now parse JSON
    const payload = JSON.parse(rawBody);
    console.log("Verified Fygaro payload keys:", Object.keys(payload || {}));

    // New payload contains transactionId + customReference :contentReference[oaicite:1]{index=1}
    if (!payload?.customReference || !payload?.transactionId) {
      return badRequest({ body: { error: "Missing customReference or transactionId in payload" } });
    }

    await wixPaymentProviderBackend.submitEvent({
      event: {
        transaction: {
          wixTransactionId: payload.customReference,
          pluginTransactionId: payload.transactionId,
        },
      },
    });

    return ok({ body: { status: "ok" } });
  } catch (err) {
    console.error("Webhook processing failed:", err?.stack || err);
    return serverError({ body: { error: String(err?.message || err) } });
  }

}
```
---

### Paso 5: Configura el Plugin de Pago

1. Ve a la sección de **Plugins de Servicio**.
    
2. Haz clic en el signo **+** y selecciona la opción **Pagos**.
    
3. Cuando se te solicite un nombre, ingresa `FGW` (todo en mayúsculas).
---

### Paso 6: Edita los Archivos de Configuración de FGW

1. Ubica los archivos creados y abre `FGW-config.js`.
    
2. Copia y pega el siguiente código en el archivo:

```
// Working sample Code Block, update as needed
import * as paymentProvider from 'interfaces-psp-v1-payment-service-provider';

/** @returns {import('interfaces-psp-v1-payment-service-provider').PaymentServiceProviderConfig} */
export function getConfig() {
    //throw new Error('getConfig was not implemented');
    return {
        title: 'Fygaro Payments',
        paymentMethods: [{
            hostedPage: {
                title: 'Fygaro Payments',
                logos: {
                    white: {
                        svg: 'https://static-app.fygaro.com/static/img/10542c9bde49a22d7779.svg'
                        //png: 'https://freesvg.org/img/15930333081593032446pitr_Bananas_icon.png'
                    },
                    colored: {
                        svg: 'https://static-app.fygaro.com/static/img/10542c9bde49a22d7779.svg'
                        //png: 'https://freesvg.org/img/15930333081593032446pitr_Bananas_icon.png'
                    }
                }
            }
        }],
        credentialsFields: [{
                simpleField: {
                    name: 'clientId',
                    label: 'API id'
                }
            },
            {
                simpleField: {
                    name: 'clientSecret',
                    label: 'API secret'
                }
            },
            {
                simpleField: {
                    name: 'clientURL',
                    label: 'Botton URL'
                }
            },
            {
                dropdownField: {
                    name: 'taxFlag',
                    label: 'Send Tax Detail',
                    options: [
                        {
                            key: '0',
                            value: 'No'
                        },
                        {
                            key: '1',
                            value: 'Yes'
                        }
                        ]
                }
            }
        ]
    }
}
```

3. Abre el archivo `FGW.js` y pega el siguiente código:

```
// Working sample Code Block, update as needed
import * as paymentProvider from 'interfaces-psp-v1-payment-service-provider';
import { getFJWT, fygaroRefund, upsertFygaroHookSecret, createNewSecret } from 'backend/fygaro.web';

/**
 * This payment plugin endpoint is triggered when a merchant provides required data to connect their PSP account to a Wix site.
 * The plugin has to verify merchant's credentials, and ensure the merchant has an operational PSP account.
 * @param {import('interfaces-psp-v1-payment-service-provider').ConnectAccountOptions} options
 * @param {import('interfaces-psp-v1-payment-service-provider').Context} context
 * @returns {Promise<import('interfaces-psp-v1-payment-service-provider').ConnectAccountResponse | import('interfaces-psp-v1-payment-service-provider').BusinessError>}
 */
export const connectAccount = async (options, context) => {
        const { credentials } = options;

    const keyId = credentials?.clientId;
    const secret = credentials?.clientSecret;

    if (keyId && secret) {
        try {
        await upsertFygaroHookSecret(keyId, secret);
        } catch (e) {
        console.log("Fygaro secret upsert failed:", e?.message || e);
        }
    }

    return { credentials }; 
    

};

/**
 * This payment plugin endpoint is triggered when a buyer pays on a Wix site.
 * The plugin has to process this payment request but prevent double payments for the same `wixTransactionId`.
 * @param {import('interfaces-psp-v1-payment-service-provider').CreateTransactionOptions} options
 * @param {import('interfaces-psp-v1-payment-service-provider').Context} context
 * @returns {Promise<import('interfaces-psp-v1-payment-service-provider').CreateTransactionResponse | import('interfaces-psp-v1-payment-service-provider').BusinessError>}
 */
export const createTransaction = async (options, context) => {

    const {merchantCredentials, order, wixTransactionId} = options;

    const api_public = merchantCredentials.clientId;
    const api_secrete = merchantCredentials.clientSecret;
    const redirect_url = merchantCredentials.clientURL;
    
    const taxFlag = merchantCredentials.taxFlag;
    //Check for taxes
    var taxes = 0;
    if(taxFlag ==  1){
        if(order.description.charges.tax !== undefined && order.description.charges.tax > 0){
            taxes = parseInt(order.description.charges.tax) / Math.pow(10,2);
        }
    }

    var totalAmount = parseInt(order.description.totalAmount) / Math.pow(10,2);
    const currency = order.description.currency;
    const orderNumber = wixTransactionId;

    const fygaroCheckout = await getFJWT(api_public, api_secrete, totalAmount, taxes, currency, orderNumber);

    return {

        "pluginTransactionId": orderNumber,
        "redirectUrl": `${redirect_url}?jwt=${fygaroCheckout}`

    } 

};

/**
 * This payment plugin endpoint is triggered when a merchant refunds a payment made on a Wix site.
 * The plugin has to process this refund request but prevent double refunds for the same `wixRefundId`.
 * @param {import('interfaces-psp-v1-payment-service-provider').RefundTransactionOptions} options
 * @param {import('interfaces-psp-v1-payment-service-provider').Context} context
 * @returns {Promise<import('interfaces-psp-v1-payment-service-provider').CreateRefundResponse | import('interfaces-psp-v1-payment-service-provider').BusinessError>}
 */
function minorToMajor(minor) {
  // Wix often provides amounts as minor units (e.g. cents).
  // If you already get major units, remove this conversion.
  return Number(minor) / 100;
}

export const refundTransaction = async (options, context) => {

    const { merchantCredentials, pluginTransactionId, refund, wixRefundId } = options;

    const api_public = merchantCredentials.clientId;       // kid
    const api_secrete = merchantCredentials.clientSecret;  // HS256 secret

    // 1) This should be the original Fygaro transactionId you saved as pluginTransactionId
    const fygaroTransactionId = pluginTransactionId;

    // 2) Refund amount
    // If Wix provides minor units, convert. If it provides major, pass directly.
    // If you want "full refund" behavior, pass undefined when refund amount is not specified.
    const refundAmountMajor =
        refund?.amount !== undefined ? minorToMajor(refund.amount) : undefined;

    // 3) Call Fygaro refund API
    const result = await fygaroRefund({
        apiKeyKid: api_public,
        apiSecret: api_secrete,
        transactionId: fygaroTransactionId,
        amount: refundAmountMajor,
    });

    // 4) Return back to Wix (shape may vary slightly depending on your PSP interface version)
    return {
        pluginRefundId: result.transactionId, // refund transaction id from Fygaro
    };

};
```

4. Haz clic en el botón **Publicar** en la esquina superior derecha del editor.
---

### Paso 7: Conecta los Pagos de Fygaro

1. Regresa al Panel de Control de Wix.
    
2. Ve a **Configuraciones** y localiza la opción **Aceptar Pagos**.
    
3. Recorre las opciones de pago hasta encontrar Fygaro y haz clic en **Conectar**.
    
4. Ingresa tu **API Key** y **API Secret** obtenidos desde Fygaro (consulta [este tutorial](https://help.fygaro.com/en-us/article/obtain-api-credentials-bf1byq/) para saber cómo obtener tus credenciales).
    
5. En el campo **URL del Botón**, pega el enlace del botón de pago de Fygaro (consulta [este tutorial](https://help.fygaro.com/en-us/article/fygaro-links-payment-buttons-tck05y/) para saber cómo crear un botón de pago con monto variable).
    
6. Haz clic en **Conectar**.
---

### Paso 8: Configura Tu Botón de Pago en Fygaro

Cuando crees tu botón de pago en Fygaro:

1. Activa la opción **JWT** en **Configuraciones Avanzadas**.
    
2. En el campo **Hook**, ingresa la URL de tu sitio Wix seguido de `/_functions/updateTransaction`. Ejemplo:

```
https://www.misitio.com/_functions/updateTransaction
```

3. Guarda tu botón de pago.
---

### Paso Final: Prueba Tu Integración

Para asegurarte de que todo está configurado correctamente:

1. Realiza una transacción de prueba por USD $1 (o el equivalente en moneda local).
    
2. Confirma que el pago se procese exitosamente.
---

¡Ahora estás listo para comenzar a aceptar pagos con Fygaro en tu sitio de Wix!

||| Este tutorial es un ejemplo guiado sobre cómo implementar pagos en tu sitio existente de Wix. Fygaro no garantiza la exactitud, funcionalidad o idoneidad del mismo para tus necesidades específicas. La implementación es bajo tu propio riesgo, y podrías necesitar contratar a un desarrollador de Wix para personalizar la configuración según los requerimientos de tu cuenta.