@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.js CHANGED
@@ -43,6 +43,14 @@ let CURRENCY = /* @__PURE__ */ function(CURRENCY$1) {
43
43
  return CURRENCY$1;
44
44
  }({});
45
45
 
46
+ //#endregion
47
+ //#region src/contracts/webhook.ts
48
+ let WebhookAuthenticationMethod = /* @__PURE__ */ function(WebhookAuthenticationMethod$1) {
49
+ WebhookAuthenticationMethod$1["SignatureHeader"] = "SignatureHeader";
50
+ WebhookAuthenticationMethod$1["Api"] = "Api";
51
+ return WebhookAuthenticationMethod$1;
52
+ }({});
53
+
46
54
  //#endregion
47
55
  //#region src/api/parser.ts
48
56
  function idToNumber(id) {
@@ -108,9 +116,60 @@ function parsePaymentMethod(gateway) {
108
116
  return gateway?.startsWith("stripe") ? "card" : gateway?.startsWith("paypal") ? "paypal" : null;
109
117
  }
110
118
 
119
+ //#endregion
120
+ //#region src/errors/ActionError.ts
121
+ var ActionError = class ActionError extends Error {
122
+ statusCode;
123
+ validationIssues;
124
+ constructor(message, statusCode = 400, validationIssues) {
125
+ super(message);
126
+ this.name = "ActionError";
127
+ this.statusCode = statusCode;
128
+ this.validationIssues = validationIssues;
129
+ }
130
+ toResponse() {
131
+ const errorResponse = { message: this.message };
132
+ if (this.validationIssues) errorResponse.issues = this.validationIssues;
133
+ return Response.json(errorResponse, { status: this.statusCode });
134
+ }
135
+ static badRequest(message) {
136
+ return new ActionError(message, 400);
137
+ }
138
+ static unauthorized(message = "Unauthorized") {
139
+ return new ActionError(message, 401);
140
+ }
141
+ static notFound(message = "Not found") {
142
+ return new ActionError(message, 404);
143
+ }
144
+ static validationFailed(message, validationIssues) {
145
+ return new ActionError(message, 400, validationIssues);
146
+ }
147
+ static internalError(message = "Internal server error") {
148
+ return new ActionError(message, 500);
149
+ }
150
+ };
151
+
152
+ //#endregion
153
+ //#region src/errors/WebhookError.ts
154
+ var WebhookError = class extends Error {
155
+ statusCode;
156
+ constructor(message, statusCode = 400) {
157
+ super(message);
158
+ this.name = "WebhookError";
159
+ this.statusCode = statusCode;
160
+ }
161
+ toResponse() {
162
+ return {
163
+ status: this.statusCode,
164
+ success: false,
165
+ error: this.message
166
+ };
167
+ }
168
+ };
169
+
111
170
  //#endregion
112
171
  //#region package.json
113
- var version = "0.0.6";
172
+ var version = "0.1.0";
114
173
 
115
174
  //#endregion
116
175
  //#region src/api/client.ts
@@ -458,6 +517,30 @@ var Payment = class extends ApiBase {
458
517
  }
459
518
  };
460
519
 
520
+ //#endregion
521
+ //#region src/api/WebhookEvent.ts
522
+ var WebhookEvent = class extends ApiBase {
523
+ async retrieve(eventId) {
524
+ const result = await this.client.GET(`/products/{product_id}/events/{event_id}.json`, { params: { path: {
525
+ product_id: this.productId,
526
+ event_id: this.getIdForPath(eventId)
527
+ } } });
528
+ if (!this.isGoodResponse(result.response) || !result.data || !result.data.id) return null;
529
+ return result.data;
530
+ }
531
+ async retrieveMany(filter, pagination) {
532
+ const result = await this.client.GET(`/products/{product_id}/events.json`, { params: {
533
+ path: { product_id: this.productId },
534
+ query: {
535
+ ...this.getPagingParams(pagination),
536
+ ...filter ?? {}
537
+ }
538
+ } });
539
+ if (!this.isGoodResponse(result.response) || !result.data || !Array.isArray(result.data.events)) return [];
540
+ return result.data.events;
541
+ }
542
+ };
543
+
461
544
  //#endregion
462
545
  //#region src/utils/ops.ts
463
546
  function splitName(name) {
@@ -486,6 +569,7 @@ var ApiService = class {
486
569
  product;
487
570
  subscription;
488
571
  payment;
572
+ event;
489
573
  baseUrl;
490
574
  constructor(productId, apiKey, secretKey, publicKey) {
491
575
  this.secretKey = secretKey;
@@ -498,6 +582,7 @@ var ApiService = class {
498
582
  this.product = new Product(this.productId, this.client);
499
583
  this.subscription = new Subscription(this.productId, this.client);
500
584
  this.payment = new Payment(this.productId, this.client);
585
+ this.event = new WebhookEvent(this.productId, this.client);
501
586
  }
502
587
  /**
503
588
  * Low level API client for direct access to the Freemius API.
@@ -578,15 +663,7 @@ var ApiService = class {
578
663
  * Every method returns the existing instance of the builder for chainability,
579
664
  * The final `getOptions()` method returns the constructed `CheckoutOptions` object.
580
665
  */
581
- var Checkout = class Checkout {
582
- static createSandboxToken(productId, secretKey, publicKey) {
583
- const timestamp = Math.floor(Date.now() / 1e3).toString();
584
- const token = `${timestamp}${productId}${secretKey}${publicKey}checkout`;
585
- return {
586
- ctx: timestamp,
587
- token: (0, crypto.createHash)("md5").update(token).digest("hex")
588
- };
589
- }
666
+ var Checkout = class {
590
667
  options;
591
668
  constructor(productId, publicKey, secretKey) {
592
669
  this.productId = productId;
@@ -597,12 +674,11 @@ var Checkout = class Checkout {
597
674
  /**
598
675
  * Enables sandbox mode for testing purposes.
599
676
  *
600
- * @returns A new builder instance with sandbox configuration
601
677
  */
602
- setSandbox() {
678
+ setSandbox(sandbox) {
603
679
  this.options = {
604
680
  ...this.options,
605
- sandbox: Checkout.createSandboxToken(this.productId, this.secretKey, this.publicKey)
681
+ sandbox
606
682
  };
607
683
  return this;
608
684
  }
@@ -612,7 +688,6 @@ var Checkout = class Checkout {
612
688
  * @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.
613
689
  * @param readonly If true, the user information will be read-only in the checkout session.
614
690
  *
615
- * @returns A new builder instance with user configuration
616
691
  */
617
692
  setUser(user, readonly = true) {
618
693
  if (!user) return this;
@@ -636,7 +711,6 @@ var Checkout = class Checkout {
636
711
  * Applies recommended UI settings for better user experience.
637
712
  * This includes fullscreen mode, upsells, refund badge, and reviews display.
638
713
  *
639
- * @returns A new builder instance with recommended UI settings
640
714
  */
641
715
  setRecommendations() {
642
716
  this.options = {
@@ -653,7 +727,6 @@ var Checkout = class Checkout {
653
727
  * Sets the plan ID for the checkout.
654
728
  *
655
729
  * @param planId The plan ID to purchase
656
- * @returns A new builder instance with plan ID set
657
730
  */
658
731
  setPlan(planId) {
659
732
  this.options = {
@@ -666,7 +739,6 @@ var Checkout = class Checkout {
666
739
  * Sets the number of licenses to purchase.
667
740
  *
668
741
  * @param count Number of licenses
669
- * @returns A new builder instance with license count set
670
742
  */
671
743
  setQuota(count) {
672
744
  this.options = {
@@ -694,7 +766,6 @@ var Checkout = class Checkout {
694
766
  *
695
767
  * @param coupon The coupon code to apply
696
768
  * @param hideUI Whether to hide the coupon input field from users
697
- * @returns A new builder instance with coupon configuration
698
769
  */
699
770
  setCoupon(options) {
700
771
  const { code: coupon, hideUI = false } = options;
@@ -709,7 +780,6 @@ var Checkout = class Checkout {
709
780
  * Enables trial mode for the checkout.
710
781
  *
711
782
  * @param mode Trial type - true/false for plan default, or specific 'free'/'paid' mode
712
- * @returns A new builder instance with trial configuration
713
783
  */
714
784
  setTrial(mode = true) {
715
785
  this.options = {
@@ -722,7 +792,6 @@ var Checkout = class Checkout {
722
792
  * Configures the visual layout and appearance of the checkout.
723
793
  *
724
794
  * @param options Appearance configuration options
725
- * @returns A new builder instance with appearance configuration
726
795
  */
727
796
  setAppearance(options) {
728
797
  this.options = { ...this.options };
@@ -737,7 +806,6 @@ var Checkout = class Checkout {
737
806
  * Configures discount display settings.
738
807
  *
739
808
  * @param options Discount configuration options
740
- * @returns A new builder instance with discount configuration
741
809
  */
742
810
  setDiscounts(options) {
743
811
  this.options = { ...this.options };
@@ -752,7 +820,6 @@ var Checkout = class Checkout {
752
820
  *
753
821
  * @param selector Type of billing cycle selector to show
754
822
  * @param defaultCycle Default billing cycle to select
755
- * @returns A new builder instance with billing cycle configuration
756
823
  */
757
824
  setBillingCycle(defaultCycle, selector) {
758
825
  this.options = { ...this.options };
@@ -764,7 +831,6 @@ var Checkout = class Checkout {
764
831
  * Sets the language/locale for the checkout.
765
832
  *
766
833
  * @param locale Language setting - 'auto', 'auto-beta', or specific locale like 'en_US'
767
- * @returns A new builder instance with locale configuration
768
834
  */
769
835
  setLanguage(locale = "auto") {
770
836
  this.options = {
@@ -777,7 +843,6 @@ var Checkout = class Checkout {
777
843
  * Configures review and badge display settings.
778
844
  *
779
845
  * @param options Review and badge configuration
780
- * @returns A new builder instance with reviews and badges configuration
781
846
  */
782
847
  setSocialProofing(options) {
783
848
  this.options = { ...this.options };
@@ -793,7 +858,6 @@ var Checkout = class Checkout {
793
858
  * @param currency Primary currency or 'auto' for automatic detection
794
859
  * @param defaultCurrency Default currency when using 'auto'
795
860
  * @param showInlineSelector Whether to show inline currency selector
796
- * @returns A new builder instance with currency configuration
797
861
  */
798
862
  setCurrency(currency, defaultCurrency = "usd", showInlineSelector = true) {
799
863
  this.options = {
@@ -809,7 +873,6 @@ var Checkout = class Checkout {
809
873
  *
810
874
  * @param cancelUrl URL for back button when in page mode
811
875
  * @param cancelIcon Custom cancel icon URL
812
- * @returns A new builder instance with navigation configuration
813
876
  */
814
877
  setCancelButton(cancelUrl, cancelIcon) {
815
878
  this.options = { ...this.options };
@@ -821,7 +884,6 @@ var Checkout = class Checkout {
821
884
  * Associates purchases with an affiliate account.
822
885
  *
823
886
  * @param userId Affiliate user ID
824
- * @returns A new builder instance with affiliate configuration
825
887
  */
826
888
  setAffiliate(userId) {
827
889
  this.options = {
@@ -834,7 +896,6 @@ var Checkout = class Checkout {
834
896
  * Sets a custom image/icon for the checkout.
835
897
  *
836
898
  * @param imageUrl Secure HTTPS URL to the image
837
- * @returns A new builder instance with custom image
838
899
  */
839
900
  setImage(imageUrl) {
840
901
  this.options = {
@@ -844,12 +905,13 @@ var Checkout = class Checkout {
844
905
  return this;
845
906
  }
846
907
  /**
847
- * Configures the checkout for license renewal.
908
+ * Configures the checkout for license renewal or upgrade by the license key.
909
+ *
910
+ * @note - This is less secure since it exposes the license key to the client. Use only in authenticated contexts.
848
911
  *
849
912
  * @param licenseKey The license key to renew
850
- * @returns A new builder instance configured for renewal
851
913
  */
852
- setLicenseRenewal(licenseKey) {
914
+ setLicenseUpgradeByKey(licenseKey) {
853
915
  this.options = {
854
916
  ...this.options,
855
917
  license_key: licenseKey
@@ -857,9 +919,20 @@ var Checkout = class Checkout {
857
919
  return this;
858
920
  }
859
921
  /**
860
- * Builds and returns the final checkout options to be used with the `@freemius/checkout` package.
922
+ * Configures the checkout for license upgrade using an authorization token.
861
923
  *
862
- * @note - This is async by purpose so that we can allow for future enhancements that might require async operations.
924
+ * @param params The license upgrade authorization parameters
925
+ */
926
+ setLicenseUpgradeByAuth(params) {
927
+ this.options = {
928
+ ...this.options,
929
+ license_id: params.licenseId,
930
+ authorization: params.authorization
931
+ };
932
+ return this;
933
+ }
934
+ /**
935
+ * Builds and returns the final checkout options to be used with the `@freemius/checkout` package.
863
936
  *
864
937
  * @returns The constructed CheckoutOptions object
865
938
  */
@@ -868,8 +941,6 @@ var Checkout = class Checkout {
868
941
  }
869
942
  /**
870
943
  * Generates a checkout link based on the current builder state.
871
- *
872
- * @note - This is async by purpose so that we can allow for future enhancements that might require async operations.
873
944
  */
874
945
  getLink() {
875
946
  const checkoutOptions = (0, __freemius_checkout.convertCheckoutOptionsToQueryParams)(this.options);
@@ -890,39 +961,6 @@ var Checkout = class Checkout {
890
961
  }
891
962
  };
892
963
 
893
- //#endregion
894
- //#region src/errors/ActionError.ts
895
- var ActionError = class ActionError extends Error {
896
- statusCode;
897
- validationIssues;
898
- constructor(message, statusCode = 400, validationIssues) {
899
- super(message);
900
- this.name = "ActionError";
901
- this.statusCode = statusCode;
902
- this.validationIssues = validationIssues;
903
- }
904
- toResponse() {
905
- const errorResponse = { message: this.message };
906
- if (this.validationIssues) errorResponse.issues = this.validationIssues;
907
- return Response.json(errorResponse, { status: this.statusCode });
908
- }
909
- static badRequest(message) {
910
- return new ActionError(message, 400);
911
- }
912
- static unauthorized(message = "Unauthorized") {
913
- return new ActionError(message, 401);
914
- }
915
- static notFound(message = "Not found") {
916
- return new ActionError(message, 404);
917
- }
918
- static validationFailed(message, validationIssues) {
919
- return new ActionError(message, 400, validationIssues);
920
- }
921
- static internalError(message = "Internal server error") {
922
- return new ActionError(message, 500);
923
- }
924
- };
925
-
926
964
  //#endregion
927
965
  //#region src/checkout/PricingRetriever.ts
928
966
  var PricingRetriever = class {
@@ -955,7 +993,10 @@ var PurchaseProcessor = class {
955
993
  return request.method === "POST" && action === "process_purchase";
956
994
  }
957
995
  async processAction(request) {
958
- const purchaseSchema = zod.object({ purchase: zod.object({ license_id: zod.string() }) });
996
+ const purchaseSchema = zod.object({
997
+ purchase: zod.object({ license_id: zod.string() }).optional(),
998
+ trial: zod.object({ license_id: zod.string() }).optional()
999
+ });
959
1000
  const contentType = request.headers.get("content-type");
960
1001
  if (!contentType || !contentType.includes("application/json")) throw ActionError.badRequest("Invalid content type. Expected application/json");
961
1002
  let requestBody;
@@ -966,7 +1007,8 @@ var PurchaseProcessor = class {
966
1007
  }
967
1008
  const parseResult = purchaseSchema.safeParse(requestBody);
968
1009
  if (!parseResult.success) throw ActionError.validationFailed("Invalid request body format", parseResult.error.issues);
969
- const { purchase: { license_id: licenseId } } = parseResult.data;
1010
+ const licenseId = parseResult.data.purchase?.license_id ?? parseResult.data.trial?.license_id;
1011
+ if (!licenseId) throw ActionError.badRequest("License ID is required in the request body, either from purchase or from trial.");
970
1012
  const purchase = await this.purchase.retrievePurchase(licenseId);
971
1013
  if (!purchase) throw ActionError.notFound("No purchase data found for the provided license ID");
972
1014
  if (this.callback) {
@@ -984,11 +1026,13 @@ var CheckoutRedirectInfo = class {
984
1026
  plan_id;
985
1027
  email;
986
1028
  pricing_id;
987
- currency;
1029
+ action;
988
1030
  license_id;
989
1031
  expiration;
990
1032
  quota;
991
- action;
1033
+ trial;
1034
+ trial_ends_at;
1035
+ currency;
992
1036
  amount;
993
1037
  tax;
994
1038
  type;
@@ -1000,17 +1044,19 @@ var CheckoutRedirectInfo = class {
1000
1044
  this.plan_id = idToString(data.plan_id);
1001
1045
  this.email = data.email;
1002
1046
  this.pricing_id = idToString(data.pricing_id);
1003
- this.currency = data.currency ? parseCurrency(data.currency) : CURRENCY.USD;
1047
+ this.action = data.action ? data.action : "purchase";
1004
1048
  this.license_id = idToString(data.license_id);
1005
1049
  this.expiration = data.expiration ? parseDateTime(data.expiration) : null;
1006
1050
  this.quota = data.quota ? parseNumber(data.quota) : null;
1007
- this.action = data.action ? data.action : null;
1051
+ this.trial = data.trial ? data.trial : null;
1052
+ this.trial_ends_at = data.trial_ends_at ? parseDateTime(data.trial_ends_at) : null;
1053
+ this.currency = data.currency ? parseCurrency(data.currency) : CURRENCY.USD;
1008
1054
  this.amount = parseNumber(data.amount);
1009
1055
  this.tax = parseNumber(data.tax);
1010
1056
  this.subscription_id = data.subscription_id ? idToString(data.subscription_id) : null;
1011
1057
  this.billing_cycle = data.billing_cycle ? parseBillingCycle(data.billing_cycle) : null;
1012
1058
  this.payment_id = data.payment_id ? idToString(data.payment_id) : null;
1013
- this.type = this.subscription_id ? "subscription" : "one-off";
1059
+ this.type = this.subscription_id ? "subscription" : this.payment_id ? "one-off" : null;
1014
1060
  }
1015
1061
  isSubscription() {
1016
1062
  return this.type === "subscription";
@@ -1021,17 +1067,19 @@ var CheckoutRedirectInfo = class {
1021
1067
  plan_id: this.plan_id,
1022
1068
  email: this.email,
1023
1069
  pricing_id: this.pricing_id,
1024
- currency: this.currency,
1025
1070
  license_id: this.license_id,
1026
1071
  expiration: this.expiration,
1027
1072
  quota: this.quota,
1073
+ trial: this.trial,
1074
+ trial_ends_at: this.trial_ends_at,
1075
+ currency: this.currency,
1028
1076
  action: this.action,
1029
1077
  amount: this.amount,
1030
1078
  tax: this.tax,
1031
- type: this.type,
1032
1079
  subscription_id: this.subscription_id,
1033
1080
  billing_cycle: this.billing_cycle,
1034
- payment_id: this.payment_id
1081
+ payment_id: this.payment_id,
1082
+ type: this.type
1035
1083
  };
1036
1084
  }
1037
1085
  };
@@ -1075,11 +1123,16 @@ var RedirectProcessor = class {
1075
1123
  }
1076
1124
  const cleanUrl = this.getCleanUrl(url.href);
1077
1125
  const calculatedSignature = (0, crypto.createHmac)("sha256", this.secretKey).update(cleanUrl).digest("hex");
1078
- const result = (0, crypto.timingSafeEqual)(Buffer.from(calculatedSignature), Buffer.from(signature));
1079
- if (!result) return null;
1080
- const params = Object.fromEntries(url.searchParams.entries());
1081
- if (!params.user_id || !params.plan_id || !params.pricing_id || !params.email) return null;
1082
- return new CheckoutRedirectInfo(params);
1126
+ try {
1127
+ const result = (0, crypto.timingSafeEqual)(Buffer.from(calculatedSignature), Buffer.from(signature));
1128
+ if (!result) return null;
1129
+ const params = Object.fromEntries(url.searchParams.entries());
1130
+ if (!params.user_id || !params.plan_id || !params.pricing_id || !params.email) return null;
1131
+ return new CheckoutRedirectInfo(params);
1132
+ } catch (e) {
1133
+ console.error("Error getting redirection information:", e);
1134
+ return null;
1135
+ }
1083
1136
  }
1084
1137
  getCleanUrl(url) {
1085
1138
  const signatureParam = "&signature=";
@@ -1103,9 +1156,6 @@ var CheckoutRequestProcessor = class {
1103
1156
  return (request) => this.process(config, request);
1104
1157
  }
1105
1158
  async process(config, request) {
1106
- const url = new URL(request.url);
1107
- const action = url.searchParams.get("action");
1108
- if (!action) return Response.json({ error: "Action parameter is required" }, { status: 400 });
1109
1159
  const actionHandlers = [
1110
1160
  this.getPricingRetriever(),
1111
1161
  this.getRedirectProcessor({
@@ -1167,7 +1217,8 @@ var CheckoutRequestProcessor = class {
1167
1217
  //#region src/services/CheckoutService.ts
1168
1218
  var CheckoutService = class {
1169
1219
  request;
1170
- constructor(productId, publicKey, secretKey, purchase, pricing) {
1220
+ constructor(api, productId, publicKey, secretKey, purchase, pricing) {
1221
+ this.api = api;
1171
1222
  this.productId = productId;
1172
1223
  this.publicKey = publicKey;
1173
1224
  this.secretKey = secretKey;
@@ -1208,16 +1259,20 @@ var CheckoutService = class {
1208
1259
  * @example
1209
1260
  */
1210
1261
  async create(options = {}) {
1211
- const { user, isSandbox = false, withRecommendation = true, title, image, planId, quota, trial } = options;
1262
+ const { user, isSandbox = false, withRecommendation = true, title, image, planId, quota, trial, licenseId } = options;
1212
1263
  const builder = new Checkout(idToString(this.productId), this.publicKey, this.secretKey);
1213
1264
  if (user) builder.setUser(user, true);
1214
1265
  if (withRecommendation) builder.setRecommendations();
1215
- if (isSandbox) builder.setSandbox();
1266
+ if (isSandbox) builder.setSandbox(await this.getSandboxParams());
1216
1267
  if (title) builder.setTitle(title);
1217
1268
  if (image) builder.setImage(image);
1218
1269
  if (planId) builder.setPlan(planId);
1219
1270
  if (quota) builder.setQuota(quota);
1220
1271
  if (trial) builder.setTrial(trial);
1272
+ if (licenseId) {
1273
+ const authorization = await this.getLicenseUpgradeAuth(licenseId);
1274
+ builder.setLicenseUpgradeByAuth(authorization);
1275
+ }
1221
1276
  return builder;
1222
1277
  }
1223
1278
  /**
@@ -1226,12 +1281,28 @@ var CheckoutService = class {
1226
1281
  * This shouldn't be used in production, but is useful for testing purposes.
1227
1282
  *
1228
1283
  * @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).
1229
- *
1230
- * @todo - This has a duplication with the `inSandbox` method in the builder. Consider refactoring to avoid this duplication.
1231
- * Also think about whether we should make the builder's `inSandbox` method async as well.
1232
1284
  */
1233
1285
  async getSandboxParams() {
1234
- return Checkout.createSandboxToken(idToString(this.productId), this.secretKey, this.publicKey);
1286
+ const productId = idToString(this.productId);
1287
+ const timestamp = Math.floor(Date.now() / 1e3).toString();
1288
+ const token = `${timestamp}${productId}${this.secretKey}${this.publicKey}checkout`;
1289
+ return {
1290
+ ctx: timestamp,
1291
+ token: (0, crypto.createHash)("md5").update(token).digest("hex")
1292
+ };
1293
+ }
1294
+ /**
1295
+ * Retrieves the license upgrade authorization for a given license ID.
1296
+ *
1297
+ * This is used to authorize a license upgrade during the checkout process. Useful when creating upgrade links for existing users.
1298
+ */
1299
+ async getLicenseUpgradeAuth(licenseId) {
1300
+ const auth = await this.api.license.retrieveCheckoutUpgradeAuthorization(licenseId);
1301
+ if (!auth) throw new Error("Failed to retrieve license upgrade authorization");
1302
+ return {
1303
+ licenseId,
1304
+ authorization: auth
1305
+ };
1235
1306
  }
1236
1307
  /**
1237
1308
  * Processes a redirect URL and returns the checkout redirect information if valid.
@@ -1863,11 +1934,16 @@ var CustomerPortalService = class {
1863
1934
  //#endregion
1864
1935
  //#region src/webhook/WebhookListener.ts
1865
1936
  const SIGNATURE_HEADER = "x-signature";
1937
+ const DEFAULT_ERROR_HANDLER = async (error) => {
1938
+ console.error("Webhook processing error:", error);
1939
+ };
1866
1940
  var WebhookListener = class {
1867
1941
  eventHandlers = /* @__PURE__ */ new Map();
1868
- constructor(secretKey, onError = console.error) {
1942
+ constructor(api, secretKey, onError = DEFAULT_ERROR_HANDLER, authenticationMethod = WebhookAuthenticationMethod.SignatureHeader) {
1943
+ this.api = api;
1869
1944
  this.secretKey = secretKey;
1870
1945
  this.onError = onError;
1946
+ this.authenticationMethod = authenticationMethod;
1871
1947
  }
1872
1948
  on(typeOrTypes, handler) {
1873
1949
  const types = Array.isArray(typeOrTypes) ? typeOrTypes : [typeOrTypes];
@@ -1935,40 +2011,52 @@ var WebhookListener = class {
1935
2011
  * Returns an object you can map to your framework's response easily.
1936
2012
  */
1937
2013
  async process(input) {
1938
- const sig = this.getHeader(SIGNATURE_HEADER, input.headers);
1939
- if (!this.verifySignature(input.rawBody, sig)) return {
1940
- status: 401,
1941
- success: false,
1942
- error: "Invalid signature"
1943
- };
1944
- let evt;
1945
2014
  try {
1946
- const parsed = JSON.parse(typeof input.rawBody === "string" ? input.rawBody : input.rawBody.toString("utf8"));
1947
- if (!parsed || typeof parsed.type !== "string") return {
1948
- status: 400,
1949
- success: false,
1950
- error: "Invalid payload"
1951
- };
1952
- evt = parsed;
1953
- } catch {
2015
+ const event = this.authenticationMethod === WebhookAuthenticationMethod.SignatureHeader ? await this.authenticateAndGetEventFromInput(input) : await this.authenticateAndGetEventFromApi(input);
2016
+ return this.processEvent(event);
2017
+ } catch (error) {
2018
+ if (error instanceof WebhookError) return error.toResponse();
1954
2019
  return {
1955
- status: 400,
2020
+ status: 500,
1956
2021
  success: false,
1957
- error: "Malformed JSON"
2022
+ error: "Internal Server Error"
1958
2023
  };
1959
2024
  }
1960
- const eventType = evt.type;
2025
+ }
2026
+ async authenticateAndGetEventFromInput(input) {
2027
+ const sig = this.getHeader(SIGNATURE_HEADER, input.headers);
2028
+ if (!this.verifySignature(input.rawBody, sig)) throw new WebhookError("Invalid signature", 401);
2029
+ return this.parseEventFromInput(input);
2030
+ }
2031
+ async authenticateAndGetEventFromApi(input) {
2032
+ const jsonPayload = this.parseEventFromInput(input);
2033
+ if (!jsonPayload.id) throw new WebhookError("Invalid payload");
2034
+ const event = await this.api.event.retrieve(jsonPayload.id);
2035
+ if (!event) throw new WebhookError("Event not found", 404);
2036
+ return event;
2037
+ }
2038
+ parseEventFromInput(input) {
2039
+ try {
2040
+ const parsed = JSON.parse(typeof input.rawBody === "string" ? input.rawBody : input.rawBody.toString("utf8"));
2041
+ if (!parsed || typeof parsed.type !== "string") throw new WebhookError("Invalid payload");
2042
+ return parsed;
2043
+ } catch {
2044
+ throw new WebhookError("Malformed JSON");
2045
+ }
2046
+ }
2047
+ async processEvent(event) {
2048
+ const eventType = event.type;
1961
2049
  const eventHandlers = this.eventHandlers.get(eventType);
1962
2050
  if (!eventHandlers || eventHandlers.size === 0) console.warn(`No handlers registered for event type: ${eventType}`);
1963
2051
  try {
1964
2052
  const promises = Array.from(eventHandlers || []).map((handler) => {
1965
2053
  const typedHandler = handler;
1966
- const typedEvent = evt;
2054
+ const typedEvent = event;
1967
2055
  return typedHandler(typedEvent);
1968
2056
  });
1969
2057
  await Promise.all(promises);
1970
2058
  } catch (error) {
1971
- this.onError?.(error);
2059
+ await this.onError?.(error);
1972
2060
  return {
1973
2061
  status: 500,
1974
2062
  success: false,
@@ -1992,11 +2080,13 @@ var WebhookListener = class {
1992
2080
  //#endregion
1993
2081
  //#region src/services/WebhookService.ts
1994
2082
  var WebhookService = class {
1995
- constructor(secretKey) {
2083
+ constructor(api, secretKey) {
2084
+ this.api = api;
1996
2085
  this.secretKey = secretKey;
1997
2086
  }
1998
- createListener(onError) {
1999
- return new WebhookListener(this.secretKey, onError);
2087
+ createListener(config = {}) {
2088
+ const { onError, authenticationMethod } = config;
2089
+ return new WebhookListener(this.api, this.secretKey, onError, authenticationMethod);
2000
2090
  }
2001
2091
  createRequestProcessor(listener) {
2002
2092
  return (request) => this.processFetch(listener, request);
@@ -2498,16 +2588,19 @@ var Freemius = class {
2498
2588
  this.auth = new AuthService(productId, secretKey);
2499
2589
  this.pricing = new PricingService(this.api);
2500
2590
  this.purchase = new PurchaseService(this.api);
2501
- this.checkout = new CheckoutService(productId, publicKey, secretKey, this.purchase, this.pricing);
2591
+ this.checkout = new CheckoutService(this.api, productId, publicKey, secretKey, this.purchase, this.pricing);
2502
2592
  this.customerPortal = new CustomerPortalService(this.api, this.checkout, this.auth, this.purchase);
2503
- this.webhook = new WebhookService(secretKey);
2593
+ this.webhook = new WebhookService(this.api, secretKey);
2504
2594
  }
2505
2595
  };
2506
2596
 
2507
2597
  //#endregion
2598
+ exports.ActionError = ActionError;
2508
2599
  exports.BILLING_CYCLE = BILLING_CYCLE;
2509
2600
  exports.CURRENCY = CURRENCY;
2510
2601
  exports.Freemius = Freemius;
2602
+ exports.WebhookAuthenticationMethod = WebhookAuthenticationMethod;
2603
+ exports.WebhookError = WebhookError;
2511
2604
  exports.idToNumber = idToNumber;
2512
2605
  exports.idToString = idToString;
2513
2606
  exports.isIdsEqual = isIdsEqual;