@freemius/sdk 0.0.6 → 0.2.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.2.0";
114
173
 
115
174
  //#endregion
116
175
  //#region src/api/client.ts
@@ -421,6 +480,22 @@ var User = class extends ApiBase {
421
480
  if (!this.isGoodResponse(response.response) || !response.data || !response.data) return null;
422
481
  return response.data;
423
482
  }
483
+ async retrieveHostedCustomerPortal(userId) {
484
+ const response = await this.client.POST(`/products/{product_id}/portal/login.json`, {
485
+ params: { path: { product_id: this.productId } },
486
+ body: { id: idToString(userId) }
487
+ });
488
+ if (!this.isGoodResponse(response.response) || !response.data) return null;
489
+ return response.data;
490
+ }
491
+ async retrieveHostedCustomerPortalByEmail(email) {
492
+ const response = await this.client.POST(`/products/{product_id}/portal/login.json`, {
493
+ params: { path: { product_id: this.productId } },
494
+ body: { email }
495
+ });
496
+ if (!this.isGoodResponse(response.response) || !response.data) return null;
497
+ return response.data;
498
+ }
424
499
  };
425
500
 
426
501
  //#endregion
@@ -458,6 +533,30 @@ var Payment = class extends ApiBase {
458
533
  }
459
534
  };
460
535
 
536
+ //#endregion
537
+ //#region src/api/WebhookEvent.ts
538
+ var WebhookEvent = class extends ApiBase {
539
+ async retrieve(eventId) {
540
+ const result = await this.client.GET(`/products/{product_id}/events/{event_id}.json`, { params: { path: {
541
+ product_id: this.productId,
542
+ event_id: this.getIdForPath(eventId)
543
+ } } });
544
+ if (!this.isGoodResponse(result.response) || !result.data || !result.data.id) return null;
545
+ return result.data;
546
+ }
547
+ async retrieveMany(filter, pagination) {
548
+ const result = await this.client.GET(`/products/{product_id}/events.json`, { params: {
549
+ path: { product_id: this.productId },
550
+ query: {
551
+ ...this.getPagingParams(pagination),
552
+ ...filter ?? {}
553
+ }
554
+ } });
555
+ if (!this.isGoodResponse(result.response) || !result.data || !Array.isArray(result.data.events)) return [];
556
+ return result.data.events;
557
+ }
558
+ };
559
+
461
560
  //#endregion
462
561
  //#region src/utils/ops.ts
463
562
  function splitName(name) {
@@ -486,6 +585,7 @@ var ApiService = class {
486
585
  product;
487
586
  subscription;
488
587
  payment;
588
+ event;
489
589
  baseUrl;
490
590
  constructor(productId, apiKey, secretKey, publicKey) {
491
591
  this.secretKey = secretKey;
@@ -498,6 +598,7 @@ var ApiService = class {
498
598
  this.product = new Product(this.productId, this.client);
499
599
  this.subscription = new Subscription(this.productId, this.client);
500
600
  this.payment = new Payment(this.productId, this.client);
601
+ this.event = new WebhookEvent(this.productId, this.client);
501
602
  }
502
603
  /**
503
604
  * Low level API client for direct access to the Freemius API.
@@ -578,15 +679,7 @@ var ApiService = class {
578
679
  * Every method returns the existing instance of the builder for chainability,
579
680
  * The final `getOptions()` method returns the constructed `CheckoutOptions` object.
580
681
  */
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
- }
682
+ var Checkout = class {
590
683
  options;
591
684
  constructor(productId, publicKey, secretKey) {
592
685
  this.productId = productId;
@@ -597,12 +690,11 @@ var Checkout = class Checkout {
597
690
  /**
598
691
  * Enables sandbox mode for testing purposes.
599
692
  *
600
- * @returns A new builder instance with sandbox configuration
601
693
  */
602
- setSandbox() {
694
+ setSandbox(sandbox) {
603
695
  this.options = {
604
696
  ...this.options,
605
- sandbox: Checkout.createSandboxToken(this.productId, this.secretKey, this.publicKey)
697
+ sandbox
606
698
  };
607
699
  return this;
608
700
  }
@@ -612,7 +704,6 @@ var Checkout = class Checkout {
612
704
  * @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
705
  * @param readonly If true, the user information will be read-only in the checkout session.
614
706
  *
615
- * @returns A new builder instance with user configuration
616
707
  */
617
708
  setUser(user, readonly = true) {
618
709
  if (!user) return this;
@@ -636,7 +727,6 @@ var Checkout = class Checkout {
636
727
  * Applies recommended UI settings for better user experience.
637
728
  * This includes fullscreen mode, upsells, refund badge, and reviews display.
638
729
  *
639
- * @returns A new builder instance with recommended UI settings
640
730
  */
641
731
  setRecommendations() {
642
732
  this.options = {
@@ -653,7 +743,6 @@ var Checkout = class Checkout {
653
743
  * Sets the plan ID for the checkout.
654
744
  *
655
745
  * @param planId The plan ID to purchase
656
- * @returns A new builder instance with plan ID set
657
746
  */
658
747
  setPlan(planId) {
659
748
  this.options = {
@@ -666,7 +755,6 @@ var Checkout = class Checkout {
666
755
  * Sets the number of licenses to purchase.
667
756
  *
668
757
  * @param count Number of licenses
669
- * @returns A new builder instance with license count set
670
758
  */
671
759
  setQuota(count) {
672
760
  this.options = {
@@ -694,7 +782,6 @@ var Checkout = class Checkout {
694
782
  *
695
783
  * @param coupon The coupon code to apply
696
784
  * @param hideUI Whether to hide the coupon input field from users
697
- * @returns A new builder instance with coupon configuration
698
785
  */
699
786
  setCoupon(options) {
700
787
  const { code: coupon, hideUI = false } = options;
@@ -709,7 +796,6 @@ var Checkout = class Checkout {
709
796
  * Enables trial mode for the checkout.
710
797
  *
711
798
  * @param mode Trial type - true/false for plan default, or specific 'free'/'paid' mode
712
- * @returns A new builder instance with trial configuration
713
799
  */
714
800
  setTrial(mode = true) {
715
801
  this.options = {
@@ -722,7 +808,6 @@ var Checkout = class Checkout {
722
808
  * Configures the visual layout and appearance of the checkout.
723
809
  *
724
810
  * @param options Appearance configuration options
725
- * @returns A new builder instance with appearance configuration
726
811
  */
727
812
  setAppearance(options) {
728
813
  this.options = { ...this.options };
@@ -737,7 +822,6 @@ var Checkout = class Checkout {
737
822
  * Configures discount display settings.
738
823
  *
739
824
  * @param options Discount configuration options
740
- * @returns A new builder instance with discount configuration
741
825
  */
742
826
  setDiscounts(options) {
743
827
  this.options = { ...this.options };
@@ -752,7 +836,6 @@ var Checkout = class Checkout {
752
836
  *
753
837
  * @param selector Type of billing cycle selector to show
754
838
  * @param defaultCycle Default billing cycle to select
755
- * @returns A new builder instance with billing cycle configuration
756
839
  */
757
840
  setBillingCycle(defaultCycle, selector) {
758
841
  this.options = { ...this.options };
@@ -764,7 +847,6 @@ var Checkout = class Checkout {
764
847
  * Sets the language/locale for the checkout.
765
848
  *
766
849
  * @param locale Language setting - 'auto', 'auto-beta', or specific locale like 'en_US'
767
- * @returns A new builder instance with locale configuration
768
850
  */
769
851
  setLanguage(locale = "auto") {
770
852
  this.options = {
@@ -777,7 +859,6 @@ var Checkout = class Checkout {
777
859
  * Configures review and badge display settings.
778
860
  *
779
861
  * @param options Review and badge configuration
780
- * @returns A new builder instance with reviews and badges configuration
781
862
  */
782
863
  setSocialProofing(options) {
783
864
  this.options = { ...this.options };
@@ -793,7 +874,6 @@ var Checkout = class Checkout {
793
874
  * @param currency Primary currency or 'auto' for automatic detection
794
875
  * @param defaultCurrency Default currency when using 'auto'
795
876
  * @param showInlineSelector Whether to show inline currency selector
796
- * @returns A new builder instance with currency configuration
797
877
  */
798
878
  setCurrency(currency, defaultCurrency = "usd", showInlineSelector = true) {
799
879
  this.options = {
@@ -809,7 +889,6 @@ var Checkout = class Checkout {
809
889
  *
810
890
  * @param cancelUrl URL for back button when in page mode
811
891
  * @param cancelIcon Custom cancel icon URL
812
- * @returns A new builder instance with navigation configuration
813
892
  */
814
893
  setCancelButton(cancelUrl, cancelIcon) {
815
894
  this.options = { ...this.options };
@@ -821,7 +900,6 @@ var Checkout = class Checkout {
821
900
  * Associates purchases with an affiliate account.
822
901
  *
823
902
  * @param userId Affiliate user ID
824
- * @returns A new builder instance with affiliate configuration
825
903
  */
826
904
  setAffiliate(userId) {
827
905
  this.options = {
@@ -834,7 +912,6 @@ var Checkout = class Checkout {
834
912
  * Sets a custom image/icon for the checkout.
835
913
  *
836
914
  * @param imageUrl Secure HTTPS URL to the image
837
- * @returns A new builder instance with custom image
838
915
  */
839
916
  setImage(imageUrl) {
840
917
  this.options = {
@@ -844,12 +921,13 @@ var Checkout = class Checkout {
844
921
  return this;
845
922
  }
846
923
  /**
847
- * Configures the checkout for license renewal.
924
+ * Configures the checkout for license renewal or upgrade by the license key.
925
+ *
926
+ * @note - This is less secure since it exposes the license key to the client. Use only in authenticated contexts.
848
927
  *
849
928
  * @param licenseKey The license key to renew
850
- * @returns A new builder instance configured for renewal
851
929
  */
852
- setLicenseRenewal(licenseKey) {
930
+ setLicenseUpgradeByKey(licenseKey) {
853
931
  this.options = {
854
932
  ...this.options,
855
933
  license_key: licenseKey
@@ -857,9 +935,20 @@ var Checkout = class Checkout {
857
935
  return this;
858
936
  }
859
937
  /**
860
- * Builds and returns the final checkout options to be used with the `@freemius/checkout` package.
938
+ * Configures the checkout for license upgrade using an authorization token.
861
939
  *
862
- * @note - This is async by purpose so that we can allow for future enhancements that might require async operations.
940
+ * @param params The license upgrade authorization parameters
941
+ */
942
+ setLicenseUpgradeByAuth(params) {
943
+ this.options = {
944
+ ...this.options,
945
+ license_id: params.licenseId,
946
+ authorization: params.authorization
947
+ };
948
+ return this;
949
+ }
950
+ /**
951
+ * Builds and returns the final checkout options to be used with the `@freemius/checkout` package.
863
952
  *
864
953
  * @returns The constructed CheckoutOptions object
865
954
  */
@@ -868,8 +957,6 @@ var Checkout = class Checkout {
868
957
  }
869
958
  /**
870
959
  * 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
960
  */
874
961
  getLink() {
875
962
  const checkoutOptions = (0, __freemius_checkout.convertCheckoutOptionsToQueryParams)(this.options);
@@ -890,39 +977,6 @@ var Checkout = class Checkout {
890
977
  }
891
978
  };
892
979
 
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
980
  //#endregion
927
981
  //#region src/checkout/PricingRetriever.ts
928
982
  var PricingRetriever = class {
@@ -955,7 +1009,10 @@ var PurchaseProcessor = class {
955
1009
  return request.method === "POST" && action === "process_purchase";
956
1010
  }
957
1011
  async processAction(request) {
958
- const purchaseSchema = zod.object({ purchase: zod.object({ license_id: zod.string() }) });
1012
+ const purchaseSchema = zod.object({
1013
+ purchase: zod.object({ license_id: zod.string() }).optional(),
1014
+ trial: zod.object({ license_id: zod.string() }).optional()
1015
+ });
959
1016
  const contentType = request.headers.get("content-type");
960
1017
  if (!contentType || !contentType.includes("application/json")) throw ActionError.badRequest("Invalid content type. Expected application/json");
961
1018
  let requestBody;
@@ -966,7 +1023,8 @@ var PurchaseProcessor = class {
966
1023
  }
967
1024
  const parseResult = purchaseSchema.safeParse(requestBody);
968
1025
  if (!parseResult.success) throw ActionError.validationFailed("Invalid request body format", parseResult.error.issues);
969
- const { purchase: { license_id: licenseId } } = parseResult.data;
1026
+ const licenseId = parseResult.data.purchase?.license_id ?? parseResult.data.trial?.license_id;
1027
+ if (!licenseId) throw ActionError.badRequest("License ID is required in the request body, either from purchase or from trial.");
970
1028
  const purchase = await this.purchase.retrievePurchase(licenseId);
971
1029
  if (!purchase) throw ActionError.notFound("No purchase data found for the provided license ID");
972
1030
  if (this.callback) {
@@ -984,11 +1042,13 @@ var CheckoutRedirectInfo = class {
984
1042
  plan_id;
985
1043
  email;
986
1044
  pricing_id;
987
- currency;
1045
+ action;
988
1046
  license_id;
989
1047
  expiration;
990
1048
  quota;
991
- action;
1049
+ trial;
1050
+ trial_ends_at;
1051
+ currency;
992
1052
  amount;
993
1053
  tax;
994
1054
  type;
@@ -1000,17 +1060,19 @@ var CheckoutRedirectInfo = class {
1000
1060
  this.plan_id = idToString(data.plan_id);
1001
1061
  this.email = data.email;
1002
1062
  this.pricing_id = idToString(data.pricing_id);
1003
- this.currency = data.currency ? parseCurrency(data.currency) : CURRENCY.USD;
1063
+ this.action = data.action ? data.action : "purchase";
1004
1064
  this.license_id = idToString(data.license_id);
1005
1065
  this.expiration = data.expiration ? parseDateTime(data.expiration) : null;
1006
1066
  this.quota = data.quota ? parseNumber(data.quota) : null;
1007
- this.action = data.action ? data.action : null;
1067
+ this.trial = data.trial ? data.trial : null;
1068
+ this.trial_ends_at = data.trial_ends_at ? parseDateTime(data.trial_ends_at) : null;
1069
+ this.currency = data.currency ? parseCurrency(data.currency) : CURRENCY.USD;
1008
1070
  this.amount = parseNumber(data.amount);
1009
1071
  this.tax = parseNumber(data.tax);
1010
1072
  this.subscription_id = data.subscription_id ? idToString(data.subscription_id) : null;
1011
1073
  this.billing_cycle = data.billing_cycle ? parseBillingCycle(data.billing_cycle) : null;
1012
1074
  this.payment_id = data.payment_id ? idToString(data.payment_id) : null;
1013
- this.type = this.subscription_id ? "subscription" : "one-off";
1075
+ this.type = this.subscription_id ? "subscription" : this.payment_id ? "one-off" : null;
1014
1076
  }
1015
1077
  isSubscription() {
1016
1078
  return this.type === "subscription";
@@ -1021,17 +1083,19 @@ var CheckoutRedirectInfo = class {
1021
1083
  plan_id: this.plan_id,
1022
1084
  email: this.email,
1023
1085
  pricing_id: this.pricing_id,
1024
- currency: this.currency,
1025
1086
  license_id: this.license_id,
1026
1087
  expiration: this.expiration,
1027
1088
  quota: this.quota,
1089
+ trial: this.trial,
1090
+ trial_ends_at: this.trial_ends_at,
1091
+ currency: this.currency,
1028
1092
  action: this.action,
1029
1093
  amount: this.amount,
1030
1094
  tax: this.tax,
1031
- type: this.type,
1032
1095
  subscription_id: this.subscription_id,
1033
1096
  billing_cycle: this.billing_cycle,
1034
- payment_id: this.payment_id
1097
+ payment_id: this.payment_id,
1098
+ type: this.type
1035
1099
  };
1036
1100
  }
1037
1101
  };
@@ -1075,11 +1139,16 @@ var RedirectProcessor = class {
1075
1139
  }
1076
1140
  const cleanUrl = this.getCleanUrl(url.href);
1077
1141
  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);
1142
+ try {
1143
+ const result = (0, crypto.timingSafeEqual)(Buffer.from(calculatedSignature), Buffer.from(signature));
1144
+ if (!result) return null;
1145
+ const params = Object.fromEntries(url.searchParams.entries());
1146
+ if (!params.user_id || !params.plan_id || !params.pricing_id || !params.email) return null;
1147
+ return new CheckoutRedirectInfo(params);
1148
+ } catch (e) {
1149
+ console.error("Error getting redirection information:", e);
1150
+ return null;
1151
+ }
1083
1152
  }
1084
1153
  getCleanUrl(url) {
1085
1154
  const signatureParam = "&signature=";
@@ -1103,9 +1172,6 @@ var CheckoutRequestProcessor = class {
1103
1172
  return (request) => this.process(config, request);
1104
1173
  }
1105
1174
  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
1175
  const actionHandlers = [
1110
1176
  this.getPricingRetriever(),
1111
1177
  this.getRedirectProcessor({
@@ -1167,7 +1233,8 @@ var CheckoutRequestProcessor = class {
1167
1233
  //#region src/services/CheckoutService.ts
1168
1234
  var CheckoutService = class {
1169
1235
  request;
1170
- constructor(productId, publicKey, secretKey, purchase, pricing) {
1236
+ constructor(api, productId, publicKey, secretKey, purchase, pricing) {
1237
+ this.api = api;
1171
1238
  this.productId = productId;
1172
1239
  this.publicKey = publicKey;
1173
1240
  this.secretKey = secretKey;
@@ -1208,16 +1275,20 @@ var CheckoutService = class {
1208
1275
  * @example
1209
1276
  */
1210
1277
  async create(options = {}) {
1211
- const { user, isSandbox = false, withRecommendation = true, title, image, planId, quota, trial } = options;
1278
+ const { user, isSandbox = false, withRecommendation = true, title, image, planId, quota, trial, licenseId } = options;
1212
1279
  const builder = new Checkout(idToString(this.productId), this.publicKey, this.secretKey);
1213
1280
  if (user) builder.setUser(user, true);
1214
1281
  if (withRecommendation) builder.setRecommendations();
1215
- if (isSandbox) builder.setSandbox();
1282
+ if (isSandbox) builder.setSandbox(await this.getSandboxParams());
1216
1283
  if (title) builder.setTitle(title);
1217
1284
  if (image) builder.setImage(image);
1218
1285
  if (planId) builder.setPlan(planId);
1219
1286
  if (quota) builder.setQuota(quota);
1220
1287
  if (trial) builder.setTrial(trial);
1288
+ if (licenseId) {
1289
+ const authorization = await this.getLicenseUpgradeAuth(licenseId);
1290
+ builder.setLicenseUpgradeByAuth(authorization);
1291
+ }
1221
1292
  return builder;
1222
1293
  }
1223
1294
  /**
@@ -1226,12 +1297,28 @@ var CheckoutService = class {
1226
1297
  * This shouldn't be used in production, but is useful for testing purposes.
1227
1298
  *
1228
1299
  * @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
1300
  */
1233
1301
  async getSandboxParams() {
1234
- return Checkout.createSandboxToken(idToString(this.productId), this.secretKey, this.publicKey);
1302
+ const productId = idToString(this.productId);
1303
+ const timestamp = Math.floor(Date.now() / 1e3).toString();
1304
+ const token = `${timestamp}${productId}${this.secretKey}${this.publicKey}checkout`;
1305
+ return {
1306
+ ctx: timestamp,
1307
+ token: (0, crypto.createHash)("md5").update(token).digest("hex")
1308
+ };
1309
+ }
1310
+ /**
1311
+ * Retrieves the license upgrade authorization for a given license ID.
1312
+ *
1313
+ * This is used to authorize a license upgrade during the checkout process. Useful when creating upgrade links for existing users.
1314
+ */
1315
+ async getLicenseUpgradeAuth(licenseId) {
1316
+ const auth = await this.api.license.retrieveCheckoutUpgradeAuthorization(licenseId);
1317
+ if (!auth) throw new Error("Failed to retrieve license upgrade authorization");
1318
+ return {
1319
+ licenseId,
1320
+ authorization: auth
1321
+ };
1235
1322
  }
1236
1323
  /**
1237
1324
  * Processes a redirect URL and returns the checkout redirect information if valid.
@@ -1863,11 +1950,16 @@ var CustomerPortalService = class {
1863
1950
  //#endregion
1864
1951
  //#region src/webhook/WebhookListener.ts
1865
1952
  const SIGNATURE_HEADER = "x-signature";
1953
+ const DEFAULT_ERROR_HANDLER = async (error) => {
1954
+ console.error("Webhook processing error:", error);
1955
+ };
1866
1956
  var WebhookListener = class {
1867
1957
  eventHandlers = /* @__PURE__ */ new Map();
1868
- constructor(secretKey, onError = console.error) {
1958
+ constructor(api, secretKey, onError = DEFAULT_ERROR_HANDLER, authenticationMethod = WebhookAuthenticationMethod.SignatureHeader) {
1959
+ this.api = api;
1869
1960
  this.secretKey = secretKey;
1870
1961
  this.onError = onError;
1962
+ this.authenticationMethod = authenticationMethod;
1871
1963
  }
1872
1964
  on(typeOrTypes, handler) {
1873
1965
  const types = Array.isArray(typeOrTypes) ? typeOrTypes : [typeOrTypes];
@@ -1935,40 +2027,52 @@ var WebhookListener = class {
1935
2027
  * Returns an object you can map to your framework's response easily.
1936
2028
  */
1937
2029
  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
2030
  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 {
2031
+ const event = this.authenticationMethod === WebhookAuthenticationMethod.SignatureHeader ? await this.authenticateAndGetEventFromInput(input) : await this.authenticateAndGetEventFromApi(input);
2032
+ return this.processEvent(event);
2033
+ } catch (error) {
2034
+ if (error instanceof WebhookError) return error.toResponse();
1954
2035
  return {
1955
- status: 400,
2036
+ status: 500,
1956
2037
  success: false,
1957
- error: "Malformed JSON"
2038
+ error: "Internal Server Error"
1958
2039
  };
1959
2040
  }
1960
- const eventType = evt.type;
2041
+ }
2042
+ async authenticateAndGetEventFromInput(input) {
2043
+ const sig = this.getHeader(SIGNATURE_HEADER, input.headers);
2044
+ if (!this.verifySignature(input.rawBody, sig)) throw new WebhookError("Invalid signature", 401);
2045
+ return this.parseEventFromInput(input);
2046
+ }
2047
+ async authenticateAndGetEventFromApi(input) {
2048
+ const jsonPayload = this.parseEventFromInput(input);
2049
+ if (!jsonPayload.id) throw new WebhookError("Invalid payload");
2050
+ const event = await this.api.event.retrieve(jsonPayload.id);
2051
+ if (!event) throw new WebhookError("Event not found", 404);
2052
+ return event;
2053
+ }
2054
+ parseEventFromInput(input) {
2055
+ try {
2056
+ const parsed = JSON.parse(typeof input.rawBody === "string" ? input.rawBody : input.rawBody.toString("utf8"));
2057
+ if (!parsed || typeof parsed.type !== "string") throw new WebhookError("Invalid payload");
2058
+ return parsed;
2059
+ } catch {
2060
+ throw new WebhookError("Malformed JSON");
2061
+ }
2062
+ }
2063
+ async processEvent(event) {
2064
+ const eventType = event.type;
1961
2065
  const eventHandlers = this.eventHandlers.get(eventType);
1962
2066
  if (!eventHandlers || eventHandlers.size === 0) console.warn(`No handlers registered for event type: ${eventType}`);
1963
2067
  try {
1964
2068
  const promises = Array.from(eventHandlers || []).map((handler) => {
1965
2069
  const typedHandler = handler;
1966
- const typedEvent = evt;
2070
+ const typedEvent = event;
1967
2071
  return typedHandler(typedEvent);
1968
2072
  });
1969
2073
  await Promise.all(promises);
1970
2074
  } catch (error) {
1971
- this.onError?.(error);
2075
+ await this.onError?.(error);
1972
2076
  return {
1973
2077
  status: 500,
1974
2078
  success: false,
@@ -1992,11 +2096,13 @@ var WebhookListener = class {
1992
2096
  //#endregion
1993
2097
  //#region src/services/WebhookService.ts
1994
2098
  var WebhookService = class {
1995
- constructor(secretKey) {
2099
+ constructor(api, secretKey) {
2100
+ this.api = api;
1996
2101
  this.secretKey = secretKey;
1997
2102
  }
1998
- createListener(onError) {
1999
- return new WebhookListener(this.secretKey, onError);
2103
+ createListener(config = {}) {
2104
+ const { onError, authenticationMethod } = config;
2105
+ return new WebhookListener(this.api, this.secretKey, onError, authenticationMethod);
2000
2106
  }
2001
2107
  createRequestProcessor(listener) {
2002
2108
  return (request) => this.processFetch(listener, request);
@@ -2498,16 +2604,19 @@ var Freemius = class {
2498
2604
  this.auth = new AuthService(productId, secretKey);
2499
2605
  this.pricing = new PricingService(this.api);
2500
2606
  this.purchase = new PurchaseService(this.api);
2501
- this.checkout = new CheckoutService(productId, publicKey, secretKey, this.purchase, this.pricing);
2607
+ this.checkout = new CheckoutService(this.api, productId, publicKey, secretKey, this.purchase, this.pricing);
2502
2608
  this.customerPortal = new CustomerPortalService(this.api, this.checkout, this.auth, this.purchase);
2503
- this.webhook = new WebhookService(secretKey);
2609
+ this.webhook = new WebhookService(this.api, secretKey);
2504
2610
  }
2505
2611
  };
2506
2612
 
2507
2613
  //#endregion
2614
+ exports.ActionError = ActionError;
2508
2615
  exports.BILLING_CYCLE = BILLING_CYCLE;
2509
2616
  exports.CURRENCY = CURRENCY;
2510
2617
  exports.Freemius = Freemius;
2618
+ exports.WebhookAuthenticationMethod = WebhookAuthenticationMethod;
2619
+ exports.WebhookError = WebhookError;
2511
2620
  exports.idToNumber = idToNumber;
2512
2621
  exports.idToString = idToString;
2513
2622
  exports.isIdsEqual = isIdsEqual;