Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/subscription-controller/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `getSubscription`: Retrieve current user subscription info if exist.
- `cancelSubscription`: Cancel user active subscription.
- `startShieldSubscriptionWithCard`: start shield subscription via card (with trial option) ([#6300](https://github.com/MetaMask/core/pull/6300))
- Add `getPricing` method ([#6356](https://github.com/MetaMask/core/pull/6356))

[Unreleased]: https://github.com/MetaMask/core/
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
type SubscriptionControllerOptions,
type SubscriptionControllerState,
} from './SubscriptionController';
import type { Subscription } from './types';
import type { Subscription, PricingResponse } from './types';
import {
PaymentType,
ProductType,
Expand Down Expand Up @@ -114,18 +114,21 @@ function createMockSubscriptionService() {
const mockGetSubscriptions = jest.fn().mockImplementation();
const mockCancelSubscription = jest.fn();
const mockStartSubscriptionWithCard = jest.fn();
const mockGetPricing = jest.fn();

const mockService = {
getSubscriptions: mockGetSubscriptions,
cancelSubscription: mockCancelSubscription,
startSubscriptionWithCard: mockStartSubscriptionWithCard,
getPricing: mockGetPricing,
};

return {
mockService,
mockGetSubscriptions,
mockCancelSubscription,
mockStartSubscriptionWithCard,
mockGetPricing,
};
}

Expand Down Expand Up @@ -526,4 +529,21 @@ describe('SubscriptionController', () => {
});
});
});

describe('getPricing', () => {
const mockPricingResponse: PricingResponse = {
products: [],
paymentMethods: [],
};

it('should return pricing response', async () => {
await withController(async ({ controller, mockService }) => {
mockService.getPricing.mockResolvedValue(mockPricingResponse);

const result = await controller.getPricing();

expect(result).toStrictEqual(mockPricingResponse);
});
});
});
});
20 changes: 20 additions & 0 deletions packages/subscription-controller/src/SubscriptionController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
import {
SubscriptionStatus,
type ISubscriptionService,
type PricingResponse,
type ProductType,
type StartSubscriptionRequest,
type Subscription,
Expand All @@ -36,6 +37,10 @@ export type SubscriptionControllerStartShieldSubscriptionWithCardAction = {
type: `${typeof controllerName}:startShieldSubscriptionWithCard`;
handler: SubscriptionController['startShieldSubscriptionWithCard'];
};
export type SubscriptionControllerGetPricingAction = {
type: `${typeof controllerName}:getPricing`;
handler: SubscriptionController['getPricing'];
};

export type SubscriptionControllerGetStateAction = ControllerGetStateAction<
typeof controllerName,
Expand All @@ -45,6 +50,7 @@ export type SubscriptionControllerActions =
| SubscriptionControllerGetSubscriptionsAction
| SubscriptionControllerCancelSubscriptionAction
| SubscriptionControllerStartShieldSubscriptionWithCardAction
| SubscriptionControllerGetPricingAction
| SubscriptionControllerGetStateAction;

export type AllowedActions =
Expand Down Expand Up @@ -167,6 +173,20 @@ export class SubscriptionController extends BaseController<
'SubscriptionController:startShieldSubscriptionWithCard',
this.startShieldSubscriptionWithCard.bind(this),
);

this.messagingSystem.registerActionHandler(
'SubscriptionController:getPricing',
this.getPricing.bind(this),
);
}

/**
Comment thread
matthiasgeihs marked this conversation as resolved.
Outdated
Comment thread
matthiasgeihs marked this conversation as resolved.
Outdated
* Gets the pricing information from the subscription service.
*
* @returns The pricing information.
*/
async getPricing(): Promise<PricingResponse> {
return await this.#subscriptionService.getPricing();
}

async getSubscriptions() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ import {
} from './constants';
import { SubscriptionServiceError } from './errors';
import { SubscriptionService } from './SubscriptionService';
import type { StartSubscriptionRequest, Subscription } from './types';
import type {
StartSubscriptionRequest,
Subscription,
PricingResponse,
} from './types';
import {
PaymentType,
ProductType,
Expand Down Expand Up @@ -288,4 +292,23 @@ describe('SubscriptionService', () => {
);
});
});

describe('getPricing', () => {
const mockPricingResponse: PricingResponse = {
products: [],
paymentMethods: [],
};

it('should fetch pricing successfully', async () => {
const config = createMockConfig();
const service = new SubscriptionService(config);
const testUrl = getTestUrl(Env.DEV);

nock(testUrl).get('/api/v1/pricing').reply(200, mockPricingResponse);

const result = await service.getPricing();

expect(result).toStrictEqual(mockPricingResponse);
});
});
});
6 changes: 6 additions & 0 deletions packages/subscription-controller/src/SubscriptionService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type {
AuthUtils,
GetSubscriptionsResponse,
ISubscriptionService,
PricingResponse,
StartSubscriptionRequest,
StartSubscriptionResponse,
} from './types';
Expand Down Expand Up @@ -101,4 +102,9 @@ export class SubscriptionService implements ISubscriptionService {
const accessToken = await this.authUtils.getAccessToken();
return { Authorization: `Bearer ${accessToken}` };
}

async getPricing(): Promise<PricingResponse> {
const path = 'pricing';
return await this.#makeRequest<PricingResponse>(path);
}
}
7 changes: 7 additions & 0 deletions packages/subscription-controller/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export type {
SubscriptionControllerGetSubscriptionsAction,
SubscriptionControllerCancelSubscriptionAction,
SubscriptionControllerStartShieldSubscriptionWithCardAction,
SubscriptionControllerGetPricingAction,
SubscriptionControllerGetStateAction,
SubscriptionControllerMessenger,
SubscriptionControllerOptions,
Expand All @@ -22,6 +23,12 @@ export type {
PaymentType,
Product,
ProductType,
ProductPrice,
ProductPricing,
TokenPaymentInfo,
ChainPaymentInfo,
PricingPaymentMethod,
PricingResponse,
} from './types';
export { SubscriptionServiceError } from './errors';
export { Env, SubscriptionControllerErrorMessage } from './constants';
Expand Down
40 changes: 40 additions & 0 deletions packages/subscription-controller/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,50 @@ export type AuthUtils = {
getAccessToken: () => Promise<string>;
};

export type ProductPrice = {
interval: string; // "month" | "year"
unitAmount: string; // amount in the smallest unit of the currency, e.g., cents
unitDecimals: number; // number of decimals for the smallest unit of the currency
currency: string; // "usd"
trialPeriodDays: number;
minBillingCycles: number;
};

export type ProductPricing = {
name: string;
prices: ProductPrice[];
};

export type TokenPaymentInfo = {
symbol: string;
address: string;
decimals: number;
conversionRate: {
usd: string;
};
};

export type ChainPaymentInfo = {
chainId: string;
paymentAddress: string;
tokens: TokenPaymentInfo[];
};

export type PricingPaymentMethod = {
type: PaymentType;
chains?: ChainPaymentInfo[];
};

export type PricingResponse = {
products: ProductPricing[];
paymentMethods: PricingPaymentMethod[];
};

export type ISubscriptionService = {
getSubscriptions(): Promise<GetSubscriptionsResponse>;
cancelSubscription(request: { subscriptionId: string }): Promise<void>;
startSubscriptionWithCard(
request: StartSubscriptionRequest,
): Promise<StartSubscriptionResponse>;
getPricing(): Promise<PricingResponse>;
};
Loading