const ALLOW_ORIGIN = "*";
const KEYRING = new Map();
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();
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 });
}
}
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 });
}
}
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 });
}
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 });
}
};