@solvapay/react 1.0.9-preview.1 → 1.0.11-preview-e89911bc8c4dd3759eab37b440559220b691fd23

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 ADDED
@@ -0,0 +1,279 @@
1
+ # @solvapay/react changelog
2
+
3
+ ## 1.0.11-preview-e89911bc8c4dd3759eab37b440559220b691fd23
4
+
5
+ ### Patch Changes
6
+
7
+ - d4183ba: **PAYG checkout now activates at the plan step.**
8
+
9
+ `CheckoutStateMachine` fires `activate_plan` from the plan picker's
10
+ `Continue with Pay as you go` button instead of from the amount picker.
11
+ This pairs with the backend change that makes `activate_plan` eagerly
12
+ create the active usage-based purchase regardless of current credit
13
+ balance — one user-visible click, one activation round-trip.
14
+
15
+ Behaviour change:
16
+ - Plan step `Continue` now awaits `activate_plan` before advancing to
17
+ the amount picker. The button renders in its existing activating
18
+ state while the call is in flight; failures surface via
19
+ `activationError` below the button.
20
+ - Amount step `Continue` is now a purely-local state transition — it
21
+ no longer fires `activate_plan`. Back-navigation from the payment
22
+ step (`Change amount`) therefore does not re-activate.
23
+ - `AmountStep` props trimmed: `isActivating` and `activationError`
24
+ removed (unused on this step now). `onContinue` is synchronous.
25
+
26
+ Migration notes:
27
+ - Custom surfaces that pass `isActivating` / `activationError` into
28
+ their own `<AmountStep>` wrapper should drop those props.
29
+ - Consumers subclassing `CheckoutStateMachine` should move any logic
30
+ that reacted to "activation-in-flight at the amount step" into the
31
+ plan step's activating window.
32
+
33
+ Paired backend PR: `solvapay-backend#112` — eager activation for
34
+ usage-based plans in the `activate_plan` MCP tool handler.
35
+
36
+ From `1.0.10` onwards this changelog is generated by
37
+ [changesets](https://github.com/changesets/changesets) — prior entries
38
+ are maintained by hand and are grouped by the first preview / stable
39
+ release that contains them.
40
+
41
+ ## 1.0.10
42
+
43
+ SEP-1865 text-only-paywall surface trim. Hand-set version (the
44
+ Changesets peer-dep cascade would otherwise have forced this to
45
+ `2.0.0` because the upstream `@solvapay/mcp-core` minor bump on a
46
+ `workspace:*` peer cascades to `major` under Changesets' default
47
+ rule). Released as a patch despite the removed public surface
48
+ because no external consumers rely on the removed views — they
49
+ lived in `@solvapay/react/mcp` behind the preview channel and were
50
+ never stabilised. See
51
+ `.changeset/hand-set-versions-consolidation.md` for the full
52
+ rationale.
53
+
54
+ ### Removed (breaking): MCP paywall / nudge / upsell views
55
+
56
+ Per SEP-1865 / MCP Apps (2026-01-26) descriptor-advertising means
57
+ the host MUST open the iframe on every call. Text-only narrations
58
+ live on `content[0].text` now; there's no widget state for the
59
+ paywall / nudge / activation surfaces anymore, so the views that
60
+ rendered them are removed from the public API:
61
+
62
+ - `McpPaywallView` + `McpPaywallViewProps`
63
+ - `McpNudgeView` + `McpNudgeViewProps`
64
+ - `McpUpsellStrip` + `McpUpsellStripProps`
65
+ - The matching slots on `McpAppViewOverrides`
66
+ (`paywall` / `nudge` / `upsellStrip`)
67
+ - `McpViewKind` narrows to `'checkout' | 'account' | 'topup'`
68
+ (the `'paywall'` and `'nudge'` variants are gone)
69
+ - `HostEntryClassification` collapses from the four-way paywall /
70
+ nudge / intent / other split to `'intent' | 'other'`
71
+ - `McpBootstrap` loses the `paywall`, `nudge`, and `data` fields —
72
+ the payload the text-only paywall returns no longer carries
73
+ those slots
74
+
75
+ Migration: drop any imports of `McpPaywallView` / `McpNudgeView` /
76
+ `McpUpsellStrip` from your MCP App shell — the paywall narration
77
+ surfaces in the LLM's conversation transcript via the paywalled
78
+ tool's text response, and no widget mounts for a gate. If you had
79
+ custom view overrides for the paywall / nudge slots, delete them;
80
+ the compound `<McpApp>` no longer routes through those kinds.
81
+
82
+ ## 1.0.9 — peer-dep rename: `@solvapay/mcp` → `@solvapay/mcp-core`
83
+
84
+ The optional peer dependency that guards `@solvapay/react/mcp` types was
85
+ renamed from `@solvapay/mcp` → `@solvapay/mcp-core` to match the
86
+ reshuffled SolvaPay MCP package layout (framework-neutral contracts
87
+ now live in `@solvapay/mcp-core`; the official
88
+ `@modelcontextprotocol/sdk` adapter is `@solvapay/mcp`). No source
89
+ changes — this is a rename of the `peerDependencies` /
90
+ `peerDependenciesMeta` contract line only.
91
+
92
+ Integrators who install the optional peer explicitly:
93
+
94
+ ```diff
95
+ - pnpm add @solvapay/mcp
96
+ + pnpm add @solvapay/mcp-core
97
+ ```
98
+
99
+ Integrators who don't use `@solvapay/react/mcp` are unaffected.
100
+
101
+ ### `@solvapay/react/mcp` — per-view primitives + `<McpApp>` compound (minor, additive)
102
+
103
+ The MCP App reference views (`checkout`, `account`, `topup`, `activate`) that
104
+ previously lived in `examples/mcp-checkout-app` have been lifted into
105
+ `@solvapay/react/mcp` as four composable primitives plus a thin compound
106
+ wrapper:
107
+
108
+ ```tsx
109
+ import '@solvapay/react/styles.css'
110
+ import '@solvapay/react/mcp/styles.css'
111
+ import { App } from '@modelcontextprotocol/ext-apps'
112
+ import { McpApp } from '@solvapay/react/mcp'
113
+
114
+ const app = new App({ name: 'my-mcp-app', version: '1.0.0' })
115
+ createRoot(rootEl).render(<McpApp app={app} />)
116
+ ```
117
+
118
+ Integrators who want a custom shell compose the primitives directly:
119
+
120
+ ```tsx
121
+ import {
122
+ McpCheckoutView,
123
+ McpAccountView,
124
+ McpTopupView,
125
+ McpActivateView,
126
+ McpViewRouter,
127
+ fetchMcpBootstrap,
128
+ createMcpFetch,
129
+ createMcpAppAdapter,
130
+ useStripeProbe,
131
+ } from '@solvapay/react/mcp'
132
+ ```
133
+
134
+ Each view accepts a `classNames?: McpViewClassNames` partial for per-slot
135
+ overrides; defaults render the `solvapay-mcp-*` classes from the new
136
+ `@solvapay/react/mcp/styles.css` stylesheet. `<McpApp>` also takes a `views`
137
+ map for per-screen component overrides without losing routing.
138
+
139
+ ### Seam fixes on primitives that the MCP views needed
140
+
141
+ Three additive primitive changes landed alongside the lift:
142
+
143
+ - **`<AmountPicker.Root emit="minor">`** — when set, `onChange` and
144
+ `Confirm.onConfirm` deliver the amount in minor units (respects
145
+ zero-decimal currencies like JPY). Default stays `'major'` for
146
+ back-compat. `useAmountPicker().resolvedAmountMinor` is a new sibling to
147
+ `resolvedAmount` for custom layouts. A new
148
+ `getMinorUnitsPerMajor(currency)` util is exported from
149
+ `@solvapay/react` for integrators doing conversions outside the picker.
150
+ - **`<AmountPicker.Root selector={…}>`** — accepts an externally-owned
151
+ `UseTopupAmountSelectorReturn`, letting parent flows share state with
152
+ the inner picker. `ActivationFlow.AmountPicker` now threads the flow's
153
+ selector through this prop, so amounts picked in the sub-picker feed
154
+ straight into `useActivation().retry()` without the old workaround.
155
+ - **`<LaunchCustomerPortalButton asChild>`** — renders the ready-state
156
+ anchor via `Slot` so consumers can substitute a real `<button>` inside
157
+ an anchor wrapper (matches the `ActivationFlow.ActivateButton`
158
+ convention). Loading / error fallback buttons are untouched.
159
+
160
+ All three are additive — no consumer API moves or renames.
161
+
162
+ ### Plans vs balance transactions (minor, behavioural filtering)
163
+
164
+ Credit top-ups now surface as balance transactions, not plans. `PurchaseInfo`
165
+ gains a `metadata?: Record<string, unknown>` field, and two new utilities —
166
+ `isPlanPurchase` / `isTopupPurchase` — classify structurally from
167
+ `planSnapshot`. The classification is applied inside `usePurchase()` so every
168
+ plan-shaped accessor is consistent:
169
+
170
+ - `activePurchase`, `activePaidPurchase`, `hasPaidPurchase`, `hasProduct`,
171
+ and `cancelledPurchase` / `shouldShowCancelledNotice` (on
172
+ `usePurchaseStatus`) all skip balance transactions.
173
+ - A new `balanceTransactions: PurchaseInfo[]` accessor on `usePurchase()`
174
+ returns the complement.
175
+ - `purchases` (raw) is unchanged — the full ordering is still available for
176
+ integrators that classify themselves.
177
+
178
+ `PurchaseInfo.planSnapshot.name` is now surfaced as a first-class field so
179
+ `<CurrentPlanCard>` renders a real plan name ("Pro Monthly") instead of the
180
+ opaque `planRef`. Legacy purchases without a snapshot name fall back to
181
+ `productName`. The `planRef` is retained only as `data-solvapay-current-plan-ref`
182
+ on the card root for QA hooks.
183
+
184
+ `<CurrentPlanCard>` also uses `copy.currentPlan.cycleUnit` to render
185
+ "500 kr / month" rather than "500 kr / monthly". Override any of
186
+ `weekly` / `monthly` / `quarterly` / `yearly` in your copy bundle to localise
187
+ the interval label.
188
+
189
+ Behavioural note for integrators who previously relied on top-ups surfacing on
190
+ `activePurchase`: drop to `purchases` (raw) to restore the old most-recent
191
+ ordering, or read from `balanceTransactions` directly.
192
+
193
+ ### BREAKING: Unified `transport` replaces per-method provider overrides
194
+
195
+ `SolvaPayProvider` now takes a single `config.transport: SolvaPayTransport`
196
+ prop. The old per-method `SolvaPayProviderProps` overrides have been removed:
197
+
198
+ - `checkPurchase`
199
+ - `createPayment`
200
+ - `processPayment`
201
+ - `createTopupPayment`
202
+
203
+ Consumers passing any of those props must migrate to `config.transport`.
204
+
205
+ **Before:**
206
+
207
+ ```tsx
208
+ <SolvaPayProvider
209
+ checkPurchase={myCheckPurchase}
210
+ createPayment={myCreatePayment}
211
+ processPayment={myProcessPayment}
212
+ createTopupPayment={myCreateTopupPayment}
213
+ >
214
+ {children}
215
+ </SolvaPayProvider>
216
+ ```
217
+
218
+ **After:**
219
+
220
+ ```tsx
221
+ const transport: SolvaPayTransport = {
222
+ checkPurchase: myCheckPurchase,
223
+ createPayment: myCreatePayment,
224
+ processPayment: myProcessPayment,
225
+ createTopupPayment: myCreateTopupPayment,
226
+ // ...plus every other required method; see types/transport.ts
227
+ }
228
+
229
+ <SolvaPayProvider config={{ transport }}>{children}</SolvaPayProvider>
230
+ ```
231
+
232
+ HTTP consumers who never set those overrides are unaffected — the provider
233
+ falls back to `createHttpTransport(config)` automatically.
234
+
235
+ ### Added: `@solvapay/react/mcp` subpath
236
+
237
+ Ships `createMcpAppAdapter(app)` which returns a `SolvaPayTransport` that
238
+ tunnels every data call through `app.callServerTool` instead of HTTP. Use it
239
+ when hosting the React tree inside an MCP App, where direct HTTP to your
240
+ backend is blocked by the host sandbox:
241
+
242
+ ```tsx
243
+ import { createMcpAppAdapter } from '@solvapay/react/mcp'
244
+ ;<SolvaPayProvider config={{ transport: createMcpAppAdapter(app) }}>{children}</SolvaPayProvider>
245
+ ```
246
+
247
+ See the [MCP App integration guide](https://docs.solvapay.com/sdks/typescript/guides/mcp-app)
248
+ for the full walkthrough. Re-exports `MCP_TOOL_NAMES` so client and server
249
+ share a single source of truth for tool names.
250
+
251
+ ### Added: account management components
252
+
253
+ - `<CurrentPlanCard>` — plan-type-aware summary card (recurring / one-time /
254
+ usage-based), payment-method display, Update card + Cancel plan actions.
255
+ Returns `null` when there is no active purchase.
256
+ - `<LaunchCustomerPortalButton>` — pre-fetches `createCustomerSession` on
257
+ mount and renders a real `<a target="_blank">` anchor (MCP sandbox-safe).
258
+ - `<UpdatePaymentMethodButton mode="portal">` — thin wrapper around
259
+ `<LaunchCustomerPortalButton>`. The `mode` prop reserves space for a
260
+ future `"inline"` value without breaking the API.
261
+
262
+ ### Added: `usePaymentMethod` hook
263
+
264
+ Fetches the customer's default payment method through
265
+ `transport.getPaymentMethod()`. Transport-keyed single-flight cache, mirrors
266
+ the `useMerchant` pattern. Returns `null` on error (graceful hide, no
267
+ `onError` re-emit — the HTTP transport already invokes `config.onError`
268
+ before throwing).
269
+
270
+ ### Fixed
271
+
272
+ - `SolvaPayProvider` no longer flips `loading: true` for polling refetches
273
+ when the customer has no purchases yet. `usePurchase` now exposes a
274
+ separate `isRefetching` flag for background polls; gate initial skeletons
275
+ on `loading` and render subtle refresh indicators on `isRefetching`.
276
+ - `LaunchCustomerPortalButton` captures `onError` via ref so parents passing
277
+ an inline arrow no longer re-fire the pre-fetch effect on every render.
278
+ - `useMerchant`, `useProduct`, and `usePaymentMethod` no longer double-call
279
+ `config.onError` — the HTTP transport already calls it before throwing.
@@ -0,0 +1,117 @@
1
+ # Contributing to @solvapay/react
2
+
3
+ This package ships two tiers:
4
+
5
+ - **`@solvapay/react`** — default tree. Drop-in components (`CheckoutLayout`, `PaymentForm`, …) that render a golden-path UI. Internally, they are thin shims over the primitives.
6
+ - **`@solvapay/react/primitives`** — unstyled compound primitives. Consumers compose these to build fully custom UIs.
7
+
8
+ This file is the canonical contract for primitives until the cross-repo Mintlify docs land. Every primitive you add or edit MUST follow these rules.
9
+
10
+ ## Primitive contract
11
+
12
+ ### Compound API
13
+
14
+ Every primitive is a compound: a `Root` plus named subcomponents.
15
+
16
+ ```tsx
17
+ <PlanSelector.Root>
18
+ <PlanSelector.Heading />
19
+ <PlanSelector.Grid>
20
+ <PlanSelector.Card>
21
+ <PlanSelector.CardName />
22
+ <PlanSelector.CardPrice />
23
+ </PlanSelector.Card>
24
+ </PlanSelector.Grid>
25
+ </PlanSelector.Root>
26
+ ```
27
+
28
+ - Export subcomponents as properties on the `Root` (`PlanSelector.Card`) **and** as named exports from `./primitives` for tree-shake-friendly use.
29
+ - No render-prop children. No `classNames` prop. No `unstyled` flag.
30
+
31
+ ### `asChild`
32
+
33
+ Every leaf subcomponent accepts `asChild?: boolean`. When `true`, it renders its single child via `<Slot>` and merges props/ref onto the child.
34
+
35
+ ```tsx
36
+ <PlanSelector.Card asChild>
37
+ <Card>{/* shadcn/ui card */}</Card>
38
+ </PlanSelector.Card>
39
+ ```
40
+
41
+ Use `Slot` from `./primitives/slot`. Refs are merged via `composeRefs`; event handlers are chained via `composeEventHandlers` (consumer runs first; `preventDefault()` skips the primitive handler).
42
+
43
+ ### `data-state` vocabulary
44
+
45
+ Every stateful subcomponent emits a `data-state` attribute from a fixed, documented vocabulary. States are driven by context (`PlanSelectionContext`, `PaymentFormContext`), never by subcomponent-local state.
46
+
47
+ | Primitive | `data-state` values |
48
+ | --- | --- |
49
+ | `PlanSelector.Card` | `idle` \| `selected` \| `current` \| `disabled` |
50
+ | `PaymentForm.SubmitButton` | `idle` \| `processing` \| `disabled` |
51
+ | `ActivationFlow.Root` | `summary` \| `activating` \| `selectAmount` \| `topupPayment` \| `retrying` \| `activated` \| `error` |
52
+ | `CreditGate.Root` | `allowed` \| `blocked` \| `loading` |
53
+ | `PurchaseGate.Root` | `allowed` \| `blocked` \| `loading` |
54
+ | `CancelledPlanNotice.Root` | `active` \| `expired` |
55
+ | `CancelPlanButton` | `idle` \| `cancelling` |
56
+ | `BalanceBadge` | `loading` \| `zero` \| `low` \| `ok` |
57
+ | `AmountPicker.Option` | `idle` \| `selected` \| `disabled` |
58
+
59
+ Secondary flags use `data-<flag>` (e.g. `data-free`, `data-popular`, `data-has-reason`).
60
+
61
+ ### Opaque selectors
62
+
63
+ Every primitive root emits `data-solvapay-<primitive>` (and subcomponents emit `data-solvapay-<primitive>-<part>` when they need to be targetable). Do not rely on internal class names; those are not a public API.
64
+
65
+ ### `.Loading` and `.Error`
66
+
67
+ Every async primitive exposes `.Loading` and `.Error` subcomponents that render when the context reports loading/error. They render `null` otherwise. Always provide a fallback UI path — don't rely on consumers remembering to add them.
68
+
69
+ ### Errors self-heal
70
+
71
+ Throw structured errors from `src/utils/errors.ts` for wiring mistakes:
72
+
73
+ - `MissingProviderError` — primitive rendered outside `<SolvaPayProvider>`.
74
+ - `MissingEnvVarError` — required env var not set.
75
+ - `MissingApiRouteError` — backend route not installed.
76
+ - `MissingProductRefError` — primitive needs `productRef` and none was provided.
77
+
78
+ Each error includes `code`, `message` (fix + docs URL), and `docsUrl`.
79
+
80
+ ## Prohibited patterns
81
+
82
+ When touching a primitive, these must stay gone:
83
+
84
+ - `classNames` prop, `unstyled` prop, `RenderArgs` / function-child overloads.
85
+ - Inline `style={{ ... }}` inside `src/primitives/**`.
86
+ - `next/*` imports (the package must bundle on Vite/Remix).
87
+ - `as any`, `as unknown as ...` except when unavoidable; justify with a comment.
88
+ - Implicit `enum`; use union types.
89
+
90
+ ## TDD checklist per primitive
91
+
92
+ Write the primitive and its test file in the same commit. Every primitive test covers:
93
+
94
+ 1. **Default render** — subcomponents render the expected DOM.
95
+ 2. **`asChild` composition** — refs merge, handlers chain, `data-*` + `aria-*` forward to the child element.
96
+ 3. **`data-state` transitions** — context-driven state flips the attribute correctly.
97
+ 4. **`.Loading` / `.Error`** — rendered when context signals loading/error.
98
+ 5. **Provider-missing + env-missing** — the correct structured error class is thrown with its docs URL.
99
+
100
+ Delete legacy tests that assert on `classNames` / function-child / `RenderArgs` **before** landing the rewrite.
101
+
102
+ ## Adding a new primitive (checklist)
103
+
104
+ 1. Write the test file following the TDD checklist above.
105
+ 2. Create `src/primitives/<Name>.tsx` with `Root` + subcomponents + `asChild` + `data-state`.
106
+ 3. Re-export from `src/primitives/index.ts`.
107
+ 4. If the primitive is part of the default tree, update the shim in `src/components/<Name>.tsx` to render it with the golden-path children.
108
+ 5. Update the `data-state` table in this file if you introduce a new state.
109
+ 6. Keep the package-local README cheat sheet in sync.
110
+
111
+ ## Commands
112
+
113
+ ```bash
114
+ pnpm --filter @solvapay/react test # run the primitive + component tests
115
+ pnpm --filter @solvapay/react build # tsup build (top-level + primitives entry)
116
+ pnpm --filter @solvapay/react lint # eslint
117
+ ```