577 lines
23 KiB
C#
577 lines
23 KiB
C#
using GoogleApi.Configuration;
|
|
using GoogleApi.Models;
|
|
using Microsoft.Extensions.Options;
|
|
|
|
using Google.Ads.GoogleAds;
|
|
using Google.Ads.GoogleAds.Lib;
|
|
|
|
using Google.Ads.GoogleAds.V22.Common;
|
|
using Google.Ads.GoogleAds.V22.Enums;
|
|
using Google.Ads.GoogleAds.V22.Errors;
|
|
using Google.Ads.GoogleAds.V22.Resources;
|
|
using Google.Ads.GoogleAds.V22.Services;
|
|
|
|
namespace GoogleApi.Services;
|
|
|
|
// ✅ IMPORTANT: force "Services" to mean Google.Ads.GoogleAds.Services (not GoogleApi.Services)
|
|
using GAdsServices = global::Google.Ads.GoogleAds.Services;
|
|
|
|
// ✅ Avoid name collision with Google.Ads.GoogleAds.V22.Resources.BiddingStrategy
|
|
using ModelBiddingStrategy = GoogleApi.Models.BiddingStrategy;
|
|
|
|
public sealed class GoogleAdsService
|
|
{
|
|
private readonly GoogleAdsConfig _config;
|
|
private readonly GoogleAdsClientFactory _clientFactory;
|
|
private readonly ILogger<GoogleAdsService> _logger;
|
|
|
|
public GoogleAdsService(
|
|
IOptions<GoogleAdsConfig> config,
|
|
GoogleAdsClientFactory clientFactory,
|
|
ILogger<GoogleAdsService> logger)
|
|
{
|
|
_config = config.Value;
|
|
_clientFactory = clientFactory;
|
|
_logger = logger;
|
|
}
|
|
|
|
public async Task<ProviderResponse> ExecuteAsync(ProviderRequest request, CancellationToken ct)
|
|
{
|
|
var requestId = request.RequestId ?? Guid.NewGuid().ToString("N");
|
|
var operation = (request.Operation ?? string.Empty).Trim();
|
|
|
|
_logger.LogInformation(
|
|
"[GoogleAds] Executing {Operation} | RequestId={RequestId} TenantId={TenantId} RealApi={RealApi}",
|
|
operation, requestId, request.TenantId, _clientFactory.IsRealApiEnabled);
|
|
|
|
try
|
|
{
|
|
var context = new GoogleAdsContext
|
|
{
|
|
CustomerId = GoogleAdsClientFactory.NormalizeCustomerId(request.TenantId ?? string.Empty),
|
|
LoginCustomerId = request.LoginCustomerId
|
|
};
|
|
|
|
var result = operation switch
|
|
{
|
|
"Ping" => Ping(requestId),
|
|
"TestPing" => Ping(requestId),
|
|
|
|
"CreateCampaign" => await CreateCampaignAsync(request, context, requestId, ct),
|
|
"GetCampaign" => await GetCampaignAsync(request, context, requestId, ct),
|
|
"UpdateCampaign" => await UpdateCampaignAsync(request, context, requestId, ct),
|
|
"ListCampaigns" => await ListCampaignsAsync(request, context, requestId, ct),
|
|
|
|
"GetCampaignStats" => GetCampaignStats(request, requestId),
|
|
"GetAccountStats" => GetAccountStats(request, requestId),
|
|
|
|
"ListAccessibleCustomers" => await ListAccessibleCustomersAsync(context, requestId, ct),
|
|
|
|
"" => ProviderResponse.Fail(requestId, "VALIDATION", "Operation is required"),
|
|
_ => ProviderResponse.Fail(requestId, "UNKNOWN_OPERATION", $"Unknown operation: {operation}")
|
|
};
|
|
|
|
_logger.LogInformation(
|
|
"[GoogleAds] Completed {Operation} | RequestId={RequestId} Ok={Ok}",
|
|
operation, requestId, result.Ok);
|
|
|
|
return result;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "[GoogleAds] Error in {Operation} | RequestId={RequestId}", operation, requestId);
|
|
return ProviderResponse.Fail(requestId, "INTERNAL_ERROR", ex.Message);
|
|
}
|
|
}
|
|
|
|
private ProviderResponse Ping(string requestId)
|
|
=> ProviderResponse.Success(requestId, new
|
|
{
|
|
message = "GoogleApi provider is healthy",
|
|
service = "GoogleApi",
|
|
realApiEnabled = _clientFactory.IsRealApiEnabled,
|
|
apiVersion = _config.ApiVersion,
|
|
timestamp = DateTimeOffset.UtcNow
|
|
});
|
|
|
|
private async Task<ProviderResponse> CreateCampaignAsync(
|
|
ProviderRequest request, GoogleAdsContext context, string requestId, CancellationToken ct)
|
|
{
|
|
var payload = request.GetPayload<CreateCampaignPayload>();
|
|
|
|
if (string.IsNullOrWhiteSpace(payload.Name))
|
|
return ProviderResponse.Fail(requestId, "VALIDATION", "Campaign name is required");
|
|
|
|
if (payload.BudgetMicros <= 0)
|
|
return ProviderResponse.Fail(requestId, "VALIDATION", "BudgetMicros must be > 0");
|
|
|
|
if (_clientFactory.IsRealApiEnabled && !string.IsNullOrWhiteSpace(context.CustomerId))
|
|
return await CreateCampaignRealAsync(payload, context, requestId, ct);
|
|
|
|
var externalId = $"customers/{context.CustomerId}/campaigns/{GenerateId()}";
|
|
_logger.LogInformation("[GoogleAds] EMULATED: Created campaign {CampaignName} => {CampaignId}", payload.Name, externalId);
|
|
|
|
return ProviderResponse.Success(requestId, new
|
|
{
|
|
externalId,
|
|
name = payload.Name,
|
|
type = payload.Type.ToString(),
|
|
status = "ENABLED",
|
|
budgetMicros = payload.BudgetMicros,
|
|
biddingStrategy = payload.BiddingStrategy.ToString(),
|
|
createdAt = DateTimeOffset.UtcNow,
|
|
emulated = true
|
|
});
|
|
}
|
|
|
|
private async Task<ProviderResponse> CreateCampaignRealAsync(
|
|
CreateCampaignPayload payload, GoogleAdsContext context, string requestId, CancellationToken ct)
|
|
{
|
|
try
|
|
{
|
|
GoogleAdsClient client = _clientFactory.CreateClient(context);
|
|
|
|
// 1) Budget
|
|
CampaignBudgetServiceClient budgetService =
|
|
client.GetService(GAdsServices.V22.CampaignBudgetService);
|
|
|
|
var budget = new CampaignBudget
|
|
{
|
|
Name = $"{payload.Name} Budget ({DateTime.UtcNow:yyyyMMddHHmmss})",
|
|
AmountMicros = payload.BudgetMicros,
|
|
DeliveryMethod = BudgetDeliveryMethodEnum.Types.BudgetDeliveryMethod.Standard,
|
|
ExplicitlyShared = false
|
|
};
|
|
|
|
var budgetResponse = await budgetService.MutateCampaignBudgetsAsync(
|
|
new MutateCampaignBudgetsRequest
|
|
{
|
|
CustomerId = context.CustomerId,
|
|
Operations = { new CampaignBudgetOperation { Create = budget } }
|
|
},
|
|
cancellationToken: ct);
|
|
|
|
var budgetResourceName = budgetResponse.Results.FirstOrDefault()?.ResourceName;
|
|
if (string.IsNullOrWhiteSpace(budgetResourceName))
|
|
return ProviderResponse.Fail(requestId, "API_ERROR", "Budget create returned no resource name");
|
|
|
|
// 2) Campaign
|
|
CampaignServiceClient campaignService =
|
|
client.GetService(GAdsServices.V22.CampaignService);
|
|
|
|
var campaign = new Campaign
|
|
{
|
|
Name = payload.Name,
|
|
Status = CampaignStatusEnum.Types.CampaignStatus.Enabled,
|
|
AdvertisingChannelType = MapChannelType(payload.Type),
|
|
CampaignBudget = budgetResourceName
|
|
};
|
|
|
|
// Dates must be yyyyMMdd for Google Ads API
|
|
if (!string.IsNullOrWhiteSpace(payload.StartDate)) campaign.StartDate = payload.StartDate;
|
|
if (!string.IsNullOrWhiteSpace(payload.EndDate)) campaign.EndDate = payload.EndDate;
|
|
|
|
// ✅ Apply bidding in a way that does NOT rely on Campaign.MaximizeClicks property existing
|
|
ApplyBiddingStrategySafe(campaign, payload.BiddingStrategy);
|
|
|
|
var campResponse = await campaignService.MutateCampaignsAsync(
|
|
new MutateCampaignsRequest
|
|
{
|
|
CustomerId = context.CustomerId,
|
|
Operations = { new CampaignOperation { Create = campaign } }
|
|
},
|
|
cancellationToken: ct);
|
|
|
|
var campaignResourceName = campResponse.Results.FirstOrDefault()?.ResourceName;
|
|
|
|
return ProviderResponse.Success(requestId, new
|
|
{
|
|
campaignResourceName,
|
|
budgetResourceName,
|
|
name = payload.Name,
|
|
type = payload.Type.ToString(),
|
|
status = "ENABLED",
|
|
budgetMicros = payload.BudgetMicros,
|
|
biddingStrategy = payload.BiddingStrategy.ToString(),
|
|
emulated = false
|
|
});
|
|
}
|
|
catch (GoogleAdsException gex)
|
|
{
|
|
_logger.LogError(gex, "Google Ads API error creating campaign | RequestId={RequestId}", requestId);
|
|
return HandleGoogleAdsException(gex, requestId);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Failed to create campaign via real API");
|
|
return ProviderResponse.Fail(requestId, "API_ERROR", ex.Message);
|
|
}
|
|
}
|
|
|
|
private async Task<ProviderResponse> GetCampaignAsync(
|
|
ProviderRequest request, GoogleAdsContext context, string requestId, CancellationToken ct)
|
|
{
|
|
var payload = request.GetPayload<GetCampaignPayload>();
|
|
|
|
if (string.IsNullOrWhiteSpace(payload.CampaignId))
|
|
return ProviderResponse.Fail(requestId, "VALIDATION", "CampaignId is required");
|
|
|
|
if (_clientFactory.IsRealApiEnabled && !string.IsNullOrWhiteSpace(context.CustomerId))
|
|
return await GetCampaignRealAsync(payload.CampaignId, context, requestId, ct);
|
|
|
|
_logger.LogInformation("[GoogleAds] EMULATED: Retrieved campaign {CampaignId}", payload.CampaignId);
|
|
|
|
return ProviderResponse.Success(requestId, new
|
|
{
|
|
externalId = payload.CampaignId,
|
|
name = "Sample Campaign",
|
|
type = CampaignType.Search.ToString(),
|
|
status = "ENABLED",
|
|
budgetMicros = 10_000_000L,
|
|
// NOTE: GetCampaignPayload doesn't have BiddingStrategy — so don't reference it
|
|
biddingStrategy = ModelBiddingStrategy.MaximizeClicks.ToString(),
|
|
createdAt = DateTimeOffset.UtcNow.AddDays(-7),
|
|
emulated = true
|
|
});
|
|
}
|
|
|
|
private Task<ProviderResponse> GetCampaignRealAsync(
|
|
string campaignId, GoogleAdsContext context, string requestId, CancellationToken ct)
|
|
{
|
|
try
|
|
{
|
|
GoogleAdsClient client = _clientFactory.CreateClient(context);
|
|
|
|
GoogleAdsServiceClient googleAdsService =
|
|
client.GetService(GAdsServices.V22.GoogleAdsService);
|
|
|
|
var isResourceName = campaignId.Contains("/campaigns/", StringComparison.OrdinalIgnoreCase);
|
|
var where = isResourceName
|
|
? $"campaign.resource_name = '{campaignId}'"
|
|
: $"campaign.id = {campaignId}";
|
|
|
|
var query = $@"
|
|
SELECT
|
|
campaign.resource_name,
|
|
campaign.id,
|
|
campaign.name,
|
|
campaign.status,
|
|
campaign.advertising_channel_type,
|
|
campaign_budget.amount_micros
|
|
FROM campaign
|
|
WHERE {where}
|
|
LIMIT 1";
|
|
|
|
var resp = googleAdsService.Search(new SearchGoogleAdsRequest
|
|
{
|
|
CustomerId = context.CustomerId,
|
|
Query = query
|
|
});
|
|
|
|
var row = resp.FirstOrDefault();
|
|
if (row == null)
|
|
return Task.FromResult(ProviderResponse.Fail(requestId, "NOT_FOUND", "Campaign not found"));
|
|
|
|
return Task.FromResult(ProviderResponse.Success(requestId, new
|
|
{
|
|
campaign = new
|
|
{
|
|
resourceName = row.Campaign.ResourceName,
|
|
id = row.Campaign.Id,
|
|
name = row.Campaign.Name,
|
|
status = row.Campaign.Status.ToString(),
|
|
channelType = row.Campaign.AdvertisingChannelType.ToString(),
|
|
budgetMicros = row.CampaignBudget?.AmountMicros
|
|
},
|
|
emulated = false
|
|
}));
|
|
}
|
|
catch (GoogleAdsException gex)
|
|
{
|
|
_logger.LogError(gex, "Google Ads API error getting campaign | RequestId={RequestId}", requestId);
|
|
return Task.FromResult(HandleGoogleAdsException(gex, requestId));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Failed to get campaign via real API");
|
|
return Task.FromResult(ProviderResponse.Fail(requestId, "API_ERROR", ex.Message));
|
|
}
|
|
}
|
|
|
|
private Task<ProviderResponse> UpdateCampaignAsync(
|
|
ProviderRequest request, GoogleAdsContext context, string requestId, CancellationToken ct)
|
|
{
|
|
var payload = request.GetPayload<UpdateCampaignPayload>();
|
|
|
|
if (string.IsNullOrWhiteSpace(payload.CampaignId))
|
|
return Task.FromResult(ProviderResponse.Fail(requestId, "VALIDATION", "CampaignId is required"));
|
|
|
|
_logger.LogInformation("[GoogleAds] EMULATED: Updated campaign {CampaignId}", payload.CampaignId);
|
|
|
|
return Task.FromResult(ProviderResponse.Success(requestId, new
|
|
{
|
|
updated = true,
|
|
campaignId = payload.CampaignId,
|
|
updatedAt = DateTimeOffset.UtcNow,
|
|
emulated = true
|
|
}));
|
|
}
|
|
|
|
private async Task<ProviderResponse> ListCampaignsAsync(
|
|
ProviderRequest request, GoogleAdsContext context, string requestId, CancellationToken ct)
|
|
{
|
|
if (_clientFactory.IsRealApiEnabled && !string.IsNullOrWhiteSpace(context.CustomerId))
|
|
return await ListCampaignsRealAsync(context, requestId, ct);
|
|
|
|
_logger.LogInformation("[GoogleAds] EMULATED: Listed campaigns for tenant {TenantId}", request.TenantId);
|
|
|
|
var campaigns = new[]
|
|
{
|
|
new { id = $"customers/{context.CustomerId}/campaigns/{GenerateId()}", name = "Brand Campaign", status = "Enabled", budgetMicros = 5_000_000L },
|
|
new { id = $"customers/{context.CustomerId}/campaigns/{GenerateId()}", name = "Product Campaign", status = "Enabled", budgetMicros = 10_000_000L },
|
|
new { id = $"customers/{context.CustomerId}/campaigns/{GenerateId()}", name = "Retargeting", status = "Paused", budgetMicros = 3_000_000L }
|
|
};
|
|
|
|
return ProviderResponse.Success(requestId, new
|
|
{
|
|
campaigns,
|
|
totalCount = campaigns.Length,
|
|
emulated = true
|
|
});
|
|
}
|
|
|
|
private Task<ProviderResponse> ListCampaignsRealAsync(
|
|
GoogleAdsContext context, string requestId, CancellationToken ct)
|
|
{
|
|
try
|
|
{
|
|
GoogleAdsClient client = _clientFactory.CreateClient(context);
|
|
|
|
GoogleAdsServiceClient googleAdsService =
|
|
client.GetService(GAdsServices.V22.GoogleAdsService);
|
|
|
|
var query = @"
|
|
SELECT
|
|
campaign.resource_name,
|
|
campaign.id,
|
|
campaign.name,
|
|
campaign.status,
|
|
campaign.advertising_channel_type,
|
|
campaign_budget.amount_micros
|
|
FROM campaign
|
|
ORDER BY campaign.name";
|
|
|
|
var results = new List<object>();
|
|
|
|
var resp = googleAdsService.Search(new SearchGoogleAdsRequest
|
|
{
|
|
CustomerId = context.CustomerId,
|
|
Query = query
|
|
});
|
|
|
|
foreach (var row in resp)
|
|
{
|
|
ct.ThrowIfCancellationRequested();
|
|
results.Add(new
|
|
{
|
|
resourceName = row.Campaign.ResourceName,
|
|
id = row.Campaign.Id,
|
|
name = row.Campaign.Name,
|
|
status = row.Campaign.Status.ToString(),
|
|
channelType = row.Campaign.AdvertisingChannelType.ToString(),
|
|
budgetMicros = row.CampaignBudget?.AmountMicros
|
|
});
|
|
}
|
|
|
|
return Task.FromResult(ProviderResponse.Success(requestId, new
|
|
{
|
|
campaigns = results,
|
|
totalCount = results.Count,
|
|
emulated = false
|
|
}));
|
|
}
|
|
catch (GoogleAdsException gex)
|
|
{
|
|
_logger.LogError(gex, "Google Ads API error listing campaigns | RequestId={RequestId}", requestId);
|
|
return Task.FromResult(HandleGoogleAdsException(gex, requestId));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Failed to list campaigns via real API");
|
|
return Task.FromResult(ProviderResponse.Fail(requestId, "API_ERROR", ex.Message));
|
|
}
|
|
}
|
|
|
|
private ProviderResponse GetCampaignStats(ProviderRequest request, string requestId)
|
|
{
|
|
var payload = request.GetPayload<CampaignStatsPayload>();
|
|
|
|
if (string.IsNullOrWhiteSpace(payload.CampaignId))
|
|
return ProviderResponse.Fail(requestId, "VALIDATION", "CampaignId is required");
|
|
|
|
_logger.LogInformation("[GoogleAds] EMULATED: Retrieved stats for campaign {CampaignId}", payload.CampaignId);
|
|
|
|
return ProviderResponse.Success(requestId, new
|
|
{
|
|
campaignId = payload.CampaignId,
|
|
dateRange = new { start = payload.StartDate ?? "2026-01-01", end = payload.EndDate ?? "2026-01-27" },
|
|
metrics = new
|
|
{
|
|
impressions = 15_234L,
|
|
clicks = 487L,
|
|
costMicros = 2_543_000L,
|
|
conversions = 23,
|
|
ctr = 0.032,
|
|
averageCpcMicros = 5_222L
|
|
},
|
|
emulated = true
|
|
});
|
|
}
|
|
|
|
private ProviderResponse GetAccountStats(ProviderRequest request, string requestId)
|
|
{
|
|
var payload = request.GetPayload<AccountStatsPayload>();
|
|
|
|
_logger.LogInformation("[GoogleAds] EMULATED: Retrieved account stats for tenant {TenantId}", request.TenantId);
|
|
|
|
return ProviderResponse.Success(requestId, new
|
|
{
|
|
tenantId = request.TenantId,
|
|
dateRange = new { start = payload.StartDate ?? "2026-01-01", end = payload.EndDate ?? "2026-01-27" },
|
|
metrics = new
|
|
{
|
|
totalCampaigns = 5,
|
|
activeCampaigns = 3,
|
|
totalImpressions = 152_340L,
|
|
totalClicks = 4_870L,
|
|
totalCostMicros = 25_430_000L,
|
|
totalConversions = 234
|
|
},
|
|
emulated = true
|
|
});
|
|
}
|
|
|
|
private Task<ProviderResponse> ListAccessibleCustomersAsync(
|
|
GoogleAdsContext context, string requestId, CancellationToken ct)
|
|
{
|
|
if (!_clientFactory.IsRealApiEnabled)
|
|
{
|
|
return Task.FromResult(ProviderResponse.Success(requestId, new
|
|
{
|
|
customers = new[] { "1234567890", "9876543210" },
|
|
emulated = true
|
|
}));
|
|
}
|
|
|
|
try
|
|
{
|
|
GoogleAdsClient client = _clientFactory.CreateClient(context);
|
|
|
|
CustomerServiceClient customerService =
|
|
client.GetService(GAdsServices.V22.CustomerService);
|
|
|
|
var resp = customerService.ListAccessibleCustomers(new ListAccessibleCustomersRequest());
|
|
|
|
var customers = resp.ResourceNames
|
|
.Select(rn => rn.Split('/').LastOrDefault() ?? rn)
|
|
.ToArray();
|
|
|
|
return Task.FromResult(ProviderResponse.Success(requestId, new
|
|
{
|
|
customers,
|
|
rawResourceNames = resp.ResourceNames.ToArray(),
|
|
emulated = false
|
|
}));
|
|
}
|
|
catch (GoogleAdsException gex)
|
|
{
|
|
_logger.LogError(gex, "Google Ads API error listing accessible customers | RequestId={RequestId}", requestId);
|
|
return Task.FromResult(HandleGoogleAdsException(gex, requestId));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Failed to list accessible customers");
|
|
return Task.FromResult(ProviderResponse.Fail(requestId, "API_ERROR", ex.Message));
|
|
}
|
|
}
|
|
|
|
private static string GenerateId() => Guid.NewGuid().ToString("N")[..12];
|
|
|
|
private static AdvertisingChannelTypeEnum.Types.AdvertisingChannelType MapChannelType(CampaignType type)
|
|
=> type switch
|
|
{
|
|
CampaignType.Search => AdvertisingChannelTypeEnum.Types.AdvertisingChannelType.Search,
|
|
CampaignType.Display => AdvertisingChannelTypeEnum.Types.AdvertisingChannelType.Display,
|
|
CampaignType.Shopping => AdvertisingChannelTypeEnum.Types.AdvertisingChannelType.Shopping,
|
|
CampaignType.Video => AdvertisingChannelTypeEnum.Types.AdvertisingChannelType.Video,
|
|
CampaignType.PerformanceMax => AdvertisingChannelTypeEnum.Types.AdvertisingChannelType.PerformanceMax,
|
|
_ => AdvertisingChannelTypeEnum.Types.AdvertisingChannelType.Search
|
|
};
|
|
|
|
// ✅ Strategy application that avoids Campaign.MaximizeClicks property dependency
|
|
private static void ApplyBiddingStrategySafe(Campaign campaign, ModelBiddingStrategy strategy)
|
|
{
|
|
// Try to set the enum safely without compile-time dependency on the member name.
|
|
// Different library/proto generations sometimes change the C# member casing.
|
|
static BiddingStrategyTypeEnum.Types.BiddingStrategyType ParseBst(params string[] names)
|
|
{
|
|
foreach (var n in names)
|
|
{
|
|
if (Enum.TryParse<BiddingStrategyTypeEnum.Types.BiddingStrategyType>(n, ignoreCase: true, out var v))
|
|
return v;
|
|
}
|
|
return BiddingStrategyTypeEnum.Types.BiddingStrategyType.Unspecified;
|
|
}
|
|
|
|
campaign.BiddingStrategyType = strategy switch
|
|
{
|
|
ModelBiddingStrategy.ManualCpc =>
|
|
ParseBst("ManualCpc", "MANUAL_CPC"),
|
|
|
|
ModelBiddingStrategy.MaximizeClicks =>
|
|
ParseBst("MaximizeClicks", "MAXIMIZE_CLICKS", "MaximizeClick"),
|
|
|
|
ModelBiddingStrategy.MaximizeConversions =>
|
|
ParseBst("MaximizeConversions", "MAXIMIZE_CONVERSIONS"),
|
|
|
|
ModelBiddingStrategy.TargetCpa =>
|
|
ParseBst("TargetCpa", "TARGET_CPA"),
|
|
|
|
ModelBiddingStrategy.TargetRoas =>
|
|
ParseBst("TargetRoas", "TARGET_ROAS"),
|
|
|
|
_ =>
|
|
ParseBst("MaximizeClicks", "MAXIMIZE_CLICKS", "Unspecified")
|
|
};
|
|
|
|
// Optional: set oneof objects ONLY when you know your generated Campaign has them.
|
|
// ManualCpc is the most consistently present.
|
|
if (strategy == ModelBiddingStrategy.ManualCpc)
|
|
{
|
|
campaign.ManualCpc = new ManualCpc();
|
|
}
|
|
|
|
// If your Campaign class DOES have these properties in your build, you can uncomment:
|
|
// if (strategy == ModelBiddingStrategy.MaximizeClicks) campaign.MaximizeClicks = new MaximizeClicks();
|
|
// if (strategy == ModelBiddingStrategy.MaximizeConversions) campaign.MaximizeConversions = new MaximizeConversions();
|
|
}
|
|
|
|
|
|
private static ProviderResponse HandleGoogleAdsException(GoogleAdsException gex, string requestId)
|
|
{
|
|
var errorDetails = gex.Failure?.Errors?.Select(e => new
|
|
{
|
|
errorCode = e.ErrorCode?.ToString(),
|
|
message = e.Message,
|
|
trigger = e.Trigger?.StringValue,
|
|
location = e.Location?.FieldPathElements?.Select(f => f.FieldName).ToArray()
|
|
}).ToList();
|
|
|
|
return ProviderResponse.Fail(requestId, "GOOGLE_ADS_ERROR", gex.Message, new
|
|
{
|
|
googleRequestId = gex.RequestId,
|
|
errors = errorDetails
|
|
});
|
|
}
|
|
}
|