priceos 1.0.21 → 1.0.23

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/next.cjs CHANGED
@@ -58,19 +58,16 @@ var getRetryDelayMs = (retryCount) => {
58
58
  return Math.min(RETRY_DELAY_CAP_MS, backoff + jitter);
59
59
  };
60
60
  var resolveIdempotencyKey = (input) => {
61
- const providedEventKey = typeof input.eventKey === "string" ? input.eventKey.trim() : "";
62
61
  const providedIdempotencyKey = typeof input.idempotencyKey === "string" ? String(input.idempotencyKey).trim() : "";
63
- return providedIdempotencyKey || providedEventKey;
62
+ return providedIdempotencyKey;
64
63
  };
65
64
  var withIdempotencyKey = (input) => {
66
65
  const idempotencyKey = resolveIdempotencyKey(input);
67
66
  if (!idempotencyKey) return input;
68
- const next = {
67
+ return {
69
68
  ...input,
70
69
  idempotencyKey
71
70
  };
72
- delete next.eventKey;
73
- return next;
74
71
  };
75
72
  var createLogger = (logLevel) => {
76
73
  const shouldLog = (level) => logLevel !== "none" && LOG_LEVELS[level] <= LOG_LEVELS[logLevel];
@@ -145,6 +142,22 @@ var throwRequestError = (log, error, response, context) => {
145
142
  log.error("Request failed", { context, status: response?.status, error });
146
143
  throw new PriceOSError(getErrorMessage(error), { status: response?.status, details: error });
147
144
  };
145
+ var resolveUrlResponse = (data, response) => {
146
+ if (data && typeof data === "object" && !Array.isArray(data)) {
147
+ const value = data.url;
148
+ if (typeof value === "string" && value.trim().length > 0) {
149
+ return { url: value };
150
+ }
151
+ }
152
+ const location = response?.headers.get("location");
153
+ if (location && location.trim().length > 0) {
154
+ return { url: location };
155
+ }
156
+ if (response?.redirected && typeof response.url === "string" && response.url.trim().length > 0) {
157
+ return { url: response.url };
158
+ }
159
+ return null;
160
+ };
148
161
  var createRetryingFetch = (baseFetch, log) => {
149
162
  return async (input, init) => {
150
163
  const { method, url } = getRequestDetails(input, init);
@@ -259,9 +272,14 @@ var PriceOS = class {
259
272
  }
260
273
  });
261
274
  this.customers = {
262
- get: async (customerId) => {
275
+ get: async (customerId, options) => {
276
+ const query = options?.expand?.length ? { expand: options.expand } : void 0;
263
277
  const { data, error, response } = await this.client.GET("/v1/customers/{customerId}", {
264
- params: { path: { customerId }, header: this.header }
278
+ params: {
279
+ path: { customerId },
280
+ ...query ? { query } : {},
281
+ header: this.header
282
+ }
265
283
  });
266
284
  if (error) throwRequestError(this.log, error, response, "GET /v1/customers/{customerId}");
267
285
  return data ?? null;
@@ -291,6 +309,34 @@ var PriceOS = class {
291
309
  if (error) throwRequestError(this.log, error, response, "PUT /v1/customers/{customerId}");
292
310
  return data;
293
311
  },
312
+ addCustomProduct: async (input) => {
313
+ const { customerId, ...body } = input;
314
+ const { data, error, response } = await this.client.POST(
315
+ "/v1/customers/{customerId}/custom-product/add",
316
+ {
317
+ params: { path: { customerId }, header: this.header },
318
+ body
319
+ }
320
+ );
321
+ if (error) {
322
+ throwRequestError(this.log, error, response, "POST /v1/customers/{customerId}/custom-product/add");
323
+ }
324
+ return data;
325
+ },
326
+ removeCustomProduct: async (input) => {
327
+ const { customerId, ...body } = input;
328
+ const { data, error, response } = await this.client.POST(
329
+ "/v1/customers/{customerId}/custom-product/remove",
330
+ {
331
+ params: { path: { customerId }, header: this.header },
332
+ body
333
+ }
334
+ );
335
+ if (error) {
336
+ throwRequestError(this.log, error, response, "POST /v1/customers/{customerId}/custom-product/remove");
337
+ }
338
+ return data;
339
+ },
294
340
  delete: async (customerId) => {
295
341
  const { data, error, response } = await this.client.DELETE("/v1/customers/{customerId}", {
296
342
  params: { path: { customerId }, header: this.header }
@@ -308,7 +354,43 @@ var PriceOS = class {
308
354
  if (error) {
309
355
  throwRequestError(this.log, error, response, "POST /v1/customers/{customerId}/customer_portal");
310
356
  }
311
- return data;
357
+ if (response && !response.ok) {
358
+ throw new PriceOSError(response.statusText || "Request failed", { status: response.status });
359
+ }
360
+ const result = resolveUrlResponse(data, response);
361
+ if (result) {
362
+ return result;
363
+ }
364
+ throw new PriceOSError("Invalid customer portal response", {
365
+ status: response?.status,
366
+ details: {
367
+ redirected: response?.redirected ?? false,
368
+ responseUrl: response?.url ?? null
369
+ }
370
+ });
371
+ },
372
+ createCheckout: async (customerId, input) => {
373
+ const { data, error, response } = await this.client.POST("/v1/customers/{customerId}/checkout", {
374
+ params: { path: { customerId }, header: this.header },
375
+ body: input
376
+ });
377
+ if (error) {
378
+ throwRequestError(this.log, error, response, "POST /v1/customers/{customerId}/checkout");
379
+ }
380
+ if (response && !response.ok) {
381
+ throw new PriceOSError(response.statusText || "Request failed", { status: response.status });
382
+ }
383
+ const result = resolveUrlResponse(data, response);
384
+ if (result) {
385
+ return result;
386
+ }
387
+ throw new PriceOSError("Invalid checkout response", {
388
+ status: response?.status,
389
+ details: {
390
+ redirected: response?.redirected ?? false,
391
+ responseUrl: response?.url ?? null
392
+ }
393
+ });
312
394
  }
313
395
  };
314
396
  const getFeatureAccessForCustomer = async (customerId) => {
@@ -346,7 +428,20 @@ var PriceOS = class {
346
428
  body: input
347
429
  });
348
430
  if (error) throwRequestError(this.log, error, response, "POST /v1/checkout");
349
- return data;
431
+ if (response && !response.ok) {
432
+ throw new PriceOSError(response.statusText || "Request failed", { status: response.status });
433
+ }
434
+ const result = resolveUrlResponse(data, response);
435
+ if (result) {
436
+ return result;
437
+ }
438
+ throw new PriceOSError("Invalid checkout response", {
439
+ status: response?.status,
440
+ details: {
441
+ redirected: response?.redirected ?? false,
442
+ responseUrl: response?.url ?? null
443
+ }
444
+ });
350
445
  }
351
446
  };
352
447
  this.bonuses = {
@@ -408,24 +503,6 @@ var PriceOS = class {
408
503
  if (error) throwRequestError(this.log, error, response, "GET /v1/usage/{id}");
409
504
  return data;
410
505
  },
411
- getEventByIdempotencyKey: async (idempotencyKey) => {
412
- const { data, error, response } = await this.client.GET("/v1/usage/idempotency-key/{idempotencyKey}", {
413
- params: { header: this.header, path: { idempotencyKey } }
414
- });
415
- if (error) {
416
- throwRequestError(this.log, error, response, "GET /v1/usage/idempotency-key/{idempotencyKey}");
417
- }
418
- return data;
419
- },
420
- getEventByKey: async (eventKey) => {
421
- const { data, error, response } = await this.client.GET("/v1/usage/idempotency-key/{idempotencyKey}", {
422
- params: { header: this.header, path: { idempotencyKey: eventKey } }
423
- });
424
- if (error) {
425
- throwRequestError(this.log, error, response, "GET /v1/usage/idempotency-key/{idempotencyKey}");
426
- }
427
- return data;
428
- },
429
506
  updateEvent: async (input) => {
430
507
  const { id, ...body } = input;
431
508
  const { data, error, response } = await this.client.PUT("/v1/usage/{id}", {
@@ -435,15 +512,6 @@ var PriceOS = class {
435
512
  if (error) throwRequestError(this.log, error, response, "PUT /v1/usage/{id}");
436
513
  return data;
437
514
  },
438
- voidEvent: async (input) => {
439
- const { id, ...body } = input;
440
- const { data, error, response } = await this.client.POST("/v1/usage/{id}/void", {
441
- params: { header: this.header, path: { id } },
442
- body
443
- });
444
- if (error) throwRequestError(this.log, error, response, "POST /v1/usage/{id}/void");
445
- return data;
446
- },
447
515
  deleteEvent: async (eventId) => {
448
516
  const { data, error, response } = await this.client.DELETE("/v1/usage/{id}", {
449
517
  params: { header: this.header, path: { id: eventId } }
@@ -490,6 +558,7 @@ var FEATURE_ACCESS_PATH = "v1/feature-access";
490
558
  var TRACK_USAGE_PATH = "v1/usage";
491
559
  var PRICING_TABLE_PATH = "v1/pricing-table";
492
560
  var CHECKOUT_PATH = "v1/checkout";
561
+ var CUSTOMER_PORTAL_PATH = "v1/customer-portal";
493
562
  var normalizePath = (value) => value.replace(/^\/+/, "");
494
563
  var logTrackUsageError = (message, details) => {
495
564
  if (details === void 0) {
@@ -507,6 +576,18 @@ var normalizeCustomerId = (value) => {
507
576
  const normalized = value.trim();
508
577
  return normalized.length ? normalized : null;
509
578
  };
579
+ var parseCustomerExpand = (url) => {
580
+ const expandValues = [...url.searchParams.getAll("expand"), ...url.searchParams.getAll("expand[]")].flatMap((value) => value.split(",")).map((value) => value.trim()).filter((value) => value.length > 0);
581
+ if (!expandValues.length) {
582
+ return { data: {} };
583
+ }
584
+ const normalizedValues = Array.from(new Set(expandValues));
585
+ const invalidValue = normalizedValues.find((value) => value !== "subscriptions");
586
+ if (invalidValue) {
587
+ return { error: `Invalid expand value: ${invalidValue}` };
588
+ }
589
+ return { data: { expand: normalizedValues } };
590
+ };
510
591
  var parseTrackUsageBody = async (request) => {
511
592
  let rawBody;
512
593
  try {
@@ -530,7 +611,7 @@ var parseTrackUsageBody = async (request) => {
530
611
  if (!Number.isFinite(amount) || amount <= 0) {
531
612
  return { error: "amount must be a positive number" };
532
613
  }
533
- const idempotencyKeyRaw = body.idempotencyKey ?? body.eventKey;
614
+ const idempotencyKeyRaw = body.idempotencyKey;
534
615
  const idempotencyKey = idempotencyKeyRaw === void 0 ? void 0 : typeof idempotencyKeyRaw === "string" && idempotencyKeyRaw.trim().length > 0 ? idempotencyKeyRaw.trim() : null;
535
616
  if (idempotencyKey === null) {
536
617
  return { error: "idempotencyKey must be a non-empty string" };
@@ -595,13 +676,20 @@ var parseCheckoutBody = async (request) => {
595
676
  return { error: "Invalid JSON body" };
596
677
  }
597
678
  const body = rawBody;
598
- const stripePriceId = typeof body.stripePriceId === "string" && body.stripePriceId.trim().length > 0 ? body.stripePriceId.trim() : "";
599
- if (!stripePriceId) {
600
- return { error: "stripePriceId is required" };
679
+ const stripeProductKey = typeof body.stripeProductKey === "string" && body.stripeProductKey.trim().length > 0 ? body.stripeProductKey.trim() : void 0;
680
+ const stripePriceId = typeof body.stripePriceId === "string" && body.stripePriceId.trim().length > 0 ? body.stripePriceId.trim() : void 0;
681
+ if (!stripeProductKey && !stripePriceId) {
682
+ return { error: "stripeProductKey or stripePriceId is required" };
601
683
  }
602
- const successUrl = typeof body.successUrl === "string" && body.successUrl.trim().length > 0 ? body.successUrl.trim() : "";
603
- if (!successUrl) {
604
- return { error: "successUrl is required" };
684
+ const successUrlRaw = body.successUrl;
685
+ if (successUrlRaw !== void 0 && typeof successUrlRaw !== "string") {
686
+ return { error: "successUrl must be a non-empty string" };
687
+ }
688
+ const successUrl = normalizeCustomerId(
689
+ typeof successUrlRaw === "string" ? successUrlRaw : void 0
690
+ );
691
+ if (successUrlRaw !== void 0 && !successUrl) {
692
+ return { error: "successUrl must be a non-empty string" };
605
693
  }
606
694
  const cancelUrl = typeof body.cancelUrl === "string" && body.cancelUrl.trim().length > 0 ? body.cancelUrl.trim() : void 0;
607
695
  const customerId = typeof body.customerId === "string" && body.customerId.trim().length > 0 ? body.customerId.trim() : void 0;
@@ -616,16 +704,71 @@ var parseCheckoutBody = async (request) => {
616
704
  }
617
705
  }
618
706
  }
707
+ const checkoutParamsRaw = body.checkoutParams;
708
+ if (checkoutParamsRaw !== void 0 && (!checkoutParamsRaw || typeof checkoutParamsRaw !== "object" || Array.isArray(checkoutParamsRaw))) {
709
+ return { error: "checkoutParams must be an object" };
710
+ }
711
+ const customerInfoRaw = body.customerInfo;
712
+ if (customerInfoRaw !== void 0 && (!customerInfoRaw || typeof customerInfoRaw !== "object" || Array.isArray(customerInfoRaw))) {
713
+ return { error: "customerInfo must be an object" };
714
+ }
715
+ const customerInfoRecord = customerInfoRaw;
716
+ const customerName = normalizeCustomerId(
717
+ typeof customerInfoRecord?.name === "string" ? customerInfoRecord.name : void 0
718
+ );
719
+ const customerEmail = normalizeCustomerId(
720
+ typeof customerInfoRecord?.email === "string" ? customerInfoRecord.email : void 0
721
+ );
619
722
  return {
620
723
  data: {
621
- stripePriceId,
622
- successUrl,
724
+ ...stripeProductKey ? { stripeProductKey } : {},
725
+ ...stripePriceId ? { stripePriceId } : {},
726
+ ...successUrl ? { successUrl } : {},
623
727
  ...cancelUrl ? { cancelUrl } : {},
624
728
  ...customerId ? { customerId } : {},
625
- ...metadataRaw ? { metadata: metadataRaw } : {}
729
+ ...metadataRaw ? { metadata: metadataRaw } : {},
730
+ ...checkoutParamsRaw ? { checkoutParams: checkoutParamsRaw } : {},
731
+ ...customerInfoRaw ? {
732
+ customerInfo: {
733
+ ...customerName ? { name: customerName } : {},
734
+ ...customerEmail ? { email: customerEmail } : {}
735
+ }
736
+ } : {}
626
737
  }
627
738
  };
628
739
  };
740
+ var parseCustomerPortalBody = async (request) => {
741
+ let rawText = "";
742
+ try {
743
+ rawText = await request.text();
744
+ } catch {
745
+ return { error: "Invalid JSON body" };
746
+ }
747
+ if (!rawText.trim()) return { data: {} };
748
+ let rawBody;
749
+ try {
750
+ rawBody = JSON.parse(rawText);
751
+ } catch {
752
+ return { error: "Invalid JSON body" };
753
+ }
754
+ if (!rawBody || typeof rawBody !== "object" || Array.isArray(rawBody)) {
755
+ return { error: "Invalid JSON body" };
756
+ }
757
+ const body = rawBody;
758
+ const customerIdRaw = body.customerId;
759
+ if (customerIdRaw !== void 0 && typeof customerIdRaw !== "string") {
760
+ return { error: "customerId must be a non-empty string" };
761
+ }
762
+ const customerId = normalizeCustomerId(
763
+ typeof customerIdRaw === "string" ? customerIdRaw : void 0
764
+ );
765
+ if (customerIdRaw !== void 0 && !customerId) {
766
+ return { error: "customerId must be a non-empty string" };
767
+ }
768
+ return {
769
+ data: customerId ? { customerId } : {}
770
+ };
771
+ };
629
772
  var handleGetCustomer = async (request, url, client, getCustomerId) => {
630
773
  if (request.method.toUpperCase() !== "GET") {
631
774
  return new Response("Method not allowed.", { status: 405 });
@@ -637,8 +780,12 @@ var handleGetCustomer = async (request, url, client, getCustomerId) => {
637
780
  if (errorResponse || !customerId) {
638
781
  return errorResponse ?? jsonResponse(401, { error: "Customer not identified" });
639
782
  }
783
+ const expandResult = parseCustomerExpand(url);
784
+ if (!expandResult.data) {
785
+ return jsonResponse(400, { error: expandResult.error ?? "Invalid request" });
786
+ }
640
787
  try {
641
- const data = await client.customers.get(customerId);
788
+ const data = await client.customers.get(customerId, expandResult.data);
642
789
  return jsonResponse(200, data);
643
790
  } catch (error) {
644
791
  if (error instanceof PriceOSError) {
@@ -769,12 +916,79 @@ var handleCreateCheckout = async (request, client, getCustomerId) => {
769
916
  request
770
917
  );
771
918
  if (errorResponse) return errorResponse;
919
+ if (parsed.data.customerId && resolvedCustomerId && parsed.data.customerId !== resolvedCustomerId) {
920
+ return jsonResponse(403, { error: "customerId must match authenticated customer" });
921
+ }
922
+ const customerId = parsed.data.customerId ?? resolvedCustomerId;
923
+ if (!customerId) {
924
+ return jsonResponse(401, { error: "Customer not identified" });
925
+ }
926
+ if (parsed.data.stripePriceId) {
927
+ const requestOrigin = new URL(request.url).origin;
928
+ const successUrl = parsed.data.successUrl ?? `${requestOrigin}/settings/billing?checkout=success`;
929
+ const cancelUrl = parsed.data.cancelUrl ?? successUrl;
930
+ try {
931
+ const data = await client.checkout.create({
932
+ stripePriceId: parsed.data.stripePriceId,
933
+ successUrl,
934
+ cancelUrl,
935
+ ...parsed.data.metadata ? { metadata: parsed.data.metadata } : {},
936
+ customerId
937
+ });
938
+ return jsonResponse(200, data);
939
+ } catch (error) {
940
+ if (error instanceof PriceOSError) {
941
+ return jsonResponse(error.status ?? 500, { error: error.message });
942
+ }
943
+ return jsonResponse(500, { error: "Request failed" });
944
+ }
945
+ }
946
+ if (!parsed.data.stripeProductKey) {
947
+ return jsonResponse(400, { error: "stripeProductKey is required" });
948
+ }
949
+ const checkoutParams = {
950
+ ...parsed.data.checkoutParams ?? {},
951
+ ...parsed.data.cancelUrl ? { cancel_url: parsed.data.cancelUrl } : {},
952
+ ...parsed.data.metadata ? { metadata: parsed.data.metadata } : {}
953
+ };
772
954
  const body = {
773
- ...parsed.data,
774
- ...parsed.data.customerId ?? resolvedCustomerId ? { customerId: parsed.data.customerId ?? resolvedCustomerId ?? void 0 } : {}
955
+ stripeProductKey: parsed.data.stripeProductKey,
956
+ ...parsed.data.successUrl ? { successUrl: parsed.data.successUrl } : {},
957
+ ...parsed.data.customerInfo ? { customerInfo: parsed.data.customerInfo } : {},
958
+ ...Object.keys(checkoutParams).length ? { checkoutParams } : {}
775
959
  };
776
960
  try {
777
- const data = await client.checkout.create(body);
961
+ const data = await client.customers.createCheckout(customerId, body);
962
+ return jsonResponse(200, data);
963
+ } catch (error) {
964
+ if (error instanceof PriceOSError) {
965
+ return jsonResponse(error.status ?? 500, { error: error.message });
966
+ }
967
+ return jsonResponse(500, { error: "Request failed" });
968
+ }
969
+ };
970
+ var handleCreateCustomerPortal = async (request, client, getCustomerId) => {
971
+ if (request.method.toUpperCase() !== "POST") {
972
+ return new Response("Method not allowed.", { status: 405 });
973
+ }
974
+ const parsed = await parseCustomerPortalBody(request);
975
+ if (!parsed.data) {
976
+ return jsonResponse(400, { error: parsed.error ?? "Invalid request body" });
977
+ }
978
+ const { customerId: resolvedCustomerId, errorResponse } = await resolveOptionalCustomerId(
979
+ getCustomerId,
980
+ request
981
+ );
982
+ if (errorResponse) return errorResponse;
983
+ if (parsed.data.customerId && resolvedCustomerId && parsed.data.customerId !== resolvedCustomerId) {
984
+ return jsonResponse(403, { error: "customerId must match authenticated customer" });
985
+ }
986
+ const customerId = parsed.data.customerId ?? resolvedCustomerId;
987
+ if (!customerId) {
988
+ return jsonResponse(401, { error: "Customer not identified" });
989
+ }
990
+ try {
991
+ const data = await client.customers.createPortal(customerId);
778
992
  return jsonResponse(200, data);
779
993
  } catch (error) {
780
994
  if (error instanceof PriceOSError) {
@@ -823,6 +1037,9 @@ function priceosHandler(options = {}) {
823
1037
  if (path === CHECKOUT_PATH) {
824
1038
  return handleCreateCheckout(request, client, getCustomerId);
825
1039
  }
1040
+ if (path === CUSTOMER_PORTAL_PATH) {
1041
+ return handleCreateCustomerPortal(request, client, getCustomerId);
1042
+ }
826
1043
  return new Response("Not found.", { status: 404 });
827
1044
  };
828
1045
  return Object.assign(handler, {