feat(whatsapp): enable reconfigure for embedded signup inboxes (#15038)

This commit is contained in:
Tanmay Deep Sharma
2026-07-16 16:18:58 +05:30
committed by GitHub
parent 522e3c4d3f
commit 2891a72cb9
9 changed files with 82 additions and 25 deletions
@@ -1,4 +1,6 @@
class Api::V1::Accounts::Whatsapp::AuthorizationsController < Api::V1::Accounts::BaseController
# Reconfiguring/reauthorizing a live inbox swaps its credentials, so restrict it to admins.
before_action :check_admin_authorization?, if: -> { params[:inbox_id].present? }
before_action :fetch_and_validate_inbox, if: -> { params[:inbox_id].present? }
# POST /api/v1/accounts/:account_id/whatsapp/authorization
@@ -31,7 +33,7 @@ class Api::V1::Accounts::Whatsapp::AuthorizationsController < Api::V1::Accounts:
end
def validate_reauthorization_required
return if @inbox.channel.reauthorization_required? || can_upgrade_to_embedded_signup?
return if @inbox.channel.reauthorization_required? || can_reconfigure_channel?
render json: {
success: false,
@@ -39,10 +41,13 @@ class Api::V1::Accounts::Whatsapp::AuthorizationsController < Api::V1::Accounts:
}, status: :unprocessable_entity
end
def can_upgrade_to_embedded_signup?
def can_reconfigure_channel?
channel = @inbox.channel
return false unless channel.provider == 'whatsapp_cloud'
# Reconfiguring a live embedded-signup channel requires the feature flag.
return Current.account.feature_enabled?('whatsapp_reconfigure') if channel.provider_config['source'] == 'embedded_signup'
true
end
+1
View File
@@ -8,6 +8,7 @@ export const FEATURE_FLAGS = {
CAMPAIGNS: 'campaigns',
WHATSAPP_CAMPAIGNS: 'whatsapp_campaign',
WHATSAPP_MANUAL_TRANSFER: 'whatsapp_manual_transfer',
WHATSAPP_RECONFIGURE: 'whatsapp_reconfigure',
CANNED_RESPONSES: 'canned_responses',
CRM: 'crm',
CUSTOM_ATTRIBUTES: 'custom_attributes',
@@ -1,5 +1,9 @@
<script>
import { mapGetters } from 'vuex';
import { useAlert } from 'dashboard/composables';
import { useWhatsappEmbeddedSignup } from 'dashboard/composables/useWhatsappEmbeddedSignup';
import { FEATURE_FLAGS } from 'dashboard/featureFlags';
import whatsappChannel from 'dashboard/api/channel/whatsappChannel';
import inboxMixin from 'shared/mixins/inboxMixin';
import SettingsFieldSection from 'dashboard/components-next/Settings/SettingsFieldSection.vue';
import SettingsToggleSection from 'dashboard/components-next/Settings/SettingsToggleSection.vue';
@@ -30,7 +34,8 @@ export default {
},
},
setup() {
return { v$: useVuelidate() };
const { runEmbeddedSignup } = useWhatsappEmbeddedSignup();
return { v$: useVuelidate(), runEmbeddedSignup };
},
data() {
return {
@@ -41,15 +46,29 @@ export default {
allowedDomains: '',
isUpdatingAllowedDomains: false,
isSettingDefaults: false,
isReconfiguring: false,
};
},
validations: {
whatsAppInboxAPIKey: { required },
},
computed: {
...mapGetters({
accountId: 'getCurrentAccountId',
isFeatureEnabledonAccount: 'accounts/isFeatureEnabledonAccount',
}),
isEmbeddedSignupWhatsApp() {
return this.inbox.provider_config?.source === 'embedded_signup';
},
showWhatsAppReconfigure() {
return (
this.isEmbeddedSignupWhatsApp &&
this.isFeatureEnabledonAccount(
this.accountId,
FEATURE_FLAGS.WHATSAPP_RECONFIGURE
)
);
},
isForwardingEnabled() {
return !!this.inbox.forwarding_enabled;
},
@@ -160,6 +179,28 @@ export default {
useAlert(this.$t('INBOX_MGMT.EDIT.API.ERROR_MESSAGE'));
}
},
async reconfigureWhatsApp() {
this.isReconfiguring = true;
try {
const credentials = await this.runEmbeddedSignup();
// User dismissed the Meta popup without completing signup.
if (!credentials) return;
await whatsappChannel.reauthorizeWhatsApp({
inboxId: this.inbox.id,
...credentials,
});
useAlert(
this.$t('INBOX_MGMT.SETTINGS_POPUP.WHATSAPP_RECONFIGURE_SUCCESS')
);
} catch (error) {
useAlert(
this.$t('INBOX_MGMT.SETTINGS_POPUP.WHATSAPP_RECONFIGURE_ERROR')
);
} finally {
this.isReconfiguring = false;
}
},
async syncTemplates() {
this.isSyncingTemplates = true;
try {
@@ -358,6 +399,23 @@ export default {
>
<woot-code :script="inbox.provider_config.webhook_verify_token" />
</SettingsFieldSection>
<SettingsFieldSection
v-if="showWhatsAppReconfigure"
:label="
$t('INBOX_MGMT.SETTINGS_POPUP.WHATSAPP_EMBEDDED_SIGNUP_TITLE')
"
:help-text="
$t('INBOX_MGMT.SETTINGS_POPUP.WHATSAPP_EMBEDDED_SIGNUP_DESCRIPTION')
"
>
<NextButton
:is-loading="isReconfiguring"
:disabled="isReconfiguring"
@click="reconfigureWhatsApp"
>
{{ $t('INBOX_MGMT.SETTINGS_POPUP.WHATSAPP_RECONFIGURE_BUTTON') }}
</NextButton>
</SettingsFieldSection>
</template>
<!-- Manual Setup Section -->
@@ -47,7 +47,7 @@ class Whatsapp::EmbeddedSignupService
account: @account,
inbox_id: @inbox_id,
phone_number_id: @phone_number_id,
business_id: @business_id
waba_id: @waba_id
).perform(access_token, phone_info)
else
waba_info = { waba_id: @waba_id, business_name: phone_info[:business_name] }
@@ -1,9 +1,9 @@
class Whatsapp::ReauthorizationService
def initialize(account:, inbox_id:, phone_number_id:, business_id:)
def initialize(account:, inbox_id:, phone_number_id:, waba_id:)
@account = account
@inbox_id = inbox_id
@phone_number_id = phone_number_id
@business_id = business_id
@waba_id = waba_id
end
def perform(access_token, phone_info)
@@ -33,7 +33,7 @@ class Whatsapp::ReauthorizationService
channel.provider_config = current_config.merge(
'api_key' => access_token,
'phone_number_id' => resolved_phone_number_id,
'business_account_id' => @business_id,
'business_account_id' => @waba_id,
'source' => 'embedded_signup'
)
channel.save!
+4
View File
@@ -261,3 +261,7 @@
display_name: API and Webhooks
enabled: true
column: feature_flags_ext_1
- name: whatsapp_reconfigure
display_name: WhatsApp Reconfigure
enabled: false
column: feature_flags_ext_1
@@ -456,29 +456,18 @@ RSpec.describe 'WhatsApp Authorization API', type: :request do
create(:inbox_member, inbox: whatsapp_inbox, user: agent)
end
it 'returns unprocessable_entity error' do
it 'returns unauthorized error' do
allow(whatsapp_channel).to receive(:reauthorization_required?).and_return(true)
# Stub the embedded signup service to prevent HTTP calls
embedded_signup_service = instance_double(Whatsapp::EmbeddedSignupService)
allow(Whatsapp::EmbeddedSignupService).to receive(:new).with(
account: account,
params: {
code: 'test',
business_id: 'test',
waba_id: 'test'
},
inbox_id: whatsapp_inbox.id
).and_return(embedded_signup_service)
allow(embedded_signup_service).to receive(:perform).and_return(whatsapp_channel)
expect(Whatsapp::EmbeddedSignupService).not_to receive(:new)
post "/api/v1/accounts/#{account.id}/whatsapp/authorization",
params: { inbox_id: whatsapp_inbox.id, code: 'test', business_id: 'test', waba_id: 'test' },
headers: agent.create_new_auth_token,
as: :json
# Agents should get unprocessable_entity since they can find the inbox but channel doesn't need reauth
expect(response).to have_http_status(:unprocessable_entity)
# Reauthorizing an existing inbox swaps live credentials, so it is restricted to admins.
expect(response).to have_http_status(:unauthorized)
end
end
+1 -1
View File
@@ -118,7 +118,7 @@ RSpec.describe Account do
it 'configures the account feature flag extension column' do
expect(described_class.flag_columns).to include('feature_flags', 'feature_flags_ext_1')
expect(described_class.flag_mapping['feature_flags_ext_1']).to eq(feature_whatsapp_manual_transfer: 1, feature_data_import: 1 << 1,
feature_api_and_webhooks: 1 << 2)
feature_api_and_webhooks: 1 << 2, feature_whatsapp_reconfigure: 1 << 3)
expect(described_class.flag_mapping['feature_flags_ext_1'][:feature_whatsapp_manual_transfer]).to eq(1)
expect(described_class.flag_mapping['feature_flags_ext_1'][:feature_data_import]).to eq(2)
end
@@ -158,7 +158,7 @@ describe Whatsapp::EmbeddedSignupService do
account: account,
inbox_id: inbox_id,
phone_number_id: params[:phone_number_id],
business_id: params[:business_id]
waba_id: params[:waba_id]
).and_return(reauth_service)
allow(reauth_service).to receive(:perform).with(access_token, phone_info).and_return(channel)
@@ -212,7 +212,7 @@ describe Whatsapp::EmbeddedSignupService do
account: account,
inbox_id: inbox.id,
phone_number_id: params[:phone_number_id],
business_id: params[:business_id]
waba_id: params[:waba_id]
).and_return(reauth_service)
allow(reauth_service).to receive(:perform) do