@fragmentpay/react 0.3.1 → 0.3.4

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
@@ -11,9 +11,9 @@ npm install @fragmentpay/react
11
11
 
12
12
  ## Required environment variables
13
13
 
14
- | Name | Where | Example |
15
- | ----------------------------- | ----------------- | ---------------------------------------- |
16
- | `NEXT_PUBLIC_FRAG_PUBLIC_KEY` | browser (public) | `pk_live_…` or `pk_test_…` |
14
+ | Name | Where | Example |
15
+ | ----------------------------- | ----------------- | ------------------------------------------- |
16
+ | `NEXT_PUBLIC_FRAG_PUBLIC_KEY` | browser (public) | `pk_live_…` or `pk_test_…` |
17
17
  | `FRAG_SECRET_KEY` | your backend only | `sk_live_…` (used by `@fragmentpay/server`) |
18
18
 
19
19
  The React SDK never touches `sk_…`. Intent creation happens on your server
@@ -81,6 +81,30 @@ Behind the scenes: click → POST to your `/api/frag` → returns intent →
81
81
  embed Frag checkout iframe → live-poll status → invoke `onSettled` when
82
82
  `status === "settled"`.
83
83
 
84
+ ## Embed vs redirect
85
+
86
+ ```tsx
87
+ // Embed (default) — hosted checkout iframe with post-message events
88
+ <FragmentPayCheckout intentId={id} onSettled={onDone} onError={onErr} />
89
+
90
+ // Redirect — full-page navigation to hosted checkout
91
+ <FragmentPayCheckout intentId={id} mode="redirect" />
92
+
93
+ // Or just point the user at the hosted URL manually:
94
+ window.location.href = intent.checkout_url;
95
+ ```
96
+
97
+ ## Polling with `useFragmentIntent`
98
+
99
+ ```tsx
100
+ const { data, status } = useFragmentIntent(intentId, { intervalMs: 3000 });
101
+ // data.status: created → quoted → executing → settled | failed | expired
102
+ ```
103
+
104
+ Polling uses exponential backoff on 5xx / network errors and honors any
105
+ `Retry-After` header the API returns; on the terminal states (`settled`,
106
+ `failed`, `expired`, `cancelled`, `refunded`) polling stops automatically.
107
+
84
108
  ## Validation
85
109
 
86
110
  Wrong props fail immediately with a clear message:
@@ -98,6 +122,7 @@ Wrong props fail immediately with a clear message:
98
122
  - `useFragmentIntent(intentId, { intervalMs })`
99
123
  - `useFragmentTokens({ chain })`
100
124
  - `useCreateIntent({ endpoint, transform, headers })`
125
+ - `fetchWithRetry(url, init)` — exported for advanced integrators
101
126
 
102
127
  ## License
103
128
 
package/dist/index.js CHANGED
@@ -44,7 +44,9 @@ var import_jsx_runtime = require("react/jsx-runtime");
44
44
  var FragContext = React.createContext(null);
45
45
  function FragmentPayProvider({ publicKey, baseUrl, children }) {
46
46
  if (typeof publicKey !== "string" || !/^pk_(test|live)_[A-Za-z0-9_-]{8,}$/.test(publicKey)) {
47
- throw new Error("[fragmentpay] publicKey must match pk_test_\u2026 or pk_live_\u2026 (min 8 chars after prefix)");
47
+ throw new Error(
48
+ "[fragmentpay] publicKey must match pk_test_\u2026 or pk_live_\u2026 (min 8 chars after prefix)"
49
+ );
48
50
  }
49
51
  if (baseUrl !== void 0) {
50
52
  try {
@@ -143,7 +145,9 @@ function useFragmentIntent(intentId, opts = {}) {
143
145
  const data = await res.json();
144
146
  if (!alive) return;
145
147
  setState({ data, status: "ready", error: null });
146
- const terminal = ["settled", "failed", "expired", "cancelled", "refunded"].includes(data.status);
148
+ const terminal = ["settled", "failed", "expired", "cancelled", "refunded"].includes(
149
+ data.status
150
+ );
147
151
  if (!terminal) timer = setTimeout(tick, interval);
148
152
  } catch (e) {
149
153
  if (!alive) return;
@@ -235,29 +239,32 @@ function useCreateIntent(opts) {
235
239
  throw new Error("[fragmentpay] useCreateIntent: `endpoint` is required");
236
240
  }
237
241
  const [state, setState] = React.useState({ intent: null, loading: false, error: null });
238
- const mutate = React.useCallback(async (input) => {
239
- setState({ intent: null, loading: true, error: null });
240
- try {
241
- const body = opts.transform ? opts.transform(input) : input;
242
- const res = await fetchWithRetry(opts.endpoint, {
243
- method: "POST",
244
- headers: { "content-type": "application/json", ...opts.headers ?? {} },
245
- body: JSON.stringify(body),
246
- maxRetries: 3
247
- });
248
- if (!res.ok) {
249
- const retryAfter = res.headers.get("retry-after");
250
- const suffix = res.status === 429 && retryAfter ? ` (retry after ${retryAfter}s)` : "";
251
- throw new Error(`HTTP ${res.status}${suffix}: ${await res.text()}`);
242
+ const mutate = React.useCallback(
243
+ async (input) => {
244
+ setState({ intent: null, loading: true, error: null });
245
+ try {
246
+ const body = opts.transform ? opts.transform(input) : input;
247
+ const res = await fetchWithRetry(opts.endpoint, {
248
+ method: "POST",
249
+ headers: { "content-type": "application/json", ...opts.headers ?? {} },
250
+ body: JSON.stringify(body),
251
+ maxRetries: 3
252
+ });
253
+ if (!res.ok) {
254
+ const retryAfter = res.headers.get("retry-after");
255
+ const suffix = res.status === 429 && retryAfter ? ` (retry after ${retryAfter}s)` : "";
256
+ throw new Error(`HTTP ${res.status}${suffix}: ${await res.text()}`);
257
+ }
258
+ const intent = await res.json();
259
+ setState({ intent, loading: false, error: null });
260
+ return intent;
261
+ } catch (e) {
262
+ setState({ intent: null, loading: false, error: e.message });
263
+ throw e;
252
264
  }
253
- const intent = await res.json();
254
- setState({ intent, loading: false, error: null });
255
- return intent;
256
- } catch (e) {
257
- setState({ intent: null, loading: false, error: e.message });
258
- throw e;
259
- }
260
- }, [opts.endpoint, opts.transform, opts.headers]);
265
+ },
266
+ [opts.endpoint, opts.transform, opts.headers]
267
+ );
261
268
  return { ...state, mutate };
262
269
  }
263
270
  function FragmentPayButton({
@@ -283,14 +290,7 @@ function FragmentPayButton({
283
290
  }
284
291
  ),
285
292
  error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { role: "alert", style: { color: "crimson", fontSize: 12 }, children: error }),
286
- intent?.id && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
287
- FragmentPayCheckout,
288
- {
289
- intentId: intent.id,
290
- onSettled,
291
- onError
292
- }
293
- )
293
+ intent?.id && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FragmentPayCheckout, { intentId: intent.id, onSettled, onError })
294
294
  ] });
295
295
  }
296
296
  // Annotate the CommonJS export names for ESM import in node:
package/dist/index.mjs CHANGED
@@ -4,7 +4,9 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4
4
  var FragContext = React.createContext(null);
5
5
  function FragmentPayProvider({ publicKey, baseUrl, children }) {
6
6
  if (typeof publicKey !== "string" || !/^pk_(test|live)_[A-Za-z0-9_-]{8,}$/.test(publicKey)) {
7
- throw new Error("[fragmentpay] publicKey must match pk_test_\u2026 or pk_live_\u2026 (min 8 chars after prefix)");
7
+ throw new Error(
8
+ "[fragmentpay] publicKey must match pk_test_\u2026 or pk_live_\u2026 (min 8 chars after prefix)"
9
+ );
8
10
  }
9
11
  if (baseUrl !== void 0) {
10
12
  try {
@@ -103,7 +105,9 @@ function useFragmentIntent(intentId, opts = {}) {
103
105
  const data = await res.json();
104
106
  if (!alive) return;
105
107
  setState({ data, status: "ready", error: null });
106
- const terminal = ["settled", "failed", "expired", "cancelled", "refunded"].includes(data.status);
108
+ const terminal = ["settled", "failed", "expired", "cancelled", "refunded"].includes(
109
+ data.status
110
+ );
107
111
  if (!terminal) timer = setTimeout(tick, interval);
108
112
  } catch (e) {
109
113
  if (!alive) return;
@@ -195,29 +199,32 @@ function useCreateIntent(opts) {
195
199
  throw new Error("[fragmentpay] useCreateIntent: `endpoint` is required");
196
200
  }
197
201
  const [state, setState] = React.useState({ intent: null, loading: false, error: null });
198
- const mutate = React.useCallback(async (input) => {
199
- setState({ intent: null, loading: true, error: null });
200
- try {
201
- const body = opts.transform ? opts.transform(input) : input;
202
- const res = await fetchWithRetry(opts.endpoint, {
203
- method: "POST",
204
- headers: { "content-type": "application/json", ...opts.headers ?? {} },
205
- body: JSON.stringify(body),
206
- maxRetries: 3
207
- });
208
- if (!res.ok) {
209
- const retryAfter = res.headers.get("retry-after");
210
- const suffix = res.status === 429 && retryAfter ? ` (retry after ${retryAfter}s)` : "";
211
- throw new Error(`HTTP ${res.status}${suffix}: ${await res.text()}`);
202
+ const mutate = React.useCallback(
203
+ async (input) => {
204
+ setState({ intent: null, loading: true, error: null });
205
+ try {
206
+ const body = opts.transform ? opts.transform(input) : input;
207
+ const res = await fetchWithRetry(opts.endpoint, {
208
+ method: "POST",
209
+ headers: { "content-type": "application/json", ...opts.headers ?? {} },
210
+ body: JSON.stringify(body),
211
+ maxRetries: 3
212
+ });
213
+ if (!res.ok) {
214
+ const retryAfter = res.headers.get("retry-after");
215
+ const suffix = res.status === 429 && retryAfter ? ` (retry after ${retryAfter}s)` : "";
216
+ throw new Error(`HTTP ${res.status}${suffix}: ${await res.text()}`);
217
+ }
218
+ const intent = await res.json();
219
+ setState({ intent, loading: false, error: null });
220
+ return intent;
221
+ } catch (e) {
222
+ setState({ intent: null, loading: false, error: e.message });
223
+ throw e;
212
224
  }
213
- const intent = await res.json();
214
- setState({ intent, loading: false, error: null });
215
- return intent;
216
- } catch (e) {
217
- setState({ intent: null, loading: false, error: e.message });
218
- throw e;
219
- }
220
- }, [opts.endpoint, opts.transform, opts.headers]);
225
+ },
226
+ [opts.endpoint, opts.transform, opts.headers]
227
+ );
221
228
  return { ...state, mutate };
222
229
  }
223
230
  function FragmentPayButton({
@@ -243,14 +250,7 @@ function FragmentPayButton({
243
250
  }
244
251
  ),
245
252
  error && /* @__PURE__ */ jsx("div", { role: "alert", style: { color: "crimson", fontSize: 12 }, children: error }),
246
- intent?.id && /* @__PURE__ */ jsx(
247
- FragmentPayCheckout,
248
- {
249
- intentId: intent.id,
250
- onSettled,
251
- onError
252
- }
253
- )
253
+ intent?.id && /* @__PURE__ */ jsx(FragmentPayCheckout, { intentId: intent.id, onSettled, onError })
254
254
  ] });
255
255
  }
256
256
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fragmentpay/react",
3
- "version": "0.3.1",
3
+ "version": "0.3.4",
4
4
  "description": "React components for Frag hosted checkout — accept any token, on any chain.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -12,9 +12,23 @@
12
12
  "require": "./dist/index.js"
13
13
  }
14
14
  },
15
- "files": ["dist", "README.md", "LICENSE"],
16
- "keywords": ["crypto", "payments", "react", "checkout", "fragmentpay", "frag"],
17
- "peerDependencies": { "react": ">=18", "react-dom": ">=18" },
15
+ "files": [
16
+ "dist",
17
+ "README.md",
18
+ "LICENSE"
19
+ ],
20
+ "keywords": [
21
+ "crypto",
22
+ "payments",
23
+ "react",
24
+ "checkout",
25
+ "fragmentpay",
26
+ "frag"
27
+ ],
28
+ "peerDependencies": {
29
+ "react": ">=18",
30
+ "react-dom": ">=18"
31
+ },
18
32
  "license": "MIT",
19
33
  "sideEffects": false,
20
34
  "repository": {
@@ -23,8 +37,13 @@
23
37
  "directory": "sdk/frag-react"
24
38
  },
25
39
  "homepage": "https://github.com/fragmentpay/fragpay#readme",
26
- "bugs": { "url": "https://github.com/fragmentpay/fragpay/issues" },
27
- "publishConfig": { "access": "public", "provenance": true },
40
+ "bugs": {
41
+ "url": "https://github.com/fragmentpay/fragpay/issues"
42
+ },
43
+ "publishConfig": {
44
+ "access": "public",
45
+ "provenance": true
46
+ },
28
47
  "scripts": {
29
48
  "build": "tsup src/index.tsx --format esm,cjs --dts --clean",
30
49
  "prepublishOnly": "npm run build"