@solvapay/react 1.0.8-preview.9 → 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 +209 -0
- package/README.md +194 -25
- package/dist/adapters/auth.d.cts +13 -0
- package/dist/adapters/auth.d.ts +13 -0
- package/dist/adapters/auth.js +1 -0
- package/dist/{chunk-MOP3ZBGC.js → chunk-37R5NZGF.js} +1116 -1041
- package/dist/chunk-HWVJL5X6.js +429 -0
- package/dist/chunk-MLKGABMK.js +9 -0
- package/dist/chunk-R2ZPZ7VM.js +597 -0
- package/dist/chunk-ZAV7CQ4G.js +361 -0
- package/dist/index-WBjulQHf.d.cts +3656 -0
- package/dist/index-onWNU7iT.d.ts +3656 -0
- package/dist/index.cjs +1422 -366
- package/dist/index.d.cts +251 -64
- package/dist/index.d.ts +251 -64
- package/dist/index.js +54 -26
- package/dist/mcp/index.cjs +8096 -0
- package/dist/mcp/index.d.cts +1013 -0
- package/dist/mcp/index.d.ts +1013 -0
- package/dist/mcp/index.js +2724 -0
- package/dist/mcp/styles.css +1020 -0
- package/dist/primitives/index.cjs +1177 -115
- package/dist/primitives/index.d.cts +208 -20
- package/dist/primitives/index.d.ts +208 -20
- package/dist/primitives/index.js +66 -21
- package/dist/styles.css +246 -0
- package/dist/useUsage-BmOYXxgG.d.cts +413 -0
- package/dist/useUsage-nD7zwSbG.d.ts +413 -0
- package/dist/webapi-K5XBCEO6.js +3775 -0
- package/package.json +20 -4
- package/dist/CancelPlanButton-CieT9swn.d.cts +0 -1102
- package/dist/CancelPlanButton-f56UlQN-.d.ts +0 -1102
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.
|
package/README.md
CHANGED
|
@@ -182,12 +182,10 @@ export default function App() {
|
|
|
182
182
|
```tsx
|
|
183
183
|
import { SolvaPayProvider } from '@solvapay/react'
|
|
184
184
|
import { createSupabaseAuthAdapter } from '@solvapay/react-supabase'
|
|
185
|
+
import { supabase } from './lib/supabase'
|
|
185
186
|
|
|
186
187
|
export default function App() {
|
|
187
|
-
const adapter = createSupabaseAuthAdapter({
|
|
188
|
-
supabaseUrl: process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
|
189
|
-
supabaseAnonKey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
|
|
190
|
-
})
|
|
188
|
+
const adapter = createSupabaseAuthAdapter({ client: supabase })
|
|
191
189
|
|
|
192
190
|
return (
|
|
193
191
|
<SolvaPayProvider config={{ auth: { adapter } }}>
|
|
@@ -199,32 +197,175 @@ export default function App() {
|
|
|
199
197
|
|
|
200
198
|
### Fully Custom Implementation
|
|
201
199
|
|
|
200
|
+
Every data-access call flows through `config.transport`. Build a custom one by
|
|
201
|
+
spreading `createHttpTransport(config)` and overriding the methods you need:
|
|
202
|
+
|
|
202
203
|
```tsx
|
|
203
|
-
import { SolvaPayProvider } from '@solvapay/react'
|
|
204
|
+
import { SolvaPayProvider, createHttpTransport } from '@solvapay/react'
|
|
204
205
|
|
|
205
206
|
export default function App() {
|
|
207
|
+
const transport = {
|
|
208
|
+
...createHttpTransport(undefined),
|
|
209
|
+
createPayment: async ({ planRef, productRef }) => {
|
|
210
|
+
const res = await fetch('/api/custom/payment', {
|
|
211
|
+
method: 'POST',
|
|
212
|
+
body: JSON.stringify({ planRef, productRef }),
|
|
213
|
+
})
|
|
214
|
+
if (!res.ok) throw new Error('Failed to create payment')
|
|
215
|
+
return res.json()
|
|
216
|
+
},
|
|
217
|
+
checkPurchase: async () => {
|
|
218
|
+
const res = await fetch('/api/custom/purchase')
|
|
219
|
+
if (!res.ok) throw new Error('Failed to check purchase')
|
|
220
|
+
return res.json()
|
|
221
|
+
},
|
|
222
|
+
}
|
|
223
|
+
|
|
206
224
|
return (
|
|
207
|
-
<SolvaPayProvider
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
225
|
+
<SolvaPayProvider config={{ transport }}>
|
|
226
|
+
<CheckoutPage />
|
|
227
|
+
</SolvaPayProvider>
|
|
228
|
+
)
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### MCP App
|
|
233
|
+
|
|
234
|
+
For React trees hosted inside an MCP App (where Stripe.js and direct HTTP
|
|
235
|
+
to your backend are both blocked by the host sandbox), use the MCP subpath:
|
|
236
|
+
|
|
237
|
+
```tsx
|
|
238
|
+
import { App } from '@modelcontextprotocol/ext-apps'
|
|
239
|
+
import { SolvaPayProvider } from '@solvapay/react'
|
|
240
|
+
import { createMcpAppAdapter } from '@solvapay/react/mcp'
|
|
241
|
+
|
|
242
|
+
const app = new App({ name: 'solvapay', version: '1.0.0' })
|
|
243
|
+
const transport = createMcpAppAdapter(app)
|
|
244
|
+
|
|
245
|
+
export default function Root() {
|
|
246
|
+
return (
|
|
247
|
+
<SolvaPayProvider config={{ transport }}>
|
|
222
248
|
<CheckoutPage />
|
|
223
249
|
</SolvaPayProvider>
|
|
224
250
|
)
|
|
225
251
|
}
|
|
226
252
|
```
|
|
227
253
|
|
|
254
|
+
The MCP server is expected to expose tools whose names match `MCP_TOOL_NAMES`
|
|
255
|
+
from `@solvapay/react/mcp` — each transport method maps 1:1 to a tool call.
|
|
256
|
+
|
|
257
|
+
### Managing plans in an MCP App
|
|
258
|
+
|
|
259
|
+
Once a customer is paid, drop `<CurrentPlanCard />` into the UI and the
|
|
260
|
+
SDK does the rest — plan name, price, next-billing / expiry line,
|
|
261
|
+
payment-method summary (via `get_payment_method`), Update-card and
|
|
262
|
+
Cancel-plan actions. The card returns `null` when there's no active
|
|
263
|
+
purchase, so you can render it unconditionally:
|
|
264
|
+
|
|
265
|
+
```tsx
|
|
266
|
+
import { CurrentPlanCard } from '@solvapay/react'
|
|
267
|
+
|
|
268
|
+
function Account() {
|
|
269
|
+
return <CurrentPlanCard />
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Behind the scenes:
|
|
274
|
+
|
|
275
|
+
- Plan metadata comes from `usePurchase` (provider state, no extra fetch)
|
|
276
|
+
- Payment-method line comes from `usePaymentMethod` → `transport.getPaymentMethod()`
|
|
277
|
+
- `<UpdatePaymentMethodButton>` pre-fetches `transport.createCustomerSession()`
|
|
278
|
+
on mount and renders a real `<a target="_blank">` to the hosted portal —
|
|
279
|
+
MCP host sandboxes permit direct anchor clicks even though scripted
|
|
280
|
+
`window.open` after an async round-trip is blocked.
|
|
281
|
+
- `<CancelPlanButton>` reuses Phase 1 behaviour — no new plumbing.
|
|
282
|
+
|
|
283
|
+
If you want the bare portal-launch button on its own (e.g. in a top nav),
|
|
284
|
+
use `<LaunchCustomerPortalButton />` directly.
|
|
285
|
+
|
|
286
|
+
> `<PlanSwitcher>` (plan upgrade/downgrade with proration) and
|
|
287
|
+
> `<PaymentMethodForm>` (inline Stripe Elements card update) are deferred
|
|
288
|
+
> to follow-up PRs — see the plan in the SDK repo for scope. For plan
|
|
289
|
+
> changes today, MCP apps use `<CancelPlanButton>` + `<CurrentPlanCard>`
|
|
290
|
+
> + a fresh hosted checkout flow.
|
|
291
|
+
|
|
292
|
+
## Building MCP Apps
|
|
293
|
+
|
|
294
|
+
`@solvapay/react/mcp` ships a turnkey compound plus four composable
|
|
295
|
+
primitives for SolvaPay MCP Apps built on
|
|
296
|
+
[`@modelcontextprotocol/ext-apps`](https://github.com/modelcontextprotocol/ext-apps).
|
|
297
|
+
|
|
298
|
+
### Quick start — `<McpApp>`
|
|
299
|
+
|
|
300
|
+
```tsx
|
|
301
|
+
import { createRoot } from 'react-dom/client'
|
|
302
|
+
import { App } from '@modelcontextprotocol/ext-apps'
|
|
303
|
+
import { McpApp } from '@solvapay/react/mcp'
|
|
304
|
+
import '@solvapay/react/styles.css'
|
|
305
|
+
import '@solvapay/react/mcp/styles.css'
|
|
306
|
+
|
|
307
|
+
const app = new App({ name: 'my-mcp-app', version: '1.0.0' })
|
|
308
|
+
createRoot(document.getElementById('root')!).render(<McpApp app={app} />)
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
`<McpApp>` handles `app.connect()`, calls the `open_*` tool matching the
|
|
312
|
+
host's invocation context, mounts `<SolvaPayProvider>` with
|
|
313
|
+
`createMcpAppAdapter(app)` + `createMcpFetch(transport)`, and routes to the
|
|
314
|
+
correct per-view primitive.
|
|
315
|
+
|
|
316
|
+
Pass `applyContext` to wire host theme / fonts / safe-area insets from the
|
|
317
|
+
`ext-apps` helpers, override individual screens via `views`, and tweak
|
|
318
|
+
per-slot styling via `classNames`:
|
|
319
|
+
|
|
320
|
+
```tsx
|
|
321
|
+
<McpApp
|
|
322
|
+
app={app}
|
|
323
|
+
applyContext={ctx => {
|
|
324
|
+
if (ctx?.theme) applyDocumentTheme(ctx.theme)
|
|
325
|
+
}}
|
|
326
|
+
views={{
|
|
327
|
+
account: MyCustomAccountScreen, // optional — one-off override
|
|
328
|
+
}}
|
|
329
|
+
classNames={{ card: 'my-card', button: 'my-btn' }}
|
|
330
|
+
onInitError={err => console.error('[mcp]', err)}
|
|
331
|
+
/>
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
See [`examples/mcp-checkout-app`](../../examples/mcp-checkout-app) for the
|
|
335
|
+
full host integration including the server-side `open_*` tool + UI resource
|
|
336
|
+
registration.
|
|
337
|
+
|
|
338
|
+
### Per-view primitives
|
|
339
|
+
|
|
340
|
+
When you need a custom shell — your own provider mount, additional routes,
|
|
341
|
+
a bespoke layout — compose the pieces directly:
|
|
342
|
+
|
|
343
|
+
```tsx
|
|
344
|
+
import {
|
|
345
|
+
createMcpAppAdapter,
|
|
346
|
+
createMcpFetch,
|
|
347
|
+
fetchMcpBootstrap,
|
|
348
|
+
McpCheckoutView,
|
|
349
|
+
McpAccountView,
|
|
350
|
+
McpTopupView,
|
|
351
|
+
McpPaywallView,
|
|
352
|
+
McpNudgeView,
|
|
353
|
+
McpViewRouter,
|
|
354
|
+
useStripeProbe,
|
|
355
|
+
} from '@solvapay/react/mcp'
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Every view accepts a `classNames?: McpViewClassNames` partial. Props are
|
|
359
|
+
typed per-view (`McpCheckoutViewProps`, `McpAccountViewProps`,
|
|
360
|
+
`McpTopupViewProps`, `McpPaywallViewProps`, `McpNudgeViewProps`).
|
|
361
|
+
|
|
362
|
+
### Architecture ADR
|
|
363
|
+
|
|
364
|
+
See [`docs/mcp-app-architecture.md`](./docs/mcp-app-architecture.md) for the
|
|
365
|
+
hybrid-API decision record and the three primitive seam fixes
|
|
366
|
+
(`AmountPicker emit="minor"` / `selector={…}`, `LaunchCustomerPortalButton asChild`)
|
|
367
|
+
that landed with this lift.
|
|
368
|
+
|
|
228
369
|
## Components
|
|
229
370
|
|
|
230
371
|
### SolvaPayProvider
|
|
@@ -371,14 +512,42 @@ Access purchase status, active purchases, and helper functions.
|
|
|
371
512
|
|
|
372
513
|
```tsx
|
|
373
514
|
const {
|
|
374
|
-
purchases, // Array of all purchases
|
|
515
|
+
purchases, // Array of all purchases (raw — plans + balance transactions)
|
|
516
|
+
balanceTransactions, // Credit top-ups and other non-plan rows
|
|
375
517
|
loading, // Loading state
|
|
376
|
-
hasPaidPurchase, // Boolean: has any paid purchase
|
|
377
|
-
activePurchase, // Most recent active purchase
|
|
518
|
+
hasPaidPurchase, // Boolean: has any paid plan purchase
|
|
519
|
+
activePurchase, // Most recent active plan purchase
|
|
378
520
|
refetch, // Function to refetch purchases
|
|
379
521
|
} = usePurchase()
|
|
380
522
|
```
|
|
381
523
|
|
|
524
|
+
#### Plans vs balance
|
|
525
|
+
|
|
526
|
+
Plans and balance credits are orthogonal. A customer can hold at most one
|
|
527
|
+
active plan and any number of balance transactions (credit top-ups today; gift
|
|
528
|
+
credits, referral bonuses, refunds tomorrow).
|
|
529
|
+
|
|
530
|
+
Every plan-shaped accessor — `activePurchase`, `activePaidPurchase`,
|
|
531
|
+
`hasPaidPurchase`, `hasProduct`, plus `cancelledPurchase` /
|
|
532
|
+
`shouldShowCancelledNotice` on `usePurchaseStatus` — filters out balance
|
|
533
|
+
transactions. Top-ups surface on `balanceTransactions`, and the aggregate
|
|
534
|
+
credit balance on `useBalance()`.
|
|
535
|
+
|
|
536
|
+
Classification is structural (`isPlanPurchase` / `isTopupPurchase`): a purchase
|
|
537
|
+
with no `planSnapshot` was never a plan. `metadata.purpose === 'credit_topup'`
|
|
538
|
+
acts as a defense-in-depth guard.
|
|
539
|
+
|
|
540
|
+
```tsx
|
|
541
|
+
import { isPlanPurchase, isTopupPurchase } from '@solvapay/react'
|
|
542
|
+
|
|
543
|
+
const plans = purchases.filter(isPlanPurchase)
|
|
544
|
+
const topups = purchases.filter(isTopupPurchase)
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
If you previously relied on top-ups surfacing on `activePurchase` (e.g. "show
|
|
548
|
+
the most recent transaction"), read from `purchases` or `balanceTransactions`
|
|
549
|
+
directly.
|
|
550
|
+
|
|
382
551
|
### usePlans
|
|
383
552
|
|
|
384
553
|
Fetch and manage available plans.
|
|
@@ -534,7 +703,7 @@ Lovable-style apps deploy React frontends against Supabase Edge Functions
|
|
|
534
703
|
// …same pattern for every other endpoint
|
|
535
704
|
},
|
|
536
705
|
auth: {
|
|
537
|
-
adapter: createSupabaseAuthAdapter({
|
|
706
|
+
adapter: createSupabaseAuthAdapter({ client: supabase }),
|
|
538
707
|
},
|
|
539
708
|
}}
|
|
540
709
|
>
|
|
@@ -547,7 +716,7 @@ Lovable-style apps deploy React frontends against Supabase Edge Functions
|
|
|
547
716
|
```
|
|
548
717
|
|
|
549
718
|
The matching Deno edge functions ship in
|
|
550
|
-
[`@solvapay/
|
|
719
|
+
[`@solvapay/fetch`](../../packages/fetch) — see
|
|
551
720
|
[`examples/supabase-edge`](../../examples/supabase-edge) for one-liner
|
|
552
721
|
`Deno.serve(handler)` files covering every handler including the new
|
|
553
722
|
`get-merchant` and `get-product` endpoints.
|
package/dist/adapters/auth.d.cts
CHANGED
|
@@ -32,6 +32,19 @@ interface AuthAdapter {
|
|
|
32
32
|
* return null and let the caller decide how to handle unauthenticated requests.
|
|
33
33
|
*/
|
|
34
34
|
getUserId: () => Promise<string | null>;
|
|
35
|
+
/**
|
|
36
|
+
* Subscribe to auth-state changes. Optional.
|
|
37
|
+
*
|
|
38
|
+
* When provided, `SolvaPayProvider` calls `subscribe` on mount and
|
|
39
|
+
* triggers `detectAuth` every time the listener fires instead of
|
|
40
|
+
* only running `detectAuth` once. This is how reactive adapters such
|
|
41
|
+
* as `@solvapay/react-supabase` push sign-in / sign-out / token-refresh
|
|
42
|
+
* events into the SDK without the app having to reload.
|
|
43
|
+
*
|
|
44
|
+
* @param listener - Called with no arguments whenever auth state changes
|
|
45
|
+
* @returns An unsubscribe function the provider calls on cleanup
|
|
46
|
+
*/
|
|
47
|
+
subscribe?: (listener: () => void) => () => void;
|
|
35
48
|
}
|
|
36
49
|
/**
|
|
37
50
|
* Default auth adapter that only checks localStorage
|
package/dist/adapters/auth.d.ts
CHANGED
|
@@ -32,6 +32,19 @@ interface AuthAdapter {
|
|
|
32
32
|
* return null and let the caller decide how to handle unauthenticated requests.
|
|
33
33
|
*/
|
|
34
34
|
getUserId: () => Promise<string | null>;
|
|
35
|
+
/**
|
|
36
|
+
* Subscribe to auth-state changes. Optional.
|
|
37
|
+
*
|
|
38
|
+
* When provided, `SolvaPayProvider` calls `subscribe` on mount and
|
|
39
|
+
* triggers `detectAuth` every time the listener fires instead of
|
|
40
|
+
* only running `detectAuth` once. This is how reactive adapters such
|
|
41
|
+
* as `@solvapay/react-supabase` push sign-in / sign-out / token-refresh
|
|
42
|
+
* events into the SDK without the app having to reload.
|
|
43
|
+
*
|
|
44
|
+
* @param listener - Called with no arguments whenever auth state changes
|
|
45
|
+
* @returns An unsubscribe function the provider calls on cleanup
|
|
46
|
+
*/
|
|
47
|
+
subscribe?: (listener: () => void) => () => void;
|
|
35
48
|
}
|
|
36
49
|
/**
|
|
37
50
|
* Default auth adapter that only checks localStorage
|