@molecule/app-react 1.0.3 → 1.1.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/README.md CHANGED
@@ -3,7 +3,7 @@ AUTO-GENERATED — DO NOT EDIT THIS FILE.
3
3
  Generated by `mlcl sync-docs` from the package's src/index.ts JSDoc + mlcl/registry.json.
4
4
  Edits here are overwritten on the next commit (molecule's pre-commit hook regenerates).
5
5
  To change this document, edit the module-level JSDoc in src/index.ts.
6
- Generated: 2026-08-04T01:52:26.674Z
6
+ Generated: 2026-08-15T19:34:35.768Z
7
7
  -->
8
8
 
9
9
  # @molecule/app-react
@@ -1364,6 +1364,66 @@ interface UseTranslationResult {
1364
1364
  }
1365
1365
  ```
1366
1366
 
1367
+ #### `UseVerifyPaymentReturnOptions`
1368
+
1369
+ Options for {@link useVerifyPaymentReturn}.
1370
+
1371
+ ```typescript
1372
+ interface UseVerifyPaymentReturnOptions {
1373
+ /**
1374
+ * Provider name to verify with, when the return URL carries no `provider`
1375
+ * query parameter. Leave unset to verify only what the URL names.
1376
+ */
1377
+ provider?: string
1378
+
1379
+ /** Set `false` to skip verification entirely (e.g. behind a feature flag). */
1380
+ enabled?: boolean
1381
+
1382
+ /**
1383
+ * Remove the provider's query parameters from the address bar once the
1384
+ * purchase is verified, so a reload (or a shared link) cannot replay the
1385
+ * verification. Defaults to `true`.
1386
+ */
1387
+ cleanUrl?: boolean
1388
+
1389
+ /** Called once, after the purchase verifies. */
1390
+ onVerified?: () => void
1391
+
1392
+ /** Called when verification fails. */
1393
+ onError?: (error: Error) => void
1394
+ }
1395
+ ```
1396
+
1397
+ #### `UseVerifyPaymentReturnResult`
1398
+
1399
+ State of the post-checkout verification.
1400
+
1401
+ ```typescript
1402
+ interface UseVerifyPaymentReturnResult {
1403
+ /**
1404
+ * `idle` when this page load is not a checkout return (no transaction id in
1405
+ * the URL) or auth is still hydrating; `verifying` while the API call is in
1406
+ * flight; then `verified` or `failed`.
1407
+ */
1408
+ status: VerifyPaymentReturnStatus
1409
+
1410
+ /** `true` when the URL identifies a purchase to verify. */
1411
+ isReturn: boolean
1412
+
1413
+ /** The provider named by the URL (or the `provider` option). */
1414
+ provider: string | null
1415
+
1416
+ /** The provider transaction/session id read from the URL. */
1417
+ transactionId: string | null
1418
+
1419
+ /** Why verification failed, when it did. */
1420
+ error: Error | null
1421
+
1422
+ /** Re-run the verification — wire this to a retry button. */
1423
+ retry: () => void
1424
+ }
1425
+ ```
1426
+
1367
1427
  #### `UseVersionResult`
1368
1428
 
1369
1429
  Hook return type.
@@ -1439,6 +1499,14 @@ type UseCapacitorAppResult = CapacitorAppState & {
1439
1499
  }
1440
1500
  ```
1441
1501
 
1502
+ #### `VerifyPaymentReturnStatus`
1503
+
1504
+ How far the return-page verification has got.
1505
+
1506
+ ```typescript
1507
+ type VerifyPaymentReturnStatus = 'idle' | 'verifying' | 'verified' | 'failed'
1508
+ ```
1509
+
1442
1510
  ### Functions
1443
1511
 
1444
1512
  #### `AuthProvider(props)`
@@ -2382,6 +2450,35 @@ function useUser(): T | null
2382
2450
 
2383
2451
  **Returns:** The authenticated user or null
2384
2452
 
2453
+ #### `useVerifyPaymentReturn(options)`
2454
+
2455
+ Reads the payment id a provider put in the return URL and confirms the
2456
+ purchase server-side with `POST /users/:id/verify-payment/:provider`.
2457
+
2458
+ **This is why a hosted checkout returns the buyer to the APP and not the
2459
+ API.** Session cookies are host-only on the app's origin, so a top-level
2460
+ redirect from the provider straight to an authenticated API callback on a
2461
+ different host arrives with NO credentials — it answers 401 and the paid plan
2462
+ is never granted. The request this hook makes is a same-origin call from a
2463
+ loaded app page, so the credentials apply. It waits for auth to hydrate
2464
+ first: after the redirect the page is a cold load, and the session is
2465
+ restored from the httpOnly cookie via `GET /users/me`.
2466
+
2467
+ Verification is idempotent server-side (first-claim-wins on the transaction),
2468
+ and this hook additionally runs at most once per transaction id per page.
2469
+ Safe on pages that are also reached normally: with no id in the URL it stays
2470
+ `idle` and issues no request.
2471
+
2472
+ ```typescript
2473
+ function useVerifyPaymentReturn(
2474
+ options?: UseVerifyPaymentReturnOptions,
2475
+ ): UseVerifyPaymentReturnResult
2476
+ ```
2477
+
2478
+ - `options` — Provider fallback + lifecycle callbacks (see {@link UseVerifyPaymentReturnOptions}).
2479
+
2480
+ **Returns:** The verification state (see {@link UseVerifyPaymentReturnResult}).
2481
+
2385
2482
  #### `useVersion()`
2386
2483
 
2387
2484
  Hook for version state and update actions.
@@ -2635,6 +2732,14 @@ Peer dependencies:
2635
2732
  mounted). Scaffolded Vite configs ship
2636
2733
  `resolve.dedupe: ['react', 'react-dom', 'react-router', 'react-router']` — keep it, and
2637
2734
  add any new hook-bearing peer library there too.
2735
+ - **A payment provider's post-checkout redirect must land on the APP, and the page it
2736
+ lands on has to finish the purchase.** `useVerifyPaymentReturn()` reads the id the
2737
+ provider left in the query and confirms it with
2738
+ `POST /users/:id/verify-payment/:provider` — a same-origin call, so the session cookie
2739
+ applies. Redirecting straight to that API route from the provider's domain sends a
2740
+ top-level navigation with NO credentials: it answers 401 and the paid plan is never
2741
+ granted. The shipped confirmation pages (`@molecule/app-plan-updated-page-react`,
2742
+ `@molecule/app-legal-pages-react`) already call it.
2638
2743
  - `RouterProvider` carries a molecule `Router` (e.g. `createReactRouter()` from
2639
2744
  `@molecule/app-routing-react-router`). react-router's own `<BrowserRouter>` context is
2640
2745
  separate — components that render react-router `<Link>` (several in
@@ -27,6 +27,7 @@ export * from './useStorage.js';
27
27
  export * from './useStore.js';
28
28
  export * from './useTheme.js';
29
29
  export * from './useTranslation.js';
30
+ export * from './useVerifyPaymentReturn.js';
30
31
  export * from './useVersion.js';
31
32
  export * from './useWorkspace.js';
32
33
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,cAAc,kBAAkB,CAAA;AAChC,cAAc,oBAAoB,CAAA;AAClC,cAAc,cAAc,CAAA;AAC5B,cAAc,sBAAsB,CAAA;AACpC,cAAc,wBAAwB,CAAA;AACtC,cAAc,cAAc,CAAA;AAC5B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,cAAc,CAAA;AAC5B,cAAc,cAAc,CAAA;AAC5B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,eAAe,CAAA;AAC7B,cAAc,eAAe,CAAA;AAC7B,cAAc,uBAAuB,CAAA;AACrC,cAAc,kBAAkB,CAAA;AAChC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,cAAc,CAAA;AAC5B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,eAAe,CAAA;AAC7B,cAAc,eAAe,CAAA;AAC7B,cAAc,qBAAqB,CAAA;AACnC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,mBAAmB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,cAAc,kBAAkB,CAAA;AAChC,cAAc,oBAAoB,CAAA;AAClC,cAAc,cAAc,CAAA;AAC5B,cAAc,sBAAsB,CAAA;AACpC,cAAc,wBAAwB,CAAA;AACtC,cAAc,cAAc,CAAA;AAC5B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,cAAc,CAAA;AAC5B,cAAc,cAAc,CAAA;AAC5B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,eAAe,CAAA;AAC7B,cAAc,eAAe,CAAA;AAC7B,cAAc,uBAAuB,CAAA;AACrC,cAAc,kBAAkB,CAAA;AAChC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,cAAc,CAAA;AAC5B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,eAAe,CAAA;AAC7B,cAAc,eAAe,CAAA;AAC7B,cAAc,qBAAqB,CAAA;AACnC,cAAc,6BAA6B,CAAA;AAC3C,cAAc,iBAAiB,CAAA;AAC/B,cAAc,mBAAmB,CAAA"}
@@ -27,6 +27,7 @@ export * from './useStorage.js';
27
27
  export * from './useStore.js';
28
28
  export * from './useTheme.js';
29
29
  export * from './useTranslation.js';
30
+ export * from './useVerifyPaymentReturn.js';
30
31
  export * from './useVersion.js';
31
32
  export * from './useWorkspace.js';
32
33
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,cAAc,kBAAkB,CAAA;AAChC,cAAc,oBAAoB,CAAA;AAClC,cAAc,cAAc,CAAA;AAC5B,cAAc,sBAAsB,CAAA;AACpC,cAAc,wBAAwB,CAAA;AACtC,cAAc,cAAc,CAAA;AAC5B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,cAAc,CAAA;AAC5B,cAAc,cAAc,CAAA;AAC5B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,eAAe,CAAA;AAC7B,cAAc,eAAe,CAAA;AAC7B,cAAc,uBAAuB,CAAA;AACrC,cAAc,kBAAkB,CAAA;AAChC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,cAAc,CAAA;AAC5B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,eAAe,CAAA;AAC7B,cAAc,eAAe,CAAA;AAC7B,cAAc,qBAAqB,CAAA;AACnC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,mBAAmB,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,cAAc,kBAAkB,CAAA;AAChC,cAAc,oBAAoB,CAAA;AAClC,cAAc,cAAc,CAAA;AAC5B,cAAc,sBAAsB,CAAA;AACpC,cAAc,wBAAwB,CAAA;AACtC,cAAc,cAAc,CAAA;AAC5B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,cAAc,CAAA;AAC5B,cAAc,cAAc,CAAA;AAC5B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,eAAe,CAAA;AAC7B,cAAc,eAAe,CAAA;AAC7B,cAAc,uBAAuB,CAAA;AACrC,cAAc,kBAAkB,CAAA;AAChC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,cAAc,CAAA;AAC5B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,eAAe,CAAA;AAC7B,cAAc,eAAe,CAAA;AAC7B,cAAc,qBAAqB,CAAA;AACnC,cAAc,6BAA6B,CAAA;AAC3C,cAAc,iBAAiB,CAAA;AAC/B,cAAc,mBAAmB,CAAA"}
@@ -0,0 +1,81 @@
1
+ /**
2
+ * React hook for the post-checkout return: verify the purchase with the API
3
+ * from the APP origin, where the session cookie applies.
4
+ *
5
+ * @module
6
+ */
7
+ /** How far the return-page verification has got. */
8
+ export type VerifyPaymentReturnStatus = 'idle' | 'verifying' | 'verified' | 'failed';
9
+ /** Options for {@link useVerifyPaymentReturn}. */
10
+ export interface UseVerifyPaymentReturnOptions {
11
+ /**
12
+ * Provider name to verify with, when the return URL carries no `provider`
13
+ * query parameter. Leave unset to verify only what the URL names.
14
+ */
15
+ provider?: string;
16
+ /** Set `false` to skip verification entirely (e.g. behind a feature flag). */
17
+ enabled?: boolean;
18
+ /**
19
+ * Remove the provider's query parameters from the address bar once the
20
+ * purchase is verified, so a reload (or a shared link) cannot replay the
21
+ * verification. Defaults to `true`.
22
+ */
23
+ cleanUrl?: boolean;
24
+ /** Called once, after the purchase verifies. */
25
+ onVerified?: () => void;
26
+ /** Called when verification fails. */
27
+ onError?: (error: Error) => void;
28
+ }
29
+ /** State of the post-checkout verification. */
30
+ export interface UseVerifyPaymentReturnResult {
31
+ /**
32
+ * `idle` when this page load is not a checkout return (no transaction id in
33
+ * the URL) or auth is still hydrating; `verifying` while the API call is in
34
+ * flight; then `verified` or `failed`.
35
+ */
36
+ status: VerifyPaymentReturnStatus;
37
+ /** `true` when the URL identifies a purchase to verify. */
38
+ isReturn: boolean;
39
+ /** The provider named by the URL (or the `provider` option). */
40
+ provider: string | null;
41
+ /** The provider transaction/session id read from the URL. */
42
+ transactionId: string | null;
43
+ /** Why verification failed, when it did. */
44
+ error: Error | null;
45
+ /** Re-run the verification — wire this to a retry button. */
46
+ retry: () => void;
47
+ }
48
+ /**
49
+ * Reads the payment id a provider put in the return URL and confirms the
50
+ * purchase server-side with `POST /users/:id/verify-payment/:provider`.
51
+ *
52
+ * **This is why a hosted checkout returns the buyer to the APP and not the
53
+ * API.** Session cookies are host-only on the app's origin, so a top-level
54
+ * redirect from the provider straight to an authenticated API callback on a
55
+ * different host arrives with NO credentials — it answers 401 and the paid plan
56
+ * is never granted. The request this hook makes is a same-origin call from a
57
+ * loaded app page, so the credentials apply. It waits for auth to hydrate
58
+ * first: after the redirect the page is a cold load, and the session is
59
+ * restored from the httpOnly cookie via `GET /users/me`.
60
+ *
61
+ * Verification is idempotent server-side (first-claim-wins on the transaction),
62
+ * and this hook additionally runs at most once per transaction id per page.
63
+ * Safe on pages that are also reached normally: with no id in the URL it stays
64
+ * `idle` and issues no request.
65
+ *
66
+ * @param options - Provider fallback + lifecycle callbacks (see
67
+ * {@link UseVerifyPaymentReturnOptions}).
68
+ * @returns The verification state (see {@link UseVerifyPaymentReturnResult}).
69
+ *
70
+ * @example
71
+ * ```tsx
72
+ * // /plan-updated — the page a provider returns the buyer to.
73
+ * const { status, retry } = useVerifyPaymentReturn()
74
+ *
75
+ * if (status === 'verifying') return <Spinner />
76
+ * if (status === 'failed') return <button onClick={retry}>Try again</button>
77
+ * return <h1>Thank you!</h1>
78
+ * ```
79
+ */
80
+ export declare function useVerifyPaymentReturn(options?: UseVerifyPaymentReturnOptions): UseVerifyPaymentReturnResult;
81
+ //# sourceMappingURL=useVerifyPaymentReturn.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useVerifyPaymentReturn.d.ts","sourceRoot":"","sources":["../../src/hooks/useVerifyPaymentReturn.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAqBH,oDAAoD;AACpD,MAAM,MAAM,yBAAyB,GAAG,MAAM,GAAG,WAAW,GAAG,UAAU,GAAG,QAAQ,CAAA;AAEpF,kDAAkD;AAClD,MAAM,WAAW,6BAA6B;IAC5C;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IAEjB,8EAA8E;IAC9E,OAAO,CAAC,EAAE,OAAO,CAAA;IAEjB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAElB,gDAAgD;IAChD,UAAU,CAAC,EAAE,MAAM,IAAI,CAAA;IAEvB,sCAAsC;IACtC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAA;CACjC;AAED,+CAA+C;AAC/C,MAAM,WAAW,4BAA4B;IAC3C;;;;OAIG;IACH,MAAM,EAAE,yBAAyB,CAAA;IAEjC,2DAA2D;IAC3D,QAAQ,EAAE,OAAO,CAAA;IAEjB,gEAAgE;IAChE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IAEvB,6DAA6D;IAC7D,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;IAE5B,4CAA4C;IAC5C,KAAK,EAAE,KAAK,GAAG,IAAI,CAAA;IAEnB,6DAA6D;IAC7D,KAAK,EAAE,MAAM,IAAI,CAAA;CAClB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,GAAE,6BAAkC,GAC1C,4BAA4B,CA0E9B"}
@@ -0,0 +1,119 @@
1
+ /**
2
+ * React hook for the post-checkout return: verify the purchase with the API
3
+ * from the APP origin, where the session cookie applies.
4
+ *
5
+ * @module
6
+ */
7
+ import { useCallback, useEffect, useRef, useState } from 'react';
8
+ import { useAuth } from './useAuth.js';
9
+ import { useHttpClient } from './useHttp.js';
10
+ /** Query parameters a payment provider may use for its transaction id. */
11
+ const TRANSACTION_ID_PARAMS = [
12
+ // What `resolveCheckoutRedirectUrls` (@molecule/api-payments) asks providers
13
+ // to substitute — Stripe's `{CHECKOUT_SESSION_ID}`.
14
+ 'sessionId',
15
+ 'session_id',
16
+ 'subscriptionId',
17
+ // PayPal appends its OWN names to the return URL after buyer approval.
18
+ 'subscription_id',
19
+ 'token',
20
+ ];
21
+ /**
22
+ * Reads the payment id a provider put in the return URL and confirms the
23
+ * purchase server-side with `POST /users/:id/verify-payment/:provider`.
24
+ *
25
+ * **This is why a hosted checkout returns the buyer to the APP and not the
26
+ * API.** Session cookies are host-only on the app's origin, so a top-level
27
+ * redirect from the provider straight to an authenticated API callback on a
28
+ * different host arrives with NO credentials — it answers 401 and the paid plan
29
+ * is never granted. The request this hook makes is a same-origin call from a
30
+ * loaded app page, so the credentials apply. It waits for auth to hydrate
31
+ * first: after the redirect the page is a cold load, and the session is
32
+ * restored from the httpOnly cookie via `GET /users/me`.
33
+ *
34
+ * Verification is idempotent server-side (first-claim-wins on the transaction),
35
+ * and this hook additionally runs at most once per transaction id per page.
36
+ * Safe on pages that are also reached normally: with no id in the URL it stays
37
+ * `idle` and issues no request.
38
+ *
39
+ * @param options - Provider fallback + lifecycle callbacks (see
40
+ * {@link UseVerifyPaymentReturnOptions}).
41
+ * @returns The verification state (see {@link UseVerifyPaymentReturnResult}).
42
+ *
43
+ * @example
44
+ * ```tsx
45
+ * // /plan-updated — the page a provider returns the buyer to.
46
+ * const { status, retry } = useVerifyPaymentReturn()
47
+ *
48
+ * if (status === 'verifying') return <Spinner />
49
+ * if (status === 'failed') return <button onClick={retry}>Try again</button>
50
+ * return <h1>Thank you!</h1>
51
+ * ```
52
+ */
53
+ export function useVerifyPaymentReturn(options = {}) {
54
+ const { provider: providerOption, enabled = true, cleanUrl = true, onVerified, onError } = options;
55
+ const http = useHttpClient();
56
+ const { state, refresh } = useAuth();
57
+ const search = typeof window === 'undefined' ? '' : window.location.search;
58
+ const params = new URLSearchParams(search);
59
+ const transactionId = TRANSACTION_ID_PARAMS.map((name) => params.get(name)).find((value) => !!value) ?? null;
60
+ const provider = params.get('provider') || providerOption || null;
61
+ const isReturn = Boolean(enabled && transactionId && provider);
62
+ const [status, setStatus] = useState('idle');
63
+ const [error, setError] = useState(null);
64
+ // One attempt per transaction id per page — an effect that re-fires (auth
65
+ // refresh, strict-mode double-invoke) must not re-POST a purchase.
66
+ const attemptedRef = useRef(null);
67
+ // Callbacks are read through a ref so a caller passing inline functions does
68
+ // not re-trigger the effect on every render.
69
+ const callbacksRef = useRef({ onVerified, onError });
70
+ callbacksRef.current = { onVerified, onError };
71
+ const userId = state.user?.id ?? null;
72
+ const verify = useCallback(async () => {
73
+ if (!transactionId || !provider || !userId)
74
+ return;
75
+ setStatus('verifying');
76
+ setError(null);
77
+ try {
78
+ await http.post(`/users/${userId}/verify-payment/${provider}`, {
79
+ subscriptionId: transactionId,
80
+ });
81
+ // The plan on the cached profile is now stale — re-read it so the UI
82
+ // (plan badges, gated features) reflects what was just paid for.
83
+ await refresh().catch(() => { });
84
+ setStatus('verified');
85
+ callbacksRef.current.onVerified?.();
86
+ if (cleanUrl && typeof window !== 'undefined' && window.history?.replaceState) {
87
+ const url = new URL(window.location.href);
88
+ for (const name of TRANSACTION_ID_PARAMS)
89
+ url.searchParams.delete(name);
90
+ url.searchParams.delete('provider');
91
+ window.history.replaceState({}, '', `${url.pathname}${url.search}${url.hash}`);
92
+ }
93
+ }
94
+ catch (caught) {
95
+ const failure = caught instanceof Error ? caught : new Error(String(caught));
96
+ setStatus('failed');
97
+ setError(failure);
98
+ callbacksRef.current.onError?.(failure);
99
+ }
100
+ }, [cleanUrl, http, provider, refresh, transactionId, userId]);
101
+ useEffect(() => {
102
+ if (!isReturn || !transactionId)
103
+ return;
104
+ // Auth is still hydrating from the httpOnly cookie — verifying now would
105
+ // send the request unauthenticated.
106
+ if (!state.initialized || !userId)
107
+ return;
108
+ if (attemptedRef.current === transactionId)
109
+ return;
110
+ attemptedRef.current = transactionId;
111
+ void verify();
112
+ }, [isReturn, state.initialized, transactionId, userId, verify]);
113
+ const retry = useCallback(() => {
114
+ attemptedRef.current = transactionId;
115
+ void verify();
116
+ }, [transactionId, verify]);
117
+ return { status, isReturn, provider, transactionId, error, retry };
118
+ }
119
+ //# sourceMappingURL=useVerifyPaymentReturn.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useVerifyPaymentReturn.js","sourceRoot":"","sources":["../../src/hooks/useVerifyPaymentReturn.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -65,6 +65,14 @@
65
65
  * mounted). Scaffolded Vite configs ship
66
66
  * `resolve.dedupe: ['react', 'react-dom', 'react-router', 'react-router']` — keep it, and
67
67
  * add any new hook-bearing peer library there too.
68
+ * - **A payment provider's post-checkout redirect must land on the APP, and the page it
69
+ * lands on has to finish the purchase.** `useVerifyPaymentReturn()` reads the id the
70
+ * provider left in the query and confirms it with
71
+ * `POST /users/:id/verify-payment/:provider` — a same-origin call, so the session cookie
72
+ * applies. Redirecting straight to that API route from the provider's domain sends a
73
+ * top-level navigation with NO credentials: it answers 401 and the paid plan is never
74
+ * granted. The shipped confirmation pages (`@molecule/app-plan-updated-page-react`,
75
+ * `@molecule/app-legal-pages-react`) already call it.
68
76
  * - `RouterProvider` carries a molecule `Router` (e.g. `createReactRouter()` from
69
77
  * `@molecule/app-routing-react-router`). react-router's own `<BrowserRouter>` context is
70
78
  * separate — components that render react-router `<Link>` (several in
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyEG;AAEH,cAAc,qBAAqB,CAAA;AACnC,cAAc,eAAe,CAAA;AAC7B,cAAc,kBAAkB,CAAA;AAChC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,YAAY,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiFG;AAEH,cAAc,qBAAqB,CAAA;AACnC,cAAc,eAAe,CAAA;AAC7B,cAAc,kBAAkB,CAAA;AAChC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,YAAY,CAAA"}
package/dist/index.js CHANGED
@@ -65,6 +65,14 @@
65
65
  * mounted). Scaffolded Vite configs ship
66
66
  * `resolve.dedupe: ['react', 'react-dom', 'react-router', 'react-router']` — keep it, and
67
67
  * add any new hook-bearing peer library there too.
68
+ * - **A payment provider's post-checkout redirect must land on the APP, and the page it
69
+ * lands on has to finish the purchase.** `useVerifyPaymentReturn()` reads the id the
70
+ * provider left in the query and confirms it with
71
+ * `POST /users/:id/verify-payment/:provider` — a same-origin call, so the session cookie
72
+ * applies. Redirecting straight to that API route from the provider's domain sends a
73
+ * top-level navigation with NO credentials: it answers 401 and the paid plan is never
74
+ * granted. The shipped confirmation pages (`@molecule/app-plan-updated-page-react`,
75
+ * `@molecule/app-legal-pages-react`) already call it.
68
76
  * - `RouterProvider` carries a molecule `Router` (e.g. `createReactRouter()` from
69
77
  * `@molecule/app-routing-react-router`). react-router's own `<BrowserRouter>` context is
70
78
  * separate — components that render react-router `<Link>` (several in
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyEG;AAEH,cAAc,qBAAqB,CAAA;AACnC,cAAc,eAAe,CAAA;AAC7B,cAAc,kBAAkB,CAAA;AAChC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,YAAY,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiFG;AAEH,cAAc,qBAAqB,CAAA;AACnC,cAAc,eAAe,CAAA;AAC7B,cAAc,kBAAkB,CAAA;AAChC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,YAAY,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@molecule/app-react",
3
- "version": "1.0.3",
3
+ "version": "1.1.0",
4
4
  "description": "React framework bindings for molecule.dev",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",