Skip to content

build(stripe): upgrade Stripe API to Dahlia, migrate field changes - #426

Draft
th0rgall wants to merge 1 commit into
masterfrom
claude/update-stripe-components-F31BI
Draft

build(stripe): upgrade Stripe API to Dahlia, migrate field changes#426
th0rgall wants to merge 1 commit into
masterfrom
claude/update-stripe-components-F31BI

Conversation

@th0rgall

@th0rgall th0rgall commented May 20, 2026

Copy link
Copy Markdown
Member

Summary

Maintenance update of our Stripe components per request.

Frontend (svelte-stripe). We already declared ^2.0.0 in package.json, and the lockfile already resolves to 2.0.0 (the Svelte 5 release from joshnuss/svelte-stripe#131, published 2026-01-22). I audited PaymentPage.svelte against the v2 API: bind:elements is still supported, callback-style event props (onready) are correct, and we don't use any of the renamed props (e.g. the old theme="flat" shorthand that became appearance={{ theme: 'flat' }}). No changes were needed; the dependency is already on the version the user asked for.

Backend (stripe-node + API version). Bumped from ^17.7.0 (which pins to 2024-10-28.acacia) to ^21.0.0 (which pins to 2026-03-25.dahlia, the first Dahlia release). STRIPE_VERSION env templates updated accordingly. Between Acacia and Dahlia, Basil (2025-03-31) is the version that actually broke shape on every object we touch; Clover and Dahlia only added more changes that don't materially affect us (V2 event-notification renames in stripe-node v19 — we don't use V2 events; decimal_string types becoming Stripe.Decimal in v21 — we only had one such read, see below).

Basil field migrations applied

Acacia path Basil path Why it changed
subscription.current_period_{start,end} subscription.items.data[0].current_period_* Period moved to subscription items to support flexible / mixed-interval billing
invoice.subscription (string id) invoice.parent.subscription_details.subscription New polymorphic parent field — invoices can also originate from quotes, etc.
invoice.payment_intent invoice.payments[].payment.payment_intent Invoice ↔ PaymentIntent is now many-to-many: an invoice can have multiple PaymentIntents (partial / retry payments)
invoice.charge (expand: charge) invoice.payments[].payment.payment_intent.latest_charge Same multi-payment refactor; charge is reached through the payment
PaymentIntent.invoice stripe.invoicePayments.list({ payment: { type: 'payment_intent', payment_intent } }) The new invoices/:id/attach_payment endpoint also lets one PI settle multiple invoices (overpayments, wires), so the singular field stopped being well-defined. Lookup now goes through the InvoicePayment join object.
invoice.lines.data[].price.unit_amount (number) invoice.lines.data[].pricing.unit_amount_decimal (string/Decimal) New polymorphic pricing object on line items, unifying Price/Plan and preparing for new pricing types

All accessors are centralised in api/src/subscriptions/basilCompat.js so each call site stays readable and the rationale lives in one place. Files updated:

  • createOrRetrieveUnpaidSubscription.js — refactored to return { subscription, clientSecret } from helpers and to walk the new invoice.payments graph for both expanded list queries and the existing-incomplete reuse path. The cloud-function wire response shape ({ subscriptionId, clientSecret }) is unchanged, so the frontend payment flow doesn't change.
  • subscriptionUpdated.js, subscriptionDeleted.js — use the item-level period via the helper.
  • invoiceCreated.js, invoicePaid.js, invoiceUpcoming.js — read subscription id from parent.subscription_details, read PI / Charge / unit amount via helpers.
  • paymentIntentProcessing.js, paymentIntentPaymentFailed.js — use the new InvoicePayment list endpoint to find the related invoice (previously paymentIntent.invoice). We use limit: 1 because a subscription-generated PI is always attached to exactly one invoice in our flow.
  • api/test/util/stripe.js, api/test/{sendInvoice,chargeAutomatically}.test.js — same migrations.

The Firestore document shape (stripeSubscription.currentPeriodStart/End, etc.) is unchanged, so no data migration is required and the frontend User.ts model + stores keep working as-is.

Things explicitly not changed

  • subscriptions.create still uses expand: ['latest_invoice'] (still valid; we just don't deep-expand payment_intent any more — we fetch it separately via the invoice.payments graph).
  • subscriptions.cancel returning a string latest_invoice in cancelUnpaidManualRenewals.js is still valid for non-expanded subscriptions.
  • The webhook handler's version gating (?version= query param vs STRIPE_VERSION) still works lexicographically across Acacia → Basil → Clover → Dahlia because the date prefix sorts correctly.

Cannot verify locally

Per the task description there's no access to a Stripe sandbox in this session, so I haven't exercised the full payment / renewal flows. The diff follows the patterns documented in the Basil and Dahlia changelogs, but a sandbox run before merging is strongly advised — especially around the SEPA generated-from-Bancontact mandate retrieval in invoiceUpcoming.js (the trickiest path through the new invoice.payments graph).

Test plan

  • Run yarn install at the repo root and yarn install under api/ after pulling the branch.
  • Run the API mocha suites against the emulators with STRIPE_VERSION=2026-03-25.dahlia.
  • In a Stripe sandbox, exercise: (1) new subscription with Bancontact/iDEAL → SEPA derivation, (2) charge_automatically renewal, (3) send_invoice renewal + cancel-after-7-days, (4) cancellation flow with the upcoming-renewal email path.
  • Update the production / staging STRIPE_VERSION env values, and re-point the webhook endpoint to send 2026-03-25.dahlia events.

@th0rgall
th0rgall had a problem deploying to e2e-test-emulators May 20, 2026 15:55 — with GitHub Actions Failure
@th0rgall
th0rgall force-pushed the claude/update-stripe-components-F31BI branch from e4c91c0 to 07e5641 Compare May 21, 2026 15:28
@th0rgall
th0rgall had a problem deploying to e2e-test-emulators May 21, 2026 15:28 — with GitHub Actions Failure
…nges

Pins stripe-node to v21 (which defaults to apiVersion 2026-03-25.dahlia)
and updates STRIPE_VERSION env templates accordingly. svelte-stripe was
already on v2 (the Svelte 5 release from PR #131) — no frontend changes
beyond confirming our PaymentPage already uses the v2 patterns.

The bulk of the diff is migrating away from fields that Basil removed:

- subscription.current_period_{start,end} → items.data[0].current_period_*
- invoice.subscription → invoice.parent.subscription_details.subscription
- invoice.payment_intent → invoice.payments[].payment.payment_intent
- invoice.charge → invoice.payments[].payment.payment_intent.latest_charge
- PaymentIntent.invoice → stripe.invoicePayments.list({ payment: ... })
- invoice.lines.data[].price.unit_amount → lines.data[].pricing.unit_amount_decimal

A new api/src/subscriptions/basilCompat.js centralises these accessors so
each call site reads naturally and the migration rationale lives in one
place.
@th0rgall
th0rgall force-pushed the claude/update-stripe-components-F31BI branch from 07e5641 to b104ef0 Compare May 21, 2026 15:28
@th0rgall
th0rgall had a problem deploying to e2e-test-emulators May 21, 2026 15:29 — with GitHub Actions Failure
@github-actions

Copy link
Copy Markdown

Visit the preview URL for this PR (updated for commit b104ef0):

https://wtmg-dev--pr426-claude-update-stripe-wqgc2dp4.web.app

(expires Sat, 20 Jun 2026 15:32:29 GMT)

🔥 via Firebase Hosting GitHub Action 🌎

Sign: 7773005e481e8bc60fd60a58f2d818cc7378f95b

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.

2 participants