@metamask-previews/subscription-controller 8.0.1-preview-3866c0ff1 → 8.0.1-preview-e3275932a
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/CHANGELOG.md +0 -1
- package/dist/SubscriptionController-method-action-types.cjs +7 -0
- package/dist/SubscriptionController-method-action-types.cjs.map +1 -0
- package/dist/{SubscriptionController-method-action-types.d.ts → SubscriptionController-method-action-types.d.cts} +2 -2
- package/dist/SubscriptionController-method-action-types.d.cts.map +1 -0
- package/dist/SubscriptionController-method-action-types.d.mts +250 -0
- package/dist/SubscriptionController-method-action-types.d.mts.map +1 -0
- package/dist/{SubscriptionController-method-action-types.js → SubscriptionController-method-action-types.mjs} +1 -1
- package/dist/SubscriptionController-method-action-types.mjs.map +1 -0
- package/dist/SubscriptionController.cjs +744 -0
- package/dist/SubscriptionController.cjs.map +1 -0
- package/dist/{SubscriptionController.d.ts → SubscriptionController.d.cts} +18 -18
- package/dist/SubscriptionController.d.cts.map +1 -0
- package/dist/SubscriptionController.d.mts +258 -0
- package/dist/SubscriptionController.d.mts.map +1 -0
- package/dist/{SubscriptionController.js → SubscriptionController.mjs} +180 -262
- package/dist/SubscriptionController.mjs.map +1 -0
- package/dist/SubscriptionService-method-action-types.cjs +7 -0
- package/dist/SubscriptionService-method-action-types.cjs.map +1 -0
- package/dist/{SubscriptionService-method-action-types.d.ts → SubscriptionService-method-action-types.d.cts} +2 -2
- package/dist/SubscriptionService-method-action-types.d.cts.map +1 -0
- package/dist/SubscriptionService-method-action-types.d.mts +155 -0
- package/dist/SubscriptionService-method-action-types.d.mts.map +1 -0
- package/dist/{SubscriptionService-method-action-types.js → SubscriptionService-method-action-types.mjs} +1 -1
- package/dist/SubscriptionService-method-action-types.mjs.map +1 -0
- package/dist/SubscriptionService-structs.cjs +158 -0
- package/dist/SubscriptionService-structs.cjs.map +1 -0
- package/dist/{SubscriptionService-structs.d.ts → SubscriptionService-structs.d.cts} +134 -134
- package/dist/SubscriptionService-structs.d.cts.map +1 -0
- package/dist/SubscriptionService-structs.d.mts +721 -0
- package/dist/SubscriptionService-structs.d.mts.map +1 -0
- package/dist/{SubscriptionService-structs.js → SubscriptionService-structs.mjs} +4 -4
- package/dist/SubscriptionService-structs.mjs.map +1 -0
- package/dist/SubscriptionService.cjs +477 -0
- package/dist/SubscriptionService.cjs.map +1 -0
- package/dist/{SubscriptionService.d.ts → SubscriptionService.d.cts} +10 -10
- package/dist/SubscriptionService.d.cts.map +1 -0
- package/dist/SubscriptionService.d.mts +149 -0
- package/dist/SubscriptionService.d.mts.map +1 -0
- package/dist/{SubscriptionService.js → SubscriptionService.mjs} +141 -139
- package/dist/SubscriptionService.mjs.map +1 -0
- package/dist/constants.cjs +73 -0
- package/dist/constants.cjs.map +1 -0
- package/dist/{constants.d.ts → constants.d.cts} +1 -1
- package/dist/constants.d.cts.map +1 -0
- package/dist/constants.d.mts +49 -0
- package/dist/constants.d.mts.map +1 -0
- package/dist/{constants.js → constants.mjs} +3 -3
- package/dist/constants.mjs.map +1 -0
- package/dist/errors.cjs +72 -0
- package/dist/errors.cjs.map +1 -0
- package/dist/{errors.d.ts → errors.d.cts} +2 -2
- package/dist/errors.d.cts.map +1 -0
- package/dist/errors.d.mts +34 -0
- package/dist/errors.d.mts.map +1 -0
- package/dist/{errors.js → errors.mjs} +1 -5
- package/dist/errors.mjs.map +1 -0
- package/dist/index.cjs +29 -0
- package/dist/index.cjs.map +1 -0
- package/dist/{index.d.ts → index.d.cts} +11 -11
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +11 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/{index.js → index.mjs} +6 -6
- package/dist/index.mjs.map +1 -0
- package/dist/logger.cjs +8 -0
- package/dist/logger.cjs.map +1 -0
- package/dist/logger.d.cts +5 -0
- package/dist/logger.d.cts.map +1 -0
- package/dist/logger.d.mts +5 -0
- package/dist/logger.d.mts.map +1 -0
- package/dist/logger.mjs +5 -0
- package/dist/logger.mjs.map +1 -0
- package/dist/types.cjs +90 -0
- package/dist/types.cjs.map +1 -0
- package/dist/{types.d.ts → types.d.cts} +41 -41
- package/dist/types.d.cts.map +1 -0
- package/dist/types.d.mts +574 -0
- package/dist/types.d.mts.map +1 -0
- package/dist/{types.js → types.mjs} +1 -1
- package/dist/types.mjs.map +1 -0
- package/package.json +16 -12
- package/dist/SubscriptionController-method-action-types.d.ts.map +0 -1
- package/dist/SubscriptionController-method-action-types.js.map +0 -1
- package/dist/SubscriptionController.d.ts.map +0 -1
- package/dist/SubscriptionController.js.map +0 -1
- package/dist/SubscriptionService-method-action-types.d.ts.map +0 -1
- package/dist/SubscriptionService-method-action-types.js.map +0 -1
- package/dist/SubscriptionService-structs.d.ts.map +0 -1
- package/dist/SubscriptionService-structs.js.map +0 -1
- package/dist/SubscriptionService.d.ts.map +0 -1
- package/dist/SubscriptionService.js.map +0 -1
- package/dist/constants.d.ts.map +0 -1
- package/dist/constants.js.map +0 -1
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/logger.d.ts +0 -4
- package/dist/logger.d.ts.map +0 -1
- package/dist/logger.js +0 -5
- package/dist/logger.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
|
@@ -0,0 +1,744 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
3
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
4
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
5
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
6
|
+
};
|
|
7
|
+
var _SubscriptionController_instances, _SubscriptionController_getSubscriptionPriceAmount, _SubscriptionController_getSubscriptionBalanceAmount, _SubscriptionController_getProductPriceByProductAndPlan, _SubscriptionController_assertValidSubscriptionStateForCryptoApproval, _SubscriptionController_assertIsUserNotSubscribed, _SubscriptionController_assertIsUserSubscribed, _SubscriptionController_assertIsPaymentMethodCrypto, _SubscriptionController_getIsEligibleForTrialedSponsorship, _SubscriptionController_getIsTrialRequested, _SubscriptionController_findCryptoPaymentMethod, _SubscriptionController_resolveCryptoPaymentMethodDefaults, _SubscriptionController_getChainSupportsSponsorship, _SubscriptionController_areTrialedProductsEqual, _SubscriptionController_areSubscriptionsEqual, _SubscriptionController_isSubscriptionEqual, _SubscriptionController_stringifySubscription;
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.SubscriptionController = exports.getDefaultSubscriptionControllerState = void 0;
|
|
10
|
+
const polling_controller_1 = require("@metamask/polling-controller");
|
|
11
|
+
const transaction_controller_1 = require("@metamask/transaction-controller");
|
|
12
|
+
const bignumber_js_1 = require("bignumber.js");
|
|
13
|
+
const constants_js_1 = require("./constants.cjs");
|
|
14
|
+
const types_js_1 = require("./types.cjs");
|
|
15
|
+
/**
|
|
16
|
+
* Get the default state for the Subscription Controller.
|
|
17
|
+
*
|
|
18
|
+
* @returns The default state for the Subscription Controller.
|
|
19
|
+
*/
|
|
20
|
+
function getDefaultSubscriptionControllerState() {
|
|
21
|
+
return {
|
|
22
|
+
subscriptions: [],
|
|
23
|
+
trialedProducts: [],
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
exports.getDefaultSubscriptionControllerState = getDefaultSubscriptionControllerState;
|
|
27
|
+
/**
|
|
28
|
+
* Seedless Onboarding Controller State Metadata.
|
|
29
|
+
*
|
|
30
|
+
* This allows us to choose if fields of the state should be persisted or not
|
|
31
|
+
* using the `persist` flag; and if they can be sent to Sentry or not, using
|
|
32
|
+
* the `anonymous` flag.
|
|
33
|
+
*/
|
|
34
|
+
const subscriptionControllerMetadata = {
|
|
35
|
+
subscriptions: {
|
|
36
|
+
includeInStateLogs: false,
|
|
37
|
+
persist: true,
|
|
38
|
+
includeInDebugSnapshot: false,
|
|
39
|
+
usedInUi: true,
|
|
40
|
+
},
|
|
41
|
+
lastSubscription: {
|
|
42
|
+
includeInStateLogs: false,
|
|
43
|
+
persist: true,
|
|
44
|
+
includeInDebugSnapshot: false,
|
|
45
|
+
usedInUi: true,
|
|
46
|
+
},
|
|
47
|
+
customerId: {
|
|
48
|
+
includeInStateLogs: true,
|
|
49
|
+
persist: true,
|
|
50
|
+
includeInDebugSnapshot: false,
|
|
51
|
+
usedInUi: true,
|
|
52
|
+
},
|
|
53
|
+
rewardAccountId: {
|
|
54
|
+
includeInStateLogs: true,
|
|
55
|
+
persist: true,
|
|
56
|
+
includeInDebugSnapshot: false,
|
|
57
|
+
usedInUi: true,
|
|
58
|
+
},
|
|
59
|
+
trialedProducts: {
|
|
60
|
+
includeInStateLogs: true,
|
|
61
|
+
persist: true,
|
|
62
|
+
includeInDebugSnapshot: true,
|
|
63
|
+
usedInUi: true,
|
|
64
|
+
},
|
|
65
|
+
pricing: {
|
|
66
|
+
includeInStateLogs: true,
|
|
67
|
+
persist: true,
|
|
68
|
+
includeInDebugSnapshot: true,
|
|
69
|
+
usedInUi: true,
|
|
70
|
+
},
|
|
71
|
+
lastSelectedPaymentMethod: {
|
|
72
|
+
includeInStateLogs: false,
|
|
73
|
+
persist: true,
|
|
74
|
+
includeInDebugSnapshot: false,
|
|
75
|
+
usedInUi: true,
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
const MESSENGER_EXPOSED_METHODS = [
|
|
79
|
+
'getPricing',
|
|
80
|
+
'getSubscriptions',
|
|
81
|
+
'getSubscriptionByProduct',
|
|
82
|
+
'getSubscriptionsEligibilities',
|
|
83
|
+
'cancelSubscription',
|
|
84
|
+
'unCancelSubscription',
|
|
85
|
+
'startSubscriptionWithCard',
|
|
86
|
+
'startSubscriptionWithCrypto',
|
|
87
|
+
'stopAllPolling',
|
|
88
|
+
'submitSubscriptionCryptoApproval',
|
|
89
|
+
'getCryptoApproveTransactionParams',
|
|
90
|
+
'updatePaymentMethod',
|
|
91
|
+
'getBillingPortalUrl',
|
|
92
|
+
'cacheLastSelectedPaymentMethod',
|
|
93
|
+
'clearLastSelectedPaymentMethod',
|
|
94
|
+
'submitSponsorshipIntents',
|
|
95
|
+
'submitUserEvent',
|
|
96
|
+
'assignUserToCohort',
|
|
97
|
+
'linkRewards',
|
|
98
|
+
'getTokenApproveAmount',
|
|
99
|
+
'getTokenMinimumBalanceAmount',
|
|
100
|
+
'clearState',
|
|
101
|
+
'triggerAccessTokenRefresh',
|
|
102
|
+
];
|
|
103
|
+
class SubscriptionController extends (0, polling_controller_1.StaticIntervalPollingController)() {
|
|
104
|
+
/**
|
|
105
|
+
* Creates a new SubscriptionController instance.
|
|
106
|
+
*
|
|
107
|
+
* @param options - The options for the SubscriptionController.
|
|
108
|
+
* @param options.messenger - A restricted messenger.
|
|
109
|
+
* @param options.state - Initial state to set on this controller.
|
|
110
|
+
* @param options.pollingInterval - The polling interval to use for the subscription controller.
|
|
111
|
+
*/
|
|
112
|
+
constructor(options) {
|
|
113
|
+
const { messenger, state, pollingInterval = constants_js_1.DEFAULT_POLLING_INTERVAL, } = options;
|
|
114
|
+
super({
|
|
115
|
+
name: constants_js_1.controllerName,
|
|
116
|
+
metadata: subscriptionControllerMetadata,
|
|
117
|
+
state: {
|
|
118
|
+
...getDefaultSubscriptionControllerState(),
|
|
119
|
+
...state,
|
|
120
|
+
},
|
|
121
|
+
messenger,
|
|
122
|
+
});
|
|
123
|
+
_SubscriptionController_instances.add(this);
|
|
124
|
+
this.setIntervalLength(pollingInterval);
|
|
125
|
+
this.messenger.registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Gets the pricing information from the subscription service.
|
|
129
|
+
*
|
|
130
|
+
* @returns The pricing information.
|
|
131
|
+
*/
|
|
132
|
+
async getPricing() {
|
|
133
|
+
const pricing = await this.messenger.call('SubscriptionService:getPricing');
|
|
134
|
+
this.update((state) => {
|
|
135
|
+
state.pricing = pricing;
|
|
136
|
+
});
|
|
137
|
+
return pricing;
|
|
138
|
+
}
|
|
139
|
+
async getSubscriptions() {
|
|
140
|
+
const currentSubscriptions = this.state.subscriptions;
|
|
141
|
+
const currentTrialedProducts = this.state.trialedProducts;
|
|
142
|
+
const currentCustomerId = this.state.customerId;
|
|
143
|
+
const currentLastSubscription = this.state.lastSubscription;
|
|
144
|
+
const currentRewardAccountId = this.state.rewardAccountId;
|
|
145
|
+
const { customerId: newCustomerId, subscriptions: newSubscriptions, trialedProducts: newTrialedProducts, lastSubscription: newLastSubscription, rewardAccountId: newRewardAccountId, } = await this.messenger.call('SubscriptionService:getSubscriptions');
|
|
146
|
+
// check if the new subscriptions are different from the current subscriptions
|
|
147
|
+
const areSubscriptionsEqual = __classPrivateFieldGet(this, _SubscriptionController_instances, "m", _SubscriptionController_areSubscriptionsEqual).call(this, currentSubscriptions, newSubscriptions);
|
|
148
|
+
// check if the new trialed products are different from the current trialed products
|
|
149
|
+
const areTrialedProductsEqual = __classPrivateFieldGet(this, _SubscriptionController_instances, "m", _SubscriptionController_areTrialedProductsEqual).call(this, currentTrialedProducts, newTrialedProducts);
|
|
150
|
+
// check if the new last subscription is different from the current last subscription
|
|
151
|
+
const isLastSubscriptionEqual = __classPrivateFieldGet(this, _SubscriptionController_instances, "m", _SubscriptionController_isSubscriptionEqual).call(this, currentLastSubscription, newLastSubscription);
|
|
152
|
+
const areCustomerIdsEqual = currentCustomerId === newCustomerId;
|
|
153
|
+
const areRewardAccountIdsEqual = currentRewardAccountId === newRewardAccountId;
|
|
154
|
+
// only update the state if the subscriptions or trialed products are different
|
|
155
|
+
// this prevents unnecessary state updates events, easier for the clients to handle
|
|
156
|
+
if (!areSubscriptionsEqual ||
|
|
157
|
+
!isLastSubscriptionEqual ||
|
|
158
|
+
!areTrialedProductsEqual ||
|
|
159
|
+
!areCustomerIdsEqual ||
|
|
160
|
+
!areRewardAccountIdsEqual) {
|
|
161
|
+
this.update((state) => {
|
|
162
|
+
state.subscriptions = newSubscriptions;
|
|
163
|
+
state.customerId = newCustomerId;
|
|
164
|
+
state.trialedProducts = newTrialedProducts;
|
|
165
|
+
state.lastSubscription = newLastSubscription;
|
|
166
|
+
state.rewardAccountId = newRewardAccountId;
|
|
167
|
+
});
|
|
168
|
+
// trigger access token refresh to ensure the user has the latest access token if subscription state change
|
|
169
|
+
this.triggerAccessTokenRefresh();
|
|
170
|
+
}
|
|
171
|
+
return newSubscriptions;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Get the subscription by product.
|
|
175
|
+
*
|
|
176
|
+
* @param productType - The product type.
|
|
177
|
+
* @returns The subscription.
|
|
178
|
+
*/
|
|
179
|
+
getSubscriptionByProduct(productType) {
|
|
180
|
+
return this.state.subscriptions.find((subscription) => subscription.products.some((product) => product.name === productType));
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Get the subscriptions eligibilities.
|
|
184
|
+
*
|
|
185
|
+
* @param request - Optional request object containing user balance to check cohort eligibility.
|
|
186
|
+
* @returns The subscriptions eligibilities.
|
|
187
|
+
*/
|
|
188
|
+
async getSubscriptionsEligibilities(request) {
|
|
189
|
+
return await this.messenger.call('SubscriptionService:getSubscriptionsEligibilities', request);
|
|
190
|
+
}
|
|
191
|
+
async cancelSubscription(request) {
|
|
192
|
+
__classPrivateFieldGet(this, _SubscriptionController_instances, "m", _SubscriptionController_assertIsUserSubscribed).call(this, { subscriptionId: request.subscriptionId });
|
|
193
|
+
const cancelledSubscription = await this.messenger.call('SubscriptionService:cancelSubscription', request);
|
|
194
|
+
this.update((state) => {
|
|
195
|
+
state.subscriptions = state.subscriptions.map((subscription) => subscription.id === request.subscriptionId
|
|
196
|
+
? { ...subscription, ...cancelledSubscription }
|
|
197
|
+
: subscription);
|
|
198
|
+
});
|
|
199
|
+
this.triggerAccessTokenRefresh();
|
|
200
|
+
}
|
|
201
|
+
async unCancelSubscription(request) {
|
|
202
|
+
__classPrivateFieldGet(this, _SubscriptionController_instances, "m", _SubscriptionController_assertIsUserSubscribed).call(this, { subscriptionId: request.subscriptionId });
|
|
203
|
+
const uncancelledSubscription = await this.messenger.call('SubscriptionService:unCancelSubscription', {
|
|
204
|
+
subscriptionId: request.subscriptionId,
|
|
205
|
+
});
|
|
206
|
+
this.update((state) => {
|
|
207
|
+
state.subscriptions = state.subscriptions.map((subscription) => subscription.id === request.subscriptionId
|
|
208
|
+
? { ...subscription, ...uncancelledSubscription }
|
|
209
|
+
: subscription);
|
|
210
|
+
});
|
|
211
|
+
this.triggerAccessTokenRefresh();
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Starts a card-paid subscription checkout session for the requested products
|
|
215
|
+
* (e.g. Shield or Money Account Plus).
|
|
216
|
+
*
|
|
217
|
+
* `isTrialRequested` on the request is ignored and overwritten from pricing
|
|
218
|
+
* (`trialPeriodDays > 0`) and `trialedProducts`.
|
|
219
|
+
*
|
|
220
|
+
* @param request - The start subscription request.
|
|
221
|
+
* @returns The checkout session response.
|
|
222
|
+
*/
|
|
223
|
+
async startSubscriptionWithCard(request) {
|
|
224
|
+
// get the latest subscriptions state before computing trial eligibility
|
|
225
|
+
await this.getSubscriptions();
|
|
226
|
+
__classPrivateFieldGet(this, _SubscriptionController_instances, "m", _SubscriptionController_assertIsUserNotSubscribed).call(this, { products: request.products });
|
|
227
|
+
const response = await this.messenger.call('SubscriptionService:startSubscriptionWithCard', {
|
|
228
|
+
...request,
|
|
229
|
+
isTrialRequested: __classPrivateFieldGet(this, _SubscriptionController_instances, "m", _SubscriptionController_getIsTrialRequested).call(this, request.products, request.recurringInterval),
|
|
230
|
+
});
|
|
231
|
+
// note: no need to trigger access token refresh after startSubscriptionWithCard request because this only return stripe checkout session url, subscription not created yet
|
|
232
|
+
return response;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Starts a crypto-paid subscription for the requested products
|
|
236
|
+
* (e.g. Shield or Money Account Plus). Unlike card checkout, this
|
|
237
|
+
* creates the subscription immediately, so local state is refreshed
|
|
238
|
+
* afterwards.
|
|
239
|
+
*
|
|
240
|
+
* `isTrialRequested` on the request is ignored and overwritten from pricing
|
|
241
|
+
* (`trialPeriodDays > 0`) and `trialedProducts`.
|
|
242
|
+
*
|
|
243
|
+
* @param request - The start crypto subscription request.
|
|
244
|
+
* @returns The start crypto subscription response.
|
|
245
|
+
* @throws If `products` is empty.
|
|
246
|
+
*/
|
|
247
|
+
async startSubscriptionWithCrypto(request) {
|
|
248
|
+
if (request.products.length === 0) {
|
|
249
|
+
throw new Error(constants_js_1.SubscriptionControllerErrorMessage.SubscriptionProductsEmpty);
|
|
250
|
+
}
|
|
251
|
+
// get the latest subscriptions state before computing trial eligibility
|
|
252
|
+
await this.getSubscriptions();
|
|
253
|
+
__classPrivateFieldGet(this, _SubscriptionController_instances, "m", _SubscriptionController_assertIsUserNotSubscribed).call(this, { products: request.products });
|
|
254
|
+
const response = await this.messenger.call('SubscriptionService:startSubscriptionWithCrypto', {
|
|
255
|
+
...request,
|
|
256
|
+
isTrialRequested: __classPrivateFieldGet(this, _SubscriptionController_instances, "m", _SubscriptionController_getIsTrialRequested).call(this, request.products, request.recurringInterval),
|
|
257
|
+
});
|
|
258
|
+
// Crypto start creates the subscription immediately (unlike card checkout).
|
|
259
|
+
await this.getSubscriptions();
|
|
260
|
+
return response;
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Submits a Shield ERC-20 crypto approval transaction to start or update a
|
|
264
|
+
* crypto subscription.
|
|
265
|
+
*
|
|
266
|
+
* This handler is Shield / `TransactionType.shieldSubscriptionApprove` only.
|
|
267
|
+
* Delegation-based products (e.g. Money Account) must call
|
|
268
|
+
* `startSubscriptionWithCrypto` instead.
|
|
269
|
+
*
|
|
270
|
+
* @param request - The crypto approval request.
|
|
271
|
+
* @param request.productType - The subscription product. Typed as
|
|
272
|
+
* `typeof PRODUCT_TYPES.SHIELD` only at the moment (future might support more
|
|
273
|
+
* product).
|
|
274
|
+
* @param request.txMeta - The transaction metadata. Must have type
|
|
275
|
+
* `TransactionType.shieldSubscriptionApprove`.
|
|
276
|
+
* @param request.isSponsored - Whether the transaction is sponsored.
|
|
277
|
+
* @param request.rewardAccountId - The account ID of the reward subscription
|
|
278
|
+
* to link.
|
|
279
|
+
* @throws If `productType` is not Shield or `txMeta.type` is not
|
|
280
|
+
* `shieldSubscriptionApprove`.
|
|
281
|
+
* @returns void
|
|
282
|
+
*/
|
|
283
|
+
async submitSubscriptionCryptoApproval(request) {
|
|
284
|
+
const { productType, txMeta, isSponsored, rewardAccountId } = request;
|
|
285
|
+
if (
|
|
286
|
+
// Widen for the runtime guard: JS / unsound callers may still pass a
|
|
287
|
+
// non-Shield product.
|
|
288
|
+
productType !== types_js_1.PRODUCT_TYPES.SHIELD ||
|
|
289
|
+
txMeta.type !== transaction_controller_1.TransactionType.shieldSubscriptionApprove) {
|
|
290
|
+
throw new Error(constants_js_1.SubscriptionControllerErrorMessage.CryptoApprovalRequiresShieldApprove);
|
|
291
|
+
}
|
|
292
|
+
const { chainId, rawTx } = txMeta;
|
|
293
|
+
if (!chainId || !rawTx) {
|
|
294
|
+
throw new Error('Chain ID or raw transaction not found');
|
|
295
|
+
}
|
|
296
|
+
const { pricing, lastSelectedPaymentMethod } = this.state;
|
|
297
|
+
if (!pricing) {
|
|
298
|
+
throw new Error('Subscription pricing not found');
|
|
299
|
+
}
|
|
300
|
+
if (!lastSelectedPaymentMethod) {
|
|
301
|
+
throw new Error('Last selected payment method not found');
|
|
302
|
+
}
|
|
303
|
+
const lastSelectedPaymentMethodForProduct = lastSelectedPaymentMethod[productType];
|
|
304
|
+
__classPrivateFieldGet(this, _SubscriptionController_instances, "m", _SubscriptionController_assertIsPaymentMethodCrypto).call(this, lastSelectedPaymentMethodForProduct);
|
|
305
|
+
const productPrice = __classPrivateFieldGet(this, _SubscriptionController_instances, "m", _SubscriptionController_getProductPriceByProductAndPlan).call(this, productType, lastSelectedPaymentMethodForProduct.plan);
|
|
306
|
+
// get the latest subscriptions state before computing trial eligibility
|
|
307
|
+
await this.getSubscriptions();
|
|
308
|
+
const isTrialRequested = __classPrivateFieldGet(this, _SubscriptionController_instances, "m", _SubscriptionController_getIsTrialRequested).call(this, [productType], lastSelectedPaymentMethodForProduct.plan);
|
|
309
|
+
const currentSubscription = this.getSubscriptionByProduct(productType);
|
|
310
|
+
__classPrivateFieldGet(this, _SubscriptionController_instances, "m", _SubscriptionController_assertValidSubscriptionStateForCryptoApproval).call(this, {
|
|
311
|
+
productType,
|
|
312
|
+
});
|
|
313
|
+
// if subscription exists, this transaction is for changing payment method
|
|
314
|
+
const isChangePaymentMethod = Boolean(currentSubscription);
|
|
315
|
+
if (isChangePaymentMethod) {
|
|
316
|
+
await this.updatePaymentMethod({
|
|
317
|
+
paymentType: types_js_1.PAYMENT_TYPES.byCrypto,
|
|
318
|
+
subscriptionId: currentSubscription.id,
|
|
319
|
+
chainId,
|
|
320
|
+
payerAddress: txMeta.txParams.from,
|
|
321
|
+
tokenSymbol: lastSelectedPaymentMethodForProduct.paymentTokenSymbol,
|
|
322
|
+
rawTransaction: rawTx,
|
|
323
|
+
recurringInterval: productPrice.interval,
|
|
324
|
+
billingCycles: productPrice.minBillingCycles,
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
const params = {
|
|
329
|
+
products: [productType],
|
|
330
|
+
isTrialRequested,
|
|
331
|
+
recurringInterval: productPrice.interval,
|
|
332
|
+
billingCycles: productPrice.minBillingCycles,
|
|
333
|
+
chainId,
|
|
334
|
+
payerAddress: txMeta.txParams.from,
|
|
335
|
+
tokenSymbol: lastSelectedPaymentMethodForProduct.paymentTokenSymbol,
|
|
336
|
+
rawTransaction: rawTx,
|
|
337
|
+
cryptoAuthMethod: types_js_1.CRYPTO_AUTH_METHODS.ERC20_APPROVAL,
|
|
338
|
+
isSponsored,
|
|
339
|
+
useTestClock: lastSelectedPaymentMethodForProduct.useTestClock,
|
|
340
|
+
rewardAccountId,
|
|
341
|
+
};
|
|
342
|
+
await this.startSubscriptionWithCrypto(params);
|
|
343
|
+
}
|
|
344
|
+
// update the subscriptions state after subscription created in server
|
|
345
|
+
await this.getSubscriptions();
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Get transaction params to create crypto approve transaction for subscription payment
|
|
349
|
+
*
|
|
350
|
+
* @param request - The request object
|
|
351
|
+
* @param request.chainId - The chain ID
|
|
352
|
+
* @param request.tokenAddress - The address of the token
|
|
353
|
+
* @param request.productType - The product type
|
|
354
|
+
* @param request.interval - The interval
|
|
355
|
+
* @returns The crypto approve transaction params
|
|
356
|
+
*/
|
|
357
|
+
getCryptoApproveTransactionParams(request) {
|
|
358
|
+
const { pricing } = this.state;
|
|
359
|
+
if (!pricing) {
|
|
360
|
+
throw new Error('Subscription pricing not found');
|
|
361
|
+
}
|
|
362
|
+
const product = pricing.products.find((productInfo) => productInfo.name === request.productType);
|
|
363
|
+
if (!product) {
|
|
364
|
+
throw new Error('Product price not found');
|
|
365
|
+
}
|
|
366
|
+
const price = product.prices.find((productPrice) => productPrice.interval === request.interval);
|
|
367
|
+
if (!price) {
|
|
368
|
+
throw new Error('Price not found');
|
|
369
|
+
}
|
|
370
|
+
const chainsPaymentInfo = __classPrivateFieldGet(this, _SubscriptionController_instances, "m", _SubscriptionController_findCryptoPaymentMethod).call(this, request.productType, types_js_1.CRYPTO_AUTH_METHODS.ERC20_APPROVAL);
|
|
371
|
+
if (!chainsPaymentInfo) {
|
|
372
|
+
throw new Error('Chains payment info not found');
|
|
373
|
+
}
|
|
374
|
+
const chainPaymentInfo = chainsPaymentInfo.chains?.find((chain) => chain.chainId === request.chainId);
|
|
375
|
+
if (!chainPaymentInfo) {
|
|
376
|
+
throw new Error('Invalid chain id');
|
|
377
|
+
}
|
|
378
|
+
const tokenPaymentInfo = chainPaymentInfo.tokens.find((token) => token.address.toLowerCase() ===
|
|
379
|
+
request.paymentTokenAddress.toLowerCase());
|
|
380
|
+
if (!tokenPaymentInfo) {
|
|
381
|
+
throw new Error('Invalid token address');
|
|
382
|
+
}
|
|
383
|
+
const tokenApproveAmount = this.getTokenApproveAmount(price, tokenPaymentInfo);
|
|
384
|
+
return {
|
|
385
|
+
approveAmount: tokenApproveAmount,
|
|
386
|
+
paymentAddress: chainPaymentInfo.paymentAddress,
|
|
387
|
+
paymentTokenAddress: request.paymentTokenAddress,
|
|
388
|
+
chainId: request.chainId,
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
async updatePaymentMethod(opts) {
|
|
392
|
+
if (opts.paymentType === types_js_1.PAYMENT_TYPES.byCard) {
|
|
393
|
+
const { paymentType, ...cardRequest } = opts;
|
|
394
|
+
return await this.messenger.call('SubscriptionService:updatePaymentMethodCard', cardRequest);
|
|
395
|
+
}
|
|
396
|
+
else if (opts.paymentType === types_js_1.PAYMENT_TYPES.byCrypto) {
|
|
397
|
+
const { paymentType, ...cryptoRequest } = opts;
|
|
398
|
+
await this.messenger.call('SubscriptionService:updatePaymentMethodCrypto', cryptoRequest);
|
|
399
|
+
return await this.getSubscriptions();
|
|
400
|
+
}
|
|
401
|
+
throw new Error('Invalid payment type');
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Gets the billing portal URL.
|
|
405
|
+
*
|
|
406
|
+
* @returns The billing portal URL
|
|
407
|
+
*/
|
|
408
|
+
async getBillingPortalUrl() {
|
|
409
|
+
return await this.messenger.call('SubscriptionService:getBillingPortalUrl');
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Cache the last selected payment method for a specific product.
|
|
413
|
+
*
|
|
414
|
+
* @param request - The request object.
|
|
415
|
+
* @param request.product - The product to cache the payment method for.
|
|
416
|
+
* @param request.paymentMethod - The payment method to cache.
|
|
417
|
+
* @param request.paymentMethod.type - The type of the payment method.
|
|
418
|
+
* @param request.paymentMethod.paymentTokenAddress - The payment token address.
|
|
419
|
+
* @param request.paymentMethod.plan - The plan of the payment method.
|
|
420
|
+
*/
|
|
421
|
+
cacheLastSelectedPaymentMethod(request) {
|
|
422
|
+
const { product, paymentMethod } = request;
|
|
423
|
+
if (paymentMethod.type === types_js_1.PAYMENT_TYPES.byCrypto &&
|
|
424
|
+
(!paymentMethod.paymentTokenAddress || !paymentMethod.paymentTokenSymbol)) {
|
|
425
|
+
throw new Error(constants_js_1.SubscriptionControllerErrorMessage.PaymentTokenAddressAndSymbolRequiredForCrypto);
|
|
426
|
+
}
|
|
427
|
+
this.update((state) => {
|
|
428
|
+
state.lastSelectedPaymentMethod = {
|
|
429
|
+
...state.lastSelectedPaymentMethod,
|
|
430
|
+
[product]: paymentMethod,
|
|
431
|
+
};
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Clear the last selected payment method for a specific product.
|
|
436
|
+
*
|
|
437
|
+
* @param product - The product to clear the payment method for.
|
|
438
|
+
*/
|
|
439
|
+
clearLastSelectedPaymentMethod(product) {
|
|
440
|
+
this.update((state) => {
|
|
441
|
+
if (state.lastSelectedPaymentMethod) {
|
|
442
|
+
const { [product]: _, ...rest } = state.lastSelectedPaymentMethod;
|
|
443
|
+
state.lastSelectedPaymentMethod =
|
|
444
|
+
rest;
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Submit sponsorship intents to the Subscription Service backend.
|
|
450
|
+
*
|
|
451
|
+
* This is intended to be used together with the crypto subscription flow.
|
|
452
|
+
* When the user has enabled the smart transaction feature, we will sponsor the gas fees for the subscription approval transaction.
|
|
453
|
+
*
|
|
454
|
+
* @param request - Request object containing the address and products.
|
|
455
|
+
* @example {
|
|
456
|
+
* address: '0x1234567890123456789012345678901234567890',
|
|
457
|
+
* products: [ProductType.Shield],
|
|
458
|
+
* recurringInterval: RecurringInterval.Month,
|
|
459
|
+
* billingCycles: 1,
|
|
460
|
+
* }
|
|
461
|
+
* @returns resolves to true if the sponsorship is supported and intents were submitted successfully, false if the chain does not support sponsorship or the user has already trialed
|
|
462
|
+
* @throws If the crypto payment method or chain is missing from pricing
|
|
463
|
+
*/
|
|
464
|
+
async submitSponsorshipIntents(request) {
|
|
465
|
+
if (request.products.length === 0) {
|
|
466
|
+
throw new Error(constants_js_1.SubscriptionControllerErrorMessage.SubscriptionProductsEmpty);
|
|
467
|
+
}
|
|
468
|
+
__classPrivateFieldGet(this, _SubscriptionController_instances, "m", _SubscriptionController_assertIsUserNotSubscribed).call(this, { products: request.products });
|
|
469
|
+
const selectedPaymentMethod = this.state.lastSelectedPaymentMethod?.[request.products[0]];
|
|
470
|
+
__classPrivateFieldGet(this, _SubscriptionController_instances, "m", _SubscriptionController_assertIsPaymentMethodCrypto).call(this, selectedPaymentMethod);
|
|
471
|
+
const cryptoAuthMethod = selectedPaymentMethod.cryptoAuthMethod ??
|
|
472
|
+
types_js_1.CRYPTO_AUTH_METHODS.ERC20_APPROVAL;
|
|
473
|
+
const isEligibleForTrialedSponsorship = __classPrivateFieldGet(this, _SubscriptionController_instances, "m", _SubscriptionController_getIsEligibleForTrialedSponsorship).call(this, request.chainId, request.products, cryptoAuthMethod);
|
|
474
|
+
if (!isEligibleForTrialedSponsorship) {
|
|
475
|
+
return false;
|
|
476
|
+
}
|
|
477
|
+
const { paymentTokenSymbol, plan } = selectedPaymentMethod;
|
|
478
|
+
const productPrice = __classPrivateFieldGet(this, _SubscriptionController_instances, "m", _SubscriptionController_getProductPriceByProductAndPlan).call(this,
|
|
479
|
+
// we only support one product at a time for now
|
|
480
|
+
request.products[0], plan);
|
|
481
|
+
const billingCycles = productPrice.minBillingCycles;
|
|
482
|
+
await this.messenger.call('SubscriptionService:submitSponsorshipIntents', {
|
|
483
|
+
...request,
|
|
484
|
+
paymentTokenSymbol,
|
|
485
|
+
billingCycles,
|
|
486
|
+
recurringInterval: plan,
|
|
487
|
+
});
|
|
488
|
+
return true;
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Submit a user event from the UI. (e.g. shield modal viewed)
|
|
492
|
+
*
|
|
493
|
+
* @param request - Request object containing the event to submit.
|
|
494
|
+
* @example { event: SubscriptionUserEvent.ShieldEntryModalViewed, cohort: 'post_tx' }
|
|
495
|
+
*/
|
|
496
|
+
async submitUserEvent(request) {
|
|
497
|
+
await this.messenger.call('SubscriptionService:submitUserEvent', request);
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Assign user to a cohort.
|
|
501
|
+
*
|
|
502
|
+
* @param request - Request object containing the cohort to assign the user to.
|
|
503
|
+
* @example { cohort: 'post_tx' }
|
|
504
|
+
*/
|
|
505
|
+
async assignUserToCohort(request) {
|
|
506
|
+
await this.messenger.call('SubscriptionService:assignUserToCohort', request);
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* Link rewards to a subscription.
|
|
510
|
+
*
|
|
511
|
+
* @param request - Request object containing the reward subscription ID.
|
|
512
|
+
* @param request.subscriptionId - The ID of the subscription to link rewards to.
|
|
513
|
+
* @param request.rewardAccountId - The account ID of the reward subscription to link to the subscription.
|
|
514
|
+
* @example { subscriptionId: '1234567890', rewardAccountId: 'eip155:1:0x1234567890123456789012345678901234567890' }
|
|
515
|
+
* @returns Resolves when the rewards are linked successfully.
|
|
516
|
+
*/
|
|
517
|
+
async linkRewards(request) {
|
|
518
|
+
// assert that the user is subscribed to the subscription
|
|
519
|
+
__classPrivateFieldGet(this, _SubscriptionController_instances, "m", _SubscriptionController_assertIsUserSubscribed).call(this, { subscriptionId: request.subscriptionId });
|
|
520
|
+
// link rewards to the subscription
|
|
521
|
+
const response = await this.messenger.call('SubscriptionService:linkRewards', {
|
|
522
|
+
rewardAccountId: request.rewardAccountId,
|
|
523
|
+
});
|
|
524
|
+
if (!response.success) {
|
|
525
|
+
throw new Error(constants_js_1.SubscriptionControllerErrorMessage.LinkRewardsFailed);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
async _executePoll() {
|
|
529
|
+
await this.getSubscriptions();
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Calculate token approve amount from price info
|
|
533
|
+
*
|
|
534
|
+
* @param price - The price info
|
|
535
|
+
* @param tokenPaymentInfo - The token price info
|
|
536
|
+
* @returns The token approve amount
|
|
537
|
+
*/
|
|
538
|
+
getTokenApproveAmount(price, tokenPaymentInfo) {
|
|
539
|
+
const conversionRate = tokenPaymentInfo.conversionRate?.[price.currency];
|
|
540
|
+
if (!conversionRate) {
|
|
541
|
+
throw new Error('Conversion rate not found');
|
|
542
|
+
}
|
|
543
|
+
// price of the product
|
|
544
|
+
const priceAmount = new bignumber_js_1.BigNumber(__classPrivateFieldGet(this, _SubscriptionController_instances, "m", _SubscriptionController_getSubscriptionPriceAmount).call(this, price));
|
|
545
|
+
const tokenDecimal = new bignumber_js_1.BigNumber(10).pow(tokenPaymentInfo.decimals);
|
|
546
|
+
const tokenAmount = priceAmount
|
|
547
|
+
.multipliedBy(tokenDecimal)
|
|
548
|
+
.div(conversionRate);
|
|
549
|
+
return tokenAmount.toFixed(0);
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* Calculate token minimum balance amount from price info
|
|
553
|
+
*
|
|
554
|
+
* @param price - The price info
|
|
555
|
+
* @param tokenPaymentInfo - The token price info
|
|
556
|
+
* @returns The token balance amount
|
|
557
|
+
*/
|
|
558
|
+
getTokenMinimumBalanceAmount(price, tokenPaymentInfo) {
|
|
559
|
+
const conversionRate = tokenPaymentInfo.conversionRate?.[price.currency];
|
|
560
|
+
if (!conversionRate) {
|
|
561
|
+
throw new Error('Conversion rate not found');
|
|
562
|
+
}
|
|
563
|
+
const balanceAmount = new bignumber_js_1.BigNumber(__classPrivateFieldGet(this, _SubscriptionController_instances, "m", _SubscriptionController_getSubscriptionBalanceAmount).call(this, price));
|
|
564
|
+
const tokenDecimal = new bignumber_js_1.BigNumber(10).pow(tokenPaymentInfo.decimals);
|
|
565
|
+
const tokenAmount = balanceAmount
|
|
566
|
+
.multipliedBy(tokenDecimal)
|
|
567
|
+
.div(conversionRate);
|
|
568
|
+
return tokenAmount.toFixed(0);
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* Clears the subscription state and resets to default values.
|
|
572
|
+
*/
|
|
573
|
+
clearState() {
|
|
574
|
+
const defaultState = getDefaultSubscriptionControllerState();
|
|
575
|
+
this.update(() => {
|
|
576
|
+
return defaultState;
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Triggers an access token refresh.
|
|
581
|
+
*/
|
|
582
|
+
triggerAccessTokenRefresh() {
|
|
583
|
+
// We perform a sign out to clear the access token from the authentication
|
|
584
|
+
// controller. Next time the access token is requested, a new access token
|
|
585
|
+
// will be fetched.
|
|
586
|
+
this.messenger.call('AuthenticationController:performSignOut');
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
exports.SubscriptionController = SubscriptionController;
|
|
590
|
+
_SubscriptionController_instances = new WeakSet(), _SubscriptionController_getSubscriptionPriceAmount = function _SubscriptionController_getSubscriptionPriceAmount(price) {
|
|
591
|
+
// no need to use BigInt since max unitDecimals are always 2 for price
|
|
592
|
+
const amount = new bignumber_js_1.BigNumber(price.unitAmount)
|
|
593
|
+
.div(10 ** price.unitDecimals)
|
|
594
|
+
.multipliedBy(price.minBillingCycles)
|
|
595
|
+
.toString();
|
|
596
|
+
return amount;
|
|
597
|
+
}, _SubscriptionController_getSubscriptionBalanceAmount = function _SubscriptionController_getSubscriptionBalanceAmount(price) {
|
|
598
|
+
// no need to use BigInt since max unitDecimals are always 2 for price
|
|
599
|
+
const amount = new bignumber_js_1.BigNumber(price.unitAmount)
|
|
600
|
+
.div(10 ** price.unitDecimals)
|
|
601
|
+
.multipliedBy(price.minBillingCyclesForBalance)
|
|
602
|
+
.toString();
|
|
603
|
+
return amount;
|
|
604
|
+
}, _SubscriptionController_getProductPriceByProductAndPlan = function _SubscriptionController_getProductPriceByProductAndPlan(productType, plan) {
|
|
605
|
+
const { pricing } = this.state;
|
|
606
|
+
const productPricing = pricing?.products.find((product) => product.name === productType);
|
|
607
|
+
const productPrice = productPricing?.prices.find((price) => price.interval === plan);
|
|
608
|
+
if (!productPrice) {
|
|
609
|
+
throw new Error(constants_js_1.SubscriptionControllerErrorMessage.ProductPriceNotFound);
|
|
610
|
+
}
|
|
611
|
+
return productPrice;
|
|
612
|
+
}, _SubscriptionController_assertValidSubscriptionStateForCryptoApproval = function _SubscriptionController_assertValidSubscriptionStateForCryptoApproval({ productType, }) {
|
|
613
|
+
const subscription = this.state.subscriptions.find((sub) => sub.products.some((product) => product.name === productType));
|
|
614
|
+
const isValid = !subscription ||
|
|
615
|
+
[
|
|
616
|
+
types_js_1.SUBSCRIPTION_STATUSES.pastDue,
|
|
617
|
+
types_js_1.SUBSCRIPTION_STATUSES.unpaid,
|
|
618
|
+
types_js_1.SUBSCRIPTION_STATUSES.paused,
|
|
619
|
+
types_js_1.SUBSCRIPTION_STATUSES.provisional,
|
|
620
|
+
types_js_1.SUBSCRIPTION_STATUSES.active,
|
|
621
|
+
types_js_1.SUBSCRIPTION_STATUSES.trialing,
|
|
622
|
+
].includes(subscription.status);
|
|
623
|
+
if (!isValid) {
|
|
624
|
+
throw new Error(constants_js_1.SubscriptionControllerErrorMessage.SubscriptionNotValidForCryptoApproval);
|
|
625
|
+
}
|
|
626
|
+
}, _SubscriptionController_assertIsUserNotSubscribed = function _SubscriptionController_assertIsUserNotSubscribed({ products }) {
|
|
627
|
+
const subscription = this.state.subscriptions.find((sub) => sub.products.some((product) => products.includes(product.name)));
|
|
628
|
+
if (subscription &&
|
|
629
|
+
constants_js_1.ACTIVE_SUBSCRIPTION_STATUSES.includes(subscription.status)) {
|
|
630
|
+
throw new Error(constants_js_1.SubscriptionControllerErrorMessage.UserAlreadySubscribed);
|
|
631
|
+
}
|
|
632
|
+
}, _SubscriptionController_assertIsUserSubscribed = function _SubscriptionController_assertIsUserSubscribed(request) {
|
|
633
|
+
if (!this.state.subscriptions.find((subscription) => subscription.id === request.subscriptionId)) {
|
|
634
|
+
throw new Error(constants_js_1.SubscriptionControllerErrorMessage.UserNotSubscribed);
|
|
635
|
+
}
|
|
636
|
+
}, _SubscriptionController_assertIsPaymentMethodCrypto = function _SubscriptionController_assertIsPaymentMethodCrypto(value) {
|
|
637
|
+
if (value?.type !== types_js_1.PAYMENT_TYPES.byCrypto ||
|
|
638
|
+
!value.paymentTokenAddress ||
|
|
639
|
+
!value.paymentTokenSymbol) {
|
|
640
|
+
throw new Error(constants_js_1.SubscriptionControllerErrorMessage.PaymentMethodNotCrypto);
|
|
641
|
+
}
|
|
642
|
+
}, _SubscriptionController_getIsEligibleForTrialedSponsorship = function _SubscriptionController_getIsEligibleForTrialedSponsorship(chainId, products, cryptoAuthMethod) {
|
|
643
|
+
const isSponsorshipSupported = __classPrivateFieldGet(this, _SubscriptionController_instances, "m", _SubscriptionController_getChainSupportsSponsorship).call(this, chainId, products[0], cryptoAuthMethod);
|
|
644
|
+
// verify if the user has trialed the provided products before
|
|
645
|
+
const hasTrialedBefore = this.state.trialedProducts.some((product) => products.includes(product));
|
|
646
|
+
return isSponsorshipSupported && !hasTrialedBefore;
|
|
647
|
+
}, _SubscriptionController_getIsTrialRequested = function _SubscriptionController_getIsTrialRequested(products, plan) {
|
|
648
|
+
return products.every((productType) => {
|
|
649
|
+
if (this.state.trialedProducts.includes(productType)) {
|
|
650
|
+
return false;
|
|
651
|
+
}
|
|
652
|
+
const productPrice = __classPrivateFieldGet(this, _SubscriptionController_instances, "m", _SubscriptionController_getProductPriceByProductAndPlan).call(this, productType, plan);
|
|
653
|
+
return productPrice.trialPeriodDays > 0;
|
|
654
|
+
});
|
|
655
|
+
}, _SubscriptionController_findCryptoPaymentMethod = function _SubscriptionController_findCryptoPaymentMethod(productType, cryptoAuthMethod) {
|
|
656
|
+
const matches = [];
|
|
657
|
+
for (const paymentMethod of this.state.pricing?.paymentMethods ?? []) {
|
|
658
|
+
if (paymentMethod.type !== types_js_1.PAYMENT_TYPES.byCrypto) {
|
|
659
|
+
continue;
|
|
660
|
+
}
|
|
661
|
+
const resolved = __classPrivateFieldGet(this, _SubscriptionController_instances, "m", _SubscriptionController_resolveCryptoPaymentMethodDefaults).call(this, paymentMethod);
|
|
662
|
+
if (!resolved) {
|
|
663
|
+
continue;
|
|
664
|
+
}
|
|
665
|
+
if (resolved.cryptoAuthMethod === cryptoAuthMethod &&
|
|
666
|
+
resolved.products.includes(productType)) {
|
|
667
|
+
matches.push({
|
|
668
|
+
method: paymentMethod,
|
|
669
|
+
explicit: resolved.explicit,
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
if (matches.length === 0) {
|
|
674
|
+
return undefined;
|
|
675
|
+
}
|
|
676
|
+
const explicitMatches = matches.filter((match) => match.explicit);
|
|
677
|
+
const candidates = explicitMatches.length > 0 ? explicitMatches : matches;
|
|
678
|
+
if (candidates.length > 1) {
|
|
679
|
+
throw new Error('Multiple matching crypto payment methods found');
|
|
680
|
+
}
|
|
681
|
+
return candidates[0].method;
|
|
682
|
+
}, _SubscriptionController_resolveCryptoPaymentMethodDefaults = function _SubscriptionController_resolveCryptoPaymentMethodDefaults(paymentMethod) {
|
|
683
|
+
const productsPresent = paymentMethod.products !== undefined;
|
|
684
|
+
const authPresent = paymentMethod.cryptoAuthMethod !== undefined;
|
|
685
|
+
if (!productsPresent && !authPresent) {
|
|
686
|
+
return {
|
|
687
|
+
products: [types_js_1.PRODUCT_TYPES.SHIELD],
|
|
688
|
+
cryptoAuthMethod: types_js_1.CRYPTO_AUTH_METHODS.ERC20_APPROVAL,
|
|
689
|
+
explicit: false,
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
if (!paymentMethod.products?.length || !paymentMethod.cryptoAuthMethod) {
|
|
693
|
+
return undefined;
|
|
694
|
+
}
|
|
695
|
+
return {
|
|
696
|
+
products: paymentMethod.products,
|
|
697
|
+
cryptoAuthMethod: paymentMethod.cryptoAuthMethod,
|
|
698
|
+
explicit: true,
|
|
699
|
+
};
|
|
700
|
+
}, _SubscriptionController_getChainSupportsSponsorship = function _SubscriptionController_getChainSupportsSponsorship(chainId, productType, cryptoAuthMethod) {
|
|
701
|
+
const cryptoPaymentInfo = __classPrivateFieldGet(this, _SubscriptionController_instances, "m", _SubscriptionController_findCryptoPaymentMethod).call(this, productType, cryptoAuthMethod);
|
|
702
|
+
if (!cryptoPaymentInfo) {
|
|
703
|
+
throw new Error('Chains payment info not found');
|
|
704
|
+
}
|
|
705
|
+
const chainPaymentInfo = cryptoPaymentInfo.chains?.find((chain) => chain.chainId === chainId);
|
|
706
|
+
if (!chainPaymentInfo) {
|
|
707
|
+
throw new Error('Invalid chain id');
|
|
708
|
+
}
|
|
709
|
+
return Boolean(chainPaymentInfo.isSponsorshipSupported);
|
|
710
|
+
}, _SubscriptionController_areTrialedProductsEqual = function _SubscriptionController_areTrialedProductsEqual(oldTrialedProducts, newTrialedProducts) {
|
|
711
|
+
return (oldTrialedProducts.length === newTrialedProducts?.length &&
|
|
712
|
+
oldTrialedProducts.every((product) => newTrialedProducts?.includes(product)));
|
|
713
|
+
}, _SubscriptionController_areSubscriptionsEqual = function _SubscriptionController_areSubscriptionsEqual(oldSubs, newSubs) {
|
|
714
|
+
// Check if arrays have different lengths
|
|
715
|
+
if (oldSubs.length !== newSubs.length) {
|
|
716
|
+
return false;
|
|
717
|
+
}
|
|
718
|
+
// Sort both arrays by id to ensure consistent comparison
|
|
719
|
+
const sortedOldSubs = [...oldSubs].sort((a, b) => a.id.localeCompare(b.id));
|
|
720
|
+
const sortedNewSubs = [...newSubs].sort((a, b) => a.id.localeCompare(b.id));
|
|
721
|
+
// Check if all subscriptions are equal
|
|
722
|
+
return sortedOldSubs.every((oldSub, index) => {
|
|
723
|
+
const newSub = sortedNewSubs[index];
|
|
724
|
+
return __classPrivateFieldGet(this, _SubscriptionController_instances, "m", _SubscriptionController_isSubscriptionEqual).call(this, oldSub, newSub);
|
|
725
|
+
});
|
|
726
|
+
}, _SubscriptionController_isSubscriptionEqual = function _SubscriptionController_isSubscriptionEqual(oldSub, newSub) {
|
|
727
|
+
// not equal if one is undefined and the other is defined
|
|
728
|
+
if (!oldSub || !newSub) {
|
|
729
|
+
if (!oldSub && !newSub) {
|
|
730
|
+
return true;
|
|
731
|
+
}
|
|
732
|
+
return false;
|
|
733
|
+
}
|
|
734
|
+
return (__classPrivateFieldGet(this, _SubscriptionController_instances, "m", _SubscriptionController_stringifySubscription).call(this, oldSub) ===
|
|
735
|
+
__classPrivateFieldGet(this, _SubscriptionController_instances, "m", _SubscriptionController_stringifySubscription).call(this, newSub));
|
|
736
|
+
}, _SubscriptionController_stringifySubscription = function _SubscriptionController_stringifySubscription(subscription) {
|
|
737
|
+
const subsWithSortedProducts = {
|
|
738
|
+
...subscription,
|
|
739
|
+
// order the products by name
|
|
740
|
+
products: [...subscription.products].sort((a, b) => a.name.localeCompare(b.name)),
|
|
741
|
+
};
|
|
742
|
+
return JSON.stringify(subsWithSortedProducts);
|
|
743
|
+
};
|
|
744
|
+
//# sourceMappingURL=SubscriptionController.cjs.map
|