@fantastic.dev/better-stripe 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +73 -45
- package/README.md +22 -14
- package/dist/client/webhooks/processors.js +9 -5
- package/dist/client/webhooks/processors.js.map +1 -1
- package/dist/component/_generated/component.d.ts +34 -0
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/billing/queries.d.ts +36 -0
- package/dist/component/billing/queries.d.ts.map +1 -1
- package/dist/component/billing/queries.js +10 -0
- package/dist/component/billing/queries.js.map +1 -1
- package/dist/component/billing/schema.d.ts +1 -0
- package/dist/component/billing/schema.d.ts.map +1 -1
- package/dist/component/billing/schema.js +1 -0
- package/dist/component/billing/schema.js.map +1 -1
- package/dist/component/connect/mutations.d.ts.map +1 -1
- package/dist/component/connect/mutations.js +46 -8
- package/dist/component/connect/mutations.js.map +1 -1
- package/dist/component/connect/queries.d.ts +3 -4
- package/dist/component/connect/queries.d.ts.map +1 -1
- package/dist/component/connect/queries.js +19 -10
- package/dist/component/connect/queries.js.map +1 -1
- package/dist/component/schema.d.ts +1 -0
- package/dist/react/hooks/useSplitBreakdown.d.ts +4 -6
- package/dist/react/hooks/useSplitBreakdown.d.ts.map +1 -1
- package/dist/react/hooks/useSplitBreakdown.js +4 -6
- package/dist/react/hooks/useSplitBreakdown.js.map +1 -1
- package/package.json +1 -1
- package/src/client/webhooks/processors.test.ts +16 -0
- package/src/client/webhooks/processors.ts +8 -3
- package/src/component/_generated/component.ts +46 -0
- package/src/component/billing/queries.ts +12 -0
- package/src/component/billing/schema.ts +1 -0
- package/src/component/connect/mutations.ts +68 -17
- package/src/component/connect/queries.ts +18 -10
- package/src/component/reversal-races.test.ts +41 -1
- package/src/component/seller-case-ownership.test.ts +61 -0
- package/src/component/store-subscriptions.test.ts +32 -0
- package/src/component/transfers.test.ts +69 -0
- package/src/react/hooks/useSplitBreakdown.ts +4 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
- Return every original split leg by default from `listTransfersByCharge`,
|
|
6
|
+
excluding reinstatement rows before applying any explicit preview limit.
|
|
7
|
+
Split breakdowns and reversal claims now cover sales with more than 50 legs.
|
|
8
|
+
Queries and atomic claims fail on Convex resource limits rather than silently
|
|
9
|
+
truncate financial data. `listTransfersByAccount` remains a preview API;
|
|
10
|
+
earnings hooks use exact server-side aggregates.
|
|
11
|
+
- Update the former roadmap to describe the existing product-level first-charge
|
|
12
|
+
descriptors, first-invoice fee recovery, retry-safe reversal claims, and exact
|
|
13
|
+
earnings aggregates, including their practical limitations.
|
|
14
|
+
- Remove internal tracker references from public documentation and add a
|
|
15
|
+
regression check that prevents their reintroduction.
|
|
16
|
+
|
|
17
|
+
## [0.7.0](https://github.com/FantasticDevHQ/better-stripe/compare/v0.6.1...v0.7.0) (2026-10-13)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Features
|
|
21
|
+
|
|
22
|
+
* expand marketplace example with seller billing and sandbox fixtures ([#129](https://github.com/FantasticDevHQ/better-stripe/issues/129)) ([7f21f53](https://github.com/FantasticDevHQ/better-stripe/commit/7f21f5365ce82cab9ecc984b94f444408223f8b8))
|
|
23
|
+
|
|
24
|
+
## [0.6.1](https://github.com/FantasticDevHQ/better-stripe/compare/v0.6.0...v0.6.1) (2026-10-13)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
### Bug Fixes
|
|
28
|
+
|
|
29
|
+
* complete large split reversals and keep public docs current ([#127](https://github.com/FantasticDevHQ/better-stripe/issues/127)) ([e7cc0a9](https://github.com/FantasticDevHQ/better-stripe/commit/e7cc0a9b0ea01f4993447f9d73edebe71b364081))
|
|
30
|
+
|
|
3
31
|
## [0.6.0](https://github.com/FantasticDevHQ/better-stripe/compare/v0.5.0...v0.6.0) (2026-10-12)
|
|
4
32
|
|
|
5
33
|
|
|
@@ -10,7 +38,7 @@
|
|
|
10
38
|
### Features
|
|
11
39
|
|
|
12
40
|
* complete Stripe Endive compatibility and release 0.5.0 ([#126](https://github.com/FantasticDevHQ/better-stripe/issues/126)) ([135695f](https://github.com/FantasticDevHQ/better-stripe/commit/135695f51906ea156a36dbe8d0d43cf1d99e5fc1))
|
|
13
|
-
* publish as [@fantastic](https://github.com/fantastic).dev/better-stripe via Release Please and trusted publishing (
|
|
41
|
+
* publish as [@fantastic](https://github.com/fantastic).dev/better-stripe via Release Please and trusted publishing ([#120](https://github.com/FantasticDevHQ/better-stripe/issues/120)) ([7451eeb](https://github.com/FantasticDevHQ/better-stripe/commit/7451eeb514b09a09656355c74c4bec083e1ddce7))
|
|
14
42
|
|
|
15
43
|
|
|
16
44
|
### Bug Fixes
|
|
@@ -20,7 +48,7 @@
|
|
|
20
48
|
|
|
21
49
|
### Maintenance
|
|
22
50
|
|
|
23
|
-
* **deps:** update dependencies to latest (
|
|
51
|
+
* **deps:** update dependencies to latest ([#122](https://github.com/FantasticDevHQ/better-stripe/issues/122)) ([a7e4161](https://github.com/FantasticDevHQ/better-stripe/commit/a7e416179d20afdd2356668cbd4482e2e3fa4eff))
|
|
24
52
|
|
|
25
53
|
|
|
26
54
|
### Continuous Integration
|
|
@@ -69,13 +97,13 @@ changed by this release.
|
|
|
69
97
|
|
|
70
98
|
### Minor Changes
|
|
71
99
|
|
|
72
|
-
- [#76](https://github.com/kellykampen/better-stripe/pull/76) [`c9f1489`](https://github.com/kellykampen/better-stripe/commit/c9f148948f2f5ea77f343c5fc1a78faffbb5ac1e) Thanks [@kellykampen](https://github.com/kellykampen)! - Add `getAccountBalance` for per-account balance retrieval (
|
|
100
|
+
- [#76](https://github.com/kellykampen/better-stripe/pull/76) [`c9f1489`](https://github.com/kellykampen/better-stripe/commit/c9f148948f2f5ea77f343c5fc1a78faffbb5ac1e) Thanks [@kellykampen](https://github.com/kellykampen)! - Add `getAccountBalance` for per-account balance retrieval ([#62](https://github.com/kellykampen/better-stripe/issues/62)).
|
|
73
101
|
|
|
74
102
|
New `BetterStripe.getAccountBalance({ stripeAccountId })` reads a connected account's live Stripe balance, scoped via the `Stripe-Account` header (mirroring the other per-account calls like `createPayout`). Pairs Stripe's separate `available`/`pending` per-currency lists into one `AccountBalance` (`{ available, pending, currency }`, minor units) per currency — a currency present on only one side defaults to `0` on the other. `AccountBalance` is re-exported at the package root, and its shape matches the `PayoutSchedule` component's `balance` prop directly.
|
|
75
103
|
|
|
76
104
|
Purely additive.
|
|
77
105
|
|
|
78
|
-
- [#76](https://github.com/kellykampen/better-stripe/pull/76) [`c9f1489`](https://github.com/kellykampen/better-stripe/commit/c9f148948f2f5ea77f343c5fc1a78faffbb5ac1e) Thanks [@kellykampen](https://github.com/kellykampen)! - Add headless `BuyerBillingView` component (
|
|
106
|
+
- [#76](https://github.com/kellykampen/better-stripe/pull/76) [`c9f1489`](https://github.com/kellykampen/better-stripe/commit/c9f148948f2f5ea77f343c5fc1a78faffbb5ac1e) Thanks [@kellykampen](https://github.com/kellykampen)! - Add headless `BuyerBillingView` component ([#50](https://github.com/kellykampen/better-stripe/issues/50)).
|
|
79
107
|
|
|
80
108
|
The buyer's embedded-only, in-app billing surface: shows a buyer's subscriptions grouped by store (via the existing `groupSubscriptionsByStore` util, not recomputed), with per-store cancel/reactivate through the shared `SubscriptionCard`/`SubscriptionActions`, the one saved payment method reusable across every store, and an embedded (no-redirect) card-update slot.
|
|
81
109
|
|
|
@@ -88,20 +116,20 @@ changed by this release.
|
|
|
88
116
|
|
|
89
117
|
Previously `DEFAULT_ACCOUNT_CONFIGURATION` only requested the merchant configuration, so there was no first-class path to create a customer-billable account.
|
|
90
118
|
|
|
91
|
-
- [#76](https://github.com/kellykampen/better-stripe/pull/76) [`c9f1489`](https://github.com/kellykampen/better-stripe/commit/c9f148948f2f5ea77f343c5fc1a78faffbb5ac1e) Thanks [@kellykampen](https://github.com/kellykampen)! - Add `DisputeDetail` + `EvidenceForm` headless React components (
|
|
119
|
+
- [#76](https://github.com/kellykampen/better-stripe/pull/76) [`c9f1489`](https://github.com/kellykampen/better-stripe/commit/c9f148948f2f5ea77f343c5fc1a78faffbb5ac1e) Thanks [@kellykampen](https://github.com/kellykampen)! - Add `DisputeDetail` + `EvidenceForm` headless React components ([#40](https://github.com/kellykampen/better-stripe/issues/40)).
|
|
92
120
|
|
|
93
121
|
- `DisputeDetail` — renders a dispute's amount, reason, status, evidence-due countdown message, and linked transfer ids. Loading/empty states, i18n label overrides.
|
|
94
122
|
- `EvidenceForm` — headless controlled form for the four Skool-style evidence fields (product description, access activity, additional info, customer communication); `stage()` saves a draft (no `submit` key), `submit()` finalizes with `submit: true`. Supports `stripeAccountId` scoping and `onStaged`/`onSubmitted`/`onError` callbacks.
|
|
95
123
|
|
|
96
|
-
Both are headless/themeable (`className` plus a `children` render-prop) and consume the existing
|
|
124
|
+
Both are headless/themeable (`className` plus a `children` render-prop) and consume the existing dispute handling/dispute hooks core with no duplicated logic. Purely additive.
|
|
97
125
|
|
|
98
|
-
- [#76](https://github.com/kellykampen/better-stripe/pull/76) [`c9f1489`](https://github.com/kellykampen/better-stripe/commit/c9f148948f2f5ea77f343c5fc1a78faffbb5ac1e) Thanks [@kellykampen](https://github.com/kellykampen)! - Add headless `DisputesList` component with due-by countdown badges (
|
|
126
|
+
- [#76](https://github.com/kellykampen/better-stripe/pull/76) [`c9f1489`](https://github.com/kellykampen/better-stripe/commit/c9f148948f2f5ea77f343c5fc1a78faffbb5ac1e) Thanks [@kellykampen](https://github.com/kellykampen)! - Add headless `DisputesList` component with due-by countdown badges ([#41](https://github.com/kellykampen/better-stripe/issues/41)).
|
|
99
127
|
|
|
100
|
-
Pure, hook-free presentational list of a seller's disputes, sorted by evidence deadline (soonest first, no-deadline rows last, overdue rows naturally at the top). Rows accept the exact shape `getDisputeWithCountdown`/`useDisputeWithCountdown`
|
|
128
|
+
Pure, hook-free presentational list of a seller's disputes, sorted by evidence deadline (soonest first, no-deadline rows last, overdue rows naturally at the top). Rows accept the exact shape `getDisputeWithCountdown`/`useDisputeWithCountdown` produce, so the component never recomputes deadlines itself.
|
|
101
129
|
|
|
102
130
|
Headless overrides: `className`, i18n labels (`emptyLabel`, `loadingLabel`, `dueLabel`/`overdueLabel` with `{days}` templating), a per-row `renderRow` slot, and a whole-list `children` render prop. Default markup is semantic `ul[role=list] > li[data-dispute-id]` with a `span[role=status]` badge carrying `data-overdue`. Purely additive.
|
|
103
131
|
|
|
104
|
-
- [#76](https://github.com/kellykampen/better-stripe/pull/76) [`c9f1489`](https://github.com/kellykampen/better-stripe/commit/c9f148948f2f5ea77f343c5fc1a78faffbb5ac1e) Thanks [@kellykampen](https://github.com/kellykampen)! - Add dispute React hooks: `createUseDisputes` + `createUseDisputeWithCountdown` (
|
|
132
|
+
- [#76](https://github.com/kellykampen/better-stripe/pull/76) [`c9f1489`](https://github.com/kellykampen/better-stripe/commit/c9f148948f2f5ea77f343c5fc1a78faffbb5ac1e) Thanks [@kellykampen](https://github.com/kellykampen)! - Add dispute React hooks: `createUseDisputes` + `createUseDisputeWithCountdown` ([#36](https://github.com/kellykampen/better-stripe/issues/36)).
|
|
105
133
|
|
|
106
134
|
Two new factory hooks following the existing `createUse*` convention:
|
|
107
135
|
|
|
@@ -110,7 +138,7 @@ changed by this release.
|
|
|
110
138
|
|
|
111
139
|
Both wired into the `@getdojo/better-stripe/react` entry export; `StripeComponentDispute` re-exported for consumers. Purely additive.
|
|
112
140
|
|
|
113
|
-
- [#51](https://github.com/kellykampen/better-stripe/pull/51) [`6345352`](https://github.com/kellykampen/better-stripe/commit/6345352cf4b3ad4247c865090693e877da6bf5ef) Thanks [@kellykampen](https://github.com/kellykampen)! - Smart-retry dunning: surface failed subscription cycles
|
|
141
|
+
- [#51](https://github.com/kellykampen/better-stripe/pull/51) [`6345352`](https://github.com/kellykampen/better-stripe/commit/6345352cf4b3ad4247c865090693e877da6bf5ef) Thanks [@kellykampen](https://github.com/kellykampen)! - Smart-retry dunning: surface failed subscription cycles.
|
|
114
142
|
|
|
115
143
|
`invoice.payment_failed` previously upserted the invoice but fired **no** async hook, so apps couldn't react to a failed subscription cycle. It now schedules a new invoice-level hook:
|
|
116
144
|
|
|
@@ -124,7 +152,7 @@ changed by this release.
|
|
|
124
152
|
|
|
125
153
|
Note: covered by unit tests (hook scheduling, invoice state transition + retry metadata, `past_due`/`unpaid` persistence). The live `invoice.payment_failed` path (real failed-card subscription cycle) should be exercised by the E2E webhook run before release.
|
|
126
154
|
|
|
127
|
-
- [#64](https://github.com/kellykampen/better-stripe/pull/64) [`477e282`](https://github.com/kellykampen/better-stripe/commit/477e282ea1cfd2fc198f1aa19cc376eea96b38ca) Thanks [@kellykampen](https://github.com/kellykampen)! - Exact earnings totals — fix silently truncated financial figures past 50 rows
|
|
155
|
+
- [#64](https://github.com/kellykampen/better-stripe/pull/64) [`477e282`](https://github.com/kellykampen/better-stripe/commit/477e282ea1cfd2fc198f1aa19cc376eea96b38ca) Thanks [@kellykampen](https://github.com/kellykampen)! - Exact earnings totals — fix silently truncated financial figures past 50 rows.
|
|
128
156
|
|
|
129
157
|
`listTransfersByAccount` and `listPayouts` cap their results at 50 rows, and the `useEarnings` hook summed those rows client-side — so an account with more than 50 transfers or payouts reported **silently wrong** (understated) gross / reversed / net / paid-out totals, with no error.
|
|
130
158
|
|
|
@@ -139,37 +167,37 @@ changed by this release.
|
|
|
139
167
|
|
|
140
168
|
**Behavior change:** if you wired `createUseEarnings` against `listTransfersByAccount` / `listPayouts`, repoint the refs at the new aggregate queries. The list queries and `BetterStripe.listPayouts()` are unchanged, now documented as capped drill-down views. `listTransfersByCharge` (per-sale split legs) is unchanged and safe in practice — a sale has few legs.
|
|
141
169
|
|
|
142
|
-
- [#76](https://github.com/kellykampen/better-stripe/pull/76) [`c9f1489`](https://github.com/kellykampen/better-stripe/commit/c9f148948f2f5ea77f343c5fc1a78faffbb5ac1e) Thanks [@kellykampen](https://github.com/kellykampen)! - Add `createUseEarnings` + `createUseSplitBreakdown` React hook factories (
|
|
170
|
+
- [#76](https://github.com/kellykampen/better-stripe/pull/76) [`c9f1489`](https://github.com/kellykampen/better-stripe/commit/c9f148948f2f5ea77f343c5fc1a78faffbb5ac1e) Thanks [@kellykampen](https://github.com/kellykampen)! - Add `createUseEarnings` + `createUseSplitBreakdown` React hook factories ([#44](https://github.com/kellykampen/better-stripe/issues/44)).
|
|
143
171
|
|
|
144
172
|
- `createUseEarnings(useQuery, transfersRef, payoutsRef)` → `useEarnings(accountId)` reads a recipient's transfer ledger and payouts, deriving `gross` (total transferred in), `reversed` (pulled back via fee collection + refund/dispute clawbacks — deliberately not labeled "fees", since a true platform-fees figure lives on the payments rows' `feeCollectedAmount`), `net`, `payouts`/`paidOut`, and the raw `transfers` for drill-down.
|
|
145
173
|
- `createUseSplitBreakdown(useQuery, transfersByChargeRef)` → `useSplitBreakdown({ sourceChargeId, saleAmount? })` sums a sale's split legs by role (`store`/`affiliate`/`other`), each net of reversals, with the `platform` share derived when the caller supplies the sale amount.
|
|
146
174
|
|
|
147
|
-
Both follow the `createUse*` factory convention from
|
|
175
|
+
Both follow the `createUse*` factory convention from dispute hooks. Purely additive.
|
|
148
176
|
|
|
149
|
-
- [#76](https://github.com/kellykampen/better-stripe/pull/76) [`c9f1489`](https://github.com/kellykampen/better-stripe/commit/c9f148948f2f5ea77f343c5fc1a78faffbb5ac1e) Thanks [@kellykampen](https://github.com/kellykampen)! - Add embedded Stripe Connect dispute components (
|
|
177
|
+
- [#76](https://github.com/kellykampen/better-stripe/pull/76) [`c9f1489`](https://github.com/kellykampen/better-stripe/commit/c9f148948f2f5ea77f343c5fc1a78faffbb5ac1e) Thanks [@kellykampen](https://github.com/kellykampen)! - Add embedded Stripe Connect dispute components ([#48](https://github.com/kellykampen/better-stripe/issues/48)).
|
|
150
178
|
|
|
151
179
|
The Stripe-hosted alternative to the headless dispute components, built on `@stripe/react-connect-js`:
|
|
152
180
|
|
|
153
|
-
- `ConnectProvider` — initializes ConnectJS once per mount via `loadConnectAndInitialize({ publishableKey, fetchClientSecret, appearance?, locale? })` and provides the instance through `ConnectComponentsProvider`. `fetchClientSecret` wires to an app action wrapping `stripe.createDisputeSession({ stripeAccountId })
|
|
181
|
+
- `ConnectProvider` — initializes ConnectJS once per mount via `loadConnectAndInitialize({ publishableKey, fetchClientSecret, appearance?, locale? })` and provides the instance through `ConnectComponentsProvider`. `fetchClientSecret` wires to an app action wrapping `stripe.createDisputeSession({ stripeAccountId })`.
|
|
154
182
|
- `EmbeddedDisputes` — mounts Stripe's `disputes_list` embedded component, or `payment_disputes` scoped to one payment via the `payment` prop. Must render inside `ConnectProvider`.
|
|
155
183
|
|
|
156
184
|
Adds `@stripe/connect-js` and `@stripe/react-connect-js` as regular dependencies, mirroring the existing `@stripe/stripe-js`/`@stripe/react-stripe-js` convention. Purely additive.
|
|
157
185
|
|
|
158
|
-
- [#76](https://github.com/kellykampen/better-stripe/pull/76) [`c9f1489`](https://github.com/kellykampen/better-stripe/commit/c9f148948f2f5ea77f343c5fc1a78faffbb5ac1e) Thanks [@kellykampen](https://github.com/kellykampen)! - Add headless `PayoutSchedule` component for recipient balance + payout display (
|
|
186
|
+
- [#76](https://github.com/kellykampen/better-stripe/pull/76) [`c9f1489`](https://github.com/kellykampen/better-stripe/commit/c9f148948f2f5ea77f343c5fc1a78faffbb5ac1e) Thanks [@kellykampen](https://github.com/kellykampen)! - Add headless `PayoutSchedule` component for recipient balance + payout display ([#46](https://github.com/kellykampen/better-stripe/issues/46)).
|
|
159
187
|
|
|
160
188
|
Pure, hook-free component showing a seller's available/pending balance (from a `RecipientBalance` snapshot prop) and next payout — the earliest `pending`/`in_transit` payout row with an arrival date, templated as "Next payout of $40.00 expected Jul 10, 2026", falling back to a rolling-schedule message when none is upcoming (payouts are automatic/rolling; the component deliberately offers no manual withdrawal). Optional `manageUrl` link out to the hosted Express dashboard.
|
|
161
189
|
|
|
162
190
|
Headless conventions: `className`, i18n label overrides, loading/empty states, and a `children` render-prop with all computed state. Data comes entirely via props (no data-fetching hook yet); purely additive.
|
|
163
191
|
|
|
164
|
-
- [#76](https://github.com/kellykampen/better-stripe/pull/76) [`c9f1489`](https://github.com/kellykampen/better-stripe/commit/c9f148948f2f5ea77f343c5fc1a78faffbb5ac1e) Thanks [@kellykampen](https://github.com/kellykampen)! - Add product-level `statement_descriptor` so it covers a subscription's first charge (
|
|
192
|
+
- [#76](https://github.com/kellykampen/better-stripe/pull/76) [`c9f1489`](https://github.com/kellykampen/better-stripe/commit/c9f148948f2f5ea77f343c5fc1a78faffbb5ac1e) Thanks [@kellykampen](https://github.com/kellykampen)! - Add product-level `statement_descriptor` so it covers a subscription's first charge ([#63](https://github.com/kellykampen/better-stripe/issues/63)).
|
|
165
193
|
|
|
166
|
-
The invoice-time descriptor path
|
|
194
|
+
The invoice-time descriptor path sets the descriptor at `invoice.created`, which never reaches a subscription's first invoice — Stripe finalizes and (for `charge_automatically`/Checkout subscription-mode) pays invoice [#1](https://github.com/kellykampen/better-stripe/issues/1) synchronously at creation, before that handler runs. Per Stripe's descriptor precedence (Invoice → Product → charge-type default), a product-level `statement_descriptor` covers every cycle including the first.
|
|
167
195
|
|
|
168
|
-
`createProduct`/`updateProduct` now resolve the per-store descriptor (the store's stored suffix, else the platform default — mirroring the
|
|
196
|
+
`createProduct`/`updateProduct` now resolve the per-store descriptor (the store's stored suffix, else the platform default — mirroring the account-level descriptor resolution) and stamp it onto the Stripe Product's `statement_descriptor`, for products with an `accountId`. No public API change to existing call shapes.
|
|
169
197
|
|
|
170
|
-
- [#70](https://github.com/kellykampen/better-stripe/pull/70) [`306d402`](https://github.com/kellykampen/better-stripe/commit/306d402b0148e0794a2239a5c83a0d3780300c38) Thanks [@kellykampen](https://github.com/kellykampen)! - Reclaim/expiry of dead reversal claims
|
|
198
|
+
- [#70](https://github.com/kellykampen/better-stripe/pull/70) [`306d402`](https://github.com/kellykampen/better-stripe/commit/306d402b0148e0794a2239a5c83a0d3780300c38) Thanks [@kellykampen](https://github.com/kellykampen)! - Reclaim/expiry of dead reversal claims.
|
|
171
199
|
|
|
172
|
-
|
|
200
|
+
Atomic reversal claims reserve reversal slices atomically before money moves; the executor now refuses to leapfrog an unexecuted predecessor claim. The residual, by-design failure mode: if the operation that reserved a slice dies permanently (its webhook exhausts Stripe's retries and never confirms), the claim frontier stays ahead of the confirmed `reversedAmount` forever, and every successor reversal on that leg blocks with `predecessor claim unexecuted`. This adds an ops path to resolve such wedged legs.
|
|
173
201
|
|
|
174
202
|
- **`BetterStripe.listWedgedReversalClaims(ctx)`** — lists legs where `reversalClaimedAmount > reversedAmount` and the recorded ops holding the blocking (unexecuted) slice on each. An ops/admin diagnostic; scans the ledger to completion (exact, and loud past Convex's 16,384-doc read limit rather than silently truncating).
|
|
175
203
|
- **`BetterStripe.reclaimReversalClaim(ctx, { operationId, mode })`** — resolves a dead claim:
|
|
@@ -178,13 +206,13 @@ changed by this release.
|
|
|
178
206
|
|
|
179
207
|
New component functions: `connect/queries/listWedgedReversalClaims`, `connect/queries/getReversalOp`, `connect/mutations/releaseReversalClaim`. No behavior change to existing reversal paths.
|
|
180
208
|
|
|
181
|
-
- [#76](https://github.com/kellykampen/better-stripe/pull/76) [`c9f1489`](https://github.com/kellykampen/better-stripe/commit/c9f148948f2f5ea77f343c5fc1a78faffbb5ac1e) Thanks [@kellykampen](https://github.com/kellykampen)! - Add refund actor scoping — seller (own sales) vs platform admin (
|
|
209
|
+
- [#76](https://github.com/kellykampen/better-stripe/pull/76) [`c9f1489`](https://github.com/kellykampen/better-stripe/commit/c9f148948f2f5ea77f343c5fc1a78faffbb5ac1e) Thanks [@kellykampen](https://github.com/kellykampen)! - Add refund actor scoping — seller (own sales) vs platform admin ([#56](https://github.com/kellykampen/better-stripe/issues/56)).
|
|
182
210
|
|
|
183
|
-
`createRefund`
|
|
211
|
+
`createRefund` previously had no notion of who may initiate a refund. It now takes an optional `actor` option (`{ type: "admin" }` or `{ type: "seller", accountId }`) and enforces the rule before any Stripe call is made — an unauthorized refund never moves money. A platform admin may refund any sale; a seller may refund only sales routed to their own connected account (destination charge, or one of the split legs in a separate-charges split), resolved from the payment's own component-ledger routing. Fails closed: a seller is denied when the payment's routing can't be resolved.
|
|
184
212
|
|
|
185
|
-
Additive and backwards compatible — omitting `actor` preserves the prior unrestricted behavior. The refund fee/transfer-reversal math from
|
|
213
|
+
Additive and backwards compatible — omitting `actor` preserves the prior unrestricted behavior. The refund fee/transfer-reversal math from refund handling is unchanged.
|
|
186
214
|
|
|
187
|
-
- [#76](https://github.com/kellykampen/better-stripe/pull/76) [`c9f1489`](https://github.com/kellykampen/better-stripe/commit/c9f148948f2f5ea77f343c5fc1a78faffbb5ac1e) Thanks [@kellykampen](https://github.com/kellykampen)! - Refunds now return the platform fee pro-rata and reverse recipient transfers (
|
|
215
|
+
- [#76](https://github.com/kellykampen/better-stripe/pull/76) [`c9f1489`](https://github.com/kellykampen/better-stripe/commit/c9f148948f2f5ea77f343c5fc1a78faffbb5ac1e) Thanks [@kellykampen](https://github.com/kellykampen)! - Refunds now return the platform fee pro-rata and reverse recipient transfers ([#45](https://github.com/kellykampen/better-stripe/issues/45)).
|
|
188
216
|
|
|
189
217
|
**Destination charges:** `createRefund` gains `refundApplicationFee` and `reverseTransfer` options, both defaulting `true`, sent only when the charge actually carries an application fee / destination transfer (sending either on an incapable charge is a hard Stripe error). Stripe pro-rates both natively for partial refunds; an explicit `false` always wins.
|
|
190
218
|
|
|
@@ -212,14 +240,14 @@ changed by this release.
|
|
|
212
240
|
|
|
213
241
|
Note: V2 `customer_account` + `pause_collection`/dispute interplay and the refund-via-API path are covered by unit tests; the live E2E (`refund.created` via real API call, dispute triggers) should be run before release — `charge.dispute.funds_withdrawn`/`funds_reinstated` cannot be reliably triggered in test mode.
|
|
214
242
|
|
|
215
|
-
- [#76](https://github.com/kellykampen/better-stripe/pull/76) [`c9f1489`](https://github.com/kellykampen/better-stripe/commit/c9f148948f2f5ea77f343c5fc1a78faffbb5ac1e) Thanks [@kellykampen](https://github.com/kellykampen)! - Add `SplitBreakdown` + `EarningsSummary` headless components (
|
|
243
|
+
- [#76](https://github.com/kellykampen/better-stripe/pull/76) [`c9f1489`](https://github.com/kellykampen/better-stripe/commit/c9f148948f2f5ea77f343c5fc1a78faffbb5ac1e) Thanks [@kellykampen](https://github.com/kellykampen)! - Add `SplitBreakdown` + `EarningsSummary` headless components ([#49](https://github.com/kellykampen/better-stripe/issues/49)).
|
|
216
244
|
|
|
217
245
|
- `SplitBreakdown` renders a sale's store + affiliate + platform lines (the `other` bucket only when it carries money) from `useSplitBreakdown`'s totals. When the platform share is underivable (no sale amount given to the hook) it renders an explicit "Unknown without the sale amount" state — never a silent zero.
|
|
218
246
|
- `EarningsSummary` renders gross / reversals / net / paid-out plus the latest payout status line. Its props are a subset of `useEarnings`'s result shape, so `<EarningsSummary {...useEarnings(accountId)} />` works directly.
|
|
219
247
|
|
|
220
|
-
Consumes the
|
|
248
|
+
Consumes the earnings hooks hooks ([#44](https://github.com/kellykampen/better-stripe/issues/44)). Both headless/themeable (`className` + `children` render-prop). Purely additive.
|
|
221
249
|
|
|
222
|
-
- [#76](https://github.com/kellykampen/better-stripe/pull/76) [`c9f1489`](https://github.com/kellykampen/better-stripe/commit/c9f148948f2f5ea77f343c5fc1a78faffbb5ac1e) Thanks [@kellykampen](https://github.com/kellykampen)! - Add per-store statement descriptors on destination charges (
|
|
250
|
+
- [#76](https://github.com/kellykampen/better-stripe/pull/76) [`c9f1489`](https://github.com/kellykampen/better-stripe/commit/c9f148948f2f5ea77f343c5fc1a78faffbb5ac1e) Thanks [@kellykampen](https://github.com/kellykampen)! - Add per-store statement descriptors on destination charges ([#47](https://github.com/kellykampen/better-stripe/issues/47)).
|
|
223
251
|
|
|
224
252
|
- New `BetterStripe.setAccountStatementDescriptor` method (validates first; `null` clears) writes a `statementDescriptor` field on the component's account record via a new `core/mutations/setStatementDescriptor` mutation.
|
|
225
253
|
- New constructor option `statementDescriptorSuffix` sets a configurable platform-wide default (validated at construction, same pattern as `platformFee`), used whenever a store account carries no suffix of its own. Resolution order: store's stored suffix → configured default → none.
|
|
@@ -242,9 +270,9 @@ changed by this release.
|
|
|
242
270
|
- `listTransfersByCharge(ctx, { sourceChargeId, limit? })` — a sale's original split legs (reinstatement rows excluded), the read behind the `useSplitBreakdown` hook / per-sale breakdown UIs.
|
|
243
271
|
- `listTransfersByAccount(ctx, { destinationAccountId, limit? })` — a recipient's transfer ledger (the earnings view, includes reinstatements), the read behind the `useEarnings` hook.
|
|
244
272
|
|
|
245
|
-
The underlying component queries already existed, but there was no client-side method to reach them, so an app couldn't wire the `useSplitBreakdown` / `useEarnings` hook factories to real data. These thin read wrappers close that gap (surfaced while building the affiliate-split demo
|
|
273
|
+
The underlying component queries already existed, but there was no client-side method to reach them, so an app couldn't wire the `useSplitBreakdown` / `useEarnings` hook factories to real data. These thin read wrappers close that gap (surfaced while building the affiliate-split demo).
|
|
246
274
|
|
|
247
|
-
- [#58](https://github.com/kellykampen/better-stripe/pull/58) [`61f755e`](https://github.com/kellykampen/better-stripe/commit/61f755ebf38065d5c3cc73db18e7dfd1c3d8c394) Thanks [@kellykampen](https://github.com/kellykampen)! - `updateDispute` now stages evidence by default instead of submitting it
|
|
275
|
+
- [#58](https://github.com/kellykampen/better-stripe/pull/58) [`61f755e`](https://github.com/kellykampen/better-stripe/commit/61f755ebf38065d5c3cc73db18e7dfd1c3d8c394) Thanks [@kellykampen](https://github.com/kellykampen)! - `updateDispute` now stages evidence by default instead of submitting it.
|
|
248
276
|
|
|
249
277
|
Stripe's dispute-update `submit` parameter **defaults to `true`**, so evidence sent without an explicit `submit` is submitted to the bank — a one-shot, outcome-affecting action. The previous docblock incorrectly said omitting `submit` "saves a draft", and the method forwarded `submit` only when explicitly set, so a caller following the docs would submit unintentionally.
|
|
250
278
|
|
|
@@ -252,52 +280,52 @@ changed by this release.
|
|
|
252
280
|
|
|
253
281
|
- A bare metadata/status update (no `evidence`) is unchanged — no `submit` is forced.
|
|
254
282
|
- An explicit `submit: true` / `submit: false` is always honored.
|
|
255
|
-
- `EvidenceForm`
|
|
283
|
+
- `EvidenceForm` already passes `submit` explicitly, so it is unaffected.
|
|
256
284
|
|
|
257
285
|
If you relied on the old behavior of submitting evidence by omitting `submit`, pass `submit: true` explicitly.
|
|
258
286
|
|
|
259
287
|
### Patch Changes
|
|
260
288
|
|
|
261
|
-
- [#76](https://github.com/kellykampen/better-stripe/pull/76) [`c9f1489`](https://github.com/kellykampen/better-stripe/commit/c9f148948f2f5ea77f343c5fc1a78faffbb5ac1e) Thanks [@kellykampen](https://github.com/kellykampen)! - Regenerate the component's `_generated` API so transfer/dispute queries are typed for consumers (
|
|
289
|
+
- [#76](https://github.com/kellykampen/better-stripe/pull/76) [`c9f1489`](https://github.com/kellykampen/better-stripe/commit/c9f148948f2f5ea77f343c5fc1a78faffbb5ac1e) Thanks [@kellykampen](https://github.com/kellykampen)! - Regenerate the component's `_generated` API so transfer/dispute queries are typed for consumers ([#71](https://github.com/kellykampen/better-stripe/issues/71)).
|
|
262
290
|
|
|
263
|
-
`src/component/_generated/component.ts` was stale — it predated `listTransfersByCharge`/`listTransfersByAccount`
|
|
291
|
+
`src/component/_generated/component.ts` was stale — it predated `listTransfersByCharge`/`listTransfersByAccount` and `getDisputeByStripeId`/`listDisputes`, so `components.betterStripe.connect.queries.*` for those methods was untyped for any consumer reaching into the raw component API. Regenerated via `npx convex codegen`; no source logic changed, generated bindings only.
|
|
264
292
|
|
|
265
|
-
- [#76](https://github.com/kellykampen/better-stripe/pull/76) [`c9f1489`](https://github.com/kellykampen/better-stripe/commit/c9f148948f2f5ea77f343c5fc1a78faffbb5ac1e) Thanks [@kellykampen](https://github.com/kellykampen)! - Fix: `updateDispute` now sends an explicit `submit: false` on every call, including metadata-only updates (
|
|
293
|
+
- [#76](https://github.com/kellykampen/better-stripe/pull/76) [`c9f1489`](https://github.com/kellykampen/better-stripe/commit/c9f148948f2f5ea77f343c5fc1a78faffbb5ac1e) Thanks [@kellykampen](https://github.com/kellykampen)! - Fix: `updateDispute` now sends an explicit `submit: false` on every call, including metadata-only updates ([#67](https://github.com/kellykampen/better-stripe/issues/67)).
|
|
266
294
|
|
|
267
|
-
|
|
295
|
+
Earlier evidence handling made evidence-bearing `updateDispute` calls stage by default (`submit: false`), but left metadata-only updates sending no `submit` key at all — inheriting Stripe's server-side default, which Stripe's docs state is `true` on every update. Empirically, a metadata-only update does not submit previously staged evidence today, but that leniency contradicts the documented default and submission is one-shot and outcome-affecting. `updateDispute` now never omits `submit`: every call — evidence, metadata, or both — sends an explicit `submit: false` unless the caller passes `submit: true` to finalize.
|
|
268
296
|
|
|
269
297
|
No public API change; the safer default only ever narrows behavior (no call that previously submitted now stages, and vice versa).
|
|
270
298
|
|
|
271
|
-
- [#68](https://github.com/kellykampen/better-stripe/pull/68) [`4d1b843`](https://github.com/kellykampen/better-stripe/commit/4d1b843cb9891a313c9b9a270ca067e36c686420) Thanks [@kellykampen](https://github.com/kellykampen)! - Fix a first-invoice fee double-reversal crack
|
|
299
|
+
- [#68](https://github.com/kellykampen/better-stripe/pull/68) [`4d1b843`](https://github.com/kellykampen/better-stripe/commit/4d1b843cb9891a313c9b9a270ca067e36c686420) Thanks [@kellykampen](https://github.com/kellykampen)! - Fix a first-invoice fee double-reversal crack.
|
|
272
300
|
|
|
273
301
|
`applyFirstInvoiceFee` recomputed the platform fee from **live** subscription metadata (`bsFeeConfig`) on every webhook retry. If the transfer reversal succeeded but the `bsFeeCollected` invoice-marker write then failed (event marked `failed`), and the platform edited `bsFeeConfig` before Stripe redelivered, a retry landing **after** Stripe's ≤24h idempotency-key window would recompute a _different_ fee and create a **second** reversal — double-collecting the fee.
|
|
274
302
|
|
|
275
|
-
**Fix:** before reversing, `applyFirstInvoiceFee` now lists the destination transfer's existing reversals and adopts one tagged `metadata.bsFeeFor:<invoiceId>` as the idempotency source of truth. That tag survives both the marker-write failure and the idempotency-key TTL, so a retry re-records the marker with the already-reversed amount instead of reversing again — mirroring
|
|
303
|
+
**Fix:** before reversing, `applyFirstInvoiceFee` now lists the destination transfer's existing reversals and adopts one tagged `metadata.bsFeeFor:<invoiceId>` as the idempotency source of truth. That tag survives both the marker-write failure and the idempotency-key TTL, so a retry re-records the marker with the already-reversed amount instead of reversing again — mirroring the per-charge fee collector's frozen-config guarantee. No API surface change.
|
|
276
304
|
|
|
277
|
-
- [#76](https://github.com/kellykampen/better-stripe/pull/76) [`c9f1489`](https://github.com/kellykampen/better-stripe/commit/c9f148948f2f5ea77f343c5fc1a78faffbb5ac1e) Thanks [@kellykampen](https://github.com/kellykampen)! - Fix: collect the fixed/tier platform fee on a subscription's first invoice (
|
|
305
|
+
- [#76](https://github.com/kellykampen/better-stripe/pull/76) [`c9f1489`](https://github.com/kellykampen/better-stripe/commit/c9f148948f2f5ea77f343c5fc1a78faffbb5ac1e) Thanks [@kellykampen](https://github.com/kellykampen)! - Fix: collect the fixed/tier platform fee on a subscription's first invoice ([#52](https://github.com/kellykampen/better-stripe/issues/52)).
|
|
278
306
|
|
|
279
|
-
`applyPerInvoiceFee`
|
|
307
|
+
`applyPerInvoiceFee` applies a fixed/tier `application_fee_amount` at `invoice.created`, which only works while the invoice is still a draft. Stripe finalizes a `charge_automatically` subscription's first invoice synchronously at creation (and Checkout subscription-mode pays it during checkout), so `invoice.created` fires with an already-`open` invoice — the update is silently rejected and the first cycle's platform fee was lost on every destination-charge subscription with a fixed/tier fee. Percent-only fees and renewal invoices (which get Stripe's ~1h draft window) were unaffected.
|
|
280
308
|
|
|
281
|
-
Fixed with a new `applyFirstInvoiceFee` processor hooked at `invoice.paid`: it collects the missed fee via a partial reversal of the automatic destination transfer, sized from `amount_paid` — the same mechanism
|
|
309
|
+
Fixed with a new `applyFirstInvoiceFee` processor hooked at `invoice.paid`: it collects the missed fee via a partial reversal of the automatic destination transfer, sized from `amount_paid` — the same mechanism the per-charge fee collector's `applyPerChargeFee` uses for one-time charges. Skips any invoice whose fee the draft path already collected, so there's no double-collection. No public API change.
|
|
282
310
|
|
|
283
|
-
- [#21](https://github.com/kellykampen/better-stripe/pull/21) [`1f43345`](https://github.com/kellykampen/better-stripe/commit/1f433459ec6eb11d75982f1ed883cff9414d4e15) Thanks [@kellykampen](https://github.com/kellykampen)! - Fix `createPrice` orphaning a Stripe price when the product is missing from the component DB
|
|
311
|
+
- [#21](https://github.com/kellykampen/better-stripe/pull/21) [`1f43345`](https://github.com/kellykampen/better-stripe/commit/1f433459ec6eb11d75982f1ed883cff9414d4e15) Thanks [@kellykampen](https://github.com/kellykampen)! - Fix `createPrice` orphaning a Stripe price when the product is missing from the component DB.
|
|
284
312
|
|
|
285
313
|
`createPrice` previously called `stripe.prices.create(...)` **before** verifying the product existed in the component DB, then threw `PRODUCT_NOT_FOUND` afterward — leaving an orphaned price in Stripe with no corresponding component record. The product lookup now runs **first**, so a missing/unsynced product throws before any Stripe write happens. No API change; behavior only differs on the error path.
|
|
286
314
|
|
|
287
|
-
- [#76](https://github.com/kellykampen/better-stripe/pull/76) [`c9f1489`](https://github.com/kellykampen/better-stripe/commit/c9f148948f2f5ea77f343c5fc1a78faffbb5ac1e) Thanks [@kellykampen](https://github.com/kellykampen)! - Fix: collect deferred `per_charge` platform fees via transfer reversal (
|
|
315
|
+
- [#76](https://github.com/kellykampen/better-stripe/pull/76) [`c9f1489`](https://github.com/kellykampen/better-stripe/commit/c9f148948f2f5ea77f343c5fc1a78faffbb5ac1e) Thanks [@kellykampen](https://github.com/kellykampen)! - Fix: collect deferred `per_charge` platform fees via transfer reversal ([#38](https://github.com/kellykampen/better-stripe/issues/38)).
|
|
288
316
|
|
|
289
317
|
`bsFeeMode="per_charge"` (used when a one-time destination charge's final amount isn't known up front — discounts, `custom_unit_amount` prices, or a `sessionOverrides`-replaced `line_items`) had no webhook consumer, so the fixed/tiered platform fee was silently never collected on those charges.
|
|
290
318
|
|
|
291
319
|
Fixed with a new `applyPerChargeFee` processor at `payment_intent.succeeded`: it collects the fee the same way Stripe itself settles application fees on destination charges — a partial reversal of the automatic destination transfer, sized by `computeFee` from the amount actually charged (`amount_received`). No public API change; charges that already pass `amount` up front (computing `application_fee_amount` at session creation) are unaffected.
|
|
292
320
|
|
|
293
|
-
- [#66](https://github.com/kellykampen/better-stripe/pull/66) [`a47d64d`](https://github.com/kellykampen/better-stripe/commit/a47d64d39ece42b4cc0d96508e9b87925c6bf8e9) Thanks [@kellykampen](https://github.com/kellykampen)! - Fix `addRecipientConfiguration` not persisting `appliedConfigurations` to the component DB
|
|
321
|
+
- [#66](https://github.com/kellykampen/better-stripe/pull/66) [`a47d64d`](https://github.com/kellykampen/better-stripe/commit/a47d64d39ece42b4cc0d96508e9b87925c6bf8e9) Thanks [@kellykampen](https://github.com/kellykampen)! - Fix `addRecipientConfiguration` not persisting `appliedConfigurations` to the component DB.
|
|
294
322
|
|
|
295
323
|
`addRecipientConfiguration` applied the V2 recipient configuration on Stripe but never wrote the result back to the component `accounts` row, unlike `addCustomerConfiguration` which re-fetches and records `applied_configurations` after every update. Callers had to manually run `syncAllAccounts` afterward to reconcile the component DB, leaving it stale in between — anything reading `appliedConfigurations` off the component row (e.g. billing-view/UI gating) saw an out-of-date picture. `addRecipientConfiguration` now follows the same pattern: re-fetch the account after the Stripe update, read `applied_configurations`, recover the owning `userId` from the existing component record (failing closed with `ACCOUNT_NOT_FOUND` if the account isn't in the component DB yet), and `upsertAccount`. No public API change on the call side; the return shape now includes `appliedConfigurations` alongside `success`, matching `addCustomerConfiguration`.
|
|
296
324
|
|
|
297
|
-
- [#76](https://github.com/kellykampen/better-stripe/pull/76) [`c9f1489`](https://github.com/kellykampen/better-stripe/commit/c9f148948f2f5ea77f343c5fc1a78faffbb5ac1e) Thanks [@kellykampen](https://github.com/kellykampen)! - Fix three transfer-reversal money races via atomic reversal-slice claiming (
|
|
325
|
+
- [#76](https://github.com/kellykampen/better-stripe/pull/76) [`c9f1489`](https://github.com/kellykampen/better-stripe/commit/c9f148948f2f5ea77f343c5fc1a78faffbb5ac1e) Thanks [@kellykampen](https://github.com/kellykampen)! - Fix three transfer-reversal money races via atomic reversal-slice claiming ([#61](https://github.com/kellykampen/better-stripe/issues/61)).
|
|
298
326
|
|
|
299
327
|
- Dispute clawback (amount mode): a retry after partial success recomputed per-leg amounts from mutable ledger state — a same-params replay could double-record the ledger, or a shifted remainder could resend different params under an already-used Stripe idempotency key, causing a permanent event-retry loop.
|
|
300
|
-
- Refund clawback (delta-to-target
|
|
328
|
+
- Refund clawback (delta-to-target): two distinct refunds racing between the ledger read and the Stripe call could both compute the same reversal target from stale state, over-reversing a leg and under-recording the ledger.
|
|
301
329
|
- `payment_intent.succeeded` redelivery could have `upsertPayment`'s blanket patch rewrite `feeCollectedAmount` back to the gross fee, bypassing the monotonic fee-refund guard.
|
|
302
330
|
|
|
303
331
|
Fixed by claiming the exact reversal slice atomically, in a new component mutation, before any money moves — so a retry re-adopts the already-claimed slice instead of recomputing and re-reversing it. No public API change.
|
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ Built for Convex + Next.js applications. Follows the conventions established by
|
|
|
6
6
|
|
|
7
7
|
## Status
|
|
8
8
|
|
|
9
|
-
**Version**: `0.
|
|
9
|
+
**Version**: `0.7.0` <!-- x-release-please-version --> (pre-1.0). API may change between minor versions until 1.0.
|
|
10
10
|
|
|
11
11
|
Used in production by its authors. Webhook pipeline is covered by unit tests and a live E2E harness (`npm run e2e:webhooks`) that fires real Stripe-signed events and asserts the ledger.
|
|
12
12
|
|
|
@@ -35,6 +35,14 @@ and `pnpm dev` to start the example at http://localhost:5173. The example needs
|
|
|
35
35
|
Node.js 22.12+ and a configured Convex development deployment; see
|
|
36
36
|
[the example setup guide](example/README.md).
|
|
37
37
|
|
|
38
|
+
The BetterStripe example separates visitor, customer, affiliate, seller, and
|
|
39
|
+
platform-admin workflows. Its resumable Stripe sandbox fixtures populate sales,
|
|
40
|
+
referrals, refunds, disputes, subscriptions, and payouts. Seller plans include
|
|
41
|
+
monthly billing, actual Stripe fee reconciliation, and refund/dispute cost
|
|
42
|
+
recovery. The example also demonstrates optional balance-funded subscriptions
|
|
43
|
+
using Stripe's Accounts v2 preview. See the setup guide for consent requirements,
|
|
44
|
+
renewal tests, and payment-recovery limitations.
|
|
45
|
+
|
|
38
46
|
Builds and typechecks use TypeScript 7 (`@typescript/native` aliases
|
|
39
47
|
`typescript`). ESLint and Convex code generation still need the TypeScript 6
|
|
40
48
|
API, so `typescript` aliases Microsoft's `@typescript/typescript6` compatibility
|
|
@@ -61,11 +69,11 @@ pnpm codegen:example # regen example/convex/_generated only
|
|
|
61
69
|
|
|
62
70
|
All three require a configured dev deployment (`CONVEX_DEPLOYMENT` env var, normally set by a prior `npx convex dev`). They write only the local `_generated/` files — they don't push code or data to the deployment.
|
|
63
71
|
|
|
64
|
-
**CI gate
|
|
72
|
+
**CI gate.** Two layers verify the committed files are not stale:
|
|
65
73
|
|
|
66
74
|
1. **Structural drift check** (`pnpm codegen:check`, always runs in CI): two independent scans, each checking both directions (every source export appears in the generated file, and every generated entry still has a matching source), neither needing a deployment:
|
|
67
|
-
- Library component — scans `src/component/**/*.{queries,mutations,actions}.ts` and verifies every exported function appears in `src/component/_generated/component.ts`.
|
|
68
|
-
- Example app — scans `example/convex/**/*.ts` and verifies every source module is imported and registered in `example/convex/_generated/api.ts`. Catches the common case of adding a new `example/convex/<module>.ts` (or nested file like `example/convex/lib/<module>.ts`) without regenerating
|
|
75
|
+
- Library component — scans `src/component/**/*.{queries,mutations,actions}.ts` and verifies every exported function appears in `src/component/_generated/component.ts`. The reverse direction also flags a stale entry left behind by a *deleted* query/mutation/action. The export-detection regex intentionally matches only direct `query(...)`/`mutation(...)`/`action(...)` exports — not wrapper or re-export forms like `customQuery(...)` (convex-helpers) or `export { fn as name }`, which this component has none of today; widen the regex (and the reverse scan) rather than assuming that stays true.
|
|
76
|
+
- Example app — scans `example/convex/**/*.ts` and verifies every source module is imported and registered in `example/convex/_generated/api.ts`. Catches the common case of adding a new `example/convex/<module>.ts` (or nested file like `example/convex/lib/<module>.ts`) without regenerating. The reverse direction likewise flags a stale import left behind by a *deleted* module.
|
|
69
77
|
2. **Authoritative regen-and-diff** (runs in CI when `CONVEX_DEPLOYMENT` is configured): runs `pnpm codegen` against the configured dev deployment and fails if `git diff` shows changes. Catches every drift case, including validator-shape changes the structural check can't see. To enable on a fork, set the `CONVEX_DEPLOYMENT` repo secret to a dev deployment name and authorize the CI runner — see Convex's [GitHub Actions guide](https://docs.convex.dev/production/integration/deployments#github-actions).
|
|
70
78
|
|
|
71
79
|
### Relation to `@convex-dev/stripe`
|
|
@@ -96,12 +104,12 @@ See [Roadmap / Known Gaps](#roadmap--known-gaps) for what's still in flight.
|
|
|
96
104
|
|
|
97
105
|
## Roadmap / Known Gaps
|
|
98
106
|
|
|
99
|
-
The marketplace economics layer
|
|
107
|
+
The marketplace economics layer includes platform fees, split transfers, rolling payouts, dispute recovery, refund reversals, and per-store statement descriptors. The former roadmap gaps are covered as follows:
|
|
100
108
|
|
|
101
|
-
- **
|
|
102
|
-
- **
|
|
103
|
-
- **
|
|
104
|
-
- **
|
|
109
|
+
- **First subscription charge descriptor** — `createProduct` and `updateProduct` stamp the store's descriptor (or platform fallback) onto store-owned Stripe products. The product descriptor covers the first invoice for direct subscriptions and Checkout subscription mode; the invoice-time descriptor still takes precedence on later invoices. Existing products need `updateProduct` to receive the descriptor, and a later store descriptor change requires refreshing its products. Shared products have one descriptor and should not be reused across stores that need different descriptors.
|
|
110
|
+
- **First-invoice fees** — finalized first invoices bypass the draft update window. At `invoice.paid`, the processor collects the fixed/tiered fee through a partial reversal of the destination transfer. Invoice markers and invoice-tagged reversals prevent duplicate collection, including recovery after a failed marker write. Renewals whose fees were set on the draft invoice are skipped.
|
|
111
|
+
- **Dispute reversal retries** — reversal slices are claimed atomically before any Stripe call. Partial-success retries reuse the recorded amounts and idempotency keys, skip confirmed legs, and reconcile money already moved before a ledger-write failure.
|
|
112
|
+
- **Earnings and split totals** — `useEarnings` reads exact server-side aggregates with bounded drill-down previews. `listTransfersByAccount` remains a preview API; do not sum it for financial totals. `listTransfersByCharge` now returns every original split leg by default, and reversal claims cover all of those legs. An explicit list limit requests a preview. Convex read/write/response limits fail loudly rather than silently truncate financial data.
|
|
105
113
|
|
|
106
114
|
## Installation
|
|
107
115
|
|
|
@@ -598,7 +606,7 @@ const { feeAmount, percentApplied, fixedApplied, tier } = computeFee(2999, {
|
|
|
598
606
|
|
|
599
607
|
- **Percent-only** fees (`isPercentOnlyFee(config)` — no `fixed`, no `tiers`) map directly to Stripe's `application_fee_percent` on the subscription/PaymentIntent — no per-invoice computation needed.
|
|
600
608
|
- **Fixed or tiered** fees can't be expressed as a flat Stripe percentage, so they're computed and applied per invoice (`applyPerInvoiceFee`, at `invoice.created`) or per one-time charge (`applyPerChargeFee`) in the webhook processor, via `application_fee_amount`.
|
|
601
|
-
-
|
|
609
|
+
- For synchronously finalized first invoices, `invoice.paid` collects the missed fixed/tiered fee through a partial destination-transfer reversal. See [first-invoice fee handling](#roadmap--known-gaps).
|
|
602
610
|
|
|
603
611
|
Fees only apply to **destination charges** (`destinationAccountId` set, or a single-recipient `split`). The **separate-charges-and-transfers** path (multi-recipient `split`) never uses `application_fee` — the platform's cut is realized by transferring less than was charged (see Split Engine below).
|
|
604
612
|
|
|
@@ -690,7 +698,7 @@ await stripe.setAccountStatementDescriptor(ctx, {
|
|
|
690
698
|
});
|
|
691
699
|
```
|
|
692
700
|
|
|
693
|
-
Validated against Stripe's rules (`validateStatementDescriptorSuffix`: 1–22 chars, at least one Latin letter, no `< > \ ' " *`, no non-Latin — rejects rather than truncates) before writing. Resolution order per charge: the store's stored suffix → the platform's configured `statementDescriptorSuffix` default (constructor option) → none. One-time payments set the suffix directly on the PaymentIntent at checkout
|
|
701
|
+
Validated against Stripe's rules (`validateStatementDescriptorSuffix`: 1–22 chars, at least one Latin letter, no `< > \ ' " *`, no non-Latin — rejects rather than truncates) before writing. Resolution order per charge: the store's stored suffix → the platform's configured `statementDescriptorSuffix` default (constructor option) → none. One-time payments set the suffix directly on the PaymentIntent at checkout. Store-owned products carry the descriptor for subscriptions' first charges; the webhook processor sets it on later invoices. Refresh existing products with `updateProduct` after changing a store's descriptor.
|
|
694
702
|
|
|
695
703
|
## React Hooks
|
|
696
704
|
|
|
@@ -711,8 +719,8 @@ Read hooks are exported as factory functions that take the app's `useQuery` bind
|
|
|
711
719
|
| `createUsePaymentMethods` | `(accountId?)` | `{ methods, defaultMethod, isLoading }` |
|
|
712
720
|
| `createUseInvoices` | `({ userId?, subscriptionId?, status? }?)` | `{ invoices, isLoading }` |
|
|
713
721
|
| `createUseAccountOnboarding` | `(accountId?)` | `{ account, status, isReady, missingRequirements, isLoading }` |
|
|
714
|
-
| `createUseEarnings` | `(accountId?)` | `{ gross, reversed, net, payouts, paidOut, transfers, isLoading }` — recipient balance/earnings
|
|
715
|
-
| `createUseSplitBreakdown` | `({ sourceChargeId?, saleAmount? })` | `{ breakdown: { legs, store, affiliate, other, recipientsTotal, platform? } \| null, isLoading }` — per-sale split
|
|
722
|
+
| `createUseEarnings` | `(accountId?)` | `{ gross, reversed, net, payouts, paidOut, transfers, isLoading }` — recipient balance/earnings |
|
|
723
|
+
| `createUseSplitBreakdown` | `({ sourceChargeId?, saleAmount? })` | `{ breakdown: { legs, store, affiliate, other, recipientsTotal, platform? } \| null, isLoading }` — per-sale split |
|
|
716
724
|
| `createUseDisputes` | `({ accountId?, status? }?)` | `{ disputes, isLoading }` |
|
|
717
725
|
| `createUseDisputeWithCountdown` | `(stripeDisputeId?)` | `{ dispute, countdown: { dueBy?, daysRemaining?, isOverdue } \| null, isLoading }` |
|
|
718
726
|
|
|
@@ -919,7 +927,7 @@ views and responds to their own disputes via Stripe's embedded `disputes_list`
|
|
|
919
927
|
**Dependencies:** the wrapper uses [`@stripe/react-connect-js`](https://www.npmjs.com/package/@stripe/react-connect-js) and [`@stripe/connect-js`](https://www.npmjs.com/package/@stripe/connect-js), which ship as regular dependencies of this package (same convention as `@stripe/react-stripe-js` / `@stripe/stripe-js`) -- no extra install step. ConnectJS components are client-only iframes; render them in client components.
|
|
920
928
|
|
|
921
929
|
```tsx
|
|
922
|
-
// convex/stripe.ts — expose the account session (server side
|
|
930
|
+
// convex/stripe.ts — expose the account session (server side)
|
|
923
931
|
export const createDisputeSession = action({
|
|
924
932
|
args: { stripeAccountId: v.string() },
|
|
925
933
|
handler: async (ctx, args) => stripe.createDisputeSession(ctx, args),
|
|
@@ -71,7 +71,7 @@ export async function processEvent(whCtx, event) {
|
|
|
71
71
|
case "payout.updated":
|
|
72
72
|
case "payout.paid":
|
|
73
73
|
case "payout.failed":
|
|
74
|
-
await handlePayoutEvent(whCtx, obj);
|
|
74
|
+
await handlePayoutEvent(whCtx, obj, event.account);
|
|
75
75
|
break;
|
|
76
76
|
case "refund.created":
|
|
77
77
|
case "refund.updated":
|
|
@@ -741,12 +741,16 @@ async function upsertPaymentFromStripe(whCtx, paymentIntent, perChargeFee) {
|
|
|
741
741
|
metadata: paymentIntent.metadata ?? undefined,
|
|
742
742
|
});
|
|
743
743
|
}
|
|
744
|
-
async function handlePayoutEvent(whCtx, payout) {
|
|
744
|
+
async function handlePayoutEvent(whCtx, payout, connectedAccountId) {
|
|
745
745
|
await dispatchUpsert(whCtx, "payoutUpserted", {
|
|
746
746
|
stripePayoutId: payout.id,
|
|
747
|
-
accountId:
|
|
748
|
-
|
|
749
|
-
|
|
747
|
+
accountId: connectedAccountId ??
|
|
748
|
+
(payout.metadata?.accountId?.startsWith("acct_")
|
|
749
|
+
? payout.metadata.accountId
|
|
750
|
+
: undefined) ??
|
|
751
|
+
(typeof payout.destination === "string"
|
|
752
|
+
? payout.destination
|
|
753
|
+
: (payout.destination?.id ?? "")),
|
|
750
754
|
amount: payout.amount,
|
|
751
755
|
currency: payout.currency,
|
|
752
756
|
status: payout.status,
|