feat: make the changes

This commit is contained in:
Muhsin
2025-10-27 23:51:59 +05:30
parent f3176afc1c
commit 44ff579c5a
8 changed files with 426 additions and 4 deletions
@@ -44,7 +44,12 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
def update
inbox_params = permitted_params.except(:channel, :csat_config)
inbox_params[:csat_config] = format_csat_config(permitted_params[:csat_config]) if permitted_params[:csat_config].present?
if permitted_params[:csat_config].present?
inbox_params[:csat_config] = format_csat_config(permitted_params[:csat_config])
handle_whatsapp_csat_template_update(inbox_params[:csat_config]) if @inbox.whatsapp?
end
@inbox.update!(inbox_params)
update_inbox_working_hours
update_channel if channel_update_required?
@@ -87,6 +92,30 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
render json: { error: e.message }, status: :unprocessable_entity
end
def csat_template_status
return render json: { error: 'CSAT template status only available for WhatsApp channels' }, status: :bad_request unless @inbox.whatsapp?
template_config = @inbox.csat_config&.dig('template')
return render json: { template_exists: false } unless template_config
template_name = template_config['name'] || 'customer_satisfaction_survey'
status_result = @inbox.channel.provider_service.get_template_status(template_name)
if status_result[:success]
render json: {
template_exists: true,
template_name: template_name,
status: status_result[:template][:status],
template_id: status_result[:template][:id]
}
else
render json: { template_exists: false, error: status_result[:error] }
end
rescue StandardError => e
Rails.logger.error "Error fetching CSAT template status: #{e.message}"
render json: { error: e.message }, status: :internal_server_error
end
private
def fetch_inbox
@@ -151,8 +180,46 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
@inbox.channel.save!
end
def handle_whatsapp_csat_template_update(csat_config)
return unless @inbox.channel.is_a?(Channel::Whatsapp)
return if csat_config[:message].blank?
# Check if message has changed or template doesn't exist
existing_message = @inbox.csat_config&.dig('message')
template_exists = @inbox.csat_config&.dig('template').present?
create_whatsapp_csat_template(csat_config) if !template_exists || existing_message != csat_config[:message]
rescue StandardError => e
Rails.logger.error "Error handling WhatsApp CSAT template update: #{e.message}"
# Don't fail the entire update if template creation fails
end
def create_whatsapp_csat_template(csat_config)
template_config = {
message: csat_config[:message],
button_text: 'Please rate us',
base_url: ENV.fetch('FRONTEND_URL', 'http://localhost:3000'),
language: 'en'
}
result = @inbox.channel.provider_service.create_csat_template(template_config)
if result[:success]
# Add template info to csat_config
csat_config[:template] = {
name: 'customer_satisfaction_survey',
template_id: result[:template_id],
created_at: Time.current.iso8601,
language: 'en'
}
Rails.logger.info "WhatsApp CSAT template created successfully for inbox #{@inbox.id}"
else
Rails.logger.error "Failed to create WhatsApp CSAT template: #{result[:error]}"
end
end
def format_csat_config(config)
{
formatted = {
display_type: config['display_type'] || 'emoji',
message: config['message'] || '',
survey_rules: {
@@ -160,13 +227,20 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
values: config.dig('survey_rules', 'values') || []
}
}
# Preserve existing template config if present
formatted[:template] = config['template'] if config['template'].present?
formatted
end
def inbox_attributes
[:name, :avatar, :greeting_enabled, :greeting_message, :enable_email_collect, :csat_survey_enabled,
:enable_auto_assignment, :working_hours_enabled, :out_of_office_message, :timezone, :allow_messages_after_resolved,
:lock_to_single_conversation, :portal_id, :sender_name_type, :business_name,
{ csat_config: [:display_type, :message, { survey_rules: [:operator, { values: [] }] }] }]
{ csat_config: [:display_type, :message,
{ survey_rules: [:operator, { values: [] }],
template: [:name, :template_id, :created_at, :language] }] }]
end
def permitted_params(channel_attributes = [])
+69 -1
View File
@@ -4,7 +4,9 @@ class CsatSurveyService
def perform
return unless should_send_csat_survey?
if within_messaging_window?
if whatsapp_channel? && template_available_and_approved?
send_whatsapp_template_survey
elsif within_messaging_window?
::MessageTemplates::Template::CsatSurvey.new(conversation: conversation).perform
else
create_csat_not_sent_activity_message
@@ -35,6 +37,72 @@ class CsatSurveyService
conversation.can_reply?
end
def whatsapp_channel?
inbox.channel_type == 'Channel::Whatsapp'
end
def template_available_and_approved?
template_config = inbox.csat_config&.dig('template')
return false unless template_config
template_name = template_config['name'] || 'customer_satisfaction_survey'
status_result = inbox.channel.provider_service.get_template_status(template_name)
status_result[:success] && status_result[:template][:status] == 'APPROVED'
rescue StandardError => e
Rails.logger.error "Error checking CSAT template status: #{e.message}"
false
end
def send_whatsapp_template_survey
template_config = inbox.csat_config&.dig('template')
template_name = template_config['name'] || 'customer_satisfaction_survey'
phone_number = conversation.contact_inbox.source_id
template_info = build_template_info(template_name, template_config)
message = create_csat_message
inbox.channel.provider_service.send_template(phone_number, template_info, message)
rescue StandardError => e
Rails.logger.error "Error sending WhatsApp CSAT template: #{e.message}"
handle_template_send_failure
end
def build_template_info(template_name, template_config)
{
name: template_name,
lang_code: template_config['language'] || 'en',
parameters: [
{
type: 'button',
sub_type: 'url',
index: '0',
parameters: [{ type: 'text', text: conversation.uuid }]
}
]
}
end
def create_csat_message
message = conversation.messages.build(
account: conversation.account,
inbox: inbox,
message_type: :outgoing,
content: inbox.csat_config&.dig('message') || 'Please rate this conversation',
content_type: :input_csat
)
message.save!
message
end
def handle_template_send_failure
if within_messaging_window?
::MessageTemplates::Template::CsatSurvey.new(conversation: conversation).perform
else
create_csat_not_sent_activity_message
end
end
def create_csat_not_sent_activity_message
content = I18n.t('conversations.activity.csat.not_sent_due_to_messaging_window')
activity_message_params = {
@@ -58,6 +58,75 @@ class Whatsapp::Providers::WhatsappCloudService < Whatsapp::Providers::BaseServi
response.success?
end
def create_csat_template(template_config)
request_body = {
name: 'customer_satisfaction_survey',
language: template_config[:language] || 'en',
category: 'UTILITY',
components: [
{
type: 'BODY',
text: template_config[:message]
},
{
type: 'BUTTONS',
buttons: [
{
type: 'URL',
text: template_config[:button_text] || 'Please rate us',
url: "#{template_config[:base_url]}/survey/responses/{{1}}",
example: ['12345']
}
]
}
]
}
response = HTTParty.post(
"#{business_account_path}/message_templates",
headers: api_headers,
body: request_body.to_json
)
if response.success?
{
success: true,
template_id: response['id'],
template_name: 'customer_satisfaction_survey',
status: 'PENDING'
}
else
{
success: false,
error: error_message(response) || 'Failed to create template'
}
end
end
def get_template_status(template_name)
url = "#{business_account_path}/message_templates?name=#{template_name}&access_token=#{whatsapp_channel.provider_config['api_key']}"
response = HTTParty.get(url)
return { success: false, error: 'API request failed' } unless response.success?
templates = response['data'] || []
template = templates.find { |t| t['name'] == template_name }
if template
{
success: true,
template: {
id: template['id'],
name: template['name'],
status: template['status'],
language: template['language']
}
}
else
{ success: false, error: 'Template not found' }
end
end
def api_headers
{ 'Authorization' => "Bearer #{whatsapp_channel.provider_config['api_key']}", 'Content-Type' => 'application/json' }
end
+1
View File
@@ -196,6 +196,7 @@ Rails.application.routes.draw do
delete :avatar, on: :member
post :sync_templates, on: :member
get :health, on: :member
get :csat_template_status, on: :member
end
resources :inbox_members, only: [:create, :show], param: :inbox_id do
collection do
Binary file not shown.

After

Width:  |  Height:  |  Size: 689 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 197 KiB

+210
View File
@@ -0,0 +1,210 @@
# CSAT Surveys via WhatsApp Message Templates
## Overview
To ensure successful delivery of CSAT (Customer Satisfaction) surveys via WhatsApp, particularly after the 24-hour customer interaction window, messages must be sent using approved WhatsApp message templates. This spec outlines support for using templates, including automation and service integration.
## Problem
Messages sent after 24 hours without an approved template result in delivery failures. Currently, CSAT surveys are completely disabled for WhatsApp channels after the 24-hour messaging window expires.
## Solution
Support free-form content input and create WhatsApp message templates automatically in the background. Users configure their CSAT message through the existing UI, and the system handles template creation and approval workflow via WhatsApp Business API.
**Key Design Decisions:**
- Templates cannot be edited once submitted to WhatsApp - editing requires deleting the old template and creating a new one
- Template lifecycle is managed automatically - new templates overwrite previous ones
- **Template Priority**: Approved templates are always preferred over regular CSAT messages, regardless of messaging window
- Fallback to regular CSAT only when templates are unavailable and within messaging window
## Data Storage
Template information is stored in the channel's `csat_config` JSONB field:
```json
{
"message": "Hello! Can you please take this quick survey and provide us with your feedback.",
"display_type": "emoji",
"survey_rules": { ... },
"template": {
"name": "customer_satisfaction_survey",
"template_id": "123456789",
"created_at": "2024-01-01T00:00:00Z",
"language": "en"
}
}
```
**Template Naming:**
- **Hardcoded template name**: `customer_satisfaction_survey`
- **Single template per channel**: No multiple templates support
- **Language handling**: Deferred to future implementation
**Note:** Template status is checked in real-time via API calls, not stored in the database.
## Template Structure
Template uses conversation UUID as parameter:
- **Message**: User-configured message content
- **Button URL**: `{{base_url}}/survey/responses/{{1}}` where `{{1}}` is the conversation UUID
- **Button Text**: User-configured button text (default: "Please rate us")
#### Template Creation API
Send a POST request to the WhatsApp Business Account > Message Templates endpoint to create a template.
Request Syntax
POST /<WHATSAPP_BUSINESS_ACCOUNT_ID>/message_templates
Post Body
{
"name": "customer_satisfaction_survey",
"category": "MARKETING",
"language": "<LANGUAGE>",
"components": [<COMPONENTS>]
}
```
curl --location 'https://graph.facebook.com/v22.0/{{business_account_id}}/message_templates' \
--header 'Content-Type: application/json' \
--data '{
"name": "customer_satisfaction_survey",
"language": "en",
"category": "MARKETING",
"components": [
{
"type": "BODY",
"text": "Hello! Can you please take this quick survey and provide us with your feedback."
},
{
"type": "BUTTONS",
"buttons": [
{
"type": "URL",
"text": "Please rate us",
"url": "{{base_url}}/survey/responses/{{1}}",
"example": [
"12345"
]
}
]
}
]
}'
```
## Service Integration
### CsatSurveyService Modifications
Extend `app/services/csat_survey_service.rb` to handle WhatsApp templates:
1. **Template Check**: Check if template exists in `csat_config` and verify status via real-time API call
2. **Template Priority Logic**:
- If template exists and approved: Always send template (regardless of messaging window)
- If no template or not approved: Fall back to regular CSAT within messaging window
- If outside window and no approved template: Create activity message
3. **Survey Rules**: Apply existing label-based survey rules before sending
### WhatsApp Provider Integration
Modify `app/services/whatsapp/send_on_whatsapp_service.rb` to support CSAT templates:
- Add CSAT template sending capability
- Use conversation UUID as template parameter
- Handle template-specific error cases
### Template Management
**Creation Workflow:**
1. User updates CSAT configuration in settings
2. System creates template via WhatsApp Business API
3. Template info stored in `csat_config` (without status)
4. Old templates are automatically replaced
**Send Survey Logic:**
1. Conversation is resolved
2. Check existing survey rules (labels, etc.)
3. Check if template exists and get status via API call
4. **Template Priority:**
- If template approved: Send template (regardless of messaging window)
- If no template or not approved:
- Within messaging window: Send regular CSAT message
- Outside messaging window: Create activity message
## Error Handling & Fallback
**Template Creation Failures:**
- Display error message in frontend
- Log error details for debugging
- Continue using regular CSAT within messaging window
**Template Sending Failures:**
- Log failure reason
- Create activity message indicating survey couldn't be sent
- Track failure metrics for monitoring
**Fallback Strategy:**
- **Template Priority**: Always prefer approved templates over regular CSAT messages
- **No Template Available**:
- Within messaging window: Send regular CSAT message
- Outside messaging window: Create activity message
## Implementation Notes
**Scope:**
- WhatsApp Cloud API channels only (primary focus)
- 360Dialog provider is deprecated and not supported
- Future extension to other WhatsApp providers like twilio can be considered
**Provider Support:**
- Implement in `app/services/whatsapp/providers/whatsapp_cloud_service.rb`
- Use existing template management methods
## Template Status Checking
**Real-time Status API:**
Check template status before sending surveys:
```bash
curl --location 'https://graph.facebook.com/v20.0/{{business_account_id}}/message_templates?name={{template_name}}&access_token={{access_token}}'
```
Example:
```bash
curl --location 'https://graph.facebook.com/v20.0/1189403312549467/message_templates?name=customer_satisfaction_survey&access_token={{access_token}}'
```
**Response Format:**
```json
{
"data": [
{
"name": "customer_satisfaction_survey",
"status": "APPROVED|PENDING|REJECTED|DISABLED",
"id": "123456789",
"language": "en",
"category": "MARKETING"
}
]
}
```
**Implementation Points:**
1. **Frontend Configuration Page**: Check template status when user visits CSAT settings to show approval status
2. **Before Survey Sending**: Real-time API call to verify template is approved - if approved, always use template
3. **Caching Strategy**: Consider short-term caching (5-10 minutes) to avoid excessive API calls during high-volume periods
4. **Template Priority**: Approved templates bypass messaging window restrictions and are always sent
**Analytics:**
- Template usage tracking not included in initial implementation
- Regular CSAT analytics remain unchanged
- Can be added in future iterations
**Enterprise Compatibility:**
- No specific enterprise overrides required
- Standard CSAT enterprise policies apply