Publicar en Google Bussines, salir en maps
Un ejemplo de cómo implementar el código para gestionar ofertas y contenidos (local posts) en Google My Business (Google Business Profile) mediante su API. Este código se basa en la infraestructura y patrones sugeridos anteriormente: obtención de tokens a través de un servicio genérico (IExternalTokenService
), uso de HttpClient
para llamadas a la API y DTOs simples para modelar las respuestas y peticiones.
Importante:
- Google My Business ahora se denomina Google Business Profile.
- La API utilizada es la Google My Business API (o Google Business Profile API).
- Necesitas habilitar la API en Google Cloud Console y contar con las credenciales OAuth 2.0.
- Debes haber implementado el flujo OAuth y contar con un
RefreshToken
yAccessToken
vigentes. - La creación de posts, ofertas y otro contenido requiere permisos específicos y scopes adecuados (por ejemplo,
https://www.googleapis.com/auth/business.manage
).
Documentación:
Consulta la documentación oficial para los endpoints, ya que puede cambiar con el tiempo. En la versión actual (v4) de la API, las localPosts se gestionan en:POST https://mybusiness.googleapis.com/v4/accounts/{accountId}/locations/{locationId}/localPosts
Ref: LocalPosts Reference
Ejemplo de Creación de una Oferta (Local Post de tipo 'OFFER')
A continuación, presentamos un servicio de aplicación GoogleMyBusinessContentAppService
con métodos para crear, listar y actualizar posts. Suponemos que ya tienes el accountId
y el locationId
de tu negocio, así como el accountIdentifier
para obtener el token.
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using Newtonsoft.Json;
// Interfaz genérica para obtener tokens
public interface IExternalTokenService
{
Task<string> GetValidAccessTokenAsync(string providerName, string accountIdentifier);
}
// DTOs para Local Posts
public class LocalPost
{
public string Name { get; set; } // "accounts/{accountId}/locations/{locationId}/localPosts/{postId}"
public string LanguageCode { get; set; } = "en";
public string Summary { get; set; }
public string EventType { get; set; } // "OFFER", "STANDARD", "EVENT"
public Offer Offer { get; set; }
public List<MediaItem> Media { get; set; }
}
public class Offer
{
public string CouponCode { get; set; }
public string RedeemOnlineUrl { get; set; }
public string TermsConditions { get; set; }
public TimeRange OfferTime { get; set; }
}
public class TimeRange
{
public string StartTime { get; set; } // RFC3339 format: "2024-01-01T09:00:00Z"
public string EndTime { get; set; } // RFC3339 format
}
public class MediaItem
{
public string MediaFormat { get; set; } = "PHOTO"; // o "VIDEO"
public string SourceUrl { get; set; }
}
public class LocalPostListResponse
{
[JsonProperty("localPosts")]
public List<LocalPost> LocalPosts { get; set; }
}
public class GoogleMyBusinessContentAppService
{
private readonly IExternalTokenService _tokenService;
private readonly HttpClient _httpClient;
// Constructor, inyectar dependencias
public GoogleMyBusinessContentAppService(IExternalTokenService tokenService, HttpClient httpClient)
{
_tokenService = tokenService;
_httpClient = httpClient;
}
/// <summary>
/// Crea una oferta (local post) en Google My Business.
/// </summary>
public async Task<LocalPost> CreateOfferPostAsync(string accountIdentifier, string accountId, string locationId,
string summary, string couponCode, string redeemUrl, string terms, DateTime startTimeUtc, DateTime endTimeUtc, string imageUrl)
{
var accessToken = await _tokenService.GetValidAccessTokenAsync("Google", accountIdentifier);
_httpClient.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken);
var url = $"https://mybusiness.googleapis.com/v4/accounts/{accountId}/locations/{locationId}/localPosts";
var post = new LocalPost
{
EventType = "OFFER",
Summary = summary,
Offer = new Offer
{
CouponCode = couponCode,
RedeemOnlineUrl = redeemUrl,
TermsConditions = terms,
OfferTime = new TimeRange
{
StartTime = startTimeUtc.ToString("o"),
EndTime = endTimeUtc.ToString("o")
}
},
Media = new List<MediaItem>()
};
if (!string.IsNullOrEmpty(imageUrl))
{
post.Media.Add(new MediaItem { SourceUrl = imageUrl });
}
var content = new StringContent(JsonConvert.SerializeObject(post), System.Text.Encoding.UTF8, "application/json");
var response = await _httpClient.PostAsync(url, content);
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadAsStringAsync();
var createdPost = JsonConvert.DeserializeObject<LocalPost>(json);
return createdPost;
}
/// <summary>
/// Lista los posts (incluyendo ofertas) existentes en una ubicación
/// </summary>
public async Task<List<LocalPost>> ListPostsAsync(string accountIdentifier, string accountId, string locationId)
{
var accessToken = await _tokenService.GetValidAccessTokenAsync("Google", accountIdentifier);
_httpClient.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken);
var url = $"https://mybusiness.googleapis.com/v4/accounts/{accountId}/locations/{locationId}/localPosts";
var response = await _httpClient.GetAsync(url);
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadAsStringAsync();
var postsData = JsonConvert.DeserializeObject<LocalPostListResponse>(json);
return postsData?.LocalPosts ?? new List<LocalPost>();
}
/// <summary>
/// Actualiza un post existente. Por ejemplo, extender la fecha de la oferta o cambiar el texto.
/// </summary>
public async Task<LocalPost> UpdatePostAsync(string accountIdentifier, string postName, string newSummary)
{
var accessToken = await _tokenService.GetValidAccessTokenAsync("Google", accountIdentifier);
_httpClient.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken);
// postName viene con el formato "accounts/{accountId}/locations/{locationId}/localPosts/{postId}"
var url = $"https://mybusiness.googleapis.com/v4/{postName}?updateMask=summary";
var updatePayload = new { summary = newSummary };
var content = new StringContent(JsonConvert.SerializeObject(updatePayload), System.Text.Encoding.UTF8, "application/json");
var response = await _httpClient.PatchAsync(url, content); // PATCH no está en HttpClient por defecto, puedes simularlo con:
// var req = new HttpRequestMessage(new HttpMethod("PATCH"), url) { Content = content };
// var response = await _httpClient.SendAsync(req);
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadAsStringAsync();
var updatedPost = JsonConvert.DeserializeObject<LocalPost>(json);
return updatedPost;
}
/// <summary>
/// Elimina un post existente
/// </summary>
public async Task DeletePostAsync(string accountIdentifier, string postName)
{
var accessToken = await _tokenService.GetValidAccessTokenAsync("Google", accountIdentifier);
_httpClient.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken);
var url = $"https://mybusiness.googleapis.com/v4/{postName}";
var response = await _httpClient.DeleteAsync(url);
response.EnsureSuccessStatusCode();
}
}
// Extensión para HttpClient para realizar PATCH (si no tienes .NET 5+ o un método HttpMethod.Patch)
public static class HttpClientExtensions
{
public static async Task<HttpResponseMessage> PatchAsync(this HttpClient client, string requestUri, HttpContent content)
{
var request = new HttpRequestMessage(new HttpMethod("PATCH"), requestUri)
{
Content = content
};
return await client.SendAsync(request);
}
}
Uso del Servicio
Ejemplo de uso dentro de un ApplicationService mayor:
public class PromotionPublisherAppService
{
private readonly GoogleMyBusinessContentAppService _gmbContentService;
public PromotionPublisherAppService(GoogleMyBusinessContentAppService gmbContentService)
{
_gmbContentService = gmbContentService;
}
public async Task PublishSpecialOfferAsync(string accountIdentifier, string accountId, string locationId)
{
var summary = "Oferta especial: 2x1 en sushi";
var couponCode = "SUSHI2024";
var redeemUrl = "https://mi-restaurante.com/ofertas";
var terms = "Válido hasta agotar existencias.";
var startTime = DateTime.UtcNow;
var endTime = DateTime.UtcNow.AddDays(7);
var imageUrl = "https://mi-storage.blob.core.windows.net/promotions/sushi2x1.png";
var createdPost = await _gmbContentService.CreateOfferPostAsync(
accountIdentifier, accountId, locationId, summary, couponCode, redeemUrl, terms, startTime, endTime, imageUrl
);
Console.WriteLine($"Post creado: {createdPost.Name}");
}
}
Consideraciones Finales
- Ajusta los scopes OAuth y permisos necesarios en Google Cloud Console.
- Asegúrate de tener el
accountId
ylocationId
correctos. Estos se obtienen llamando aaccounts
ylocations
endpoints previamente, como se mostró en ejemplos anteriores. - Valida las fechas y formato RFC3339 (
.ToString("o")
) para las ofertas. - Maneja errores y excepciones más robustamente en un entorno real.
- Consulta la documentación actualizada de Google Business Profile para confirmar endpoints, campos y permisos.
Este ejemplo muestra la lógica base para crear, listar, actualizar y eliminar posts (ofertas) en Google My Business mediante la API, integrándolo con el flujo de autenticación previamente descrito y un servicio genérico para manejo de tokens.