Commit Graph

143 Commits

Author SHA1 Message Date
Stephon Brown
5d96a614ba [PM-37820] Update Missing Tax Logic for US Customers (#7719)
* fix(billing): guard US customers from missing tax ID warning when automatic tax flag is enabled

  US has no customer-facing VAT/Tax ID equivalent, so the warning should never appear for US customers regardless of the PM37597 flag state.

* fix(billing): fix provider warnings test asserting buggy US tax ID warning behavior
2026-05-27 11:27:13 -04:00
cyprain-okeke
d07a941896 [PM-37092] feat: Add business plan migration handler to SubscriptionUpdatedHandler (#7707)
* feat(billing): add Organization.ChangePlan extension for structural plan shape

Pure helper that writes plan-derived structural columns (PlanType, Plan,
Use* capability flags, UsersGetPremium, MaxCollections) without touching
customer-purchase columns. Preserves the existing UseKeyConnector carve-out.
Behavior-preserving extraction of the field-copy block at lines 287-310
of UpgradeOrganizationPlanCommand.UpgradePlanAsync.

* refactor(billing): use Organization.ChangePlan in UpgradePlanAsync

Replaces the inline structural field-copy block with a call to the new
ChangePlan helper. Customer-purchase columns (Seats, MaxStorageGb,
Enabled, UseSecretsManager) and the PremiumAccessAddon override on
UsersGetPremium stay inline at the call site. Behavior-preserving.

* feat(billing): register Teams 2020 -> current migration paths

Appends Teams2020AnnualToCurrent (byte 3) and Teams2020MonthlyToCurrent
(byte 4) to MigrationPathId and the MigrationPaths registry. Required for
the business migration handler to resolve cohorts mapped to Teams source
plans; without these entries MigrationPaths.FromId returns null and the
handler no-ops on Teams orgs in Cohort A1. Snapshot tests updated.

* chore(billing): inject migration cohort repositories into SubscriptionUpdatedHandler

Adds IOrganizationPlanMigrationCohortRepository and
IOrganizationPlanMigrationCohortAssignmentRepository as constructor
dependencies in preparation for HandleScheduleTriggeredBusinessMigrationAsync.
No behavior change.

* feat(billing): scaffold business plan Phase-2 migration handler

Adds HandleScheduleTriggeredBusinessMigrationAsync as a sibling call in
HandleAsync's organization branch, gated on PM35215_BusinessPlanPriceMigration.
Initial implementation short-circuits when ScheduleId is null. Locks the
no-op behaviour with NoScheduleId + FeatureFlagOff tests. Full handler body
lands in subsequent commits.

* feat(billing): gate business migration handler on registered source price IDs

Builds the source-price allowlist from MigrationPaths.All, using the
seat-vs-non-seat pattern (HasNonSeatBasedPasswordManagerPlan). All four
Track A 2020 plans register automatically. Skips when the previous
subscription items don't include any registered 2020 source price.

* feat(billing): resolve cohort via assignment row for business migration handler

Reads assignment by organization id (DB is source of truth), then resolves
the cohort and migration path. Stripe subscription.Metadata['migration_cohort_id']
remains stamped by PriceIncreaseScheduler for dashboard attribution but is
not consulted by the handler. Skips with a warning when the assignment is
missing, the cohort is missing, or MigrationPathId references an unregistered
path. Idempotent: skips with info-level log when assignment.MigratedDate is
already set, before any further DB reads.

* feat(billing): defensive target-price sanity check for business migration

After resolving the target plan from cohort.MigrationPath.ToPlan, verifies
the current subscription items contain the target's PM price ID (seat-aware).
Skips with a warning on mismatch to protect against operator data errors or
off-path schedule transitions.

* feat(billing): apply plan shape and mark assignment migrated on Phase 2

Completes HandleScheduleTriggeredBusinessMigrationAsync: loads the org,
calls Organization.ChangePlan(targetPlan), persists via ReplaceAsync, and
sets assignment.MigratedDate + RevisionDate before persisting the
assignment. Happy-path coverage for all four Track A pairs (Teams +
Enterprise, monthly + annual). Teams tests assert the UseScim flip - the
load-bearing capability gain for Teams 2020 -> current.

* Add more unit test

* fix: add UTF-8 BOM to .cs files for editorconfig charset compliance

* Add exception handle

* Code refactoring

* Add more unit testing

* Resolve the pr comment
2026-05-27 10:51:44 +00:00
Stephon Brown
ee07462d28 [PM-37084] Business Aware Schedule Recovery and Cancellation (#7686)
* feat(billing): introduce unified subscription price increase scheduler API

* feat(billing): implement unified subscription price increase scheduler logic

* refactor(billing): update subscription handlers to use unified scheduler

* feat(billing): extend price migration feature flag checks

* test(billing): add and update tests for unified price increase scheduler

* fix(billing): run dotnet format

* feat(billing): expand customer and customer.discount on subscription fetch

* refactor(ReinstateSubscriptionCommandTests): rename test method for broader scope

* feat(billing): expand customer.discount in update handler

* test(billing): update test name

* feat(billing): add test clock waiting mechanism for upcoming invoices

* feat(billing): introduce cancelling user ID metadata key

* feat(billing): store cancelling user ID on subscription cancellation

* feat(billing): clear cancelling user ID on subscription reinstatement

* test(billing): update subscriber service tests for cancelling user ID

* style(SubscriberService): use 'is not null' pattern matching

* feat(SubscriberService): add PM35215 migration cohort metadata handling

* feat(SubscriberService): extend price migration deferral to PM35215

* test(SubscriberService): add and update tests for PM35215 feature

* feat(billing): Introduce OrganizationPriceIncreaseOptions

* refactor(billing): Centralize price increase eligibility in scheduler

* refactor(billing): Delegate price increase validation from UpcomingInvoiceHandler

* feat(billing): Manage price increase schedules during subscription lifecycle events

* test(billing): Update UpcomingInvoiceHandlerTests for centralized validation

* test(billing): Add PriceIncreaseScheduler tests for SkipIfAlreadyScheduled option

* test(billing): Add SubscriberService tests for price increase schedule management

* fix(billing): run dotnet format

* fix(billing): remove redundant customer expansion

* fix(billing): expand discounts for customer and subscription

* refactor(billing): Rename method to clarify dispatching role for organization scheduling

* fix(billing): Prevent clearing migration cohort metadata on cancellation

* fix(billing): Fallback to standard email when price increase migration fails

* feat(billing): improve observability for missing migration path data

* refactor(billing): simplify business plan type identification
2026-05-26 17:16:18 -04:00
cyprain-okeke
81bec7cfff [PM 35227](server) Extend checkout endpoint for browser/desktop platforms (#7550)
* Implementation desktop and browser checkout

* Fixed the failing test

* Add a logger to see gobal settings in qa

* Add log

* fix the lint error

* Removed the log
2026-05-26 15:12:20 +01:00
Alex Morask
b1395aafbe [PM-37083] feat: Add per-phase price resolution to UpdateOrganizationSubscriptionCommand (#7695)
* [PM-37083] feat: Add per-phase price resolution to UpdateOrganizationSubscriptionCommand

Resolve source vs. target plan pricing per schedule phase so item changes
target the correct phase-specific price ID. Move cohort metadata onto the
schedule phases themselves to avoid Stripe normalization triggered by
direct subscription metadata updates. Filter the schedule-aware update
path to phases where EndDate > now, and drop the feature-flag gate on
PriceIncreaseScheduler.Release so schedule existence is the gate.

* Add defensive guard for source-priced single-phase migration schedules
2026-05-22 11:32:13 -05:00
cyprain-okeke
761d8d055a [PM-36964] Add per-org migration cohort assignment to the Admin portal (#7681)
* [PM-36949] Add OrganizationPlanMigrationCohort schema and Core domain types

Add the foundation for cohort-based plan migrations:
- Two tables: OrganizationPlanMigrationCohort and OrganizationPlanMigrationCohortAssignment
- Two views and nine stored procedures (four CRUD on cohort, four CRUD plus
  ReadByOrganizationId on assignment)
- Single Migrator script for MSSQL deployment
- Core entities, MigrationPath value object and its registry, and bare repository
  interfaces under Bit.Core.Billing.Organizations.PlanMigration

The cohort table holds the human-managed metadata (name, discount coupons,
MigrationPathId byte) and the assignment table records each organization's
position in the migration lifecycle (scheduled, migrated, churn-mitigated).
Both Update SPs follow the accept-but-don't-assign pattern: immutable columns
(OrganizationId, CohortId, CreatedAt) are parameters but not SET clauses.

* [PM-36949] Add Dapper repositories for plan migration cohort tables

OrganizationPlanMigrationCohortRepository inherits the base Repository<T, TId>
CRUD methods unchanged. OrganizationPlanMigrationCohortAssignmentRepository
also relies on the base for CRUD and adds GetByOrganizationIdAsync which
returns at most one row (the UNIQUE constraint on OrganizationId at the
database layer guarantees this).

* [PM-36949] Add EF Core configurations, repositories, and provider migrations for plan migration cohort tables

- EF models wrap the Core entities; the assignment model exposes nav properties
  for Organization and Cohort so the FK + cascade-delete is inferred by EF.
- EntityTypeConfiguration classes pin ID generation to application code
  (ValueGeneratedNever) and declare the UNIQUE indexes plus the composite
  (CohortId, ScheduledAt, MigratedAt) index.
- Repositories follow the OrganizationInstallationRepository template; the
  assignment repo adds GetByOrganizationIdAsync to mirror the SP exposed on
  the MSSQL side.
- DatabaseContext gets two DbSet properties; auto-discovery picks up the
  configuration classes.
- Generated migrations for MySQL, Postgres, and SQLite create matching schemas;
  EF truncates FK and index names on providers with 64-char identifier limits,
  which is consistent with the rest of the codebase.

* [PM-36949] Wire up DI and add tests for plan migration cohort repositories

Register both Dapper and EF Core repositories in their respective service
collection extensions, following the existing AddSingleton convention in
these files.

Add tests:
- MigrationPathIdsSnapshotTests guards the immortal byte IDs that downstream
  code pins on. The class- and method-level comments document why these
  values can never be renumbered.
- MigrationPathTests covers the FromId round-trip and the null-on-unknown
  behavior the registry promises to callers.
- OrganizationPlanMigrationCohortRepositoryTests exercises CRUD, the UNIQUE
  Name constraint, and verifies that ReplaceAsync ignores CreatedAt
  mutations (per the accept-but-don't-assign Update SP).
- OrganizationPlanMigrationCohortAssignmentRepositoryTests exercises CRUD,
  GetByOrganizationIdAsync, the UNIQUE OrganizationId constraint,
  cascade-delete from both Organization and Cohort, and verifies that
  ReplaceAsync ignores OrganizationId, CohortId, and CreatedAt mutations.

* [PM-36949] Use PlanType and MigrationPathId enums on MigrationPath

Replace the byte Id with a byte-backed MigrationPathId enum and replace
the string FromPlan/ToPlan fields with PlanType. Persistence is
unchanged -- EF normalises enum-backed properties to their underlying
type in the model snapshot, and Dapper handles enum-to-byte parameter
mapping automatically.

* [PM-36949] Add *.lscache to .gitignore

* [PM-36949] fix: Override ReplaceAsync on EF cohort repositories for immutability parity

The Dapper _Update SPs accept-but-don't-assign certain columns (CreatedAt
on cohort; OrganizationId, CohortId, and CreatedAt on assignment), but
the base EF Repository<T,TEntity,Guid>.ReplaceAsync uses SetValues which
writes every scalar. Override on both repos and mark the immutable
properties as IsModified = false so MySQL/Postgres/Sqlite match MSSQL
behavior. Mirrors the existing DeviceRepository.ReplaceAsync pattern.

* [PM-36949] fix: Bound cohort string columns and widen Name to 255 chars

Add [MaxLength] attributes to the three cohort string properties so the
EF providers (MySQL/Postgres/Sqlite) enforce the same limits as MSSQL,
where the columns were already NVARCHAR-capped. Widen Name from 64 to
255 chars across MSSQL DDL, both _Create/_Update SP signatures, the
Migrator script, the entity, and all three regenerated EF migrations.
Coupon codes stay at 64 (Stripe IDs are short).

* [PM-36949] test: Lock FromPlan and ToPlan per MigrationPath

The snapshot test class doc says "byte N means a specific FromPlan ->
ToPlan transition forever", but only the byte value was being asserted.
A silent refactor of the registry's PlanType references would not have
been caught. Add per-path FromPlan/ToPlan assertions to close the gap.

* [PM-36949] chore: Apply dotnet format

* [PM-36949] test: Use LaxDateTimeComparer for round-tripped DateTimes

Postgres timestamp and MySQL datetime(6) store microsecond precision (6
fractional digits), but .NET DateTime is 100ns ticks (7 digits). Exact
Assert.Equal fails by a single tick on round-trip. Switch the three
DateTime comparisons in ReplaceAsync_UpdatesMutableColumns_AndIgnoresImmutableOnes
to LaxDateTimeComparer.Default -- the same 2ms-tolerance comparer used
by SendRepositoryTests and InstallationRepositoryTests for the same
precision issue.

* Add implementation for dropdown

* Reconcile dropdown work with renamed PM-36949 schema

After merging origin/main, the cohort schema now uses *Date suffixes
(ScheduledDate / MigratedDate / ChurnDiscountAppliedDate / CreationDate).
Rename references in the dropdown work, restore IsLocked() on the merged
entity, and re-add GetManyAsync() to the cohort repository (interface +
Dapper + EF) since the merge took main's pre-dropdown versions.

Also remove the six superseded provider migration files (20260515*) -- the
20260518* renames from origin/main are now the only cohort migrations.

* Fix UTF-8 BOM on cohort assignment unit test file

* Resolve the FF and permission issue

* Extract migration cohort resolution into a helper

Addresses PR feedback to keep the Edit action focused: the cohort
resolution/validation now lives in ResolveMigrationCohortAssignmentChangeAsync
and returns a MigrationCohortAssignmentChange record. The endpoint still
owns the page return.

---------

Co-authored-by: Alex Morask <amorask@bitwarden.com>
Co-authored-by: Alex Morask <144709477+amorask-bitwarden@users.noreply.github.com>
2026-05-21 15:17:00 +01:00
Alex Morask
34d4b6b3f6 [PM-37068] feat: Add business plan cohort branch to UpcomingInvoiceHandler (#7678)
* [PM-37068] feat: Add business plan cohort branch to UpcomingInvoiceHandler

Wires the caller side of the Teams/Enterprise 2020 → current price
migration. AlignOrganizationSubscriptionConcernsAsync becomes a
ProductTier dispatcher; the new business branch loads the cohort
assignment, runs eligibility guards (assignment unscheduled, cohort
active, org PlanType matches cohort source), and invokes PM-37064's
ScheduleBusinessPriceIncrease. The renewal email is a placeholder until
PM-37070 lands.

Also expands subscriptions.data.customer on the customer load so
PM-37064 can preserve customer-level discounts into Phase 2.

Incidentally strips pre-existing #region markers in
UpcomingInvoiceHandlerTests.cs per the no-regions rule.

* Apply review feedback: gate FF in handler, split MigrationPath checks, silence scheduler churn-only warning

* Add cohort metadata to subscription on migration schedule

---------

Co-authored-by: cyprain-okeke <108260115+cyprain-okeke@users.noreply.github.com>
2026-05-20 11:40:50 -05:00
Stephon Brown
779320aabe [PM-37598] Update Tax Id Warning Logic with Feature Flag (#7677)
* feat(billing): inject feature service into billing warning queries

* test(billing): add provider tax warning tests for automatic tax flag

* test(billing): add organization tax warning tests for automatic tax flag

* feat(billing): modify provider tax id warning based on automatic tax feature flag

* feat(billing): modify organization tax id warning based on automatic tax feature flag

* refactor(billing): clean up unused usings and file encoding

* test(billing): add tax id verification warnings for providers

* test(billing): add tax id verification warnings for organizations
2026-05-20 10:02:51 -04:00
Alex Morask
466bf01148 [PM-37064] feat: Add ScheduleBusinessPriceIncrease scheduler entry point (#7665)
* [PM-37064] feat: Add ScheduleBusinessPriceIncrease scheduler entry point

Extends PriceIncreaseScheduler (PM-32645) with a parallel entry point for
Teams/Enterprise 2020 → current plan migrations under epic PM-35215. The
business path consumes a cohort directly, preserves existing discounts,
optionally appends the cohort's proactive coupon, and stamps ScheduledDate
on the assignment row on success.

Existing Schedule renames to SchedulePersonalPriceIncrease across four
call sites; ResolvePhase2Async becomes private. Shared concerns extract
into ActiveScheduleExistsAsync and CreateAndConfigureScheduleAsync helpers.

ScheduleBusinessPriceIncrease has zero production callers — the
UpcomingInvoiceHandler dispatcher branch lands in a follow-up. The new
PM35215_BusinessPlanPriceMigration feature flag defaults off.

* Address PR review: prevent orphan schedules and fix Release flag gate
2026-05-19 14:14:30 -05:00
Stephon Brown
e5419a7662 [PM-37597] Set Automatic Tax and Prevent Tax Exempt Mutations (#7662)
* feat(billing): add feature flag for automatic tax enforcement

* refactor(billing): remove unused SubscriptionUpdateOptionsExtensions

* refactor(billing): inject IFeatureService into billing services and commands

* feat(billing): conditionalize customer tax exemption logic with feature flag

* feat(billing): conditionally enable Stripe automatic tax in OrganizationBillingService

* test(billing): add unit tests for Stripe automatic tax feature flag

* fix(billing): Run dotnet format

* test(Premium): use class-level IFeatureService mock in UpgradePremiumToOrganizationCommandTests

* refactor(billing): consolidate customer return conditions for automatic tax

* refactor(billing): broaden postal code validation for organization creation

* refactor(billing): remove PM37597 feature flag for automatic tax logic

* Revert "refactor(billing): broaden postal code validation for organization creation"

This reverts commit cddbda838c.

* fix(test): remove outdated test
2026-05-19 13:18:06 -05:00
Alex Morask
d009a52f43 [PM-36949] feat: Add OrganizationPlanMigrationCohort and Assignment tables with bare repositories (#7644)
* [PM-36949] Add OrganizationPlanMigrationCohort schema and Core domain types

Add the foundation for cohort-based plan migrations:
- Two tables: OrganizationPlanMigrationCohort and OrganizationPlanMigrationCohortAssignment
- Two views and nine stored procedures (four CRUD on cohort, four CRUD plus
  ReadByOrganizationId on assignment)
- Single Migrator script for MSSQL deployment
- Core entities, MigrationPath value object and its registry, and bare repository
  interfaces under Bit.Core.Billing.Organizations.PlanMigration

The cohort table holds the human-managed metadata (name, discount coupons,
MigrationPathId byte) and the assignment table records each organization's
position in the migration lifecycle (scheduled, migrated, churn-mitigated).
Both Update SPs follow the accept-but-don't-assign pattern: immutable columns
(OrganizationId, CohortId, CreatedAt) are parameters but not SET clauses.

* [PM-36949] Add Dapper repositories for plan migration cohort tables

OrganizationPlanMigrationCohortRepository inherits the base Repository<T, TId>
CRUD methods unchanged. OrganizationPlanMigrationCohortAssignmentRepository
also relies on the base for CRUD and adds GetByOrganizationIdAsync which
returns at most one row (the UNIQUE constraint on OrganizationId at the
database layer guarantees this).

* [PM-36949] Add EF Core configurations, repositories, and provider migrations for plan migration cohort tables

- EF models wrap the Core entities; the assignment model exposes nav properties
  for Organization and Cohort so the FK + cascade-delete is inferred by EF.
- EntityTypeConfiguration classes pin ID generation to application code
  (ValueGeneratedNever) and declare the UNIQUE indexes plus the composite
  (CohortId, ScheduledAt, MigratedAt) index.
- Repositories follow the OrganizationInstallationRepository template; the
  assignment repo adds GetByOrganizationIdAsync to mirror the SP exposed on
  the MSSQL side.
- DatabaseContext gets two DbSet properties; auto-discovery picks up the
  configuration classes.
- Generated migrations for MySQL, Postgres, and SQLite create matching schemas;
  EF truncates FK and index names on providers with 64-char identifier limits,
  which is consistent with the rest of the codebase.

* [PM-36949] Wire up DI and add tests for plan migration cohort repositories

Register both Dapper and EF Core repositories in their respective service
collection extensions, following the existing AddSingleton convention in
these files.

Add tests:
- MigrationPathIdsSnapshotTests guards the immortal byte IDs that downstream
  code pins on. The class- and method-level comments document why these
  values can never be renumbered.
- MigrationPathTests covers the FromId round-trip and the null-on-unknown
  behavior the registry promises to callers.
- OrganizationPlanMigrationCohortRepositoryTests exercises CRUD, the UNIQUE
  Name constraint, and verifies that ReplaceAsync ignores CreatedAt
  mutations (per the accept-but-don't-assign Update SP).
- OrganizationPlanMigrationCohortAssignmentRepositoryTests exercises CRUD,
  GetByOrganizationIdAsync, the UNIQUE OrganizationId constraint,
  cascade-delete from both Organization and Cohort, and verifies that
  ReplaceAsync ignores OrganizationId, CohortId, and CreatedAt mutations.

* [PM-36949] Use PlanType and MigrationPathId enums on MigrationPath

Replace the byte Id with a byte-backed MigrationPathId enum and replace
the string FromPlan/ToPlan fields with PlanType. Persistence is
unchanged -- EF normalises enum-backed properties to their underlying
type in the model snapshot, and Dapper handles enum-to-byte parameter
mapping automatically.

* [PM-36949] Add *.lscache to .gitignore

* [PM-36949] fix: Override ReplaceAsync on EF cohort repositories for immutability parity

The Dapper _Update SPs accept-but-don't-assign certain columns (CreatedAt
on cohort; OrganizationId, CohortId, and CreatedAt on assignment), but
the base EF Repository<T,TEntity,Guid>.ReplaceAsync uses SetValues which
writes every scalar. Override on both repos and mark the immutable
properties as IsModified = false so MySQL/Postgres/Sqlite match MSSQL
behavior. Mirrors the existing DeviceRepository.ReplaceAsync pattern.

* [PM-36949] fix: Bound cohort string columns and widen Name to 255 chars

Add [MaxLength] attributes to the three cohort string properties so the
EF providers (MySQL/Postgres/Sqlite) enforce the same limits as MSSQL,
where the columns were already NVARCHAR-capped. Widen Name from 64 to
255 chars across MSSQL DDL, both _Create/_Update SP signatures, the
Migrator script, the entity, and all three regenerated EF migrations.
Coupon codes stay at 64 (Stripe IDs are short).

* [PM-36949] test: Lock FromPlan and ToPlan per MigrationPath

The snapshot test class doc says "byte N means a specific FromPlan ->
ToPlan transition forever", but only the byte value was being asserted.
A silent refactor of the registry's PlanType references would not have
been caught. Add per-path FromPlan/ToPlan assertions to close the gap.

* [PM-36949] chore: Apply dotnet format

* [PM-36949] test: Use LaxDateTimeComparer for round-tripped DateTimes

Postgres timestamp and MySQL datetime(6) store microsecond precision (6
fractional digits), but .NET DateTime is 100ns ticks (7 digits). Exact
Assert.Equal fails by a single tick on round-trip. Switch the three
DateTime comparisons in ReplaceAsync_UpdatesMutableColumns_AndIgnoresImmutableOnes
to LaxDateTimeComparer.Default -- the same 2ms-tolerance comparer used
by SendRepositoryTests and InstallationRepositoryTests for the same
precision issue.

* [PM-36949] refactor: Rename DateTime columns to Date suffix per naming convention

Addresses PR review feedback. Also tightens DATETIME2(7) / NVARCHAR(N) spacing
per the SQL style guide, drops *.lscache from .gitignore (handled by #7648),
and regenerates EF migrations for MySQL, Postgres, and SQLite.

* Apply dotnet format to regenerated migrations

* [PM-36949] chore: Align NULL/NOT NULL columns in cohort tables
2026-05-18 12:34:55 -05:00
Stephon Brown
4224933c62 [PM-35357] Update Trial Length Parameter (#7597)
* test(billing): Add unit tests for TrialInitiationCache

* feat(billing): Add trial initiation cache interface and implementation

* feat(billing): Register trial initiation cache services

* feat(core): Add trial properties to OrganizationSignup model

* feat(mail): Update trial verification email model and services for TrialInitiationId

* feat(billing): Enhance TrialSendVerificationEmailRequestModel validation

* test(billing): Add tests for TrialSendVerificationEmailRequestModel validation

* feat(billing): Introduce default trial length constant

* refactor(identity): Use constant for default trial length in AccountsController

* test(identity): Update accounts controller tests for default trial length constant

* feat(billing): Integrate trial initiation into email sending command

* feat(billing): Add TrialLength to SubscriptionSetup model

* feat(billing): Map TrialLength in OrganizationSale creation

* feat(billing): Allow custom trial length in organization billing service

* feat(admin-console): Add TrialLength and TrialInitiationId to organization creation requests

* feat(admin-console): Validate trial length during cloud organization signup

* test(admin-console): Add tests for organization create request trial properties

* test(admin-console): Add tests for CloudOrganizationSignUpCommand trial length validation

* refactor(TrialInitiationCache): change validation method to retrieval

* test(TrialInitiationCache): update tests for GetAndRemoveAsync

* feat(OrganizationSignUp): refactor trial validation to command

* test(OrganizationSignUp): add trial validation scenarios

* test(OrganizationSignUp): nullify TrialLength in unrelated tests

* fix(billing): dotnet format

* refactor: remove `TrialInitiationId` property from data models
refactor: update mail service interfaces and implementations

* refactor: remove `ITrialInitiationCache` infrastructure
test: update `CloudOrganizationSignUpCommandTests` for trial validation

* refactor: update `SendTrialInitiationEmailForRegistrationCommand`

* refactor: update `CloudOrganizationSignUpCommand` trial length validation

* test(organization): fix plan call in tests

* test(billing): fix test settings
2026-05-18 09:32:04 -04:00
cyprain-okeke
b0dd1c9d16 [PM 34174]Do not show renewal reminder banners to exempt organizations (#7483)
* expose the ExemptFromBillingAutomation to client

* Fix the failing database

* Rename the file to resolve chronological order error

* Renamed the migration file name

* fix the failing database

* Suppress warnings for exempt orgs at the query level

Gate InactiveSubscription and ResellerRenewal warnings inside
GetOrganizationWarningsQuery on Organization.ExemptFromBillingAutomation
instead of plumbing the field through the profile response, view models,
EF queries, and SQL views.

* Add the ExemptFromBillingAutomation property

---------

Co-authored-by: Alex Morask <144709477+amorask-bitwarden@users.noreply.github.com>
2026-05-14 18:22:08 +01:00
Alex Morask
316f7cdc6c [PM-36613] Void open invoices for unpaid subscriptions (#7589)
* fix(billing): void open invoices when subscription is deleted

* refactor(billing): gate invoice voiding on subscription deletion

* feat(billing): clear pending unpaid cancellation on subscriber re-enable

* feat(billing): schedule unpaid cancellation on subscriber disable

* fix(billing): respect subscription test clock when scheduling cancellation
2026-05-12 21:29:57 +00:00
Alex Morask
869cd3a34e [PM-35909] Preserve existing discounts during price migration (#7561)
* fix(billing): preserve existing discounts in price migration schedules

* refactor(billing): source cart discounts from schedule phase 2 when attached
2026-05-06 07:57:27 -05:00
Rui Tomé
52d9a9cc88 [PM-35253] Add organization ability UseInviteLinks (#7489)
* Add UseInviteLinks to Organization SQL schema and views

* Add Migrator scripts for UseInviteLinks column and data migration

* Add EF migrations for UseInviteLinks on Organization

* Wire UseInviteLinks through organization domain and repositories

* Add HasInviteLinks plan support and UseInviteLinks license handling

* Expose UseInviteLinks and HasInviteLinks on organization and plan API models

* Update tests for UseInviteLinks and invite-links plan feature

* Update migration script with missing update to Organization_ReadManyByIds

* Move UseInviteLinks column after ExemptFromBillingAutomation

* Bump date on migration scripts
2026-04-30 10:13:50 +01:00
Stephon Brown
995ccbbe0c [PM-34565] Save Cancellation Details for Scheduled Subscriptions (#7535)
* refactor(billing): add constant for deferred price increase cancellation

* feat(billing): update cancellation logic to release schedules and set metadata

* feat(billing): update reinstatement logic to recreate schedules

* style(billing): cleanup formatting

* fix(billing) run dotnet format

* docs(billing): clarify stripe subscription update behavior regarding schedules
2026-04-28 22:03:41 +00:00
Kyle Denney
2820ecc567 [PM-34813] fix system coupons regression (#7515)
* [PM-34813] fix system coupons regression

refactor customer setup class to split system coupons from discount coupons so that they can be applied systematically
2026-04-22 16:42:44 -05:00
cyprain-okeke
bbc1a315e2 billing/pm-24665/license-file-generation-should-fail-for-unpaid-subscription (#7444)
* Add changes for unpaid subscriptions

* Remove the unpaid status and update the test

* Add changes for premium user

* Fix the lint error

* remove subscriptionInfo and use subscription

* Fix file encoding issue

* SubscriptionLicenseValidator.cs is deleted

* Fix the lint error
2026-04-21 13:58:16 +01:00
Stephon Brown
94fd9af1f5 [PM-32853] Add Trial Initiation Metadata for Marketing or Product (#7462)
* feat(billing): add FromMarketing property to subscription purchase model

* feat(billing): expose FromMarketing in cloud hosted subscription request

* feat(stripe): define TrialInitiationPath metadata key

* feat(billing): propagate FromMarketing to Stripe trial initiation path

* test(billing): verify Stripe trial initiation path metadata

* fix(billing): run dotnet format
2026-04-17 09:26:34 -05:00
Conner Turnbull
9e49955045 [PM-31909] Remove m3 flagged logic (#7352)
* Remove pm-26462-milestone-3 flag from PricingClient

Simplify FamiliesAnnually2025 lookup to always return "families-2025".
Remove PreProcessFamiliesPreMigrationPlan deployment safeguard method
and its call sites. Remove unused IFeatureService constructor dependency.

* Remove pm-26462-milestone-3 flag from UpcomingInvoiceHandler.HandleForOrganizationAsync

Remove milestone3 variable and parameter from
AlignOrganizationSubscriptionConcernsAsync. Simplify guard clause
to only check plan type.

* Update PricingClientTests after pm-26462-milestone-3 removal

Remove IFeatureService from test setup. Delete tests for flag-disabled
safeguard behavior. Clean up remaining test names.

* Update UpcomingInvoiceHandlerTests after pm-26462-milestone-3 removal

Remove all feature flag mock setup lines for PM26462_Milestone_3.
Delete test methods that verified flag-disabled behavior.

* Merge GetPlan lookup key test regions into one
2026-04-14 15:31:56 -04:00
Conner Turnbull
22398808fa Fix test clock awareness in schedule-aware cancellation (#7440)
* Fix test clock awareness in schedule-aware cancellation

* Update test mocks for SubscriptionGetOptions parameter
2026-04-10 12:16:43 -04:00
Conner Turnbull
aad805a976 [PM-34866][PM-34865] Fix EnableAutomaticTaxAsync to update schedule phases (#7437)
* [PM-34866] Fix EnableAutomaticTaxAsync to update schedule phases

* Use test clock frozen time for phase filtering

* Expand test_clock on customer subscription fetches
2026-04-10 15:50:41 +00:00
Stephon Brown
52ff4a7613 [PM-33301] Add Functionality for Upgrading Using PayPal (#7183)
* feat(billing): implement paypal payments for premium organization upgrade

* test(billing): add paypal payment scenarios for organization upgrade

* tests(billing): simplify tests

* fix(billing): run dotnet format

* style(billing): formatting

* refactor(billing): inline subscription detail fetching logic

* fix(billing): use OrganizationId for PayPal invoice payment

* refactor(billing): simplify subscription item quantity to 1 for upgrades

* feat(subscriber): add type-checking properties to SubscriberId

* refactor(tests): update result assertion to use 'Success' property

* fix(billing) run dotnet format

* feat(billing): implement payment method pre-check for organization upgrade

* feat(billing): refine Stripe subscription payment behavior for default incomplete

* test(billing): update and add tests for organization upgrade command

* chore(billing): clarify comment on user subscription removal

* feat(payment): add type helper properties to masked payment method

* refactor(billing): replace IHasPaymentMethodQuery with IGetPaymentMethodQuery

* feat(billing): prohibit bank accounts for organization upgrades

* refactor(billing): simplify subscription payment behavior logic

* test(billing): update premium organization upgrade bank account test

* feat(billing): allow premium organization upgrade with verified bank accounts

* test(billing): add test for premium organization upgrade with verified bank account

* style(files): remove byte order mark from files

* refactor(billing): centralize premium upgrade billing logic
2026-04-10 11:22:02 -04:00
Kyle Denney
18525843bb [PM-26043] Fix bug: can't add secrets manager to legacy plans (#7414)
* [PM-26043] refactored AddSecretsManagerSubscriptionCommand

move to billing, fix bug unable to add secrets manager to legacy plan by moving all validation into command and skipping the disabled check

* forgot BillingCommandResult is being deprecated

* cleanup

* add unit test coverage

* one more test

* pr feedback

* forgot to fix in actual code file
2026-04-10 09:46:30 -05:00
Alex Morask
a5052d8d0f fix(billing): skip ended schedule phases when updating storage (#7420) 2026-04-08 09:51:03 -05:00
Alex Morask
aa1aa58190 fix(billing): use top-level ProrationBehavior on schedule updates for immediate storage invoicing (#7410) 2026-04-07 15:09:21 -05:00
Alex Morask
c04ee9c0a9 fix(billing): handle cross-product Phase 2 price overlay for Families 2019 (#7408) 2026-04-07 12:32:51 -05:00
Alex Morask
e758ca2e27 fix(billing): display Phase 2 prices and discount on org subscription page (#7393) 2026-04-06 14:15:20 -05:00
Alex Morask
c4f9ad79a9 fix(billing): prevent stale schedule discount after Stripe deletion (#7391) 2026-04-06 10:54:28 -05:00
Conner Turnbull
6eb0c3c3ef Include schedule Phase 2 discount in premium tax estimate preview (#7385)
When a subscription has an active schedule during the ~15-day window before
renewal, the invoice preview for tax estimation was built with the new price
but without the Phase 2 discount coupon. This caused the estimated tax on the
subscription page to be higher than what Stripe would actually charge.

Pass the coupon ID from the schedule's Phase 2 discount through to
EstimatePremiumTaxAsync so it is included in the InvoiceCreatePreviewOptions.
2026-04-03 10:41:33 -04:00
Alex Morask
69672bf912 fix(billing): scope schedule discount to seats item instead of cart (#7378) 2026-04-02 14:32:22 +00:00
Stephon Brown
da34581991 [PM-33897] Schedule Aware Cancellation and Reinstatement (#7374)
* feat(pricing): add ResolvePhase2Async to price increase scheduler interface

* feat(pricing): implement ResolvePhase2Async and add unit tests

* feat(subscriber): adjust subscription cancellation for price migration schedules

* feat(reinstate): update subscription reinstatement for price migration schedules

* fix(billing) simplify cancellation data logic

* fix(billing): Cast StartDate to DateTime in price increase tests

* fix(billing): PR feedback

* refactor(test): rename subscription cancellation test

* fix(billing): properly apply cancellation metadata to subscription schedules
2026-04-02 10:04:41 -04:00
Alex Morask
1fac799e3d fix(billing): display schedule phase 2 discount on premium subscription page (#7375) 2026-04-01 14:53:45 -05:00
Stephon Brown
f1a43078f3 [PM-33891] Migrate Cancel and Reinstate Paths (#7331)
* refactor(billing): make OffboardingSurveyResponse optional in CancelSubscription

* refactor(billing): migrate UserService.CancelPremiumAsync behind PM32645 feature flag

* refactor(billing): migrate OrganizationDeleteCommand cancel behind PM32645 feature flag

* refactor(billing): migrate AccountsController reinstate-premium behind PM32645 feature flag

* refactor(billing): migrate OrganizationsController reinstate behind PM32645 feature flag

* feat(billing): Introduce CancelSubscription overload for system cancellations

* refactor(billing): Conditionally apply Stripe cancellation metadata

* chore(tests): Remove unused using directive in OrganizationDeleteCommandTests

* refactor(billing): simplify OneOf result handling

* chore(deps): remove unused OneOf.Types imports

* chore(deps): remove unused Bit.Core.Billing.Commands imports

* chore(deps): remove unused Bit.Core imports

* refactor(billing): unify ISubscriberService.CancelSubscription overloads

* refactor(billing): update SubscriberService.CancelSubscription implementation

* fix(api): adjust CancelSubscription calls in billing controllers

* fix(user): adjust UserService.CancelSubscription call

* test(billing): adjust SubscriberServiceTests for new CancelSubscription signature

* test(user): adjust UserServiceTests for new CancelSubscription signature

* refactor(billing): feature flag subscription cancel

* refactor: decouple premium cancellation logic from user service

* test: add tests for organization cancellation with feature flag

* test: add tests for handling gateway exceptions during cancellation

* fix(billing): run dotnet format
2026-03-31 14:29:27 +00:00
Alex Morask
11605dd551 feat(billing): make storage commands schedule-aware for price migration (#7350) 2026-03-31 08:32:03 -05:00
Conner Turnbull
c604379f04 [PM-33901] Implement schedule-aware tax handling (#7319)
Make tax-related subscription updates schedule-aware during the ~15-day
window between invoice.upcoming and renewal. When a subscription schedule
is present and the feature flag is enabled, update default_settings.automatic_tax
on the schedule instead of the subscription directly.

Modified paths:
- UpcomingInvoiceHandler: AlignOrganizationTaxConcernsAsync,
  AlignPremiumUsersTaxConcernsAsync, new shared EnableAutomaticTaxAsync helper
- UpdateBillingAddressCommand: EnableAutomaticTaxAsync, added IFeatureService
2026-03-27 14:27:36 -04:00
Conner Turnbull
7458eba830 [PM-33901] Remove unused UpdateTaxInformation (#7320)
UpdateTaxInformation has no callers in src/. Remove from ISubscriberService,
SubscriberService, and associated tests. Also removes the now-unused
ITaxService constructor parameter from SubscriberService.
2026-03-27 14:26:41 -04:00
Stephon Brown
3c2cc45215 [PM-32216] Create Stripe Checkout Session Endpoint (#7246)
* feat(stripe): add checkout session constants and settings

* feat(billing): integrate Stripe Checkout Session adapter

* feat(billing): define premium checkout session DTOs

* feat(billing): implement CreatePremiumCheckoutSessionCommand

* feat(billing): add premium checkout session API endpoint

* test(billing): add premium checkout session tests

* fix(billing): run dotnet format

* fix(billing): run dotnet format

* refactor(billing): clarify Stripe session types in IStripeAdapter

* refactor(billing): clarify Stripe session service and types in StripeAdapter

* refactor(StripeAdapter): remove duplicate billing portal session method

* style(premium): remove trailing comma from payment method types

* refactor(billing): retrieve client version from context

* refactor(premium): remove IUserService dependency from checkout command

* refactor(premium): consolidate stripe customer creation logic

* fix(billing) run dotnet format

* feat(billing): add user ID to premium checkout session subscription

* test(billing): verify user ID is set in premium checkout session metadata

* test(billing): handle billing exception during stripe customer creation

* [PM-32218] Create Session Complete Handler (#7283)

* feat(billing): add checkout.session.completed webhook infrastructure

* feat(billing): introduce StripeAdapter for Checkout Session retrieval

* feat(billing): enable StripeEventService to retrieve Checkout Sessions

* feat(billing): implement CheckoutSessionCompletedHandler

* test(billing): add comprehensive tests for CheckoutSessionCompletedHandler and StripeEventService

* fix(billing): run dotnet format

* style: fix incorrect 'using' directive format

* fix(billing): standardize logging levels for critical checkout session states

* feat(billing): implement default payment method update on checkout session completion

* refactor(billing): preload subscription with checkout session

* refactor(billing): pass payment method ID to update method

* test(billing): update mocks for direct subscription access

* test(billing): update test names and expectations for payment method

* fix(billing): run dotnet format

* fix(billing): update order of operations

* feat(billing): Prevent re-upgrading for existing premium users

* refactor(billing): Augment UpdateDefaultPaymentMethodAsync with subscription ID

* feat(billing): Reset Stripe subscription default payment method
2026-03-27 13:53:49 -04:00
Stephon Brown
c52ea40c28 fix(billing): ensure UserHasNoPreviousSubscriptionsFilter checks all subscription statuses (#7301) 2026-03-27 09:49:41 -04:00
Alex Morask
54c3e4a695 feat(billing): add IPriceIncreaseScheduler for deferred price migration scheduling (#7305) 2026-03-26 15:00:12 -05:00
Alex Morask
c84ac1403e [PM-33980] Only verify UseMyItems when claim exists (#7278)
* fix(licensing): skip UseMyItems comparison for pre-2026.3.0 license files

* docs(licensing): add backward-compatibility guidance to ability flag README
2026-03-23 13:52:28 -05:00
Alex Morask
212a0609c0 [PM-33415] [PM-33418] Fix add-on item proration and Families > Teams/Enterprise upgrade seat count (#7259)
* fix(billing): replace per-change IsStructural with changeset-level ChargeImmediately flag

* fix(billing): set seat quantity when upgrading from non-seat-based to seat-based plan
2026-03-20 09:29:45 -05:00
cyprain-okeke
fae3e95492 [PM-32480] Add endpoint for Stripe billing portal session (#7227)
* Implement the portal session url

* Remove comment

* formatting issues have been resolved

* Allow deep linking url

* remove thr return url request

* Resolve review comments around comments

* Fix the failing test after removing _globalSettings

* Fix the failing unit test
2026-03-20 09:31:43 +01:00
cyprain-okeke
768de5f1e2 [PM-32477]PremiumStatusChanged Push Notification (#7198)
* changes for the premium push notification

* Fix the lint build

* implement the hub-helper

* Resolve the pr comments

* fix the lint error

* move PremiumStatusPushNotification to billing
2026-03-19 15:51:36 +01:00
Kyle Denney
2efacd596d [PM-30101] add multiple coupon support to server preview/purchase (#7229)
* [PM-30101] add multiple coupon support to server preview/purchase

* pr feedback
2026-03-19 09:07:49 -05:00
Stephon Brown
8302509bf9 [PM-31645] Implement Swiss Tax Logic (#7186)
* feat(tax): introduce direct tax country utilities and Switzerland constant

* refactor(tax): use `TaxHelpers.IsDirectTaxCountry` for country checks

* feat(tax): implement customer tax exempt status alignment

* test(tax): add comprehensive unit tests for tax exempt alignment logic

* tests(billing): clarify tests

* fix(billing): run dotnet format

* fix(billing): run dotnet format

* fix(billing): Prevent NullReferenceException when accessing customer country

* test(billing): Add Stripe adapter mocks for AdjustSubscription scenarios

* refactor(billing): apply null-conditional operator for address country access

* feat(billing): update missing tax exemption determinations

* test(billing): add unit tests for tax exemption updates

* fix(billing) run dotnet format

* fix(billing): add nullability

* style(files): normalize file encoding for billing utilities

* refactor(TaxHelpers): simplify tax exempt status determination

* test(Tax): update tax exempt determination tests

* fix(billing): revert postal code validation

* test(billing): update tax exempt tests

* fix(billing): run dotnet format
2026-03-17 14:09:41 -04:00
Alex Morask
ed861d89f8 [PM-32581] Refactor organization subscription update process (#7132)
* chore: add CLAUDE.local.md and .worktrees to gitignore

* feat(billing): add Stripe interval and payment behavior constants and feature flag

* feat(billing): add OrganizationSubscriptionChangeSet model and unit tests

* refactor(billing): rename UpdateOrganizationSubscriptionCommand to BulkUpdateOrganizationSubscriptionsCommand

* feat(billing): add UpdateOrganizationSubscriptionCommand with tests

* feat(billing): use UpdateOrganizationSubscriptionCommand in BulkUpdateOrganizationSubscriptions behind feature flag

* feat(billing): use UpdateOrganizationSubscriptionCommand in SetUpSponsorshipCommand behind feature flag

* feat(billing): add UpgradeOrganizationPlanVNextCommand with tests and feature flag gate

* feat(billing): use UpdateOrganizationSubscriptionCommand in OrganizationService.AdjustSeatsAsync behind feature flag

* feat(billing): use UpdateOrganizationSubscriptionCommand in UpdateSecretsManagerSubscriptionCommand behind feature flag

* feat(billing): use UpdateOrganizationSubscriptionCommand in BillingHelpers.AdjustStorageAsync behind feature flag

* chore: run dotnet format

* fix(billing): missed optional owner in OrganizationBillingService.Finalize after merge

* refactor(billing): address PR feedback on UpdateOrganizationSubscription
2026-03-09 15:37:51 -05:00
Stephon Brown
4732d7fcd2 [PM-33061] Tax Id Should Be Added When Upgrading to Teams or Enterprise (#7131)
* refactor(billing): change billing address request type

* feat(billing): add tax id support for international business plans

* feat(billing): add billing address tax id handling

* test: add tests for tax id handling during upgrade

* fix(billing): run dotnet format

* fix(billing): remove extra line

* fix(billing): modify return type of HandleAsync

* test(billing): update tests to reflect updated command signature

* fix(billing): run dotnet format

* tests(billing): fix tests

* test(billing): format
2026-03-09 17:27:15 +00:00
cyprain-okeke
153c175ed8 Add coupon support to invoice preview and subscription creation (#6994)
* Add coupon support to invoice preview and subscription creation

* Fix the build lint error

* Resolve the initial review comments

* fix  the failing test

* fix the build lint error

* Fix the failing test

* Resolve the unaddressed issues

* Fixed the deconstruction error

* Fix the lint issue

* Fix the lint error

* Fix the lint error

* Fix the build lint error

* lint error resolved

* remove the setting file

* rename the variable name  validatedCoupon

* Remove the owner property

* Update OrganizationBillingService tests to align with recent refactoring

- Remove GetMetadata tests as method no longer exists
- Remove Owner property references from OrganizationSale (removed in d7613365ed)
- Update coupon validation to use SubscriptionDiscountRepository instead of SubscriptionDiscountService
- Add missing imports for SubscriptionDiscount entities
- Rename test for clarity: Finalize_WithNullOwner_SkipsValidation → Finalize_WithCouponOutsideDateRange_IgnoresCouponAndProceeds

All tests passing (14/14)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Fix the lint error

* Making the owner non nullable

* fix the failing unit test

* Make the owner nullable

* Fix the bug for coupon in Stripe with no audience restrictions(PM-32756)

* Return validation message for invalid coupon

* Update the valid token message

* Fix the failing unit test

* Remove the duplicate method

* Fix the failing build and test

* Resolve the failing test

* Add delete of invalid coupon

* Add the expired error message

* Delete on invalid coupon in stripe

* Fix the lint errors

* return null if we get exception from stripe

* remove the auto-delete change

* fix the failing test

* Fix the lint build error

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-05 17:08:40 +01:00