@metamask/subscription-controller 8.1.0 → 9.0.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/CHANGELOG.md +19 -1
- package/dist/{SubscriptionController-method-action-types.d.mts → SubscriptionController-method-action-types.d.ts} +2 -2
- package/dist/SubscriptionController-method-action-types.d.ts.map +1 -0
- package/dist/{SubscriptionController-method-action-types.mjs → SubscriptionController-method-action-types.js} +1 -1
- package/dist/SubscriptionController-method-action-types.js.map +1 -0
- package/dist/{SubscriptionController.d.mts → SubscriptionController.d.ts} +18 -18
- package/dist/SubscriptionController.d.ts.map +1 -0
- package/dist/{SubscriptionController.mjs → SubscriptionController.js} +371 -274
- package/dist/SubscriptionController.js.map +1 -0
- package/dist/{SubscriptionService-method-action-types.d.cts → SubscriptionService-method-action-types.d.ts} +2 -2
- package/dist/SubscriptionService-method-action-types.d.ts.map +1 -0
- package/dist/{SubscriptionService-method-action-types.mjs → SubscriptionService-method-action-types.js} +1 -1
- package/dist/SubscriptionService-method-action-types.js.map +1 -0
- package/dist/{SubscriptionService-structs.d.cts → SubscriptionService-structs.d.ts} +164 -164
- package/dist/SubscriptionService-structs.d.ts.map +1 -0
- package/dist/{SubscriptionService-structs.mjs → SubscriptionService-structs.js} +4 -4
- package/dist/SubscriptionService-structs.js.map +1 -0
- package/dist/{SubscriptionService.d.cts → SubscriptionService.d.ts} +10 -10
- package/dist/SubscriptionService.d.ts.map +1 -0
- package/dist/{SubscriptionService.mjs → SubscriptionService.js} +141 -143
- package/dist/SubscriptionService.js.map +1 -0
- package/dist/{constants.d.cts → constants.d.ts} +1 -1
- package/dist/constants.d.ts.map +1 -0
- package/dist/{constants.mjs → constants.js} +3 -3
- package/dist/constants.js.map +1 -0
- package/dist/{errors.d.cts → errors.d.ts} +2 -2
- package/dist/errors.d.ts.map +1 -0
- package/dist/{errors.mjs → errors.js} +5 -1
- package/dist/errors.js.map +1 -0
- package/dist/{index.d.cts → index.d.ts} +12 -12
- package/dist/index.d.ts.map +1 -0
- package/dist/{index.mjs → index.js} +7 -7
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +4 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +5 -0
- package/dist/logger.js.map +1 -0
- package/dist/{selectors.d.mts → selectors.d.ts} +3 -3
- package/dist/selectors.d.ts.map +1 -0
- package/dist/{selectors.mjs → selectors.js} +2 -2
- package/dist/selectors.js.map +1 -0
- package/dist/{types.d.mts → types.d.ts} +47 -47
- package/dist/types.d.ts.map +1 -0
- package/dist/{types.mjs → types.js} +1 -1
- package/dist/types.js.map +1 -0
- package/package.json +19 -23
- package/dist/SubscriptionController-method-action-types.cjs +0 -7
- package/dist/SubscriptionController-method-action-types.cjs.map +0 -1
- package/dist/SubscriptionController-method-action-types.d.cts +0 -261
- package/dist/SubscriptionController-method-action-types.d.cts.map +0 -1
- package/dist/SubscriptionController-method-action-types.d.mts.map +0 -1
- package/dist/SubscriptionController-method-action-types.mjs.map +0 -1
- package/dist/SubscriptionController.cjs +0 -844
- package/dist/SubscriptionController.cjs.map +0 -1
- package/dist/SubscriptionController.d.cts +0 -268
- package/dist/SubscriptionController.d.cts.map +0 -1
- package/dist/SubscriptionController.d.mts.map +0 -1
- package/dist/SubscriptionController.mjs.map +0 -1
- package/dist/SubscriptionService-method-action-types.cjs +0 -7
- package/dist/SubscriptionService-method-action-types.cjs.map +0 -1
- package/dist/SubscriptionService-method-action-types.d.cts.map +0 -1
- package/dist/SubscriptionService-method-action-types.d.mts +0 -164
- package/dist/SubscriptionService-method-action-types.d.mts.map +0 -1
- package/dist/SubscriptionService-method-action-types.mjs.map +0 -1
- package/dist/SubscriptionService-structs.cjs +0 -209
- package/dist/SubscriptionService-structs.cjs.map +0 -1
- package/dist/SubscriptionService-structs.d.cts.map +0 -1
- package/dist/SubscriptionService-structs.d.mts +0 -889
- package/dist/SubscriptionService-structs.d.mts.map +0 -1
- package/dist/SubscriptionService-structs.mjs.map +0 -1
- package/dist/SubscriptionService.cjs +0 -497
- package/dist/SubscriptionService.cjs.map +0 -1
- package/dist/SubscriptionService.d.cts.map +0 -1
- package/dist/SubscriptionService.d.mts +0 -155
- package/dist/SubscriptionService.d.mts.map +0 -1
- package/dist/SubscriptionService.mjs.map +0 -1
- package/dist/constants.cjs +0 -74
- package/dist/constants.cjs.map +0 -1
- package/dist/constants.d.cts.map +0 -1
- package/dist/constants.d.mts +0 -50
- package/dist/constants.d.mts.map +0 -1
- package/dist/constants.mjs.map +0 -1
- package/dist/errors.cjs +0 -72
- package/dist/errors.cjs.map +0 -1
- package/dist/errors.d.cts.map +0 -1
- package/dist/errors.d.mts +0 -34
- package/dist/errors.d.mts.map +0 -1
- package/dist/errors.mjs.map +0 -1
- package/dist/index.cjs +0 -35
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts.map +0 -1
- package/dist/index.d.mts +0 -12
- package/dist/index.d.mts.map +0 -1
- package/dist/index.mjs.map +0 -1
- package/dist/logger.cjs +0 -8
- package/dist/logger.cjs.map +0 -1
- package/dist/logger.d.cts +0 -5
- package/dist/logger.d.cts.map +0 -1
- package/dist/logger.d.mts +0 -5
- package/dist/logger.d.mts.map +0 -1
- package/dist/logger.mjs +0 -5
- package/dist/logger.mjs.map +0 -1
- package/dist/selectors.cjs +0 -52
- package/dist/selectors.cjs.map +0 -1
- package/dist/selectors.d.cts +0 -38
- package/dist/selectors.d.cts.map +0 -1
- package/dist/selectors.d.mts.map +0 -1
- package/dist/selectors.mjs.map +0 -1
- package/dist/types.cjs +0 -100
- package/dist/types.cjs.map +0 -1
- package/dist/types.d.cts +0 -645
- package/dist/types.d.cts.map +0 -1
- package/dist/types.d.mts.map +0 -1
- package/dist/types.mjs.map +0 -1
|
@@ -1,23 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
};
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
if (module?.__esModule) {
|
|
9
|
-
return module.default;
|
|
10
|
-
}
|
|
11
|
-
return module;
|
|
12
|
-
}
|
|
13
|
-
import { StaticIntervalPollingController } from "@metamask/polling-controller";
|
|
14
|
-
import { TransactionType } from "@metamask/transaction-controller";
|
|
15
|
-
import { BigNumber } from "bignumber.js";
|
|
16
|
-
import $deepEqual from "fast-deep-equal";
|
|
17
|
-
const deepEqual = $importDefault($deepEqual);
|
|
18
|
-
import { ACTIVE_SUBSCRIPTION_STATUSES, controllerName, DEFAULT_POLLING_INTERVAL, SubscriptionControllerErrorMessage } from "./constants.mjs";
|
|
19
|
-
import { createModuleLogger, projectLogger } from "./logger.mjs";
|
|
20
|
-
import { CRYPTO_AUTH_METHODS, PAYMENT_TYPES, PRODUCT_TYPES, SUBSCRIPTION_STATUSES } from "./types.mjs";
|
|
1
|
+
import { StaticIntervalPollingController } from '@metamask/polling-controller';
|
|
2
|
+
import { TransactionType } from '@metamask/transaction-controller';
|
|
3
|
+
import { BigNumber } from 'bignumber.js';
|
|
4
|
+
import deepEqual from 'fast-deep-equal';
|
|
5
|
+
import { ACTIVE_SUBSCRIPTION_STATUSES, controllerName, DEFAULT_POLLING_INTERVAL, SubscriptionControllerErrorMessage, } from './constants.js';
|
|
6
|
+
import { createModuleLogger, projectLogger } from './logger.js';
|
|
7
|
+
import { CRYPTO_AUTH_METHODS, PAYMENT_TYPES, PRODUCT_TYPES, SUBSCRIPTION_STATUSES, } from './types.js';
|
|
21
8
|
const log = createModuleLogger(projectLogger, controllerName);
|
|
22
9
|
/**
|
|
23
10
|
* Get the default state for the Subscription Controller.
|
|
@@ -139,7 +126,6 @@ export class SubscriptionController extends StaticIntervalPollingController() {
|
|
|
139
126
|
},
|
|
140
127
|
messenger,
|
|
141
128
|
});
|
|
142
|
-
_SubscriptionController_instances.add(this);
|
|
143
129
|
this.setIntervalLength(pollingInterval);
|
|
144
130
|
this.messenger.registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
|
|
145
131
|
}
|
|
@@ -156,7 +142,68 @@ export class SubscriptionController extends StaticIntervalPollingController() {
|
|
|
156
142
|
return pricing;
|
|
157
143
|
}
|
|
158
144
|
async getSubscriptions() {
|
|
159
|
-
return await
|
|
145
|
+
return await this.#getSubscriptions();
|
|
146
|
+
}
|
|
147
|
+
async #getSubscriptions({ refreshBenefits = true, } = {}) {
|
|
148
|
+
const currentSubscriptions = this.state.subscriptions;
|
|
149
|
+
const currentTrialedProducts = this.state.trialedProducts;
|
|
150
|
+
const currentCustomerId = this.state.customerId;
|
|
151
|
+
const currentLastSubscription = this.state.lastSubscription;
|
|
152
|
+
const currentRewardAccountId = this.state.rewardAccountId;
|
|
153
|
+
const currentProductEntitlements = this.state.productEntitlements;
|
|
154
|
+
const currentSubscriptionState = {
|
|
155
|
+
customerId: currentCustomerId,
|
|
156
|
+
subscriptions: currentSubscriptions,
|
|
157
|
+
trialedProducts: currentTrialedProducts,
|
|
158
|
+
lastSubscription: currentLastSubscription,
|
|
159
|
+
rewardAccountId: currentRewardAccountId,
|
|
160
|
+
productEntitlements: currentProductEntitlements,
|
|
161
|
+
};
|
|
162
|
+
const newSubscriptionState = await this.messenger.call('SubscriptionService:getSubscriptions');
|
|
163
|
+
this.#updateSubscriptionStateIfChanged(currentSubscriptionState, newSubscriptionState);
|
|
164
|
+
await this.#refreshBenefitsIfActive(refreshBenefits);
|
|
165
|
+
return newSubscriptionState.subscriptions;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Updates the subscription state and refreshes the access token when the
|
|
169
|
+
* fetched subscription state differs from the current state.
|
|
170
|
+
*
|
|
171
|
+
* @param currentState - The subscription state captured before the request.
|
|
172
|
+
* @param newState - The subscription state returned by the service.
|
|
173
|
+
*/
|
|
174
|
+
#updateSubscriptionStateIfChanged(currentState, newState) {
|
|
175
|
+
const { subscriptions: currentSubscriptions, trialedProducts: currentTrialedProducts, customerId: currentCustomerId, lastSubscription: currentLastSubscription, rewardAccountId: currentRewardAccountId, productEntitlements: currentProductEntitlements, } = currentState;
|
|
176
|
+
const { customerId: newCustomerId, subscriptions: newSubscriptions, trialedProducts: newTrialedProducts, lastSubscription: newLastSubscription, rewardAccountId: newRewardAccountId, productEntitlements: newProductEntitlements, } = newState;
|
|
177
|
+
const areSubscriptionsEqual = this.#areSubscriptionsEqual(currentSubscriptions, newSubscriptions);
|
|
178
|
+
const areTrialedProductsEqual = this.#areTrialedProductsEqual(currentTrialedProducts, newTrialedProducts);
|
|
179
|
+
const isLastSubscriptionEqual = this.#isSubscriptionEqual(currentLastSubscription, newLastSubscription);
|
|
180
|
+
const areCustomerIdsEqual = currentCustomerId === newCustomerId;
|
|
181
|
+
const areRewardAccountIdsEqual = currentRewardAccountId === newRewardAccountId;
|
|
182
|
+
const areProductEntitlementsEqual = this.#areProductEntitlementsEqual(currentProductEntitlements, newProductEntitlements);
|
|
183
|
+
// only update the state if the subscriptions or trialed products are different
|
|
184
|
+
// this prevents unnecessary state updates events, easier for the clients to handle
|
|
185
|
+
// Only update the state if any subscription-related field changed. This
|
|
186
|
+
// prevents unnecessary state change events for clients to handle.
|
|
187
|
+
if (!areSubscriptionsEqual ||
|
|
188
|
+
!isLastSubscriptionEqual ||
|
|
189
|
+
!areTrialedProductsEqual ||
|
|
190
|
+
!areCustomerIdsEqual ||
|
|
191
|
+
!areRewardAccountIdsEqual ||
|
|
192
|
+
!areProductEntitlementsEqual) {
|
|
193
|
+
this.update((state) => {
|
|
194
|
+
state.subscriptions = newSubscriptions;
|
|
195
|
+
state.customerId = newCustomerId;
|
|
196
|
+
state.trialedProducts = newTrialedProducts;
|
|
197
|
+
state.lastSubscription = newLastSubscription;
|
|
198
|
+
state.rewardAccountId = newRewardAccountId;
|
|
199
|
+
// Omitted entitlements are an empty map so selectors fail closed
|
|
200
|
+
// instead of keeping last-known paid-feature flags.
|
|
201
|
+
state.productEntitlements = newProductEntitlements ?? {};
|
|
202
|
+
});
|
|
203
|
+
// Trigger an access token refresh to ensure the user has the latest
|
|
204
|
+
// access token after a subscription state change.
|
|
205
|
+
this.triggerAccessTokenRefresh();
|
|
206
|
+
}
|
|
160
207
|
}
|
|
161
208
|
/**
|
|
162
209
|
* Gets and stores the user's subscription benefits.
|
|
@@ -166,12 +213,12 @@ export class SubscriptionController extends StaticIntervalPollingController() {
|
|
|
166
213
|
* not eligible for benefits.
|
|
167
214
|
*/
|
|
168
215
|
async getBenefits() {
|
|
169
|
-
|
|
216
|
+
this.#assertIsActiveMoneyAccountPlusSubscriber();
|
|
170
217
|
const benefits = await this.messenger.call('SubscriptionService:getBenefits');
|
|
171
218
|
// The subscription may have become inactive while the benefits request
|
|
172
219
|
// was in flight. Re-check before persisting the response so stale
|
|
173
220
|
// benefits cannot be restored after entitlement ends.
|
|
174
|
-
|
|
221
|
+
this.#assertIsActiveMoneyAccountPlusSubscriber();
|
|
175
222
|
this.update((state) => {
|
|
176
223
|
state.benefits = benefits.eligible
|
|
177
224
|
? {
|
|
@@ -204,18 +251,18 @@ export class SubscriptionController extends StaticIntervalPollingController() {
|
|
|
204
251
|
return await this.messenger.call('SubscriptionService:getSubscriptionsEligibilities', request);
|
|
205
252
|
}
|
|
206
253
|
async cancelSubscription(request) {
|
|
207
|
-
|
|
254
|
+
this.#assertIsUserSubscribed({ subscriptionId: request.subscriptionId });
|
|
208
255
|
const cancelledSubscription = await this.messenger.call('SubscriptionService:cancelSubscription', request);
|
|
209
256
|
this.update((state) => {
|
|
210
257
|
state.subscriptions = state.subscriptions.map((subscription) => subscription.id === request.subscriptionId
|
|
211
258
|
? { ...subscription, ...cancelledSubscription }
|
|
212
259
|
: subscription);
|
|
213
260
|
});
|
|
214
|
-
await
|
|
261
|
+
await this.#refreshBenefitsIfActive();
|
|
215
262
|
this.triggerAccessTokenRefresh();
|
|
216
263
|
}
|
|
217
264
|
async unCancelSubscription(request) {
|
|
218
|
-
|
|
265
|
+
this.#assertIsUserSubscribed({ subscriptionId: request.subscriptionId });
|
|
219
266
|
const uncancelledSubscription = await this.messenger.call('SubscriptionService:unCancelSubscription', {
|
|
220
267
|
subscriptionId: request.subscriptionId,
|
|
221
268
|
});
|
|
@@ -224,7 +271,7 @@ export class SubscriptionController extends StaticIntervalPollingController() {
|
|
|
224
271
|
? { ...subscription, ...uncancelledSubscription }
|
|
225
272
|
: subscription);
|
|
226
273
|
});
|
|
227
|
-
await
|
|
274
|
+
await this.#refreshBenefitsIfActive();
|
|
228
275
|
this.triggerAccessTokenRefresh();
|
|
229
276
|
}
|
|
230
277
|
/**
|
|
@@ -240,10 +287,10 @@ export class SubscriptionController extends StaticIntervalPollingController() {
|
|
|
240
287
|
async startSubscriptionWithCard(request) {
|
|
241
288
|
// get the latest subscriptions state before computing trial eligibility
|
|
242
289
|
await this.getSubscriptions();
|
|
243
|
-
|
|
290
|
+
this.#assertIsUserNotSubscribed({ products: request.products });
|
|
244
291
|
const response = await this.messenger.call('SubscriptionService:startSubscriptionWithCard', {
|
|
245
292
|
...request,
|
|
246
|
-
isTrialRequested:
|
|
293
|
+
isTrialRequested: this.#getIsTrialRequested(request.products, request.recurringInterval),
|
|
247
294
|
});
|
|
248
295
|
// note: no need to trigger access token refresh after startSubscriptionWithCard request because this only return stripe checkout session url, subscription not created yet
|
|
249
296
|
return response;
|
|
@@ -266,11 +313,11 @@ export class SubscriptionController extends StaticIntervalPollingController() {
|
|
|
266
313
|
throw new Error(SubscriptionControllerErrorMessage.SubscriptionProductsEmpty);
|
|
267
314
|
}
|
|
268
315
|
// get the latest subscriptions state before computing trial eligibility
|
|
269
|
-
await
|
|
270
|
-
|
|
316
|
+
await this.#getSubscriptions({ refreshBenefits: false });
|
|
317
|
+
this.#assertIsUserNotSubscribed({ products: request.products });
|
|
271
318
|
const response = await this.messenger.call('SubscriptionService:startSubscriptionWithCrypto', {
|
|
272
319
|
...request,
|
|
273
|
-
isTrialRequested:
|
|
320
|
+
isTrialRequested: this.#getIsTrialRequested(request.products, request.recurringInterval),
|
|
274
321
|
});
|
|
275
322
|
// Crypto start creates the subscription immediately (unlike card checkout).
|
|
276
323
|
await this.getSubscriptions();
|
|
@@ -318,13 +365,13 @@ export class SubscriptionController extends StaticIntervalPollingController() {
|
|
|
318
365
|
throw new Error('Last selected payment method not found');
|
|
319
366
|
}
|
|
320
367
|
const lastSelectedPaymentMethodForProduct = lastSelectedPaymentMethod[productType];
|
|
321
|
-
|
|
322
|
-
const productPrice =
|
|
368
|
+
this.#assertIsPaymentMethodCrypto(lastSelectedPaymentMethodForProduct);
|
|
369
|
+
const productPrice = this.#getProductPriceByProductAndPlan(productType, lastSelectedPaymentMethodForProduct.plan);
|
|
323
370
|
// get the latest subscriptions state before computing trial eligibility
|
|
324
|
-
await
|
|
325
|
-
const isTrialRequested =
|
|
371
|
+
await this.#getSubscriptions({ refreshBenefits: false });
|
|
372
|
+
const isTrialRequested = this.#getIsTrialRequested([productType], lastSelectedPaymentMethodForProduct.plan);
|
|
326
373
|
const currentSubscription = this.getSubscriptionByProduct(productType);
|
|
327
|
-
|
|
374
|
+
this.#assertValidSubscriptionStateForCryptoApproval({
|
|
328
375
|
productType,
|
|
329
376
|
});
|
|
330
377
|
// if subscription exists, this transaction is for changing payment method
|
|
@@ -359,7 +406,7 @@ export class SubscriptionController extends StaticIntervalPollingController() {
|
|
|
359
406
|
await this.startSubscriptionWithCrypto(params);
|
|
360
407
|
}
|
|
361
408
|
// update the subscriptions state after subscription created in server
|
|
362
|
-
await
|
|
409
|
+
await this.#getSubscriptions({ refreshBenefits: false });
|
|
363
410
|
}
|
|
364
411
|
/**
|
|
365
412
|
* Get transaction params to create crypto approve transaction for subscription payment
|
|
@@ -384,7 +431,7 @@ export class SubscriptionController extends StaticIntervalPollingController() {
|
|
|
384
431
|
if (!price) {
|
|
385
432
|
throw new Error('Price not found');
|
|
386
433
|
}
|
|
387
|
-
const chainsPaymentInfo =
|
|
434
|
+
const chainsPaymentInfo = this.#findCryptoPaymentMethod(request.productType, CRYPTO_AUTH_METHODS.ERC20_APPROVAL);
|
|
388
435
|
if (!chainsPaymentInfo) {
|
|
389
436
|
throw new Error('Chains payment info not found');
|
|
390
437
|
}
|
|
@@ -482,17 +529,17 @@ export class SubscriptionController extends StaticIntervalPollingController() {
|
|
|
482
529
|
if (request.products.length === 0) {
|
|
483
530
|
throw new Error(SubscriptionControllerErrorMessage.SubscriptionProductsEmpty);
|
|
484
531
|
}
|
|
485
|
-
|
|
532
|
+
this.#assertIsUserNotSubscribed({ products: request.products });
|
|
486
533
|
const selectedPaymentMethod = this.state.lastSelectedPaymentMethod?.[request.products[0]];
|
|
487
|
-
|
|
534
|
+
this.#assertIsPaymentMethodCrypto(selectedPaymentMethod);
|
|
488
535
|
const cryptoAuthMethod = selectedPaymentMethod.cryptoAuthMethod ??
|
|
489
536
|
CRYPTO_AUTH_METHODS.ERC20_APPROVAL;
|
|
490
|
-
const isEligibleForTrialedSponsorship =
|
|
537
|
+
const isEligibleForTrialedSponsorship = this.#getIsEligibleForTrialedSponsorship(request.chainId, request.products, cryptoAuthMethod);
|
|
491
538
|
if (!isEligibleForTrialedSponsorship) {
|
|
492
539
|
return false;
|
|
493
540
|
}
|
|
494
541
|
const { paymentTokenSymbol, plan } = selectedPaymentMethod;
|
|
495
|
-
const productPrice =
|
|
542
|
+
const productPrice = this.#getProductPriceByProductAndPlan(
|
|
496
543
|
// we only support one product at a time for now
|
|
497
544
|
request.products[0], plan);
|
|
498
545
|
const billingCycles = productPrice.minBillingCycles;
|
|
@@ -533,7 +580,7 @@ export class SubscriptionController extends StaticIntervalPollingController() {
|
|
|
533
580
|
*/
|
|
534
581
|
async linkRewards(request) {
|
|
535
582
|
// assert that the user is subscribed to the subscription
|
|
536
|
-
|
|
583
|
+
this.#assertIsUserSubscribed({ subscriptionId: request.subscriptionId });
|
|
537
584
|
// link rewards to the subscription
|
|
538
585
|
const response = await this.messenger.call('SubscriptionService:linkRewards', {
|
|
539
586
|
rewardAccountId: request.rewardAccountId,
|
|
@@ -545,6 +592,54 @@ export class SubscriptionController extends StaticIntervalPollingController() {
|
|
|
545
592
|
async _executePoll() {
|
|
546
593
|
await this.getSubscriptions();
|
|
547
594
|
}
|
|
595
|
+
async #refreshBenefitsIfActive(refreshBenefits = true) {
|
|
596
|
+
if (!this.#hasActiveMoneyAccountPlusSubscription()) {
|
|
597
|
+
if (this.state.benefits) {
|
|
598
|
+
this.update((state) => {
|
|
599
|
+
state.benefits = undefined;
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
if (!refreshBenefits) {
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
try {
|
|
608
|
+
await this.getBenefits();
|
|
609
|
+
}
|
|
610
|
+
catch (error) {
|
|
611
|
+
log('Failed to refresh subscription benefits', error);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Calculate total subscription price amount (approval amount) from price info
|
|
616
|
+
* e.g: $8 per month * 12 months min billing cycles = $96
|
|
617
|
+
*
|
|
618
|
+
* @param price - The price info
|
|
619
|
+
* @returns The price amount
|
|
620
|
+
*/
|
|
621
|
+
#getSubscriptionPriceAmount(price) {
|
|
622
|
+
// no need to use BigInt since max unitDecimals are always 2 for price
|
|
623
|
+
const amount = new BigNumber(price.unitAmount)
|
|
624
|
+
.div(10 ** price.unitDecimals)
|
|
625
|
+
.multipliedBy(price.minBillingCycles)
|
|
626
|
+
.toString();
|
|
627
|
+
return amount;
|
|
628
|
+
}
|
|
629
|
+
/**
|
|
630
|
+
* Calculate minimum subscription balance amount from price info
|
|
631
|
+
*
|
|
632
|
+
* @param price - The price info
|
|
633
|
+
* @returns The balance amount
|
|
634
|
+
*/
|
|
635
|
+
#getSubscriptionBalanceAmount(price) {
|
|
636
|
+
// no need to use BigInt since max unitDecimals are always 2 for price
|
|
637
|
+
const amount = new BigNumber(price.unitAmount)
|
|
638
|
+
.div(10 ** price.unitDecimals)
|
|
639
|
+
.multipliedBy(price.minBillingCyclesForBalance)
|
|
640
|
+
.toString();
|
|
641
|
+
return amount;
|
|
642
|
+
}
|
|
548
643
|
/**
|
|
549
644
|
* Calculate token approve amount from price info
|
|
550
645
|
*
|
|
@@ -558,7 +653,7 @@ export class SubscriptionController extends StaticIntervalPollingController() {
|
|
|
558
653
|
throw new Error('Conversion rate not found');
|
|
559
654
|
}
|
|
560
655
|
// price of the product
|
|
561
|
-
const priceAmount = new BigNumber(
|
|
656
|
+
const priceAmount = new BigNumber(this.#getSubscriptionPriceAmount(price));
|
|
562
657
|
const tokenDecimal = new BigNumber(10).pow(tokenPaymentInfo.decimals);
|
|
563
658
|
const tokenAmount = priceAmount
|
|
564
659
|
.multipliedBy(tokenDecimal)
|
|
@@ -577,7 +672,7 @@ export class SubscriptionController extends StaticIntervalPollingController() {
|
|
|
577
672
|
if (!conversionRate) {
|
|
578
673
|
throw new Error('Conversion rate not found');
|
|
579
674
|
}
|
|
580
|
-
const balanceAmount = new BigNumber(
|
|
675
|
+
const balanceAmount = new BigNumber(this.#getSubscriptionBalanceAmount(price));
|
|
581
676
|
const tokenDecimal = new BigNumber(10).pow(tokenPaymentInfo.decimals);
|
|
582
677
|
const tokenAmount = balanceAmount
|
|
583
678
|
.multipliedBy(tokenDecimal)
|
|
@@ -602,242 +697,244 @@ export class SubscriptionController extends StaticIntervalPollingController() {
|
|
|
602
697
|
// will be fetched.
|
|
603
698
|
this.messenger.call('AuthenticationController:performSignOut');
|
|
604
699
|
}
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
subscriptions
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
const areSubscriptionsEqual = __classPrivateFieldGet(this, _SubscriptionController_instances, "m", _SubscriptionController_areSubscriptionsEqual).call(this, currentSubscriptions, newSubscriptions);
|
|
629
|
-
const areTrialedProductsEqual = __classPrivateFieldGet(this, _SubscriptionController_instances, "m", _SubscriptionController_areTrialedProductsEqual).call(this, currentTrialedProducts, newTrialedProducts);
|
|
630
|
-
const isLastSubscriptionEqual = __classPrivateFieldGet(this, _SubscriptionController_instances, "m", _SubscriptionController_isSubscriptionEqual).call(this, currentLastSubscription, newLastSubscription);
|
|
631
|
-
const areCustomerIdsEqual = currentCustomerId === newCustomerId;
|
|
632
|
-
const areRewardAccountIdsEqual = currentRewardAccountId === newRewardAccountId;
|
|
633
|
-
const areProductEntitlementsEqual = __classPrivateFieldGet(this, _SubscriptionController_instances, "m", _SubscriptionController_areProductEntitlementsEqual).call(this, currentProductEntitlements, newProductEntitlements);
|
|
634
|
-
// only update the state if the subscriptions or trialed products are different
|
|
635
|
-
// this prevents unnecessary state updates events, easier for the clients to handle
|
|
636
|
-
// Only update the state if any subscription-related field changed. This
|
|
637
|
-
// prevents unnecessary state change events for clients to handle.
|
|
638
|
-
if (!areSubscriptionsEqual ||
|
|
639
|
-
!isLastSubscriptionEqual ||
|
|
640
|
-
!areTrialedProductsEqual ||
|
|
641
|
-
!areCustomerIdsEqual ||
|
|
642
|
-
!areRewardAccountIdsEqual ||
|
|
643
|
-
!areProductEntitlementsEqual) {
|
|
644
|
-
this.update((state) => {
|
|
645
|
-
state.subscriptions = newSubscriptions;
|
|
646
|
-
state.customerId = newCustomerId;
|
|
647
|
-
state.trialedProducts = newTrialedProducts;
|
|
648
|
-
state.lastSubscription = newLastSubscription;
|
|
649
|
-
state.rewardAccountId = newRewardAccountId;
|
|
650
|
-
// Omitted entitlements are an empty map so selectors fail closed
|
|
651
|
-
// instead of keeping last-known paid-feature flags.
|
|
652
|
-
state.productEntitlements = newProductEntitlements ?? {};
|
|
653
|
-
});
|
|
654
|
-
// Trigger an access token refresh to ensure the user has the latest
|
|
655
|
-
// access token after a subscription state change.
|
|
656
|
-
this.triggerAccessTokenRefresh();
|
|
700
|
+
#getProductPriceByProductAndPlan(productType, plan) {
|
|
701
|
+
const { pricing } = this.state;
|
|
702
|
+
const productPricing = pricing?.products.find((product) => product.name === productType);
|
|
703
|
+
const productPrice = productPricing?.prices.find((price) => price.interval === plan);
|
|
704
|
+
if (!productPrice) {
|
|
705
|
+
throw new Error(SubscriptionControllerErrorMessage.ProductPriceNotFound);
|
|
706
|
+
}
|
|
707
|
+
return productPrice;
|
|
708
|
+
}
|
|
709
|
+
#assertValidSubscriptionStateForCryptoApproval({ productType, }) {
|
|
710
|
+
const subscription = this.state.subscriptions.find((sub) => sub.products.some((product) => product.name === productType));
|
|
711
|
+
const isValid = !subscription ||
|
|
712
|
+
[
|
|
713
|
+
SUBSCRIPTION_STATUSES.pastDue,
|
|
714
|
+
SUBSCRIPTION_STATUSES.unpaid,
|
|
715
|
+
SUBSCRIPTION_STATUSES.paused,
|
|
716
|
+
SUBSCRIPTION_STATUSES.provisional,
|
|
717
|
+
SUBSCRIPTION_STATUSES.active,
|
|
718
|
+
SUBSCRIPTION_STATUSES.trialing,
|
|
719
|
+
].includes(subscription.status);
|
|
720
|
+
if (!isValid) {
|
|
721
|
+
throw new Error(SubscriptionControllerErrorMessage.SubscriptionNotValidForCryptoApproval);
|
|
722
|
+
}
|
|
657
723
|
}
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
if (
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
});
|
|
724
|
+
#assertIsUserNotSubscribed({ products }) {
|
|
725
|
+
const subscription = this.state.subscriptions.find((sub) => sub.products.some((product) => products.includes(product.name)));
|
|
726
|
+
if (subscription &&
|
|
727
|
+
ACTIVE_SUBSCRIPTION_STATUSES.includes(subscription.status)) {
|
|
728
|
+
throw new Error(SubscriptionControllerErrorMessage.UserAlreadySubscribed);
|
|
664
729
|
}
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
log('Failed to refresh subscription benefits', error);
|
|
675
|
-
}
|
|
676
|
-
}, _SubscriptionController_getSubscriptionPriceAmount = function _SubscriptionController_getSubscriptionPriceAmount(price) {
|
|
677
|
-
// no need to use BigInt since max unitDecimals are always 2 for price
|
|
678
|
-
const amount = new BigNumber(price.unitAmount)
|
|
679
|
-
.div(10 ** price.unitDecimals)
|
|
680
|
-
.multipliedBy(price.minBillingCycles)
|
|
681
|
-
.toString();
|
|
682
|
-
return amount;
|
|
683
|
-
}, _SubscriptionController_getSubscriptionBalanceAmount = function _SubscriptionController_getSubscriptionBalanceAmount(price) {
|
|
684
|
-
// no need to use BigInt since max unitDecimals are always 2 for price
|
|
685
|
-
const amount = new BigNumber(price.unitAmount)
|
|
686
|
-
.div(10 ** price.unitDecimals)
|
|
687
|
-
.multipliedBy(price.minBillingCyclesForBalance)
|
|
688
|
-
.toString();
|
|
689
|
-
return amount;
|
|
690
|
-
}, _SubscriptionController_getProductPriceByProductAndPlan = function _SubscriptionController_getProductPriceByProductAndPlan(productType, plan) {
|
|
691
|
-
const { pricing } = this.state;
|
|
692
|
-
const productPricing = pricing?.products.find((product) => product.name === productType);
|
|
693
|
-
const productPrice = productPricing?.prices.find((price) => price.interval === plan);
|
|
694
|
-
if (!productPrice) {
|
|
695
|
-
throw new Error(SubscriptionControllerErrorMessage.ProductPriceNotFound);
|
|
696
|
-
}
|
|
697
|
-
return productPrice;
|
|
698
|
-
}, _SubscriptionController_assertValidSubscriptionStateForCryptoApproval = function _SubscriptionController_assertValidSubscriptionStateForCryptoApproval({ productType, }) {
|
|
699
|
-
const subscription = this.state.subscriptions.find((sub) => sub.products.some((product) => product.name === productType));
|
|
700
|
-
const isValid = !subscription ||
|
|
701
|
-
[
|
|
702
|
-
SUBSCRIPTION_STATUSES.pastDue,
|
|
703
|
-
SUBSCRIPTION_STATUSES.unpaid,
|
|
704
|
-
SUBSCRIPTION_STATUSES.paused,
|
|
705
|
-
SUBSCRIPTION_STATUSES.provisional,
|
|
706
|
-
SUBSCRIPTION_STATUSES.active,
|
|
707
|
-
SUBSCRIPTION_STATUSES.trialing,
|
|
708
|
-
].includes(subscription.status);
|
|
709
|
-
if (!isValid) {
|
|
710
|
-
throw new Error(SubscriptionControllerErrorMessage.SubscriptionNotValidForCryptoApproval);
|
|
711
|
-
}
|
|
712
|
-
}, _SubscriptionController_assertIsUserNotSubscribed = function _SubscriptionController_assertIsUserNotSubscribed({ products }) {
|
|
713
|
-
const subscription = this.state.subscriptions.find((sub) => sub.products.some((product) => products.includes(product.name)));
|
|
714
|
-
if (subscription &&
|
|
715
|
-
ACTIVE_SUBSCRIPTION_STATUSES.includes(subscription.status)) {
|
|
716
|
-
throw new Error(SubscriptionControllerErrorMessage.UserAlreadySubscribed);
|
|
717
|
-
}
|
|
718
|
-
}, _SubscriptionController_assertIsActiveMoneyAccountPlusSubscriber = function _SubscriptionController_assertIsActiveMoneyAccountPlusSubscriber() {
|
|
719
|
-
if (!__classPrivateFieldGet(this, _SubscriptionController_instances, "m", _SubscriptionController_hasActiveMoneyAccountPlusSubscription).call(this)) {
|
|
720
|
-
if (this.state.benefits) {
|
|
721
|
-
this.update((state) => {
|
|
722
|
-
state.benefits = undefined;
|
|
723
|
-
});
|
|
730
|
+
}
|
|
731
|
+
#assertIsActiveMoneyAccountPlusSubscriber() {
|
|
732
|
+
if (!this.#hasActiveMoneyAccountPlusSubscription()) {
|
|
733
|
+
if (this.state.benefits) {
|
|
734
|
+
this.update((state) => {
|
|
735
|
+
state.benefits = undefined;
|
|
736
|
+
});
|
|
737
|
+
}
|
|
738
|
+
throw new Error(SubscriptionControllerErrorMessage.UserNotSubscribed);
|
|
724
739
|
}
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
throw new Error(SubscriptionControllerErrorMessage.UserNotSubscribed);
|
|
730
|
-
}
|
|
731
|
-
}, _SubscriptionController_assertIsPaymentMethodCrypto = function _SubscriptionController_assertIsPaymentMethodCrypto(value) {
|
|
732
|
-
if (value?.type !== PAYMENT_TYPES.byCrypto ||
|
|
733
|
-
!value.paymentTokenAddress ||
|
|
734
|
-
!value.paymentTokenSymbol) {
|
|
735
|
-
throw new Error(SubscriptionControllerErrorMessage.PaymentMethodNotCrypto);
|
|
736
|
-
}
|
|
737
|
-
}, _SubscriptionController_hasActiveMoneyAccountPlusSubscription = function _SubscriptionController_hasActiveMoneyAccountPlusSubscription() {
|
|
738
|
-
return this.state.subscriptions.some((subscription) => subscription.products.some((product) => product.name === PRODUCT_TYPES.MONEY_ACCOUNT_PLUS) && ACTIVE_SUBSCRIPTION_STATUSES.includes(subscription.status));
|
|
739
|
-
}, _SubscriptionController_getIsEligibleForTrialedSponsorship = function _SubscriptionController_getIsEligibleForTrialedSponsorship(chainId, products, cryptoAuthMethod) {
|
|
740
|
-
const isSponsorshipSupported = __classPrivateFieldGet(this, _SubscriptionController_instances, "m", _SubscriptionController_getChainSupportsSponsorship).call(this, chainId, products[0], cryptoAuthMethod);
|
|
741
|
-
// verify if the user has trialed the provided products before
|
|
742
|
-
const hasTrialedBefore = this.state.trialedProducts.some((product) => products.includes(product));
|
|
743
|
-
return isSponsorshipSupported && !hasTrialedBefore;
|
|
744
|
-
}, _SubscriptionController_getIsTrialRequested = function _SubscriptionController_getIsTrialRequested(products, plan) {
|
|
745
|
-
return products.every((productType) => {
|
|
746
|
-
if (this.state.trialedProducts.includes(productType)) {
|
|
747
|
-
return false;
|
|
740
|
+
}
|
|
741
|
+
#assertIsUserSubscribed(request) {
|
|
742
|
+
if (!this.state.subscriptions.find((subscription) => subscription.id === request.subscriptionId)) {
|
|
743
|
+
throw new Error(SubscriptionControllerErrorMessage.UserNotSubscribed);
|
|
748
744
|
}
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
matches.push({
|
|
765
|
-
method: paymentMethod,
|
|
766
|
-
explicit: resolved.explicit,
|
|
767
|
-
});
|
|
745
|
+
}
|
|
746
|
+
/**
|
|
747
|
+
* Asserts that the value is a valid crypto payment method.
|
|
748
|
+
*
|
|
749
|
+
* After this assert, `cryptoAuthMethod` and `useTestClock` remain optional
|
|
750
|
+
* because persisted cache entries may omit them.
|
|
751
|
+
*
|
|
752
|
+
* @param value - The value to assert.
|
|
753
|
+
* @throws an error if the value is not a valid crypto payment method.
|
|
754
|
+
*/
|
|
755
|
+
#assertIsPaymentMethodCrypto(value) {
|
|
756
|
+
if (value?.type !== PAYMENT_TYPES.byCrypto ||
|
|
757
|
+
!value.paymentTokenAddress ||
|
|
758
|
+
!value.paymentTokenSymbol) {
|
|
759
|
+
throw new Error(SubscriptionControllerErrorMessage.PaymentMethodNotCrypto);
|
|
768
760
|
}
|
|
769
761
|
}
|
|
770
|
-
|
|
771
|
-
return
|
|
762
|
+
#hasActiveMoneyAccountPlusSubscription() {
|
|
763
|
+
return this.state.subscriptions.some((subscription) => subscription.products.some((product) => product.name === PRODUCT_TYPES.MONEY_ACCOUNT_PLUS) && ACTIVE_SUBSCRIPTION_STATUSES.includes(subscription.status));
|
|
772
764
|
}
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
765
|
+
/**
|
|
766
|
+
* Determines if the user is eligible for trialed sponsorship for the given chain and products.
|
|
767
|
+
* The user is eligible if the chain supports sponsorship and the user has not trialed the provided products before.
|
|
768
|
+
*
|
|
769
|
+
* @param chainId - The chain ID
|
|
770
|
+
* @param products - The products to check eligibility for
|
|
771
|
+
* @param cryptoAuthMethod - The crypto authorization method of the selected payment method
|
|
772
|
+
* @returns True if the user is eligible for trialed sponsorship, false otherwise
|
|
773
|
+
*/
|
|
774
|
+
#getIsEligibleForTrialedSponsorship(chainId, products, cryptoAuthMethod) {
|
|
775
|
+
const isSponsorshipSupported = this.#getChainSupportsSponsorship(chainId, products[0], cryptoAuthMethod);
|
|
776
|
+
// verify if the user has trialed the provided products before
|
|
777
|
+
const hasTrialedBefore = this.state.trialedProducts.some((product) => products.includes(product));
|
|
778
|
+
return isSponsorshipSupported && !hasTrialedBefore;
|
|
777
779
|
}
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
780
|
+
/**
|
|
781
|
+
* Whether a trial should be requested for the given products and plan.
|
|
782
|
+
* True only when every product has `trialPeriodDays > 0` and has not
|
|
783
|
+
* already been trialed.
|
|
784
|
+
*
|
|
785
|
+
* @param products - The products to check.
|
|
786
|
+
* @param plan - The recurring interval to look up pricing for.
|
|
787
|
+
* @returns Whether a trial should be requested.
|
|
788
|
+
*/
|
|
789
|
+
#getIsTrialRequested(products, plan) {
|
|
790
|
+
return products.every((productType) => {
|
|
791
|
+
if (this.state.trialedProducts.includes(productType)) {
|
|
792
|
+
return false;
|
|
793
|
+
}
|
|
794
|
+
const productPrice = this.#getProductPriceByProductAndPlan(productType, plan);
|
|
795
|
+
return productPrice.trialPeriodDays > 0;
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
#findCryptoPaymentMethod(productType, cryptoAuthMethod) {
|
|
799
|
+
const matches = [];
|
|
800
|
+
for (const paymentMethod of this.state.pricing?.paymentMethods ?? []) {
|
|
801
|
+
if (paymentMethod.type !== PAYMENT_TYPES.byCrypto) {
|
|
802
|
+
continue;
|
|
803
|
+
}
|
|
804
|
+
const resolved = this.#resolveCryptoPaymentMethodDefaults(paymentMethod);
|
|
805
|
+
if (!resolved) {
|
|
806
|
+
continue;
|
|
807
|
+
}
|
|
808
|
+
if (resolved.cryptoAuthMethod === cryptoAuthMethod &&
|
|
809
|
+
resolved.products.includes(productType)) {
|
|
810
|
+
matches.push({
|
|
811
|
+
method: paymentMethod,
|
|
812
|
+
explicit: resolved.explicit,
|
|
813
|
+
});
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
if (matches.length === 0) {
|
|
817
|
+
return undefined;
|
|
818
|
+
}
|
|
819
|
+
const explicitMatches = matches.filter((match) => match.explicit);
|
|
820
|
+
const candidates = explicitMatches.length > 0 ? explicitMatches : matches;
|
|
821
|
+
if (candidates.length > 1) {
|
|
822
|
+
throw new Error('Multiple matching crypto payment methods found');
|
|
823
|
+
}
|
|
824
|
+
return candidates[0].method;
|
|
825
|
+
}
|
|
826
|
+
/**
|
|
827
|
+
* Resolves omitted `products` / `cryptoAuthMethod` on a crypto pricing row.
|
|
828
|
+
* Legacy Shield + `erc20_approval` defaults apply only when both fields are
|
|
829
|
+
* absent. If `products` is present, the list must be non-empty and
|
|
830
|
+
* `cryptoAuthMethod` must be explicit; otherwise the row is ignored.
|
|
831
|
+
*
|
|
832
|
+
* @param paymentMethod - The crypto pricing row.
|
|
833
|
+
* @returns Resolved products and auth method, or `undefined` if the row is
|
|
834
|
+
* incomplete.
|
|
835
|
+
*/
|
|
836
|
+
#resolveCryptoPaymentMethodDefaults(paymentMethod) {
|
|
837
|
+
const productsPresent = paymentMethod.products !== undefined;
|
|
838
|
+
const authPresent = paymentMethod.cryptoAuthMethod !== undefined;
|
|
839
|
+
if (!productsPresent && !authPresent) {
|
|
840
|
+
return {
|
|
841
|
+
products: [PRODUCT_TYPES.SHIELD],
|
|
842
|
+
cryptoAuthMethod: CRYPTO_AUTH_METHODS.ERC20_APPROVAL,
|
|
843
|
+
explicit: false,
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
if (!paymentMethod.products?.length || !paymentMethod.cryptoAuthMethod) {
|
|
847
|
+
return undefined;
|
|
848
|
+
}
|
|
783
849
|
return {
|
|
784
|
-
products:
|
|
785
|
-
cryptoAuthMethod:
|
|
786
|
-
explicit:
|
|
850
|
+
products: paymentMethod.products,
|
|
851
|
+
cryptoAuthMethod: paymentMethod.cryptoAuthMethod,
|
|
852
|
+
explicit: true,
|
|
787
853
|
};
|
|
788
854
|
}
|
|
789
|
-
|
|
790
|
-
|
|
855
|
+
/**
|
|
856
|
+
* Whether the given chain supports sponsorship for the product and auth method.
|
|
857
|
+
*
|
|
858
|
+
* @param chainId - The chain ID
|
|
859
|
+
* @param productType - The product type
|
|
860
|
+
* @param cryptoAuthMethod - The crypto authorization method to look up
|
|
861
|
+
* @returns True if the chain row has sponsorship enabled, false if it is explicitly not sponsored
|
|
862
|
+
* @throws If the crypto payment method or chain row is missing from pricing
|
|
863
|
+
*/
|
|
864
|
+
#getChainSupportsSponsorship(chainId, productType, cryptoAuthMethod) {
|
|
865
|
+
const cryptoPaymentInfo = this.#findCryptoPaymentMethod(productType, cryptoAuthMethod);
|
|
866
|
+
if (!cryptoPaymentInfo) {
|
|
867
|
+
throw new Error('Chains payment info not found');
|
|
868
|
+
}
|
|
869
|
+
const chainPaymentInfo = cryptoPaymentInfo.chains?.find((chain) => chain.chainId === chainId);
|
|
870
|
+
if (!chainPaymentInfo) {
|
|
871
|
+
throw new Error('Invalid chain id');
|
|
872
|
+
}
|
|
873
|
+
return Boolean(chainPaymentInfo.isSponsorshipSupported);
|
|
791
874
|
}
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
//
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
}
|
|
843
|
-
|
|
875
|
+
/**
|
|
876
|
+
* Determines whether two trialed products arrays are equal by comparing all products in the arrays.
|
|
877
|
+
*
|
|
878
|
+
* @param oldTrialedProducts - The first trialed products array to compare.
|
|
879
|
+
* @param newTrialedProducts - The second trialed products array to compare.
|
|
880
|
+
* @returns True if the trialed products arrays are equal, false otherwise.
|
|
881
|
+
*/
|
|
882
|
+
#areTrialedProductsEqual(oldTrialedProducts, newTrialedProducts) {
|
|
883
|
+
return (oldTrialedProducts.length === newTrialedProducts?.length &&
|
|
884
|
+
oldTrialedProducts.every((product) => newTrialedProducts?.includes(product)));
|
|
885
|
+
}
|
|
886
|
+
/**
|
|
887
|
+
* Compares product entitlement maps without treating object key order as a
|
|
888
|
+
* change. Insertion-order-sensitive `JSON.stringify` can otherwise treat an
|
|
889
|
+
* equivalent API payload as different and trigger `performSignOut`.
|
|
890
|
+
*
|
|
891
|
+
* @param oldProductEntitlements - Stored product entitlements.
|
|
892
|
+
* @param newProductEntitlements - Freshly fetched product entitlements.
|
|
893
|
+
* @returns True if the maps are equal regardless of key order.
|
|
894
|
+
*/
|
|
895
|
+
#areProductEntitlementsEqual(oldProductEntitlements, newProductEntitlements) {
|
|
896
|
+
return deepEqual(oldProductEntitlements ?? {}, newProductEntitlements ?? {});
|
|
897
|
+
}
|
|
898
|
+
/**
|
|
899
|
+
* Determines whether two subscription arrays are equal by comparing all properties
|
|
900
|
+
* of each subscription in the arrays.
|
|
901
|
+
*
|
|
902
|
+
* @param oldSubs - The first subscription array to compare.
|
|
903
|
+
* @param newSubs - The second subscription array to compare.
|
|
904
|
+
* @returns True if the subscription arrays are equal, false otherwise.
|
|
905
|
+
*/
|
|
906
|
+
#areSubscriptionsEqual(oldSubs, newSubs) {
|
|
907
|
+
// Check if arrays have different lengths
|
|
908
|
+
if (oldSubs.length !== newSubs.length) {
|
|
909
|
+
return false;
|
|
910
|
+
}
|
|
911
|
+
// Sort both arrays by id to ensure consistent comparison
|
|
912
|
+
const sortedOldSubs = [...oldSubs].sort((a, b) => a.id.localeCompare(b.id));
|
|
913
|
+
const sortedNewSubs = [...newSubs].sort((a, b) => a.id.localeCompare(b.id));
|
|
914
|
+
// Check if all subscriptions are equal
|
|
915
|
+
return sortedOldSubs.every((oldSub, index) => {
|
|
916
|
+
const newSub = sortedNewSubs[index];
|
|
917
|
+
return this.#isSubscriptionEqual(oldSub, newSub);
|
|
918
|
+
});
|
|
919
|
+
}
|
|
920
|
+
#isSubscriptionEqual(oldSub, newSub) {
|
|
921
|
+
// not equal if one is undefined and the other is defined
|
|
922
|
+
if (!oldSub || !newSub) {
|
|
923
|
+
if (!oldSub && !newSub) {
|
|
924
|
+
return true;
|
|
925
|
+
}
|
|
926
|
+
return false;
|
|
927
|
+
}
|
|
928
|
+
return (this.#stringifySubscription(oldSub) ===
|
|
929
|
+
this.#stringifySubscription(newSub));
|
|
930
|
+
}
|
|
931
|
+
#stringifySubscription(subscription) {
|
|
932
|
+
const subsWithSortedProducts = {
|
|
933
|
+
...subscription,
|
|
934
|
+
// order the products by name
|
|
935
|
+
products: [...subscription.products].sort((a, b) => a.name.localeCompare(b.name)),
|
|
936
|
+
};
|
|
937
|
+
return JSON.stringify(subsWithSortedProducts);
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
//# sourceMappingURL=SubscriptionController.js.map
|