diff --git a/CLAUDE.md b/CLAUDE.md index 33c810d0021..246096022c0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -118,7 +118,7 @@ The eight pages under `pages/dashboard/project/[projectId]/environment/[environm ## Stripe in the browser -A panel contacts Stripe's hosts only after a click on Subscribe now in the onboarding plan step: `plan-step.tsx` imports `@stripe/stripe-js/pure` and calls `loadStripe` in that handler. The package's main entry injects `js.stripe.com/v3` as soon as the module runs, even with no key, and the script then calls `m.stripe.network` and `m.stripe.com` by itself. The pages router's prefetch runs a page's module on idle, so an import anywhere under a page linked from the sidebar reaches every panel page. The billing page needs no browser SDK: it redirects to the URL of the checkout session. `__test__/billing/stripe-script-on-demand.test.ts` fails on the main entry; after an upstream merge, check that it still passes. +The browser never loads Stripe's script. Both places that start a checkout, the billing page and the onboarding plan step, redirect to the `url` of the checkout session that `stripe.createCheckoutSession` returns, so `@stripe/stripe-js` is not a dependency (its 10.x removed `redirectToCheckout`, the only call the plan step used). The package's main entry used to inject `js.stripe.com/v3` as soon as the module ran, even with no key, and the pages router's prefetch runs a page's module on idle, so an import anywhere under a page linked from the sidebar reached every panel page. `__test__/billing/stripe-script-on-demand.test.ts` fails on any import of the package and on the dependency itself; after an upstream merge, check that it still passes. `NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY` stays in `Dockerfile.cloud` and `deploy.yml` unused (the token cannot change workflows). ## Server runtime @@ -187,6 +187,7 @@ One GitHub release `agenthits-vX.Y.Z` covers a batch of work, not a single PR (o - `apps/schedules` declares no `ioredis` of its own: the queues pass `connection` as plain options, and `bullmq` brings `ioredis` as an exact version. A direct `ioredis` at any other version leaves two copies in the lockfile. - `bullmq` stays on 5.x (owner's decision 2026-11-09): 6.0 removed the legacy repeatable jobs (`repeat` in `Queue#add`, `getRepeatableJobs`, `removeRepeatable`, `removeRepeatableByKey`) that `apps/schedules/src/queue.ts` is built on, made `ioredis` an optional peer and replaced `connection` with a `BackendFactory`. `apps/schedules` is upstream's cloud scheduler, which the fork's panel does not run (its schedules use node-schedule), and upstream itself is on bullmq 5.67, so a rewrite to job schedulers would only bring conflicts at each upstream sync. The `^5` range keeps minor and patch updates coming. - `inngest` stays at 4.26.1: 4.26.2 ships `sandbox-tool/dist/inngest-tool-linux-amd64`, built with Go 1.22, and the image scan fails on its Go stdlib findings. Move it when a release drops that binary or builds it with a current Go; do not add these findings to `.trivyignore.yaml`. +- `stripe` stays at 17.2.1 with `apiVersion: "2024-09-30.acacia"` in `billing.ts`, `routers/stripe.ts` and `pages/api/stripe/webhook.ts`: 23.1.0 only types its own newest API version (`2026-10-28.endive`) and has no `Invoice.subscription` (it moved under `parent.subscription_details`), so a bump means moving the code to ~two years of changed object shapes (`current_period_*` on items, invoice parents, and more) and the Stripe account's webhook endpoint to the same version in one cutover. That code runs only with `IS_CLOUD`, no test keys exist here, and a real checkout was never exercised; move it with the cloud billing owner, against a Stripe test account. (Checked 2026-11-11 on the VM: 5 type errors in 4 files.) - `ssh2` stays at 1.16.0: 1.17.0's native crypto module (`lib/protocol/crypto/src/binding.cc`) calls `v8::Context::GetIsolate()`, which Node 26's V8 no longer has, so `node-gyp` fails at install and ssh2 falls back to its JavaScript crypto. On Node that made 20 terminal open-and-close cycles of `__test__/wss/terminal-ssh-release.test.ts` take over 5 s instead of 1.9 s. Bun never loads the native module, 1.16.0 included, so the panel itself ran the same on both. Move it when a release builds on Node 26: `bindingAvailable` from `ssh2/lib/protocol/crypto.js` under `node` must be `true`. - Everything else takes a `^` range in every `package.json` that declares it, so the daily update moves it. A new exact version needs a line here saying why. On `0.x`, `^` narrows to patch (`^0.12.0` is `<0.13.0`), so a minor of `@xterm/addon-*`, `drizzle-zod` or another `0.x` package moves by hand. - Every other root override is a range `>= ` (for `0.x`, ``), so the override keeps the fixed floor and the workflow still moves the package. A security override follows the same form; an exact version there freezes the package and can silently override a newer direct dependency. diff --git a/apps/dokploy/__test__/billing/stripe-script-on-demand.test.ts b/apps/dokploy/__test__/billing/stripe-script-on-demand.test.ts index c671dcc8633..ee05a739012 100644 --- a/apps/dokploy/__test__/billing/stripe-script-on-demand.test.ts +++ b/apps/dokploy/__test__/billing/stripe-script-on-demand.test.ts @@ -12,15 +12,20 @@ const clientSources = ["components", "hooks", "lib", "pages", "utils"].flatMap( .map((file) => path.join(dir, file)), ); -describe("Stripe's browser script", () => { - it("is reached only through the entry that waits for loadStripe", () => { +describe("Stripe in the browser", () => { + it("is never loaded: checkout redirects to the session URL", () => { const planStep = "components/dashboard/onboarding/steps/plan-step.tsx"; expect(clientSources).toContain(planStep); - expect(read(planStep)).toContain('from "@stripe/stripe-js/pure"'); + expect(read(planStep)).toContain("window.location.href = session.url"); - const importsMainEntry = clientSources.filter((file) => - /["']@stripe\/stripe-js["']/.test(read(file)), + const importsStripeJs = clientSources.filter((file) => + /["']@stripe\/stripe-js(\/[a-z]+)?["']/.test(read(file)), ); - expect(importsMainEntry).toEqual([]); + expect(importsStripeJs).toEqual([]); + }); + + it("does not declare the browser SDK", () => { + const manifest = JSON.parse(read("package.json")); + expect(manifest.dependencies["@stripe/stripe-js"]).toBeUndefined(); }); }); diff --git a/apps/dokploy/components/dashboard/onboarding/steps/plan-step.tsx b/apps/dokploy/components/dashboard/onboarding/steps/plan-step.tsx index f1520fb80d8..4127baf4202 100644 --- a/apps/dokploy/components/dashboard/onboarding/steps/plan-step.tsx +++ b/apps/dokploy/components/dashboard/onboarding/steps/plan-step.tsx @@ -1,6 +1,5 @@ // Not the main entry: it injects Stripe's script as soon as this module runs, and the // sidebar's prefetch of the home page runs it on every panel, self-hosted ones included. -import { loadStripe } from "@stripe/stripe-js/pure"; import { ArrowRightIcon, CheckIcon } from "lucide-react"; import { useState } from "react"; import { toast } from "sonner"; @@ -72,15 +71,14 @@ export const PlanStep = ({ onNext }: Props) => { if (!productId) return; setLoading(`checkout:${tier}`); try { - const publishableKey = process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY; - const stripe = publishableKey ? await loadStripe(publishableKey) : null; const session = await createCheckoutSession({ tier, productId, serverQuantity: tier === "startup" ? STARTUP_SERVERS_INCLUDED : 1, isAnnual: false, }); - await stripe?.redirectToCheckout({ sessionId: session.sessionId }); + if (!session.url) throw new Error("Checkout session has no URL"); + window.location.href = session.url; } catch { toast.error("Error starting checkout"); setLoading(null); diff --git a/apps/dokploy/package.json b/apps/dokploy/package.json index 849c46fd0b0..faaa8ec6c7d 100644 --- a/apps/dokploy/package.json +++ b/apps/dokploy/package.json @@ -74,7 +74,6 @@ "@openpanel/nextjs": "^1.5.4", "@react-email/components": "^1.0.12", "@stepperize/react": "8.0.1", - "@stripe/stripe-js": "^4.10.0", "@tailwindcss/typography": "^0.5.20", "@tanstack/react-query": "^5.107.1", "@tanstack/react-table": "^9.3.1", diff --git a/bun.lock b/bun.lock index 986fe52a1ef..579454db6e2 100644 --- a/bun.lock +++ b/bun.lock @@ -76,7 +76,6 @@ "@openpanel/nextjs": "^1.5.4", "@react-email/components": "^1.0.12", "@stepperize/react": "8.0.1", - "@stripe/stripe-js": "^4.10.0", "@tailwindcss/typography": "^0.5.20", "@tanstack/react-query": "^5.107.1", "@tanstack/react-table": "^9.3.1", @@ -1367,8 +1366,6 @@ "@stepperize/react": ["@stepperize/react@8.0.1", "", { "dependencies": { "@stepperize/core": "4.0.0" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0" } }, "sha512-/Sz9QXzRblvqUlmQfKsMzzjH1TCGruip0DHlLRzYSoHL1XAUFhtVYnytGH2Gf7esNjyfPEQJoFcm+mO69bVx3Q=="], - "@stripe/stripe-js": ["@stripe/stripe-js@4.10.0", "", {}, "sha512-KrMOL+sH69htCIXCaZ4JluJ35bchuCCznyPyrbN8JXSGQfwBI1SuIEMZNwvy8L8ykj29t6sa5BAAiL7fNoLZ8A=="], - "@swagger-api/apidom-ast": ["@swagger-api/apidom-ast@1.14.0", "", { "dependencies": { "@babel/runtime-corejs3": "^7.26.10", "@swagger-api/apidom-error": "^1.14.0", "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0", "unraw": "^3.0.0" } }, "sha512-yzKA1z63/YL/bT50axexISPwQpQC7Vtl+OBVGMktz9lQgKpQTudKEp/k4c3D61jOS5YWKtu2UzcB65l/AY+0mQ=="], "@swagger-api/apidom-core": ["@swagger-api/apidom-core@1.14.0", "", { "dependencies": { "@babel/runtime-corejs3": "^7.26.10", "@swagger-api/apidom-ast": "^1.14.0", "@swagger-api/apidom-error": "^1.14.0", "@types/ramda": "~0.30.0", "minim": "~0.23.8", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0", "short-unique-id": "^5.3.2", "ts-mixer": "^6.0.3" } }, "sha512-4BCnyq7P7ZJZawZpIt0tw/OGgiTYsfZ5nCq+9az0hHQWyQ/C7nbYzjeCoY+gMGlO33S/CIlzkJL7nL7xv4rijA=="],