@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.d.mts +262 -231
- package/dist/index.d.mts.map +1 -1
- package/dist/index.d.ts +262 -231
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +228 -119
- package/dist/index.mjs +226 -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.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
|
|
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
|
|
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
|
-
|
|
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
|
-
*
|
|
938
|
+
* Configures the checkout for license upgrade using an authorization token.
|
|
861
939
|
*
|
|
862
|
-
* @
|
|
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({
|
|
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
|
|
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
|
-
|
|
1045
|
+
action;
|
|
988
1046
|
license_id;
|
|
989
1047
|
expiration;
|
|
990
1048
|
quota;
|
|
991
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
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:
|
|
2036
|
+
status: 500,
|
|
1956
2037
|
success: false,
|
|
1957
|
-
error: "
|
|
2038
|
+
error: "Internal Server Error"
|
|
1958
2039
|
};
|
|
1959
2040
|
}
|
|
1960
|
-
|
|
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 =
|
|
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(
|
|
1999
|
-
|
|
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;
|