fix: inbound and outbound email
This commit is contained in:
@@ -100,6 +100,21 @@ const canInitiateWhatsappCall = computed(() => {
|
||||
return !!inbox.value?.calling_enabled;
|
||||
});
|
||||
|
||||
const waitForOutboundIceGathering = pc =>
|
||||
new Promise(resolve => {
|
||||
if (pc.iceGatheringState === 'complete') {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
const timeout = setTimeout(() => resolve(), 10000);
|
||||
pc.onicegatheringstatechange = () => {
|
||||
if (pc.iceGatheringState === 'complete') {
|
||||
clearTimeout(timeout);
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
const initiateWhatsappCall = async () => {
|
||||
if (isInitiatingCall.value || !currentChat.value?.id) return;
|
||||
isInitiatingCall.value = true;
|
||||
@@ -112,12 +127,31 @@ const initiateWhatsappCall = async () => {
|
||||
});
|
||||
localStream.getTracks().forEach(track => pc.addTrack(track, localStream));
|
||||
|
||||
// Handle remote audio from Meta
|
||||
pc.ontrack = event => {
|
||||
const [stream] = event.streams;
|
||||
if (!stream) return;
|
||||
const audio = document.createElement('audio');
|
||||
audio.srcObject = stream;
|
||||
audio.autoplay = true;
|
||||
document.body.appendChild(audio);
|
||||
window.__outboundCallAudio = audio;
|
||||
};
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
pc.oniceconnectionstatechange = () =>
|
||||
console.log('[WhatsApp Call] Outbound ICE state:', pc.iceConnectionState);
|
||||
|
||||
const offer = await pc.createOffer();
|
||||
await pc.setLocalDescription(offer);
|
||||
|
||||
// Wait for ICE gathering to complete before sending offer
|
||||
await waitForOutboundIceGathering(pc);
|
||||
const completeSdp = pc.localDescription.sdp;
|
||||
|
||||
const response = await WhatsappCallsAPI.initiate(
|
||||
currentChat.value.id,
|
||||
offer.sdp
|
||||
completeSdp
|
||||
);
|
||||
|
||||
const callStatus = response.data?.status;
|
||||
|
||||
@@ -52,13 +52,57 @@ export function useWhatsappCallSession() {
|
||||
}
|
||||
};
|
||||
|
||||
// Register cleanup callback so store can trigger WebRTC teardown on external events
|
||||
callsStore.registerCleanupCallback(() => {
|
||||
cleanupWebRTC();
|
||||
durationTimer.stop();
|
||||
callDuration.value = 0;
|
||||
});
|
||||
|
||||
/**
|
||||
* Waits for ICE candidate gathering to complete so the SDP contains all candidates.
|
||||
* Meta's REST API doesn't support trickle ICE — the full SDP must be sent at once.
|
||||
*/
|
||||
const waitForIceGatheringComplete = pc =>
|
||||
new Promise((resolve, reject) => {
|
||||
if (pc.iceGatheringState === 'complete') {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
// If gathering hasn't finished in 10s, send what we have
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(
|
||||
'[WhatsApp Call] ICE gathering timed out, sending partial SDP'
|
||||
);
|
||||
resolve();
|
||||
}, 10000);
|
||||
|
||||
pc.onicegatheringstatechange = () => {
|
||||
if (pc.iceGatheringState === 'complete') {
|
||||
clearTimeout(timeout);
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
|
||||
// Also reject if connection fails during gathering
|
||||
pc.oniceconnectionstatechange = () => {
|
||||
if (pc.iceConnectionState === 'failed') {
|
||||
clearTimeout(timeout);
|
||||
reject(new Error('ICE connection failed'));
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Accepts an incoming WhatsApp call:
|
||||
* 1. Requests mic access
|
||||
* 2. Creates RTCPeerConnection with ICE servers from the call payload
|
||||
* 3. Sets remote description (the SDP offer from Meta)
|
||||
* 4. Creates an SDP answer
|
||||
* 5. Posts the SDP answer to Chatwoot backend → Meta API
|
||||
* 5. Waits for ICE gathering to complete (Meta needs full SDP, no trickle ICE)
|
||||
* 6. Posts the complete SDP answer to Chatwoot backend → Meta API
|
||||
*/
|
||||
const acceptCall = async call => {
|
||||
if (isAccepting.value) return;
|
||||
@@ -82,14 +126,18 @@ export function useWhatsappCallSession() {
|
||||
peerConnection.addTrack(track, localStream);
|
||||
});
|
||||
|
||||
// 5. Handle remote audio stream → play via hidden <audio> element
|
||||
// 5. Handle remote audio stream → play via <audio> element
|
||||
peerConnection.ontrack = event => {
|
||||
const [stream] = event.streams;
|
||||
if (!stream) return;
|
||||
|
||||
if (remoteAudio.value) {
|
||||
remoteAudio.value.srcObject = stream;
|
||||
remoteAudio.value.play().catch(() => {});
|
||||
remoteAudio.value.play().catch(e => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn('[WhatsApp Call] Audio autoplay blocked:', e);
|
||||
});
|
||||
} else {
|
||||
// Fallback: create audio element dynamically
|
||||
const audio = document.createElement('audio');
|
||||
audio.srcObject = stream;
|
||||
audio.autoplay = true;
|
||||
@@ -98,24 +146,36 @@ export function useWhatsappCallSession() {
|
||||
}
|
||||
};
|
||||
|
||||
// 6. Set remote description from Meta's SDP offer
|
||||
// 6. Monitor ICE connection state for debugging
|
||||
peerConnection.oniceconnectionstatechange = () => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(
|
||||
'[WhatsApp Call] ICE state:',
|
||||
peerConnection?.iceConnectionState
|
||||
);
|
||||
};
|
||||
|
||||
// 7. Set remote description from Meta's SDP offer
|
||||
await peerConnection.setRemoteDescription({
|
||||
type: 'offer',
|
||||
sdp: call.sdpOffer,
|
||||
});
|
||||
|
||||
// 7. Create SDP answer
|
||||
// 8. Create SDP answer
|
||||
const answer = await peerConnection.createAnswer();
|
||||
await peerConnection.setLocalDescription(answer);
|
||||
|
||||
// 8. Post the SDP answer to Chatwoot backend
|
||||
await WhatsappCallsAPI.accept(call.id, answer.sdp);
|
||||
// 9. Wait for ICE gathering to complete so SDP has all candidates
|
||||
await waitForIceGatheringComplete(peerConnection);
|
||||
|
||||
// 9. Mark as active in store
|
||||
// 10. Post the COMPLETE SDP answer (with all ICE candidates) to backend
|
||||
const completeSdp = peerConnection.localDescription.sdp;
|
||||
await WhatsappCallsAPI.accept(call.id, completeSdp);
|
||||
|
||||
// 11. Mark as active in store
|
||||
callsStore.removeIncomingCall(call.callId);
|
||||
callsStore.setActiveCall({
|
||||
...call,
|
||||
peerConnection,
|
||||
});
|
||||
|
||||
durationTimer.start();
|
||||
@@ -124,6 +184,8 @@ export function useWhatsappCallSession() {
|
||||
err.name === 'NotAllowedError'
|
||||
? 'Microphone access denied. Please allow mic access and try again.'
|
||||
: 'Failed to accept call. Please try again.';
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('[WhatsApp Call] acceptCall error:', err);
|
||||
cleanupWebRTC();
|
||||
} finally {
|
||||
isAccepting.value = false;
|
||||
|
||||
@@ -38,6 +38,7 @@ class ActionCableConnector extends BaseActionCableConnector {
|
||||
'whatsapp_call.incoming': this.onWhatsappCallIncoming,
|
||||
'whatsapp_call.accepted': this.onWhatsappCallAccepted,
|
||||
'whatsapp_call.ended': this.onWhatsappCallEnded,
|
||||
'whatsapp_call.outbound_connected': this.onWhatsappCallOutboundConnected,
|
||||
'whatsapp_call.permission_granted': this.onWhatsappCallPermissionGranted,
|
||||
};
|
||||
}
|
||||
@@ -237,6 +238,20 @@ class ActionCableConnector extends BaseActionCableConnector {
|
||||
whatsappCallsStore.handleCallEnded(data.call_id);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
onWhatsappCallOutboundConnected = data => {
|
||||
// When Meta sends the SDP answer for an outbound call, set it on the peer connection
|
||||
const pc = window.__outboundCallPC;
|
||||
if (pc && window.__outboundCallId === data.call_id && data.sdp_answer) {
|
||||
pc.setRemoteDescription({ type: 'answer', sdp: data.sdp_answer }).catch(
|
||||
err => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('[WhatsApp Call] Failed to set remote SDP answer:', err);
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
onWhatsappCallPermissionGranted = data => {
|
||||
emitter.emit(BUS_EVENTS.SHOW_ALERT, {
|
||||
|
||||
@@ -6,11 +6,15 @@ export const useWhatsappCallsStore = defineStore('whatsappCalls', {
|
||||
incomingCalls: [],
|
||||
// The single active call (accepted + audio connected)
|
||||
activeCall: null,
|
||||
// Cleanup callback registered by the composable — called when a call ends externally
|
||||
_cleanupCallback: null,
|
||||
}),
|
||||
|
||||
getters: {
|
||||
hasIncomingCall: state => state.incomingCalls.length > 0,
|
||||
hasActiveCall: state => state.activeCall !== null,
|
||||
hasWhatsappCall: state =>
|
||||
state.incomingCalls.length > 0 || state.activeCall !== null,
|
||||
firstIncomingCall: state => state.incomingCalls[0] || null,
|
||||
},
|
||||
|
||||
@@ -33,8 +37,11 @@ export const useWhatsappCallsStore = defineStore('whatsappCalls', {
|
||||
this.activeCall = null;
|
||||
},
|
||||
|
||||
registerCleanupCallback(callback) {
|
||||
this._cleanupCallback = callback;
|
||||
},
|
||||
|
||||
handleCallAcceptedByOther(callId) {
|
||||
// Another agent accepted — remove from incoming list for this agent
|
||||
this.removeIncomingCall(callId);
|
||||
},
|
||||
|
||||
@@ -42,6 +49,24 @@ export const useWhatsappCallsStore = defineStore('whatsappCalls', {
|
||||
this.removeIncomingCall(callId);
|
||||
if (this.activeCall?.callId === callId) {
|
||||
this.activeCall = null;
|
||||
// Trigger WebRTC cleanup via the registered callback
|
||||
if (this._cleanupCallback) {
|
||||
this._cleanupCallback();
|
||||
}
|
||||
}
|
||||
// Also clean up outbound call globals if they match
|
||||
if (window.__outboundCallId === callId) {
|
||||
if (window.__outboundCallPC) window.__outboundCallPC.close();
|
||||
if (window.__outboundCallStream)
|
||||
window.__outboundCallStream.getTracks().forEach(t => t.stop());
|
||||
if (window.__outboundCallAudio) {
|
||||
window.__outboundCallAudio.srcObject = null;
|
||||
window.__outboundCallAudio.remove();
|
||||
}
|
||||
window.__outboundCallPC = null;
|
||||
window.__outboundCallStream = null;
|
||||
window.__outboundCallAudio = null;
|
||||
window.__outboundCallId = null;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
class Webhooks::WhatsappEventsJob < ApplicationJob
|
||||
queue_as :low
|
||||
queue_as :default
|
||||
|
||||
def perform(params = {})
|
||||
channel = find_channel_from_whatsapp_business_payload(params)
|
||||
@@ -123,6 +123,7 @@ class Webhooks::WhatsappEventsJob < ApplicationJob
|
||||
ActionCable.server.broadcast("account_#{channel.inbox.account_id}", {
|
||||
event: 'whatsapp_call.permission_granted',
|
||||
data: {
|
||||
account_id: channel.inbox.account_id,
|
||||
conversation_id: conversation.id,
|
||||
contact_name: contact.name,
|
||||
contact_phone: contact.phone_number
|
||||
|
||||
@@ -2,46 +2,51 @@ class Whatsapp::CallService
|
||||
pattr_initialize [:wa_call!, :agent!]
|
||||
|
||||
def pre_accept_and_accept(sdp_answer)
|
||||
ensure_ringing!
|
||||
ensure_not_already_taken!
|
||||
wa_call.with_lock do
|
||||
ensure_ringing!
|
||||
ensure_not_already_taken!
|
||||
|
||||
provider = wa_call.inbox.channel.provider_service
|
||||
call_id = wa_call.call_id
|
||||
provider = wa_call.inbox.channel.provider_service
|
||||
call_id = wa_call.call_id
|
||||
fixed_sdp = fix_sdp_setup(sdp_answer)
|
||||
|
||||
# Step 1: pre_accept
|
||||
pre_response = provider.pre_accept_call(call_id)
|
||||
raise "pre_accept failed: #{pre_response}" unless pre_response
|
||||
# Step 1: pre_accept (with SDP answer — required by Meta)
|
||||
pre_response = provider.pre_accept_call(call_id, fixed_sdp)
|
||||
raise Whatsapp::CallErrors::NotRinging, 'Meta pre_accept failed' unless pre_response
|
||||
|
||||
# Step 2: accept with SDP answer (fix setup attribute as required by Meta)
|
||||
fixed_sdp = fix_sdp_setup(sdp_answer)
|
||||
accept_response = provider.accept_call(call_id, fixed_sdp)
|
||||
raise "accept failed: #{accept_response}" unless accept_response
|
||||
# Step 2: accept with same SDP answer
|
||||
accept_response = provider.accept_call(call_id, fixed_sdp)
|
||||
raise Whatsapp::CallErrors::NotRinging, 'Meta accept failed' unless accept_response
|
||||
|
||||
wa_call.update!(
|
||||
status: 'accepted',
|
||||
accepted_by_agent_id: agent.id
|
||||
)
|
||||
wa_call.update!(
|
||||
status: 'accepted',
|
||||
accepted_by_agent_id: agent.id
|
||||
)
|
||||
end
|
||||
|
||||
broadcast_accepted
|
||||
wa_call
|
||||
end
|
||||
|
||||
def reject
|
||||
return if wa_call.terminal?
|
||||
return wa_call if wa_call.terminal?
|
||||
|
||||
provider = wa_call.inbox.channel.provider_service
|
||||
provider.reject_call(wa_call.call_id)
|
||||
success = provider.reject_call(wa_call.call_id)
|
||||
Rails.logger.error "[WHATSAPP CALL] reject_call API returned false for call #{wa_call.call_id}" unless success
|
||||
|
||||
# Update local status regardless — the call may have already ended on Meta's side
|
||||
wa_call.update!(status: 'rejected')
|
||||
broadcast_call_ended
|
||||
wa_call
|
||||
end
|
||||
|
||||
def terminate
|
||||
return if wa_call.terminal?
|
||||
return wa_call if wa_call.terminal?
|
||||
|
||||
provider = wa_call.inbox.channel.provider_service
|
||||
provider.terminate_call(wa_call.call_id)
|
||||
success = provider.terminate_call(wa_call.call_id)
|
||||
Rails.logger.error "[WHATSAPP CALL] terminate_call API returned false for call #{wa_call.call_id}" unless success
|
||||
|
||||
wa_call.update!(status: 'ended')
|
||||
broadcast_call_ended
|
||||
@@ -66,6 +71,7 @@ class Whatsapp::CallService
|
||||
payload = {
|
||||
event: 'whatsapp_call.accepted',
|
||||
data: {
|
||||
account_id: wa_call.account_id,
|
||||
id: wa_call.id,
|
||||
call_id: wa_call.call_id,
|
||||
accepted_by_agent_id: agent.id,
|
||||
@@ -79,6 +85,7 @@ class Whatsapp::CallService
|
||||
payload = {
|
||||
event: 'whatsapp_call.ended',
|
||||
data: {
|
||||
account_id: wa_call.account_id,
|
||||
id: wa_call.id,
|
||||
call_id: wa_call.call_id,
|
||||
status: wa_call.status,
|
||||
|
||||
@@ -16,26 +16,40 @@ class Whatsapp::IncomingCallService
|
||||
event = call_payload[:event]
|
||||
|
||||
case event
|
||||
when 'call_connect'
|
||||
when 'connect'
|
||||
handle_call_connect(call_payload)
|
||||
when 'call_terminate'
|
||||
when 'terminate'
|
||||
handle_call_terminate(call_payload)
|
||||
else
|
||||
Rails.logger.warn "[WHATSAPP CALL] Unknown call event: #{event}"
|
||||
end
|
||||
end
|
||||
|
||||
def handle_call_connect(call_payload)
|
||||
call_id = call_payload[:id]
|
||||
direction = map_direction(call_payload[:direction])
|
||||
|
||||
# For outbound calls, a WhatsappCall record already exists from initiate.
|
||||
# Update it instead of creating a duplicate.
|
||||
existing_call = WhatsappCall.find_by(call_id: call_id)
|
||||
if existing_call
|
||||
Rails.logger.info "[WHATSAPP CALL] call_connect for existing call #{call_id} (direction=#{direction})"
|
||||
existing_call.update!(meta: existing_call.meta.merge('sdp_answer' => call_payload.dig(:session, :sdp)))
|
||||
broadcast_outbound_call_connected(existing_call, call_payload.dig(:session, :sdp))
|
||||
return
|
||||
end
|
||||
|
||||
contact = find_or_create_contact("+#{call_payload[:from]}")
|
||||
return unless contact
|
||||
|
||||
conversation = find_or_create_conversation(contact)
|
||||
return unless conversation
|
||||
|
||||
direction = call_payload.fetch(:direction, 'inbound')
|
||||
wa_call = create_call_record(call_payload, conversation, direction)
|
||||
create_call_activity_message(conversation, 'incoming_call', direction)
|
||||
broadcast_incoming_call(wa_call, contact, call_payload.dig(:session, :sdp))
|
||||
rescue ActiveRecord::RecordNotUnique
|
||||
Rails.logger.warn "[WHATSAPP CALL] Duplicate call_id received: #{call_payload[:id]}"
|
||||
Rails.logger.warn "[WHATSAPP CALL] Duplicate call_id received: #{call_id}"
|
||||
end
|
||||
|
||||
def create_call_record(call_payload, conversation, direction)
|
||||
@@ -142,6 +156,7 @@ class Whatsapp::IncomingCallService
|
||||
payload = {
|
||||
event: 'whatsapp_call.incoming',
|
||||
data: {
|
||||
account_id: inbox.account_id,
|
||||
id: wa_call.id,
|
||||
call_id: wa_call.call_id,
|
||||
direction: wa_call.direction,
|
||||
@@ -164,6 +179,7 @@ class Whatsapp::IncomingCallService
|
||||
payload = {
|
||||
event: 'whatsapp_call.ended',
|
||||
data: {
|
||||
account_id: inbox.account_id,
|
||||
id: wa_call.id,
|
||||
call_id: wa_call.call_id,
|
||||
status: wa_call.status,
|
||||
@@ -175,6 +191,30 @@ class Whatsapp::IncomingCallService
|
||||
ActionCable.server.broadcast("account_#{inbox.account_id}", payload)
|
||||
end
|
||||
|
||||
def broadcast_outbound_call_connected(wa_call, sdp_answer)
|
||||
payload = {
|
||||
event: 'whatsapp_call.outbound_connected',
|
||||
data: {
|
||||
account_id: inbox.account_id,
|
||||
id: wa_call.id,
|
||||
call_id: wa_call.call_id,
|
||||
conversation_id: wa_call.conversation_id,
|
||||
sdp_answer: sdp_answer
|
||||
}
|
||||
}
|
||||
|
||||
ActionCable.server.broadcast("account_#{inbox.account_id}", payload)
|
||||
end
|
||||
|
||||
# Meta sends "USER_INITIATED" / "BUSINESS_INITIATED", map to our model values
|
||||
def map_direction(raw_direction)
|
||||
case raw_direction&.upcase
|
||||
when 'USER_INITIATED' then 'inbound'
|
||||
when 'BUSINESS_INITIATED' then 'outbound'
|
||||
else 'inbound'
|
||||
end
|
||||
end
|
||||
|
||||
def default_ice_servers
|
||||
[{ urls: 'stun:stun.l.google.com:19302' }]
|
||||
end
|
||||
|
||||
@@ -79,44 +79,48 @@ class Whatsapp::Providers::WhatsappCloudService < Whatsapp::Providers::BaseServi
|
||||
"#{api_base_path}/v13.0/#{media_id}"
|
||||
end
|
||||
|
||||
def pre_accept_call(call_id)
|
||||
response = HTTParty.post(
|
||||
"#{phone_id_path}/calls/#{call_id}",
|
||||
headers: api_headers,
|
||||
body: { action: 'pre_accept' }.to_json
|
||||
)
|
||||
response.success?
|
||||
def pre_accept_call(call_id, sdp_answer)
|
||||
body = {
|
||||
messaging_product: 'whatsapp',
|
||||
call_id: call_id,
|
||||
action: 'pre_accept',
|
||||
session: {
|
||||
sdp: sdp_answer,
|
||||
sdp_type: 'answer'
|
||||
}
|
||||
}
|
||||
call_api('pre_accept_call', body)
|
||||
end
|
||||
|
||||
def accept_call(call_id, sdp_answer)
|
||||
response = HTTParty.post(
|
||||
"#{phone_id_path}/calls/#{call_id}",
|
||||
headers: api_headers,
|
||||
body: {
|
||||
action: 'accept',
|
||||
body = {
|
||||
messaging_product: 'whatsapp',
|
||||
call_id: call_id,
|
||||
action: 'accept',
|
||||
session: {
|
||||
sdp: sdp_answer,
|
||||
sdp_type: 'answer'
|
||||
}.to_json
|
||||
)
|
||||
response.success?
|
||||
}
|
||||
}
|
||||
call_api('accept_call', body)
|
||||
end
|
||||
|
||||
def reject_call(call_id)
|
||||
response = HTTParty.post(
|
||||
"#{phone_id_path}/calls/#{call_id}",
|
||||
headers: api_headers,
|
||||
body: { action: 'reject' }.to_json
|
||||
)
|
||||
response.success?
|
||||
body = {
|
||||
messaging_product: 'whatsapp',
|
||||
call_id: call_id,
|
||||
action: 'reject'
|
||||
}
|
||||
call_api('reject_call', body)
|
||||
end
|
||||
|
||||
def terminate_call(call_id)
|
||||
response = HTTParty.post(
|
||||
"#{phone_id_path}/calls/#{call_id}",
|
||||
headers: api_headers,
|
||||
body: { action: 'terminate' }.to_json
|
||||
)
|
||||
response.success?
|
||||
body = {
|
||||
messaging_product: 'whatsapp',
|
||||
call_id: call_id,
|
||||
action: 'terminate'
|
||||
}
|
||||
call_api('terminate_call', body)
|
||||
end
|
||||
|
||||
def send_call_permission_request(to_phone_number, body_text = 'We would like to call you regarding your conversation.')
|
||||
@@ -169,6 +173,16 @@ class Whatsapp::Providers::WhatsappCloudService < Whatsapp::Providers::BaseServi
|
||||
|
||||
private
|
||||
|
||||
def call_api(action_name, body)
|
||||
url = "#{phone_id_path}/calls"
|
||||
Rails.logger.info "[WHATSAPP CALL] #{action_name} POST #{url} body=#{body.except(:session).to_json}"
|
||||
response = HTTParty.post(url, headers: api_headers, body: body.to_json)
|
||||
unless response.success?
|
||||
Rails.logger.error "[WHATSAPP CALL] #{action_name} failed: status=#{response.code} body=#{response.body}"
|
||||
end
|
||||
response.success?
|
||||
end
|
||||
|
||||
def csat_template_service
|
||||
@csat_template_service ||= Whatsapp::CsatTemplateService.new(whatsapp_channel)
|
||||
end
|
||||
@@ -177,9 +191,9 @@ class Whatsapp::Providers::WhatsappCloudService < Whatsapp::Providers::BaseServi
|
||||
ENV.fetch('WHATSAPP_CLOUD_BASE_URL', 'https://graph.facebook.com')
|
||||
end
|
||||
|
||||
# TODO: See if we can unify the API versions and for both paths and make it consistent with out facebook app API versions
|
||||
def phone_id_path
|
||||
"#{api_base_path}/v13.0/#{whatsapp_channel.provider_config['phone_number_id']}"
|
||||
api_version = GlobalConfigService.load('WHATSAPP_API_VERSION', 'v22.0')
|
||||
"#{api_base_path}/#{api_version}/#{whatsapp_channel.provider_config['phone_number_id']}"
|
||||
end
|
||||
|
||||
def business_account_path
|
||||
|
||||
Reference in New Issue
Block a user