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.
Paso 1: Accede al Editor de Sitio de Wix
- Inicia sesión en tu panel de control de Wix.
- Abre el editor de tu sitio web.
Paso 2: Activa el Modo Desarrollador
- En la barra superior del editor del sitio, haz clic en Modo Dev.
- Sigue los pasos de activación para habilitar el Modo Desarrollador.
Paso 3: Instala el Paquete NPM Requerido
- Una vez que el Modo Desarrollador esté habilitado, haz clic en Paquetes y Aplicaciones en la barra de menú izquierda.
- Haz clic en el signo + junto a npm e instala el paquete
jsonwebtoken.
Paso 4: Crea los Archivos Backend
- En la barra de menú izquierda, selecciona Backend y Público.
- Haz clic en el signo + bajo Backend, luego haz clic en Agregar Archivo .js.
- Nombra el archivo
fygaro.web.jse 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 `fygaro_hook_secret_${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");
}
- Haz clic nuevamente en el signo +, pero esta vez selecciona Exponer API del Sitio.
- 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 "fygaro_hook_secret_<keyId>"
const secretName = `fygaro_hook_secret_${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
- Ve a la sección de Plugins de Servicio.
- Haz clic en el signo + y selecciona la opción Pagos.
- Cuando se te solicite un nombre, ingresa
FGW(todo en mayúsculas).
Paso 6: Edita los Archivos de Configuración de FGW
- Ubica los archivos creados y abre
FGW-config.js.
- 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'
}
]
}
}
]
}
}
- Abre el archivo
FGW.jsy 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
};
};
- Haz clic en el botón Publicar en la esquina superior derecha del editor.
Paso 7: Conecta los Pagos de Fygaro
- Regresa al Panel de Control de Wix.
- Ve a Configuraciones y localiza la opción Aceptar Pagos.
- Recorre las opciones de pago hasta encontrar Fygaro y haz clic en Conectar.
- Ingresa tu API Key y API Secret obtenidos desde Fygaro (consulta este tutorial para saber cómo obtener tus credenciales).
- En el campo URL del Botón, pega el enlace del botón de pago de Fygaro (consulta este tutorial para saber cómo crear un botón de pago con monto variable).
- Haz clic en Conectar.
Paso 8: Configura Tu Botón de Pago en Fygaro
Cuando crees tu botón de pago en Fygaro:
- Activa la opción JWT en Configuraciones Avanzadas.
- En el campo Hook, ingresa la URL de tu sitio Wix seguido de
/_functions/updateTransaction. Ejemplo:
https://www.misitio.com/_functions/updateTransaction
- Guarda tu botón de pago.
Paso Final: Prueba Tu Integración
Para asegurarte de que todo está configurado correctamente:
- Realiza una transacción de prueba por USD $1 (o el equivalente en moneda local).
- Confirma que el pago se procese exitosamente.
¡Ahora estás listo para comenzar a aceptar pagos con Fygaro en tu sitio de Wix!
Actualizado el: 14/01/2026
¡Gracias!
