From e6f542ac6eef15ec3f4949a818caf4a4e5e07a3e Mon Sep 17 00:00:00 2001 From: New1Direction <285551516+New1Direction@users.noreply.github.com> Date: Wed, 10 Jun 2026 15:11:29 -0600 Subject: [PATCH] feat(impulse): one-time no-install digital product engine + $2.99 Drop-Day Playbook --- worker/src/digital.ts | 227 ++++++++++++++++++++++++++++++++++++ worker/src/index.ts | 20 ++++ worker/src/stripe.ts | 7 +- worker/test/digital.test.ts | 55 +++++++++ 4 files changed, 307 insertions(+), 2 deletions(-) create mode 100644 worker/src/digital.ts create mode 100644 worker/test/digital.test.ts diff --git a/worker/src/digital.ts b/worker/src/digital.ts new file mode 100644 index 0000000..adf6439 --- /dev/null +++ b/worker/src/digital.ts @@ -0,0 +1,227 @@ +// digital.ts — one-time, no-install, instant-unlock impulse products. +// +// The consumer SEO traffic is mostly MOBILE and multilingual; a Chrome +// extension (QuickCatch) or a subscription can't convert it. These products are +// the opposite: $1-7, one-time (mode=payment), zero install, delivered on the +// page the instant Stripe confirms payment. Same engine for every product — +// add a row to PRODUCTS + a Stripe price id and it's sellable. +// +// Flow: buy button → POST /api/v1//checkout → Stripe Checkout (collects +// card + email, no pre-form) → success_url //unlock?session_id=… → +// verify payment_status==="paid" → render content + mint a re-access token +// (KV dlg:, 1y) so the buyer can reopen it from //read?t=… . + +import type { Context } from "hono"; +import { STRIPE_API, createSessionResponse } from "./stripe"; + +type Env = { + KEYS: KVNamespace; + STRIPE_SECRET_KEY?: string; + STRIPE_PRICE_GUIDE?: string; +}; + +type ProductId = "guide"; + +interface ProductDef { + priceEnv: keyof Env; + price: string; // display only + title: string; + render: () => string; // the gated content (HTML body) +} + +const PRODUCTS: Record = { + guide: { + priceEnv: "STRIPE_PRICE_GUIDE", + price: "$2.99", + title: "The Pokémon Drop-Day Playbook", + render: () => GUIDE_HTML, + }, +}; + +const esc = (s: string) => + String(s).replace(/[&<>"]/g, (ch) => ({ "&": "&", "<": "<", ">": ">", '"': """ } as any)[ch]); + +const TOKEN_TTL_DAYS = 365; +function newToken(): string { + return Array.from(crypto.getRandomValues(new Uint8Array(16))) + .map((b) => b.toString(16).padStart(2, "0")) + .join(""); +} + +// Mobile-first shell in the amber/dark identity. +function shell(title: string, body: string): string { + return ` + +${esc(title)} +
${body}
`; +} + +// ----------------------------- the product ------------------------------ +// Real, actionable playbook assembled from genuine shopper practice — no +// invented dates or prices (those move; the method doesn't). +const GUIDE_HTML = ` +

The Pokémon Drop-Day Playbook

+

Land sealed product at retail instead of paying a reseller. A tight, do-this checklist for restock day — works on any store, on your phone.

+ +

1 · The 90-second pre-drop checklist

+
    +
  • Logged into the store account you'll buy from (not just browsing).
  • +
  • Payment method + shipping address saved for one-tap checkout.
  • +
  • The exact product page open — not the search results, the product itself.
  • +
  • Store app installed with notifications on (apps often get stock before web).
  • +
  • Your max price decided in advance, so you don't hesitate or overpay.
  • +
+ +

2 · Store-by-store setup

+

Pokémon Center — the hardest, because demand is global and exclusives sell out in seconds. Log in early, save payment, keep the product tab open, and expect a queue on big drops; don't refresh frantically — that can bounce you back.

+

Walmart — the app beats the website on most drops. Be signed in with a saved card; the "Add to cart" button appears the instant stock flips, so have the page ready.

+

Target — in-store pickup often shows stock before shipping does; set your store and check pickup availability, not just ship-to-home.

+

Best Buy — account + saved payment, and watch for "available soon"→"add to cart" on the product page during the window.

+ +

3 · Retail vs resale — the 10-second math

+

Before you pay a reseller, do the margin check. Resale gets eaten by fees and shipping:

+
Keep cost = resale price − (resale × ~12-15% marketplace fee) − shipping.
+If that number is close to retail, just buy retail and skip the markup. If a sealed item is far above retail resale, it's usually worth grabbing at retail to flip — but only if you can actually land it.
+

A free calculator that does this for you: /tools/pokemon-resale-calculator

+ +

4 · Is it worth grading?

+

Grading pays off when a top grade multiplies value — modern alt arts and special-illustration rares of popular Pokémon, vintage holos in clean condition, key chase cards from hyped sets. It rarely pays on bulk or already-cheap cards once you count the grading fee and the weeks of turnaround.

+
    +
  • Grade if: the card is a chase card, condition is near-mint or better, and the PSA-10/raw price gap clearly beats the grading cost.
  • +
  • Skip if: it's common, visibly off-center/whitened, or the graded premium is thin.
  • +
+ +

5 · Timing + avoiding traps

+
    +
  • Restocks cluster early morning and mid-week; turn on alerts rather than guessing.
  • +
  • Big restocks come in waves — if you miss the first, keep the page armed for the next few minutes.
  • +
  • Resale prices fall after a restock. Don't panic-buy at the peak; buy what you'll actually keep or play.
  • +
+ +
That's the whole method. Save this page or print it (your browser's Share → Print). You can reopen it anytime from the link below.
+`; + +// ------------------------------- handlers ------------------------------- + +// POST /api/v1//checkout — no email pre-form (Stripe collects it); +// lowest-friction impulse path. 503 until the price id is set (fail-closed). +export async function createDigitalCheckout(c: Context<{ Bindings: Env }>, product: ProductId) { + const def = PRODUCTS[product]; + if (!def) return c.json({ error: "unknown_product" }, 404); + const price = c.env[def.priceEnv] as string | undefined; + if (!c.env.STRIPE_SECRET_KEY || !price) { + return c.json({ error: `${product}_not_configured` }, 503); + } + 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 form = new URLSearchParams(); + form.set("mode", "payment"); + 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); + 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); +} + +// GET //unlock?session_id=… — verify paid, mint a re-access token, +// deliver the content. Idempotent: refreshing re-verifies and re-shows. +export async function digitalUnlock(c: Context<{ Bindings: Env }>, product: ProductId) { + const def = PRODUCTS[product]; + if (!def) return c.notFound(); + const session_id = c.req.query("session_id"); + if (!session_id) return c.html(shell(def.title, `

${esc(def.title)}

Missing session. Back

`), 400); + if (!c.env.STRIPE_SECRET_KEY) return c.html(shell(def.title, `

Almost there

Billing isn't switched on yet — your payment is safe; email support and we'll send it.

`)); + const sRes = await fetch(`${STRIPE_API}/checkout/sessions/${encodeURIComponent(session_id)}`, { + headers: { authorization: `Bearer ${c.env.STRIPE_SECRET_KEY}` }, + }); + const session: any = await sRes.json().catch(() => null); + if (!sRes.ok || !session) return c.html(shell(def.title, `

Hmm

Couldn't confirm that checkout. Email support with your receipt.

`)); + if (session.payment_status !== "paid") { + return c.html(shell(def.title, `

Processing…

Payment status: ${esc(session.payment_status || "pending")}. Refresh in a moment.

`)); + } + // Mint a stable re-access token keyed to this session (idempotent per session). + const tokKey = `dlgsess:${session_id}`; + let token = await c.env.KEYS.get(tokKey); + if (!token) { + token = newToken(); + await c.env.KEYS.put(tokKey, token, { expirationTtl: TOKEN_TTL_DAYS * 86400 }); + await c.env.KEYS.put(`dlg:${token}`, JSON.stringify({ product, ts: Date.now() }), { expirationTtl: TOKEN_TTL_DAYS * 86400 }); + } + return c.html(renderUnlocked(def, product, token)); +} + +// GET //read?t=… — re-access via the token (no account). +export async function digitalRead(c: Context<{ Bindings: Env }>, product: ProductId) { + const def = PRODUCTS[product]; + if (!def) return c.notFound(); + const t = c.req.query("t") || ""; + const raw = t ? await c.env.KEYS.get(`dlg:${t}`) : null; + let ok = false; + try { ok = !!raw && JSON.parse(raw).product === product; } catch {} + if (!ok) return c.html(shell(def.title, `

${esc(def.title)}

That access link isn't valid. Get the playbook

`), 403); + return c.html(renderUnlocked(def, product, t)); +} + +function renderUnlocked(def: ProductDef, product: ProductId, token: string): string { + return shell(def.title, `${def.render()} +

Reopen anytime: your private access link — bookmark it.

`); +} + +// GET / — the mobile-first sales page. +export function digitalSalesHtml(origin: string, product: ProductId, canceled: boolean): string { + const def = PRODUCTS[product]; + if (!def) return shell("Not found", "

Not found

"); + const body = ` +

${esc(def.title)}

+

Stop paying resellers. The exact restock-day checklist to land Pokémon sealed product at retail — on your phone, no app, no subscription.

+${canceled ? `
No worries — checkout was canceled. The playbook's still here when you want it.
` : ""} +
+

What's inside

+
    +
  • The 90-second pre-drop checklist
  • +
  • Store-by-store setup (Pokémon Center, Walmart, Target, Best Buy)
  • +
  • Retail-vs-resale margin math (skip the markup)
  • +
  • Is-it-worth-grading decision framework
  • +
  • Restock timing + scalper-trap avoidance
  • +
+
+
+
${esc(def.price)}
+

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

+ +

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

+
+`; + return shell(def.title, body); +} diff --git a/worker/src/index.ts b/worker/src/index.ts index 4872920..f1073d1 100644 --- a/worker/src/index.ts +++ b/worker/src/index.ts @@ -175,6 +175,26 @@ app.get("/tools/:lang", async (c) => { return c.html(toolsIndexHtml(new URL(c.req.url).origin, lang as any)); }); +// ---- Impulse digital products: one-time, no-install, instant on-page unlock. +// Mobile-first $2.99 buy for the consumer SEO traffic that can't install an +// extension or commit to a subscription. Engine in ./digital. +app.get("/guide", async (c) => { + const { digitalSalesHtml } = await import("./digital"); + return c.html(digitalSalesHtml(new URL(c.req.url).origin, "guide", c.req.query("canceled") === "1")); +}); +app.post("/api/v1/guide/checkout", async (c) => { + const { createDigitalCheckout } = await import("./digital"); + return createDigitalCheckout(c as any, "guide"); +}); +app.get("/guide/unlock", async (c) => { + const { digitalUnlock } = await import("./digital"); + return digitalUnlock(c as any, "guide"); +}); +app.get("/guide/read", async (c) => { + const { digitalRead } = await import("./digital"); + return digitalRead(c as any, "guide"); +}); + app.get("/api/v1/health", (c) => c.json({ ok: true, version: "0.1.0", env: c.env.ENVIRONMENT }) ); diff --git a/worker/src/stripe.ts b/worker/src/stripe.ts index 7ae9ffc..519e136 100644 --- a/worker/src/stripe.ts +++ b/worker/src/stripe.ts @@ -35,9 +35,12 @@ type Env = { // plan for collectors — auto-cart + anti-bot hard sites + unlimited watches in // the browser extension. A license key (not an API key) gates the extension. STRIPE_PRICE_QUICKCATCH?: string; + // One-time, no-install impulse products sold on the consumer SEO pages + // (mobile-first, instant on-page unlock). 503 until the price id is set. + STRIPE_PRICE_GUIDE?: string; }; -const STRIPE_API = "https://api.stripe.com/v1"; +export const STRIPE_API = "https://api.stripe.com/v1"; // =================== Checkout Session creator =================== @@ -882,7 +885,7 @@ export async function quickCatchActivate(c: Context<{ Bindings: Env }>) {

Don't have the extension yet? Install QuickCatch, then open its Options and paste the key. Keep this key — you can re-open this page from your receipt anytime.

`); } -async function createSessionResponse( +export async function createSessionResponse( c: Context<{ Bindings: Env }>, form: URLSearchParams ) { diff --git a/worker/test/digital.test.ts b/worker/test/digital.test.ts new file mode 100644 index 0000000..645c4db --- /dev/null +++ b/worker/test/digital.test.ts @@ -0,0 +1,55 @@ +import { describe, it, expect } from "vitest"; +import { makeCtx, envMock } from "./helpers"; +import { + createDigitalCheckout, + digitalUnlock, + digitalRead, + digitalSalesHtml, +} from "../src/digital"; + +describe("digital impulse products", () => { + it("checkout is fail-closed without the Stripe price (503)", async () => { + const c = makeCtx({ env: envMock(), method: "POST", body: {}, url: "https://wmcp.sh/api/v1/guide/checkout" }); + const r: any = await createDigitalCheckout(c, "guide"); + expect(r.status).toBe(503); + expect(r.body.error).toBe("guide_not_configured"); + }); + + it("unlock requires a session_id (400)", async () => { + const c = makeCtx({ env: envMock({ STRIPE_SECRET_KEY: "sk_test" }), method: "GET", url: "https://wmcp.sh/guide/unlock" }); + const r: any = await digitalUnlock(c, "guide"); + expect(r.status).toBe(400); + }); + + it("read rejects an invalid token (403)", async () => { + const c = makeCtx({ env: envMock(), method: "GET", query: { t: "bogus" }, url: "https://wmcp.sh/guide/read?t=bogus" }); + const r: any = await digitalRead(c, "guide"); + expect(r.status).toBe(403); + }); + + it("read serves the guide for a valid token", async () => { + const env = envMock(); + await env.KEYS.put("dlg:goodtoken", JSON.stringify({ product: "guide", ts: 1 })); + const c = makeCtx({ env, method: "GET", query: { t: "goodtoken" }, url: "https://wmcp.sh/guide/read?t=goodtoken" }); + const r: any = await digitalRead(c, "guide"); + expect(r.status).toBe(200); + expect(String(r.body)).toContain("Drop-Day Playbook"); + expect(String(r.body)).toContain("/guide/read?t=goodtoken"); + }); + + it("a token minted for the guide can't unlock a different product namespace", async () => { + const env = envMock(); + await env.KEYS.put("dlg:wrongkind", JSON.stringify({ product: "other", ts: 1 })); + const c = makeCtx({ env, method: "GET", query: { t: "wrongkind" }, url: "https://wmcp.sh/guide/read?t=wrongkind" }); + const r: any = await digitalRead(c, "guide"); + expect(r.status).toBe(403); + }); + + it("sales page renders a mobile buy button at the price", () => { + const html = digitalSalesHtml("https://wmcp.sh", "guide", false); + expect(html).toContain("$2.99"); + expect(html).toContain('data-product="guide"'); + expect(html).toContain("Get instant access"); + expect(html).toContain("no signup"); + }); +});