From 9007bf1ecf61ac768e21ca07cfaaca9fb8e0c8bc Mon Sep 17 00:00:00 2001 From: Tanmay Deep Sharma <32020192+tds-1@users.noreply.github.com> Date: Thu, 10 Jul 2025 03:09:16 +0700 Subject: [PATCH] feat: Add WhatsApp reauthorization bankend flow --- .../whatsapp/embedded_controller.rb | 43 ++++++++ .../whatsapp/embedded_signup_service.rb | 52 +++++++++- app/views/api/v1/models/_inbox.json.jbuilder | 1 + config/routes.rb | 1 + get_business_account_path.rb | 99 +++++++++++++++++++ 5 files changed, 195 insertions(+), 1 deletion(-) create mode 100755 get_business_account_path.rb diff --git a/app/controllers/whatsapp/embedded_controller.rb b/app/controllers/whatsapp/embedded_controller.rb index 625a11f3e..feb582cc2 100644 --- a/app/controllers/whatsapp/embedded_controller.rb +++ b/app/controllers/whatsapp/embedded_controller.rb @@ -32,6 +32,28 @@ class Whatsapp::EmbeddedController < ApplicationController handle_signup_error(e) end + def reauthorize + # Reauthorize existing WhatsApp inbox using embedded signup flow + validate_authorization_code! + return if performed? + + validate_required_parameters! + return if performed? + + validate_inbox_id! + return if performed? + + channel = process_reauthorization + @inbox = channel.inbox + + # Clear reauthorization required flag + channel.reauthorized! + + render json: { message: 'WhatsApp channel reauthorized successfully' }, status: :ok + rescue StandardError => e + handle_signup_error(e) + end + private def validate_authorization_code! @@ -51,6 +73,14 @@ class Whatsapp::EmbeddedController < ApplicationController }, status: :bad_request end + def validate_inbox_id! + return if params[:inbox_id].present? + + render json: { + error: 'Missing inbox_id parameter' + }, status: :bad_request + end + def process_signup service = Whatsapp::EmbeddedSignupService.new( account: Current.account, @@ -63,6 +93,19 @@ class Whatsapp::EmbeddedController < ApplicationController service.perform end + def process_reauthorization + service = Whatsapp::EmbeddedSignupService.new( + account: Current.account, + code: params[:code], + business_id: params[:business_id], + waba_id: params[:waba_id], + phone_number_id: params[:phone_number_id], + inbox_id: params[:inbox_id] + ) + + service.perform_reauthorization + end + def handle_signup_error(error) Rails.logger.error("WhatsApp embedded signup processing error: #{error.message}") Rails.logger.error(error.backtrace.join("\n")) diff --git a/app/services/whatsapp/embedded_signup_service.rb b/app/services/whatsapp/embedded_signup_service.rb index 95ecfac2b..d99b526dd 100644 --- a/app/services/whatsapp/embedded_signup_service.rb +++ b/app/services/whatsapp/embedded_signup_service.rb @@ -1,12 +1,13 @@ class Whatsapp::EmbeddedSignupService include Rails.application.routes.url_helpers - def initialize(account:, code:, business_id:, waba_id:, phone_number_id:) + def initialize(account:, code:, business_id:, waba_id:, phone_number_id:, inbox_id: nil) @account = account @code = code @business_id = business_id @waba_id = waba_id @phone_number_id = phone_number_id + @inbox_id = inbox_id end def perform @@ -33,6 +34,43 @@ class Whatsapp::EmbeddedSignupService raise e end + def perform_reauthorization + # Validate required parameters + unless @code.present? && @business_id.present? && @waba_id.present? && @phone_number_id.present? && @inbox_id.present? + raise ArgumentError, 'Code, business_id, waba_id, phone_number_id, and inbox_id are all required for reauthorization' + end + + # Find the existing inbox and channel + inbox = @account.inboxes.find_by(id: @inbox_id) + raise ActiveRecord::RecordNotFound, 'Inbox not found' unless inbox + raise ArgumentError, 'Inbox is not a WhatsApp channel' unless inbox.channel_type == 'Channel::Whatsapp' + + channel = inbox.channel + raise ArgumentError, 'Channel is not WhatsApp Cloud provider' unless channel.provider == 'whatsapp_cloud' + + GlobalConfig.clear_cache + # Exchange code for new access token + access_token = exchange_code_for_token + + # Use the provided business info directly + phone_info = fetch_phone_info_via_waba(@waba_id, @phone_number_id, access_token) + + # Validate that the token has access to the provided WABA + validate_token_waba_access(access_token, @waba_id) + + # Update the channel with new access token and configuration + update_channel_for_reauthorization(channel, phone_info, access_token) + + # Re-register webhook with new token + register_phone_number(phone_info[:phone_number_id], access_token) + override_waba_webhook(@waba_id, channel, access_token) + + channel + rescue StandardError => e + Rails.logger.error("[WHATSAPP] Reauthorization failed: #{e.message}") + raise e + end + private def whatsapp_api_version @@ -138,6 +176,18 @@ class Whatsapp::EmbeddedSignupService ) end + def update_channel_for_reauthorization(channel, phone_info, access_token) + # Update channel with new access token and configuration + channel.update!( + provider_config: channel.provider_config.merge( + 'api_key' => access_token, + 'phone_number_id' => phone_info[:phone_number_id], + 'business_account_id' => @waba_id, + 'reauthorized_at' => Time.current.iso8601 + ) + ) + end + def sanitize_phone_number(phone_number) return phone_number if phone_number.blank? diff --git a/app/views/api/v1/models/_inbox.json.jbuilder b/app/views/api/v1/models/_inbox.json.jbuilder index c66a6eb78..8042ee47c 100644 --- a/app/views/api/v1/models/_inbox.json.jbuilder +++ b/app/views/api/v1/models/_inbox.json.jbuilder @@ -116,4 +116,5 @@ json.provider resource.channel.try(:provider) if resource.whatsapp? json.message_templates resource.channel.try(:message_templates) json.provider_config resource.channel.try(:provider_config) if Current.account_user&.administrator? + json.reauthorization_required resource.channel.try(:reauthorization_required?) end diff --git a/config/routes.rb b/config/routes.rb index 75119414c..578e15e71 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -486,6 +486,7 @@ Rails.application.routes.draw do get 'signup', to: 'embedded#new' get 'signup/callback', to: 'embedded#callback' post 'embedded_signup', to: 'embedded#embedded_signup' + post 'reauthorize', to: 'embedded#reauthorize' end namespace :twitter do diff --git a/get_business_account_path.rb b/get_business_account_path.rb new file mode 100755 index 000000000..aef6ed500 --- /dev/null +++ b/get_business_account_path.rb @@ -0,0 +1,99 @@ +#!/usr/bin/env ruby + +# Script to get business account path for a specific inbox ID +# Usage: ruby get_business_account_path.rb + +# Set the inbox ID to query +INBOX_ID = 1090 + +# Load Rails environment (assuming this script is run from the Rails app root) +require_relative 'config/environment' + +def get_business_account_path(inbox_id) + # Find the inbox by ID + inbox = Inbox.find(inbox_id) + + # Check if this is a WhatsApp inbox + unless inbox.whatsapp? + puts "Error: Inbox #{inbox_id} is not a WhatsApp inbox. Channel type: #{inbox.channel_type}" + return nil + end + + # Get the WhatsApp channel + whatsapp_channel = inbox.channel + + # Check if it's a WhatsApp Cloud provider + unless whatsapp_channel.provider == 'whatsapp_cloud' + puts "Error: Inbox #{inbox_id} is not using WhatsApp Cloud provider. Provider: #{whatsapp_channel.provider}" + return nil + end + + # Get the business_account_id and api_key from provider_config + business_account_id = whatsapp_channel.provider_config['business_account_id'] + api_key = whatsapp_channel.provider_config['api_key'] + + if business_account_id.blank? + puts "Error: No business_account_id found in provider_config for inbox #{inbox_id}" + return nil + end + + if api_key.blank? + puts "Error: No api_key (access token) found in provider_config for inbox #{inbox_id}" + return nil + end + + # Construct the business account path (following the pattern from WhatsappCloudService) + api_base_path = ENV.fetch('WHATSAPP_CLOUD_BASE_URL', 'https://graph.facebook.com') + business_account_path = "#{api_base_path}/v14.0/#{business_account_id}" + + return { + inbox_id: inbox_id, + inbox_name: inbox.name, + business_account_id: business_account_id, + business_account_path: business_account_path, + provider: whatsapp_channel.provider, + phone_number: whatsapp_channel.phone_number, + api_key: api_key, + access_token: api_key # alias for clarity + } + +rescue ActiveRecord::RecordNotFound + puts "Error: Inbox with ID #{inbox_id} not found" + return nil +rescue StandardError => e + puts "Error: #{e.message}" + puts e.backtrace.first(5).join("\n") if ENV['DEBUG'] + return nil +end + +# Main execution +puts "Getting business account path for inbox ID: #{INBOX_ID}" +puts '=' * 50 + +result = get_business_account_path(INBOX_ID) + +if result + puts 'Success! Found WhatsApp Business Account details:' + puts + puts "Inbox ID: #{result[:inbox_id]}" + puts "Inbox Name: #{result[:inbox_name]}" + puts "Phone Number: #{result[:phone_number]}" + puts "Provider: #{result[:provider]}" + puts "Business Account ID: #{result[:business_account_id]}" + puts + puts 'Business Account Path:' + puts result[:business_account_path] + puts + puts 'Access Token (API Key):' + puts result[:access_token] + puts + puts 'Complete API Endpoint Examples:' + puts "• Message Templates: #{result[:business_account_path]}/message_templates?access_token=#{result[:access_token]}" + puts "• Phone Numbers: #{result[:business_account_path]}/phone_numbers?access_token=#{result[:access_token]}" + puts + puts 'Authorization Header Format:' + puts "Authorization: Bearer #{result[:access_token]}" +else + puts "Failed to get business account path for inbox ID #{INBOX_ID}" + exit 1 +end \ No newline at end of file