feat: Remove subscription on WhatsApp inbox delete (#11977)
- remove webhook subscription while deleting a whatsapp inbox created via embedded signup Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
This commit is contained in:
committed by
GitHub
parent
420be64c45
commit
8262123481
@@ -34,6 +34,7 @@ class Channel::Whatsapp < ApplicationRecord
|
||||
|
||||
after_create :sync_templates
|
||||
after_create_commit :setup_webhooks
|
||||
before_destroy :teardown_webhooks
|
||||
|
||||
def name
|
||||
'Whatsapp'
|
||||
@@ -105,4 +106,8 @@ class Channel::Whatsapp < ApplicationRecord
|
||||
# Don't raise the error to prevent channel creation from failing
|
||||
# Webhooks can be retried later
|
||||
end
|
||||
|
||||
def teardown_webhooks
|
||||
Whatsapp::WebhookTeardownService.new(self).perform
|
||||
end
|
||||
end
|
||||
|
||||
@@ -63,6 +63,15 @@ class Whatsapp::FacebookApiClient
|
||||
handle_response(response, 'Webhook subscription failed')
|
||||
end
|
||||
|
||||
def unsubscribe_waba_webhook(waba_id)
|
||||
response = HTTParty.delete(
|
||||
"#{BASE_URI}/#{@api_version}/#{waba_id}/subscribed_apps",
|
||||
headers: request_headers
|
||||
)
|
||||
|
||||
handle_response(response, 'Webhook unsubscription failed')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def request_headers
|
||||
|
||||
47
app/services/whatsapp/webhook_teardown_service.rb
Normal file
47
app/services/whatsapp/webhook_teardown_service.rb
Normal file
@@ -0,0 +1,47 @@
|
||||
class Whatsapp::WebhookTeardownService
|
||||
def initialize(channel)
|
||||
@channel = channel
|
||||
end
|
||||
|
||||
def perform
|
||||
return unless should_teardown_webhook?
|
||||
|
||||
teardown_webhook
|
||||
rescue StandardError => e
|
||||
handle_webhook_teardown_error(e)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def should_teardown_webhook?
|
||||
whatsapp_cloud_provider? && embedded_signup_source? && webhook_config_present?
|
||||
end
|
||||
|
||||
def whatsapp_cloud_provider?
|
||||
@channel.provider == 'whatsapp_cloud'
|
||||
end
|
||||
|
||||
def embedded_signup_source?
|
||||
@channel.provider_config['source'] == 'embedded_signup'
|
||||
end
|
||||
|
||||
def webhook_config_present?
|
||||
@channel.provider_config['business_account_id'].present? &&
|
||||
@channel.provider_config['api_key'].present?
|
||||
end
|
||||
|
||||
def teardown_webhook
|
||||
waba_id = @channel.provider_config['business_account_id']
|
||||
access_token = @channel.provider_config['api_key']
|
||||
api_client = Whatsapp::FacebookApiClient.new(access_token)
|
||||
|
||||
api_client.unsubscribe_waba_webhook(waba_id)
|
||||
Rails.logger.info "[WHATSAPP] Webhook unsubscribed successfully for channel #{@channel.id}"
|
||||
end
|
||||
|
||||
def handle_webhook_teardown_error(error)
|
||||
Rails.logger.error "[WHATSAPP] Webhook teardown failed: #{error.message}"
|
||||
# Don't raise the error to prevent channel deletion from failing
|
||||
# Failed webhook teardown shouldn't block deletion
|
||||
end
|
||||
end
|
||||
@@ -122,4 +122,60 @@ RSpec.describe Channel::Whatsapp do
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#teardown_webhooks' do
|
||||
let(:account) { create(:account) }
|
||||
|
||||
context 'when channel is whatsapp_cloud with embedded_signup' do
|
||||
it 'calls WebhookTeardownService on destroy' do
|
||||
# Mock the setup service to prevent HTTP calls during creation
|
||||
setup_service = instance_double(Whatsapp::WebhookSetupService)
|
||||
allow(Whatsapp::WebhookSetupService).to receive(:new).and_return(setup_service)
|
||||
allow(setup_service).to receive(:perform)
|
||||
|
||||
channel = create(:channel_whatsapp,
|
||||
account: account,
|
||||
provider: 'whatsapp_cloud',
|
||||
provider_config: {
|
||||
'source' => 'embedded_signup',
|
||||
'business_account_id' => 'test_waba_id',
|
||||
'api_key' => 'test_access_token',
|
||||
'phone_number_id' => '123456789'
|
||||
},
|
||||
validate_provider_config: false,
|
||||
sync_templates: false)
|
||||
|
||||
teardown_service = instance_double(Whatsapp::WebhookTeardownService)
|
||||
allow(Whatsapp::WebhookTeardownService).to receive(:new).with(channel).and_return(teardown_service)
|
||||
allow(teardown_service).to receive(:perform)
|
||||
|
||||
channel.destroy
|
||||
|
||||
expect(Whatsapp::WebhookTeardownService).to have_received(:new).with(channel)
|
||||
expect(teardown_service).to have_received(:perform)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when channel is not embedded_signup' do
|
||||
it 'does not call WebhookTeardownService on destroy' do
|
||||
channel = create(:channel_whatsapp,
|
||||
account: account,
|
||||
provider: 'whatsapp_cloud',
|
||||
provider_config: {
|
||||
'source' => 'manual',
|
||||
'api_key' => 'test_access_token'
|
||||
},
|
||||
validate_provider_config: false,
|
||||
sync_templates: false)
|
||||
|
||||
teardown_service = instance_double(Whatsapp::WebhookTeardownService)
|
||||
allow(Whatsapp::WebhookTeardownService).to receive(:new).with(channel).and_return(teardown_service)
|
||||
allow(teardown_service).to receive(:perform)
|
||||
|
||||
channel.destroy
|
||||
|
||||
expect(teardown_service).to have_received(:perform)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -194,4 +194,41 @@ describe Whatsapp::FacebookApiClient do
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#unsubscribe_waba_webhook' do
|
||||
let(:waba_id) { 'test_waba_id' }
|
||||
|
||||
context 'when successful' do
|
||||
before do
|
||||
stub_request(:delete, "https://graph.facebook.com/#{api_version}/#{waba_id}/subscribed_apps")
|
||||
.with(
|
||||
headers: { 'Authorization' => "Bearer #{access_token}", 'Content-Type' => 'application/json' }
|
||||
)
|
||||
.to_return(
|
||||
status: 200,
|
||||
body: { success: true }.to_json,
|
||||
headers: { 'Content-Type' => 'application/json' }
|
||||
)
|
||||
end
|
||||
|
||||
it 'returns success response' do
|
||||
result = api_client.unsubscribe_waba_webhook(waba_id)
|
||||
expect(result['success']).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when failed' do
|
||||
before do
|
||||
stub_request(:delete, "https://graph.facebook.com/#{api_version}/#{waba_id}/subscribed_apps")
|
||||
.with(
|
||||
headers: { 'Authorization' => "Bearer #{access_token}", 'Content-Type' => 'application/json' }
|
||||
)
|
||||
.to_return(status: 400, body: { error: 'Webhook unsubscription failed' }.to_json)
|
||||
end
|
||||
|
||||
it 'raises an error' do
|
||||
expect { api_client.unsubscribe_waba_webhook(waba_id) }.to raise_error(/Webhook unsubscription failed/)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
81
spec/services/whatsapp/webhook_teardown_service_spec.rb
Normal file
81
spec/services/whatsapp/webhook_teardown_service_spec.rb
Normal file
@@ -0,0 +1,81 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Whatsapp::WebhookTeardownService do
|
||||
describe '#perform' do
|
||||
let(:channel) { create(:channel_whatsapp, validate_provider_config: false, sync_templates: false) }
|
||||
let(:service) { described_class.new(channel) }
|
||||
|
||||
context 'when channel is whatsapp_cloud with embedded_signup' do
|
||||
before do
|
||||
channel.update!(
|
||||
provider: 'whatsapp_cloud',
|
||||
provider_config: {
|
||||
'source' => 'embedded_signup',
|
||||
'business_account_id' => 'test_waba_id',
|
||||
'api_key' => 'test_api_key'
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
it 'calls unsubscribe_waba_webhook on Facebook API client' do
|
||||
api_client = instance_double(Whatsapp::FacebookApiClient)
|
||||
allow(Whatsapp::FacebookApiClient).to receive(:new).with('test_api_key').and_return(api_client)
|
||||
allow(api_client).to receive(:unsubscribe_waba_webhook).with('test_waba_id')
|
||||
|
||||
service.perform
|
||||
|
||||
expect(api_client).to have_received(:unsubscribe_waba_webhook).with('test_waba_id')
|
||||
end
|
||||
|
||||
it 'handles errors gracefully without raising' do
|
||||
api_client = instance_double(Whatsapp::FacebookApiClient)
|
||||
allow(Whatsapp::FacebookApiClient).to receive(:new).and_return(api_client)
|
||||
allow(api_client).to receive(:unsubscribe_waba_webhook).and_raise(StandardError, 'API Error')
|
||||
|
||||
expect { service.perform }.not_to raise_error
|
||||
end
|
||||
end
|
||||
|
||||
context 'when channel is not whatsapp_cloud' do
|
||||
before do
|
||||
channel.update!(provider: 'default')
|
||||
end
|
||||
|
||||
it 'does not attempt to unsubscribe webhook' do
|
||||
expect(Whatsapp::FacebookApiClient).not_to receive(:new)
|
||||
|
||||
service.perform
|
||||
end
|
||||
end
|
||||
|
||||
context 'when channel is whatsapp_cloud but not embedded_signup' do
|
||||
before do
|
||||
channel.update!(
|
||||
provider: 'whatsapp_cloud',
|
||||
provider_config: { 'source' => 'manual' }
|
||||
)
|
||||
end
|
||||
|
||||
it 'does not attempt to unsubscribe webhook' do
|
||||
expect(Whatsapp::FacebookApiClient).not_to receive(:new)
|
||||
|
||||
service.perform
|
||||
end
|
||||
end
|
||||
|
||||
context 'when required config is missing' do
|
||||
before do
|
||||
channel.update!(
|
||||
provider: 'whatsapp_cloud',
|
||||
provider_config: { 'source' => 'embedded_signup' }
|
||||
)
|
||||
end
|
||||
|
||||
it 'does not attempt to unsubscribe webhook' do
|
||||
expect(Whatsapp::FacebookApiClient).not_to receive(:new)
|
||||
|
||||
service.perform
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user