> ## 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).

# Embed Fygaro Link into Wix Checkout

**Tutorial: Connecting Fygaro Payments to WIX Checkout**

### Disclaimer

Ensure you back up your site before implementing this integration and thoroughly test it before enabling it in production.

Connecting Fygaro with Wix to collect payments requires:

* A Fygaro subscription plan with Hook capabilities.
* A Wix account with an online store/e-commerce feature.

| Looking for a better solution?  [Explore Fygaro Shops](https://fygaro.com/w/shops)  – the no-code, ready-to-use webstore builder with built-in payments and logistics to simplify your business.
|| Last Update: January 14, 2026 (Includes refunds and security improvements)
---

### Step 1: Access Wix Website Editor

1. Log in to your Wix dashboard.
2. Open the website editor.

* * ---

### Step 2: Enable Developer Mode

1. In the top menu bar of the site editor, click on **Dev Mode**.
2. Follow the activation steps to enable Developer Mode.

* * ---

### Step 3: Install Required NPM Package

1. Once Developer Mode is enabled, click on **Packages & App** in the left menu bar.
2. Click the **+** sign next to npm and install the `jsonwebtoken` package.

* * ---

### Step 4: Create Backend Files

1. In the left menu bar, select **Backend and Public**.
2. Click the **+** sign under **Backend**, then click **Add .js File**.
3. Name the file `fygaro.web.js` and include the following code:

```
// 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 }) — keep minimal
  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. Click the **+** sign again, but this time select **Expose Site API**.
5. Include the following code in the new file (https-functions.js):

```
// 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) } });
  }

}
```

* * ---

### Step 5: Configure Payment Plugin

1. Navigate to the **Service Plugins** section.
2. Click the **+** sign and select the **Payments** option.
3. When prompted for a name, enter `FGW` (all uppercase).

* * ---

### Step 6: Edit FGW Configuration Files

1. Locate the created files and open `FGW-config.js`.
2. Copy and paste the following code into the file:

```
// 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. Open the `FGW.js` file and paste the following code:

```
// 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. Click on the **Publish** button in the top-right corner of the editor.
---

### Step 7: Connect Fygaro Payments

1. Return to the Wix Dashboard.
2. Go to **Settings** and locate the **Accept Payments** option.
3. Scroll through the payment options to find Fygaro and click on **Connect**.
4. Enter your **API Key** and **API Secret** obtained from Fygaro (refer to [this tutorial](https://help.fygaro.com/en-us/article/obtain-api-credentials-bf1byq/) to learn how to obtain your API keys).
5. In the **Button URL** field, paste the Fygaro Payment Button link (refer to [this tutorial](https://help.fygaro.com/en-us/article/fygaro-links-payment-buttons-tck05y/) to learn how to create a variable amount payment button).
6. Click **Connect**.
---

### Step 8: Configure Your Payment Button in Fygaro

When creating your payment button in Fygaro:

1. Activate the **JWT** option in **Advanced Settings**.
2. In the **Hook** field, enter your Wix site URL followed by `/_functions/updateTransaction`. Example:
    
```
https://www.mysite.com/_functions/updateTransaction
```
    
3. Save your payment button.
---

### Final Step: Test Your Integration

To ensure everything is set up correctly:

1. Run a test transaction of USD $1 (or the local currency equivalent).
2. Confirm that the payment processes successfully.
---

Now you're ready to start accepting payments with Fygaro on your Wix site!

||| This tutorial is a guided example on how to implement payments on your existing Wix site. Fygaro provides no guarantees regarding its accuracy, functionality, or suitability for your specific needs. Implementation is at your own risk, and you may need to hire a Wix developer to customize the setup according to the requirements of your specific Wix account.