import { ApiService } from "../../abstractions/api.service"; 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 { SyncService } from "../../vault/abstractions/sync/sync.service.abstraction"; import { BillingApiServiceAbstraction as BillingApiService } from "../abstractions/billilng-api.service.abstraction"; 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 apiService: ApiService, private billingApiService: BillingApiService, private cryptoService: CryptoService, private encryptService: EncryptService, private i18nService: I18nService, private organizationApiService: OrganizationApiService, private syncService: SyncService, ) {} async isOnSecretsManagerStandalone(organizationId: string): Promise { const response = await this.billingApiService.getOrganizationSubscription(organizationId); if (response.customerDiscount?.id === "sm-standalone") { const productIds = response.subscription.items.map((item) => item.productId); return ( response.customerDiscount?.appliesTo.filter((appliesToProductId) => productIds.includes(appliesToProductId), ).length > 0 ); } return false; } async purchaseSubscription(subscription: SubscriptionInformation): Promise { 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); const response = await this.organizationApiService.create(request); await this.apiService.refreshIdentityToken(); await this.syncService.fullSync(true); return response; } async startFree(subscription: SubscriptionInformation): Promise { const request = new OrganizationCreateRequest(); const organizationKeys = await this.makeOrganizationKeys(); this.setOrganizationKeys(request, organizationKeys); this.setOrganizationInformation(request, subscription.organization); this.setPlanInformation(request, subscription.plan); const response = await this.organizationApiService.create(request); await this.apiService.refreshIdentityToken(); await this.syncService.fullSync(true); return response; } private async makeOrganizationKeys(): Promise { const [encryptedKey, key] = await this.cryptoService.makeOrgKey(); 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 prohibitsAdditionalSeats(planType: PlanType) { switch (planType) { case PlanType.Free: case PlanType.FamiliesAnnually: case PlanType.FamiliesAnnually2019: case PlanType.TeamsStarter2023: case PlanType.TeamsStarter: return true; default: return false; } } private setOrganizationInformation( request: OrganizationCreateRequest, information: OrganizationInformation, ): void { request.name = information.name; request.businessName = information.businessName; request.billingEmail = information.billingEmail; request.initiationPath = information.initiationPath; } 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 (this.prohibitsAdditionalSeats(request.planType)) { 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; } } }