@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.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)) return fail(path, `expected finite number, got ${JSON.stringify(val)}`);
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 = ["solana", "ethereum", "base", "arbitrum", "optimism", "polygon", "bnb"];
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("options.apiKey", "must match sk_test_\u2026 or sk_live_\u2026 (min 8 chars after prefix)");
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) v.num("options.timeout", opts.timeout, { min: 0, max: 6e5, int: true });
164
- if (opts.retries !== void 0) v.num("options.retries", opts.retries, { min: 0, max: 10, int: true });
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://api.frag.dev").replace(/\/$/, ""),
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") throw new FragValidationError("createIntent", "expected 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, { min: 3, max: 8, pattern: /^[A-Z]{3,8}$/ }) ?? "USD";
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, { min: 1, max: 64 });
260
- const settlementAddress = v.optStr("createIntent.settlement.address", input.settlement?.address, { min: 26, max: 128 });
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, { min: 60, max: 86400, int: true });
263
- const idempotencyKey = v.optStr("createIntent.idempotencyKey", input.idempotencyKey, { min: 8, max: 128 });
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(`/api/public/v1/payment-intents/${encodeURIComponent(id)}/cancel`, { method: "POST" });
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") throw new FragValidationError("refunds.create", "expected 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.str("refunds.create.destination", input.destination, { min: 26, max: 128 });
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) v.id("refunds.list.paymentIntentId", params.paymentIntentId);
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: { payment_intent_id: params.paymentIntentId, limit: params.limit }
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) v.id("payouts.list.paymentIntentId", params.paymentIntentId);
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: { status: params.status, payment_intent_id: params.paymentIntentId, limit: params.limit }
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") throw new FragValidationError("webhookEndpoints.create", "expected 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("webhookEndpoints.create.events", "at least one event required");
486
+ throw new FragValidationError(
487
+ "webhookEndpoints.create.events",
488
+ "at least one event required"
489
+ );
379
490
  }
380
- input.events.forEach((e, i) => v.enum(`webhookEndpoints.create.events[${i}]`, e, WEBHOOK_EVENTS));
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)) throw new FragValidationError("webhookEndpoints.update.events", "expected array");
398
- patch.events.forEach((e, i) => v.enum(`webhookEndpoints.update.events[${i}]`, e, WEBHOOK_EVENTS));
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)}`, { method: "DELETE" });
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(`/api/public/v1/webhook-endpoints/${encodeURIComponent(id)}/rotate-secret`, { method: "POST" });
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") throw new FragValidationError("verifyWebhook", "expected 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) v.num("verifyWebhook.tolerance", input.tolerance, { min: 0, max: 3600, int: true });
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(input.payload, input.signature ?? null, input.secret, input.tolerance);
447
- if (!ok) throw new FragError(401, "invalid_signature", "webhook signature verification failed", null);
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({ apiKey, baseUrl: opts.baseUrl, fetch: opts.fetch, timeout: opts.timeout, retries: opts.retries, debug: opts.debug });
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
  });