@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.d.mts +153 -63
- package/dist/index.d.mts.map +1 -1
- package/dist/index.d.ts +153 -63
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +212 -119
- package/dist/index.mjs +210 -120
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
*
|
|
922
|
+
* Configures the checkout for license upgrade using an authorization token.
|
|
861
923
|
*
|
|
862
|
-
* @
|
|
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({
|
|
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
|
|
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
|
-
|
|
1029
|
+
action;
|
|
988
1030
|
license_id;
|
|
989
1031
|
expiration;
|
|
990
1032
|
quota;
|
|
991
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
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:
|
|
2020
|
+
status: 500,
|
|
1956
2021
|
success: false,
|
|
1957
|
-
error: "
|
|
2022
|
+
error: "Internal Server Error"
|
|
1958
2023
|
};
|
|
1959
2024
|
}
|
|
1960
|
-
|
|
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 =
|
|
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(
|
|
1999
|
-
|
|
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;
|