@moonbase.sh/storefront-api 0.4.25 → 0.4.27

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
@@ -164,7 +164,7 @@ var storefrontProductSchema = z3.object({
164
164
  downloads: downloadSchema.array().optional(),
165
165
  defaultVariation: pricingVariationSchema.optional(),
166
166
  variations: pricingVariationSchema.array().optional(),
167
- type: z3.void().transform(() => "product").pipe(z3.literal("product"))
167
+ type: z3.string().optional().default("Product").pipe(z3.literal("Product"))
168
168
  });
169
169
  var storefrontBundleSchema = z3.object({
170
170
  id: z3.string(),
@@ -179,12 +179,28 @@ var storefrontBundleSchema = z3.object({
179
179
  })).array(),
180
180
  defaultVariation: pricingVariationSchema.optional(),
181
181
  variations: pricingVariationSchema.array().optional(),
182
- type: z3.void().transform(() => "bundle").pipe(z3.literal("bundle"))
182
+ type: z3.string().optional().default("Bundle").pipe(z3.literal("Bundle"))
183
+ });
184
+ var cartContainsItemsConditionSchema = z3.object({
185
+ type: z3.literal("CartContainsItems"),
186
+ minimumItems: z3.number(),
187
+ relevantItemVariations: z3.record(z3.string(), z3.string().array())
188
+ });
189
+ var offerConditionSchema = z3.discriminatedUnion("type", [cartContainsItemsConditionSchema]);
190
+ var storefrontOfferSchema = z3.object({
191
+ id: z3.string(),
192
+ target: z3.union([storefrontProductSchema, storefrontBundleSchema]),
193
+ targetVariations: z3.string().array(),
194
+ condition: offerConditionSchema,
195
+ discount: discountSchema
183
196
  });
184
197
  var storefrontSchema = z3.object({
185
198
  suggestedCurrency: z3.string(),
199
+ enabledCurrencies: z3.string().array(),
186
200
  products: storefrontProductSchema.array(),
187
- bundles: storefrontBundleSchema.array()
201
+ bundles: storefrontBundleSchema.array(),
202
+ // Offers need to be optional since we may still have old, cached representations in browsers
203
+ offers: storefrontOfferSchema.array().optional()
188
204
  });
189
205
 
190
206
  // src/activationRequests/models.ts
@@ -608,6 +624,132 @@ var ProductEndpoints = class {
608
624
  }
609
625
  };
610
626
 
627
+ // src/utils/api.ts
628
+ import fetch2 from "cross-fetch";
629
+ function objectToQuery(obj) {
630
+ return Object.entries(obj != null ? obj : {}).filter(([_, value]) => value !== void 0).map(([key, value]) => `${key}=${encodeURIComponent(value instanceof Date ? value.toISOString() : value)}`).join("&");
631
+ }
632
+ var MoonbaseApi = class {
633
+ constructor(baseUrl, tokenStore, logger) {
634
+ this.baseUrl = baseUrl;
635
+ this.tokenStore = tokenStore;
636
+ this.logger = logger;
637
+ }
638
+ async authenticatedFetch(path, schema, options) {
639
+ if (!this.tokenStore.user)
640
+ throw new NotAuthenticatedError();
641
+ return await this.fetch(path, schema, options);
642
+ }
643
+ async fetch(path, schema, options) {
644
+ var _a;
645
+ const accessToken = await this.tokenStore.getAccessToken();
646
+ const contentType = (_a = options == null ? void 0 : options.contentType) != null ? _a : "application/json";
647
+ this.logger.debug("Making request to Moonbase API...", {
648
+ path,
649
+ body: options == null ? void 0 : options.body
650
+ });
651
+ const startedAt = /* @__PURE__ */ new Date();
652
+ const request = {
653
+ method: (options == null ? void 0 : options.method) || "GET",
654
+ mode: "cors",
655
+ headers: {
656
+ "Accept": "application/json",
657
+ "Content-Type": contentType,
658
+ // While this fetch can be anonymous, we add the token if we have it
659
+ ...accessToken ? { Authorization: `Bearer ${accessToken}` } : {},
660
+ // Force CORS on all calls
661
+ "x-mb-cors": "1"
662
+ },
663
+ body: (options == null ? void 0 : options.body) ? contentType !== "application/json" ? options.body : JSON.stringify(options.body) : void 0,
664
+ signal: options == null ? void 0 : options.abort,
665
+ redirect: "manual"
666
+ };
667
+ const response = await fetch2(this.baseUrl + path, request);
668
+ const finishedAt = /* @__PURE__ */ new Date();
669
+ this.logger.debug("Received response from Moonbase", {
670
+ path,
671
+ status: response.status,
672
+ duration: finishedAt.getTime() - startedAt.getTime()
673
+ });
674
+ if (response.status >= 400) {
675
+ try {
676
+ await handleResponseProblem(response, this.logger);
677
+ } catch (err) {
678
+ this.reportRequestProblem(path, request, response, err);
679
+ throw err;
680
+ }
681
+ }
682
+ let json;
683
+ try {
684
+ json = schema ? await response.json() : null;
685
+ return {
686
+ data: schema ? schema.parse(json) : null,
687
+ headers: Object.fromEntries(response.headers.entries()),
688
+ status: response.status
689
+ };
690
+ } catch (err) {
691
+ this.logger.warn("Could not parse response", {
692
+ status: response.status,
693
+ path,
694
+ content: json || (response.bodyUsed ? "unknown" : await response.text()),
695
+ headers: Object.fromEntries(response.headers.entries()),
696
+ userAgent: window && window.navigator && window.navigator.userAgent,
697
+ err
698
+ });
699
+ this.reportParsingProblem(path, err, json || (response.bodyUsed ? "unknown" : await response.text()));
700
+ throw new MoonbaseError("Bad response", "Could not parse server response", response.status);
701
+ }
702
+ }
703
+ async reportParsingProblem(path, err, body) {
704
+ try {
705
+ await fetch2(`${this.baseUrl}/api/customer/insights/error`, {
706
+ mode: "cors",
707
+ method: "POST",
708
+ headers: {
709
+ "Accept": "application/json",
710
+ "Content-Type": "application/json"
711
+ },
712
+ body: JSON.stringify({
713
+ title: "Parse error",
714
+ detail: `Could not parse response body`,
715
+ path,
716
+ origin: window == null ? void 0 : window.location.href,
717
+ userAgent: window && window.navigator && window.navigator.userAgent,
718
+ err,
719
+ body
720
+ })
721
+ });
722
+ } catch (e) {
723
+ }
724
+ }
725
+ async reportRequestProblem(path, request, response, err) {
726
+ try {
727
+ await fetch2(`${this.baseUrl}/api/customer/insights/warn`, {
728
+ mode: "cors",
729
+ method: "POST",
730
+ headers: {
731
+ "Accept": "application/json",
732
+ "Content-Type": "application/json"
733
+ },
734
+ body: JSON.stringify({
735
+ title: "Request error",
736
+ detail: `Request failed with status ${response.status}`,
737
+ status: response.status,
738
+ request: {
739
+ ...request,
740
+ headers: void 0,
741
+ body: void 0
742
+ },
743
+ error: err,
744
+ path,
745
+ origin: window == null ? void 0 : window.location.href
746
+ })
747
+ });
748
+ } catch (e) {
749
+ }
750
+ }
751
+ };
752
+
611
753
  // src/inventory/subscriptions/endpoints.ts
612
754
  import { z as z13 } from "zod";
613
755
 
@@ -669,6 +811,7 @@ var openProductLineItem = z11.object({
669
811
  productId: z11.string(),
670
812
  quantity: z11.number(),
671
813
  variationId: z11.string(),
814
+ offerId: z11.string().optional(),
672
815
  isDefaultVariation: z11.boolean().nullish(),
673
816
  isSubcriptionPayment: z11.boolean().nullish(),
674
817
  price: priceCollectionSchema.optional(),
@@ -692,7 +835,9 @@ var openBundleLineItem = z11.object({
692
835
  bundleId: z11.string(),
693
836
  quantity: z11.number(),
694
837
  variationId: z11.string(),
838
+ offerId: z11.string().optional(),
695
839
  partial: z11.boolean().optional(),
840
+ replaced: z11.string().array().optional(),
696
841
  isDefaultVariation: z11.boolean().nullish(),
697
842
  isSubcriptionPayment: z11.boolean().nullish(),
698
843
  price: priceCollectionSchema.optional(),
@@ -765,6 +910,8 @@ var subscriptionSchema = z12.object({
765
910
  startedAt: z12.coerce.date(),
766
911
  total: orderTotalSchema,
767
912
  cycleLength: z12.nativeEnum(CycleLength),
913
+ paymentMethod: z12.string().optional(),
914
+ embeddedUpdatePaymentUrl: z12.string().optional(),
768
915
  content: z12.discriminatedUnion("type", [
769
916
  z12.object({
770
917
  type: z12.literal("Product"),
@@ -789,8 +936,8 @@ var SubscriptionEndpoints = class {
789
936
  const response = await this.api.authenticatedFetch(nextUrl || "/api/customer/inventory/subscriptions", paged(subscriptionSchema));
790
937
  return response.data;
791
938
  }
792
- async getById(subscriptionId) {
793
- const response = await this.api.authenticatedFetch(`/api/customer/inventory/subscriptions/${subscriptionId}`, subscriptionSchema);
939
+ async getById(subscriptionId, options) {
940
+ const response = await this.api.authenticatedFetch(`/api/customer/inventory/subscriptions/${subscriptionId}?${objectToQuery(options)}`, subscriptionSchema);
794
941
  return response.data;
795
942
  }
796
943
  async cancel(subscriptionId) {
@@ -881,129 +1028,6 @@ var StorefrontEndpoints = class {
881
1028
  }
882
1029
  };
883
1030
 
884
- // src/utils/api.ts
885
- import fetch2 from "cross-fetch";
886
- var MoonbaseApi = class {
887
- constructor(baseUrl, tokenStore, logger) {
888
- this.baseUrl = baseUrl;
889
- this.tokenStore = tokenStore;
890
- this.logger = logger;
891
- }
892
- async authenticatedFetch(path, schema, options) {
893
- if (!this.tokenStore.user)
894
- throw new NotAuthenticatedError();
895
- return await this.fetch(path, schema, options);
896
- }
897
- async fetch(path, schema, options) {
898
- var _a;
899
- const accessToken = await this.tokenStore.getAccessToken();
900
- const contentType = (_a = options == null ? void 0 : options.contentType) != null ? _a : "application/json";
901
- this.logger.debug("Making request to Moonbase API...", {
902
- path,
903
- body: options == null ? void 0 : options.body
904
- });
905
- const startedAt = /* @__PURE__ */ new Date();
906
- const request = {
907
- method: (options == null ? void 0 : options.method) || "GET",
908
- mode: "cors",
909
- headers: {
910
- "Accept": "application/json",
911
- "Content-Type": contentType,
912
- // While this fetch can be anonymous, we add the token if we have it
913
- ...accessToken ? { Authorization: `Bearer ${accessToken}` } : {},
914
- // Force CORS on all calls
915
- "x-mb-cors": "1"
916
- },
917
- body: (options == null ? void 0 : options.body) ? contentType !== "application/json" ? options.body : JSON.stringify(options.body) : void 0,
918
- signal: options == null ? void 0 : options.abort,
919
- redirect: "manual"
920
- };
921
- const response = await fetch2(this.baseUrl + path, request);
922
- const finishedAt = /* @__PURE__ */ new Date();
923
- this.logger.debug("Received response from Moonbase", {
924
- path,
925
- status: response.status,
926
- duration: finishedAt.getTime() - startedAt.getTime()
927
- });
928
- if (response.status >= 400) {
929
- try {
930
- await handleResponseProblem(response, this.logger);
931
- } catch (err) {
932
- this.reportRequestProblem(path, request, response, err);
933
- throw err;
934
- }
935
- }
936
- let json;
937
- try {
938
- json = schema ? await response.json() : null;
939
- return {
940
- data: schema ? schema.parse(json) : null,
941
- headers: Object.fromEntries(response.headers.entries()),
942
- status: response.status
943
- };
944
- } catch (err) {
945
- this.logger.warn("Could not parse response", {
946
- status: response.status,
947
- path,
948
- content: json || (response.bodyUsed ? "unknown" : await response.text()),
949
- headers: Object.fromEntries(response.headers.entries()),
950
- userAgent: window && window.navigator && window.navigator.userAgent,
951
- err
952
- });
953
- this.reportParsingProblem(path, err, json || (response.bodyUsed ? "unknown" : await response.text()));
954
- throw new MoonbaseError("Bad response", "Could not parse server response", response.status);
955
- }
956
- }
957
- async reportParsingProblem(path, err, body) {
958
- try {
959
- await fetch2(`${this.baseUrl}/api/customer/insights/error`, {
960
- mode: "cors",
961
- method: "POST",
962
- headers: {
963
- "Accept": "application/json",
964
- "Content-Type": "application/json"
965
- },
966
- body: JSON.stringify({
967
- title: "Parse error",
968
- detail: `Could not parse response body`,
969
- path,
970
- origin: window == null ? void 0 : window.location.href,
971
- userAgent: window && window.navigator && window.navigator.userAgent,
972
- err,
973
- body
974
- })
975
- });
976
- } catch (e) {
977
- }
978
- }
979
- async reportRequestProblem(path, request, response, err) {
980
- try {
981
- await fetch2(`${this.baseUrl}/api/customer/insights/warn`, {
982
- mode: "cors",
983
- method: "POST",
984
- headers: {
985
- "Accept": "application/json",
986
- "Content-Type": "application/json"
987
- },
988
- body: JSON.stringify({
989
- title: "Request error",
990
- detail: `Request failed with status ${response.status}`,
991
- status: response.status,
992
- request: {
993
- ...request,
994
- headers: void 0,
995
- body: void 0
996
- },
997
- error: err,
998
- path,
999
- origin: window == null ? void 0 : window.location.href
1000
- })
1001
- });
1002
- } catch (e) {
1003
- }
1004
- }
1005
- };
1006
-
1007
1031
  // src/utils/tokenStore.ts
1008
1032
  import fetch3 from "cross-fetch";
1009
1033
 
@@ -1252,6 +1276,55 @@ __export(schemas_exports3, {
1252
1276
  orders: () => schemas_exports2
1253
1277
  });
1254
1278
 
1279
+ // src/utils/discount.ts
1280
+ var DiscountUtils = class {
1281
+ static apply(discount, price) {
1282
+ switch (discount.type) {
1283
+ case "PercentageOffDiscount":
1284
+ return Object.fromEntries(Object.entries(price).map(([currency, amount]) => [currency, amount * discount.percentage]));
1285
+ case "FlatAmountOffDiscount":
1286
+ return Object.fromEntries(Object.entries(price).map(([currency, amount]) => {
1287
+ var _a, _b;
1288
+ return [currency, Math.min(amount, (_b = (_a = discount.total) == null ? void 0 : _a[currency]) != null ? _b : 0)];
1289
+ }));
1290
+ }
1291
+ }
1292
+ };
1293
+
1294
+ // src/utils/money.ts
1295
+ var MoneyCollectionUtils = class {
1296
+ static sum(a, b) {
1297
+ return Object.fromEntries(Object.entries(a).map(([currency, amount]) => [currency, amount + b[currency]]));
1298
+ }
1299
+ static subtract(a, b) {
1300
+ return Object.fromEntries(Object.entries(a).map(([currency, amount]) => [currency, amount - b[currency]]));
1301
+ }
1302
+ };
1303
+
1304
+ // src/utils/offer.ts
1305
+ var OfferUtils = class {
1306
+ static eligible(offer, order) {
1307
+ switch (offer.condition.type) {
1308
+ case "CartContainsItems":
1309
+ const relevantItems = order.items.filter(
1310
+ (i) => i.type === "Product" && offer.condition.relevantItemVariations[`Product/${i.productId}`] && (offer.condition.relevantItemVariations[`Product/${i.productId}`].length === 0 || offer.condition.relevantItemVariations[`Product/${i.productId}`].includes(i.variationId)) || i.type === "Bundle" && offer.condition.relevantItemVariations[`Bundle/${i.bundleId}`] && (offer.condition.relevantItemVariations[`Bundle/${i.bundleId}`].length === 0 || offer.condition.relevantItemVariations[`Bundle/${i.bundleId}`].includes(i.variationId))
1311
+ );
1312
+ console.log("Relevant items for offer:", { offer, relevantItems });
1313
+ return relevantItems.length >= offer.condition.minimumItems;
1314
+ }
1315
+ console.warn("Unsupported offer condition found:", offer.condition);
1316
+ return false;
1317
+ }
1318
+ static applyToVariation(offer, variation) {
1319
+ const discount = DiscountUtils.apply(offer.discount, variation.price);
1320
+ return {
1321
+ ...variation,
1322
+ price: MoneyCollectionUtils.subtract(variation.price, discount),
1323
+ hasDiscount: Object.values(discount).some((v) => v > 0) || variation.hasDiscount
1324
+ };
1325
+ }
1326
+ };
1327
+
1255
1328
  // src/index.ts
1256
1329
  var MoonbaseClient = class {
1257
1330
  constructor(configuration) {
@@ -1277,20 +1350,24 @@ export {
1277
1350
  ActivationStatus,
1278
1351
  ConsoleLogger,
1279
1352
  CycleLength,
1353
+ DiscountUtils,
1280
1354
  InMemoryStore,
1281
1355
  LicenseStatus,
1282
1356
  LocalStorageStore,
1283
1357
  LogLevel,
1358
+ MoneyCollectionUtils,
1284
1359
  MoonbaseApi,
1285
1360
  MoonbaseClient,
1286
1361
  MoonbaseError,
1287
1362
  NotAuthenticatedError,
1288
1363
  NotAuthorizedError,
1289
1364
  NotFoundError,
1365
+ OfferUtils,
1290
1366
  OrderStatus,
1291
1367
  Platform,
1292
1368
  SubscriptionStatus,
1293
1369
  TokenStore,
1370
+ objectToQuery,
1294
1371
  problemDetailsSchema,
1295
1372
  schemas_exports3 as schemas,
1296
1373
  utmToObject
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@moonbase.sh/storefront-api",
3
3
  "type": "module",
4
- "version": "0.4.25",
4
+ "version": "0.4.27",
5
5
  "description": "Package to let you build storefronts with Moonbase.sh as payment and delivery provider",
6
6
  "author": "Tobias Lønnerød Madsen <m@dsen.tv>",
7
7
  "license": "MIT",
@@ -19,8 +19,8 @@
19
19
  "devDependencies": {
20
20
  "@types/node": "^22.5.4",
21
21
  "rimraf": "^6.0.1",
22
- "tsup": "^8.2.4",
23
- "typescript": "~5.5.4",
22
+ "tsup": "^8.5.0",
23
+ "typescript": "~5.8.3",
24
24
  "vitest": "^2.1.4"
25
25
  },
26
26
  "scripts": {