Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
882fd8be78 | ||
|
|
a59598ee15 | ||
|
|
8e499904d0 | ||
|
|
4d6810722e | ||
|
|
9b58e8940a | ||
|
|
b17ff51c8e | ||
|
|
a25113cea5 | ||
|
|
86582569ee |
@@ -0,0 +1,77 @@
|
|||||||
|
class Api::V1::Accounts::Whatsapp::ManualSetupController < Api::V1::Accounts::BaseController
|
||||||
|
before_action :authorize_create, only: [:preview, :connect]
|
||||||
|
before_action :fetch_inbox, only: [:webhook_status, :setup_webhook]
|
||||||
|
|
||||||
|
def preview
|
||||||
|
render json: validation_service.perform
|
||||||
|
rescue StandardError => e
|
||||||
|
render_setup_error(e)
|
||||||
|
end
|
||||||
|
|
||||||
|
def connect
|
||||||
|
setup = Whatsapp::ManualSetupService.new(account: Current.account, **connect_params.to_h.symbolize_keys).perform
|
||||||
|
render json: connection_response(setup), status: :created
|
||||||
|
rescue CustomExceptions::Inbox::LimitExceeded => e
|
||||||
|
render_error_response(e)
|
||||||
|
rescue StandardError => e
|
||||||
|
render_setup_error(e)
|
||||||
|
end
|
||||||
|
|
||||||
|
def webhook_status
|
||||||
|
render json: Whatsapp::ManualWebhookStatusService.new(@inbox.channel).perform
|
||||||
|
rescue StandardError => e
|
||||||
|
render_setup_error(e)
|
||||||
|
end
|
||||||
|
|
||||||
|
def setup_webhook
|
||||||
|
channel = @inbox.channel
|
||||||
|
Whatsapp::WebhookSetupService.new(channel).register_callback
|
||||||
|
render json: Whatsapp::ManualWebhookStatusService.new(channel.reload).perform
|
||||||
|
rescue StandardError => e
|
||||||
|
render_setup_error(e)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def authorize_create
|
||||||
|
authorize ::Inbox, :create?
|
||||||
|
end
|
||||||
|
|
||||||
|
def fetch_inbox
|
||||||
|
@inbox = Current.account.inboxes.find(params[:inbox_id])
|
||||||
|
authorize @inbox, :update?
|
||||||
|
channel = @inbox.channel
|
||||||
|
return if channel.is_a?(Channel::Whatsapp) && channel.provider_config['source'] == 'manual_setup_v2'
|
||||||
|
|
||||||
|
raise ActiveRecord::RecordNotFound
|
||||||
|
end
|
||||||
|
|
||||||
|
def validation_service
|
||||||
|
Whatsapp::ManualSetupValidationService.new(**connection_params.to_h.symbolize_keys)
|
||||||
|
end
|
||||||
|
|
||||||
|
def connection_params
|
||||||
|
params.permit(:waba_id, :phone_number_id, :access_token)
|
||||||
|
end
|
||||||
|
|
||||||
|
def connect_params
|
||||||
|
params.permit(:waba_id, :phone_number_id, :access_token, :inbox_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
def connection_response(setup)
|
||||||
|
channel = setup.channel.reload
|
||||||
|
{
|
||||||
|
id: channel.inbox.id,
|
||||||
|
name: channel.inbox.name,
|
||||||
|
number_access: true,
|
||||||
|
template_access: true,
|
||||||
|
webhook_setup: setup.webhook_setup?,
|
||||||
|
webhook_error: setup.webhook_error
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def render_setup_error(error)
|
||||||
|
Rails.logger.error "[WHATSAPP MANUAL SETUP] account_id=#{Current.account.id} error=#{error.class}: #{error.message}"
|
||||||
|
render json: { message: error.message }, status: :unprocessable_entity
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -16,6 +16,26 @@ class WhatsappChannel extends ApiClient {
|
|||||||
inbox_id: inboxId,
|
inbox_id: inboxId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
previewManualSetup(params) {
|
||||||
|
return axios.post(`${this.baseUrl()}/whatsapp/manual/preview`, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
connectManualSetup(params) {
|
||||||
|
return axios.post(`${this.baseUrl()}/whatsapp/manual/connect`, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
getManualWebhookStatus(inboxId) {
|
||||||
|
return axios.get(
|
||||||
|
`${this.baseUrl()}/whatsapp/manual/${inboxId}/webhook_status`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
setupManualWebhook(inboxId) {
|
||||||
|
return axios.post(
|
||||||
|
`${this.baseUrl()}/whatsapp/manual/${inboxId}/setup_webhook`
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new WhatsappChannel();
|
export default new WhatsappChannel();
|
||||||
|
|||||||
@@ -293,6 +293,100 @@
|
|||||||
"WEBHOOK_URL": "Webhook URL",
|
"WEBHOOK_URL": "Webhook URL",
|
||||||
"WEBHOOK_VERIFICATION_TOKEN": "Webhook Verification Token"
|
"WEBHOOK_VERIFICATION_TOKEN": "Webhook Verification Token"
|
||||||
},
|
},
|
||||||
|
"MANUAL_SETUP": {
|
||||||
|
"HEADER": {
|
||||||
|
"TITLE": "Connect WhatsApp manually",
|
||||||
|
"DESCRIPTION": "Follow these steps to prepare your Meta account and connect your number to Chatwoot."
|
||||||
|
},
|
||||||
|
"APP": {
|
||||||
|
"TITLE": "Create or select a Meta app",
|
||||||
|
"DESCRIPTION": "Your WhatsApp number must belong to a Meta app with the WhatsApp use case enabled.",
|
||||||
|
"ITEM_1": "Open {metaDevelopers} and sign in with an administrator account.",
|
||||||
|
"META_DEVELOPERS": "Meta Developers",
|
||||||
|
"ITEM_2": "Create a new app, or select the app you already use for this WhatsApp number.",
|
||||||
|
"ITEM_3": "Choose the option to connect with customers through WhatsApp.",
|
||||||
|
"ITEM_4": "Select the business portfolio that owns, or will own, the WhatsApp number.",
|
||||||
|
"VIDEO_TITLE": "Watch: Create a Meta app",
|
||||||
|
"VIDEO_DESCRIPTION": "This short walkthrough shows where to start a new app in Meta Developers."
|
||||||
|
},
|
||||||
|
"NUMBER": {
|
||||||
|
"TITLE": "Add your phone number and get its IDs",
|
||||||
|
"DESCRIPTION": "Add and verify the production number in your Meta app, then copy the two identifiers shown in API Setup.",
|
||||||
|
"ITEM_1": "Open the WhatsApp use case in your Meta app and choose API Setup.",
|
||||||
|
"ITEM_2": "In the Send and receive messages section, open the From selector.",
|
||||||
|
"ITEM_3": "Select an existing production number, or choose Add phone number.",
|
||||||
|
"ITEM_4": "Complete the WhatsApp business profile requested by Meta.",
|
||||||
|
"ITEM_5": "Verify the phone number using the OTP sent by SMS or voice call.",
|
||||||
|
"ITEM_6": "Copy the Phone Number ID and WhatsApp Business Account ID shown in API Setup.",
|
||||||
|
"VIDEO_TITLE": "Watch: Add a phone number and find its IDs",
|
||||||
|
"VIDEO_DESCRIPTION": "This walkthrough shows how to add or select a production number and copy the identifiers from Meta."
|
||||||
|
},
|
||||||
|
"TOKEN": {
|
||||||
|
"TITLE": "Generate a permanent access token",
|
||||||
|
"DESCRIPTION": "Create a Meta system user with access to your app and WhatsApp Business Account.",
|
||||||
|
"ITEM_1": "Open Meta Business Settings and go to Users → System users.",
|
||||||
|
"ITEM_2": "Create an admin system user, or select an existing one.",
|
||||||
|
"ITEM_3": "Assign your Meta app and WhatsApp Business Account to the system user.",
|
||||||
|
"ITEM_4": "Grant full control for the assigned WhatsApp assets.",
|
||||||
|
"ITEM_5": "Generate a token for your Meta app and set its expiration to Never.",
|
||||||
|
"ITEM_6": "Select whatsapp_business_management and whatsapp_business_messaging, then copy the token.",
|
||||||
|
"VIDEO_TITLE": "Watch: Generate a permanent access token",
|
||||||
|
"VIDEO_DESCRIPTION": "This walkthrough shows how to select your Meta app, choose a non-expiring token, and grant the required WhatsApp permissions.",
|
||||||
|
"WARNING": "Meta shows the token only once. Copy it before closing the dialog."
|
||||||
|
},
|
||||||
|
"DETAILS": {
|
||||||
|
"WABA_LABEL": "WhatsApp Business Account ID",
|
||||||
|
"WABA_PLACEHOLDER": "Enter WABA ID",
|
||||||
|
"PHONE_ID_LABEL": "Phone Number ID",
|
||||||
|
"PHONE_ID_PLACEHOLDER": "Enter Phone Number ID",
|
||||||
|
"TOKEN_LABEL": "Permanent access token",
|
||||||
|
"TOKEN_PLACEHOLDER": "Paste access token"
|
||||||
|
},
|
||||||
|
"REVIEW": {
|
||||||
|
"TITLE": "Create your WhatsApp inbox",
|
||||||
|
"DESCRIPTION": "Review the number details and choose a name for the inbox.",
|
||||||
|
"VERIFIED": "Your Meta number and access token are valid.",
|
||||||
|
"BUSINESS_NAME": "Business name",
|
||||||
|
"PHONE_NUMBER": "Phone number",
|
||||||
|
"PHONE_ID": "Phone Number ID",
|
||||||
|
"WABA_ID": "WhatsApp Business Account ID",
|
||||||
|
"INBOX_NAME": "Inbox name",
|
||||||
|
"INBOX_NAME_HELP": "We generated this name from your verified Meta business name. You can change it."
|
||||||
|
},
|
||||||
|
"VERIFY": {
|
||||||
|
"TITLE": "Verify your connection",
|
||||||
|
"DESCRIPTION": "Chatwoot is checking number access and configuring your Meta webhook.",
|
||||||
|
"NUMBER_ACCESS": "Number access",
|
||||||
|
"TEMPLATE_ACCESS": "Template access",
|
||||||
|
"CALLBACK": "Webhook callback",
|
||||||
|
"SUBSCRIPTION": "Webhook subscription",
|
||||||
|
"WEBHOOK_URL": "Webhook URL",
|
||||||
|
"COPY": "Copy",
|
||||||
|
"COPY_SUCCESS": "Webhook URL copied to clipboard",
|
||||||
|
"COMPLETE": "Verified",
|
||||||
|
"PENDING": "Pending",
|
||||||
|
"SUCCESS": "Your WhatsApp number is connected and ready for agent assignment."
|
||||||
|
},
|
||||||
|
"ACTIONS": {
|
||||||
|
"BACK": "Back",
|
||||||
|
"OPEN_META_APPS": "Open Meta Apps",
|
||||||
|
"APP_READY": "My Meta app is ready",
|
||||||
|
"NEXT": "Next",
|
||||||
|
"OPEN_BUSINESS_SETTINGS": "Open Meta Business Settings",
|
||||||
|
"SHOW_TOKEN": "Show token",
|
||||||
|
"HIDE_TOKEN": "Hide token",
|
||||||
|
"VERIFY_DETAILS": "Verify details",
|
||||||
|
"CONNECT": "Create inbox",
|
||||||
|
"RETRY_WEBHOOK": "Retry webhook setup",
|
||||||
|
"CONTINUE": "Continue to add agents"
|
||||||
|
},
|
||||||
|
"ERRORS": {
|
||||||
|
"IDS_REQUIRED": "Enter the Phone Number ID and WhatsApp Business Account ID.",
|
||||||
|
"REQUIRED": "Enter the WABA ID, Phone Number ID, and permanent access token.",
|
||||||
|
"INVALID_TOKEN": "This access token is invalid or expired. Generate a new permanent token and try again.",
|
||||||
|
"GENERIC": "We could not complete the WhatsApp setup. Check your details and try again."
|
||||||
|
}
|
||||||
|
},
|
||||||
"SUBMIT_BUTTON": "Create WhatsApp Channel",
|
"SUBMIT_BUTTON": "Create WhatsApp Channel",
|
||||||
"EMBEDDED_SIGNUP": {
|
"EMBEDDED_SIGNUP": {
|
||||||
"TITLE": "Quick setup with Meta",
|
"TITLE": "Quick setup with Meta",
|
||||||
|
|||||||
@@ -49,9 +49,10 @@ const hasDuplicateInstagramInbox = computed(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const shouldShowWhatsAppWebhookDetails = computed(() => {
|
const shouldShowWhatsAppWebhookDetails = computed(() => {
|
||||||
|
const source = currentInbox.value.provider_config?.source;
|
||||||
return (
|
return (
|
||||||
isAWhatsAppCloudChannel.value &&
|
isAWhatsAppCloudChannel.value &&
|
||||||
currentInbox.value.provider_config?.source !== 'embedded_signup'
|
!['embedded_signup', 'manual_setup_v2'].includes(source)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ const items = computed(() => {
|
|||||||
:global-config="globalConfig"
|
:global-config="globalConfig"
|
||||||
:items="items"
|
:items="items"
|
||||||
/>
|
/>
|
||||||
<div class="col-span-6 flex flex-col overflow-y-auto">
|
<div class="col-span-6 flex min-h-0 flex-col overflow-y-auto">
|
||||||
<router-view />
|
<router-view />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { useI18n, I18nT } from 'vue-i18n';
|
|||||||
import Twilio from './Twilio.vue';
|
import Twilio from './Twilio.vue';
|
||||||
import ThreeSixtyDialogWhatsapp from './360DialogWhatsapp.vue';
|
import ThreeSixtyDialogWhatsapp from './360DialogWhatsapp.vue';
|
||||||
import CloudWhatsapp from './CloudWhatsapp.vue';
|
import CloudWhatsapp from './CloudWhatsapp.vue';
|
||||||
|
import WhatsappManualSetup from './WhatsappManualSetup.vue';
|
||||||
import WhatsappEmbeddedSignup from './WhatsappEmbeddedSignup.vue';
|
import WhatsappEmbeddedSignup from './WhatsappEmbeddedSignup.vue';
|
||||||
import ChannelSelector from 'dashboard/components/ChannelSelector.vue';
|
import ChannelSelector from 'dashboard/components/ChannelSelector.vue';
|
||||||
import { useAccount } from 'dashboard/composables/useAccount';
|
import { useAccount } from 'dashboard/composables/useAccount';
|
||||||
@@ -79,14 +80,21 @@ const shouldShowCloudWhatsapp = provider => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isManualSetup = computed(
|
||||||
|
() =>
|
||||||
|
showConfiguration.value && shouldShowCloudWhatsapp(selectedProvider.value)
|
||||||
|
);
|
||||||
|
|
||||||
const handleManualLinkClick = () => {
|
const handleManualLinkClick = () => {
|
||||||
selectProvider(PROVIDER_TYPES.WHATSAPP_MANUAL);
|
selectProvider(PROVIDER_TYPES.WHATSAPP_MANUAL);
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="overflow-auto col-span-6 p-6 w-full h-full">
|
<div class="col-span-6 w-full h-full min-h-0 overflow-y-auto p-6">
|
||||||
<div v-if="showProviderSelection">
|
<WhatsappManualSetup v-if="isManualSetup" />
|
||||||
|
|
||||||
|
<div v-else-if="showProviderSelection">
|
||||||
<div class="mb-10 text-left">
|
<div class="mb-10 text-left">
|
||||||
<h1 class="mb-2 text-lg font-medium text-n-slate-12">
|
<h1 class="mb-2 text-lg font-medium text-n-slate-12">
|
||||||
{{ $t('INBOX_MGMT.ADD.WHATSAPP.SELECT_PROVIDER.TITLE') }}
|
{{ $t('INBOX_MGMT.ADD.WHATSAPP.SELECT_PROVIDER.TITLE') }}
|
||||||
@@ -137,9 +145,6 @@ const handleManualLinkClick = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Show manual setup -->
|
|
||||||
<CloudWhatsapp v-else-if="shouldShowCloudWhatsapp(selectedProvider)" />
|
|
||||||
|
|
||||||
<!-- Other providers -->
|
<!-- Other providers -->
|
||||||
<Twilio
|
<Twilio
|
||||||
v-else-if="selectedProvider === PROVIDER_TYPES.TWILIO"
|
v-else-if="selectedProvider === PROVIDER_TYPES.TWILIO"
|
||||||
|
|||||||
+826
@@ -0,0 +1,826 @@
|
|||||||
|
<script setup>
|
||||||
|
import { computed, nextTick, onBeforeUnmount, reactive, ref } from 'vue';
|
||||||
|
import { I18nT, useI18n } from 'vue-i18n';
|
||||||
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
import { useStore } from 'vuex';
|
||||||
|
|
||||||
|
import WhatsappChannelAPI from 'dashboard/api/channel/whatsappChannel';
|
||||||
|
import Button from 'dashboard/components-next/button/Button.vue';
|
||||||
|
import Icon from 'dashboard/components-next/icon/Icon.vue';
|
||||||
|
import Input from 'dashboard/components-next/input/Input.vue';
|
||||||
|
import { useAlert } from 'dashboard/composables';
|
||||||
|
import { copyTextToClipboard } from 'shared/helpers/clipboard';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
|
const store = useStore();
|
||||||
|
|
||||||
|
const TOTAL_STEPS = 5;
|
||||||
|
const POLL_INTERVAL = 2000;
|
||||||
|
const MAX_POLL_ATTEMPTS = 5;
|
||||||
|
const META_APPS_URL = 'https://developers.facebook.com/apps/';
|
||||||
|
const META_BUSINESS_SETTINGS_URL =
|
||||||
|
'https://business.facebook.com/settings/system-users/';
|
||||||
|
const CREATE_APP_VIDEO_URL =
|
||||||
|
'/videos/whatsapp/manual-setup/create-meta-app.mp4';
|
||||||
|
const CREATE_APP_VIDEO_POSTER_URL =
|
||||||
|
'/videos/whatsapp/manual-setup/create-meta-app-poster.jpg';
|
||||||
|
const ADD_NUMBER_VIDEO_URL =
|
||||||
|
'/videos/whatsapp/manual-setup/add-phone-number.mp4';
|
||||||
|
const ADD_NUMBER_VIDEO_POSTER_URL =
|
||||||
|
'/videos/whatsapp/manual-setup/add-phone-number-poster.jpg';
|
||||||
|
const GENERATE_TOKEN_VIDEO_URL =
|
||||||
|
'/videos/whatsapp/manual-setup/generate-access-token.mp4';
|
||||||
|
const GENERATE_TOKEN_VIDEO_POSTER_URL =
|
||||||
|
'/videos/whatsapp/manual-setup/generate-access-token-poster.jpg';
|
||||||
|
|
||||||
|
const currentStep = ref(1);
|
||||||
|
const isLoading = ref(false);
|
||||||
|
const errorMessage = ref('');
|
||||||
|
const inboxId = ref(null);
|
||||||
|
const pollTimer = ref(null);
|
||||||
|
const showAccessToken = ref(false);
|
||||||
|
const setupRoot = ref(null);
|
||||||
|
|
||||||
|
const form = reactive({
|
||||||
|
wabaId: '',
|
||||||
|
phoneNumberId: '',
|
||||||
|
accessToken: '',
|
||||||
|
inboxName: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const preview = ref(null);
|
||||||
|
const connection = reactive({
|
||||||
|
numberAccess: false,
|
||||||
|
templateAccess: false,
|
||||||
|
webhookSetup: false,
|
||||||
|
callbackVerified: false,
|
||||||
|
callbackConfigured: false,
|
||||||
|
callbackUrl: '',
|
||||||
|
subscriptionVerified: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const idsComplete = computed(
|
||||||
|
() => form.wabaId.trim() && form.phoneNumberId.trim()
|
||||||
|
);
|
||||||
|
|
||||||
|
const detailsComplete = computed(
|
||||||
|
() => idsComplete.value && form.accessToken.trim()
|
||||||
|
);
|
||||||
|
|
||||||
|
const connectionReady = computed(
|
||||||
|
() =>
|
||||||
|
connection.numberAccess &&
|
||||||
|
connection.templateAccess &&
|
||||||
|
connection.webhookSetup &&
|
||||||
|
connection.callbackVerified &&
|
||||||
|
connection.callbackConfigured &&
|
||||||
|
connection.subscriptionVerified
|
||||||
|
);
|
||||||
|
|
||||||
|
const progressSteps = computed(() =>
|
||||||
|
Array.from({ length: TOTAL_STEPS }, (_, index) => index + 1)
|
||||||
|
);
|
||||||
|
|
||||||
|
const appInstructions = computed(() => [
|
||||||
|
t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.APP.ITEM_2'),
|
||||||
|
t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.APP.ITEM_3'),
|
||||||
|
t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.APP.ITEM_4'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const numberInstructions = computed(() => [
|
||||||
|
t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.NUMBER.ITEM_1'),
|
||||||
|
t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.NUMBER.ITEM_2'),
|
||||||
|
t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.NUMBER.ITEM_3'),
|
||||||
|
t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.NUMBER.ITEM_4'),
|
||||||
|
t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.NUMBER.ITEM_5'),
|
||||||
|
t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.NUMBER.ITEM_6'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const tokenInstructions = computed(() => [
|
||||||
|
t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.TOKEN.ITEM_1'),
|
||||||
|
t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.TOKEN.ITEM_2'),
|
||||||
|
t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.TOKEN.ITEM_3'),
|
||||||
|
t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.TOKEN.ITEM_4'),
|
||||||
|
t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.TOKEN.ITEM_5'),
|
||||||
|
t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.TOKEN.ITEM_6'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const statusRows = computed(() => [
|
||||||
|
{
|
||||||
|
key: 'number',
|
||||||
|
label: t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.VERIFY.NUMBER_ACCESS'),
|
||||||
|
complete: connection.numberAccess,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'templates',
|
||||||
|
label: t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.VERIFY.TEMPLATE_ACCESS'),
|
||||||
|
complete: connection.templateAccess,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'callback',
|
||||||
|
label: t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.VERIFY.CALLBACK'),
|
||||||
|
complete:
|
||||||
|
connection.webhookSetup &&
|
||||||
|
connection.callbackConfigured &&
|
||||||
|
connection.callbackVerified,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'subscription',
|
||||||
|
label: t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.VERIFY.SUBSCRIPTION'),
|
||||||
|
complete: connection.subscriptionVerified,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const apiErrorMessage = error => {
|
||||||
|
const message = error.response?.data?.message || '';
|
||||||
|
if (/invalid oauth access token|cannot parse access token/i.test(message)) {
|
||||||
|
return t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.ERRORS.INVALID_TOKEN');
|
||||||
|
}
|
||||||
|
|
||||||
|
return message || t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.ERRORS.GENERIC');
|
||||||
|
};
|
||||||
|
|
||||||
|
const setStep = step => {
|
||||||
|
errorMessage.value = '';
|
||||||
|
currentStep.value = step;
|
||||||
|
nextTick(() => setupRoot.value?.scrollIntoView({ block: 'start' }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const returnToProviders = () => {
|
||||||
|
router.push({
|
||||||
|
name: route.name,
|
||||||
|
params: route.params,
|
||||||
|
query: {},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const goBack = () => {
|
||||||
|
if (currentStep.value > 1) {
|
||||||
|
setStep(currentStep.value - 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
returnToProviders();
|
||||||
|
};
|
||||||
|
|
||||||
|
const continueFromIds = () => {
|
||||||
|
if (!idsComplete.value) {
|
||||||
|
errorMessage.value = t(
|
||||||
|
'INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.ERRORS.IDS_REQUIRED'
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setStep(3);
|
||||||
|
};
|
||||||
|
|
||||||
|
const verifyDetails = async () => {
|
||||||
|
if (!detailsComplete.value) {
|
||||||
|
errorMessage.value = t(
|
||||||
|
'INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.ERRORS.REQUIRED'
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.value = true;
|
||||||
|
errorMessage.value = '';
|
||||||
|
try {
|
||||||
|
const { data } = await WhatsappChannelAPI.previewManualSetup({
|
||||||
|
waba_id: form.wabaId.trim(),
|
||||||
|
phone_number_id: form.phoneNumberId.trim(),
|
||||||
|
access_token: form.accessToken.trim(),
|
||||||
|
});
|
||||||
|
preview.value = data;
|
||||||
|
form.inboxName = data.suggested_inbox_name;
|
||||||
|
setStep(4);
|
||||||
|
} catch (error) {
|
||||||
|
errorMessage.value = apiErrorMessage(error);
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const applyWebhookStatus = status => {
|
||||||
|
connection.callbackVerified = Boolean(status.callback_verified);
|
||||||
|
connection.callbackConfigured = Boolean(status.callback_configured);
|
||||||
|
connection.callbackUrl = status.callback_url || '';
|
||||||
|
connection.subscriptionVerified = Boolean(status.subscription_verified);
|
||||||
|
};
|
||||||
|
|
||||||
|
const copyWebhookUrl = async () => {
|
||||||
|
await copyTextToClipboard(connection.callbackUrl);
|
||||||
|
useAlert(t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.VERIFY.COPY_SUCCESS'));
|
||||||
|
};
|
||||||
|
|
||||||
|
const refreshWebhookStatus = async ({ showError = true } = {}) => {
|
||||||
|
if (!inboxId.value) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data } = await WhatsappChannelAPI.getManualWebhookStatus(
|
||||||
|
inboxId.value
|
||||||
|
);
|
||||||
|
applyWebhookStatus(data);
|
||||||
|
} catch (error) {
|
||||||
|
if (showError) errorMessage.value = apiErrorMessage(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const pollWebhookStatus = async attempt => {
|
||||||
|
await refreshWebhookStatus({ showError: false });
|
||||||
|
if (connectionReady.value || attempt >= MAX_POLL_ATTEMPTS) return;
|
||||||
|
|
||||||
|
pollTimer.value = window.setTimeout(
|
||||||
|
() => pollWebhookStatus(attempt + 1),
|
||||||
|
POLL_INTERVAL
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const connectNumber = async () => {
|
||||||
|
isLoading.value = true;
|
||||||
|
errorMessage.value = '';
|
||||||
|
try {
|
||||||
|
const { data } = await WhatsappChannelAPI.connectManualSetup({
|
||||||
|
waba_id: form.wabaId.trim(),
|
||||||
|
phone_number_id: form.phoneNumberId.trim(),
|
||||||
|
access_token: form.accessToken.trim(),
|
||||||
|
inbox_name: form.inboxName.trim(),
|
||||||
|
});
|
||||||
|
inboxId.value = data.id;
|
||||||
|
connection.numberAccess = Boolean(data.number_access);
|
||||||
|
connection.templateAccess = Boolean(data.template_access);
|
||||||
|
connection.webhookSetup = Boolean(data.webhook_setup);
|
||||||
|
if (data.webhook_error) errorMessage.value = data.webhook_error;
|
||||||
|
currentStep.value = 5;
|
||||||
|
await pollWebhookStatus(1);
|
||||||
|
} catch (error) {
|
||||||
|
errorMessage.value = apiErrorMessage(error);
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const retryWebhookSetup = async () => {
|
||||||
|
isLoading.value = true;
|
||||||
|
errorMessage.value = '';
|
||||||
|
try {
|
||||||
|
const { data } = await WhatsappChannelAPI.setupManualWebhook(inboxId.value);
|
||||||
|
connection.webhookSetup = true;
|
||||||
|
applyWebhookStatus(data);
|
||||||
|
if (!connectionReady.value) await pollWebhookStatus(1);
|
||||||
|
} catch (error) {
|
||||||
|
errorMessage.value = apiErrorMessage(error);
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const continueToAgents = async () => {
|
||||||
|
await store.dispatch('inboxes/get');
|
||||||
|
router.replace({
|
||||||
|
name: 'settings_inboxes_add_agents',
|
||||||
|
params: { page: 'new', inbox_id: inboxId.value },
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (pollTimer.value) window.clearTimeout(pollTimer.value);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
ref="setupRoot"
|
||||||
|
class="mx-auto flex w-full max-w-6xl flex-col gap-4 py-2"
|
||||||
|
>
|
||||||
|
<div class="px-1">
|
||||||
|
<div class="min-w-0 flex-1">
|
||||||
|
<h1 class="text-heading-1 text-n-slate-12">
|
||||||
|
{{ $t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.HEADER.TITLE') }}
|
||||||
|
</h1>
|
||||||
|
<p class="mt-1 text-body-main text-n-slate-11">
|
||||||
|
{{ $t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.HEADER.DESCRIPTION') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="rounded-2xl border border-n-weak bg-n-background p-5 shadow-sm sm:p-6"
|
||||||
|
>
|
||||||
|
<div class="flex justify-end">
|
||||||
|
<div class="grid w-40 grid-cols-5 gap-1.5">
|
||||||
|
<div
|
||||||
|
v-for="step in progressSteps"
|
||||||
|
:key="step"
|
||||||
|
class="h-1.5 rounded-full"
|
||||||
|
:class="step <= currentStep ? 'bg-n-brand' : 'bg-n-alpha-3'"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="errorMessage && currentStep !== 3"
|
||||||
|
class="mt-6 rounded-lg border border-n-ruby-5 bg-n-ruby-3 px-4 py-3 text-sm text-n-ruby-11"
|
||||||
|
>
|
||||||
|
{{ errorMessage }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section v-if="currentStep === 1" class="mt-3 grid gap-5 lg:grid-cols-2">
|
||||||
|
<div class="lg:col-span-2">
|
||||||
|
<h2 class="text-heading-2 text-n-slate-12">
|
||||||
|
{{ $t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.APP.TITLE') }}
|
||||||
|
</h2>
|
||||||
|
<p class="mt-2 max-w-3xl text-body-main text-n-slate-11">
|
||||||
|
{{ $t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.APP.DESCRIPTION') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ol
|
||||||
|
class="ml-5 list-decimal space-y-3 text-body-main text-n-slate-11 marker:font-medium marker:text-n-slate-12"
|
||||||
|
>
|
||||||
|
<li class="pl-2">
|
||||||
|
<I18nT
|
||||||
|
keypath="INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.APP.ITEM_1"
|
||||||
|
tag="span"
|
||||||
|
>
|
||||||
|
<template #metaDevelopers>
|
||||||
|
<a
|
||||||
|
:href="META_APPS_URL"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
class="font-medium text-n-brand hover:underline"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
$t(
|
||||||
|
'INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.APP.META_DEVELOPERS'
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</a>
|
||||||
|
</template>
|
||||||
|
</I18nT>
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
v-for="instruction in appInstructions"
|
||||||
|
:key="instruction"
|
||||||
|
class="pl-2"
|
||||||
|
>
|
||||||
|
{{ instruction }}
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<figure
|
||||||
|
class="self-start overflow-hidden rounded-xl border border-n-weak"
|
||||||
|
>
|
||||||
|
<video
|
||||||
|
class="block w-full bg-n-solid-1"
|
||||||
|
controls
|
||||||
|
muted
|
||||||
|
playsinline
|
||||||
|
preload="metadata"
|
||||||
|
:poster="CREATE_APP_VIDEO_POSTER_URL"
|
||||||
|
:aria-label="
|
||||||
|
$t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.APP.VIDEO_TITLE')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<source :src="CREATE_APP_VIDEO_URL" type="video/mp4" />
|
||||||
|
</video>
|
||||||
|
<figcaption class="border-t border-n-weak bg-n-alpha-1 px-4 py-3">
|
||||||
|
<p class="text-sm font-medium text-n-slate-12">
|
||||||
|
{{ $t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.APP.VIDEO_TITLE') }}
|
||||||
|
</p>
|
||||||
|
</figcaption>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="flex items-center justify-between border-t border-n-weak pt-5 lg:col-span-2"
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
:label="$t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.ACTIONS.BACK')"
|
||||||
|
variant="outline"
|
||||||
|
color="slate"
|
||||||
|
@click="goBack"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
:label="
|
||||||
|
$t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.ACTIONS.APP_READY')
|
||||||
|
"
|
||||||
|
trailing-icon
|
||||||
|
icon="i-lucide-arrow-right"
|
||||||
|
@click="setStep(2)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section
|
||||||
|
v-else-if="currentStep === 2"
|
||||||
|
class="mt-3 grid gap-5 lg:grid-cols-2"
|
||||||
|
>
|
||||||
|
<div class="lg:col-span-2">
|
||||||
|
<h2 class="text-heading-2 text-n-slate-12">
|
||||||
|
{{ $t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.NUMBER.TITLE') }}
|
||||||
|
</h2>
|
||||||
|
<p class="mt-2 max-w-3xl text-body-main text-n-slate-11">
|
||||||
|
{{ $t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.NUMBER.DESCRIPTION') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ol
|
||||||
|
class="ml-5 list-decimal space-y-3 text-body-main text-n-slate-11 marker:font-medium marker:text-n-slate-12"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
v-for="instruction in numberInstructions"
|
||||||
|
:key="instruction"
|
||||||
|
class="pl-2"
|
||||||
|
>
|
||||||
|
{{ instruction }}
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<div class="flex min-w-0 flex-col gap-4">
|
||||||
|
<figure class="overflow-hidden rounded-xl border border-n-weak">
|
||||||
|
<video
|
||||||
|
class="block w-full bg-n-solid-1"
|
||||||
|
controls
|
||||||
|
muted
|
||||||
|
playsinline
|
||||||
|
preload="metadata"
|
||||||
|
:poster="ADD_NUMBER_VIDEO_POSTER_URL"
|
||||||
|
:aria-label="
|
||||||
|
$t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.NUMBER.VIDEO_TITLE')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<source :src="ADD_NUMBER_VIDEO_URL" type="video/mp4" />
|
||||||
|
</video>
|
||||||
|
<figcaption class="border-t border-n-weak bg-n-alpha-1 px-4 py-3">
|
||||||
|
<p class="text-sm font-medium text-n-slate-12">
|
||||||
|
{{
|
||||||
|
$t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.NUMBER.VIDEO_TITLE')
|
||||||
|
}}
|
||||||
|
</p>
|
||||||
|
</figcaption>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
<a
|
||||||
|
:href="META_APPS_URL"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
class="inline-flex w-fit items-center gap-2 text-sm font-medium text-n-brand hover:underline"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
$t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.ACTIONS.OPEN_META_APPS')
|
||||||
|
}}
|
||||||
|
<Icon icon="i-lucide-external-link" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="grid gap-5 rounded-xl border border-n-weak p-5 lg:col-span-2 lg:grid-cols-2"
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
v-model="form.phoneNumberId"
|
||||||
|
:label="
|
||||||
|
$t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.DETAILS.PHONE_ID_LABEL')
|
||||||
|
"
|
||||||
|
:placeholder="
|
||||||
|
$t(
|
||||||
|
'INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.DETAILS.PHONE_ID_PLACEHOLDER'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
v-model="form.wabaId"
|
||||||
|
:label="
|
||||||
|
$t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.DETAILS.WABA_LABEL')
|
||||||
|
"
|
||||||
|
:placeholder="
|
||||||
|
$t(
|
||||||
|
'INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.DETAILS.WABA_PLACEHOLDER'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="flex items-center justify-between border-t border-n-weak pt-5 lg:col-span-2"
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
:label="$t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.ACTIONS.BACK')"
|
||||||
|
variant="outline"
|
||||||
|
color="slate"
|
||||||
|
@click="goBack"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
:label="$t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.ACTIONS.NEXT')"
|
||||||
|
trailing-icon
|
||||||
|
icon="i-lucide-arrow-right"
|
||||||
|
@click="continueFromIds"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section
|
||||||
|
v-else-if="currentStep === 3"
|
||||||
|
class="mt-3 grid gap-5 lg:grid-cols-2"
|
||||||
|
>
|
||||||
|
<div class="lg:col-span-2">
|
||||||
|
<h2 class="text-heading-2 text-n-slate-12">
|
||||||
|
{{ $t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.TOKEN.TITLE') }}
|
||||||
|
</h2>
|
||||||
|
<p class="mt-2 max-w-3xl text-body-main text-n-slate-11">
|
||||||
|
{{ $t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.TOKEN.DESCRIPTION') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ol
|
||||||
|
class="ml-5 list-decimal space-y-3 text-body-main text-n-slate-11 marker:font-medium marker:text-n-slate-12"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
v-for="instruction in tokenInstructions"
|
||||||
|
:key="instruction"
|
||||||
|
class="pl-2"
|
||||||
|
>
|
||||||
|
{{ instruction }}
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<div class="flex min-w-0 flex-col gap-4">
|
||||||
|
<figure class="overflow-hidden rounded-xl border border-n-weak">
|
||||||
|
<video
|
||||||
|
class="block w-full bg-n-solid-1"
|
||||||
|
controls
|
||||||
|
muted
|
||||||
|
playsinline
|
||||||
|
preload="metadata"
|
||||||
|
:poster="GENERATE_TOKEN_VIDEO_POSTER_URL"
|
||||||
|
:aria-label="
|
||||||
|
$t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.TOKEN.VIDEO_TITLE')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<source :src="GENERATE_TOKEN_VIDEO_URL" type="video/mp4" />
|
||||||
|
</video>
|
||||||
|
<figcaption class="border-t border-n-weak bg-n-alpha-1 px-4 py-3">
|
||||||
|
<p class="text-sm font-medium text-n-slate-12">
|
||||||
|
{{
|
||||||
|
$t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.TOKEN.VIDEO_TITLE')
|
||||||
|
}}
|
||||||
|
</p>
|
||||||
|
</figcaption>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
<a
|
||||||
|
:href="META_BUSINESS_SETTINGS_URL"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
class="inline-flex w-fit items-center gap-2 text-sm font-medium text-n-brand hover:underline"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
$t(
|
||||||
|
'INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.ACTIONS.OPEN_BUSINESS_SETTINGS'
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
<Icon icon="i-lucide-external-link" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="rounded-xl border border-n-weak p-5 lg:col-span-2">
|
||||||
|
<div class="grid items-start gap-3 sm:grid-cols-[1fr_auto]">
|
||||||
|
<Input
|
||||||
|
v-model="form.accessToken"
|
||||||
|
:type="showAccessToken ? 'text' : 'password'"
|
||||||
|
autocomplete="off"
|
||||||
|
:label="
|
||||||
|
$t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.DETAILS.TOKEN_LABEL')
|
||||||
|
"
|
||||||
|
:placeholder="
|
||||||
|
$t(
|
||||||
|
'INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.DETAILS.TOKEN_PLACEHOLDER'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
:message="errorMessage"
|
||||||
|
message-type="error"
|
||||||
|
@update:model-value="errorMessage = ''"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
:label="
|
||||||
|
showAccessToken
|
||||||
|
? $t(
|
||||||
|
'INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.ACTIONS.HIDE_TOKEN'
|
||||||
|
)
|
||||||
|
: $t(
|
||||||
|
'INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.ACTIONS.SHOW_TOKEN'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
variant="outline"
|
||||||
|
color="slate"
|
||||||
|
class="sm:mt-7"
|
||||||
|
@click="showAccessToken = !showAccessToken"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
v-if="!errorMessage"
|
||||||
|
class="mt-3 flex items-start gap-2 text-sm text-n-amber-11"
|
||||||
|
>
|
||||||
|
<Icon icon="i-lucide-triangle-alert" class="mt-0.5 shrink-0" />
|
||||||
|
{{ $t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.TOKEN.WARNING') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="flex items-center justify-between border-t border-n-weak pt-5 lg:col-span-2"
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
:label="$t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.ACTIONS.BACK')"
|
||||||
|
variant="outline"
|
||||||
|
color="slate"
|
||||||
|
@click="goBack"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
:label="
|
||||||
|
$t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.ACTIONS.VERIFY_DETAILS')
|
||||||
|
"
|
||||||
|
:is-loading="isLoading"
|
||||||
|
:disabled="isLoading"
|
||||||
|
@click="verifyDetails"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section v-else-if="currentStep === 4" class="mt-3 flex flex-col gap-5">
|
||||||
|
<div>
|
||||||
|
<h2 class="text-heading-2 text-n-slate-12">
|
||||||
|
{{ $t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.REVIEW.TITLE') }}
|
||||||
|
</h2>
|
||||||
|
<p class="mt-2 max-w-3xl text-body-main text-n-slate-11">
|
||||||
|
{{ $t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.REVIEW.DESCRIPTION') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="rounded-xl border border-n-teal-5 bg-n-teal-3 px-4 py-3 text-sm text-n-teal-11"
|
||||||
|
>
|
||||||
|
<div class="flex items-center gap-2 font-medium">
|
||||||
|
<Icon icon="i-lucide-badge-check" />
|
||||||
|
{{ $t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.REVIEW.VERIFIED') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<dl class="divide-y divide-n-weak rounded-xl border border-n-weak px-5">
|
||||||
|
<div class="grid grid-cols-2 gap-4 py-4">
|
||||||
|
<dt class="text-sm text-n-slate-11">
|
||||||
|
{{
|
||||||
|
$t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.REVIEW.BUSINESS_NAME')
|
||||||
|
}}
|
||||||
|
</dt>
|
||||||
|
<dd class="text-sm font-medium text-n-slate-12">
|
||||||
|
{{ preview.verified_name || preview.display_phone_number }}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 gap-4 py-4">
|
||||||
|
<dt class="text-sm text-n-slate-11">
|
||||||
|
{{
|
||||||
|
$t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.REVIEW.PHONE_NUMBER')
|
||||||
|
}}
|
||||||
|
</dt>
|
||||||
|
<dd class="text-sm font-medium text-n-slate-12">
|
||||||
|
{{ preview.display_phone_number }}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 gap-4 py-4">
|
||||||
|
<dt class="text-sm text-n-slate-11">
|
||||||
|
{{ $t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.REVIEW.PHONE_ID') }}
|
||||||
|
</dt>
|
||||||
|
<dd class="text-sm font-medium text-n-slate-12">
|
||||||
|
{{ preview.phone_number_id }}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 gap-4 py-4">
|
||||||
|
<dt class="text-sm text-n-slate-11">
|
||||||
|
{{ $t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.REVIEW.WABA_ID') }}
|
||||||
|
</dt>
|
||||||
|
<dd class="text-sm font-medium text-n-slate-12">
|
||||||
|
{{ preview.waba_id }}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
<Input
|
||||||
|
v-model="form.inboxName"
|
||||||
|
:label="$t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.REVIEW.INBOX_NAME')"
|
||||||
|
:message="
|
||||||
|
$t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.REVIEW.INBOX_NAME_HELP')
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="flex items-center justify-between border-t border-n-weak pt-5"
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
:label="$t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.ACTIONS.BACK')"
|
||||||
|
variant="outline"
|
||||||
|
color="slate"
|
||||||
|
@click="goBack"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
:label="$t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.ACTIONS.CONNECT')"
|
||||||
|
:is-loading="isLoading"
|
||||||
|
:disabled="isLoading || !form.inboxName.trim()"
|
||||||
|
@click="connectNumber"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section v-else class="mt-3 flex flex-col gap-5">
|
||||||
|
<div>
|
||||||
|
<h2 class="text-heading-2 text-n-slate-12">
|
||||||
|
{{ $t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.VERIFY.TITLE') }}
|
||||||
|
</h2>
|
||||||
|
<p class="mt-2 max-w-3xl text-body-main text-n-slate-11">
|
||||||
|
{{ $t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.VERIFY.DESCRIPTION') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="connection.callbackUrl"
|
||||||
|
class="rounded-xl border border-n-weak p-5"
|
||||||
|
>
|
||||||
|
<p class="text-sm font-medium text-n-slate-12">
|
||||||
|
{{ $t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.VERIFY.WEBHOOK_URL') }}
|
||||||
|
</p>
|
||||||
|
<div class="mt-3 flex items-center gap-3">
|
||||||
|
<code
|
||||||
|
class="min-w-0 flex-1 break-all rounded-lg bg-n-alpha-1 px-3 py-2.5 text-sm text-n-slate-12"
|
||||||
|
>
|
||||||
|
{{ connection.callbackUrl }}
|
||||||
|
</code>
|
||||||
|
<Button
|
||||||
|
:label="$t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.VERIFY.COPY')"
|
||||||
|
icon="i-lucide-copy"
|
||||||
|
variant="outline"
|
||||||
|
color="slate"
|
||||||
|
@click="copyWebhookUrl"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="flex flex-col divide-y divide-n-weak rounded-xl border border-n-weak px-5"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="status in statusRows"
|
||||||
|
:key="status.key"
|
||||||
|
class="flex items-center justify-between py-4"
|
||||||
|
>
|
||||||
|
<span class="text-sm font-medium text-n-slate-12">
|
||||||
|
{{ status.label }}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="flex items-center gap-2 text-sm"
|
||||||
|
:class="status.complete ? 'text-n-teal-11' : 'text-n-amber-11'"
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
:icon="
|
||||||
|
status.complete ? 'i-lucide-check-circle' : 'i-lucide-clock-3'
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
{{
|
||||||
|
status.complete
|
||||||
|
? $t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.VERIFY.COMPLETE')
|
||||||
|
: $t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.VERIFY.PENDING')
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p
|
||||||
|
v-if="connectionReady"
|
||||||
|
class="rounded-lg bg-n-teal-3 px-4 py-3 text-sm text-n-teal-11"
|
||||||
|
>
|
||||||
|
{{ $t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.VERIFY.SUCCESS') }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="flex items-center justify-between border-t border-n-weak pt-5"
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
:label="
|
||||||
|
$t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.ACTIONS.RETRY_WEBHOOK')
|
||||||
|
"
|
||||||
|
variant="outline"
|
||||||
|
color="slate"
|
||||||
|
:is-loading="isLoading"
|
||||||
|
:disabled="isLoading"
|
||||||
|
@click="retryWebhookSetup"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
:label="$t('INBOX_MGMT.ADD.WHATSAPP.MANUAL_SETUP.ACTIONS.CONTINUE')"
|
||||||
|
trailing-icon
|
||||||
|
icon="i-lucide-arrow-right"
|
||||||
|
:disabled="isLoading"
|
||||||
|
@click="continueToAgents"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -160,8 +160,9 @@ class Channel::Whatsapp < ApplicationRecord
|
|||||||
end
|
end
|
||||||
|
|
||||||
def should_auto_setup_webhooks?
|
def should_auto_setup_webhooks?
|
||||||
# Only auto-setup webhooks for whatsapp_cloud provider with manual setup
|
# Embedded signup and Manual V2 run webhook setup explicitly so their API
|
||||||
# Embedded signup calls setup_webhooks explicitly in EmbeddedSignupService
|
# responses can reflect the real result instead of swallowing callback errors.
|
||||||
provider == 'whatsapp_cloud' && provider_config['source'] != 'embedded_signup'
|
explicitly_configured_sources = %w[embedded_signup manual_setup_v2]
|
||||||
|
provider == 'whatsapp_cloud' && explicitly_configured_sources.exclude?(provider_config['source'])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -30,6 +30,54 @@ class Whatsapp::FacebookApiClient
|
|||||||
handle_response(response, 'WABA phone numbers fetch failed')
|
handle_response(response, 'WABA phone numbers fetch failed')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def fetch_all_phone_numbers(waba_id)
|
||||||
|
phone_numbers = []
|
||||||
|
after_cursor = nil
|
||||||
|
|
||||||
|
loop do
|
||||||
|
response = HTTParty.get(
|
||||||
|
"#{BASE_URI}/#{@api_version}/#{waba_id}/phone_numbers",
|
||||||
|
headers: request_headers,
|
||||||
|
query: after_cursor.present? ? { after: after_cursor } : {}
|
||||||
|
)
|
||||||
|
data = handle_response(response, 'WABA phone numbers fetch failed')
|
||||||
|
phone_numbers.concat(data['data'] || [])
|
||||||
|
after_cursor = data.dig('paging', 'cursors', 'after') if data.dig('paging', 'next').present?
|
||||||
|
break if after_cursor.blank?
|
||||||
|
end
|
||||||
|
|
||||||
|
phone_numbers
|
||||||
|
end
|
||||||
|
|
||||||
|
def fetch_message_templates(waba_id)
|
||||||
|
response = HTTParty.get(
|
||||||
|
"#{BASE_URI}/#{@api_version}/#{waba_id}/message_templates",
|
||||||
|
headers: request_headers,
|
||||||
|
query: { limit: 1 }
|
||||||
|
)
|
||||||
|
|
||||||
|
handle_response(response, 'WABA message templates fetch failed')
|
||||||
|
end
|
||||||
|
|
||||||
|
def fetch_subscribed_apps(waba_id)
|
||||||
|
response = HTTParty.get(
|
||||||
|
"#{BASE_URI}/#{@api_version}/#{waba_id}/subscribed_apps",
|
||||||
|
headers: request_headers
|
||||||
|
)
|
||||||
|
|
||||||
|
handle_response(response, 'WABA webhook subscription fetch failed')
|
||||||
|
end
|
||||||
|
|
||||||
|
def fetch_phone_number(phone_number_id, fields: nil)
|
||||||
|
response = HTTParty.get(
|
||||||
|
"#{BASE_URI}/#{@api_version}/#{phone_number_id}",
|
||||||
|
headers: request_headers,
|
||||||
|
query: fields.present? ? { fields: fields } : {}
|
||||||
|
)
|
||||||
|
|
||||||
|
handle_response(response, 'Phone number fetch failed')
|
||||||
|
end
|
||||||
|
|
||||||
def debug_token(input_token)
|
def debug_token(input_token)
|
||||||
response = HTTParty.get(
|
response = HTTParty.get(
|
||||||
"#{BASE_URI}/#{@api_version}/debug_token",
|
"#{BASE_URI}/#{@api_version}/debug_token",
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
class Whatsapp::ManualSetupService
|
||||||
|
attr_reader :channel, :webhook_error
|
||||||
|
|
||||||
|
def initialize(account:, waba_id:, phone_number_id:, access_token:, inbox_name: nil)
|
||||||
|
@account = account
|
||||||
|
@waba_id = waba_id
|
||||||
|
@phone_number_id = phone_number_id
|
||||||
|
@access_token = access_token
|
||||||
|
@inbox_name = inbox_name
|
||||||
|
end
|
||||||
|
|
||||||
|
def perform
|
||||||
|
preview = validate_setup
|
||||||
|
create_channel_and_inbox(preview)
|
||||||
|
setup_webhook
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
|
def webhook_setup?
|
||||||
|
webhook_error.blank?
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def validate_setup
|
||||||
|
Whatsapp::ManualSetupValidationService.new(
|
||||||
|
waba_id: @waba_id,
|
||||||
|
phone_number_id: @phone_number_id,
|
||||||
|
access_token: @access_token
|
||||||
|
).perform
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_channel_and_inbox(preview)
|
||||||
|
ActiveRecord::Base.transaction do
|
||||||
|
@channel = @account.whatsapp_channels.create!(
|
||||||
|
phone_number: preview[:display_phone_number],
|
||||||
|
provider: 'whatsapp_cloud',
|
||||||
|
provider_config: {
|
||||||
|
api_key: @access_token,
|
||||||
|
phone_number_id: preview[:phone_number_id],
|
||||||
|
business_account_id: preview[:waba_id],
|
||||||
|
source: 'manual_setup_v2'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@account.inboxes.create!(
|
||||||
|
name: @inbox_name.to_s.strip.presence || preview[:suggested_inbox_name],
|
||||||
|
channel: @channel
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def setup_webhook
|
||||||
|
Whatsapp::WebhookSetupService.new(@channel, @waba_id, @access_token).register_callback
|
||||||
|
rescue StandardError => e
|
||||||
|
@webhook_error = e.message
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
class Whatsapp::ManualSetupValidationService
|
||||||
|
LOG_PREFIX = '[WHATSAPP MANUAL SETUP]'.freeze
|
||||||
|
|
||||||
|
def initialize(waba_id:, phone_number_id:, access_token:)
|
||||||
|
@waba_id = waba_id
|
||||||
|
@phone_number_id = phone_number_id
|
||||||
|
@access_token = access_token
|
||||||
|
@api_client = Whatsapp::FacebookApiClient.new(access_token)
|
||||||
|
end
|
||||||
|
|
||||||
|
def perform
|
||||||
|
validate_parameters!
|
||||||
|
Rails.logger.info "#{LOG_PREFIX} Validation started waba_id=#{@waba_id} phone_number_id=#{@phone_number_id}"
|
||||||
|
|
||||||
|
phone_data = find_phone_data!
|
||||||
|
verify_uniqueness!(phone_data)
|
||||||
|
verify_template_access!
|
||||||
|
|
||||||
|
build_preview(phone_data)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def validate_parameters!
|
||||||
|
raise ArgumentError, 'WABA ID is required' if @waba_id.blank?
|
||||||
|
raise ArgumentError, 'Phone Number ID is required' if @phone_number_id.blank?
|
||||||
|
raise ArgumentError, 'Access token is required' if @access_token.blank?
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_phone_data!
|
||||||
|
phone_numbers = @api_client.fetch_all_phone_numbers(@waba_id)
|
||||||
|
returned_phone_number_ids = phone_numbers.filter_map { |phone| phone['id'] }.join(',')
|
||||||
|
Rails.logger.info "#{LOG_PREFIX} Meta returned #{phone_numbers.size} phone number(s) for waba_id=#{@waba_id} " \
|
||||||
|
"phone_number_ids=#{returned_phone_number_ids}"
|
||||||
|
|
||||||
|
phone_data = phone_numbers.find { |phone| phone['id'].to_s == @phone_number_id.to_s }
|
||||||
|
raise ArgumentError, 'This Phone Number ID does not belong to the WABA ID you entered.' if phone_data.blank?
|
||||||
|
|
||||||
|
Rails.logger.info "#{LOG_PREFIX} Matched phone_number_id=#{@phone_number_id} fields=#{phone_data.keys.sort.join(',')} " \
|
||||||
|
"code_verification_status=#{phone_data['code_verification_status'].inspect} " \
|
||||||
|
"name_status=#{phone_data['name_status'].inspect}"
|
||||||
|
|
||||||
|
phone_data
|
||||||
|
end
|
||||||
|
|
||||||
|
def verify_uniqueness!(phone_data)
|
||||||
|
phone_number = normalized_phone_number(phone_data['display_phone_number'])
|
||||||
|
raise ArgumentError, 'This WhatsApp number is already connected to another inbox.' if Channel::Whatsapp.exists?(phone_number: phone_number)
|
||||||
|
|
||||||
|
duplicate_phone_id = Channel::Whatsapp.exists?(["provider_config->>'phone_number_id' = ?", @phone_number_id.to_s])
|
||||||
|
raise ArgumentError, 'This Phone Number ID is already used by another WhatsApp inbox.' if duplicate_phone_id
|
||||||
|
end
|
||||||
|
|
||||||
|
def verify_template_access!
|
||||||
|
@api_client.fetch_message_templates(@waba_id)
|
||||||
|
rescue StandardError
|
||||||
|
raise ArgumentError,
|
||||||
|
'The token can access the number but cannot access message templates. Generate a token with whatsapp_business_management permission.'
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_preview(phone_data)
|
||||||
|
phone_number = normalized_phone_number(phone_data['display_phone_number'])
|
||||||
|
verified_name = phone_data['verified_name'].presence
|
||||||
|
|
||||||
|
{
|
||||||
|
verified_name: verified_name,
|
||||||
|
display_phone_number: phone_number,
|
||||||
|
phone_number_id: phone_data['id'].to_s,
|
||||||
|
waba_id: @waba_id.to_s,
|
||||||
|
template_access: true,
|
||||||
|
suggested_inbox_name: "#{verified_name || phone_number} WhatsApp"
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def normalized_phone_number(phone_number)
|
||||||
|
digits = phone_number.to_s.gsub(/[^\d]/, '')
|
||||||
|
"+#{digits}"
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
class Whatsapp::ManualWebhookStatusService
|
||||||
|
def initialize(channel)
|
||||||
|
@channel = channel
|
||||||
|
@api_client = Whatsapp::FacebookApiClient.new(channel.provider_config['api_key'])
|
||||||
|
end
|
||||||
|
|
||||||
|
def perform
|
||||||
|
callback_configured = callback_configured?
|
||||||
|
|
||||||
|
{
|
||||||
|
callback_verified: callback_configured,
|
||||||
|
callback_configured: callback_configured,
|
||||||
|
callback_url: callback_url,
|
||||||
|
subscription_verified: subscription_verified?
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def callback_configured?
|
||||||
|
phone_number = @api_client.fetch_phone_number(
|
||||||
|
@channel.provider_config['phone_number_id'],
|
||||||
|
fields: 'webhook_configuration'
|
||||||
|
)
|
||||||
|
webhook_configuration = phone_number.fetch('webhook_configuration', {})
|
||||||
|
|
||||||
|
%w[override_callback_uri phone_number whatsapp_business_account application].any? do |key|
|
||||||
|
webhook_configuration[key] == callback_url
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def subscription_verified?
|
||||||
|
@api_client.fetch_subscribed_apps(@channel.provider_config['business_account_id']).fetch('data', []).present?
|
||||||
|
end
|
||||||
|
|
||||||
|
def callback_url
|
||||||
|
"#{ENV.fetch('FRONTEND_URL', nil)}/webhooks/whatsapp/#{@channel.phone_number}"
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -357,6 +357,10 @@ Rails.application.routes.draw do
|
|||||||
|
|
||||||
namespace :whatsapp do
|
namespace :whatsapp do
|
||||||
resource :authorization, only: [:create]
|
resource :authorization, only: [:create]
|
||||||
|
post 'manual/preview', to: 'manual_setup#preview'
|
||||||
|
post 'manual/connect', to: 'manual_setup#connect'
|
||||||
|
get 'manual/:inbox_id/webhook_status', to: 'manual_setup#webhook_status'
|
||||||
|
post 'manual/:inbox_id/setup_webhook', to: 'manual_setup#setup_webhook'
|
||||||
end
|
end
|
||||||
|
|
||||||
resources :webhooks, only: [:index, :create, :update, :destroy]
|
resources :webhooks, only: [:index, :create, :update, :destroy]
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 5.5 KiB |
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 5.5 KiB |
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 5.5 KiB |
Binary file not shown.
Reference in New Issue
Block a user