Skip to content

fix(deps): Stripe — stripe 23, stripe-js 10, react-stripe-js 7, API 2026-09-30.endive (#433) - #441

Merged
efymd9 merged 5 commits into
mainfrom
chore/stripe-majors-433
Oct 11, 2026
Merged

efymd9 merged 5 commits into
mainfrom
chore/stripe-majors-433

Conversation

@efymd9

@efymd9 efymd9 commented Oct 9, 2026 •

Copy link
Copy Markdown
Owner

Решение владельца (11.10): вариант 1 — PR сливается в main, стенд выкладывается сам, репетиция покупки идёт на стенде по чек-листу ниже, и ТОЛЬКО ПОСЛЕ отчёта владельца — «релизь». До репетиции релиз не делается: Release PR несёт это обновление Stripe. Прод в платном режиме на live-Stripe; тестовую карту на не-локальном хосте вводит только владелец.

Что и зачем

Три мажора платёжного контура из Dependabot #431 — одним PR, потому что это один «поезд» Stripe:

Пакет Было Стало Что это значит
stripe 22.6.2 23.0.0 закреплённая версия API 2026-08-26.dahlia → 2026-09-30.endive
@stripe/stripe-js 9.17.0 10.0.0 браузер грузит js.stripe.com/dahlia/stripe.js → js.stripe.com/endive/stripe.js
@stripe/react-stripe-js 6.12.0 7.0.0 пир @stripe/stripe-js >=10 <11

Лок закреплён ровно на этих версиях (последние патчи мажоров на сегодня); в package.json двинулись только три спеки — правка руками + pnpm install, без pnpm update.

  • lib/stripe.ts: STRIPE_API_VERSION = "2026-09-30.endive". До этого шага lib/stripe-api-version.test.ts был красным (expected '2026-09-30.endive' to be '2026-08-26.dahlia'), а tsc — тоже (Type '"2026-08-26.dahlia"' is not assignable to type '"2026-09-30.endive"'). Это и был сигнал «прочитай changelog», ради которого Stripe: «пина» версии API нет — закрепить константой + тест-страж; no-store на redirect-ветках billing-portal #266 вводил пин.
  • Новый lib/stripe-browser.test.ts (jsdom): поезд Stripe.js, который реально вставляет loadStripe (читается src вставленного <script>, jsdom ничего не качает), должен совпадать с суффиксом STRIPE_API_VERSION. Stripe прямо советует держать Stripe.js и серверную версию API на одном поезде («keep the Stripe.js version and server-side API version on the same release train», docs/sdks/stripejs-versioning), а пакеты живут раздельно — раздельный бамп от Dependabot теперь красный. Проверено, что тест падает без изменения: с пином dahlia и установленным stripe-js 10 — expected 'endive' to be 'dahlia'.
  • Код приложения не менялся: ни одна из ломающих правок Endive и SDK 23 нас не задевает (разбор ниже). Все существующие тесты Stripe проходят с неизменёнными ассертами.
  • Доки: docs/gotchas.md → «Stripe API 2024+ moves» — абзац про три пакета-поезд и что сломал Endive; «Stripe 22» → «Stripe 23» в стеке CLAUDE.md, README.md, docs/services.md.

Разбор ломающих изменений (что читал и задевает ли нас)

Источники: stripe-node CHANGELOG 23.0.0, docs.stripe.com/changelog/endive (все пункты «Breaking»), релизы stripe-js v10.0.0 и react-stripe-js v7.0.0 + diff их dist/; плюс diff .d.ts stripe 22.6.2 ↔ 23.0.0 по Checkout/Sessions, Subscriptions, SubscriptionItems, Invoices, Customers, BillingPortal/Sessions, Prices, Products, Events и Webhooks.js.

API 2026-09-30.endive

Изменение Нас касается?
payment_method_types на checkout.sessions.create → 400 Нет: не шлём (динамические способы оплаты). NB: buildCheckoutSessionParams возвращает объект через as Stripe.Checkout.SessionCreateParams, поэтому tsc такой ключ не поймал бы — проверено грепом по всему репо: ноль вхождений
billing_cycle_anchor в update/resume подписки и preview invoice → объект {type} Нет: не передаём. Наши subscriptions.update — только metadata (скраб CAPI, lib/subscription-mirror.ts, scripts/scrub-capi-metadata.ts) и cancel_at_period_end (lib/erase-user.ts)
ui_mode Без изменений: 'elements' | 'embedded_page' | 'form' | 'hosted_page' — сверено в типах 23.0.0
Поля, которые читают вебхук и зеркало (items.data[].current_period_end, invoice.parent.subscription_details.subscription, cancel_at, cancel_at_period_end, status, metadata, customer, client_reference_id, amount_total…) Не тронуты: diff типов — только добавления (status_details, pending_update.cancel_at_period_end, current_trial, …). Enum Subscription.Status прежний
Новый код ошибки failed_tax_calculation (automatic tax) Нет ветвлений по кодам ошибок Stripe; при такой ошибке Checkout просто не создастся — как и при любой другой
Payment Request Button удалена (Stripe.js и React) Нет: кошелёк — ExpressCheckoutElement
paymentMethodTypes в опциях Elements Нет: не используем
canConfirm = false, пока висит серверный апдейт Checkout Session (Elements + Checkout Sessions) Наш onConfirm в components/watch/wallet-express-checkout.tsx canConfirm не читает и зовёт checkout.confirm() по событию кошелька. По описанию Stripe меняется только флаг, но это единственный пункт, который реально проходит через наш код — проверяется репетицией кошелька (п. 6 чек-листа)
Пустые необязательные поля адреса / строгие billing details при fields.billingDetails: 'never' у Payment Element Нет: Payment Element не используем; Embedded Checkout сам собирает адрес (billing_address_collection: required)
Остальное (Connect, Treasury, Financial Connections, Reserve, Radar, India mandates, SEPA, PayTo, BLIK…) Не используем

SDK stripe 23

Изменение Нас касается?
verifyHeader без tolerance теперь берёт DEFAULT_TOLERANCE; constructEvent(…, 0) реально отключает проверку Нет: вебхук зовёт constructEvent(body, sig, secret) с тремя аргументами — было tolerance || 300, стало tolerance ?? 300, т.е. те же 300 с (сверено в Webhooks.js обеих версий)
Обрыв тела ответа → StripeConnectionError вместо StripeAPIError Нет ветвлений по классу ошибки: expireIfOpen перечитывает сессию при ЛЮБОЙ ошибке expire; lib/observability.ts/lib/user-export.ts/scripts/scrub-capi-metadata.ts только печатают name/code
Удалены Stripe.constructEventWithoutVerification, экспорт ErrorType, V1-поля V2List Не используем (Stripe.errors.StripeError на месте — tsc зелёный)
Node ≥ 20 CI на .nvmrc = 24; Next 16 и так требует ≥ 20.9
Скрипты (scripts/stripe-setup.ts, scrub-capi-metadata.ts, erase-user.ts, export-user-data.ts) создают new Stripe(key) без apiVersion Теперь ходят на endive (дефолт SDK = наш пин). Их вызовы (products/prices list/create/update, subscriptions list/update {metadata}, customers.retrieve, invoices.list) в Endive не менялись

@stripe/stripe-js 10 / @stripe/react-stripe-js 7: кроме смены поезда на endive и удаления PRB — одна автоматическая повторная попытка загрузки скрипта в loadStripe (наш getStripeBrowser мемоизирует промис как и раньше); «Upgrade to React 19» в react-stripe-js 7 — это девтулинг пакета (jest/RTL), рантайм-diff checkout.esm.mjs — косметика useMemo. Экспорты, которые мы используем (EmbeddedCheckoutProvider, EmbeddedCheckout, CheckoutElementsProvider, ExpressCheckoutElement, useCheckoutElements), и типы checkout.confirm / опций Express Checkout не менялись.

Вебхук-эндпоинт. Версия эндпоинта задаётся в дашборде Stripe и этим PR не управляется. Поля шести обрабатываемых событий одинаковы в dahlia и endive, так что поднимать версию эндпоинта не обязательно; если владелец захочет — отдельно и после релиза.

Как проверено

  • pnpm lint — exit 0
  • pnpm typecheck — exit 0 (до смены литерала — exit 2, ровно одна ошибка про apiVersion)
  • pnpm test:unit — exit 0, 156 файлов / 2048 тестов (unit + mobile; cd mobile && npm ci сделан)
  • lib/stripe-api-version.test.ts: красный на 23.0.0 с пином dahlia → зелёный с endive
  • lib/stripe-browser.test.ts: зелёный; доказано, что без изменения (пин dahlia) красный
  • Lab-истории Stripe-компоненты не рендерят (кошелёк в lab/wallet-slot-variants.tsx — инертная заглушка), голдены не затронуты
  • Живых вызовов Stripe, БД и .env.local не было

Критерии из #433 — по пунктам

  • CI зелёный — см. чеки PR.
  • lib/stripe-api-version.test.ts держит SDK и версию API — да: был красным на бампе, переведён литерал, зелёный; плюс новый lib/stripe-browser.test.ts держит браузерную половину того же поезда.
  • Репетиция покупки на стенде пройдена и описана в PR — ⏳ за владельцем, чек-лист ниже. До отчёта PR не мержится.
  • docs/gotchas.md обновлён — да, раздел Stripe.

Репетиция на стенде — ГЕЙТ МЕРЖА

Условие: matio-staging.vercel.app должен отдавать код этой ветки — вебхуки Stripe test-mode доставляются на этот хост, превью-URL их не получит. Например: в проекте matio-staging назначить домену деплой этого PR (Promote), после репетиции стенд вернётся к main следующим деплоем main (или передеплоить main). Проверка: /api/healthz отвечает "commit" = голова этой ветки.

Всё — test-mode Stripe стенда, тестовая карта 4242 4242 4242 4242, любая будущая дата, любой CVC.

  1. Гостевая покупка (pay-first, embedded). Инкогнито → эпизод уровня subscriber → пейволл → «Become a member · $25/mo» → /checkout: iframe монтируется на нашем домене (без ухода на checkout.stripe.com), в консоли браузера нет предупреждения Stripe.js@… was loaded on the page, but @stripe/stripe-js@10.0.0 expected Stripe.js@endive → один line item $25/мес, адрес обязателен, чекбокс ToS с текстом отказа от 14-дневного отзыва → оплатить.
    1b. Hosted Checkout на новой версии API (путь покупателей из FB/IG, процедура Stripe: «пина» версии API нет — закрепить константой + тест-страж; no-store на redirect-ветках billing-portal #266). DevTools → подмена User-Agent на строку со Instagram, тот же профиль браузера → гостевой pay-first: /checkout должен целиком уйти на тестовую страницу checkout.stripe.com, оплата 4242, возврат на /welcome?session_id=…, вебхуки 200, строка подписки на стенде — как в шагах 2–4.
  2. Вебхуки. Stripe test Dashboard → Webhooks → эндпоинт стенда: checkout.session.completed, customer.subscription.created, invoice.paid — каждый 200.
  3. БД стенда (не прод!): новая строка subscriptions — status=active, current_period_end ≈ +1 мес; строка users с signup_origin='guest_checkout' и stripe_customer_id. Плюс скраб CAPI (Минимизация: сырой IP и User-Agent (capi_ip/capi_ua) живут в метаданных подписки Stripe бессрочно после единственного Purchase #165): с принятым маркетинговым согласием в Stripe test Dashboard у этой подписки в metadata больше нет capi_fbp / capi_fbc / capi_ip / capi_ua, а capi_consent остался (сбой скраба виден только как console.warn — шаг 10 его не поймает).
  4. /welcome: тот же браузер → вход по ticket без ввода кода (кука checkout_claim), пользователь залогинен.
  5. Воспроизведение: эпизод уровня subscriber играет (/api/playback-token 200).
  6. Кошелёк (если на стенде задан WALLET_EXPRESS_CHECKOUT): другой залогиненный аккаунт, устройство с Apple Pay / Google Pay → пейволл → галочка согласия → кнопка появилась → оплатить → возврат с cs= → доступ сразу. Если confirm упал — это кандидат на Endive-изменение canConfirm, сообщить в треде. Предусловия и проверка, что Endive не сломал создание сессии: (а) в Stripe test Dashboard → Settings → Payment method domains домен matio-staging.vercel.app зарегистрирован и активен (без него слот просто не рисуется); (б) после галочки согласия в Stripe test → Logs запрос POST /v1/checkout/sessions с ui_mode=elements вернул 200 — сбой создания в коде проглатывается и выглядит так же, как «здесь кошелька нет»; (в) подтверждение оплаты кошельком проходит (Endive: canConfirm=false, пока висит серверный апдейт сессии).
  7. Залогиненный путь /subscribe → /checkout: ещё один тестовый аккаунт → embedded → 4242 → возврат на watch-путь с cs= → доступ сразу (inline-зеркало), вебхуки 200.
  8. Отмена через портал: меню пользователя → «Manage subscription» → /api/billing-portal 302 → Customer Portal → отменить → customer.subscription.updated 200 → в строке subscriptions отмена запланирована (cancel_at/cancel_at_period_end), доступ сохраняется до конца периода.
  9. По желанию — свип Чекаут: не более одной оплачиваемой сессии на покупателя — ключ идемпотентности этого не гарантирует #217: залогиненный аккаунт, /checkout в двух вкладках → возврат в первую показывает карточку «сессия истекла».
  10. Sentry, environment: staging: новых ошибок за время репетиции нет.

Отчёт — в этот тред (какие пункты пройдены, id тестовых подписок/событий). Только после него — ревью/мерж основной сессией.

Отложено

  • Комментарий в lib/checkout-session-params.ts просит при смене версии API перепроверить, что custom_text по-прежнему отвергается под ui_mode: 'elements'. На 2026-08-26.dahlia это проверено, на 2026-09-30.endive — нет. Вреда нет: поверхность elements custom_text не шлёт. Строка в docs/registry.md (после ревью).

Ничего: незавершённых кусков в коде нет, строк в docs/registry.md не добавлено. Порог покрытия в vitest.config.mts не трогал — новый тест добавляет считанные строки; если CI покажет переход через шаг рэтчета, пересчитаю.

Closes #433

GDPR: не касается — состав данных, уходящих в Stripe (и в Stripe.js), не меняется; новых процессоров, полей, логов и событий нет.

🤖 Generated with Claude Code

https://claude.ai/code/session_0184yUssUtNnEmKVaft78cbT

…09-30.endive (#433)

Три мажора платёжного контура одним поездом: серверный SDK закрепляет
2026-09-30.endive, stripe-js 10 грузит js.stripe.com/endive/stripe.js,
react-stripe-js 7 — пир stripe-js 10. STRIPE_API_VERSION переведён на
endive; lib/stripe-api-version.test.ts был красным до этого шага.

Ломающие изменения Endive нас не задевают: payment_method_types мы не
шлём, billing_cycle_anchor не трогаем, Payment Request Button не
используем, значения ui_mode те же. Новый lib/stripe-browser.test.ts
держит поезд Stripe.js в браузере равным поезду серверной версии API,
чтобы раздельный бамп пакетов был красным.

Мерж — только после репетиции покупки на стенде владельцем.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0184yUssUtNnEmKVaft78cbT
@vercel

vercel Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
matio Ready Ready Preview Oct 11, 2026 1:02pm UTC
matio-staging Ready Ready Preview Oct 11, 2026 1:02pm UTC

Request Review

@github-actions

github-actions Bot commented Oct 9, 2026

Copy link
Copy Markdown

Покрытие

=============================== Coverage summary ===============================
Statements   : 56.73% ( 5126/9035 )
Branches     : 50.39% ( 3407/6760 )
Functions    : 50.04% ( 1128/2254 )
Lines        : 57.49% ( 4543/7902 )
================================================================================

Изменённые строки (diff-cover, порог 85%)

-------------
Diff Coverage
Diff: origin/main...HEAD, staged and unstaged changes
-------------
lib/stripe.ts (100%)
-------------
Total:   1 line
Missing: 0 lines
Coverage: 100%
-------------

…нтарий и строка реестра (#433)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0184yUssUtNnEmKVaft78cbT
@efymd9 efymd9 changed the title chore: Stripe — stripe 23, stripe-js 10, react-stripe-js 7, API endive (#433) — ждёт репетиции покупки на стенде fix(deps): Stripe — stripe 23, stripe-js 10, react-stripe-js 7, API 2026-09-30.endive (#433) Oct 9, 2026
@efymd9

efymd9 commented Oct 11, 2026

Copy link
Copy Markdown
Owner Author

Владелец выбрал вариант 1 (11.10): сливаю в main → стенд → репетиция покупки по чек-листу в описании → только потом релиз.

@efymd9
efymd9 enabled auto-merge (squash) October 11, 2026 12:58

This branch was successfully deployed

2 active deployments
Preview – matio-staging — 90adb81e Deployed Oct 11, 2026 by vercel[bot]
Preview – matio — 90adb81e Deployed Oct 11, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Stripe-мажоры: stripe 22→23, @stripe/stripe-js 9→10, @stripe/react-stripe-js 6→7 (из Dependabot #431)

1 participant