refactor(billing): gate multi-currency on MULTIPLE_CURRENCY_SUPPORTED config instead of feature flag
This commit is contained in:
@@ -24,6 +24,7 @@ class DashboardController < ActionController::Base
|
||||
DISABLE_USER_PROFILE_UPDATE
|
||||
DEPLOYMENT_ENV
|
||||
INSTALLATION_PRICING_PLAN
|
||||
MULTIPLE_CURRENCY_SUPPORTED
|
||||
].freeze
|
||||
|
||||
before_action :set_application_pack
|
||||
|
||||
@@ -27,8 +27,7 @@ import ButtonV4 from 'next/button/Button.vue';
|
||||
|
||||
const router = useRouter();
|
||||
const { t } = useI18n();
|
||||
const { currentAccount, isOnChatwootCloud, isCloudFeatureEnabled } =
|
||||
useAccount();
|
||||
const { currentAccount, isOnChatwootCloud } = useAccount();
|
||||
const {
|
||||
captainEnabled,
|
||||
captainLimits,
|
||||
@@ -39,6 +38,7 @@ const {
|
||||
} = useCaptain();
|
||||
|
||||
const uiFlags = useMapGetter('accounts/getUIFlags');
|
||||
const globalConfig = useMapGetter('globalConfig/get');
|
||||
const store = useStore();
|
||||
|
||||
const BILLING_REFRESH_ATTEMPTED = 'billing_refresh_attempted';
|
||||
@@ -138,9 +138,9 @@ const handleBillingPageLogic = async () => {
|
||||
|
||||
const isSwitchingCurrency = computed(() => uiFlags.value.isSwitchingCurrency);
|
||||
|
||||
// Currency switching is rolled out per-account via the billing_currency_switch feature flag.
|
||||
const showCurrencyToggle = computed(() =>
|
||||
isCloudFeatureEnabled('billing_currency_switch')
|
||||
// Currency switching is shown only when multi-currency billing is enabled for the installation.
|
||||
const showCurrencyToggle = computed(
|
||||
() => globalConfig.value.multipleCurrencySupported
|
||||
);
|
||||
|
||||
const currentBillingCurrency = computed(() =>
|
||||
|
||||
@@ -25,6 +25,7 @@ const {
|
||||
DISABLE_USER_PROFILE_UPDATE: disableUserProfileUpdate,
|
||||
DEPLOYMENT_ENV: deploymentEnv,
|
||||
ACTIVE_PLATFORM_BANNERS: activePlatformBanners,
|
||||
MULTIPLE_CURRENCY_SUPPORTED: multipleCurrencySupported,
|
||||
} = window.globalConfig || {};
|
||||
|
||||
const state = {
|
||||
@@ -51,6 +52,7 @@ const state = {
|
||||
widgetBrandURL,
|
||||
isEnterprise: parseBoolean(isEnterprise),
|
||||
activePlatformBanners: activePlatformBanners || [],
|
||||
multipleCurrencySupported: parseBoolean(multipleCurrencySupported),
|
||||
};
|
||||
|
||||
export const getters = {
|
||||
|
||||
+3
-4
@@ -216,11 +216,10 @@
|
||||
display_name: Reply Mailer Migration
|
||||
enabled: false
|
||||
chatwoot_internal: true
|
||||
- name: billing_currency_switch
|
||||
display_name: Billing Currency Switch
|
||||
- name: quoted_email_reply
|
||||
display_name: Quoted Email Reply
|
||||
enabled: false
|
||||
premium: true
|
||||
chatwoot_internal: true
|
||||
deprecated: true
|
||||
- name: companies
|
||||
display_name: Companies
|
||||
enabled: false
|
||||
|
||||
@@ -262,6 +262,12 @@
|
||||
display_title: 'Planwise Features List'
|
||||
value:
|
||||
description: 'Config to features and their associated plans'
|
||||
- name: MULTIPLE_CURRENCY_SUPPORTED
|
||||
display_title: 'Multiple Currency Support'
|
||||
value: false
|
||||
description: 'Enable multi-currency billing: show the billing currency switch and onboard new pt_BR accounts in BRL'
|
||||
locked: false
|
||||
type: boolean
|
||||
- name: DEPLOYMENT_ENV
|
||||
value: self-hosted
|
||||
description: 'The deployment environment of the installation, to differentiate between Chatwoot cloud and self-hosted'
|
||||
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
class RepurposeQuotedEmailReplyFlagForBillingCurrencySwitch < ActiveRecord::Migration[7.1]
|
||||
def up
|
||||
# The quoted_email_reply flag (deprecated) has been renamed to billing_currency_switch.
|
||||
# They share a bit position, so disable it on any accounts that had quoted_email_reply
|
||||
# enabled — otherwise they would silently start with billing_currency_switch on.
|
||||
Account.feature_billing_currency_switch.find_each(batch_size: 100) do |account|
|
||||
account.disable_features(:billing_currency_switch)
|
||||
account.save!(validate: false)
|
||||
end
|
||||
|
||||
# Remove the stale quoted_email_reply entry from ACCOUNT_LEVEL_FEATURE_DEFAULTS.
|
||||
# ConfigLoader only adds new flags; it never removes renamed ones.
|
||||
config = InstallationConfig.find_by(name: 'ACCOUNT_LEVEL_FEATURE_DEFAULTS')
|
||||
return if config&.value.blank?
|
||||
|
||||
config.value = config.value.reject { |feature| feature['name'] == 'quoted_email_reply' }
|
||||
config.save!
|
||||
GlobalConfig.clear_cache
|
||||
end
|
||||
end
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[7.1].define(version: 2026_06_15_000000) do
|
||||
ActiveRecord::Schema[7.1].define(version: 2026_06_11_184600) do
|
||||
# These extensions should be enabled to support this database
|
||||
enable_extension "pg_stat_statements"
|
||||
enable_extension "pg_trgm"
|
||||
|
||||
@@ -75,6 +75,9 @@ module Enterprise::Account
|
||||
# Existing Stripe customers stay on USD (webhook backfills); only new accounts infer from locale.
|
||||
return Enterprise::Billing::Currencies::DEFAULT if custom_attributes&.dig('stripe_customer_id').present?
|
||||
|
||||
# New accounts onboard in their locale's currency only while multi-currency billing is enabled.
|
||||
return Enterprise::Billing::Currencies::DEFAULT unless Enterprise::Billing::Currencies.multi_currency_supported?
|
||||
|
||||
Enterprise::Billing::Currencies.for_locale(locale)
|
||||
end
|
||||
|
||||
|
||||
@@ -45,6 +45,12 @@ module Enterprise::Billing::Currencies
|
||||
LOCALE_DEFAULTS.fetch(locale.to_s, DEFAULT)
|
||||
end
|
||||
|
||||
# Master switch for multi-currency billing (currency switch UI + non-default onboarding currency).
|
||||
# Read raw from InstallationConfig so a super-admin toggle takes effect without cache staleness.
|
||||
def multi_currency_supported?
|
||||
ActiveModel::Type::Boolean.new.cast(InstallationConfig.find_by(name: 'MULTIPLE_CURRENCY_SUPPORTED')&.value)
|
||||
end
|
||||
|
||||
def country_for(code)
|
||||
COUNTRY_BY_CURRENCY[to_supported(code)]
|
||||
end
|
||||
|
||||
@@ -20,7 +20,7 @@ class Enterprise::Billing::CurrencySwitchEligibility
|
||||
private
|
||||
|
||||
def validate!
|
||||
raise Error, I18n.t('errors.billing.currency_switch_unavailable') unless account.feature_enabled?('billing_currency_switch')
|
||||
raise Error, I18n.t('errors.billing.currency_switch_unavailable') unless Enterprise::Billing::Currencies.multi_currency_supported?
|
||||
raise Error, I18n.t('errors.billing.unsupported_currency') unless Enterprise::Billing::Currencies.supported?(currency)
|
||||
raise Error, I18n.t('errors.billing.same_currency') if target_currency == account.billing_currency
|
||||
raise Error, I18n.t('errors.billing.stripe_customer_not_configured') if stripe_customer_id.blank?
|
||||
|
||||
@@ -11,6 +11,34 @@ RSpec.describe Account, type: :model do
|
||||
it { is_expected.to have_many(:custom_roles).dependent(:destroy_async) }
|
||||
end
|
||||
|
||||
describe '#billing_currency' do
|
||||
let(:account) { create(:account, locale: 'pt_BR') }
|
||||
|
||||
it 'returns the stored currency when set' do
|
||||
account.update!(custom_attributes: { 'billing_currency' => 'brl' })
|
||||
expect(account.billing_currency).to eq('brl')
|
||||
end
|
||||
|
||||
it 'keeps existing stripe customers on usd' do
|
||||
account.update!(custom_attributes: { 'stripe_customer_id' => 'cus_123' })
|
||||
expect(account.billing_currency).to eq('usd')
|
||||
end
|
||||
|
||||
context 'when multi-currency billing is enabled' do
|
||||
before { create(:installation_config, name: 'MULTIPLE_CURRENCY_SUPPORTED', value: true) }
|
||||
|
||||
it 'onboards a new pt_BR account in brl' do
|
||||
expect(account.billing_currency).to eq('brl')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when multi-currency billing is disabled' do
|
||||
it 'onboards a new pt_BR account in usd' do
|
||||
expect(account.billing_currency).to eq('usd')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'sla_policies' do
|
||||
let!(:account) { create(:account) }
|
||||
let!(:sla_policy) { create(:sla_policy, account: account) }
|
||||
|
||||
@@ -33,7 +33,7 @@ describe Enterprise::Billing::SwitchCurrencyService do
|
||||
'price_ids' => { 'usd' => ['price_business_usd'], 'brl' => ['price_business_brl'] } }
|
||||
])
|
||||
|
||||
account.enable_features!(:billing_currency_switch)
|
||||
create(:installation_config, name: 'MULTIPLE_CURRENCY_SUPPORTED', value: true)
|
||||
account.update!(custom_attributes: { plan_name: 'Business', stripe_customer_id: stripe_customer_id, billing_currency: 'usd' })
|
||||
|
||||
allow(Stripe::Subscription).to receive(:list).and_return(Struct.new(:data).new([active_subscription]))
|
||||
@@ -84,8 +84,8 @@ describe Enterprise::Billing::SwitchCurrencyService do
|
||||
)
|
||||
end
|
||||
|
||||
it 'raises when the feature is not enabled' do
|
||||
account.disable_features!(:billing_currency_switch)
|
||||
it 'raises when multi-currency billing is not enabled' do
|
||||
InstallationConfig.find_by(name: 'MULTIPLE_CURRENCY_SUPPORTED').update!(value: false)
|
||||
|
||||
expect { service.perform }.to raise_error do |error|
|
||||
expect(error.class.name).to eq('Enterprise::Billing::SwitchCurrencyService::Error')
|
||||
|
||||
Reference in New Issue
Block a user