@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 +28 -3
- package/dist/index.js +32 -32
- package/dist/index.mjs +32 -32
- package/package.json +25 -6
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(
|
|
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(
|
|
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(
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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.
|
|
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": [
|
|
16
|
-
|
|
17
|
-
|
|
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": {
|
|
27
|
-
|
|
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"
|