@solvapay/react 1.0.9-preview.1 → 1.0.9

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,209 @@
1
+ # @solvapay/react changelog
2
+
3
+ From `1.0.10` onwards this changelog is generated by
4
+ [changesets](https://github.com/changesets/changesets) — prior entries
5
+ are maintained by hand and are grouped by the first preview / stable
6
+ release that contains them.
7
+
8
+ ## Unreleased
9
+
10
+ ## 1.0.9 — peer-dep rename: `@solvapay/mcp` → `@solvapay/mcp-core`
11
+
12
+ The optional peer dependency that guards `@solvapay/react/mcp` types was
13
+ renamed from `@solvapay/mcp` → `@solvapay/mcp-core` to match the
14
+ reshuffled SolvaPay MCP package layout (framework-neutral contracts
15
+ now live in `@solvapay/mcp-core`; the official
16
+ `@modelcontextprotocol/sdk` adapter is `@solvapay/mcp`). No source
17
+ changes — this is a rename of the `peerDependencies` /
18
+ `peerDependenciesMeta` contract line only.
19
+
20
+ Integrators who install the optional peer explicitly:
21
+
22
+ ```diff
23
+ - pnpm add @solvapay/mcp
24
+ + pnpm add @solvapay/mcp-core
25
+ ```
26
+
27
+ Integrators who don't use `@solvapay/react/mcp` are unaffected.
28
+
29
+ ### `@solvapay/react/mcp` — per-view primitives + `<McpApp>` compound (minor, additive)
30
+
31
+ The MCP App reference views (`checkout`, `account`, `topup`, `activate`) that
32
+ previously lived in `examples/mcp-checkout-app` have been lifted into
33
+ `@solvapay/react/mcp` as four composable primitives plus a thin compound
34
+ wrapper:
35
+
36
+ ```tsx
37
+ import '@solvapay/react/styles.css'
38
+ import '@solvapay/react/mcp/styles.css'
39
+ import { App } from '@modelcontextprotocol/ext-apps'
40
+ import { McpApp } from '@solvapay/react/mcp'
41
+
42
+ const app = new App({ name: 'my-mcp-app', version: '1.0.0' })
43
+ createRoot(rootEl).render(<McpApp app={app} />)
44
+ ```
45
+
46
+ Integrators who want a custom shell compose the primitives directly:
47
+
48
+ ```tsx
49
+ import {
50
+ McpCheckoutView,
51
+ McpAccountView,
52
+ McpTopupView,
53
+ McpActivateView,
54
+ McpViewRouter,
55
+ fetchMcpBootstrap,
56
+ createMcpFetch,
57
+ createMcpAppAdapter,
58
+ useStripeProbe,
59
+ } from '@solvapay/react/mcp'
60
+ ```
61
+
62
+ Each view accepts a `classNames?: McpViewClassNames` partial for per-slot
63
+ overrides; defaults render the `solvapay-mcp-*` classes from the new
64
+ `@solvapay/react/mcp/styles.css` stylesheet. `<McpApp>` also takes a `views`
65
+ map for per-screen component overrides without losing routing.
66
+
67
+ ### Seam fixes on primitives that the MCP views needed
68
+
69
+ Three additive primitive changes landed alongside the lift:
70
+
71
+ - **`<AmountPicker.Root emit="minor">`** — when set, `onChange` and
72
+ `Confirm.onConfirm` deliver the amount in minor units (respects
73
+ zero-decimal currencies like JPY). Default stays `'major'` for
74
+ back-compat. `useAmountPicker().resolvedAmountMinor` is a new sibling to
75
+ `resolvedAmount` for custom layouts. A new
76
+ `getMinorUnitsPerMajor(currency)` util is exported from
77
+ `@solvapay/react` for integrators doing conversions outside the picker.
78
+ - **`<AmountPicker.Root selector={…}>`** — accepts an externally-owned
79
+ `UseTopupAmountSelectorReturn`, letting parent flows share state with
80
+ the inner picker. `ActivationFlow.AmountPicker` now threads the flow's
81
+ selector through this prop, so amounts picked in the sub-picker feed
82
+ straight into `useActivation().retry()` without the old workaround.
83
+ - **`<LaunchCustomerPortalButton asChild>`** — renders the ready-state
84
+ anchor via `Slot` so consumers can substitute a real `<button>` inside
85
+ an anchor wrapper (matches the `ActivationFlow.ActivateButton`
86
+ convention). Loading / error fallback buttons are untouched.
87
+
88
+ All three are additive — no consumer API moves or renames.
89
+
90
+ ### Plans vs balance transactions (minor, behavioural filtering)
91
+
92
+ Credit top-ups now surface as balance transactions, not plans. `PurchaseInfo`
93
+ gains a `metadata?: Record<string, unknown>` field, and two new utilities —
94
+ `isPlanPurchase` / `isTopupPurchase` — classify structurally from
95
+ `planSnapshot`. The classification is applied inside `usePurchase()` so every
96
+ plan-shaped accessor is consistent:
97
+
98
+ - `activePurchase`, `activePaidPurchase`, `hasPaidPurchase`, `hasProduct`,
99
+ and `cancelledPurchase` / `shouldShowCancelledNotice` (on
100
+ `usePurchaseStatus`) all skip balance transactions.
101
+ - A new `balanceTransactions: PurchaseInfo[]` accessor on `usePurchase()`
102
+ returns the complement.
103
+ - `purchases` (raw) is unchanged — the full ordering is still available for
104
+ integrators that classify themselves.
105
+
106
+ `PurchaseInfo.planSnapshot.name` is now surfaced as a first-class field so
107
+ `<CurrentPlanCard>` renders a real plan name ("Pro Monthly") instead of the
108
+ opaque `planRef`. Legacy purchases without a snapshot name fall back to
109
+ `productName`. The `planRef` is retained only as `data-solvapay-current-plan-ref`
110
+ on the card root for QA hooks.
111
+
112
+ `<CurrentPlanCard>` also uses `copy.currentPlan.cycleUnit` to render
113
+ "500 kr / month" rather than "500 kr / monthly". Override any of
114
+ `weekly` / `monthly` / `quarterly` / `yearly` in your copy bundle to localise
115
+ the interval label.
116
+
117
+ Behavioural note for integrators who previously relied on top-ups surfacing on
118
+ `activePurchase`: drop to `purchases` (raw) to restore the old most-recent
119
+ ordering, or read from `balanceTransactions` directly.
120
+
121
+ ### BREAKING: Unified `transport` replaces per-method provider overrides
122
+
123
+ `SolvaPayProvider` now takes a single `config.transport: SolvaPayTransport`
124
+ prop. The old per-method `SolvaPayProviderProps` overrides have been removed:
125
+
126
+ - `checkPurchase`
127
+ - `createPayment`
128
+ - `processPayment`
129
+ - `createTopupPayment`
130
+
131
+ Consumers passing any of those props must migrate to `config.transport`.
132
+
133
+ **Before:**
134
+
135
+ ```tsx
136
+ <SolvaPayProvider
137
+ checkPurchase={myCheckPurchase}
138
+ createPayment={myCreatePayment}
139
+ processPayment={myProcessPayment}
140
+ createTopupPayment={myCreateTopupPayment}
141
+ >
142
+ {children}
143
+ </SolvaPayProvider>
144
+ ```
145
+
146
+ **After:**
147
+
148
+ ```tsx
149
+ const transport: SolvaPayTransport = {
150
+ checkPurchase: myCheckPurchase,
151
+ createPayment: myCreatePayment,
152
+ processPayment: myProcessPayment,
153
+ createTopupPayment: myCreateTopupPayment,
154
+ // ...plus every other required method; see types/transport.ts
155
+ }
156
+
157
+ <SolvaPayProvider config={{ transport }}>{children}</SolvaPayProvider>
158
+ ```
159
+
160
+ HTTP consumers who never set those overrides are unaffected — the provider
161
+ falls back to `createHttpTransport(config)` automatically.
162
+
163
+ ### Added: `@solvapay/react/mcp` subpath
164
+
165
+ Ships `createMcpAppAdapter(app)` which returns a `SolvaPayTransport` that
166
+ tunnels every data call through `app.callServerTool` instead of HTTP. Use it
167
+ when hosting the React tree inside an MCP App, where direct HTTP to your
168
+ backend is blocked by the host sandbox:
169
+
170
+ ```tsx
171
+ import { createMcpAppAdapter } from '@solvapay/react/mcp'
172
+ <SolvaPayProvider config={{ transport: createMcpAppAdapter(app) }}>
173
+ {children}
174
+ </SolvaPayProvider>
175
+ ```
176
+
177
+ See the [MCP App integration guide](https://docs.solvapay.com/sdks/typescript/guides/mcp-app)
178
+ for the full walkthrough. Re-exports `MCP_TOOL_NAMES` so client and server
179
+ share a single source of truth for tool names.
180
+
181
+ ### Added: account management components
182
+
183
+ - `<CurrentPlanCard>` — plan-type-aware summary card (recurring / one-time /
184
+ usage-based), payment-method display, Update card + Cancel plan actions.
185
+ Returns `null` when there is no active purchase.
186
+ - `<LaunchCustomerPortalButton>` — pre-fetches `createCustomerSession` on
187
+ mount and renders a real `<a target="_blank">` anchor (MCP sandbox-safe).
188
+ - `<UpdatePaymentMethodButton mode="portal">` — thin wrapper around
189
+ `<LaunchCustomerPortalButton>`. The `mode` prop reserves space for a
190
+ future `"inline"` value without breaking the API.
191
+
192
+ ### Added: `usePaymentMethod` hook
193
+
194
+ Fetches the customer's default payment method through
195
+ `transport.getPaymentMethod()`. Transport-keyed single-flight cache, mirrors
196
+ the `useMerchant` pattern. Returns `null` on error (graceful hide, no
197
+ `onError` re-emit — the HTTP transport already invokes `config.onError`
198
+ before throwing).
199
+
200
+ ### Fixed
201
+
202
+ - `SolvaPayProvider` no longer flips `loading: true` for polling refetches
203
+ when the customer has no purchases yet. `usePurchase` now exposes a
204
+ separate `isRefetching` flag for background polls; gate initial skeletons
205
+ on `loading` and render subtle refresh indicators on `isRefetching`.
206
+ - `LaunchCustomerPortalButton` captures `onError` via ref so parents passing
207
+ an inline arrow no longer re-fire the pre-fetch effect on every render.
208
+ - `useMerchant`, `useProduct`, and `usePaymentMethod` no longer double-call
209
+ `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
+ ```