@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/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)) return fail(path, `expected finite number, got ${JSON.stringify(val)}`);
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 = ["solana", "ethereum", "base", "arbitrum", "optimism", "polygon", "bnb"];
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",
@@ -79,6 +88,13 @@ var INTENT_STATUSES = [
79
88
  "refunded"
80
89
  ];
81
90
  var PAYOUT_STATUSES = ["queued", "pending", "paid", "failed", "paused"];
91
+ var REFUND_STATUSES = [
92
+ "pending",
93
+ "processing",
94
+ "retry_scheduled",
95
+ "succeeded",
96
+ "failed"
97
+ ];
82
98
  var WEBHOOK_EVENTS = [
83
99
  "payment_intent.created",
84
100
  "payment_intent.quoted",
@@ -100,6 +116,18 @@ var WEBHOOK_EVENTS = [
100
116
  ];
101
117
 
102
118
  // src/index.ts
119
+ function refundPhase(status) {
120
+ switch (status) {
121
+ case "succeeded":
122
+ return "completed";
123
+ case "processing":
124
+ return "sent";
125
+ case "failed":
126
+ return "failed";
127
+ default:
128
+ return "queued";
129
+ }
130
+ }
103
131
  var FragError = class extends Error {
104
132
  status;
105
133
  code;
@@ -121,20 +149,26 @@ var FragmentPay = class {
121
149
  webhookEndpoints;
122
150
  webhooks;
123
151
  tokens;
152
+ checkout;
124
153
  #opts;
125
154
  constructor(opts) {
126
155
  if (!opts || typeof opts !== "object") {
127
156
  throw new FragValidationError("options", "expected FragOptions object");
128
157
  }
129
158
  if (typeof opts.apiKey !== "string" || !/^sk_(test|live)_[A-Za-z0-9_-]{8,}$/.test(opts.apiKey)) {
130
- throw new FragValidationError("options.apiKey", "must match sk_test_\u2026 or sk_live_\u2026 (min 8 chars after prefix)");
159
+ throw new FragValidationError(
160
+ "options.apiKey",
161
+ "must match sk_test_\u2026 or sk_live_\u2026 (min 8 chars after prefix)"
162
+ );
131
163
  }
132
164
  if (opts.baseUrl !== void 0) v.url("options.baseUrl", opts.baseUrl);
133
- if (opts.timeout !== void 0) v.num("options.timeout", opts.timeout, { min: 0, max: 6e5, int: true });
134
- if (opts.retries !== void 0) v.num("options.retries", opts.retries, { min: 0, max: 10, int: true });
165
+ if (opts.timeout !== void 0)
166
+ v.num("options.timeout", opts.timeout, { min: 0, max: 6e5, int: true });
167
+ if (opts.retries !== void 0)
168
+ v.num("options.retries", opts.retries, { min: 0, max: 10, int: true });
135
169
  this.#opts = {
136
170
  apiKey: opts.apiKey,
137
- baseUrl: (opts.baseUrl ?? "https://api.frag.dev").replace(/\/$/, ""),
171
+ baseUrl: (opts.baseUrl ?? "https://frag.cash").replace(/\/$/, ""),
138
172
  fetch: opts.fetch ?? globalThis.fetch.bind(globalThis),
139
173
  timeout: opts.timeout ?? 3e4,
140
174
  retries: opts.retries ?? 2,
@@ -146,8 +180,16 @@ var FragmentPay = class {
146
180
  this.payouts = new PayoutResource(req);
147
181
  this.webhookEndpoints = new WebhookEndpointResource(req);
148
182
  this.tokens = new TokenResource(req);
183
+ this.checkout = new PublicCheckoutResource(req);
149
184
  this.webhooks = new WebhookHelpers();
150
185
  }
186
+ /**
187
+ * Unauthenticated health probe against the API host. Safe to call from
188
+ * uptime monitors and CI. Returns `{ status: "ok" | "degraded" | "down", ... }`.
189
+ */
190
+ health() {
191
+ return this.#req(`/api/public/health`, { skipAuth: true });
192
+ }
151
193
  setDebug(on) {
152
194
  this.#opts.debug = on;
153
195
  }
@@ -157,7 +199,7 @@ var FragmentPay = class {
157
199
  ).toString() : "";
158
200
  const url = `${this.#opts.baseUrl}${path}${query}`;
159
201
  const headers = new Headers(init.headers);
160
- headers.set("authorization", `Bearer ${this.#opts.apiKey}`);
202
+ if (!init.skipAuth) headers.set("authorization", `Bearer ${this.#opts.apiKey}`);
161
203
  if (init.body && !headers.has("content-type")) headers.set("content-type", "application/json");
162
204
  if (init.idempotencyKey) headers.set("idempotency-key", init.idempotencyKey);
163
205
  const maxAttempts = Math.max(1, this.#opts.retries + 1);
@@ -221,16 +263,35 @@ var PaymentIntentResource = class {
221
263
  }
222
264
  req;
223
265
  create(input) {
224
- if (!input || typeof input !== "object") throw new FragValidationError("createIntent", "expected object");
266
+ if (!input || typeof input !== "object")
267
+ throw new FragValidationError("createIntent", "expected object");
225
268
  const amount = v.num("createIntent.amount", input.amount, { min: 0.01, max: 1e7 });
226
- const currency = v.optStr("createIntent.currency", input.currency, { min: 3, max: 8, pattern: /^[A-Z]{3,8}$/ }) ?? "USD";
269
+ const currency = v.optStr("createIntent.currency", input.currency, {
270
+ min: 3,
271
+ max: 8,
272
+ pattern: /^[A-Z]{3,8}$/
273
+ }) ?? "USD";
227
274
  if (input.settlement !== void 0) v.obj("createIntent.settlement", input.settlement);
228
275
  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, { min: 1, max: 64 });
230
- const settlementAddress = v.optStr("createIntent.settlement.address", input.settlement?.address, { min: 26, max: 128 });
276
+ const settlementToken = v.optStr("createIntent.settlement.token", input.settlement?.token, {
277
+ min: 1,
278
+ max: 64
279
+ });
280
+ const settlementAddress = v.optStr(
281
+ "createIntent.settlement.address",
282
+ input.settlement?.address,
283
+ { min: 26, max: 128 }
284
+ );
231
285
  const customerEmail = input.customerEmail !== void 0 ? v.email("createIntent.customerEmail", input.customerEmail) : void 0;
232
- const expiresInSeconds = v.optNum("createIntent.expiresInSeconds", input.expiresInSeconds, { min: 60, max: 86400, int: true });
233
- const idempotencyKey = v.optStr("createIntent.idempotencyKey", input.idempotencyKey, { min: 8, max: 128 });
286
+ const expiresInSeconds = v.optNum("createIntent.expiresInSeconds", input.expiresInSeconds, {
287
+ min: 60,
288
+ max: 86400,
289
+ int: true
290
+ });
291
+ const idempotencyKey = v.optStr("createIntent.idempotencyKey", input.idempotencyKey, {
292
+ min: 8,
293
+ max: 128
294
+ });
234
295
  const successUrl = input.successUrl !== void 0 ? v.url("createIntent.successUrl", input.successUrl) : void 0;
235
296
  const cancelUrl = input.cancelUrl !== void 0 ? v.url("createIntent.cancelUrl", input.cancelUrl) : void 0;
236
297
  if (input.metadata !== void 0) v.obj("createIntent.metadata", input.metadata);
@@ -264,7 +325,10 @@ var PaymentIntentResource = class {
264
325
  }
265
326
  cancel(id) {
266
327
  v.id("paymentIntents.cancel.id", id);
267
- return this.req(`/api/public/v1/payment-intents/${encodeURIComponent(id)}/cancel`, { method: "POST" });
328
+ return this.req(
329
+ `/api/public/v1/payment-intents/${encodeURIComponent(id)}/cancel`,
330
+ { method: "POST" }
331
+ );
268
332
  }
269
333
  transactions(id) {
270
334
  v.id("paymentIntents.transactions.id", id);
@@ -285,10 +349,11 @@ var RefundResource = class {
285
349
  }
286
350
  req;
287
351
  create(input) {
288
- if (!input || typeof input !== "object") throw new FragValidationError("refunds.create", "expected object");
352
+ if (!input || typeof input !== "object")
353
+ throw new FragValidationError("refunds.create", "expected object");
289
354
  v.id("refunds.create.paymentIntentId", input.paymentIntentId);
290
355
  v.num("refunds.create.amountUsd", input.amountUsd, { min: 0.01, max: 1e7 });
291
- v.str("refunds.create.destination", input.destination, { min: 26, max: 128 });
356
+ v.optStr("refunds.create.destination", input.destination, { min: 26, max: 128 });
292
357
  v.optStr("refunds.create.reason", input.reason, { max: 500 });
293
358
  v.optStr("refunds.create.idempotencyKey", input.idempotencyKey, { min: 8, max: 128 });
294
359
  return this.req("/api/public/v1/refunds", {
@@ -307,12 +372,48 @@ var RefundResource = class {
307
372
  return this.req(`/api/public/v1/refunds/${encodeURIComponent(id)}`);
308
373
  }
309
374
  list(params = {}) {
310
- if (params.paymentIntentId !== void 0) v.id("refunds.list.paymentIntentId", params.paymentIntentId);
375
+ if (params.paymentIntentId !== void 0)
376
+ v.id("refunds.list.paymentIntentId", params.paymentIntentId);
377
+ v.optEnum("refunds.list.status", params.status, REFUND_STATUSES);
311
378
  v.optNum("refunds.list.limit", params.limit, { min: 1, max: 100, int: true });
312
379
  return this.req(`/api/public/v1/refunds`, {
313
- query: { payment_intent_id: params.paymentIntentId, limit: params.limit }
380
+ query: {
381
+ payment_intent_id: params.paymentIntentId,
382
+ status: params.status,
383
+ limit: params.limit
384
+ }
314
385
  });
315
386
  }
387
+ /**
388
+ * Fetch the payer-facing safe status view for a refund. UNAUTHENTICATED —
389
+ * safe to expose in a customer-facing page or email. Redacts the destination
390
+ * address (only the last 6 chars) and omits merchant PII. The returned
391
+ * `phase` field is a stable 4-bucket categorization
392
+ * (`queued | sent | completed | failed`) computed on the client for
393
+ * convenient status pills.
394
+ */
395
+ async status(id) {
396
+ v.id("refunds.status.id", id);
397
+ const raw = await this.req(
398
+ `/api/public/refunds/${encodeURIComponent(id)}/status`,
399
+ { skipAuth: true }
400
+ );
401
+ return { ...raw, phase: refundPhase(raw.status) };
402
+ }
403
+ /** Client-side helper: bucket a raw {@link RefundStatus} to a UI phase. */
404
+ phase(status) {
405
+ return refundPhase(status);
406
+ }
407
+ };
408
+ var PublicCheckoutResource = class {
409
+ constructor(req) {
410
+ this.req = req;
411
+ }
412
+ req;
413
+ retrieve(id) {
414
+ v.id("checkout.retrieve.id", id);
415
+ return this.req(`/api/public/v1/checkout/${encodeURIComponent(id)}`, { skipAuth: true });
416
+ }
316
417
  };
317
418
  var PayoutResource = class {
318
419
  constructor(req) {
@@ -325,10 +426,15 @@ var PayoutResource = class {
325
426
  }
326
427
  list(params = {}) {
327
428
  v.optEnum("payouts.list.status", params.status, PAYOUT_STATUSES);
328
- if (params.paymentIntentId !== void 0) v.id("payouts.list.paymentIntentId", params.paymentIntentId);
429
+ if (params.paymentIntentId !== void 0)
430
+ v.id("payouts.list.paymentIntentId", params.paymentIntentId);
329
431
  v.optNum("payouts.list.limit", params.limit, { min: 1, max: 100, int: true });
330
432
  return this.req(`/api/public/v1/payouts`, {
331
- query: { status: params.status, payment_intent_id: params.paymentIntentId, limit: params.limit }
433
+ query: {
434
+ status: params.status,
435
+ payment_intent_id: params.paymentIntentId,
436
+ limit: params.limit
437
+ }
332
438
  });
333
439
  }
334
440
  replay(id) {
@@ -342,12 +448,18 @@ var WebhookEndpointResource = class {
342
448
  }
343
449
  req;
344
450
  create(input) {
345
- if (!input || typeof input !== "object") throw new FragValidationError("webhookEndpoints.create", "expected object");
451
+ if (!input || typeof input !== "object")
452
+ throw new FragValidationError("webhookEndpoints.create", "expected object");
346
453
  v.url("webhookEndpoints.create.url", input.url);
347
454
  if (!Array.isArray(input.events) || input.events.length === 0) {
348
- throw new FragValidationError("webhookEndpoints.create.events", "at least one event required");
455
+ throw new FragValidationError(
456
+ "webhookEndpoints.create.events",
457
+ "at least one event required"
458
+ );
349
459
  }
350
- input.events.forEach((e, i) => v.enum(`webhookEndpoints.create.events[${i}]`, e, WEBHOOK_EVENTS));
460
+ input.events.forEach(
461
+ (e, i) => v.enum(`webhookEndpoints.create.events[${i}]`, e, WEBHOOK_EVENTS)
462
+ );
351
463
  return this.req("/api/public/v1/webhook-endpoints", {
352
464
  method: "POST",
353
465
  body: JSON.stringify(input)
@@ -364,8 +476,11 @@ var WebhookEndpointResource = class {
364
476
  v.id("webhookEndpoints.update.id", id);
365
477
  if (patch.url !== void 0) v.url("webhookEndpoints.update.url", patch.url);
366
478
  if (patch.events !== void 0) {
367
- if (!Array.isArray(patch.events)) throw new FragValidationError("webhookEndpoints.update.events", "expected array");
368
- patch.events.forEach((e, i) => v.enum(`webhookEndpoints.update.events[${i}]`, e, WEBHOOK_EVENTS));
479
+ if (!Array.isArray(patch.events))
480
+ throw new FragValidationError("webhookEndpoints.update.events", "expected array");
481
+ patch.events.forEach(
482
+ (e, i) => v.enum(`webhookEndpoints.update.events[${i}]`, e, WEBHOOK_EVENTS)
483
+ );
369
484
  }
370
485
  return this.req(`/api/public/v1/webhook-endpoints/${encodeURIComponent(id)}`, {
371
486
  method: "PATCH",
@@ -374,11 +489,16 @@ var WebhookEndpointResource = class {
374
489
  }
375
490
  delete(id) {
376
491
  v.id("webhookEndpoints.delete.id", id);
377
- return this.req(`/api/public/v1/webhook-endpoints/${encodeURIComponent(id)}`, { method: "DELETE" });
492
+ return this.req(`/api/public/v1/webhook-endpoints/${encodeURIComponent(id)}`, {
493
+ method: "DELETE"
494
+ });
378
495
  }
379
496
  rotateSecret(id) {
380
497
  v.id("webhookEndpoints.rotateSecret.id", id);
381
- return this.req(`/api/public/v1/webhook-endpoints/${encodeURIComponent(id)}/rotate-secret`, { method: "POST" });
498
+ return this.req(
499
+ `/api/public/v1/webhook-endpoints/${encodeURIComponent(id)}/rotate-secret`,
500
+ { method: "POST" }
501
+ );
382
502
  }
383
503
  };
384
504
  var TokenResource = class {
@@ -408,19 +528,34 @@ var WebhookHelpers = class {
408
528
  }
409
529
  };
410
530
  async function verifyWebhook(input) {
411
- if (!input || typeof input !== "object") throw new FragValidationError("verifyWebhook", "expected object");
531
+ if (!input || typeof input !== "object")
532
+ throw new FragValidationError("verifyWebhook", "expected object");
412
533
  v.str("verifyWebhook.payload", input.payload, { min: 1 });
413
534
  v.str("verifyWebhook.secret", input.secret, { min: 8 });
414
- if (input.tolerance !== void 0) v.num("verifyWebhook.tolerance", input.tolerance, { min: 0, max: 3600, int: true });
535
+ if (input.tolerance !== void 0)
536
+ v.num("verifyWebhook.tolerance", input.tolerance, { min: 0, max: 3600, int: true });
415
537
  const helper = new WebhookHelpers();
416
- const ok = await helper.verify(input.payload, input.signature ?? null, input.secret, input.tolerance);
417
- if (!ok) throw new FragError(401, "invalid_signature", "webhook signature verification failed", null);
538
+ const ok = await helper.verify(
539
+ input.payload,
540
+ input.signature ?? null,
541
+ input.secret,
542
+ input.tolerance
543
+ );
544
+ if (!ok)
545
+ throw new FragError(401, "invalid_signature", "webhook signature verification failed", null);
418
546
  return helper.parse(input.payload);
419
547
  }
420
548
  var Frag = class extends FragmentPay {
421
549
  constructor(opts) {
422
550
  const apiKey = "secretKey" in opts ? opts.secretKey : opts.apiKey;
423
- super({ apiKey, baseUrl: opts.baseUrl, fetch: opts.fetch, timeout: opts.timeout, retries: opts.retries, debug: opts.debug });
551
+ super({
552
+ apiKey,
553
+ baseUrl: opts.baseUrl,
554
+ fetch: opts.fetch,
555
+ timeout: opts.timeout,
556
+ retries: opts.retries,
557
+ debug: opts.debug
558
+ });
424
559
  }
425
560
  };
426
561
  async function hmacHex(secret, message) {
@@ -445,5 +580,6 @@ export {
445
580
  FragError,
446
581
  FragValidationError,
447
582
  FragmentPay,
583
+ refundPhase,
448
584
  verifyWebhook
449
585
  };