@fragmentpay/server 0.3.0 → 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 +40 -8
- package/dist/index.d.mts +29 -2
- package/dist/index.d.ts +29 -2
- package/dist/index.js +118 -31
- package/dist/index.mjs +118 -31
- package/package.json +26 -6
package/README.md
CHANGED
|
@@ -17,11 +17,11 @@ npm install @fragmentpay/server
|
|
|
17
17
|
|
|
18
18
|
## Required environment variables
|
|
19
19
|
|
|
20
|
-
| Name | Where
|
|
21
|
-
| ------------------------ |
|
|
22
|
-
| `FRAG_SECRET_KEY` | server only
|
|
23
|
-
| `FRAG_WEBHOOK_SECRET` | server only
|
|
24
|
-
| `FRAG_BASE_URL`
|
|
20
|
+
| Name | Where | Example |
|
|
21
|
+
| ------------------------ | ----------- | ------------------------------------------ |
|
|
22
|
+
| `FRAG_SECRET_KEY` | server only | `sk_live_…` or `sk_test_…` |
|
|
23
|
+
| `FRAG_WEBHOOK_SECRET` | server only | value shown when you create the webhook |
|
|
24
|
+
| `FRAG_BASE_URL` _(opt.)_ | server only | override host, default `https://frag.cash` |
|
|
25
25
|
|
|
26
26
|
Never expose `sk_…` in a browser bundle.
|
|
27
27
|
|
|
@@ -46,7 +46,7 @@ export async function startCheckout(orderId: string, cents: number, email: strin
|
|
|
46
46
|
address: process.env.MERCHANT_USDC_ADDRESS!,
|
|
47
47
|
},
|
|
48
48
|
successUrl: "https://shop.example.com/thanks",
|
|
49
|
-
cancelUrl:
|
|
49
|
+
cancelUrl: "https://shop.example.com/cart",
|
|
50
50
|
metadata: { order_id: orderId },
|
|
51
51
|
idempotencyKey: `order_${orderId}`,
|
|
52
52
|
});
|
|
@@ -64,7 +64,7 @@ export async function POST(req: Request) {
|
|
|
64
64
|
const payload = await req.text();
|
|
65
65
|
const event = await verifyWebhook({
|
|
66
66
|
payload,
|
|
67
|
-
signature: req.headers.get("
|
|
67
|
+
signature: req.headers.get("frag-signature"),
|
|
68
68
|
secret: process.env.FRAG_WEBHOOK_SECRET!,
|
|
69
69
|
});
|
|
70
70
|
|
|
@@ -77,6 +77,22 @@ export async function POST(req: Request) {
|
|
|
77
77
|
|
|
78
78
|
**3. React drop-in on the frontend** — see `@fragmentpay/react`.
|
|
79
79
|
|
|
80
|
+
## Advanced options
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
const frag = new Frag({
|
|
84
|
+
secretKey: process.env.FRAG_SECRET_KEY!,
|
|
85
|
+
baseUrl: "https://frag.cash", // default
|
|
86
|
+
timeoutMs: 15_000, // per-request timeout
|
|
87
|
+
maxRetries: 3, // retries on 5xx / network errors with Retry-After honored
|
|
88
|
+
fetch: myCustomFetch, // inject a custom fetch (Workers, tracing, etc.)
|
|
89
|
+
});
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Every method throws `FragError` on non-2xx responses. It carries `status`,
|
|
93
|
+
`code`, `message`, and `requestId` for observability, plus the raw provider
|
|
94
|
+
body when available.
|
|
95
|
+
|
|
80
96
|
## Validation errors
|
|
81
97
|
|
|
82
98
|
Every method validates its arguments and throws a `FragValidationError`
|
|
@@ -89,7 +105,8 @@ frag.paymentIntents.create({ amount: -5 });
|
|
|
89
105
|
|
|
90
106
|
## Public API surface
|
|
91
107
|
|
|
92
|
-
- `frag.paymentIntents.{create, retrieve, list, cancel, transactions,
|
|
108
|
+
- `frag.paymentIntents.{create, retrieve, list, cancel, transactions, routing}`
|
|
109
|
+
- `routing(id)` returns router candidates evaluated for the intent (chosen + rejected with reasons). `route(id)` is kept as a deprecated alias.
|
|
93
110
|
- `frag.refunds.{create, retrieve, list}`
|
|
94
111
|
- `frag.payouts.{retrieve, list, replay}`
|
|
95
112
|
- `frag.webhookEndpoints.{create, list, retrieve, update, delete, rotateSecret}`
|
|
@@ -97,6 +114,21 @@ frag.paymentIntents.create({ amount: -5 });
|
|
|
97
114
|
- `verifyWebhook({ payload, signature, secret })`
|
|
98
115
|
- `FragError` (HTTP + upstream) and `FragValidationError` (input)
|
|
99
116
|
|
|
117
|
+
## Idempotency
|
|
118
|
+
|
|
119
|
+
Every `POST` accepts an optional `Idempotency-Key` header (SDK auto-sends it
|
|
120
|
+
when you set `idempotencyKey`). Replays within 24h with the **same body**
|
|
121
|
+
return the original response; a **different body** returns HTTP `409` with
|
|
122
|
+
`code: "idempotency_conflict"`.
|
|
123
|
+
|
|
124
|
+
## Webhook events
|
|
125
|
+
|
|
126
|
+
The SDK's `WebhookEventType` union stays in sync with the API. Notable events:
|
|
127
|
+
`payment_intent.created`, `payment_intent.quoted`, `payment_intent.executing`,
|
|
128
|
+
`payment_intent.settled`, `payment_intent.failed`, `payment_intent.expired`,
|
|
129
|
+
`payment_intent.cancelled`, `payment_intent.refunded`, `payout.created`,
|
|
130
|
+
`payout.paid`, `payout.failed`, `ping`.
|
|
131
|
+
|
|
100
132
|
## License
|
|
101
133
|
|
|
102
134
|
MIT
|
package/dist/index.d.mts
CHANGED
|
@@ -94,7 +94,7 @@ interface Refund {
|
|
|
94
94
|
status: RefundStatus;
|
|
95
95
|
tx_hash: string | null;
|
|
96
96
|
created_at: string;
|
|
97
|
-
|
|
97
|
+
updated_at: string;
|
|
98
98
|
failure_reason?: string | null;
|
|
99
99
|
}
|
|
100
100
|
interface Payout {
|
|
@@ -132,7 +132,7 @@ interface FragToken {
|
|
|
132
132
|
verified: boolean;
|
|
133
133
|
min_liquidity_usd?: number;
|
|
134
134
|
}
|
|
135
|
-
type WebhookEventType = "payment_intent.created" | "payment_intent.quoted" | "payment_intent.executing" | "payment_intent.executed" | "payment_intent.settled" | "payment_intent.failed" | "payment_intent.expired" | "payment_intent.cancelled" | "payment_intent.refunded" | "quote.created" | "settlement.confirmed" | "payout.
|
|
135
|
+
type WebhookEventType = "payment_intent.created" | "payment_intent.quoted" | "payment_intent.executing" | "payment_intent.executed" | "payment_intent.settled" | "payment_intent.failed" | "payment_intent.expired" | "payment_intent.cancelled" | "payment_intent.refunded" | "quote.created" | "settlement.confirmed" | "payout.created" | "payout.paid" | "payout.failed" | "refund.created" | "refund.processing" | "refund.paid" | "refund.failed";
|
|
136
136
|
interface WebhookEvent<T = unknown> {
|
|
137
137
|
id: string;
|
|
138
138
|
type: WebhookEventType;
|
|
@@ -160,6 +160,7 @@ declare class FragError extends Error {
|
|
|
160
160
|
type Requester = <T>(path: string, init?: RequestInit & {
|
|
161
161
|
idempotencyKey?: string;
|
|
162
162
|
query?: Record<string, string | number | boolean | undefined>;
|
|
163
|
+
skipAuth?: boolean;
|
|
163
164
|
}) => Promise<T>;
|
|
164
165
|
declare class FragmentPay {
|
|
165
166
|
#private;
|
|
@@ -187,6 +188,10 @@ declare class PaymentIntentResource {
|
|
|
187
188
|
transactions(id: string): Promise<{
|
|
188
189
|
data: IntentTransaction[];
|
|
189
190
|
}>;
|
|
191
|
+
routing(id: string): Promise<{
|
|
192
|
+
data: RouteCandidate[];
|
|
193
|
+
}>;
|
|
194
|
+
/** @deprecated use `routing()` — kept for backwards compatibility. */
|
|
190
195
|
route(id: string): Promise<{
|
|
191
196
|
data: RouteCandidate[];
|
|
192
197
|
}>;
|
|
@@ -208,6 +213,28 @@ declare class RefundResource {
|
|
|
208
213
|
}): Promise<{
|
|
209
214
|
data: Refund[];
|
|
210
215
|
}>;
|
|
216
|
+
/**
|
|
217
|
+
* Fetch the payer-facing safe status view for a refund. UNAUTHENTICATED —
|
|
218
|
+
* safe to expose in a customer-facing page or email. Redacts the destination
|
|
219
|
+
* address (only the last 6 chars) and omits merchant PII.
|
|
220
|
+
*/
|
|
221
|
+
status(id: string): Promise<{
|
|
222
|
+
id: string;
|
|
223
|
+
payment_intent_id: string;
|
|
224
|
+
amount_usd: number;
|
|
225
|
+
chain: string | null;
|
|
226
|
+
token: string | null;
|
|
227
|
+
destination_tail: string;
|
|
228
|
+
reason: string;
|
|
229
|
+
status: RefundStatus;
|
|
230
|
+
tx_hash: string | null;
|
|
231
|
+
failure_reason: string | null;
|
|
232
|
+
attempts: number;
|
|
233
|
+
created_at: string;
|
|
234
|
+
updated_at: string;
|
|
235
|
+
paid_at: string | null;
|
|
236
|
+
terminal: boolean;
|
|
237
|
+
}>;
|
|
211
238
|
}
|
|
212
239
|
declare class PayoutResource {
|
|
213
240
|
private req;
|
package/dist/index.d.ts
CHANGED
|
@@ -94,7 +94,7 @@ interface Refund {
|
|
|
94
94
|
status: RefundStatus;
|
|
95
95
|
tx_hash: string | null;
|
|
96
96
|
created_at: string;
|
|
97
|
-
|
|
97
|
+
updated_at: string;
|
|
98
98
|
failure_reason?: string | null;
|
|
99
99
|
}
|
|
100
100
|
interface Payout {
|
|
@@ -132,7 +132,7 @@ interface FragToken {
|
|
|
132
132
|
verified: boolean;
|
|
133
133
|
min_liquidity_usd?: number;
|
|
134
134
|
}
|
|
135
|
-
type WebhookEventType = "payment_intent.created" | "payment_intent.quoted" | "payment_intent.executing" | "payment_intent.executed" | "payment_intent.settled" | "payment_intent.failed" | "payment_intent.expired" | "payment_intent.cancelled" | "payment_intent.refunded" | "quote.created" | "settlement.confirmed" | "payout.
|
|
135
|
+
type WebhookEventType = "payment_intent.created" | "payment_intent.quoted" | "payment_intent.executing" | "payment_intent.executed" | "payment_intent.settled" | "payment_intent.failed" | "payment_intent.expired" | "payment_intent.cancelled" | "payment_intent.refunded" | "quote.created" | "settlement.confirmed" | "payout.created" | "payout.paid" | "payout.failed" | "refund.created" | "refund.processing" | "refund.paid" | "refund.failed";
|
|
136
136
|
interface WebhookEvent<T = unknown> {
|
|
137
137
|
id: string;
|
|
138
138
|
type: WebhookEventType;
|
|
@@ -160,6 +160,7 @@ declare class FragError extends Error {
|
|
|
160
160
|
type Requester = <T>(path: string, init?: RequestInit & {
|
|
161
161
|
idempotencyKey?: string;
|
|
162
162
|
query?: Record<string, string | number | boolean | undefined>;
|
|
163
|
+
skipAuth?: boolean;
|
|
163
164
|
}) => Promise<T>;
|
|
164
165
|
declare class FragmentPay {
|
|
165
166
|
#private;
|
|
@@ -187,6 +188,10 @@ declare class PaymentIntentResource {
|
|
|
187
188
|
transactions(id: string): Promise<{
|
|
188
189
|
data: IntentTransaction[];
|
|
189
190
|
}>;
|
|
191
|
+
routing(id: string): Promise<{
|
|
192
|
+
data: RouteCandidate[];
|
|
193
|
+
}>;
|
|
194
|
+
/** @deprecated use `routing()` — kept for backwards compatibility. */
|
|
190
195
|
route(id: string): Promise<{
|
|
191
196
|
data: RouteCandidate[];
|
|
192
197
|
}>;
|
|
@@ -208,6 +213,28 @@ declare class RefundResource {
|
|
|
208
213
|
}): Promise<{
|
|
209
214
|
data: Refund[];
|
|
210
215
|
}>;
|
|
216
|
+
/**
|
|
217
|
+
* Fetch the payer-facing safe status view for a refund. UNAUTHENTICATED —
|
|
218
|
+
* safe to expose in a customer-facing page or email. Redacts the destination
|
|
219
|
+
* address (only the last 6 chars) and omits merchant PII.
|
|
220
|
+
*/
|
|
221
|
+
status(id: string): Promise<{
|
|
222
|
+
id: string;
|
|
223
|
+
payment_intent_id: string;
|
|
224
|
+
amount_usd: number;
|
|
225
|
+
chain: string | null;
|
|
226
|
+
token: string | null;
|
|
227
|
+
destination_tail: string;
|
|
228
|
+
reason: string;
|
|
229
|
+
status: RefundStatus;
|
|
230
|
+
tx_hash: string | null;
|
|
231
|
+
failure_reason: string | null;
|
|
232
|
+
attempts: number;
|
|
233
|
+
created_at: string;
|
|
234
|
+
updated_at: string;
|
|
235
|
+
paid_at: string | null;
|
|
236
|
+
terminal: boolean;
|
|
237
|
+
}>;
|
|
211
238
|
}
|
|
212
239
|
declare class PayoutResource {
|
|
213
240
|
private req;
|
package/dist/index.js
CHANGED
|
@@ -54,7 +54,8 @@ var v = {
|
|
|
54
54
|
},
|
|
55
55
|
num(path, val, opts = {}) {
|
|
56
56
|
const n = typeof val === "string" ? Number(val) : val;
|
|
57
|
-
if (typeof n !== "number" || !Number.isFinite(n))
|
|
57
|
+
if (typeof n !== "number" || !Number.isFinite(n))
|
|
58
|
+
return fail(path, `expected finite number, got ${JSON.stringify(val)}`);
|
|
58
59
|
if (opts.int && !Number.isInteger(n)) fail(path, "expected integer");
|
|
59
60
|
if (opts.min != null && n < opts.min) fail(path, `min ${opts.min}`);
|
|
60
61
|
if (opts.max != null && n > opts.max) fail(path, `max ${opts.max}`);
|
|
@@ -94,7 +95,15 @@ var v = {
|
|
|
94
95
|
return v.str(path, val, { max: 254, pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/ });
|
|
95
96
|
}
|
|
96
97
|
};
|
|
97
|
-
var CHAINS = [
|
|
98
|
+
var CHAINS = [
|
|
99
|
+
"solana",
|
|
100
|
+
"ethereum",
|
|
101
|
+
"base",
|
|
102
|
+
"arbitrum",
|
|
103
|
+
"optimism",
|
|
104
|
+
"polygon",
|
|
105
|
+
"bnb"
|
|
106
|
+
];
|
|
98
107
|
var INTENT_STATUSES = [
|
|
99
108
|
"created",
|
|
100
109
|
"requires_payment",
|
|
@@ -157,11 +166,16 @@ var FragmentPay = class {
|
|
|
157
166
|
throw new FragValidationError("options", "expected FragOptions object");
|
|
158
167
|
}
|
|
159
168
|
if (typeof opts.apiKey !== "string" || !/^sk_(test|live)_[A-Za-z0-9_-]{8,}$/.test(opts.apiKey)) {
|
|
160
|
-
throw new FragValidationError(
|
|
169
|
+
throw new FragValidationError(
|
|
170
|
+
"options.apiKey",
|
|
171
|
+
"must match sk_test_\u2026 or sk_live_\u2026 (min 8 chars after prefix)"
|
|
172
|
+
);
|
|
161
173
|
}
|
|
162
174
|
if (opts.baseUrl !== void 0) v.url("options.baseUrl", opts.baseUrl);
|
|
163
|
-
if (opts.timeout !== void 0)
|
|
164
|
-
|
|
175
|
+
if (opts.timeout !== void 0)
|
|
176
|
+
v.num("options.timeout", opts.timeout, { min: 0, max: 6e5, int: true });
|
|
177
|
+
if (opts.retries !== void 0)
|
|
178
|
+
v.num("options.retries", opts.retries, { min: 0, max: 10, int: true });
|
|
165
179
|
this.#opts = {
|
|
166
180
|
apiKey: opts.apiKey,
|
|
167
181
|
baseUrl: (opts.baseUrl ?? "https://api.frag.dev").replace(/\/$/, ""),
|
|
@@ -187,7 +201,7 @@ var FragmentPay = class {
|
|
|
187
201
|
).toString() : "";
|
|
188
202
|
const url = `${this.#opts.baseUrl}${path}${query}`;
|
|
189
203
|
const headers = new Headers(init.headers);
|
|
190
|
-
headers.set("authorization", `Bearer ${this.#opts.apiKey}`);
|
|
204
|
+
if (!init.skipAuth) headers.set("authorization", `Bearer ${this.#opts.apiKey}`);
|
|
191
205
|
if (init.body && !headers.has("content-type")) headers.set("content-type", "application/json");
|
|
192
206
|
if (init.idempotencyKey) headers.set("idempotency-key", init.idempotencyKey);
|
|
193
207
|
const maxAttempts = Math.max(1, this.#opts.retries + 1);
|
|
@@ -251,16 +265,35 @@ var PaymentIntentResource = class {
|
|
|
251
265
|
}
|
|
252
266
|
req;
|
|
253
267
|
create(input) {
|
|
254
|
-
if (!input || typeof input !== "object")
|
|
268
|
+
if (!input || typeof input !== "object")
|
|
269
|
+
throw new FragValidationError("createIntent", "expected object");
|
|
255
270
|
const amount = v.num("createIntent.amount", input.amount, { min: 0.01, max: 1e7 });
|
|
256
|
-
const currency = v.optStr("createIntent.currency", input.currency, {
|
|
271
|
+
const currency = v.optStr("createIntent.currency", input.currency, {
|
|
272
|
+
min: 3,
|
|
273
|
+
max: 8,
|
|
274
|
+
pattern: /^[A-Z]{3,8}$/
|
|
275
|
+
}) ?? "USD";
|
|
257
276
|
if (input.settlement !== void 0) v.obj("createIntent.settlement", input.settlement);
|
|
258
277
|
const settlementChain = input.settlement?.chain ? v.enum("createIntent.settlement.chain", input.settlement.chain, CHAINS) : void 0;
|
|
259
|
-
const settlementToken = v.optStr("createIntent.settlement.token", input.settlement?.token, {
|
|
260
|
-
|
|
278
|
+
const settlementToken = v.optStr("createIntent.settlement.token", input.settlement?.token, {
|
|
279
|
+
min: 1,
|
|
280
|
+
max: 64
|
|
281
|
+
});
|
|
282
|
+
const settlementAddress = v.optStr(
|
|
283
|
+
"createIntent.settlement.address",
|
|
284
|
+
input.settlement?.address,
|
|
285
|
+
{ min: 26, max: 128 }
|
|
286
|
+
);
|
|
261
287
|
const customerEmail = input.customerEmail !== void 0 ? v.email("createIntent.customerEmail", input.customerEmail) : void 0;
|
|
262
|
-
const expiresInSeconds = v.optNum("createIntent.expiresInSeconds", input.expiresInSeconds, {
|
|
263
|
-
|
|
288
|
+
const expiresInSeconds = v.optNum("createIntent.expiresInSeconds", input.expiresInSeconds, {
|
|
289
|
+
min: 60,
|
|
290
|
+
max: 86400,
|
|
291
|
+
int: true
|
|
292
|
+
});
|
|
293
|
+
const idempotencyKey = v.optStr("createIntent.idempotencyKey", input.idempotencyKey, {
|
|
294
|
+
min: 8,
|
|
295
|
+
max: 128
|
|
296
|
+
});
|
|
264
297
|
const successUrl = input.successUrl !== void 0 ? v.url("createIntent.successUrl", input.successUrl) : void 0;
|
|
265
298
|
const cancelUrl = input.cancelUrl !== void 0 ? v.url("createIntent.cancelUrl", input.cancelUrl) : void 0;
|
|
266
299
|
if (input.metadata !== void 0) v.obj("createIntent.metadata", input.metadata);
|
|
@@ -294,15 +327,22 @@ var PaymentIntentResource = class {
|
|
|
294
327
|
}
|
|
295
328
|
cancel(id) {
|
|
296
329
|
v.id("paymentIntents.cancel.id", id);
|
|
297
|
-
return this.req(
|
|
330
|
+
return this.req(
|
|
331
|
+
`/api/public/v1/payment-intents/${encodeURIComponent(id)}/cancel`,
|
|
332
|
+
{ method: "POST" }
|
|
333
|
+
);
|
|
298
334
|
}
|
|
299
335
|
transactions(id) {
|
|
300
336
|
v.id("paymentIntents.transactions.id", id);
|
|
301
337
|
return this.req(`/api/public/v1/payment-intents/${encodeURIComponent(id)}/transactions`);
|
|
302
338
|
}
|
|
339
|
+
routing(id) {
|
|
340
|
+
v.id("paymentIntents.routing.id", id);
|
|
341
|
+
return this.req(`/api/public/v1/payment-intents/${encodeURIComponent(id)}/routing`);
|
|
342
|
+
}
|
|
343
|
+
/** @deprecated use `routing()` — kept for backwards compatibility. */
|
|
303
344
|
route(id) {
|
|
304
|
-
|
|
305
|
-
return this.req(`/api/public/v1/payment-intents/${encodeURIComponent(id)}/route`);
|
|
345
|
+
return this.routing(id);
|
|
306
346
|
}
|
|
307
347
|
};
|
|
308
348
|
var RefundResource = class {
|
|
@@ -311,7 +351,8 @@ var RefundResource = class {
|
|
|
311
351
|
}
|
|
312
352
|
req;
|
|
313
353
|
create(input) {
|
|
314
|
-
if (!input || typeof input !== "object")
|
|
354
|
+
if (!input || typeof input !== "object")
|
|
355
|
+
throw new FragValidationError("refunds.create", "expected object");
|
|
315
356
|
v.id("refunds.create.paymentIntentId", input.paymentIntentId);
|
|
316
357
|
v.num("refunds.create.amountUsd", input.amountUsd, { min: 0.01, max: 1e7 });
|
|
317
358
|
v.str("refunds.create.destination", input.destination, { min: 26, max: 128 });
|
|
@@ -333,12 +374,24 @@ var RefundResource = class {
|
|
|
333
374
|
return this.req(`/api/public/v1/refunds/${encodeURIComponent(id)}`);
|
|
334
375
|
}
|
|
335
376
|
list(params = {}) {
|
|
336
|
-
if (params.paymentIntentId !== void 0)
|
|
377
|
+
if (params.paymentIntentId !== void 0)
|
|
378
|
+
v.id("refunds.list.paymentIntentId", params.paymentIntentId);
|
|
337
379
|
v.optNum("refunds.list.limit", params.limit, { min: 1, max: 100, int: true });
|
|
338
380
|
return this.req(`/api/public/v1/refunds`, {
|
|
339
381
|
query: { payment_intent_id: params.paymentIntentId, limit: params.limit }
|
|
340
382
|
});
|
|
341
383
|
}
|
|
384
|
+
/**
|
|
385
|
+
* Fetch the payer-facing safe status view for a refund. UNAUTHENTICATED —
|
|
386
|
+
* safe to expose in a customer-facing page or email. Redacts the destination
|
|
387
|
+
* address (only the last 6 chars) and omits merchant PII.
|
|
388
|
+
*/
|
|
389
|
+
status(id) {
|
|
390
|
+
v.id("refunds.status.id", id);
|
|
391
|
+
return this.req(`/api/public/refunds/${encodeURIComponent(id)}/status`, {
|
|
392
|
+
skipAuth: true
|
|
393
|
+
});
|
|
394
|
+
}
|
|
342
395
|
};
|
|
343
396
|
var PayoutResource = class {
|
|
344
397
|
constructor(req) {
|
|
@@ -351,10 +404,15 @@ var PayoutResource = class {
|
|
|
351
404
|
}
|
|
352
405
|
list(params = {}) {
|
|
353
406
|
v.optEnum("payouts.list.status", params.status, PAYOUT_STATUSES);
|
|
354
|
-
if (params.paymentIntentId !== void 0)
|
|
407
|
+
if (params.paymentIntentId !== void 0)
|
|
408
|
+
v.id("payouts.list.paymentIntentId", params.paymentIntentId);
|
|
355
409
|
v.optNum("payouts.list.limit", params.limit, { min: 1, max: 100, int: true });
|
|
356
410
|
return this.req(`/api/public/v1/payouts`, {
|
|
357
|
-
query: {
|
|
411
|
+
query: {
|
|
412
|
+
status: params.status,
|
|
413
|
+
payment_intent_id: params.paymentIntentId,
|
|
414
|
+
limit: params.limit
|
|
415
|
+
}
|
|
358
416
|
});
|
|
359
417
|
}
|
|
360
418
|
replay(id) {
|
|
@@ -368,12 +426,18 @@ var WebhookEndpointResource = class {
|
|
|
368
426
|
}
|
|
369
427
|
req;
|
|
370
428
|
create(input) {
|
|
371
|
-
if (!input || typeof input !== "object")
|
|
429
|
+
if (!input || typeof input !== "object")
|
|
430
|
+
throw new FragValidationError("webhookEndpoints.create", "expected object");
|
|
372
431
|
v.url("webhookEndpoints.create.url", input.url);
|
|
373
432
|
if (!Array.isArray(input.events) || input.events.length === 0) {
|
|
374
|
-
throw new FragValidationError(
|
|
433
|
+
throw new FragValidationError(
|
|
434
|
+
"webhookEndpoints.create.events",
|
|
435
|
+
"at least one event required"
|
|
436
|
+
);
|
|
375
437
|
}
|
|
376
|
-
input.events.forEach(
|
|
438
|
+
input.events.forEach(
|
|
439
|
+
(e, i) => v.enum(`webhookEndpoints.create.events[${i}]`, e, WEBHOOK_EVENTS)
|
|
440
|
+
);
|
|
377
441
|
return this.req("/api/public/v1/webhook-endpoints", {
|
|
378
442
|
method: "POST",
|
|
379
443
|
body: JSON.stringify(input)
|
|
@@ -390,8 +454,11 @@ var WebhookEndpointResource = class {
|
|
|
390
454
|
v.id("webhookEndpoints.update.id", id);
|
|
391
455
|
if (patch.url !== void 0) v.url("webhookEndpoints.update.url", patch.url);
|
|
392
456
|
if (patch.events !== void 0) {
|
|
393
|
-
if (!Array.isArray(patch.events))
|
|
394
|
-
|
|
457
|
+
if (!Array.isArray(patch.events))
|
|
458
|
+
throw new FragValidationError("webhookEndpoints.update.events", "expected array");
|
|
459
|
+
patch.events.forEach(
|
|
460
|
+
(e, i) => v.enum(`webhookEndpoints.update.events[${i}]`, e, WEBHOOK_EVENTS)
|
|
461
|
+
);
|
|
395
462
|
}
|
|
396
463
|
return this.req(`/api/public/v1/webhook-endpoints/${encodeURIComponent(id)}`, {
|
|
397
464
|
method: "PATCH",
|
|
@@ -400,11 +467,16 @@ var WebhookEndpointResource = class {
|
|
|
400
467
|
}
|
|
401
468
|
delete(id) {
|
|
402
469
|
v.id("webhookEndpoints.delete.id", id);
|
|
403
|
-
return this.req(`/api/public/v1/webhook-endpoints/${encodeURIComponent(id)}`, {
|
|
470
|
+
return this.req(`/api/public/v1/webhook-endpoints/${encodeURIComponent(id)}`, {
|
|
471
|
+
method: "DELETE"
|
|
472
|
+
});
|
|
404
473
|
}
|
|
405
474
|
rotateSecret(id) {
|
|
406
475
|
v.id("webhookEndpoints.rotateSecret.id", id);
|
|
407
|
-
return this.req(
|
|
476
|
+
return this.req(
|
|
477
|
+
`/api/public/v1/webhook-endpoints/${encodeURIComponent(id)}/rotate-secret`,
|
|
478
|
+
{ method: "POST" }
|
|
479
|
+
);
|
|
408
480
|
}
|
|
409
481
|
};
|
|
410
482
|
var TokenResource = class {
|
|
@@ -434,19 +506,34 @@ var WebhookHelpers = class {
|
|
|
434
506
|
}
|
|
435
507
|
};
|
|
436
508
|
async function verifyWebhook(input) {
|
|
437
|
-
if (!input || typeof input !== "object")
|
|
509
|
+
if (!input || typeof input !== "object")
|
|
510
|
+
throw new FragValidationError("verifyWebhook", "expected object");
|
|
438
511
|
v.str("verifyWebhook.payload", input.payload, { min: 1 });
|
|
439
512
|
v.str("verifyWebhook.secret", input.secret, { min: 8 });
|
|
440
|
-
if (input.tolerance !== void 0)
|
|
513
|
+
if (input.tolerance !== void 0)
|
|
514
|
+
v.num("verifyWebhook.tolerance", input.tolerance, { min: 0, max: 3600, int: true });
|
|
441
515
|
const helper = new WebhookHelpers();
|
|
442
|
-
const ok = await helper.verify(
|
|
443
|
-
|
|
516
|
+
const ok = await helper.verify(
|
|
517
|
+
input.payload,
|
|
518
|
+
input.signature ?? null,
|
|
519
|
+
input.secret,
|
|
520
|
+
input.tolerance
|
|
521
|
+
);
|
|
522
|
+
if (!ok)
|
|
523
|
+
throw new FragError(401, "invalid_signature", "webhook signature verification failed", null);
|
|
444
524
|
return helper.parse(input.payload);
|
|
445
525
|
}
|
|
446
526
|
var Frag = class extends FragmentPay {
|
|
447
527
|
constructor(opts) {
|
|
448
528
|
const apiKey = "secretKey" in opts ? opts.secretKey : opts.apiKey;
|
|
449
|
-
super({
|
|
529
|
+
super({
|
|
530
|
+
apiKey,
|
|
531
|
+
baseUrl: opts.baseUrl,
|
|
532
|
+
fetch: opts.fetch,
|
|
533
|
+
timeout: opts.timeout,
|
|
534
|
+
retries: opts.retries,
|
|
535
|
+
debug: opts.debug
|
|
536
|
+
});
|
|
450
537
|
}
|
|
451
538
|
};
|
|
452
539
|
async function hmacHex(secret, message) {
|
package/dist/index.mjs
CHANGED
|
@@ -24,7 +24,8 @@ var v = {
|
|
|
24
24
|
},
|
|
25
25
|
num(path, val, opts = {}) {
|
|
26
26
|
const n = typeof val === "string" ? Number(val) : val;
|
|
27
|
-
if (typeof n !== "number" || !Number.isFinite(n))
|
|
27
|
+
if (typeof n !== "number" || !Number.isFinite(n))
|
|
28
|
+
return fail(path, `expected finite number, got ${JSON.stringify(val)}`);
|
|
28
29
|
if (opts.int && !Number.isInteger(n)) fail(path, "expected integer");
|
|
29
30
|
if (opts.min != null && n < opts.min) fail(path, `min ${opts.min}`);
|
|
30
31
|
if (opts.max != null && n > opts.max) fail(path, `max ${opts.max}`);
|
|
@@ -64,7 +65,15 @@ var v = {
|
|
|
64
65
|
return v.str(path, val, { max: 254, pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/ });
|
|
65
66
|
}
|
|
66
67
|
};
|
|
67
|
-
var CHAINS = [
|
|
68
|
+
var CHAINS = [
|
|
69
|
+
"solana",
|
|
70
|
+
"ethereum",
|
|
71
|
+
"base",
|
|
72
|
+
"arbitrum",
|
|
73
|
+
"optimism",
|
|
74
|
+
"polygon",
|
|
75
|
+
"bnb"
|
|
76
|
+
];
|
|
68
77
|
var INTENT_STATUSES = [
|
|
69
78
|
"created",
|
|
70
79
|
"requires_payment",
|
|
@@ -127,11 +136,16 @@ var FragmentPay = class {
|
|
|
127
136
|
throw new FragValidationError("options", "expected FragOptions object");
|
|
128
137
|
}
|
|
129
138
|
if (typeof opts.apiKey !== "string" || !/^sk_(test|live)_[A-Za-z0-9_-]{8,}$/.test(opts.apiKey)) {
|
|
130
|
-
throw new FragValidationError(
|
|
139
|
+
throw new FragValidationError(
|
|
140
|
+
"options.apiKey",
|
|
141
|
+
"must match sk_test_\u2026 or sk_live_\u2026 (min 8 chars after prefix)"
|
|
142
|
+
);
|
|
131
143
|
}
|
|
132
144
|
if (opts.baseUrl !== void 0) v.url("options.baseUrl", opts.baseUrl);
|
|
133
|
-
if (opts.timeout !== void 0)
|
|
134
|
-
|
|
145
|
+
if (opts.timeout !== void 0)
|
|
146
|
+
v.num("options.timeout", opts.timeout, { min: 0, max: 6e5, int: true });
|
|
147
|
+
if (opts.retries !== void 0)
|
|
148
|
+
v.num("options.retries", opts.retries, { min: 0, max: 10, int: true });
|
|
135
149
|
this.#opts = {
|
|
136
150
|
apiKey: opts.apiKey,
|
|
137
151
|
baseUrl: (opts.baseUrl ?? "https://api.frag.dev").replace(/\/$/, ""),
|
|
@@ -157,7 +171,7 @@ var FragmentPay = class {
|
|
|
157
171
|
).toString() : "";
|
|
158
172
|
const url = `${this.#opts.baseUrl}${path}${query}`;
|
|
159
173
|
const headers = new Headers(init.headers);
|
|
160
|
-
headers.set("authorization", `Bearer ${this.#opts.apiKey}`);
|
|
174
|
+
if (!init.skipAuth) headers.set("authorization", `Bearer ${this.#opts.apiKey}`);
|
|
161
175
|
if (init.body && !headers.has("content-type")) headers.set("content-type", "application/json");
|
|
162
176
|
if (init.idempotencyKey) headers.set("idempotency-key", init.idempotencyKey);
|
|
163
177
|
const maxAttempts = Math.max(1, this.#opts.retries + 1);
|
|
@@ -221,16 +235,35 @@ var PaymentIntentResource = class {
|
|
|
221
235
|
}
|
|
222
236
|
req;
|
|
223
237
|
create(input) {
|
|
224
|
-
if (!input || typeof input !== "object")
|
|
238
|
+
if (!input || typeof input !== "object")
|
|
239
|
+
throw new FragValidationError("createIntent", "expected object");
|
|
225
240
|
const amount = v.num("createIntent.amount", input.amount, { min: 0.01, max: 1e7 });
|
|
226
|
-
const currency = v.optStr("createIntent.currency", input.currency, {
|
|
241
|
+
const currency = v.optStr("createIntent.currency", input.currency, {
|
|
242
|
+
min: 3,
|
|
243
|
+
max: 8,
|
|
244
|
+
pattern: /^[A-Z]{3,8}$/
|
|
245
|
+
}) ?? "USD";
|
|
227
246
|
if (input.settlement !== void 0) v.obj("createIntent.settlement", input.settlement);
|
|
228
247
|
const settlementChain = input.settlement?.chain ? v.enum("createIntent.settlement.chain", input.settlement.chain, CHAINS) : void 0;
|
|
229
|
-
const settlementToken = v.optStr("createIntent.settlement.token", input.settlement?.token, {
|
|
230
|
-
|
|
248
|
+
const settlementToken = v.optStr("createIntent.settlement.token", input.settlement?.token, {
|
|
249
|
+
min: 1,
|
|
250
|
+
max: 64
|
|
251
|
+
});
|
|
252
|
+
const settlementAddress = v.optStr(
|
|
253
|
+
"createIntent.settlement.address",
|
|
254
|
+
input.settlement?.address,
|
|
255
|
+
{ min: 26, max: 128 }
|
|
256
|
+
);
|
|
231
257
|
const customerEmail = input.customerEmail !== void 0 ? v.email("createIntent.customerEmail", input.customerEmail) : void 0;
|
|
232
|
-
const expiresInSeconds = v.optNum("createIntent.expiresInSeconds", input.expiresInSeconds, {
|
|
233
|
-
|
|
258
|
+
const expiresInSeconds = v.optNum("createIntent.expiresInSeconds", input.expiresInSeconds, {
|
|
259
|
+
min: 60,
|
|
260
|
+
max: 86400,
|
|
261
|
+
int: true
|
|
262
|
+
});
|
|
263
|
+
const idempotencyKey = v.optStr("createIntent.idempotencyKey", input.idempotencyKey, {
|
|
264
|
+
min: 8,
|
|
265
|
+
max: 128
|
|
266
|
+
});
|
|
234
267
|
const successUrl = input.successUrl !== void 0 ? v.url("createIntent.successUrl", input.successUrl) : void 0;
|
|
235
268
|
const cancelUrl = input.cancelUrl !== void 0 ? v.url("createIntent.cancelUrl", input.cancelUrl) : void 0;
|
|
236
269
|
if (input.metadata !== void 0) v.obj("createIntent.metadata", input.metadata);
|
|
@@ -264,15 +297,22 @@ var PaymentIntentResource = class {
|
|
|
264
297
|
}
|
|
265
298
|
cancel(id) {
|
|
266
299
|
v.id("paymentIntents.cancel.id", id);
|
|
267
|
-
return this.req(
|
|
300
|
+
return this.req(
|
|
301
|
+
`/api/public/v1/payment-intents/${encodeURIComponent(id)}/cancel`,
|
|
302
|
+
{ method: "POST" }
|
|
303
|
+
);
|
|
268
304
|
}
|
|
269
305
|
transactions(id) {
|
|
270
306
|
v.id("paymentIntents.transactions.id", id);
|
|
271
307
|
return this.req(`/api/public/v1/payment-intents/${encodeURIComponent(id)}/transactions`);
|
|
272
308
|
}
|
|
309
|
+
routing(id) {
|
|
310
|
+
v.id("paymentIntents.routing.id", id);
|
|
311
|
+
return this.req(`/api/public/v1/payment-intents/${encodeURIComponent(id)}/routing`);
|
|
312
|
+
}
|
|
313
|
+
/** @deprecated use `routing()` — kept for backwards compatibility. */
|
|
273
314
|
route(id) {
|
|
274
|
-
|
|
275
|
-
return this.req(`/api/public/v1/payment-intents/${encodeURIComponent(id)}/route`);
|
|
315
|
+
return this.routing(id);
|
|
276
316
|
}
|
|
277
317
|
};
|
|
278
318
|
var RefundResource = class {
|
|
@@ -281,7 +321,8 @@ var RefundResource = class {
|
|
|
281
321
|
}
|
|
282
322
|
req;
|
|
283
323
|
create(input) {
|
|
284
|
-
if (!input || typeof input !== "object")
|
|
324
|
+
if (!input || typeof input !== "object")
|
|
325
|
+
throw new FragValidationError("refunds.create", "expected object");
|
|
285
326
|
v.id("refunds.create.paymentIntentId", input.paymentIntentId);
|
|
286
327
|
v.num("refunds.create.amountUsd", input.amountUsd, { min: 0.01, max: 1e7 });
|
|
287
328
|
v.str("refunds.create.destination", input.destination, { min: 26, max: 128 });
|
|
@@ -303,12 +344,24 @@ var RefundResource = class {
|
|
|
303
344
|
return this.req(`/api/public/v1/refunds/${encodeURIComponent(id)}`);
|
|
304
345
|
}
|
|
305
346
|
list(params = {}) {
|
|
306
|
-
if (params.paymentIntentId !== void 0)
|
|
347
|
+
if (params.paymentIntentId !== void 0)
|
|
348
|
+
v.id("refunds.list.paymentIntentId", params.paymentIntentId);
|
|
307
349
|
v.optNum("refunds.list.limit", params.limit, { min: 1, max: 100, int: true });
|
|
308
350
|
return this.req(`/api/public/v1/refunds`, {
|
|
309
351
|
query: { payment_intent_id: params.paymentIntentId, limit: params.limit }
|
|
310
352
|
});
|
|
311
353
|
}
|
|
354
|
+
/**
|
|
355
|
+
* Fetch the payer-facing safe status view for a refund. UNAUTHENTICATED —
|
|
356
|
+
* safe to expose in a customer-facing page or email. Redacts the destination
|
|
357
|
+
* address (only the last 6 chars) and omits merchant PII.
|
|
358
|
+
*/
|
|
359
|
+
status(id) {
|
|
360
|
+
v.id("refunds.status.id", id);
|
|
361
|
+
return this.req(`/api/public/refunds/${encodeURIComponent(id)}/status`, {
|
|
362
|
+
skipAuth: true
|
|
363
|
+
});
|
|
364
|
+
}
|
|
312
365
|
};
|
|
313
366
|
var PayoutResource = class {
|
|
314
367
|
constructor(req) {
|
|
@@ -321,10 +374,15 @@ var PayoutResource = class {
|
|
|
321
374
|
}
|
|
322
375
|
list(params = {}) {
|
|
323
376
|
v.optEnum("payouts.list.status", params.status, PAYOUT_STATUSES);
|
|
324
|
-
if (params.paymentIntentId !== void 0)
|
|
377
|
+
if (params.paymentIntentId !== void 0)
|
|
378
|
+
v.id("payouts.list.paymentIntentId", params.paymentIntentId);
|
|
325
379
|
v.optNum("payouts.list.limit", params.limit, { min: 1, max: 100, int: true });
|
|
326
380
|
return this.req(`/api/public/v1/payouts`, {
|
|
327
|
-
query: {
|
|
381
|
+
query: {
|
|
382
|
+
status: params.status,
|
|
383
|
+
payment_intent_id: params.paymentIntentId,
|
|
384
|
+
limit: params.limit
|
|
385
|
+
}
|
|
328
386
|
});
|
|
329
387
|
}
|
|
330
388
|
replay(id) {
|
|
@@ -338,12 +396,18 @@ var WebhookEndpointResource = class {
|
|
|
338
396
|
}
|
|
339
397
|
req;
|
|
340
398
|
create(input) {
|
|
341
|
-
if (!input || typeof input !== "object")
|
|
399
|
+
if (!input || typeof input !== "object")
|
|
400
|
+
throw new FragValidationError("webhookEndpoints.create", "expected object");
|
|
342
401
|
v.url("webhookEndpoints.create.url", input.url);
|
|
343
402
|
if (!Array.isArray(input.events) || input.events.length === 0) {
|
|
344
|
-
throw new FragValidationError(
|
|
403
|
+
throw new FragValidationError(
|
|
404
|
+
"webhookEndpoints.create.events",
|
|
405
|
+
"at least one event required"
|
|
406
|
+
);
|
|
345
407
|
}
|
|
346
|
-
input.events.forEach(
|
|
408
|
+
input.events.forEach(
|
|
409
|
+
(e, i) => v.enum(`webhookEndpoints.create.events[${i}]`, e, WEBHOOK_EVENTS)
|
|
410
|
+
);
|
|
347
411
|
return this.req("/api/public/v1/webhook-endpoints", {
|
|
348
412
|
method: "POST",
|
|
349
413
|
body: JSON.stringify(input)
|
|
@@ -360,8 +424,11 @@ var WebhookEndpointResource = class {
|
|
|
360
424
|
v.id("webhookEndpoints.update.id", id);
|
|
361
425
|
if (patch.url !== void 0) v.url("webhookEndpoints.update.url", patch.url);
|
|
362
426
|
if (patch.events !== void 0) {
|
|
363
|
-
if (!Array.isArray(patch.events))
|
|
364
|
-
|
|
427
|
+
if (!Array.isArray(patch.events))
|
|
428
|
+
throw new FragValidationError("webhookEndpoints.update.events", "expected array");
|
|
429
|
+
patch.events.forEach(
|
|
430
|
+
(e, i) => v.enum(`webhookEndpoints.update.events[${i}]`, e, WEBHOOK_EVENTS)
|
|
431
|
+
);
|
|
365
432
|
}
|
|
366
433
|
return this.req(`/api/public/v1/webhook-endpoints/${encodeURIComponent(id)}`, {
|
|
367
434
|
method: "PATCH",
|
|
@@ -370,11 +437,16 @@ var WebhookEndpointResource = class {
|
|
|
370
437
|
}
|
|
371
438
|
delete(id) {
|
|
372
439
|
v.id("webhookEndpoints.delete.id", id);
|
|
373
|
-
return this.req(`/api/public/v1/webhook-endpoints/${encodeURIComponent(id)}`, {
|
|
440
|
+
return this.req(`/api/public/v1/webhook-endpoints/${encodeURIComponent(id)}`, {
|
|
441
|
+
method: "DELETE"
|
|
442
|
+
});
|
|
374
443
|
}
|
|
375
444
|
rotateSecret(id) {
|
|
376
445
|
v.id("webhookEndpoints.rotateSecret.id", id);
|
|
377
|
-
return this.req(
|
|
446
|
+
return this.req(
|
|
447
|
+
`/api/public/v1/webhook-endpoints/${encodeURIComponent(id)}/rotate-secret`,
|
|
448
|
+
{ method: "POST" }
|
|
449
|
+
);
|
|
378
450
|
}
|
|
379
451
|
};
|
|
380
452
|
var TokenResource = class {
|
|
@@ -404,19 +476,34 @@ var WebhookHelpers = class {
|
|
|
404
476
|
}
|
|
405
477
|
};
|
|
406
478
|
async function verifyWebhook(input) {
|
|
407
|
-
if (!input || typeof input !== "object")
|
|
479
|
+
if (!input || typeof input !== "object")
|
|
480
|
+
throw new FragValidationError("verifyWebhook", "expected object");
|
|
408
481
|
v.str("verifyWebhook.payload", input.payload, { min: 1 });
|
|
409
482
|
v.str("verifyWebhook.secret", input.secret, { min: 8 });
|
|
410
|
-
if (input.tolerance !== void 0)
|
|
483
|
+
if (input.tolerance !== void 0)
|
|
484
|
+
v.num("verifyWebhook.tolerance", input.tolerance, { min: 0, max: 3600, int: true });
|
|
411
485
|
const helper = new WebhookHelpers();
|
|
412
|
-
const ok = await helper.verify(
|
|
413
|
-
|
|
486
|
+
const ok = await helper.verify(
|
|
487
|
+
input.payload,
|
|
488
|
+
input.signature ?? null,
|
|
489
|
+
input.secret,
|
|
490
|
+
input.tolerance
|
|
491
|
+
);
|
|
492
|
+
if (!ok)
|
|
493
|
+
throw new FragError(401, "invalid_signature", "webhook signature verification failed", null);
|
|
414
494
|
return helper.parse(input.payload);
|
|
415
495
|
}
|
|
416
496
|
var Frag = class extends FragmentPay {
|
|
417
497
|
constructor(opts) {
|
|
418
498
|
const apiKey = "secretKey" in opts ? opts.secretKey : opts.apiKey;
|
|
419
|
-
super({
|
|
499
|
+
super({
|
|
500
|
+
apiKey,
|
|
501
|
+
baseUrl: opts.baseUrl,
|
|
502
|
+
fetch: opts.fetch,
|
|
503
|
+
timeout: opts.timeout,
|
|
504
|
+
retries: opts.retries,
|
|
505
|
+
debug: opts.debug
|
|
506
|
+
});
|
|
420
507
|
}
|
|
421
508
|
};
|
|
422
509
|
async function hmacHex(secret, message) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fragmentpay/server",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"description": "Frag server SDK — accept crypto payments from any token, on any chain, settle in one asset.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -12,19 +12,39 @@
|
|
|
12
12
|
"require": "./dist/index.js"
|
|
13
13
|
}
|
|
14
14
|
},
|
|
15
|
-
"files": [
|
|
16
|
-
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md",
|
|
18
|
+
"LICENSE"
|
|
19
|
+
],
|
|
20
|
+
"keywords": [
|
|
21
|
+
"crypto",
|
|
22
|
+
"payments",
|
|
23
|
+
"stablecoin",
|
|
24
|
+
"solana",
|
|
25
|
+
"ethereum",
|
|
26
|
+
"usdc",
|
|
27
|
+
"fragmentpay",
|
|
28
|
+
"frag"
|
|
29
|
+
],
|
|
17
30
|
"license": "MIT",
|
|
18
31
|
"sideEffects": false,
|
|
19
|
-
"engines": {
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=18"
|
|
34
|
+
},
|
|
20
35
|
"repository": {
|
|
21
36
|
"type": "git",
|
|
22
37
|
"url": "git+https://github.com/fragmentpay/fragpay.git",
|
|
23
38
|
"directory": "sdk/frag-server"
|
|
24
39
|
},
|
|
25
40
|
"homepage": "https://github.com/fragmentpay/fragpay#readme",
|
|
26
|
-
"bugs": {
|
|
27
|
-
|
|
41
|
+
"bugs": {
|
|
42
|
+
"url": "https://github.com/fragmentpay/fragpay/issues"
|
|
43
|
+
},
|
|
44
|
+
"publishConfig": {
|
|
45
|
+
"access": "public",
|
|
46
|
+
"provenance": true
|
|
47
|
+
},
|
|
28
48
|
"scripts": {
|
|
29
49
|
"build": "tsup src/index.ts --format esm,cjs --dts --clean",
|
|
30
50
|
"prepublishOnly": "npm run build"
|