mirror of
https://github.com/bitwarden/browser
synced 2025-12-11 13:53:34 +00:00
[AC-1842] Secrets Manager Trial Page (#7475)
* Got trial page working without the form set up * Set up the form to create SM subscription * Add free SM trial page and sign up * Conner's changes * fixed imports * Set isFromSecretsManagerTrial * Fixed OrgKey location * Add isFromSecretsManager prop to free org create * Add LTO callout * Switch LTO to background box * Defect: AC-2081 * Fixed typo "Secrets Manger" to "Secrets Manager" * Removed discount price logic for storage and secrets manager prices since they don't apply --------- Co-authored-by: Conner Turnbull <133619638+cturnbull-bitwarden@users.noreply.github.com> Co-authored-by: Conner Turnbull <cturnbull@bitwarden.com>
This commit is contained in:
@@ -27,4 +27,5 @@ export class OrganizationCreateRequest {
|
||||
useSecretsManager: boolean;
|
||||
additionalSmSeats: number;
|
||||
additionalServiceAccounts: number;
|
||||
isFromSecretsManagerTrial: boolean;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import { OrganizationResponse } from "../../admin-console/models/response/organization.response";
|
||||
import { PaymentMethodType, PlanType } from "../enums";
|
||||
|
||||
export type OrganizationInformation = {
|
||||
name: string;
|
||||
billingEmail: string;
|
||||
businessName?: string;
|
||||
};
|
||||
|
||||
export type PlanInformation = {
|
||||
type: PlanType;
|
||||
passwordManagerSeats?: number;
|
||||
subscribeToSecretsManager?: boolean;
|
||||
isFromSecretsManagerTrial?: boolean;
|
||||
secretsManagerSeats?: number;
|
||||
secretsManagerServiceAccounts?: number;
|
||||
storage?: number;
|
||||
};
|
||||
|
||||
export type BillingInformation = {
|
||||
postalCode: string;
|
||||
country: string;
|
||||
taxId?: string;
|
||||
addressLine1?: string;
|
||||
addressLine2?: string;
|
||||
city?: string;
|
||||
state?: string;
|
||||
};
|
||||
|
||||
export type PaymentInformation = {
|
||||
paymentMethod: [string, PaymentMethodType];
|
||||
billing: BillingInformation;
|
||||
};
|
||||
|
||||
export type SubscriptionInformation = {
|
||||
organization: OrganizationInformation;
|
||||
plan?: PlanInformation;
|
||||
payment?: PaymentInformation;
|
||||
};
|
||||
|
||||
export abstract class OrganizationBillingServiceAbstraction {
|
||||
purchaseSubscription: (subscription: SubscriptionInformation) => Promise<OrganizationResponse>;
|
||||
|
||||
startFree: (subscription: SubscriptionInformation) => Promise<OrganizationResponse>;
|
||||
}
|
||||
@@ -40,17 +40,13 @@ export class BillingCustomerDiscount extends BaseResponse {
|
||||
id: string;
|
||||
active: boolean;
|
||||
percentOff?: number;
|
||||
appliesTo: string[];
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
this.id = this.getResponseProperty("Id");
|
||||
this.active = this.getResponseProperty("Active");
|
||||
this.percentOff = this.getResponseProperty("PercentOff");
|
||||
this.appliesTo = this.getResponseProperty("AppliesTo");
|
||||
}
|
||||
|
||||
discountPrice = (price: number) => {
|
||||
const discount = this !== null && this.active ? price * (this.percentOff / 100) : 0;
|
||||
|
||||
return price - discount;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ export class BillingSubscriptionResponse extends BaseResponse {
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
this.trialEndDate = this.getResponseProperty("TrialStartDate");
|
||||
this.trialStartDate = this.getResponseProperty("TrialStartDate");
|
||||
this.trialEndDate = this.getResponseProperty("TrialEndDate");
|
||||
this.periodStartDate = this.getResponseProperty("PeriodStartDate");
|
||||
this.periodEndDate = this.getResponseProperty("PeriodEndDate");
|
||||
@@ -55,6 +55,7 @@ export class BillingSubscriptionResponse extends BaseResponse {
|
||||
}
|
||||
|
||||
export class BillingSubscriptionItemResponse extends BaseResponse {
|
||||
productId: string;
|
||||
name: string;
|
||||
amount: number;
|
||||
quantity: number;
|
||||
@@ -65,6 +66,7 @@ export class BillingSubscriptionItemResponse extends BaseResponse {
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
this.productId = this.getResponseProperty("ProductId");
|
||||
this.name = this.getResponseProperty("Name");
|
||||
this.amount = this.getResponseProperty("Amount");
|
||||
this.quantity = this.getResponseProperty("Quantity");
|
||||
|
||||
143
libs/common/src/billing/services/organization-billing.service.ts
Normal file
143
libs/common/src/billing/services/organization-billing.service.ts
Normal file
@@ -0,0 +1,143 @@
|
||||
import { OrganizationApiServiceAbstraction as OrganizationApiService } from "../../admin-console/abstractions/organization/organization-api.service.abstraction";
|
||||
import { OrganizationCreateRequest } from "../../admin-console/models/request/organization-create.request";
|
||||
import { OrganizationKeysRequest } from "../../admin-console/models/request/organization-keys.request";
|
||||
import { OrganizationResponse } from "../../admin-console/models/response/organization.response";
|
||||
import { CryptoService } from "../../platform/abstractions/crypto.service";
|
||||
import { EncryptService } from "../../platform/abstractions/encrypt.service";
|
||||
import { I18nService } from "../../platform/abstractions/i18n.service";
|
||||
import { EncString } from "../../platform/models/domain/enc-string";
|
||||
import { OrgKey } from "../../types/key";
|
||||
import {
|
||||
OrganizationBillingServiceAbstraction,
|
||||
OrganizationInformation,
|
||||
PaymentInformation,
|
||||
PlanInformation,
|
||||
SubscriptionInformation,
|
||||
} from "../abstractions/organization-billing.service";
|
||||
import { PlanType } from "../enums";
|
||||
|
||||
interface OrganizationKeys {
|
||||
encryptedKey: EncString;
|
||||
publicKey: string;
|
||||
encryptedPrivateKey: EncString;
|
||||
encryptedCollectionName: EncString;
|
||||
}
|
||||
|
||||
export class OrganizationBillingService implements OrganizationBillingServiceAbstraction {
|
||||
constructor(
|
||||
private cryptoService: CryptoService,
|
||||
private encryptService: EncryptService,
|
||||
private i18nService: I18nService,
|
||||
private organizationApiService: OrganizationApiService,
|
||||
) {}
|
||||
|
||||
async purchaseSubscription(subscription: SubscriptionInformation): Promise<OrganizationResponse> {
|
||||
const request = new OrganizationCreateRequest();
|
||||
|
||||
const organizationKeys = await this.makeOrganizationKeys();
|
||||
|
||||
this.setOrganizationKeys(request, organizationKeys);
|
||||
|
||||
this.setOrganizationInformation(request, subscription.organization);
|
||||
|
||||
this.setPlanInformation(request, subscription.plan);
|
||||
|
||||
this.setPaymentInformation(request, subscription.payment);
|
||||
|
||||
return await this.organizationApiService.create(request);
|
||||
}
|
||||
|
||||
async startFree(subscription: SubscriptionInformation): Promise<OrganizationResponse> {
|
||||
const request = new OrganizationCreateRequest();
|
||||
|
||||
const organizationKeys = await this.makeOrganizationKeys();
|
||||
|
||||
this.setOrganizationKeys(request, organizationKeys);
|
||||
|
||||
this.setOrganizationInformation(request, subscription.organization);
|
||||
|
||||
this.setPlanInformation(request, subscription.plan);
|
||||
|
||||
return await this.organizationApiService.create(request);
|
||||
}
|
||||
|
||||
private async makeOrganizationKeys(): Promise<OrganizationKeys> {
|
||||
const [encryptedKey, key] = await this.cryptoService.makeOrgKey<OrgKey>();
|
||||
const [publicKey, encryptedPrivateKey] = await this.cryptoService.makeKeyPair(key);
|
||||
const encryptedCollectionName = await this.encryptService.encrypt(
|
||||
this.i18nService.t("defaultCollection"),
|
||||
key,
|
||||
);
|
||||
return {
|
||||
encryptedKey,
|
||||
publicKey,
|
||||
encryptedPrivateKey,
|
||||
encryptedCollectionName,
|
||||
};
|
||||
}
|
||||
|
||||
private setOrganizationInformation(
|
||||
request: OrganizationCreateRequest,
|
||||
information: OrganizationInformation,
|
||||
): void {
|
||||
request.name = information.name;
|
||||
request.businessName = information.businessName;
|
||||
request.billingEmail = information.billingEmail;
|
||||
}
|
||||
|
||||
private setOrganizationKeys(request: OrganizationCreateRequest, keys: OrganizationKeys): void {
|
||||
request.key = keys.encryptedKey.encryptedString;
|
||||
request.keys = new OrganizationKeysRequest(
|
||||
keys.publicKey,
|
||||
keys.encryptedPrivateKey.encryptedString,
|
||||
);
|
||||
request.collectionName = keys.encryptedCollectionName.encryptedString;
|
||||
}
|
||||
|
||||
private setPaymentInformation(
|
||||
request: OrganizationCreateRequest,
|
||||
information: PaymentInformation,
|
||||
) {
|
||||
const [paymentToken, paymentMethodType] = information.paymentMethod;
|
||||
request.paymentToken = paymentToken;
|
||||
request.paymentMethodType = paymentMethodType;
|
||||
|
||||
const billingInformation = information.billing;
|
||||
request.billingAddressPostalCode = billingInformation.postalCode;
|
||||
request.billingAddressCountry = billingInformation.country;
|
||||
|
||||
if (billingInformation.taxId) {
|
||||
request.taxIdNumber = billingInformation.taxId;
|
||||
request.billingAddressLine1 = billingInformation.addressLine1;
|
||||
request.billingAddressLine2 = billingInformation.addressLine2;
|
||||
request.billingAddressCity = billingInformation.city;
|
||||
request.billingAddressState = billingInformation.state;
|
||||
}
|
||||
}
|
||||
|
||||
private setPlanInformation(
|
||||
request: OrganizationCreateRequest,
|
||||
information: PlanInformation,
|
||||
): void {
|
||||
request.planType = information.type;
|
||||
|
||||
if (request.planType === PlanType.Free) {
|
||||
request.useSecretsManager = information.subscribeToSecretsManager;
|
||||
request.isFromSecretsManagerTrial = information.isFromSecretsManagerTrial;
|
||||
return;
|
||||
}
|
||||
|
||||
request.additionalSeats = information.passwordManagerSeats;
|
||||
|
||||
if (information.subscribeToSecretsManager) {
|
||||
request.useSecretsManager = true;
|
||||
request.isFromSecretsManagerTrial = information.isFromSecretsManagerTrial;
|
||||
request.additionalSmSeats = information.secretsManagerSeats;
|
||||
request.additionalServiceAccounts = information.secretsManagerServiceAccounts;
|
||||
}
|
||||
|
||||
if (information.storage) {
|
||||
request.additionalStorageGb = information.storage;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user