Getting Started

Table of Content

Table of Content

Table of Content

Configure your Framer Store

Configure your Framer Store

Learn how to set up your Framer store to start selling in Framer.

Learn how to set up your Framer store to start selling in Framer.

Configure your Framer account

It’s important to know that no prior configuration is required to use our component. However, we recommend that you have an active Framer paid plan.

Configure your Stripe Account

Framer Configurator uses Stripe for product checkout pages, enabling direct purchases. This ensures a solid, secure, and complete payment foundation for users who want to buy from our clients’ stores.

Therefore, all Framer Configurator users must have a Stripe account set up in advance.

Create a Stripe account today.

Get your Stripe Api keys

To get your Stripe secret key,

log in to your Stripe dashboard, navigate to the Developers > API keys section in the left-hand menu, and then click the "Reveal test key" button (or the equivalent for live keys) to copy your secret key

. For a new key, you may need to select "Create key", verify your identity, give the key a name, and then copy the generated key value.

Here are the detailed steps:

  1. Log in to your Stripe account: Go to Stripe and sign in to your dashboard.

  2. Access the API keys page: In the left-hand navigation menu, find and click on the Developers section, then select API keys from the submenu.



  3. Reveal or create your key:

    • To reveal an existing key: Look for your publishable and secret keys. Click on the "Reveal test key" button next to the secret key you need.

    • To create a new key: If you don't see an existing key or need a new one, click the "Create key" button.



  4. Verify your identity: You will likely need to enter a verification code sent to your email or phone.

  5. Name the key and save: After verification, enter a name for your key in the provided field and click Create.

  6. Copy the key: The new secret key will be displayed; click on it to copy the value.

  7. Save the key securely: Paste the copied key into a secure location and click Done.

Instructions to create the worker

Option A — 100% From the Dashboard (More simple)

  1. Create the worker

  • Go to Cloudflare → Workers & PagesCreateHTTP RouterQuick Edit.

  • Borra lo que haya y pega este código JS:

// Minimal Worker JS (sin build) — per-license Stripe + Lemon + imágenes en Checkout
const ALLOW_ORIGIN = "*";
const KEYRING = new Map(); // { license -> { pk, sk, updatedAt } }

function corsHeaders(extra = {}) {
  return {
    "Access-Control-Allow-Origin": ALLOW_ORIGIN,
    "Access-Control-Allow-Methods": "GET,POST,OPTIONS",
    "Access-Control-Allow-Headers": "Content-Type,Authorization",
    "Access-Control-Max-Age": "86400",
    ...extra,
  };
}
const json = (body, init = {}) => {
  const h = new Headers(init.headers || {});
  Object.entries(corsHeaders()).forEach(([k, v]) => h.set(k, v));
  h.set("Content-Type", "application/json;charset=utf-8");
  return new Response(JSON.stringify(body), { ...init, headers: h });
};
const noContent = () => new Response(null, { status: 204, headers: corsHeaders() });

async function lemonPost(env, path, payload) {
  const res = await fetch(`https://api.lemonsqueezy.com${path}`, {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${env.LEMON_API_KEY}`,
      "Accept": "application/json",
      "Content-Type": "application/json",
    },
    body: JSON.stringify(payload),
  });
  const raw = await res.text();
  let data; try { data = JSON.parse(raw); } catch {}
  return { ok: res.ok, status: res.status, data, raw };
}
async function lemonValidate(env, license) {
  const tries = [
    { path: "/v1/licenses/validate",     body: { license_key: license } },
    { path: "/v1/license-keys/validate", body: { license_key: license } },
    { path: "/v1/licenses/verify",       body: { license_key: license } },
    { path: "/v1/licenses/validate",     body: { license: license } },
  ];
  let last = null;
  for (const t of tries) {
    const r = await lemonPost(env, t.path, t.body);
    if (r.ok) return r;
    last = r;
    if (r.status === 404) continue;
  }
  return last || { ok: false, status: 400, raw: "unknown" };
}
function getStripeSK(license, env) {
  const rec = KEYRING.get(license) || KEYRING.get("default");
  return (rec && rec.sk) || env.STRIPE_SECRET_KEY;
}
async function stripeCreateCheckout(sk, payload) {
  const body = new URLSearchParams();
  if (Array.isArray(payload.line_items) && payload.line_items.length > 0) {
    payload.line_items.forEach((it, i) => {
      const p = it?.price_data || {};
      const pd = p?.product_data || {};
      body.append(`line_items[${i}][quantity]`, String(it.quantity || 1));
      body.append(`line_items[${i}][price_data][currency]`, (p.currency || payload.currency || "USD").toLowerCase());
      body.append(`line_items[${i}][price_data][unit_amount]`, String(p.unit_amount || 0));
      if (pd.name) body.append(`line_items[${i}][price_data][product_data][name]`, pd.name);
      if (pd.description) body.append(`line_items[${i}][price_data][product_data][description]`, pd.description);
      if (Array.isArray(pd.images) && pd.images[0]) {
        body.append(`line_items[${i}][price_data][product_data][images][]`, pd.images[0]);
      }
    });
  } else if (payload.amount_total_cents && payload.currency) {
    body.append("line_items[0][quantity]", "1");
    body.append("line_items[0][price_data][currency]", String(payload.currency).toLowerCase());
    body.append("line_items[0][price_data][unit_amount]", String(payload.amount_total_cents));
    body.append("line_items[0][price_data][product_data][name]", payload.title || "Order");
    if (payload.description) body.append("line_items[0][price_data][product_data][description]", payload.description);
    if (payload.image) body.append("line_items[0][price_data][product_data][images][]", payload.image);
  } else {
    return { ok: false, status: 400, data: { error: "invalid payload" }, raw: "invalid" };
  }
  body.append("mode", "payment");
  body.append("success_url", payload.success_url || "https://example.com/success");
  body.append("cancel_url",  payload.cancel_url  || "https://example.com/cancel");

  const res = await fetch("https://api.stripe.com/v1/checkout/sessions", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${sk}`,
      "Content-Type": "application/x-www-form-urlencoded",
    },
    body: body.toString(),
  });
  const raw = await res.text();
  let data; try { data = JSON.parse(raw); } catch {}
  return { ok: res.ok, status: res.status, data, raw };
}

export default {
  async fetch(req, env) {
    const url = new URL(req.url);
    const { pathname, searchParams } = url;

    if (req.method === "OPTIONS") return noContent();

    // Verificar licencia
    if (req.method === "POST" && (pathname === "/license/verify" || pathname === "/verifyLicense")) {
      try {
        const { license } = await req.json();
        if (!license) return json({ valid: false, error: "missing license" }, { status: 400 });
        const r = await lemonValidate(env, String(license).trim());
        if (!r.ok) return json({ valid: false, error: `lemon http ${r.status}: ${r.raw}` }, { status: 400 });
        const meta = r.data?.meta || r.data?.data || r.data;
        return json({ valid: true, lemon: meta || r.data || {} });
      } catch (e) {
        return json({ valid: false, error: String(e) }, { status: 400 });
      }
    }

    // Guardar claves Stripe (por licencia)
    if (req.method === "POST" && (pathname === "/plugin/save-secrets" || pathname === "/creds/save")) {
      try {
        const { license, stripe_pk, stripe_sk } = await req.json();
        const key = (license || "default").trim();
        if (!key) return json({ ok: false, error: "missing license" }, { status: 400 });
        if (!stripe_pk || !/^pk_(test|live)_/.test(stripe_pk)) return json({ ok: false, error: "invalid publishable key" }, { status: 400 });
        if (!stripe_sk || !/^sk_(test|live)_/.test(stripe_sk)) return json({ ok: false, error: "invalid secret key" }, { status: 400 });
        KEYRING.set(key, { pk: stripe_pk, sk: stripe_sk, updatedAt: Date.now() });
        return json({ ok: true });
      } catch (e) {
        return json({ ok: false, error: String(e) }, { status: 400 });
      }
    }

    // Estado de credenciales
    if (req.method === "GET" && pathname === "/creds/status") {
      const license = (searchParams.get("license") || "default").trim();
      const rec = KEYRING.get(license);
      return json({ hasCreds: !!rec, updatedAt: rec?.updatedAt || null });
    }

    // Crear sesión de checkout
    if (req.method === "POST" && pathname === "/create-checkout-session") {
      try {
        const payload = await req.json();
        const lic = (payload.license || "default").trim();
        const sk = getStripeSK(lic, env);
        if (!sk) return json({ error: "No Stripe secret available for this license" }, { status: 400 });
        const r = await stripeCreateCheckout(sk, payload);
        if (!r.ok) return json({ error: r.raw || `stripe ${r.status}` }, { status: r.status || 400 });
        const urlOut = r.data?.url;
        if (!urlOut) return json({ error: "Stripe did not return a Checkout URL" }, { status: 400 });
        return json({ id: r.data.id, url: urlOut });
      } catch (e) {
        return json({ error: String(e) }, { status: 400 });
      }
    }

    return json({ error: "Not found" }, { status: 404 });
  }
};

Add the secrets in the dashboard

  • Visit https://workers.new (open the editor).

  • Paste the same JS above.

  • Clic on Save & Deploy.

  • Add the secret in the Settings → Variables like in the option A.

Insert the worker into the Component


E-Commerce on Framer
was never this easy

E-Commerce on Framer was never this easy

A powerful tool designed to sell products dynamically in Framer—no third-party tools required.

Made with ♥︎ by Ollanta & Carlos Gerónimo