1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-10 13:23:34 +00:00

[PM-8161] Payment optional trial MVP (#10872)

* Initial comment

* Add changes for the create org with payment method

* Add the secrets manager trail flow

* Add the banners

* Add changes for the Disabled Org

* Add banner to payment method page

* Refactoring changes

* Resolve the bug on tha payment method

* Resolve lint error

* Resolve Pr comments

* resolve the lint issue

* Resolve the lint wrong file issue

* Rename object properly

* Resolve pr comments from sm team

* Resolve the pr comments from sm team

* Fix the failing test

* Resolve some issue with vault

* Resolve the comments from sm team

* Resolve some pr comments from vault team

* Resolve pr comments from auth team

* Exported ValidOrgParams enum

* Removed unnecessary interpolation

* Corrected bit-banner id for trial

* Resolve pr comments from auth team

* Resolve pr comments from auth team

* Removed unnecessary method

* Made OrganizationCreateRequest a subtype of OrganizationNoPaymentMethodCreateRequest

* Resolve review changes from sm

* Resolve review changes from dm

* Resolve the pr comments from billing

* move the free-trial to core

* Move free-trial change to right file

* Revert changes on the free trial  page

* Resolve the comment on protected trial page

* Resolve the comment on protected trial page

* Revert the next async change

* resolve pr comment fro vault team

* resolve the default message comments

* remove unused method

* resolve email sending issue

* Fix the pop issue on payment method

* Fix some console errors

* Fix the pop refresh page

* move the trial services to billing folder

* resolve pr comments

* Resolve the import issues

* Move the observable up

* Resolve blank payment method for trialing org

* Changes to  disable icon is removed onsubmit

* Remove unused references

* add a missing a period at the end of it

* resolve the reload issue

* Resolve the disable icon issue

* Fix the admin access bug

* Resolve the lint issue

* Fix the message incorrect format

* Formatting fixed

* Resolve the access issue of other users role
This commit is contained in:
cyprain-okeke
2024-11-11 17:05:37 +01:00
committed by GitHub
parent 888b9e346c
commit f593269133
39 changed files with 971 additions and 81 deletions

View File

@@ -6,6 +6,7 @@ import { SecretVerificationRequest } from "../../../auth/models/request/secret-v
import { ApiKeyResponse } from "../../../auth/models/response/api-key.response";
import { OrganizationSsoResponse } from "../../../auth/models/response/organization-sso.response";
import { ExpandedTaxInfoUpdateRequest } from "../../../billing/models/request/expanded-tax-info-update.request";
import { OrganizationNoPaymentMethodCreateRequest } from "../../../billing/models/request/organization-no-payment-method-create-request";
import { OrganizationSmSubscriptionUpdateRequest } from "../../../billing/models/request/organization-sm-subscription-update.request";
import { OrganizationSubscriptionUpdateRequest } from "../../../billing/models/request/organization-subscription-update.request";
import { PaymentRequest } from "../../../billing/models/request/payment.request";
@@ -40,6 +41,9 @@ export class OrganizationApiServiceAbstraction {
getLicense: (id: string, installationId: string) => Promise<unknown>;
getAutoEnrollStatus: (identifier: string) => Promise<OrganizationAutoEnrollStatusResponse>;
create: (request: OrganizationCreateRequest) => Promise<OrganizationResponse>;
createWithoutPayment: (
request: OrganizationNoPaymentMethodCreateRequest,
) => Promise<OrganizationResponse>;
createLicense: (data: FormData) => Promise<OrganizationResponse>;
save: (id: string, request: OrganizationUpdateRequest) => Promise<OrganizationResponse>;
updatePayment: (id: string, request: PaymentRequest) => Promise<void>;

View File

@@ -1,32 +1,7 @@
import { PaymentMethodType, PlanType } from "../../../billing/enums";
import { InitiationPath } from "../../../models/request/reference-event.request";
import { PaymentMethodType } from "../../../billing/enums";
import { OrganizationNoPaymentMethodCreateRequest } from "../../../billing/models/request/organization-no-payment-method-create-request";
import { OrganizationKeysRequest } from "./organization-keys.request";
export class OrganizationCreateRequest {
name: string;
businessName: string;
billingEmail: string;
planType: PlanType;
key: string;
keys: OrganizationKeysRequest;
export class OrganizationCreateRequest extends OrganizationNoPaymentMethodCreateRequest {
paymentMethodType: PaymentMethodType;
paymentToken: string;
additionalSeats: number;
maxAutoscaleSeats: number;
additionalStorageGb: number;
premiumAccessAddon: boolean;
collectionName: string;
taxIdNumber: string;
billingAddressLine1: string;
billingAddressLine2: string;
billingAddressCity: string;
billingAddressState: string;
billingAddressPostalCode: string;
billingAddressCountry: string;
useSecretsManager: boolean;
additionalSmSeats: number;
additionalServiceAccounts: number;
isFromSecretsManagerTrial: boolean;
initiationPath: InitiationPath;
}

View File

@@ -7,6 +7,7 @@ import { SecretVerificationRequest } from "../../../auth/models/request/secret-v
import { ApiKeyResponse } from "../../../auth/models/response/api-key.response";
import { OrganizationSsoResponse } from "../../../auth/models/response/organization-sso.response";
import { ExpandedTaxInfoUpdateRequest } from "../../../billing/models/request/expanded-tax-info-update.request";
import { OrganizationNoPaymentMethodCreateRequest } from "../../../billing/models/request/organization-no-payment-method-create-request";
import { OrganizationSmSubscriptionUpdateRequest } from "../../../billing/models/request/organization-sm-subscription-update.request";
import { OrganizationSubscriptionUpdateRequest } from "../../../billing/models/request/organization-subscription-update.request";
import { PaymentRequest } from "../../../billing/models/request/payment.request";
@@ -107,6 +108,21 @@ export class OrganizationApiService implements OrganizationApiServiceAbstraction
return new OrganizationResponse(r);
}
async createWithoutPayment(
request: OrganizationNoPaymentMethodCreateRequest,
): Promise<OrganizationResponse> {
const r = await this.apiService.send(
"POST",
"/organizations/create-without-payment",
request,
true,
true,
);
// Forcing a sync will notify organization service that they need to repull
await this.syncService.fullSync(true);
return new OrganizationResponse(r);
}
async createLicense(data: FormData): Promise<OrganizationResponse> {
const r = await this.apiService.send(
"POST",

View File

@@ -44,4 +44,8 @@ export abstract class OrganizationBillingServiceAbstraction {
purchaseSubscription: (subscription: SubscriptionInformation) => Promise<OrganizationResponse>;
startFree: (subscription: SubscriptionInformation) => Promise<OrganizationResponse>;
purchaseSubscriptionNoPaymentMethod: (
subscription: SubscriptionInformation,
) => Promise<OrganizationResponse>;
}

View File

@@ -0,0 +1,29 @@
import { OrganizationKeysRequest } from "../../../admin-console/models/request/organization-keys.request";
import { InitiationPath } from "../../../models/request/reference-event.request";
import { PlanType } from "../../enums";
export class OrganizationNoPaymentMethodCreateRequest {
name: string;
businessName: string;
billingEmail: string;
planType: PlanType;
key: string;
keys: OrganizationKeysRequest;
additionalSeats: number;
maxAutoscaleSeats: number;
additionalStorageGb: number;
premiumAccessAddon: boolean;
collectionName: string;
taxIdNumber: string;
billingAddressLine1: string;
billingAddressLine2: string;
billingAddressCity: string;
billingAddressState: string;
billingAddressPostalCode: string;
billingAddressCountry: string;
useSecretsManager: boolean;
additionalSmSeats: number;
additionalServiceAccounts: number;
isFromSecretsManagerTrial: boolean;
initiationPath: InitiationPath;
}

View File

@@ -4,11 +4,13 @@ export class OrganizationBillingMetadataResponse extends BaseResponse {
isEligibleForSelfHost: boolean;
isManaged: boolean;
isOnSecretsManagerStandalone: boolean;
isSubscriptionUnpaid: boolean;
constructor(response: any) {
super(response);
this.isEligibleForSelfHost = this.getResponseProperty("IsEligibleForSelfHost");
this.isManaged = this.getResponseProperty("IsManaged");
this.isOnSecretsManagerStandalone = this.getResponseProperty("IsOnSecretsManagerStandalone");
this.isSubscriptionUnpaid = this.getResponseProperty("IsSubscriptionUnpaid");
}
}

View File

@@ -17,6 +17,7 @@ import {
SubscriptionInformation,
} from "../abstractions/organization-billing.service";
import { PlanType } from "../enums";
import { OrganizationNoPaymentMethodCreateRequest } from "../models/request/organization-no-payment-method-create-request";
interface OrganizationKeys {
encryptedKey: EncString;
@@ -77,6 +78,28 @@ export class OrganizationBillingService implements OrganizationBillingServiceAbs
return response;
}
async purchaseSubscriptionNoPaymentMethod(
subscription: SubscriptionInformation,
): Promise<OrganizationResponse> {
const request = new OrganizationNoPaymentMethodCreateRequest();
const organizationKeys = await this.makeOrganizationKeys();
this.setOrganizationKeys(request, organizationKeys);
this.setOrganizationInformation(request, subscription.organization);
this.setPlanInformation(request, subscription.plan);
const response = await this.organizationApiService.createWithoutPayment(request);
await this.apiService.refreshIdentityToken();
await this.syncService.fullSync(true);
return response;
}
private async makeOrganizationKeys(): Promise<OrganizationKeys> {
const [encryptedKey, key] = await this.keyService.makeOrgKey<OrgKey>();
const [publicKey, encryptedPrivateKey] = await this.keyService.makeKeyPair(key);
@@ -106,7 +129,7 @@ export class OrganizationBillingService implements OrganizationBillingServiceAbs
}
private setOrganizationInformation(
request: OrganizationCreateRequest,
request: OrganizationCreateRequest | OrganizationNoPaymentMethodCreateRequest,
information: OrganizationInformation,
): void {
request.name = information.name;
@@ -115,7 +138,10 @@ export class OrganizationBillingService implements OrganizationBillingServiceAbs
request.initiationPath = information.initiationPath;
}
private setOrganizationKeys(request: OrganizationCreateRequest, keys: OrganizationKeys): void {
private setOrganizationKeys(
request: OrganizationCreateRequest | OrganizationNoPaymentMethodCreateRequest,
keys: OrganizationKeys,
): void {
request.key = keys.encryptedKey.encryptedString;
request.keys = new OrganizationKeysRequest(
keys.publicKey,
@@ -146,7 +172,7 @@ export class OrganizationBillingService implements OrganizationBillingServiceAbs
}
private setPlanInformation(
request: OrganizationCreateRequest,
request: OrganizationCreateRequest | OrganizationNoPaymentMethodCreateRequest,
information: PlanInformation,
): void {
request.planType = information.type;

View File

@@ -38,6 +38,7 @@ export enum FeatureFlag {
Pm13322AddPolicyDefinitions = "pm-13322-add-policy-definitions",
LimitCollectionCreationDeletionSplit = "pm-10863-limit-collection-creation-deletion-split",
CriticalApps = "pm-14466-risk-insights-critical-application",
TrialPaymentOptional = "PM-8163-trial-payment",
}
export type AllowedFeatureFlagTypes = boolean | number | string;
@@ -86,6 +87,7 @@ export const DefaultFeatureFlagValue = {
[FeatureFlag.Pm13322AddPolicyDefinitions]: FALSE,
[FeatureFlag.LimitCollectionCreationDeletionSplit]: FALSE,
[FeatureFlag.CriticalApps]: FALSE,
[FeatureFlag.TrialPaymentOptional]: FALSE,
} satisfies Record<FeatureFlag, AllowedFeatureFlagTypes>;
export type DefaultFeatureFlagValueType = typeof DefaultFeatureFlagValue;