@freemius/sdk 0.0.6 → 0.1.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
@@ -21,6 +21,14 @@ let CURRENCY = /* @__PURE__ */ function(CURRENCY$1) {
21
21
  return CURRENCY$1;
22
22
  }({});
23
23
 
24
+ //#endregion
25
+ //#region src/contracts/webhook.ts
26
+ let WebhookAuthenticationMethod = /* @__PURE__ */ function(WebhookAuthenticationMethod$1) {
27
+ WebhookAuthenticationMethod$1["SignatureHeader"] = "SignatureHeader";
28
+ WebhookAuthenticationMethod$1["Api"] = "Api";
29
+ return WebhookAuthenticationMethod$1;
30
+ }({});
31
+
24
32
  //#endregion
25
33
  //#region src/api/parser.ts
26
34
  function idToNumber(id) {
@@ -86,9 +94,60 @@ function parsePaymentMethod(gateway) {
86
94
  return gateway?.startsWith("stripe") ? "card" : gateway?.startsWith("paypal") ? "paypal" : null;
87
95
  }
88
96
 
97
+ //#endregion
98
+ //#region src/errors/ActionError.ts
99
+ var ActionError = class ActionError extends Error {
100
+ statusCode;
101
+ validationIssues;
102
+ constructor(message, statusCode = 400, validationIssues) {
103
+ super(message);
104
+ this.name = "ActionError";
105
+ this.statusCode = statusCode;
106
+ this.validationIssues = validationIssues;
107
+ }
108
+ toResponse() {
109
+ const errorResponse = { message: this.message };
110
+ if (this.validationIssues) errorResponse.issues = this.validationIssues;
111
+ return Response.json(errorResponse, { status: this.statusCode });
112
+ }
113
+ static badRequest(message) {
114
+ return new ActionError(message, 400);
115
+ }
116
+ static unauthorized(message = "Unauthorized") {
117
+ return new ActionError(message, 401);
118
+ }
119
+ static notFound(message = "Not found") {
120
+ return new ActionError(message, 404);
121
+ }
122
+ static validationFailed(message, validationIssues) {
123
+ return new ActionError(message, 400, validationIssues);
124
+ }
125
+ static internalError(message = "Internal server error") {
126
+ return new ActionError(message, 500);
127
+ }
128
+ };
129
+
130
+ //#endregion
131
+ //#region src/errors/WebhookError.ts
132
+ var WebhookError = class extends Error {
133
+ statusCode;
134
+ constructor(message, statusCode = 400) {
135
+ super(message);
136
+ this.name = "WebhookError";
137
+ this.statusCode = statusCode;
138
+ }
139
+ toResponse() {
140
+ return {
141
+ status: this.statusCode,
142
+ success: false,
143
+ error: this.message
144
+ };
145
+ }
146
+ };
147
+
89
148
  //#endregion
90
149
  //#region package.json
91
- var version = "0.0.6";
150
+ var version = "0.1.0";
92
151
 
93
152
  //#endregion
94
153
  //#region src/api/client.ts
@@ -436,6 +495,30 @@ var Payment = class extends ApiBase {
436
495
  }
437
496
  };
438
497
 
498
+ //#endregion
499
+ //#region src/api/WebhookEvent.ts
500
+ var WebhookEvent = class extends ApiBase {
501
+ async retrieve(eventId) {
502
+ const result = await this.client.GET(`/products/{product_id}/events/{event_id}.json`, { params: { path: {
503
+ product_id: this.productId,
504
+ event_id: this.getIdForPath(eventId)
505
+ } } });
506
+ if (!this.isGoodResponse(result.response) || !result.data || !result.data.id) return null;
507
+ return result.data;
508
+ }
509
+ async retrieveMany(filter, pagination) {
510
+ const result = await this.client.GET(`/products/{product_id}/events.json`, { params: {
511
+ path: { product_id: this.productId },
512
+ query: {
513
+ ...this.getPagingParams(pagination),
514
+ ...filter ?? {}
515
+ }
516
+ } });
517
+ if (!this.isGoodResponse(result.response) || !result.data || !Array.isArray(result.data.events)) return [];
518
+ return result.data.events;
519
+ }
520
+ };
521
+
439
522
  //#endregion
440
523
  //#region src/utils/ops.ts
441
524
  function splitName(name) {
@@ -464,6 +547,7 @@ var ApiService = class {
464
547
  product;
465
548
  subscription;
466
549
  payment;
550
+ event;
467
551
  baseUrl;
468
552
  constructor(productId, apiKey, secretKey, publicKey) {
469
553
  this.secretKey = secretKey;
@@ -476,6 +560,7 @@ var ApiService = class {
476
560
  this.product = new Product(this.productId, this.client);
477
561
  this.subscription = new Subscription(this.productId, this.client);
478
562
  this.payment = new Payment(this.productId, this.client);
563
+ this.event = new WebhookEvent(this.productId, this.client);
479
564
  }
480
565
  /**
481
566
  * Low level API client for direct access to the Freemius API.
@@ -556,15 +641,7 @@ var ApiService = class {
556
641
  * Every method returns the existing instance of the builder for chainability,
557
642
  * The final `getOptions()` method returns the constructed `CheckoutOptions` object.
558
643
  */
559
- var Checkout = class Checkout {
560
- static createSandboxToken(productId, secretKey, publicKey) {
561
- const timestamp = Math.floor(Date.now() / 1e3).toString();
562
- const token = `${timestamp}${productId}${secretKey}${publicKey}checkout`;
563
- return {
564
- ctx: timestamp,
565
- token: createHash("md5").update(token).digest("hex")
566
- };
567
- }
644
+ var Checkout = class {
568
645
  options;
569
646
  constructor(productId, publicKey, secretKey) {
570
647
  this.productId = productId;
@@ -575,12 +652,11 @@ var Checkout = class Checkout {
575
652
  /**
576
653
  * Enables sandbox mode for testing purposes.
577
654
  *
578
- * @returns A new builder instance with sandbox configuration
579
655
  */
580
- setSandbox() {
656
+ setSandbox(sandbox) {
581
657
  this.options = {
582
658
  ...this.options,
583
- sandbox: Checkout.createSandboxToken(this.productId, this.secretKey, this.publicKey)
659
+ sandbox
584
660
  };
585
661
  return this;
586
662
  }
@@ -590,7 +666,6 @@ var Checkout = class Checkout {
590
666
  * @param user User object with email and optional name fields. The shape matches the session from `better-auth` or next-auth packages. Also handles `null` or `undefined` gracefully.
591
667
  * @param readonly If true, the user information will be read-only in the checkout session.
592
668
  *
593
- * @returns A new builder instance with user configuration
594
669
  */
595
670
  setUser(user, readonly = true) {
596
671
  if (!user) return this;
@@ -614,7 +689,6 @@ var Checkout = class Checkout {
614
689
  * Applies recommended UI settings for better user experience.
615
690
  * This includes fullscreen mode, upsells, refund badge, and reviews display.
616
691
  *
617
- * @returns A new builder instance with recommended UI settings
618
692
  */
619
693
  setRecommendations() {
620
694
  this.options = {
@@ -631,7 +705,6 @@ var Checkout = class Checkout {
631
705
  * Sets the plan ID for the checkout.
632
706
  *
633
707
  * @param planId The plan ID to purchase
634
- * @returns A new builder instance with plan ID set
635
708
  */
636
709
  setPlan(planId) {
637
710
  this.options = {
@@ -644,7 +717,6 @@ var Checkout = class Checkout {
644
717
  * Sets the number of licenses to purchase.
645
718
  *
646
719
  * @param count Number of licenses
647
- * @returns A new builder instance with license count set
648
720
  */
649
721
  setQuota(count) {
650
722
  this.options = {
@@ -672,7 +744,6 @@ var Checkout = class Checkout {
672
744
  *
673
745
  * @param coupon The coupon code to apply
674
746
  * @param hideUI Whether to hide the coupon input field from users
675
- * @returns A new builder instance with coupon configuration
676
747
  */
677
748
  setCoupon(options) {
678
749
  const { code: coupon, hideUI = false } = options;
@@ -687,7 +758,6 @@ var Checkout = class Checkout {
687
758
  * Enables trial mode for the checkout.
688
759
  *
689
760
  * @param mode Trial type - true/false for plan default, or specific 'free'/'paid' mode
690
- * @returns A new builder instance with trial configuration
691
761
  */
692
762
  setTrial(mode = true) {
693
763
  this.options = {
@@ -700,7 +770,6 @@ var Checkout = class Checkout {
700
770
  * Configures the visual layout and appearance of the checkout.
701
771
  *
702
772
  * @param options Appearance configuration options
703
- * @returns A new builder instance with appearance configuration
704
773
  */
705
774
  setAppearance(options) {
706
775
  this.options = { ...this.options };
@@ -715,7 +784,6 @@ var Checkout = class Checkout {
715
784
  * Configures discount display settings.
716
785
  *
717
786
  * @param options Discount configuration options
718
- * @returns A new builder instance with discount configuration
719
787
  */
720
788
  setDiscounts(options) {
721
789
  this.options = { ...this.options };
@@ -730,7 +798,6 @@ var Checkout = class Checkout {
730
798
  *
731
799
  * @param selector Type of billing cycle selector to show
732
800
  * @param defaultCycle Default billing cycle to select
733
- * @returns A new builder instance with billing cycle configuration
734
801
  */
735
802
  setBillingCycle(defaultCycle, selector) {
736
803
  this.options = { ...this.options };
@@ -742,7 +809,6 @@ var Checkout = class Checkout {
742
809
  * Sets the language/locale for the checkout.
743
810
  *
744
811
  * @param locale Language setting - 'auto', 'auto-beta', or specific locale like 'en_US'
745
- * @returns A new builder instance with locale configuration
746
812
  */
747
813
  setLanguage(locale = "auto") {
748
814
  this.options = {
@@ -755,7 +821,6 @@ var Checkout = class Checkout {
755
821
  * Configures review and badge display settings.
756
822
  *
757
823
  * @param options Review and badge configuration
758
- * @returns A new builder instance with reviews and badges configuration
759
824
  */
760
825
  setSocialProofing(options) {
761
826
  this.options = { ...this.options };
@@ -771,7 +836,6 @@ var Checkout = class Checkout {
771
836
  * @param currency Primary currency or 'auto' for automatic detection
772
837
  * @param defaultCurrency Default currency when using 'auto'
773
838
  * @param showInlineSelector Whether to show inline currency selector
774
- * @returns A new builder instance with currency configuration
775
839
  */
776
840
  setCurrency(currency, defaultCurrency = "usd", showInlineSelector = true) {
777
841
  this.options = {
@@ -787,7 +851,6 @@ var Checkout = class Checkout {
787
851
  *
788
852
  * @param cancelUrl URL for back button when in page mode
789
853
  * @param cancelIcon Custom cancel icon URL
790
- * @returns A new builder instance with navigation configuration
791
854
  */
792
855
  setCancelButton(cancelUrl, cancelIcon) {
793
856
  this.options = { ...this.options };
@@ -799,7 +862,6 @@ var Checkout = class Checkout {
799
862
  * Associates purchases with an affiliate account.
800
863
  *
801
864
  * @param userId Affiliate user ID
802
- * @returns A new builder instance with affiliate configuration
803
865
  */
804
866
  setAffiliate(userId) {
805
867
  this.options = {
@@ -812,7 +874,6 @@ var Checkout = class Checkout {
812
874
  * Sets a custom image/icon for the checkout.
813
875
  *
814
876
  * @param imageUrl Secure HTTPS URL to the image
815
- * @returns A new builder instance with custom image
816
877
  */
817
878
  setImage(imageUrl) {
818
879
  this.options = {
@@ -822,12 +883,13 @@ var Checkout = class Checkout {
822
883
  return this;
823
884
  }
824
885
  /**
825
- * Configures the checkout for license renewal.
886
+ * Configures the checkout for license renewal or upgrade by the license key.
887
+ *
888
+ * @note - This is less secure since it exposes the license key to the client. Use only in authenticated contexts.
826
889
  *
827
890
  * @param licenseKey The license key to renew
828
- * @returns A new builder instance configured for renewal
829
891
  */
830
- setLicenseRenewal(licenseKey) {
892
+ setLicenseUpgradeByKey(licenseKey) {
831
893
  this.options = {
832
894
  ...this.options,
833
895
  license_key: licenseKey
@@ -835,9 +897,20 @@ var Checkout = class Checkout {
835
897
  return this;
836
898
  }
837
899
  /**
838
- * Builds and returns the final checkout options to be used with the `@freemius/checkout` package.
900
+ * Configures the checkout for license upgrade using an authorization token.
839
901
  *
840
- * @note - This is async by purpose so that we can allow for future enhancements that might require async operations.
902
+ * @param params The license upgrade authorization parameters
903
+ */
904
+ setLicenseUpgradeByAuth(params) {
905
+ this.options = {
906
+ ...this.options,
907
+ license_id: params.licenseId,
908
+ authorization: params.authorization
909
+ };
910
+ return this;
911
+ }
912
+ /**
913
+ * Builds and returns the final checkout options to be used with the `@freemius/checkout` package.
841
914
  *
842
915
  * @returns The constructed CheckoutOptions object
843
916
  */
@@ -846,8 +919,6 @@ var Checkout = class Checkout {
846
919
  }
847
920
  /**
848
921
  * Generates a checkout link based on the current builder state.
849
- *
850
- * @note - This is async by purpose so that we can allow for future enhancements that might require async operations.
851
922
  */
852
923
  getLink() {
853
924
  const checkoutOptions = convertCheckoutOptionsToQueryParams(this.options);
@@ -868,39 +939,6 @@ var Checkout = class Checkout {
868
939
  }
869
940
  };
870
941
 
871
- //#endregion
872
- //#region src/errors/ActionError.ts
873
- var ActionError = class ActionError extends Error {
874
- statusCode;
875
- validationIssues;
876
- constructor(message, statusCode = 400, validationIssues) {
877
- super(message);
878
- this.name = "ActionError";
879
- this.statusCode = statusCode;
880
- this.validationIssues = validationIssues;
881
- }
882
- toResponse() {
883
- const errorResponse = { message: this.message };
884
- if (this.validationIssues) errorResponse.issues = this.validationIssues;
885
- return Response.json(errorResponse, { status: this.statusCode });
886
- }
887
- static badRequest(message) {
888
- return new ActionError(message, 400);
889
- }
890
- static unauthorized(message = "Unauthorized") {
891
- return new ActionError(message, 401);
892
- }
893
- static notFound(message = "Not found") {
894
- return new ActionError(message, 404);
895
- }
896
- static validationFailed(message, validationIssues) {
897
- return new ActionError(message, 400, validationIssues);
898
- }
899
- static internalError(message = "Internal server error") {
900
- return new ActionError(message, 500);
901
- }
902
- };
903
-
904
942
  //#endregion
905
943
  //#region src/checkout/PricingRetriever.ts
906
944
  var PricingRetriever = class {
@@ -933,7 +971,10 @@ var PurchaseProcessor = class {
933
971
  return request.method === "POST" && action === "process_purchase";
934
972
  }
935
973
  async processAction(request) {
936
- const purchaseSchema = zod.object({ purchase: zod.object({ license_id: zod.string() }) });
974
+ const purchaseSchema = zod.object({
975
+ purchase: zod.object({ license_id: zod.string() }).optional(),
976
+ trial: zod.object({ license_id: zod.string() }).optional()
977
+ });
937
978
  const contentType = request.headers.get("content-type");
938
979
  if (!contentType || !contentType.includes("application/json")) throw ActionError.badRequest("Invalid content type. Expected application/json");
939
980
  let requestBody;
@@ -944,7 +985,8 @@ var PurchaseProcessor = class {
944
985
  }
945
986
  const parseResult = purchaseSchema.safeParse(requestBody);
946
987
  if (!parseResult.success) throw ActionError.validationFailed("Invalid request body format", parseResult.error.issues);
947
- const { purchase: { license_id: licenseId } } = parseResult.data;
988
+ const licenseId = parseResult.data.purchase?.license_id ?? parseResult.data.trial?.license_id;
989
+ if (!licenseId) throw ActionError.badRequest("License ID is required in the request body, either from purchase or from trial.");
948
990
  const purchase = await this.purchase.retrievePurchase(licenseId);
949
991
  if (!purchase) throw ActionError.notFound("No purchase data found for the provided license ID");
950
992
  if (this.callback) {
@@ -962,11 +1004,13 @@ var CheckoutRedirectInfo = class {
962
1004
  plan_id;
963
1005
  email;
964
1006
  pricing_id;
965
- currency;
1007
+ action;
966
1008
  license_id;
967
1009
  expiration;
968
1010
  quota;
969
- action;
1011
+ trial;
1012
+ trial_ends_at;
1013
+ currency;
970
1014
  amount;
971
1015
  tax;
972
1016
  type;
@@ -978,17 +1022,19 @@ var CheckoutRedirectInfo = class {
978
1022
  this.plan_id = idToString(data.plan_id);
979
1023
  this.email = data.email;
980
1024
  this.pricing_id = idToString(data.pricing_id);
981
- this.currency = data.currency ? parseCurrency(data.currency) : CURRENCY.USD;
1025
+ this.action = data.action ? data.action : "purchase";
982
1026
  this.license_id = idToString(data.license_id);
983
1027
  this.expiration = data.expiration ? parseDateTime(data.expiration) : null;
984
1028
  this.quota = data.quota ? parseNumber(data.quota) : null;
985
- this.action = data.action ? data.action : null;
1029
+ this.trial = data.trial ? data.trial : null;
1030
+ this.trial_ends_at = data.trial_ends_at ? parseDateTime(data.trial_ends_at) : null;
1031
+ this.currency = data.currency ? parseCurrency(data.currency) : CURRENCY.USD;
986
1032
  this.amount = parseNumber(data.amount);
987
1033
  this.tax = parseNumber(data.tax);
988
1034
  this.subscription_id = data.subscription_id ? idToString(data.subscription_id) : null;
989
1035
  this.billing_cycle = data.billing_cycle ? parseBillingCycle(data.billing_cycle) : null;
990
1036
  this.payment_id = data.payment_id ? idToString(data.payment_id) : null;
991
- this.type = this.subscription_id ? "subscription" : "one-off";
1037
+ this.type = this.subscription_id ? "subscription" : this.payment_id ? "one-off" : null;
992
1038
  }
993
1039
  isSubscription() {
994
1040
  return this.type === "subscription";
@@ -999,17 +1045,19 @@ var CheckoutRedirectInfo = class {
999
1045
  plan_id: this.plan_id,
1000
1046
  email: this.email,
1001
1047
  pricing_id: this.pricing_id,
1002
- currency: this.currency,
1003
1048
  license_id: this.license_id,
1004
1049
  expiration: this.expiration,
1005
1050
  quota: this.quota,
1051
+ trial: this.trial,
1052
+ trial_ends_at: this.trial_ends_at,
1053
+ currency: this.currency,
1006
1054
  action: this.action,
1007
1055
  amount: this.amount,
1008
1056
  tax: this.tax,
1009
- type: this.type,
1010
1057
  subscription_id: this.subscription_id,
1011
1058
  billing_cycle: this.billing_cycle,
1012
- payment_id: this.payment_id
1059
+ payment_id: this.payment_id,
1060
+ type: this.type
1013
1061
  };
1014
1062
  }
1015
1063
  };
@@ -1053,11 +1101,16 @@ var RedirectProcessor = class {
1053
1101
  }
1054
1102
  const cleanUrl = this.getCleanUrl(url.href);
1055
1103
  const calculatedSignature = createHmac("sha256", this.secretKey).update(cleanUrl).digest("hex");
1056
- const result = timingSafeEqual(Buffer.from(calculatedSignature), Buffer.from(signature));
1057
- if (!result) return null;
1058
- const params = Object.fromEntries(url.searchParams.entries());
1059
- if (!params.user_id || !params.plan_id || !params.pricing_id || !params.email) return null;
1060
- return new CheckoutRedirectInfo(params);
1104
+ try {
1105
+ const result = timingSafeEqual(Buffer.from(calculatedSignature), Buffer.from(signature));
1106
+ if (!result) return null;
1107
+ const params = Object.fromEntries(url.searchParams.entries());
1108
+ if (!params.user_id || !params.plan_id || !params.pricing_id || !params.email) return null;
1109
+ return new CheckoutRedirectInfo(params);
1110
+ } catch (e) {
1111
+ console.error("Error getting redirection information:", e);
1112
+ return null;
1113
+ }
1061
1114
  }
1062
1115
  getCleanUrl(url) {
1063
1116
  const signatureParam = "&signature=";
@@ -1081,9 +1134,6 @@ var CheckoutRequestProcessor = class {
1081
1134
  return (request) => this.process(config, request);
1082
1135
  }
1083
1136
  async process(config, request) {
1084
- const url = new URL(request.url);
1085
- const action = url.searchParams.get("action");
1086
- if (!action) return Response.json({ error: "Action parameter is required" }, { status: 400 });
1087
1137
  const actionHandlers = [
1088
1138
  this.getPricingRetriever(),
1089
1139
  this.getRedirectProcessor({
@@ -1145,7 +1195,8 @@ var CheckoutRequestProcessor = class {
1145
1195
  //#region src/services/CheckoutService.ts
1146
1196
  var CheckoutService = class {
1147
1197
  request;
1148
- constructor(productId, publicKey, secretKey, purchase, pricing) {
1198
+ constructor(api, productId, publicKey, secretKey, purchase, pricing) {
1199
+ this.api = api;
1149
1200
  this.productId = productId;
1150
1201
  this.publicKey = publicKey;
1151
1202
  this.secretKey = secretKey;
@@ -1186,16 +1237,20 @@ var CheckoutService = class {
1186
1237
  * @example
1187
1238
  */
1188
1239
  async create(options = {}) {
1189
- const { user, isSandbox = false, withRecommendation = true, title, image, planId, quota, trial } = options;
1240
+ const { user, isSandbox = false, withRecommendation = true, title, image, planId, quota, trial, licenseId } = options;
1190
1241
  const builder = new Checkout(idToString(this.productId), this.publicKey, this.secretKey);
1191
1242
  if (user) builder.setUser(user, true);
1192
1243
  if (withRecommendation) builder.setRecommendations();
1193
- if (isSandbox) builder.setSandbox();
1244
+ if (isSandbox) builder.setSandbox(await this.getSandboxParams());
1194
1245
  if (title) builder.setTitle(title);
1195
1246
  if (image) builder.setImage(image);
1196
1247
  if (planId) builder.setPlan(planId);
1197
1248
  if (quota) builder.setQuota(quota);
1198
1249
  if (trial) builder.setTrial(trial);
1250
+ if (licenseId) {
1251
+ const authorization = await this.getLicenseUpgradeAuth(licenseId);
1252
+ builder.setLicenseUpgradeByAuth(authorization);
1253
+ }
1199
1254
  return builder;
1200
1255
  }
1201
1256
  /**
@@ -1204,12 +1259,28 @@ var CheckoutService = class {
1204
1259
  * This shouldn't be used in production, but is useful for testing purposes.
1205
1260
  *
1206
1261
  * @note This is intentionally set as `async` because we would use the API in the future to generate more fine grained sandbox params (for example for a specific email address only).
1207
- *
1208
- * @todo - This has a duplication with the `inSandbox` method in the builder. Consider refactoring to avoid this duplication.
1209
- * Also think about whether we should make the builder's `inSandbox` method async as well.
1210
1262
  */
1211
1263
  async getSandboxParams() {
1212
- return Checkout.createSandboxToken(idToString(this.productId), this.secretKey, this.publicKey);
1264
+ const productId = idToString(this.productId);
1265
+ const timestamp = Math.floor(Date.now() / 1e3).toString();
1266
+ const token = `${timestamp}${productId}${this.secretKey}${this.publicKey}checkout`;
1267
+ return {
1268
+ ctx: timestamp,
1269
+ token: createHash("md5").update(token).digest("hex")
1270
+ };
1271
+ }
1272
+ /**
1273
+ * Retrieves the license upgrade authorization for a given license ID.
1274
+ *
1275
+ * This is used to authorize a license upgrade during the checkout process. Useful when creating upgrade links for existing users.
1276
+ */
1277
+ async getLicenseUpgradeAuth(licenseId) {
1278
+ const auth = await this.api.license.retrieveCheckoutUpgradeAuthorization(licenseId);
1279
+ if (!auth) throw new Error("Failed to retrieve license upgrade authorization");
1280
+ return {
1281
+ licenseId,
1282
+ authorization: auth
1283
+ };
1213
1284
  }
1214
1285
  /**
1215
1286
  * Processes a redirect URL and returns the checkout redirect information if valid.
@@ -1841,11 +1912,16 @@ var CustomerPortalService = class {
1841
1912
  //#endregion
1842
1913
  //#region src/webhook/WebhookListener.ts
1843
1914
  const SIGNATURE_HEADER = "x-signature";
1915
+ const DEFAULT_ERROR_HANDLER = async (error) => {
1916
+ console.error("Webhook processing error:", error);
1917
+ };
1844
1918
  var WebhookListener = class {
1845
1919
  eventHandlers = /* @__PURE__ */ new Map();
1846
- constructor(secretKey, onError = console.error) {
1920
+ constructor(api, secretKey, onError = DEFAULT_ERROR_HANDLER, authenticationMethod = WebhookAuthenticationMethod.SignatureHeader) {
1921
+ this.api = api;
1847
1922
  this.secretKey = secretKey;
1848
1923
  this.onError = onError;
1924
+ this.authenticationMethod = authenticationMethod;
1849
1925
  }
1850
1926
  on(typeOrTypes, handler) {
1851
1927
  const types = Array.isArray(typeOrTypes) ? typeOrTypes : [typeOrTypes];
@@ -1913,40 +1989,52 @@ var WebhookListener = class {
1913
1989
  * Returns an object you can map to your framework's response easily.
1914
1990
  */
1915
1991
  async process(input) {
1916
- const sig = this.getHeader(SIGNATURE_HEADER, input.headers);
1917
- if (!this.verifySignature(input.rawBody, sig)) return {
1918
- status: 401,
1919
- success: false,
1920
- error: "Invalid signature"
1921
- };
1922
- let evt;
1923
1992
  try {
1924
- const parsed = JSON.parse(typeof input.rawBody === "string" ? input.rawBody : input.rawBody.toString("utf8"));
1925
- if (!parsed || typeof parsed.type !== "string") return {
1926
- status: 400,
1927
- success: false,
1928
- error: "Invalid payload"
1929
- };
1930
- evt = parsed;
1931
- } catch {
1993
+ const event = this.authenticationMethod === WebhookAuthenticationMethod.SignatureHeader ? await this.authenticateAndGetEventFromInput(input) : await this.authenticateAndGetEventFromApi(input);
1994
+ return this.processEvent(event);
1995
+ } catch (error) {
1996
+ if (error instanceof WebhookError) return error.toResponse();
1932
1997
  return {
1933
- status: 400,
1998
+ status: 500,
1934
1999
  success: false,
1935
- error: "Malformed JSON"
2000
+ error: "Internal Server Error"
1936
2001
  };
1937
2002
  }
1938
- const eventType = evt.type;
2003
+ }
2004
+ async authenticateAndGetEventFromInput(input) {
2005
+ const sig = this.getHeader(SIGNATURE_HEADER, input.headers);
2006
+ if (!this.verifySignature(input.rawBody, sig)) throw new WebhookError("Invalid signature", 401);
2007
+ return this.parseEventFromInput(input);
2008
+ }
2009
+ async authenticateAndGetEventFromApi(input) {
2010
+ const jsonPayload = this.parseEventFromInput(input);
2011
+ if (!jsonPayload.id) throw new WebhookError("Invalid payload");
2012
+ const event = await this.api.event.retrieve(jsonPayload.id);
2013
+ if (!event) throw new WebhookError("Event not found", 404);
2014
+ return event;
2015
+ }
2016
+ parseEventFromInput(input) {
2017
+ try {
2018
+ const parsed = JSON.parse(typeof input.rawBody === "string" ? input.rawBody : input.rawBody.toString("utf8"));
2019
+ if (!parsed || typeof parsed.type !== "string") throw new WebhookError("Invalid payload");
2020
+ return parsed;
2021
+ } catch {
2022
+ throw new WebhookError("Malformed JSON");
2023
+ }
2024
+ }
2025
+ async processEvent(event) {
2026
+ const eventType = event.type;
1939
2027
  const eventHandlers = this.eventHandlers.get(eventType);
1940
2028
  if (!eventHandlers || eventHandlers.size === 0) console.warn(`No handlers registered for event type: ${eventType}`);
1941
2029
  try {
1942
2030
  const promises = Array.from(eventHandlers || []).map((handler) => {
1943
2031
  const typedHandler = handler;
1944
- const typedEvent = evt;
2032
+ const typedEvent = event;
1945
2033
  return typedHandler(typedEvent);
1946
2034
  });
1947
2035
  await Promise.all(promises);
1948
2036
  } catch (error) {
1949
- this.onError?.(error);
2037
+ await this.onError?.(error);
1950
2038
  return {
1951
2039
  status: 500,
1952
2040
  success: false,
@@ -1970,11 +2058,13 @@ var WebhookListener = class {
1970
2058
  //#endregion
1971
2059
  //#region src/services/WebhookService.ts
1972
2060
  var WebhookService = class {
1973
- constructor(secretKey) {
2061
+ constructor(api, secretKey) {
2062
+ this.api = api;
1974
2063
  this.secretKey = secretKey;
1975
2064
  }
1976
- createListener(onError) {
1977
- return new WebhookListener(this.secretKey, onError);
2065
+ createListener(config = {}) {
2066
+ const { onError, authenticationMethod } = config;
2067
+ return new WebhookListener(this.api, this.secretKey, onError, authenticationMethod);
1978
2068
  }
1979
2069
  createRequestProcessor(listener) {
1980
2070
  return (request) => this.processFetch(listener, request);
@@ -2476,12 +2566,12 @@ var Freemius = class {
2476
2566
  this.auth = new AuthService(productId, secretKey);
2477
2567
  this.pricing = new PricingService(this.api);
2478
2568
  this.purchase = new PurchaseService(this.api);
2479
- this.checkout = new CheckoutService(productId, publicKey, secretKey, this.purchase, this.pricing);
2569
+ this.checkout = new CheckoutService(this.api, productId, publicKey, secretKey, this.purchase, this.pricing);
2480
2570
  this.customerPortal = new CustomerPortalService(this.api, this.checkout, this.auth, this.purchase);
2481
- this.webhook = new WebhookService(secretKey);
2571
+ this.webhook = new WebhookService(this.api, secretKey);
2482
2572
  }
2483
2573
  };
2484
2574
 
2485
2575
  //#endregion
2486
- export { BILLING_CYCLE, CURRENCY, Freemius, idToNumber, idToString, isIdsEqual, parseBillingCycle, parseCurrency, parseDate, parseDateTime, parseNumber, parsePaymentMethod };
2576
+ export { ActionError, BILLING_CYCLE, CURRENCY, Freemius, WebhookAuthenticationMethod, WebhookError, idToNumber, idToString, isIdsEqual, parseBillingCycle, parseCurrency, parseDate, parseDateTime, parseNumber, parsePaymentMethod };
2487
2577
  //# sourceMappingURL=index.mjs.map