@fragmentpay/server 0.3.1 → 0.4.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 +40 -8
- package/dist/index.d.mts +120 -7
- package/dist/index.d.ts +120 -7
- package/dist/index.js +169 -32
- package/dist/index.mjs +168 -32
- package/dist/openapi-client.d.mts +1201 -0
- package/dist/openapi-client.d.ts +1201 -0
- package/dist/openapi-client.js +54 -0
- package/dist/openapi-client.mjs +19 -0
- package/dist/openapi.d.ts +1161 -0
- package/package.json +40 -7
package/dist/index.js
CHANGED
|
@@ -24,6 +24,7 @@ __export(index_exports, {
|
|
|
24
24
|
FragError: () => FragError,
|
|
25
25
|
FragValidationError: () => FragValidationError,
|
|
26
26
|
FragmentPay: () => FragmentPay,
|
|
27
|
+
refundPhase: () => refundPhase,
|
|
27
28
|
verifyWebhook: () => verifyWebhook
|
|
28
29
|
});
|
|
29
30
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -54,7 +55,8 @@ var v = {
|
|
|
54
55
|
},
|
|
55
56
|
num(path, val, opts = {}) {
|
|
56
57
|
const n = typeof val === "string" ? Number(val) : val;
|
|
57
|
-
if (typeof n !== "number" || !Number.isFinite(n))
|
|
58
|
+
if (typeof n !== "number" || !Number.isFinite(n))
|
|
59
|
+
return fail(path, `expected finite number, got ${JSON.stringify(val)}`);
|
|
58
60
|
if (opts.int && !Number.isInteger(n)) fail(path, "expected integer");
|
|
59
61
|
if (opts.min != null && n < opts.min) fail(path, `min ${opts.min}`);
|
|
60
62
|
if (opts.max != null && n > opts.max) fail(path, `max ${opts.max}`);
|
|
@@ -94,7 +96,15 @@ var v = {
|
|
|
94
96
|
return v.str(path, val, { max: 254, pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/ });
|
|
95
97
|
}
|
|
96
98
|
};
|
|
97
|
-
var CHAINS = [
|
|
99
|
+
var CHAINS = [
|
|
100
|
+
"solana",
|
|
101
|
+
"ethereum",
|
|
102
|
+
"base",
|
|
103
|
+
"arbitrum",
|
|
104
|
+
"optimism",
|
|
105
|
+
"polygon",
|
|
106
|
+
"bnb"
|
|
107
|
+
];
|
|
98
108
|
var INTENT_STATUSES = [
|
|
99
109
|
"created",
|
|
100
110
|
"requires_payment",
|
|
@@ -109,6 +119,13 @@ var INTENT_STATUSES = [
|
|
|
109
119
|
"refunded"
|
|
110
120
|
];
|
|
111
121
|
var PAYOUT_STATUSES = ["queued", "pending", "paid", "failed", "paused"];
|
|
122
|
+
var REFUND_STATUSES = [
|
|
123
|
+
"pending",
|
|
124
|
+
"processing",
|
|
125
|
+
"retry_scheduled",
|
|
126
|
+
"succeeded",
|
|
127
|
+
"failed"
|
|
128
|
+
];
|
|
112
129
|
var WEBHOOK_EVENTS = [
|
|
113
130
|
"payment_intent.created",
|
|
114
131
|
"payment_intent.quoted",
|
|
@@ -130,6 +147,18 @@ var WEBHOOK_EVENTS = [
|
|
|
130
147
|
];
|
|
131
148
|
|
|
132
149
|
// src/index.ts
|
|
150
|
+
function refundPhase(status) {
|
|
151
|
+
switch (status) {
|
|
152
|
+
case "succeeded":
|
|
153
|
+
return "completed";
|
|
154
|
+
case "processing":
|
|
155
|
+
return "sent";
|
|
156
|
+
case "failed":
|
|
157
|
+
return "failed";
|
|
158
|
+
default:
|
|
159
|
+
return "queued";
|
|
160
|
+
}
|
|
161
|
+
}
|
|
133
162
|
var FragError = class extends Error {
|
|
134
163
|
status;
|
|
135
164
|
code;
|
|
@@ -151,20 +180,26 @@ var FragmentPay = class {
|
|
|
151
180
|
webhookEndpoints;
|
|
152
181
|
webhooks;
|
|
153
182
|
tokens;
|
|
183
|
+
checkout;
|
|
154
184
|
#opts;
|
|
155
185
|
constructor(opts) {
|
|
156
186
|
if (!opts || typeof opts !== "object") {
|
|
157
187
|
throw new FragValidationError("options", "expected FragOptions object");
|
|
158
188
|
}
|
|
159
189
|
if (typeof opts.apiKey !== "string" || !/^sk_(test|live)_[A-Za-z0-9_-]{8,}$/.test(opts.apiKey)) {
|
|
160
|
-
throw new FragValidationError(
|
|
190
|
+
throw new FragValidationError(
|
|
191
|
+
"options.apiKey",
|
|
192
|
+
"must match sk_test_\u2026 or sk_live_\u2026 (min 8 chars after prefix)"
|
|
193
|
+
);
|
|
161
194
|
}
|
|
162
195
|
if (opts.baseUrl !== void 0) v.url("options.baseUrl", opts.baseUrl);
|
|
163
|
-
if (opts.timeout !== void 0)
|
|
164
|
-
|
|
196
|
+
if (opts.timeout !== void 0)
|
|
197
|
+
v.num("options.timeout", opts.timeout, { min: 0, max: 6e5, int: true });
|
|
198
|
+
if (opts.retries !== void 0)
|
|
199
|
+
v.num("options.retries", opts.retries, { min: 0, max: 10, int: true });
|
|
165
200
|
this.#opts = {
|
|
166
201
|
apiKey: opts.apiKey,
|
|
167
|
-
baseUrl: (opts.baseUrl ?? "https://
|
|
202
|
+
baseUrl: (opts.baseUrl ?? "https://frag.cash").replace(/\/$/, ""),
|
|
168
203
|
fetch: opts.fetch ?? globalThis.fetch.bind(globalThis),
|
|
169
204
|
timeout: opts.timeout ?? 3e4,
|
|
170
205
|
retries: opts.retries ?? 2,
|
|
@@ -176,8 +211,16 @@ var FragmentPay = class {
|
|
|
176
211
|
this.payouts = new PayoutResource(req);
|
|
177
212
|
this.webhookEndpoints = new WebhookEndpointResource(req);
|
|
178
213
|
this.tokens = new TokenResource(req);
|
|
214
|
+
this.checkout = new PublicCheckoutResource(req);
|
|
179
215
|
this.webhooks = new WebhookHelpers();
|
|
180
216
|
}
|
|
217
|
+
/**
|
|
218
|
+
* Unauthenticated health probe against the API host. Safe to call from
|
|
219
|
+
* uptime monitors and CI. Returns `{ status: "ok" | "degraded" | "down", ... }`.
|
|
220
|
+
*/
|
|
221
|
+
health() {
|
|
222
|
+
return this.#req(`/api/public/health`, { skipAuth: true });
|
|
223
|
+
}
|
|
181
224
|
setDebug(on) {
|
|
182
225
|
this.#opts.debug = on;
|
|
183
226
|
}
|
|
@@ -187,7 +230,7 @@ var FragmentPay = class {
|
|
|
187
230
|
).toString() : "";
|
|
188
231
|
const url = `${this.#opts.baseUrl}${path}${query}`;
|
|
189
232
|
const headers = new Headers(init.headers);
|
|
190
|
-
headers.set("authorization", `Bearer ${this.#opts.apiKey}`);
|
|
233
|
+
if (!init.skipAuth) headers.set("authorization", `Bearer ${this.#opts.apiKey}`);
|
|
191
234
|
if (init.body && !headers.has("content-type")) headers.set("content-type", "application/json");
|
|
192
235
|
if (init.idempotencyKey) headers.set("idempotency-key", init.idempotencyKey);
|
|
193
236
|
const maxAttempts = Math.max(1, this.#opts.retries + 1);
|
|
@@ -251,16 +294,35 @@ var PaymentIntentResource = class {
|
|
|
251
294
|
}
|
|
252
295
|
req;
|
|
253
296
|
create(input) {
|
|
254
|
-
if (!input || typeof input !== "object")
|
|
297
|
+
if (!input || typeof input !== "object")
|
|
298
|
+
throw new FragValidationError("createIntent", "expected object");
|
|
255
299
|
const amount = v.num("createIntent.amount", input.amount, { min: 0.01, max: 1e7 });
|
|
256
|
-
const currency = v.optStr("createIntent.currency", input.currency, {
|
|
300
|
+
const currency = v.optStr("createIntent.currency", input.currency, {
|
|
301
|
+
min: 3,
|
|
302
|
+
max: 8,
|
|
303
|
+
pattern: /^[A-Z]{3,8}$/
|
|
304
|
+
}) ?? "USD";
|
|
257
305
|
if (input.settlement !== void 0) v.obj("createIntent.settlement", input.settlement);
|
|
258
306
|
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
|
-
|
|
307
|
+
const settlementToken = v.optStr("createIntent.settlement.token", input.settlement?.token, {
|
|
308
|
+
min: 1,
|
|
309
|
+
max: 64
|
|
310
|
+
});
|
|
311
|
+
const settlementAddress = v.optStr(
|
|
312
|
+
"createIntent.settlement.address",
|
|
313
|
+
input.settlement?.address,
|
|
314
|
+
{ min: 26, max: 128 }
|
|
315
|
+
);
|
|
261
316
|
const customerEmail = input.customerEmail !== void 0 ? v.email("createIntent.customerEmail", input.customerEmail) : void 0;
|
|
262
|
-
const expiresInSeconds = v.optNum("createIntent.expiresInSeconds", input.expiresInSeconds, {
|
|
263
|
-
|
|
317
|
+
const expiresInSeconds = v.optNum("createIntent.expiresInSeconds", input.expiresInSeconds, {
|
|
318
|
+
min: 60,
|
|
319
|
+
max: 86400,
|
|
320
|
+
int: true
|
|
321
|
+
});
|
|
322
|
+
const idempotencyKey = v.optStr("createIntent.idempotencyKey", input.idempotencyKey, {
|
|
323
|
+
min: 8,
|
|
324
|
+
max: 128
|
|
325
|
+
});
|
|
264
326
|
const successUrl = input.successUrl !== void 0 ? v.url("createIntent.successUrl", input.successUrl) : void 0;
|
|
265
327
|
const cancelUrl = input.cancelUrl !== void 0 ? v.url("createIntent.cancelUrl", input.cancelUrl) : void 0;
|
|
266
328
|
if (input.metadata !== void 0) v.obj("createIntent.metadata", input.metadata);
|
|
@@ -294,7 +356,10 @@ var PaymentIntentResource = class {
|
|
|
294
356
|
}
|
|
295
357
|
cancel(id) {
|
|
296
358
|
v.id("paymentIntents.cancel.id", id);
|
|
297
|
-
return this.req(
|
|
359
|
+
return this.req(
|
|
360
|
+
`/api/public/v1/payment-intents/${encodeURIComponent(id)}/cancel`,
|
|
361
|
+
{ method: "POST" }
|
|
362
|
+
);
|
|
298
363
|
}
|
|
299
364
|
transactions(id) {
|
|
300
365
|
v.id("paymentIntents.transactions.id", id);
|
|
@@ -315,10 +380,11 @@ var RefundResource = class {
|
|
|
315
380
|
}
|
|
316
381
|
req;
|
|
317
382
|
create(input) {
|
|
318
|
-
if (!input || typeof input !== "object")
|
|
383
|
+
if (!input || typeof input !== "object")
|
|
384
|
+
throw new FragValidationError("refunds.create", "expected object");
|
|
319
385
|
v.id("refunds.create.paymentIntentId", input.paymentIntentId);
|
|
320
386
|
v.num("refunds.create.amountUsd", input.amountUsd, { min: 0.01, max: 1e7 });
|
|
321
|
-
v.
|
|
387
|
+
v.optStr("refunds.create.destination", input.destination, { min: 26, max: 128 });
|
|
322
388
|
v.optStr("refunds.create.reason", input.reason, { max: 500 });
|
|
323
389
|
v.optStr("refunds.create.idempotencyKey", input.idempotencyKey, { min: 8, max: 128 });
|
|
324
390
|
return this.req("/api/public/v1/refunds", {
|
|
@@ -337,12 +403,48 @@ var RefundResource = class {
|
|
|
337
403
|
return this.req(`/api/public/v1/refunds/${encodeURIComponent(id)}`);
|
|
338
404
|
}
|
|
339
405
|
list(params = {}) {
|
|
340
|
-
if (params.paymentIntentId !== void 0)
|
|
406
|
+
if (params.paymentIntentId !== void 0)
|
|
407
|
+
v.id("refunds.list.paymentIntentId", params.paymentIntentId);
|
|
408
|
+
v.optEnum("refunds.list.status", params.status, REFUND_STATUSES);
|
|
341
409
|
v.optNum("refunds.list.limit", params.limit, { min: 1, max: 100, int: true });
|
|
342
410
|
return this.req(`/api/public/v1/refunds`, {
|
|
343
|
-
query: {
|
|
411
|
+
query: {
|
|
412
|
+
payment_intent_id: params.paymentIntentId,
|
|
413
|
+
status: params.status,
|
|
414
|
+
limit: params.limit
|
|
415
|
+
}
|
|
344
416
|
});
|
|
345
417
|
}
|
|
418
|
+
/**
|
|
419
|
+
* Fetch the payer-facing safe status view for a refund. UNAUTHENTICATED —
|
|
420
|
+
* safe to expose in a customer-facing page or email. Redacts the destination
|
|
421
|
+
* address (only the last 6 chars) and omits merchant PII. The returned
|
|
422
|
+
* `phase` field is a stable 4-bucket categorization
|
|
423
|
+
* (`queued | sent | completed | failed`) computed on the client for
|
|
424
|
+
* convenient status pills.
|
|
425
|
+
*/
|
|
426
|
+
async status(id) {
|
|
427
|
+
v.id("refunds.status.id", id);
|
|
428
|
+
const raw = await this.req(
|
|
429
|
+
`/api/public/refunds/${encodeURIComponent(id)}/status`,
|
|
430
|
+
{ skipAuth: true }
|
|
431
|
+
);
|
|
432
|
+
return { ...raw, phase: refundPhase(raw.status) };
|
|
433
|
+
}
|
|
434
|
+
/** Client-side helper: bucket a raw {@link RefundStatus} to a UI phase. */
|
|
435
|
+
phase(status) {
|
|
436
|
+
return refundPhase(status);
|
|
437
|
+
}
|
|
438
|
+
};
|
|
439
|
+
var PublicCheckoutResource = class {
|
|
440
|
+
constructor(req) {
|
|
441
|
+
this.req = req;
|
|
442
|
+
}
|
|
443
|
+
req;
|
|
444
|
+
retrieve(id) {
|
|
445
|
+
v.id("checkout.retrieve.id", id);
|
|
446
|
+
return this.req(`/api/public/v1/checkout/${encodeURIComponent(id)}`, { skipAuth: true });
|
|
447
|
+
}
|
|
346
448
|
};
|
|
347
449
|
var PayoutResource = class {
|
|
348
450
|
constructor(req) {
|
|
@@ -355,10 +457,15 @@ var PayoutResource = class {
|
|
|
355
457
|
}
|
|
356
458
|
list(params = {}) {
|
|
357
459
|
v.optEnum("payouts.list.status", params.status, PAYOUT_STATUSES);
|
|
358
|
-
if (params.paymentIntentId !== void 0)
|
|
460
|
+
if (params.paymentIntentId !== void 0)
|
|
461
|
+
v.id("payouts.list.paymentIntentId", params.paymentIntentId);
|
|
359
462
|
v.optNum("payouts.list.limit", params.limit, { min: 1, max: 100, int: true });
|
|
360
463
|
return this.req(`/api/public/v1/payouts`, {
|
|
361
|
-
query: {
|
|
464
|
+
query: {
|
|
465
|
+
status: params.status,
|
|
466
|
+
payment_intent_id: params.paymentIntentId,
|
|
467
|
+
limit: params.limit
|
|
468
|
+
}
|
|
362
469
|
});
|
|
363
470
|
}
|
|
364
471
|
replay(id) {
|
|
@@ -372,12 +479,18 @@ var WebhookEndpointResource = class {
|
|
|
372
479
|
}
|
|
373
480
|
req;
|
|
374
481
|
create(input) {
|
|
375
|
-
if (!input || typeof input !== "object")
|
|
482
|
+
if (!input || typeof input !== "object")
|
|
483
|
+
throw new FragValidationError("webhookEndpoints.create", "expected object");
|
|
376
484
|
v.url("webhookEndpoints.create.url", input.url);
|
|
377
485
|
if (!Array.isArray(input.events) || input.events.length === 0) {
|
|
378
|
-
throw new FragValidationError(
|
|
486
|
+
throw new FragValidationError(
|
|
487
|
+
"webhookEndpoints.create.events",
|
|
488
|
+
"at least one event required"
|
|
489
|
+
);
|
|
379
490
|
}
|
|
380
|
-
input.events.forEach(
|
|
491
|
+
input.events.forEach(
|
|
492
|
+
(e, i) => v.enum(`webhookEndpoints.create.events[${i}]`, e, WEBHOOK_EVENTS)
|
|
493
|
+
);
|
|
381
494
|
return this.req("/api/public/v1/webhook-endpoints", {
|
|
382
495
|
method: "POST",
|
|
383
496
|
body: JSON.stringify(input)
|
|
@@ -394,8 +507,11 @@ var WebhookEndpointResource = class {
|
|
|
394
507
|
v.id("webhookEndpoints.update.id", id);
|
|
395
508
|
if (patch.url !== void 0) v.url("webhookEndpoints.update.url", patch.url);
|
|
396
509
|
if (patch.events !== void 0) {
|
|
397
|
-
if (!Array.isArray(patch.events))
|
|
398
|
-
|
|
510
|
+
if (!Array.isArray(patch.events))
|
|
511
|
+
throw new FragValidationError("webhookEndpoints.update.events", "expected array");
|
|
512
|
+
patch.events.forEach(
|
|
513
|
+
(e, i) => v.enum(`webhookEndpoints.update.events[${i}]`, e, WEBHOOK_EVENTS)
|
|
514
|
+
);
|
|
399
515
|
}
|
|
400
516
|
return this.req(`/api/public/v1/webhook-endpoints/${encodeURIComponent(id)}`, {
|
|
401
517
|
method: "PATCH",
|
|
@@ -404,11 +520,16 @@ var WebhookEndpointResource = class {
|
|
|
404
520
|
}
|
|
405
521
|
delete(id) {
|
|
406
522
|
v.id("webhookEndpoints.delete.id", id);
|
|
407
|
-
return this.req(`/api/public/v1/webhook-endpoints/${encodeURIComponent(id)}`, {
|
|
523
|
+
return this.req(`/api/public/v1/webhook-endpoints/${encodeURIComponent(id)}`, {
|
|
524
|
+
method: "DELETE"
|
|
525
|
+
});
|
|
408
526
|
}
|
|
409
527
|
rotateSecret(id) {
|
|
410
528
|
v.id("webhookEndpoints.rotateSecret.id", id);
|
|
411
|
-
return this.req(
|
|
529
|
+
return this.req(
|
|
530
|
+
`/api/public/v1/webhook-endpoints/${encodeURIComponent(id)}/rotate-secret`,
|
|
531
|
+
{ method: "POST" }
|
|
532
|
+
);
|
|
412
533
|
}
|
|
413
534
|
};
|
|
414
535
|
var TokenResource = class {
|
|
@@ -438,19 +559,34 @@ var WebhookHelpers = class {
|
|
|
438
559
|
}
|
|
439
560
|
};
|
|
440
561
|
async function verifyWebhook(input) {
|
|
441
|
-
if (!input || typeof input !== "object")
|
|
562
|
+
if (!input || typeof input !== "object")
|
|
563
|
+
throw new FragValidationError("verifyWebhook", "expected object");
|
|
442
564
|
v.str("verifyWebhook.payload", input.payload, { min: 1 });
|
|
443
565
|
v.str("verifyWebhook.secret", input.secret, { min: 8 });
|
|
444
|
-
if (input.tolerance !== void 0)
|
|
566
|
+
if (input.tolerance !== void 0)
|
|
567
|
+
v.num("verifyWebhook.tolerance", input.tolerance, { min: 0, max: 3600, int: true });
|
|
445
568
|
const helper = new WebhookHelpers();
|
|
446
|
-
const ok = await helper.verify(
|
|
447
|
-
|
|
569
|
+
const ok = await helper.verify(
|
|
570
|
+
input.payload,
|
|
571
|
+
input.signature ?? null,
|
|
572
|
+
input.secret,
|
|
573
|
+
input.tolerance
|
|
574
|
+
);
|
|
575
|
+
if (!ok)
|
|
576
|
+
throw new FragError(401, "invalid_signature", "webhook signature verification failed", null);
|
|
448
577
|
return helper.parse(input.payload);
|
|
449
578
|
}
|
|
450
579
|
var Frag = class extends FragmentPay {
|
|
451
580
|
constructor(opts) {
|
|
452
581
|
const apiKey = "secretKey" in opts ? opts.secretKey : opts.apiKey;
|
|
453
|
-
super({
|
|
582
|
+
super({
|
|
583
|
+
apiKey,
|
|
584
|
+
baseUrl: opts.baseUrl,
|
|
585
|
+
fetch: opts.fetch,
|
|
586
|
+
timeout: opts.timeout,
|
|
587
|
+
retries: opts.retries,
|
|
588
|
+
debug: opts.debug
|
|
589
|
+
});
|
|
454
590
|
}
|
|
455
591
|
};
|
|
456
592
|
async function hmacHex(secret, message) {
|
|
@@ -476,5 +612,6 @@ function timingSafeEqualHex(a, b) {
|
|
|
476
612
|
FragError,
|
|
477
613
|
FragValidationError,
|
|
478
614
|
FragmentPay,
|
|
615
|
+
refundPhase,
|
|
479
616
|
verifyWebhook
|
|
480
617
|
});
|