diff --git a/WhatsappBusiness.CloudApi/AccountMetrics/ConversationDirection.cs b/WhatsappBusiness.CloudApi/AccountMetrics/ConversationDirection.cs new file mode 100644 index 0000000..181469f --- /dev/null +++ b/WhatsappBusiness.CloudApi/AccountMetrics/ConversationDirection.cs @@ -0,0 +1,8 @@ +namespace WhatsappBusiness.CloudApi.AccountMetrics +{ + public static class ConversationDirection + { + public const string BusinessInitiated = "business_initiated"; + public const string UserInitiated = "user_initiated"; + } +} diff --git a/WhatsappBusiness.CloudApi/AccountMetrics/ConversationType.cs b/WhatsappBusiness.CloudApi/AccountMetrics/ConversationType.cs new file mode 100644 index 0000000..365847d --- /dev/null +++ b/WhatsappBusiness.CloudApi/AccountMetrics/ConversationType.cs @@ -0,0 +1,9 @@ +namespace WhatsappBusiness.CloudApi.AccountMetrics +{ + public static class ConversationType + { + public const string FREE_ENTRY = "FREE_ENTRY"; + public const string FREE_TIER = "FREE_TIER"; + public const string REGULAR = "REGULAR"; + } +} diff --git a/WhatsappBusiness.CloudApi/AccountMetrics/Dimension.cs b/WhatsappBusiness.CloudApi/AccountMetrics/Dimension.cs new file mode 100644 index 0000000..04bcaf5 --- /dev/null +++ b/WhatsappBusiness.CloudApi/AccountMetrics/Dimension.cs @@ -0,0 +1,10 @@ +namespace WhatsappBusiness.CloudApi.AccountMetrics +{ + public static class Dimension + { + public const string Phone = "phone"; + public const string Country = "country"; + public const string ConversationType = "conversation_type"; + public const string COnversationDirection = "conversation_direction"; + } +} diff --git a/WhatsappBusiness.CloudApi/AccountMetrics/Granularity.cs b/WhatsappBusiness.CloudApi/AccountMetrics/Granularity.cs new file mode 100644 index 0000000..c6c51cb --- /dev/null +++ b/WhatsappBusiness.CloudApi/AccountMetrics/Granularity.cs @@ -0,0 +1,19 @@ +namespace WhatsappBusiness.CloudApi.AccountMetrics +{ + public static class Granularity + { + public static class AnalyticsGranularity + { + public const string HALF_HOUR = "HALF_HOUR"; + public const string DAY = "DAY"; + public const string MONTH = "MONTH"; + } + + public static class ConversationGranularity + { + public const string HALF_HOUR = "HALF_HOUR"; + public const string DAILY = "DAILY"; + public const string MONTHLY = "MONTHLY"; + } + } +} diff --git a/WhatsappBusiness.CloudApi/AccountMetrics/MetricType.cs b/WhatsappBusiness.CloudApi/AccountMetrics/MetricType.cs new file mode 100644 index 0000000..e20611a --- /dev/null +++ b/WhatsappBusiness.CloudApi/AccountMetrics/MetricType.cs @@ -0,0 +1,8 @@ +namespace WhatsappBusiness.CloudApi.AccountMetrics +{ + public static class MetricType + { + public const string COST = "COST"; + public const string CONVERSATION = "CONVERSATION"; + } +} diff --git a/WhatsappBusiness.CloudApi/Extensions/ServiceCollectionExtension.cs b/WhatsappBusiness.CloudApi/Extensions/ServiceCollectionExtension.cs index 49e154e..0a32e12 100644 --- a/WhatsappBusiness.CloudApi/Extensions/ServiceCollectionExtension.cs +++ b/WhatsappBusiness.CloudApi/Extensions/ServiceCollectionExtension.cs @@ -39,7 +39,7 @@ public static void AddWhatsAppBusinessCloudApiService(this IServiceCollection se services.AddHttpClient(options => { - options.BaseAddress = (isLatestGraphApiVersion) ? WhatsAppBusinessRequestEndpoint.V14BaseAddress : WhatsAppBusinessRequestEndpoint.BaseAddress; + options.BaseAddress = WhatsAppBusinessRequestEndpoint.BaseAddress; options.Timeout = TimeSpan.FromMinutes(10); }).ConfigurePrimaryHttpMessageHandler(messageHandler => { @@ -76,7 +76,7 @@ public static void AddWhatsAppBusinessCloudApiService(this IServiceCol services.AddHttpClient(options => { - options.BaseAddress = (isLatestGraphApiVersion) ? WhatsAppBusinessRequestEndpoint.V14BaseAddress : WhatsAppBusinessRequestEndpoint.BaseAddress; + options.BaseAddress = WhatsAppBusinessRequestEndpoint.BaseAddress; options.Timeout = TimeSpan.FromMinutes(10); }).SetHandlerLifetime(Timeout.InfiniteTimeSpan) .ConfigurePrimaryHttpMessageHandler() diff --git a/WhatsappBusiness.CloudApi/Interfaces/IWhatsAppBusinessClient.cs b/WhatsappBusiness.CloudApi/Interfaces/IWhatsAppBusinessClient.cs index 3e2b411..1e47bf4 100644 --- a/WhatsappBusiness.CloudApi/Interfaces/IWhatsAppBusinessClient.cs +++ b/WhatsappBusiness.CloudApi/Interfaces/IWhatsAppBusinessClient.cs @@ -1,4 +1,6 @@ -using System.Threading; +using System; +using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using WhatsappBusiness.CloudApi.AccountMigration.Requests; using WhatsappBusiness.CloudApi.BusinessProfile.Requests; @@ -783,5 +785,151 @@ public interface IWhatsAppBusinessClient /// BaseSuccessResponse BaseSuccessResponse DeleteWABASubscription(string whatsAppBusinessAccountId, CancellationToken cancellationToken = default); #endregion + + #region Account Metrics + /// + /// The analytics field provides the number and type of messages sent and delivered by the phone numbers associated with a specific WABA + /// + /// Your WhatsApp Business Account (WABA) ID. + /// The start date for the date range you are retrieving analytics for + /// The end date for the date range you are retrieving analytics for + /// The granularity by which you would like to retrieve the analytics + /// An array of phone numbers for which you would like to retrieve analytics. If not provided, all phone numbers added to your WABA are included. + /// The types of messages (notification messages and/or customer support messages) for which you want to retrieve notifications. Provide an array and include 0 for notification messages, and 2 for customer support messages. If not provided, analytics will be returned for all messages together + /// The countries for which you would like to retrieve analytics. Provide an array with 2 letter country codes for the countries you would like to include. If not provided, analytics will be returned for all countries you have communicated with + /// Cancellation token + /// AnalyticsResponse + AnalyticsResponse GetAnalyticMetrics(string whatsAppBusinessAccountId, DateTime startDate, DateTime endDate, string granularity, List? phoneNumbers = null, List? productTypes = null, List? countryCodes = null, CancellationToken cancellationToken = default); + + /// + /// The analytics field provides the number and type of messages sent and delivered by the phone numbers associated with a specific WABA + /// + /// Your WhatsApp Business Account (WABA) ID. + /// The start date for the date range you are retrieving analytics for + /// The end date for the date range you are retrieving analytics for + /// The granularity by which you would like to retrieve the analytics + /// An array of phone numbers for which you would like to retrieve analytics. If not provided, all phone numbers added to your WABA are included. + /// The types of messages (notification messages and/or customer support messages) for which you want to retrieve notifications. Provide an array and include 0 for notification messages, and 2 for customer support messages. If not provided, analytics will be returned for all messages together + /// The countries for which you would like to retrieve analytics. Provide an array with 2 letter country codes for the countries you would like to include. If not provided, analytics will be returned for all countries you have communicated with + /// Cancellation token + /// AnalyticsResponse + Task GetAnalyticMetricsAsync(string whatsAppBusinessAccountId, DateTime startDate, DateTime endDate, string granularity, List? phoneNumbers = null, List? productTypes = null, List? countryCodes = null, CancellationToken cancellationToken = default); + + /// + /// The conversation_analytics field provides cost and conversation information for a specific WABA. + /// + /// Your WhatsApp Business Account (WABA) ID. + /// The start date for the date range you are retrieving analytics for + /// The end date for the date range you are retrieving analytics for + /// The granularity by which you would like to retrieve the analytics + /// An array of phone numbers for which you would like to retrieve analytics. If not provided, all phone numbers added to your WABA are included. + /// List of metrics you would like to receive. If you send an empty list, we return results for all metric types. + /// List of conversation types. If you send an empty list, we return results for all conversation types. + /// List of conversation directions. If you send an empty list, we return results for all conversation directions + /// List of breakdowns you would like to apply to your metrics. If you send an empty list, we return results without any breakdowns. + /// Cancellation token + /// ConversationAnalyticsResponse + ConversationAnalyticsResponse GetConversationAnalyticMetrics(string whatsAppBusinessAccountId, DateTime startDate, DateTime endDate, string granularity, List? phoneNumbers = null, List? metricTypes = null, List? conversationTypes = null, List? conversationDirections = null, List? dimensions = null, CancellationToken cancellationToken = default); + + /// + /// The conversation_analytics field provides cost and conversation information for a specific WABA. + /// + /// Your WhatsApp Business Account (WABA) ID. + /// The start date for the date range you are retrieving analytics for + /// The end date for the date range you are retrieving analytics for + /// The granularity by which you would like to retrieve the analytics + /// An array of phone numbers for which you would like to retrieve analytics. If not provided, all phone numbers added to your WABA are included. + /// List of metrics you would like to receive. If you send an empty list, we return results for all metric types. + /// List of conversation types. If you send an empty list, we return results for all conversation types. + /// List of conversation directions. If you send an empty list, we return results for all conversation directions + /// List of breakdowns you would like to apply to your metrics. If you send an empty list, we return results without any breakdowns. + /// Cancellation token + /// ConversationAnalyticsResponse + Task GetConversationAnalyticMetricsAsync(string whatsAppBusinessAccountId, DateTime startDate, DateTime endDate, string granularity, List? phoneNumbers = null, List? metricTypes = null, List? conversationTypes = null, List? conversationDirections = null, List? dimensions = null, CancellationToken cancellationToken = default); + #endregion + + #region QR Code Message + /// + /// To create a QR code for a business, send a POST request to the /{phone-number-ID}/message_qrdls endpoint with the prefilled_message parameter set to your message text and generate_qr_image parameter set to your preferred image format, either SVG or PNG. + /// + /// + /// + /// + /// QRCodeMessageResponse + QRCodeMessageResponse CreateQRCodeMessage(string messageText, string qrImageFormat, CancellationToken cancellationToken = default); + + /// + /// To create a QR code for a business, send a POST request to the /{phone-number-ID}/message_qrdls endpoint with the prefilled_message parameter set to your message text and generate_qr_image parameter set to your preferred image format, either SVG or PNG. + /// + /// + /// + /// + /// QRCodeMessageResponse + Task CreateQRCodeMessageAsync(string messageText, string qrImageFormat, CancellationToken cancellationToken = default); + + /// + /// To get a list of all the QR codes messages for a business + /// + /// + /// QRCodeMessageFilterResponse + QRCodeMessageFilterResponse GetQRCodeMessageList(CancellationToken cancellationToken = default); + + /// + /// To get a list of all the QR codes messages for a business + /// + /// + /// QRCodeMessageFilterResponse + Task GetQRCodeMessageListAsync(CancellationToken cancellationToken = default); + + /// + /// To get information about a specific QR code message + /// + /// + /// + /// QRCodeMessageFilterResponse + QRCodeMessageFilterResponse GetQRCodeMessageById(string qrCodeId, CancellationToken cancellationToken = default); + + /// + /// To get information about a specific QR code message + /// + /// + /// + /// QRCodeMessageFilterResponse + Task GetQRCodeMessageByIdAsync(string qrCodeId, CancellationToken cancellationToken = default); + + /// + /// To update a QR code for a business, send a POST request to the /{phone-number-ID}/message_qrdls/{qr-code-id} endpoint and include the parameter you wish to update. + /// + /// + /// + /// + /// QRCodeMessageResponse + QRCodeMessageResponse UpdateQRCodeMessage(string qrCodeId, string messageText, CancellationToken cancellationToken = default); + + /// + /// To update a QR code for a business, send a POST request to the /{phone-number-ID}/message_qrdls/{qr-code-id} endpoint and include the parameter you wish to update. + /// + /// + /// + /// + /// QRCodeMessageResponse + Task UpdateQRCodeMessageAsync(string qrCodeId, string messageText, CancellationToken cancellationToken = default); + + /// + /// QR codes do not expire. You must delete a QR code in order to retire it. + /// + /// + /// + /// BaseSuccessResponse + BaseSuccessResponse DeleteQRCodeMessage(string qrCodeId, CancellationToken cancellationToken = default); + + /// + /// QR codes do not expire. You must delete a QR code in order to retire it. + /// + /// + /// + /// BaseSuccessResponse + Task DeleteQRCodeMessageAsync(string qrCodeId, CancellationToken cancellationToken = default); + #endregion } } \ No newline at end of file diff --git a/WhatsappBusiness.CloudApi/Response/AnalyticsResponse.cs b/WhatsappBusiness.CloudApi/Response/AnalyticsResponse.cs new file mode 100644 index 0000000..1fcc760 --- /dev/null +++ b/WhatsappBusiness.CloudApi/Response/AnalyticsResponse.cs @@ -0,0 +1,44 @@ +using Newtonsoft.Json; +using System.Collections.Generic; + +namespace WhatsappBusiness.CloudApi.Response +{ + public class AnalyticsResponse + { + [JsonProperty("analytics")] + public Analytics Analytics { get; set; } + + [JsonProperty("id")] + public string Id { get; set; } + } + + public class Analytics + { + [JsonProperty("phone_numbers")] + public List PhoneNumbers { get; set; } + + [JsonProperty("country_codes")] + public List CountryCodes { get; set; } + + [JsonProperty("granularity")] + public string Granularity { get; set; } + + [JsonProperty("data_points")] + public List DataPoints { get; set; } + } + + public class AnalyticsDataPoint + { + [JsonProperty("start")] + public long Start { get; set; } + + [JsonProperty("end")] + public long End { get; set; } + + [JsonProperty("sent")] + public long Sent { get; set; } + + [JsonProperty("delivered")] + public long Delivered { get; set; } + } +} \ No newline at end of file diff --git a/WhatsappBusiness.CloudApi/Response/ConversationAnalyticsResponse.cs b/WhatsappBusiness.CloudApi/Response/ConversationAnalyticsResponse.cs new file mode 100644 index 0000000..3fb1613 --- /dev/null +++ b/WhatsappBusiness.CloudApi/Response/ConversationAnalyticsResponse.cs @@ -0,0 +1,47 @@ +using Newtonsoft.Json; +using System.Collections.Generic; + +namespace WhatsappBusiness.CloudApi.Response +{ + public class ConversationAnalyticsResponse + { + [JsonProperty("conversation_analytics")] + public ConversationAnalytics ConversationAnalytics { get; set; } + } + + public class ConversationAnalytics + { + [JsonProperty("data")] + public List Data { get; set; } + } + + public class ConversationAnalyticsData + { + [JsonProperty("data_points")] + public List DataPoints { get; set; } + } + + public class ConversationAnalyticsDataPoint + { + [JsonProperty("start")] + public long Start { get; set; } + + [JsonProperty("end")] + public long End { get; set; } + + [JsonProperty("conversation")] + public long Conversation { get; set; } + + [JsonProperty("phone_number")] + public string PhoneNumber { get; set; } + + [JsonProperty("conversation_type")] + public string ConversationType { get; set; } + + [JsonProperty("conversation_direction")] + public string ConversationDirection { get; set; } + + [JsonProperty("cost")] + public double Cost { get; set; } + } +} \ No newline at end of file diff --git a/WhatsappBusiness.CloudApi/Response/QRCodeMessageFilterResponse.cs b/WhatsappBusiness.CloudApi/Response/QRCodeMessageFilterResponse.cs new file mode 100644 index 0000000..8c8ed1b --- /dev/null +++ b/WhatsappBusiness.CloudApi/Response/QRCodeMessageFilterResponse.cs @@ -0,0 +1,11 @@ +using Newtonsoft.Json; +using System.Collections.Generic; + +namespace WhatsappBusiness.CloudApi.Response +{ + public class QRCodeMessageFilterResponse + { + [JsonProperty("data")] + public List Data { get; set; } + } +} diff --git a/WhatsappBusiness.CloudApi/Response/QRCodeMessageResponse.cs b/WhatsappBusiness.CloudApi/Response/QRCodeMessageResponse.cs new file mode 100644 index 0000000..e3020e1 --- /dev/null +++ b/WhatsappBusiness.CloudApi/Response/QRCodeMessageResponse.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json; + +namespace WhatsappBusiness.CloudApi.Response +{ + public class QRCodeMessageResponse + { + [JsonProperty("code")] + public string Code { get; set; } + + [JsonProperty("prefilled_message")] + public string PrefilledMessage { get; set; } + + [JsonProperty("deep_link_url")] + public string DeepLinkUrl { get; set; } + + [JsonProperty("qr_image_url")] + public string QrImageUrl { get; set; } + } +} diff --git a/WhatsappBusiness.CloudApi/WhatsAppBusinessClient.cs b/WhatsappBusiness.CloudApi/WhatsAppBusinessClient.cs index 7932f29..f16efa8 100644 --- a/WhatsappBusiness.CloudApi/WhatsAppBusinessClient.cs +++ b/WhatsappBusiness.CloudApi/WhatsAppBusinessClient.cs @@ -1,15 +1,19 @@ using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Primitives; using Newtonsoft.Json; using Polly; using Polly.Extensions.Http; using System; +using System.Collections.Generic; using System.IO; using System.Net; using System.Net.Http; using System.Net.Http.Headers; +using System.Reflection; using System.Text; using System.Threading; using System.Threading.Tasks; +using WhatsappBusiness.CloudApi.AccountMetrics; using WhatsappBusiness.CloudApi.AccountMigration.Requests; using WhatsappBusiness.CloudApi.BusinessProfile.Requests; using WhatsappBusiness.CloudApi.Configurations; @@ -54,7 +58,7 @@ public WhatsAppBusinessClient(WhatsAppBusinessCloudApiConfig whatsAppConfig, boo var services = new ServiceCollection(); services.AddHttpClient("WhatsAppBusinessApiClient", client => { - client.BaseAddress = (isLatestGraphApiVersion) ? WhatsAppBusinessRequestEndpoint.V14BaseAddress : WhatsAppBusinessRequestEndpoint.BaseAddress; + client.BaseAddress = WhatsAppBusinessRequestEndpoint.BaseAddress; client.Timeout = TimeSpan.FromMinutes(10); }).ConfigurePrimaryHttpMessageHandler(messageHandler => { @@ -87,6 +91,48 @@ public WhatsAppBusinessClient(HttpClient httpClient, WhatsAppBusinessCloudApiCon _whatsAppConfig = whatsAppConfig; } + /// + /// To create a QR code for a business, send a POST request to the /{phone-number-ID}/message_qrdls endpoint with the prefilled_message parameter set to your message text and generate_qr_image parameter set to your preferred image format, either SVG or PNG. + /// + /// + /// + /// + /// QRCodeMessageResponse + public QRCodeMessageResponse CreateQRCodeMessage(string messageText, string qrImageFormat, CancellationToken cancellationToken = default) + { + var builder = new StringBuilder(); + + builder.Append(WhatsAppBusinessRequestEndpoint.CreateQRCodeMessage); + builder.Replace("{{Phone-Number-ID}}", _whatsAppConfig.WhatsAppBusinessPhoneNumberId); + builder.Replace("{{message-text}}", messageText); + builder.Replace("{{image-format}}", qrImageFormat); + builder.Replace("{{user-access-token}}", _whatsAppConfig.AccessToken); + + var formattedWhatsAppEndpoint = builder.ToString(); + return WhatsAppBusinessPostAsync(formattedWhatsAppEndpoint, cancellationToken, isHeaderAccessTokenProvided: false).GetAwaiter().GetResult(); + } + + /// + /// To create a QR code for a business, send a POST request to the /{phone-number-ID}/message_qrdls endpoint with the prefilled_message parameter set to your message text and generate_qr_image parameter set to your preferred image format, either SVG or PNG. + /// + /// + /// + /// + /// QRCodeMessageResponse + public async Task CreateQRCodeMessageAsync(string messageText, string qrImageFormat, CancellationToken cancellationToken = default) + { + var builder = new StringBuilder(); + + builder.Append(WhatsAppBusinessRequestEndpoint.CreateQRCodeMessage); + builder.Replace("{{Phone-Number-ID}}", _whatsAppConfig.WhatsAppBusinessPhoneNumberId); + builder.Replace("{{message-text}}", messageText); + builder.Replace("{{image-format}}", qrImageFormat); + builder.Replace("{{user-access-token}}", _whatsAppConfig.AccessToken); + + var formattedWhatsAppEndpoint = builder.ToString(); + return await WhatsAppBusinessPostAsync(formattedWhatsAppEndpoint, cancellationToken, isHeaderAccessTokenProvided: false); + } + /// /// The Resumable Upload series of requests allow you to upload Profile Pictures to Meta so you can receive a handle to update these pictures in the Business Profile API. /// @@ -197,6 +243,44 @@ public async Task DeleteMediaAsync(string mediaId, bool isM return await WhatsAppBusinessDeleteAsync(formattedWhatsAppEndpoint, cancellationToken); } + /// + /// QR codes do not expire. You must delete a QR code in order to retire it. + /// + /// + /// + /// BaseSuccessResponse + public BaseSuccessResponse DeleteQRCodeMessage(string qrCodeId, CancellationToken cancellationToken = default) + { + var builder = new StringBuilder(); + + builder.Append(WhatsAppBusinessRequestEndpoint.DeleteQRCodeMessage); + builder.Replace("{{Phone-Number-ID}}", _whatsAppConfig.WhatsAppBusinessPhoneNumberId); + builder.Replace("{{qr-code-id}}", qrCodeId); + builder.Replace("{{user-access-token}}", _whatsAppConfig.AccessToken); + + var formattedWhatsAppEndpoint = builder.ToString(); + return WhatsAppBusinessDeleteAsync(formattedWhatsAppEndpoint, cancellationToken, isHeaderAccessTokenProvided: false).GetAwaiter().GetResult(); + } + + /// + /// QR codes do not expire. You must delete a QR code in order to retire it. + /// + /// + /// + /// BaseSuccessResponse + public async Task DeleteQRCodeMessageAsync(string qrCodeId, CancellationToken cancellationToken = default) + { + var builder = new StringBuilder(); + + builder.Append(WhatsAppBusinessRequestEndpoint.DeleteQRCodeMessage); + builder.Replace("{{Phone-Number-ID}}", _whatsAppConfig.WhatsAppBusinessPhoneNumberId); + builder.Replace("{{qr-code-id}}", qrCodeId); + builder.Replace("{{user-access-token}}", _whatsAppConfig.AccessToken); + + var formattedWhatsAppEndpoint = builder.ToString(); + return await WhatsAppBusinessDeleteAsync(formattedWhatsAppEndpoint, cancellationToken, isHeaderAccessTokenProvided: false); + } + /// /// If you don’t want an application to receive webhooks for a given WhatsApp Business Account anymore you can delete the subscription. /// @@ -245,6 +329,226 @@ public async Task DeRegisterWhatsAppBusinessPhoneNumberAsyn return await WhatsAppBusinessPostAsync(formattedWhatsAppEndpoint, cancellationToken); } + /// + /// The analytics field provides the number and type of messages sent and delivered by the phone numbers associated with a specific WABA + /// + /// Your WhatsApp Business Account (WABA) ID. + /// The start date for the date range you are retrieving analytics for + /// The end date for the date range you are retrieving analytics for + /// The granularity by which you would like to retrieve the analytics + /// An array of phone numbers for which you would like to retrieve analytics. If not provided, all phone numbers added to your WABA are included. + /// The types of messages (notification messages and/or customer support messages) for which you want to retrieve notifications. Provide an array and include 0 for notification messages, and 2 for customer support messages. If not provided, analytics will be returned for all messages together + /// The countries for which you would like to retrieve analytics. Provide an array with 2 letter country codes for the countries you would like to include. If not provided, analytics will be returned for all countries you have communicated with + /// Cancellation token + /// AnalyticsResponse + public AnalyticsResponse GetAnalyticMetrics(string whatsAppBusinessAccountId, DateTime startDate, DateTime endDate, string granularity, List? phoneNumbers = null, List? productTypes = null, List? countryCodes = null, CancellationToken cancellationToken = default) + { + StringBuilder analyticUrlBuilder = new StringBuilder(); + analyticUrlBuilder.Append(WhatsAppBusinessRequestEndpoint.AnalyticsAccountMetrics); + + string formattedWhatsAppEndpoint; + + if (phoneNumbers is null) + { + phoneNumbers = new(); + } + + if (productTypes is null) + { + productTypes = new(); + } + + if (countryCodes is null) + { + countryCodes = new(); + } + + analyticUrlBuilder.Replace("{{WABA-ID}}", whatsAppBusinessAccountId); + analyticUrlBuilder.Replace("{{start-date}}", new DateTimeOffset(startDate).ToUnixTimeSeconds().ToString()); + analyticUrlBuilder.Replace("{{end-date}}", new DateTimeOffset(endDate).ToUnixTimeSeconds().ToString()); + analyticUrlBuilder.Replace("{{granularity}}", granularity); + analyticUrlBuilder.Append($".phone_numbers({JsonConvert.SerializeObject(phoneNumbers)})"); + analyticUrlBuilder.Append($".product_types({JsonConvert.SerializeObject(productTypes)})"); + analyticUrlBuilder.Append($".country_codes({JsonConvert.SerializeObject(countryCodes)})"); + analyticUrlBuilder.Append($"&access_token={_whatsAppConfig.AccessToken}"); + + formattedWhatsAppEndpoint = analyticUrlBuilder.ToString(); + + return WhatsAppBusinessGetAsync(formattedWhatsAppEndpoint, cancellationToken, isHeaderAccessTokenProvided: false).GetAwaiter().GetResult(); + } + + /// + /// The analytics field provides the number and type of messages sent and delivered by the phone numbers associated with a specific WABA + /// + /// Your WhatsApp Business Account (WABA) ID. + /// The start date for the date range you are retrieving analytics for + /// The end date for the date range you are retrieving analytics for + /// The granularity by which you would like to retrieve the analytics + /// An array of phone numbers for which you would like to retrieve analytics. If not provided, all phone numbers added to your WABA are included. + /// The types of messages (notification messages and/or customer support messages) for which you want to retrieve notifications. Provide an array and include 0 for notification messages, and 2 for customer support messages. If not provided, analytics will be returned for all messages together + /// The countries for which you would like to retrieve analytics. Provide an array with 2 letter country codes for the countries you would like to include. If not provided, analytics will be returned for all countries you have communicated with + /// Cancellation token + /// AnalyticsResponse + public async Task GetAnalyticMetricsAsync(string whatsAppBusinessAccountId, DateTime startDate, DateTime endDate, string granularity, List? phoneNumbers = null, List? productTypes = null, List? countryCodes = null, CancellationToken cancellationToken = default) + { + StringBuilder analyticUrlBuilder = new StringBuilder(); + analyticUrlBuilder.Append(WhatsAppBusinessRequestEndpoint.AnalyticsAccountMetrics); + + string formattedWhatsAppEndpoint; + + if (phoneNumbers is null) + { + phoneNumbers = new(); + } + + if (productTypes is null) + { + productTypes = new(); + } + + if (countryCodes is null) + { + countryCodes = new(); + } + + analyticUrlBuilder.Replace("{{WABA-ID}}", whatsAppBusinessAccountId); + analyticUrlBuilder.Replace("{{start-date}}", new DateTimeOffset(startDate).ToUnixTimeSeconds().ToString()); + analyticUrlBuilder.Replace("{{end-date}}", new DateTimeOffset(endDate).ToUnixTimeSeconds().ToString()); + analyticUrlBuilder.Replace("{{granularity}}", granularity); + analyticUrlBuilder.Append($".phone_numbers({JsonConvert.SerializeObject(phoneNumbers)})"); + analyticUrlBuilder.Append($".product_types({JsonConvert.SerializeObject(productTypes)})"); + analyticUrlBuilder.Append($".country_codes({JsonConvert.SerializeObject(countryCodes)})"); + analyticUrlBuilder.Append($"&access_token={_whatsAppConfig.AccessToken}"); + + formattedWhatsAppEndpoint = analyticUrlBuilder.ToString(); + + return await WhatsAppBusinessGetAsync(formattedWhatsAppEndpoint, cancellationToken, isHeaderAccessTokenProvided: false); + } + + /// + /// The conversation_analytics field provides cost and conversation information for a specific WABA. + /// + /// Your WhatsApp Business Account (WABA) ID. + /// The start date for the date range you are retrieving analytics for + /// The end date for the date range you are retrieving analytics for + /// The granularity by which you would like to retrieve the analytics + /// An array of phone numbers for which you would like to retrieve analytics. If not provided, all phone numbers added to your WABA are included. + /// List of metrics you would like to receive. If you send an empty list, we return results for all metric types. + /// List of conversation types. If you send an empty list, we return results for all conversation types. + /// List of conversation directions. If you send an empty list, we return results for all conversation directions + /// List of breakdowns you would like to apply to your metrics. If you send an empty list, we return results without any breakdowns. + /// Cancellation token + /// ConversationAnalyticsResponse + public ConversationAnalyticsResponse GetConversationAnalyticMetrics(string whatsAppBusinessAccountId, DateTime startDate, DateTime endDate, string granularity, List? phoneNumbers = null, List? metricTypes = null, List? conversationTypes = null, List? conversationDirections = null, List? dimensions = null, CancellationToken cancellationToken = default) + { + StringBuilder conversationAnalyticUrlBuilder = new StringBuilder(); + conversationAnalyticUrlBuilder.Append(WhatsAppBusinessRequestEndpoint.ConversationAnalyticsAccountMetrics); + + string formattedWhatsAppEndpoint; + + if (phoneNumbers is null) + { + phoneNumbers = new(); + } + + if (metricTypes is null) + { + metricTypes = new(); + } + + if (conversationTypes is null) + { + conversationTypes = new(); + } + + if (conversationDirections is null) + { + conversationDirections = new(); + } + + if (dimensions is null) + { + dimensions = new(); + } + + conversationAnalyticUrlBuilder.Replace("{{WABA-ID}}", whatsAppBusinessAccountId); + conversationAnalyticUrlBuilder.Replace("{{start-date}}", new DateTimeOffset(startDate).ToUnixTimeSeconds().ToString()); + conversationAnalyticUrlBuilder.Replace("{{end-date}}", new DateTimeOffset(endDate).ToUnixTimeSeconds().ToString()); + conversationAnalyticUrlBuilder.Replace("{{granularity}}", granularity); + conversationAnalyticUrlBuilder.Append($".phone_numbers({JsonConvert.SerializeObject(phoneNumbers)})"); + conversationAnalyticUrlBuilder.Append($".metric_types({JsonConvert.SerializeObject(metricTypes)})"); + conversationAnalyticUrlBuilder.Append($".conversation_types({JsonConvert.SerializeObject(conversationTypes)})"); + conversationAnalyticUrlBuilder.Append($".conversation_directions({JsonConvert.SerializeObject(conversationDirections)})"); + conversationAnalyticUrlBuilder.Append($".dimensions({JsonConvert.SerializeObject(dimensions)})"); + conversationAnalyticUrlBuilder.Append($"&access_token={_whatsAppConfig.AccessToken}"); + + formattedWhatsAppEndpoint = conversationAnalyticUrlBuilder.ToString(); + + return WhatsAppBusinessGetAsync(formattedWhatsAppEndpoint, cancellationToken, isHeaderAccessTokenProvided: false).GetAwaiter().GetResult(); + } + + /// + /// The conversation_analytics field provides cost and conversation information for a specific WABA. + /// + /// Your WhatsApp Business Account (WABA) ID. + /// The start date for the date range you are retrieving analytics for + /// The end date for the date range you are retrieving analytics for + /// The granularity by which you would like to retrieve the analytics + /// An array of phone numbers for which you would like to retrieve analytics. If not provided, all phone numbers added to your WABA are included. + /// List of metrics you would like to receive. If you send an empty list, we return results for all metric types. + /// List of conversation types. If you send an empty list, we return results for all conversation types. + /// List of conversation directions. If you send an empty list, we return results for all conversation directions + /// List of breakdowns you would like to apply to your metrics. If you send an empty list, we return results without any breakdowns. + /// Cancellation token + /// ConversationAnalyticsResponse + public async Task GetConversationAnalyticMetricsAsync(string whatsAppBusinessAccountId, DateTime startDate, DateTime endDate, string granularity, List? phoneNumbers = null, List? metricTypes = null, List? conversationTypes = null, List? conversationDirections = null, List? dimensions = null, CancellationToken cancellationToken = default) + { + StringBuilder conversationAnalyticUrlBuilder = new StringBuilder(); + conversationAnalyticUrlBuilder.Append(WhatsAppBusinessRequestEndpoint.ConversationAnalyticsAccountMetrics); + + string formattedWhatsAppEndpoint; + + if (phoneNumbers is null) + { + phoneNumbers = new(); + } + + if (metricTypes is null) + { + metricTypes = new(); + } + + if (conversationTypes is null) + { + conversationTypes = new(); + } + + if (conversationDirections is null) + { + conversationDirections = new(); + } + + if (dimensions is null) + { + dimensions = new(); + } + + conversationAnalyticUrlBuilder.Replace("{{WABA-ID}}", whatsAppBusinessAccountId); + conversationAnalyticUrlBuilder.Replace("{{start-date}}", new DateTimeOffset(startDate).ToUnixTimeSeconds().ToString()); + conversationAnalyticUrlBuilder.Replace("{{end-date}}", new DateTimeOffset(endDate).ToUnixTimeSeconds().ToString()); + conversationAnalyticUrlBuilder.Replace("{{granularity}}", granularity); + conversationAnalyticUrlBuilder.Append($".phone_numbers({JsonConvert.SerializeObject(phoneNumbers)})"); + conversationAnalyticUrlBuilder.Append($".metric_types({JsonConvert.SerializeObject(metricTypes)})"); + conversationAnalyticUrlBuilder.Append($".conversation_types({JsonConvert.SerializeObject(conversationTypes)})"); + conversationAnalyticUrlBuilder.Append($".conversation_directions({JsonConvert.SerializeObject(conversationDirections)})"); + conversationAnalyticUrlBuilder.Append($".dimensions({JsonConvert.SerializeObject(dimensions)})"); + conversationAnalyticUrlBuilder.Append($"&access_token={_whatsAppConfig.AccessToken}"); + + formattedWhatsAppEndpoint = conversationAnalyticUrlBuilder.ToString(); + + return await WhatsAppBusinessGetAsync(formattedWhatsAppEndpoint, cancellationToken, isHeaderAccessTokenProvided: false); + } + /// /// To complete the following API calls, you need to get a business profile ID. To do that, make a GET call to the /{{Phone-Number-ID}} endpoint and add whatsapp_business_profile as a URL field. Within the whatsapp_business_profile request, you can specify what you want to know from your business. /// @@ -311,6 +615,74 @@ public async Task GetMediaUrlAsync(string mediaId, bool isMedi return await WhatsAppBusinessGetAsync(formattedWhatsAppEndpoint, cancellationToken); } + /// + /// To get a list of all the QR codes messages for a business + /// + /// + /// QRCodeMessageFilterResponse + public QRCodeMessageFilterResponse GetQRCodeMessageList(CancellationToken cancellationToken = default) + { + var builder = new StringBuilder(); + + builder.Append(WhatsAppBusinessRequestEndpoint.GetQRCodeMessage); + builder.Replace("{{Phone-Number-ID}}", _whatsAppConfig.WhatsAppBusinessPhoneNumberId); + + var formattedWhatsAppEndpoint = builder.ToString(); + return WhatsAppBusinessGetAsync(formattedWhatsAppEndpoint, cancellationToken).GetAwaiter().GetResult(); + } + + /// + /// To get a list of all the QR codes messages for a business + /// + /// + /// QRCodeMessageFilterResponse + public async Task GetQRCodeMessageListAsync(CancellationToken cancellationToken = default) + { + var builder = new StringBuilder(); + + builder.Append(WhatsAppBusinessRequestEndpoint.GetQRCodeMessage); + builder.Replace("{{Phone-Number-ID}}", _whatsAppConfig.WhatsAppBusinessPhoneNumberId); + + var formattedWhatsAppEndpoint = builder.ToString(); + return await WhatsAppBusinessGetAsync(formattedWhatsAppEndpoint, cancellationToken); + } + + /// + /// To get information about a specific QR code message + /// + /// + /// + /// QRCodeMessageFilterResponse + public QRCodeMessageFilterResponse GetQRCodeMessageById(string qrCodeId, CancellationToken cancellationToken = default) + { + var builder = new StringBuilder(); + + builder.Append(WhatsAppBusinessRequestEndpoint.GetQRCodeMessageById); + builder.Replace("{{Phone-Number-ID}}", _whatsAppConfig.WhatsAppBusinessPhoneNumberId); + builder.Replace("{{qr-code-id}}", qrCodeId); + + var formattedWhatsAppEndpoint = builder.ToString(); + return WhatsAppBusinessGetAsync(formattedWhatsAppEndpoint, cancellationToken).GetAwaiter().GetResult(); + } + + /// + /// To get information about a specific QR code message + /// + /// + /// + /// QRCodeMessageFilterResponse + public async Task GetQRCodeMessageByIdAsync(string qrCodeId, CancellationToken cancellationToken = default) + { + var builder = new StringBuilder(); + + builder.Append(WhatsAppBusinessRequestEndpoint.GetQRCodeMessageById); + builder.Replace("{{Phone-Number-ID}}", _whatsAppConfig.WhatsAppBusinessPhoneNumberId); + builder.Replace("{{qr-code-id}}", qrCodeId); + + var formattedWhatsAppEndpoint = builder.ToString(); + return await WhatsAppBusinessGetAsync(formattedWhatsAppEndpoint, cancellationToken); + } + /// /// Get Shared WhatsApp Business Account /// @@ -1169,6 +1541,48 @@ public async Task UpdateBusinessProfileAsync(UpdateBusiness return await WhatsAppBusinessPostAsync(updateBusinessProfile, formattedWhatsAppEndpoint, cancellationToken); } + /// + /// To update a QR code for a business, send a POST request to the /{phone-number-ID}/message_qrdls/{qr-code-id} endpoint and include the parameter you wish to update. + /// + /// + /// + /// + /// QRCodeMessageResponse + public QRCodeMessageResponse UpdateQRCodeMessage(string qrCodeId, string messageText, CancellationToken cancellationToken = default) + { + var builder = new StringBuilder(); + + builder.Append(WhatsAppBusinessRequestEndpoint.UpdateQRCodeMessage); + builder.Replace("{{Phone-Number-ID}}", _whatsAppConfig.WhatsAppBusinessPhoneNumberId); + builder.Replace("{{qr-code-id}}", qrCodeId); + builder.Replace("{{new-message-text}}", messageText); + builder.Replace("{{user-access-token}}", _whatsAppConfig.AccessToken); + + var formattedWhatsAppEndpoint = builder.ToString(); + return WhatsAppBusinessPostAsync(formattedWhatsAppEndpoint, cancellationToken, isHeaderAccessTokenProvided: false).GetAwaiter().GetResult(); + } + + /// + /// To update a QR code for a business, send a POST request to the /{phone-number-ID}/message_qrdls/{qr-code-id} endpoint and include the parameter you wish to update. + /// + /// + /// + /// + /// QRCodeMessageResponse + public async Task UpdateQRCodeMessageAsync(string qrCodeId, string messageText, CancellationToken cancellationToken = default) + { + var builder = new StringBuilder(); + + builder.Append(WhatsAppBusinessRequestEndpoint.UpdateQRCodeMessage); + builder.Replace("{{Phone-Number-ID}}", _whatsAppConfig.WhatsAppBusinessPhoneNumberId); + builder.Replace("{{qr-code-id}}", qrCodeId); + builder.Replace("{{new-message-text}}", messageText); + builder.Replace("{{user-access-token}}", _whatsAppConfig.AccessToken); + + var formattedWhatsAppEndpoint = builder.ToString(); + return await WhatsAppBusinessPostAsync(formattedWhatsAppEndpoint, cancellationToken, isHeaderAccessTokenProvided: false); + } + /// /// To upload a profile picture to your business profile, make a POST call to the named endpoint v14.0/{{Upload-ID}}, where Upload-ID is the value you received from Resumable Upload - Create an Upload Session. /// @@ -1315,9 +1729,13 @@ await response.Content.ReadAsStreamAsync().ContinueWith((Task stream) => /// Cancellation token /// Response Object /// - private async Task WhatsAppBusinessPostAsync(string whatsAppBusinessEndpoint, CancellationToken cancellationToken = default) where T : new() + private async Task WhatsAppBusinessPostAsync(string whatsAppBusinessEndpoint, CancellationToken cancellationToken = default, bool isHeaderAccessTokenProvided = true) where T : new() { - _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _whatsAppConfig.AccessToken); + if (isHeaderAccessTokenProvided) + { + _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _whatsAppConfig.AccessToken); + } + T result = new(); cancellationToken.ThrowIfCancellationRequested(); @@ -1471,6 +1889,7 @@ await response.Content.ReadAsStreamAsync().ContinueWith((Task stream) => return result; } + /// /// To perform WhatsAppBusiness Cloud API endpoint GET request /// @@ -1480,10 +1899,13 @@ await response.Content.ReadAsStreamAsync().ContinueWith((Task stream) => /// Resumable upload header parameter /// Response object /// - private async Task WhatsAppBusinessGetAsync(string whatsAppBusinessEndpoint, CancellationToken cancellationToken = default, bool isCacheControlActive = false) where T : new() + private async Task WhatsAppBusinessGetAsync(string whatsAppBusinessEndpoint, CancellationToken cancellationToken = default, bool isCacheControlActive = false, bool isHeaderAccessTokenProvided = true) where T : new() { - _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _whatsAppConfig.AccessToken); - + if (isHeaderAccessTokenProvided) + { + _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _whatsAppConfig.AccessToken); + } + if (isCacheControlActive) { _httpClient.DefaultRequestHeaders.CacheControl = new CacheControlHeaderValue @@ -1610,9 +2032,12 @@ await response.Content.ReadAsStreamAsync().ContinueWith((Task stream) => /// Cancellation token /// Response object /// - private async Task WhatsAppBusinessDeleteAsync(string whatsAppBusinessEndpoint, CancellationToken cancellationToken = default) where T : new() + private async Task WhatsAppBusinessDeleteAsync(string whatsAppBusinessEndpoint, CancellationToken cancellationToken = default, bool isHeaderAccessTokenProvided = true) where T : new() { - _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _whatsAppConfig.AccessToken); + if (isHeaderAccessTokenProvided) + { + _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _whatsAppConfig.AccessToken); + } T result = new(); cancellationToken.ThrowIfCancellationRequested(); var response = await _httpClient.DeleteAsync(whatsAppBusinessEndpoint, cancellationToken).ConfigureAwait(false); diff --git a/WhatsappBusiness.CloudApi/WhatsAppBusinessRequestEndpoint.cs b/WhatsappBusiness.CloudApi/WhatsAppBusinessRequestEndpoint.cs index 7e16290..e5217e1 100644 --- a/WhatsappBusiness.CloudApi/WhatsAppBusinessRequestEndpoint.cs +++ b/WhatsappBusiness.CloudApi/WhatsAppBusinessRequestEndpoint.cs @@ -5,14 +5,9 @@ namespace WhatsappBusiness.CloudApi public static class WhatsAppBusinessRequestEndpoint { /// - /// WhatsApp Business Cloud API V13 BaseAddress + /// WhatsApp Business Cloud API V15 BaseAddress /// - public static Uri BaseAddress { get; private set; } = new Uri("https://graph.facebook.com/v13.0/"); - - /// - /// WhatsApp Business Cloud API V14 BaseAddress - /// - public static Uri V14BaseAddress { get; private set; } = new Uri("https://graph.facebook.com/v14.0/"); + public static Uri BaseAddress { get; private set; } = new Uri("https://graph.facebook.com/v15.0/"); /// /// To register your phone to WhatsApp Business @@ -133,5 +128,25 @@ public static class WhatsAppBusinessRequestEndpoint /// If you don’t want an application to receive webhooks for a given WhatsApp Business Account anymore you can delete the subscription. /// public static string DeleteSubscribedApps { get; private set; } = "{{WABA-ID}}/subscribed_apps"; + + /// + /// You can use the analytics and conversation_analytics fields to get metrics about messages and conversations associated with your WhatsApp Business Account (WABA). Specifically, you can get the number of messages sent and delivered as well as conversation and cost information for a given period. + /// + public static string AnalyticsAccountMetrics { get; private set; } = "{{WABA-ID}}?fields=analytics.start({{start-date}}).end({{end-date}}).granularity({{granularity}})"; + + /// + /// You can use the analytics and conversation_analytics fields to get metrics about messages and conversations associated with your WhatsApp Business Account (WABA). Specifically, you can get the number of messages sent and delivered as well as conversation and cost information for a given period. + /// + public static string ConversationAnalyticsAccountMetrics { get; private set; } = "{{WABA-ID}}?fields=conversation_analytics.start({{start-date}}).end({{end-date}}).granularity({{granularity}})"; + + public static string CreateQRCodeMessage { get; private set; } = "{{Phone-Number-ID}}/message_qrdls?prefilled_message={{message-text}}&generate_qr_image={{image-format}}&access_token={{user-access-token}}"; + + public static string GetQRCodeMessage { get; private set; } = "{{Phone-Number-ID}}/message_qrdls"; + + public static string GetQRCodeMessageById { get; private set; } = "{{Phone-Number-ID}}/message_qrdls/{{qr-code-id}}"; + + public static string UpdateQRCodeMessage { get; private set; } = "{{Phone-Number-ID}}/message_qrdls/{{qr-code-id}}?prefilled_message={{new-message-text}}&access_token={{user-access-token}}"; + + public static string DeleteQRCodeMessage { get; private set; } = "{{Phone-Number-ID}}/message_qrdls/{{qr-code-id}}&access_token={{user-access-token}}"; } } diff --git a/WhatsappBusiness.CloudApi/WhatsappBusiness.CloudApi.csproj b/WhatsappBusiness.CloudApi/WhatsappBusiness.CloudApi.csproj index 6ec95ca..115f980 100644 --- a/WhatsappBusiness.CloudApi/WhatsappBusiness.CloudApi.csproj +++ b/WhatsappBusiness.CloudApi/WhatsappBusiness.CloudApi.csproj @@ -12,7 +12,7 @@ https://github.com/gabrieldwight/Whatsapp-Business-Cloud-Api-Net enable latest - 1.0.12 + 1.0.13