use crate::client::Client;
use crate::error::Result;
use crate::types::MessageResponse;
use serde::{Deserialize, Serialize};
pub struct ProductsApi {
client: Client,
}
impl ProductsApi {
pub(crate) fn new(client: Client) -> Self {
Self { client }
}
pub async fn send_product(
&self,
to: &str,
catalog_id: &str,
product_retailer_id: &str,
body_text: &str,
footer: Option<&str>,
) -> Result<MessageResponse> {
let body = SendProductRequest {
messaging_product: "whatsapp".to_string(),
recipient_type: "individual".to_string(),
to: to.to_string(),
message_type: "interactive".to_string(),
interactive: ProductInteractive {
interactive_type: "product".to_string(),
body: ProductBody {
text: body_text.to_string(),
},
footer: footer.map(|f| ProductFooter {
text: f.to_string(),
}),
action: ProductAction {
catalog_id: catalog_id.to_string(),
product_retailer_id: Some(product_retailer_id.to_string()),
sections: None,
},
},
};
let url = format!("{}/messages", self.client.base_url());
self.client.post(&url, &body).await
}
pub async fn send_product_list(
&self,
to: &str,
catalog_id: &str,
header_text: &str,
body_text: &str,
footer: Option<&str>,
sections: Vec<ProductSection>,
) -> Result<MessageResponse> {
let body = SendProductListRequest {
messaging_product: "whatsapp".to_string(),
recipient_type: "individual".to_string(),
to: to.to_string(),
message_type: "interactive".to_string(),
interactive: ProductListInteractive {
interactive_type: "product_list".to_string(),
header: ProductHeader {
header_type: "text".to_string(),
text: header_text.to_string(),
},
body: ProductBody {
text: body_text.to_string(),
},
footer: footer.map(|f| ProductFooter {
text: f.to_string(),
}),
action: ProductListAction {
catalog_id: catalog_id.to_string(),
sections,
},
},
};
let url = format!("{}/messages", self.client.base_url());
self.client.post(&url, &body).await
}
pub async fn send_catalog(
&self,
to: &str,
body_text: &str,
footer: Option<&str>,
thumbnail_product_retailer_id: Option<&str>,
) -> Result<MessageResponse> {
let body = SendCatalogRequest {
messaging_product: "whatsapp".to_string(),
recipient_type: "individual".to_string(),
to: to.to_string(),
message_type: "interactive".to_string(),
interactive: CatalogInteractive {
interactive_type: "catalog_message".to_string(),
body: ProductBody {
text: body_text.to_string(),
},
footer: footer.map(|f| ProductFooter {
text: f.to_string(),
}),
action: CatalogAction {
name: "catalog_message".to_string(),
parameters: thumbnail_product_retailer_id.map(|id| CatalogParameters {
thumbnail_product_retailer_id: id.to_string(),
}),
},
},
};
let url = format!("{}/messages", self.client.base_url());
self.client.post(&url, &body).await
}
pub async fn get_commerce_settings(&self) -> Result<CommerceSettings> {
let url = format!(
"{}/whatsapp_commerce_settings",
self.client.base_url()
);
self.client.get(&url).await
}
pub async fn update_commerce_settings(
&self,
is_catalog_visible: bool,
is_cart_enabled: bool,
) -> Result<crate::types::SuccessResponse> {
let body = UpdateCommerceSettingsRequest {
is_catalog_visible,
is_cart_enabled,
};
let url = format!(
"{}/whatsapp_commerce_settings",
self.client.base_url()
);
self.client.post(&url, &body).await
}
}
#[derive(Debug, Serialize)]
struct SendProductRequest {
messaging_product: String,
recipient_type: String,
to: String,
#[serde(rename = "type")]
message_type: String,
interactive: ProductInteractive,
}
#[derive(Debug, Serialize)]
struct ProductInteractive {
#[serde(rename = "type")]
interactive_type: String,
body: ProductBody,
#[serde(skip_serializing_if = "Option::is_none")]
footer: Option<ProductFooter>,
action: ProductAction,
}
#[derive(Debug, Serialize)]
struct ProductAction {
catalog_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
product_retailer_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
sections: Option<Vec<ProductSection>>,
}
#[derive(Debug, Serialize)]
struct SendProductListRequest {
messaging_product: String,
recipient_type: String,
to: String,
#[serde(rename = "type")]
message_type: String,
interactive: ProductListInteractive,
}
#[derive(Debug, Serialize)]
struct ProductListInteractive {
#[serde(rename = "type")]
interactive_type: String,
header: ProductHeader,
body: ProductBody,
#[serde(skip_serializing_if = "Option::is_none")]
footer: Option<ProductFooter>,
action: ProductListAction,
}
#[derive(Debug, Serialize)]
struct ProductHeader {
#[serde(rename = "type")]
header_type: String,
text: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ProductBody {
pub text: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ProductFooter {
pub text: String,
}
#[derive(Debug, Serialize)]
struct ProductListAction {
catalog_id: String,
sections: Vec<ProductSection>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProductSection {
pub title: String,
pub product_items: Vec<ProductItem>,
}
impl ProductSection {
pub fn new(title: impl Into<String>, products: Vec<ProductItem>) -> Self {
Self {
title: title.into(),
product_items: products,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProductItem {
pub product_retailer_id: String,
}
impl ProductItem {
pub fn new(product_retailer_id: impl Into<String>) -> Self {
Self {
product_retailer_id: product_retailer_id.into(),
}
}
}
#[derive(Debug, Serialize)]
struct SendCatalogRequest {
messaging_product: String,
recipient_type: String,
to: String,
#[serde(rename = "type")]
message_type: String,
interactive: CatalogInteractive,
}
#[derive(Debug, Serialize)]
struct CatalogInteractive {
#[serde(rename = "type")]
interactive_type: String,
body: ProductBody,
#[serde(skip_serializing_if = "Option::is_none")]
footer: Option<ProductFooter>,
action: CatalogAction,
}
#[derive(Debug, Serialize)]
struct CatalogAction {
name: String,
#[serde(skip_serializing_if = "Option::is_none")]
parameters: Option<CatalogParameters>,
}
#[derive(Debug, Serialize)]
struct CatalogParameters {
thumbnail_product_retailer_id: String,
}
#[derive(Debug, Serialize)]
struct UpdateCommerceSettingsRequest {
is_catalog_visible: bool,
is_cart_enabled: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommerceSettings {
#[serde(default)]
pub is_catalog_visible: bool,
#[serde(default)]
pub is_cart_enabled: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
}