This commit is contained in:
debu-2 2026-10-13 02:01:13 +05:30
parent 9dbda69db1
commit a2b076e9a1
6 changed files with 122 additions and 8 deletions

View file

@ -283,6 +283,47 @@ async def _subscribe(token: str, waba_id: str) -> bool:
return False
def _customer_callback() -> dict[str, str] | None:
"""Public address where a customer's own Meta app should deliver messages."""
url = settings.whatsapp_webhook_url
verify = settings.whatsapp_verify_token.strip()
if not url.startswith("https://") or not verify or len(url) > 200:
return None
return {"override_callback_uri": url, "verify_token": verify}
async def _token_app_id(token: str) -> str | None:
try:
data = await _graph("GET", "app", token=token, params={"fields": "id"})
except HTTPException:
return None
app_id = str(data.get("id") or "").strip()
return app_id or None
async def _attach_customer_webhook(token: str, waba_id: str, phone_ids: list[str]) -> bool:
"""Send this account's messages to the workspace, using the customer's token."""
callback = _customer_callback()
if callback is None:
return False
try:
await _graph("POST", f"{waba_id}/subscribed_apps", token=token, json_body=callback)
except HTTPException:
logger.warning("WhatsApp webhook override failed for waba %s", waba_id[-4:])
return False
for phone_id in phone_ids:
try:
await _graph(
"POST",
phone_id,
token=token,
json_body={"webhook_configuration": callback},
)
except HTTPException:
logger.info("Phone webhook override failed for %s", phone_id[-4:])
return True
async def _template_count(token: str, waba_id: str) -> tuple[int, bool]:
try:
data = await _graph(
@ -732,7 +773,12 @@ async def _save_token_account(
if previous_connection_id and previous_connection_id != connection.id:
previous = db.get(WhatsAppMetaConnection, previous_connection_id)
if previous is not None and previous.user_id == user.id:
if not connection.meta_app_secret and previous.meta_app_secret:
connection.meta_app_secret = previous.meta_app_secret
if not connection.meta_app_id and previous.meta_app_id:
connection.meta_app_id = previous.meta_app_id
previous.access_token = None
previous.meta_app_secret = None
account.name = name
account.last_error = None
account.management_status = "active"
@ -740,14 +786,35 @@ async def _save_token_account(
phones = await _phones_for_token(token, waba_id, phone_id)
if phones:
_apply_phones(db, user, account, phones, phone_id)
subscribed = await _subscribe(token, waba_id)
app_id = await _token_app_id(token)
if app_id:
connection.meta_app_id = app_id
platform_app = settings.meta_app_id.strip()
same_app = bool(app_id and platform_app and app_id == platform_app)
phone_ids = [phone.phone_number_id for phone in account.phones]
if same_app or connection.meta_app_secret:
receiving = await _attach_customer_webhook(token, waba_id, phone_ids)
if not receiving and same_app:
receiving = await _subscribe(token, waba_id)
else:
await _subscribe(token, waba_id)
receiving = False
template_count, templates_ok = await _template_count(token, waba_id)
account.template_count = template_count
account.templates_status = "active" if templates_ok else "unavailable"
account.webhooks_status = "active" if subscribed else "unavailable"
account.webhooks_status = "active" if receiving else "unavailable"
if account.phones:
account.status = "connected"
account.messaging_status = "active"
if receiving:
account.last_error = None
elif same_app:
account.last_error = "Sending is on. Meta did not accept the delivery address for replies."
else:
account.last_error = (
"Sending uses this token. Save it again with the App Secret from the Meta app that created the token "
"so replies arrive in this workspace."
)
_audit(db, user, "token_connected", "WhatsApp token saved for this account", connection_id=connection.id, waba_id=waba_id)
else:
account.status = "incomplete"
@ -763,6 +830,7 @@ async def connect_with_token(
access_token: str,
waba_id: str,
phone_number_id: str | None = None,
app_secret: str | None = None,
) -> dict[str, Any]:
"""Store a customer-supplied WhatsApp token. Does not use Embedded Signup."""
token = _clean_token(access_token)
@ -780,12 +848,19 @@ async def connect_with_token(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Phone number ID should be the number Meta shows for that phone.",
)
secret = "".join(str(app_secret or "").split())
if secret and len(secret) < 8:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="App secret should be the value from the Meta app that created this token.",
)
connection = WhatsAppMetaConnection(
user_id=user.id,
status="pending",
granted_scopes="manual",
access_token=token,
meta_app_secret=secret or None,
)
db.add(connection)
db.flush()
@ -802,7 +877,7 @@ async def connect_with_token(
db.refresh(account)
if account.status == "connected":
overall = "connected"
message = "This WhatsApp account is saved for the workspace."
message = account.last_error or "This WhatsApp account is saved. Messages send and arrive in this workspace."
elif account.status == "incomplete":
overall = "incomplete"
message = account.last_error or "The token is saved. Add the phone number ID to finish."