From ca130eac530a40234fdbbd76f9095474a24fea1c Mon Sep 17 00:00:00 2001 From: New1Direction <285551516+New1Direction@users.noreply.github.com> Date: Wed, 10 Jun 2026 15:54:16 -0600 Subject: [PATCH] fix(calcpro): subscription mode ($4.99/mo recurring price) + mode-aware copy --- worker/src/digital.ts | 15 ++++++++++++--- worker/src/tools.ts | 4 ++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/worker/src/digital.ts b/worker/src/digital.ts index 6b2ee1b..c4fcda5 100644 --- a/worker/src/digital.ts +++ b/worker/src/digital.ts @@ -26,6 +26,7 @@ type ProductId = "guide" | "calcpro"; interface ProductDef { priceEnv: keyof Env; price: string; // display only + mode?: "payment" | "subscription"; // default one-time; calcpro is recurring title: string; tagline: string; // sales-page sub-headline bullets: string[]; // "what's inside" @@ -49,7 +50,8 @@ const PRODUCTS: Record = { }, calcpro: { priceEnv: "STRIPE_PRICE_CALCPRO", - price: "$4.99", + price: "$4.99/mo", + mode: "subscription", title: "Pro Portfolio Calculator", tagline: "The free calculator does one item. This does your whole collection — track every sealed box and card, see your total savings and resale profit at a glance. Saves on your device, no account.", bullets: [ @@ -246,14 +248,21 @@ export async function createDigitalCheckout(c: Context<{ Bindings: Env }>, produ const origin = new URL(c.req.url).origin; const source = String(c.req.query("source") || c.req.query("utm_source") || "") .slice(0, 40).replace(/[^a-z0-9_.:-]/gi, "") || "direct"; + const mode = def.mode || "payment"; const form = new URLSearchParams(); - form.set("mode", "payment"); + form.set("mode", mode); form.set("line_items[0][price]", price); form.set("line_items[0][quantity]", "1"); form.set("allow_promotion_codes", "true"); form.set("client_reference_id", source); form.set("metadata[kind]", `digital:${product}`); form.set("metadata[source]", source); + if (mode === "subscription") { + // Carry the same metadata onto the subscription so a future cancel webhook + // can find + revoke the access token for this product. + form.set("subscription_data[metadata][kind]", `digital:${product}`); + form.set("subscription_data[metadata][source]", source); + } form.set("success_url", `${origin}/${product}/unlock?session_id={CHECKOUT_SESSION_ID}`); form.set("cancel_url", `${origin}/${product}?canceled=1`); return await createSessionResponse(c as any, form); @@ -319,7 +328,7 @@ ${def.bullets.map((b) => `
  • ${esc(b)}
  • `).join("\n")}
    ${esc(def.price)}
    -

    one-time · instant access · no signup · no app

    +

    ${def.mode === "subscription" ? "cancel anytime" : "one-time"} · instant access · no signup · no app

    Secure checkout by Stripe. Works on mobile. Reopen anytime from your access link.

    diff --git a/worker/src/tools.ts b/worker/src/tools.ts index c81bac0..7a83f1b 100644 --- a/worker/src/tools.ts +++ b/worker/src/tools.ts @@ -459,9 +459,9 @@ ${alternates(origin, "calc")}

    Do your whole collection — Pro Portfolio Calculator

    -

    The free tool above does one item. Pro tallies every sealed box and card you own — total spend, savings, and resale profit at a glance, saved on your device. One-time, no app.

    +

    The free tool above does one item. Pro tallies every sealed box and card you own — total spend, savings, and resale profit at a glance, saved on your device. Cancel anytime.