@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.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.2.0";
92
151
 
93
152
  //#endregion
94
153
  //#region src/api/client.ts
@@ -399,6 +458,22 @@ var User = class extends ApiBase {
399
458
  if (!this.isGoodResponse(response.response) || !response.data || !response.data) return null;
400
459
  return response.data;
401
460
  }
461
+ async retrieveHostedCustomerPortal(userId) {
462
+ const response = await this.client.POST(`/products/{product_id}/portal/login.json`, {
463
+ params: { path: { product_id: this.productId } },
464
+ body: { id: idToString(userId) }
465
+ });
466
+ if (!this.isGoodResponse(response.response) || !response.data) return null;
467
+ return response.data;
468
+ }
469
+ async retrieveHostedCustomerPortalByEmail(email) {
470
+ const response = await this.client.POST(`/products/{product_id}/portal/login.json`, {
471
+ params: { path: { product_id: this.productId } },
472
+ body: { email }
473
+ });
474
+ if (!this.isGoodResponse(response.response) || !response.data) return null;
475
+ return response.data;
476
+ }
402
477
  };
403
478
 
404
479
  //#endregion
@@ -436,6 +511,30 @@ var Payment = class extends ApiBase {
436
511
  }
437
512
  };
438
513
 
514
+ //#endregion
515
+ //#region src/api/WebhookEvent.ts
516
+ var WebhookEvent = class extends ApiBase {
517
+ async retrieve(eventId) {
518
+ const result = await this.client.GET(`/products/{product_id}/events/{event_id}.json`, { params: { path: {
519
+ product_id: this.productId,
520
+ event_id: this.getIdForPath(eventId)
521
+ } } });
522
+ if (!this.isGoodResponse(result.response) || !result.data || !result.data.id) return null;
523
+ return result.data;
524
+ }
525
+ async retrieveMany(filter, pagination) {
526
+ const result = await this.client.GET(`/products/{product_id}/events.json`, { params: {
527
+ path: { product_id: this.productId },
528
+ query: {
529
+ ...this.getPagingParams(pagination),
530
+ ...filter ?? {}
531
+ }
532
+ } });
533
+ if (!this.isGoodResponse(result.response) || !result.data || !Array.isArray(result.data.events)) return [];
534
+ return result.data.events;
535
+ }
536
+ };
537
+
439
538
  //#endregion
440
539
  //#region src/utils/ops.ts
441
540
  function splitName(name) {
@@ -464,6 +563,7 @@ var ApiService = class {
464
563
  product;
465
564
  subscription;
466
565
  payment;
566
+ event;
467
567
  baseUrl;
468
568
  constructor(productId, apiKey, secretKey, publicKey) {
469
569
  this.secretKey = secretKey;
@@ -476,6 +576,7 @@ var ApiService = class {
476
576
  this.product = new Product(this.productId, this.client);
477
577
  this.subscription = new Subscription(this.productId, this.client);
478
578
  this.payment = new Payment(this.productId, this.client);
579
+ this.event = new WebhookEvent(this.productId, this.client);
479
580
  }
480
581
  /**
481
582
  * Low level API client for direct access to the Freemius API.
@@ -556,15 +657,7 @@ var ApiService = class {
556
657
  * Every method returns the existing instance of the builder for chainability,
557
658
  * The final `getOptions()` method returns the constructed `CheckoutOptions` object.
558
659
  */
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
- }
660
+ var Checkout = class {
568
661
  options;
569
662
  constructor(productId, publicKey, secretKey) {
570
663
  this.productId = productId;
@@ -575,12 +668,11 @@ var Checkout = class Checkout {
575
668
  /**
576
669
  * Enables sandbox mode for testing purposes.
577
670
  *
578
- * @returns A new builder instance with sandbox configuration
579
671
  */
580
- setSandbox() {
672
+ setSandbox(sandbox) {
581
673
  this.options = {
582
674
  ...this.options,
583
- sandbox: Checkout.createSandboxToken(this.productId, this.secretKey, this.publicKey)
675
+ sandbox
584
676
  };
585
677
  return this;
586
678
  }
@@ -590,7 +682,6 @@ var Checkout = class Checkout {
590
682
  * @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
683
  * @param readonly If true, the user information will be read-only in the checkout session.
592
684
  *
593
- * @returns A new builder instance with user configuration
594
685
  */
595
686
  setUser(user, readonly = true) {
596
687
  if (!user) return this;
@@ -614,7 +705,6 @@ var Checkout = class Checkout {
614
705
  * Applies recommended UI settings for better user experience.
615
706
  * This includes fullscreen mode, upsells, refund badge, and reviews display.
616
707
  *
617
- * @returns A new builder instance with recommended UI settings
618
708
  */
619
709
  setRecommendations() {
620
710
  this.options = {
@@ -631,7 +721,6 @@ var Checkout = class Checkout {
631
721
  * Sets the plan ID for the checkout.
632
722
  *
633
723
  * @param planId The plan ID to purchase
634
- * @returns A new builder instance with plan ID set
635
724
  */
636
725
  setPlan(planId) {
637
726
  this.options = {
@@ -644,7 +733,6 @@ var Checkout = class Checkout {
644
733
  * Sets the number of licenses to purchase.
645
734
  *
646
735
  * @param count Number of licenses
647
- * @returns A new builder instance with license count set
648
736
  */
649
737
  setQuota(count) {
650
738
  this.options = {
@@ -672,7 +760,6 @@ var Checkout = class Checkout {
672
760
  *
673
761
  * @param coupon The coupon code to apply
674
762
  * @param hideUI Whether to hide the coupon input field from users
675
- * @returns A new builder instance with coupon configuration
676
763
  */
677
764
  setCoupon(options) {
678
765
  const { code: coupon, hideUI = false } = options;
@@ -687,7 +774,6 @@ var Checkout = class Checkout {
687
774
  * Enables trial mode for the checkout.
688
775
  *
689
776
  * @param mode Trial type - true/false for plan default, or specific 'free'/'paid' mode
690
- * @returns A new builder instance with trial configuration
691
777
  */
692
778
  setTrial(mode = true) {
693
779
  this.options = {
@@ -700,7 +786,6 @@ var Checkout = class Checkout {
700
786
  * Configures the visual layout and appearance of the checkout.
701
787
  *
702
788
  * @param options Appearance configuration options
703
- * @returns A new builder instance with appearance configuration
704
789
  */
705
790
  setAppearance(options) {
706
791
  this.options = { ...this.options };
@@ -715,7 +800,6 @@ var Checkout = class Checkout {
715
800
  * Configures discount display settings.
716
801
  *
717
802
  * @param options Discount configuration options
718
- * @returns A new builder instance with discount configuration
719
803
  */
720
804
  setDiscounts(options) {
721
805
  this.options = { ...this.options };
@@ -730,7 +814,6 @@ var Checkout = class Checkout {
730
814
  *
731
815
  * @param selector Type of billing cycle selector to show
732
816
  * @param defaultCycle Default billing cycle to select
733
- * @returns A new builder instance with billing cycle configuration
734
817
  */
735
818
  setBillingCycle(defaultCycle, selector) {
736
819
  this.options = { ...this.options };
@@ -742,7 +825,6 @@ var Checkout = class Checkout {
742
825
  * Sets the language/locale for the checkout.
743
826
  *
744
827
  * @param locale Language setting - 'auto', 'auto-beta', or specific locale like 'en_US'
745
- * @returns A new builder instance with locale configuration
746
828
  */
747
829
  setLanguage(locale = "auto") {
748
830
  this.options = {
@@ -755,7 +837,6 @@ var Checkout = class Checkout {
755
837
  * Configures review and badge display settings.
756
838
  *
757
839
  * @param options Review and badge configuration
758
- * @returns A new builder instance with reviews and badges configuration
759
840
  */
760
841
  setSocialProofing(options) {
761
842
  this.options = { ...this.options };
@@ -771,7 +852,6 @@ var Checkout = class Checkout {
771
852
  * @param currency Primary currency or 'auto' for automatic detection
772
853
  * @param defaultCurrency Default currency when using 'auto'
773
854
  * @param showInlineSelector Whether to show inline currency selector
774
- * @returns A new builder instance with currency configuration
775
855
  */
776
856
  setCurrency(currency, defaultCurrency = "usd", showInlineSelector = true) {
777
857
  this.options = {
@@ -787,7 +867,6 @@ var Checkout = class Checkout {
787
867
  *
788
868
  * @param cancelUrl URL for back button when in page mode
789
869
  * @param cancelIcon Custom cancel icon URL
790
- * @returns A new builder instance with navigation configuration
791
870
  */
792
871
  setCancelButton(cancelUrl, cancelIcon) {
793
872
  this.options = { ...this.options };
@@ -799,7 +878,6 @@ var Checkout = class Checkout {
799
878
  * Associates purchases with an affiliate account.
800
879
  *
801
880
  * @param userId Affiliate user ID
802
- * @returns A new builder instance with affiliate configuration
803
881
  */
804
882
  setAffiliate(userId) {
805
883
  this.options = {
@@ -812,7 +890,6 @@ var Checkout = class Checkout {
812
890
  * Sets a custom image/icon for the checkout.
813
891
  *
814
892
  * @param imageUrl Secure HTTPS URL to the image
815
- * @returns A new builder instance with custom image
816
893
  */
817
894
  setImage(imageUrl) {
818
895
  this.options = {
@@ -822,12 +899,13 @@ var Checkout = class Checkout {
822
899
  return this;
823
900
  }
824
901
  /**
825
- * Configures the checkout for license renewal.
902
+ * Configures the checkout for license renewal or upgrade by the license key.
903
+ *
904
+ * @note - This is less secure since it exposes the license key to the client. Use only in authenticated contexts.
826
905
  *
827
906
  * @param licenseKey The license key to renew
828
- * @returns A new builder instance configured for renewal
829
907
  */
830
- setLicenseRenewal(licenseKey) {
908
+ setLicenseUpgradeByKey(licenseKey) {
831
909
  this.options = {
832
910
  ...this.options,
833
911
  license_key: licenseKey
@@ -835,9 +913,20 @@ var Checkout = class Checkout {
835
913
  return this;
836
914
  }
837
915
  /**
838
- * Builds and returns the final checkout options to be used with the `@freemius/checkout` package.
916
+ * Configures the checkout for license upgrade using an authorization token.
839
917
  *
840
- * @note - This is async by purpose so that we can allow for future enhancements that might require async operations.
918
+ * @param params The license upgrade authorization parameters
919
+ */
920
+ setLicenseUpgradeByAuth(params) {
921
+ this.options = {
922
+ ...this.options,
923
+ license_id: params.licenseId,
924
+ authorization: params.authorization
925
+ };
926
+ return this;
927
+ }
928
+ /**
929
+ * Builds and returns the final checkout options to be used with the `@freemius/checkout` package.
841
930
  *
842
931
  * @returns The constructed CheckoutOptions object
843
932
  */
@@ -846,8 +935,6 @@ var Checkout = class Checkout {
846
935
  }
847
936
  /**
848
937
  * 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
938
  */
852
939
  getLink() {
853
940
  const checkoutOptions = convertCheckoutOptionsToQueryParams(this.options);
@@ -868,39 +955,6 @@ var Checkout = class Checkout {
868
955
  }
869
956
  };
870
957
 
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
958
  //#endregion
905
959
  //#region src/checkout/PricingRetriever.ts
906
960
  var PricingRetriever = class {
@@ -933,7 +987,10 @@ var PurchaseProcessor = class {
933
987
  return request.method === "POST" && action === "process_purchase";
934
988
  }
935
989
  async processAction(request) {
936
- const purchaseSchema = zod.object({ purchase: zod.object({ license_id: zod.string() }) });
990
+ const purchaseSchema = zod.object({
991
+ purchase: zod.object({ license_id: zod.string() }).optional(),
992
+ trial: zod.object({ license_id: zod.string() }).optional()
993
+ });
937
994
  const contentType = request.headers.get("content-type");
938
995
  if (!contentType || !contentType.includes("application/json")) throw ActionError.badRequest("Invalid content type. Expected application/json");
939
996
  let requestBody;
@@ -944,7 +1001,8 @@ var PurchaseProcessor = class {
944
1001
  }
945
1002
  const parseResult = purchaseSchema.safeParse(requestBody);
946
1003
  if (!parseResult.success) throw ActionError.validationFailed("Invalid request body format", parseResult.error.issues);
947
- const { purchase: { license_id: licenseId } } = parseResult.data;
1004
+ const licenseId = parseResult.data.purchase?.license_id ?? parseResult.data.trial?.license_id;
1005
+ if (!licenseId) throw ActionError.badRequest("License ID is required in the request body, either from purchase or from trial.");
948
1006
  const purchase = await this.purchase.retrievePurchase(licenseId);
949
1007
  if (!purchase) throw ActionError.notFound("No purchase data found for the provided license ID");
950
1008
  if (this.callback) {
@@ -962,11 +1020,13 @@ var CheckoutRedirectInfo = class {
962
1020
  plan_id;
963
1021
  email;
964
1022
  pricing_id;
965
- currency;
1023
+ action;
966
1024
  license_id;
967
1025
  expiration;
968
1026
  quota;
969
- action;
1027
+ trial;
1028
+ trial_ends_at;
1029
+ currency;
970
1030
  amount;
971
1031
  tax;
972
1032
  type;
@@ -978,17 +1038,19 @@ var CheckoutRedirectInfo = class {
978
1038
  this.plan_id = idToString(data.plan_id);
979
1039
  this.email = data.email;
980
1040
  this.pricing_id = idToString(data.pricing_id);
981
- this.currency = data.currency ? parseCurrency(data.currency) : CURRENCY.USD;
1041
+ this.action = data.action ? data.action : "purchase";
982
1042
  this.license_id = idToString(data.license_id);
983
1043
  this.expiration = data.expiration ? parseDateTime(data.expiration) : null;
984
1044
  this.quota = data.quota ? parseNumber(data.quota) : null;
985
- this.action = data.action ? data.action : null;
1045
+ this.trial = data.trial ? data.trial : null;
1046
+ this.trial_ends_at = data.trial_ends_at ? parseDateTime(data.trial_ends_at) : null;
1047
+ this.currency = data.currency ? parseCurrency(data.currency) : CURRENCY.USD;
986
1048
  this.amount = parseNumber(data.amount);
987
1049
  this.tax = parseNumber(data.tax);
988
1050
  this.subscription_id = data.subscription_id ? idToString(data.subscription_id) : null;
989
1051
  this.billing_cycle = data.billing_cycle ? parseBillingCycle(data.billing_cycle) : null;
990
1052
  this.payment_id = data.payment_id ? idToString(data.payment_id) : null;
991
- this.type = this.subscription_id ? "subscription" : "one-off";
1053
+ this.type = this.subscription_id ? "subscription" : this.payment_id ? "one-off" : null;
992
1054
  }
993
1055
  isSubscription() {
994
1056
  return this.type === "subscription";
@@ -999,17 +1061,19 @@ var CheckoutRedirectInfo = class {
999
1061
  plan_id: this.plan_id,
1000
1062
  email: this.email,
1001
1063
  pricing_id: this.pricing_id,
1002
- currency: this.currency,
1003
1064
  license_id: this.license_id,
1004
1065
  expiration: this.expiration,
1005
1066
  quota: this.quota,
1067
+ trial: this.trial,
1068
+ trial_ends_at: this.trial_ends_at,
1069
+ currency: this.currency,
1006
1070
  action: this.action,
1007
1071
  amount: this.amount,
1008
1072
  tax: this.tax,
1009
- type: this.type,
1010
1073
  subscription_id: this.subscription_id,
1011
1074
  billing_cycle: this.billing_cycle,
1012
- payment_id: this.payment_id
1075
+ payment_id: this.payment_id,
1076
+ type: this.type
1013
1077
  };
1014
1078
  }
1015
1079
  };
@@ -1053,11 +1117,16 @@ var RedirectProcessor = class {
1053
1117
  }
1054
1118
  const cleanUrl = this.getCleanUrl(url.href);
1055
1119
  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);
1120
+ try {
1121
+ const result = timingSafeEqual(Buffer.from(calculatedSignature), Buffer.from(signature));
1122
+ if (!result) return null;
1123
+ const params = Object.fromEntries(url.searchParams.entries());
1124
+ if (!params.user_id || !params.plan_id || !params.pricing_id || !params.email) return null;
1125
+ return new CheckoutRedirectInfo(params);
1126
+ } catch (e) {
1127
+ console.error("Error getting redirection information:", e);
1128
+ return null;
1129
+ }
1061
1130
  }
1062
1131
  getCleanUrl(url) {
1063
1132
  const signatureParam = "&signature=";
@@ -1081,9 +1150,6 @@ var CheckoutRequestProcessor = class {
1081
1150
  return (request) => this.process(config, request);
1082
1151
  }
1083
1152
  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
1153
  const actionHandlers = [
1088
1154
  this.getPricingRetriever(),
1089
1155
  this.getRedirectProcessor({
@@ -1145,7 +1211,8 @@ var CheckoutRequestProcessor = class {
1145
1211
  //#region src/services/CheckoutService.ts
1146
1212
  var CheckoutService = class {
1147
1213
  request;
1148
- constructor(productId, publicKey, secretKey, purchase, pricing) {
1214
+ constructor(api, productId, publicKey, secretKey, purchase, pricing) {
1215
+ this.api = api;
1149
1216
  this.productId = productId;
1150
1217
  this.publicKey = publicKey;
1151
1218
  this.secretKey = secretKey;
@@ -1186,16 +1253,20 @@ var CheckoutService = class {
1186
1253
  * @example
1187
1254
  */
1188
1255
  async create(options = {}) {
1189
- const { user, isSandbox = false, withRecommendation = true, title, image, planId, quota, trial } = options;
1256
+ const { user, isSandbox = false, withRecommendation = true, title, image, planId, quota, trial, licenseId } = options;
1190
1257
  const builder = new Checkout(idToString(this.productId), this.publicKey, this.secretKey);
1191
1258
  if (user) builder.setUser(user, true);
1192
1259
  if (withRecommendation) builder.setRecommendations();
1193
- if (isSandbox) builder.setSandbox();
1260
+ if (isSandbox) builder.setSandbox(await this.getSandboxParams());
1194
1261
  if (title) builder.setTitle(title);
1195
1262
  if (image) builder.setImage(image);
1196
1263
  if (planId) builder.setPlan(planId);
1197
1264
  if (quota) builder.setQuota(quota);
1198
1265
  if (trial) builder.setTrial(trial);
1266
+ if (licenseId) {
1267
+ const authorization = await this.getLicenseUpgradeAuth(licenseId);
1268
+ builder.setLicenseUpgradeByAuth(authorization);
1269
+ }
1199
1270
  return builder;
1200
1271
  }
1201
1272
  /**
@@ -1204,12 +1275,28 @@ var CheckoutService = class {
1204
1275
  * This shouldn't be used in production, but is useful for testing purposes.
1205
1276
  *
1206
1277
  * @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
1278
  */
1211
1279
  async getSandboxParams() {
1212
- return Checkout.createSandboxToken(idToString(this.productId), this.secretKey, this.publicKey);
1280
+ const productId = idToString(this.productId);
1281
+ const timestamp = Math.floor(Date.now() / 1e3).toString();
1282
+ const token = `${timestamp}${productId}${this.secretKey}${this.publicKey}checkout`;
1283
+ return {
1284
+ ctx: timestamp,
1285
+ token: createHash("md5").update(token).digest("hex")
1286
+ };
1287
+ }
1288
+ /**
1289
+ * Retrieves the license upgrade authorization for a given license ID.
1290
+ *
1291
+ * This is used to authorize a license upgrade during the checkout process. Useful when creating upgrade links for existing users.
1292
+ */
1293
+ async getLicenseUpgradeAuth(licenseId) {
1294
+ const auth = await this.api.license.retrieveCheckoutUpgradeAuthorization(licenseId);
1295
+ if (!auth) throw new Error("Failed to retrieve license upgrade authorization");
1296
+ return {
1297
+ licenseId,
1298
+ authorization: auth
1299
+ };
1213
1300
  }
1214
1301
  /**
1215
1302
  * Processes a redirect URL and returns the checkout redirect information if valid.
@@ -1841,11 +1928,16 @@ var CustomerPortalService = class {
1841
1928
  //#endregion
1842
1929
  //#region src/webhook/WebhookListener.ts
1843
1930
  const SIGNATURE_HEADER = "x-signature";
1931
+ const DEFAULT_ERROR_HANDLER = async (error) => {
1932
+ console.error("Webhook processing error:", error);
1933
+ };
1844
1934
  var WebhookListener = class {
1845
1935
  eventHandlers = /* @__PURE__ */ new Map();
1846
- constructor(secretKey, onError = console.error) {
1936
+ constructor(api, secretKey, onError = DEFAULT_ERROR_HANDLER, authenticationMethod = WebhookAuthenticationMethod.SignatureHeader) {
1937
+ this.api = api;
1847
1938
  this.secretKey = secretKey;
1848
1939
  this.onError = onError;
1940
+ this.authenticationMethod = authenticationMethod;
1849
1941
  }
1850
1942
  on(typeOrTypes, handler) {
1851
1943
  const types = Array.isArray(typeOrTypes) ? typeOrTypes : [typeOrTypes];
@@ -1913,40 +2005,52 @@ var WebhookListener = class {
1913
2005
  * Returns an object you can map to your framework's response easily.
1914
2006
  */
1915
2007
  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
2008
  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 {
2009
+ const event = this.authenticationMethod === WebhookAuthenticationMethod.SignatureHeader ? await this.authenticateAndGetEventFromInput(input) : await this.authenticateAndGetEventFromApi(input);
2010
+ return this.processEvent(event);
2011
+ } catch (error) {
2012
+ if (error instanceof WebhookError) return error.toResponse();
1932
2013
  return {
1933
- status: 400,
2014
+ status: 500,
1934
2015
  success: false,
1935
- error: "Malformed JSON"
2016
+ error: "Internal Server Error"
1936
2017
  };
1937
2018
  }
1938
- const eventType = evt.type;
2019
+ }
2020
+ async authenticateAndGetEventFromInput(input) {
2021
+ const sig = this.getHeader(SIGNATURE_HEADER, input.headers);
2022
+ if (!this.verifySignature(input.rawBody, sig)) throw new WebhookError("Invalid signature", 401);
2023
+ return this.parseEventFromInput(input);
2024
+ }
2025
+ async authenticateAndGetEventFromApi(input) {
2026
+ const jsonPayload = this.parseEventFromInput(input);
2027
+ if (!jsonPayload.id) throw new WebhookError("Invalid payload");
2028
+ const event = await this.api.event.retrieve(jsonPayload.id);
2029
+ if (!event) throw new WebhookError("Event not found", 404);
2030
+ return event;
2031
+ }
2032
+ parseEventFromInput(input) {
2033
+ try {
2034
+ const parsed = JSON.parse(typeof input.rawBody === "string" ? input.rawBody : input.rawBody.toString("utf8"));
2035
+ if (!parsed || typeof parsed.type !== "string") throw new WebhookError("Invalid payload");
2036
+ return parsed;
2037
+ } catch {
2038
+ throw new WebhookError("Malformed JSON");
2039
+ }
2040
+ }
2041
+ async processEvent(event) {
2042
+ const eventType = event.type;
1939
2043
  const eventHandlers = this.eventHandlers.get(eventType);
1940
2044
  if (!eventHandlers || eventHandlers.size === 0) console.warn(`No handlers registered for event type: ${eventType}`);
1941
2045
  try {
1942
2046
  const promises = Array.from(eventHandlers || []).map((handler) => {
1943
2047
  const typedHandler = handler;
1944
- const typedEvent = evt;
2048
+ const typedEvent = event;
1945
2049
  return typedHandler(typedEvent);
1946
2050
  });
1947
2051
  await Promise.all(promises);
1948
2052
  } catch (error) {
1949
- this.onError?.(error);
2053
+ await this.onError?.(error);
1950
2054
  return {
1951
2055
  status: 500,
1952
2056
  success: false,
@@ -1970,11 +2074,13 @@ var WebhookListener = class {
1970
2074
  //#endregion
1971
2075
  //#region src/services/WebhookService.ts
1972
2076
  var WebhookService = class {
1973
- constructor(secretKey) {
2077
+ constructor(api, secretKey) {
2078
+ this.api = api;
1974
2079
  this.secretKey = secretKey;
1975
2080
  }
1976
- createListener(onError) {
1977
- return new WebhookListener(this.secretKey, onError);
2081
+ createListener(config = {}) {
2082
+ const { onError, authenticationMethod } = config;
2083
+ return new WebhookListener(this.api, this.secretKey, onError, authenticationMethod);
1978
2084
  }
1979
2085
  createRequestProcessor(listener) {
1980
2086
  return (request) => this.processFetch(listener, request);
@@ -2476,12 +2582,12 @@ var Freemius = class {
2476
2582
  this.auth = new AuthService(productId, secretKey);
2477
2583
  this.pricing = new PricingService(this.api);
2478
2584
  this.purchase = new PurchaseService(this.api);
2479
- this.checkout = new CheckoutService(productId, publicKey, secretKey, this.purchase, this.pricing);
2585
+ this.checkout = new CheckoutService(this.api, productId, publicKey, secretKey, this.purchase, this.pricing);
2480
2586
  this.customerPortal = new CustomerPortalService(this.api, this.checkout, this.auth, this.purchase);
2481
- this.webhook = new WebhookService(secretKey);
2587
+ this.webhook = new WebhookService(this.api, secretKey);
2482
2588
  }
2483
2589
  };
2484
2590
 
2485
2591
  //#endregion
2486
- export { BILLING_CYCLE, CURRENCY, Freemius, idToNumber, idToString, isIdsEqual, parseBillingCycle, parseCurrency, parseDate, parseDateTime, parseNumber, parsePaymentMethod };
2592
+ export { ActionError, BILLING_CYCLE, CURRENCY, Freemius, WebhookAuthenticationMethod, WebhookError, idToNumber, idToString, isIdsEqual, parseBillingCycle, parseCurrency, parseDate, parseDateTime, parseNumber, parsePaymentMethod };
2487
2593
  //# sourceMappingURL=index.mjs.map