fix: Google Ads via marketing@ OAuth first; harden Clientify webhooks
Prefer agency google_drive+ads token over stale env refresh to avoid PERMISSION_DENIED, and accept Clientify contact_id/deal_id payload aliases. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -51,25 +51,14 @@ function oauthClientPair(): { clientId: string; clientSecret: string } | null {
|
||||
|
||||
/**
|
||||
* Resolve a Google Ads API access token.
|
||||
* Order: env GOOGLE_ADS_REFRESH → agency_oauth google_ads → agency_oauth google_drive (si tiene scope Ads).
|
||||
* Prefer agency OAuth (fresh reconnect) over env refresh — stale env tokens
|
||||
* often refresh OK but lack MCC access → PERMISSION_DENIED on customer queries.
|
||||
*/
|
||||
export async function resolveGoogleAdsAccessToken(): Promise<string> {
|
||||
const attempts: TokenAttempt[] = [];
|
||||
const adsPair = adsClientPair();
|
||||
const oauthPair = oauthClientPair();
|
||||
|
||||
const envRefresh =
|
||||
process.env.GOOGLE_ADS_REFRESH_TOKEN?.trim() ||
|
||||
process.env.GOOGLE_OAUTH_REFRESH_TOKEN?.trim();
|
||||
if (envRefresh && adsPair) {
|
||||
attempts.push({
|
||||
refresh: envRefresh,
|
||||
clientId: adsPair.clientId,
|
||||
clientSecret: adsPair.clientSecret,
|
||||
label: 'env',
|
||||
});
|
||||
}
|
||||
|
||||
const headers = serviceHeaders();
|
||||
if (headers) {
|
||||
try {
|
||||
@@ -83,31 +72,34 @@ export async function resolveGoogleAdsAccessToken(): Promise<string> {
|
||||
refresh_token?: string | null;
|
||||
scopes?: string | null;
|
||||
}[];
|
||||
// google_drive+ads first (usually the live Agencia reconnect)
|
||||
for (const row of rows) {
|
||||
if (row.provider !== 'google_drive') continue;
|
||||
const refresh = row.refresh_token?.trim();
|
||||
if (!refresh) continue;
|
||||
if (row.provider === 'google_ads' && adsPair) {
|
||||
attempts.push({
|
||||
refresh,
|
||||
clientId: adsPair.clientId,
|
||||
clientSecret: adsPair.clientSecret,
|
||||
label: 'agency_oauth.google_ads',
|
||||
});
|
||||
}
|
||||
if (row.provider === 'google_drive') {
|
||||
const scopes = row.scopes || '';
|
||||
if (!scopes.includes('adwords') && !scopes.includes(ADS_SCOPE)) {
|
||||
continue;
|
||||
}
|
||||
const pair = oauthPair || adsPair;
|
||||
if (!pair) continue;
|
||||
attempts.push({
|
||||
refresh,
|
||||
clientId: pair.clientId,
|
||||
clientSecret: pair.clientSecret,
|
||||
label: 'agency_oauth.google_drive+ads',
|
||||
});
|
||||
const scopes = row.scopes || '';
|
||||
if (!scopes.includes('adwords') && !scopes.includes(ADS_SCOPE)) {
|
||||
continue;
|
||||
}
|
||||
const pair = oauthPair || adsPair;
|
||||
if (!pair) continue;
|
||||
attempts.push({
|
||||
refresh,
|
||||
clientId: pair.clientId,
|
||||
clientSecret: pair.clientSecret,
|
||||
label: 'agency_oauth.google_drive+ads',
|
||||
});
|
||||
}
|
||||
for (const row of rows) {
|
||||
if (row.provider !== 'google_ads') continue;
|
||||
const refresh = row.refresh_token?.trim();
|
||||
if (!refresh || !adsPair) continue;
|
||||
attempts.push({
|
||||
refresh,
|
||||
clientId: adsPair.clientId,
|
||||
clientSecret: adsPair.clientSecret,
|
||||
label: 'agency_oauth.google_ads',
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
@@ -115,6 +107,18 @@ export async function resolveGoogleAdsAccessToken(): Promise<string> {
|
||||
}
|
||||
}
|
||||
|
||||
const envRefresh =
|
||||
process.env.GOOGLE_ADS_REFRESH_TOKEN?.trim() ||
|
||||
process.env.GOOGLE_OAUTH_REFRESH_TOKEN?.trim();
|
||||
if (envRefresh && adsPair) {
|
||||
attempts.push({
|
||||
refresh: envRefresh,
|
||||
clientId: adsPair.clientId,
|
||||
clientSecret: adsPair.clientSecret,
|
||||
label: 'env',
|
||||
});
|
||||
}
|
||||
|
||||
if (!attempts.length) {
|
||||
throw new Error(
|
||||
'Faltan credenciales Google Ads (GOOGLE_ADS_REFRESH_TOKEN o reconectar Agencia → Drive con Ads)'
|
||||
|
||||
Reference in New Issue
Block a user