1use crate::client::Client;
4use crate::error::Result;
5use crate::types::MessageResponse;
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8
9pub struct FlowsApi {
11 client: Client,
12}
13
14impl FlowsApi {
15 pub(crate) fn new(client: Client) -> Self {
16 Self { client }
17 }
18
19 pub async fn send_flow(
34 &self,
35 to: &str,
36 flow_token: &str,
37 flow_id: &str,
38 flow_cta: &str,
39 flow_action: FlowAction,
40 screen: &str,
41 data: Option<Value>,
42 header: Option<&str>,
43 body_text: &str,
44 footer: Option<&str>,
45 ) -> Result<MessageResponse> {
46 let body = SendFlowRequest {
47 messaging_product: "whatsapp".to_string(),
48 recipient_type: "individual".to_string(),
49 to: to.to_string(),
50 message_type: "interactive".to_string(),
51 interactive: FlowInteractive {
52 interactive_type: "flow".to_string(),
53 header: header.map(|h| FlowHeader {
54 header_type: "text".to_string(),
55 text: h.to_string(),
56 }),
57 body: FlowBody {
58 text: body_text.to_string(),
59 },
60 footer: footer.map(|f| FlowFooter {
61 text: f.to_string(),
62 }),
63 action: FlowActionPayload {
64 name: "flow".to_string(),
65 parameters: FlowParameters {
66 flow_message_version: "3".to_string(),
67 flow_token: flow_token.to_string(),
68 flow_id: flow_id.to_string(),
69 flow_cta: flow_cta.to_string(),
70 flow_action: flow_action.as_str().to_string(),
71 flow_action_payload: FlowActionPayloadData {
72 screen: screen.to_string(),
73 data,
74 },
75 },
76 },
77 },
78 };
79
80 let url = format!("{}/messages", self.client.base_url());
81 self.client.post(&url, &body).await
82 }
83
84 pub async fn list_flows(&self, waba_id: &str) -> Result<FlowsListResponse> {
90 let url = self.client.endpoint_url(&format!("{}/flows", waba_id));
91 self.client.get(&url).await
92 }
93
94 pub async fn get_flow(&self, flow_id: &str) -> Result<Flow> {
100 let url = self.client.endpoint_url(flow_id);
101 self.client.get(&url).await
102 }
103
104 pub async fn create_flow(
112 &self,
113 waba_id: &str,
114 name: &str,
115 categories: Vec<FlowCategory>,
116 ) -> Result<CreateFlowResponse> {
117 let body = CreateFlowRequest {
118 name: name.to_string(),
119 categories: categories.iter().map(|c| c.as_str().to_string()).collect(),
120 };
121
122 let url = self.client.endpoint_url(&format!("{}/flows", waba_id));
123 self.client.post(&url, &body).await
124 }
125
126 pub async fn update_flow_json(
133 &self,
134 flow_id: &str,
135 flow_json: &str,
136 ) -> Result<UpdateFlowResponse> {
137 let form = reqwest::multipart::Form::new()
138 .text("name", "flow.json")
139 .text("file", flow_json.to_string());
140
141 let url = self.client.endpoint_url(&format!("{}/assets", flow_id));
142 self.client.post_form(&url, form).await
143 }
144
145 pub async fn publish_flow(&self, flow_id: &str) -> Result<crate::types::SuccessResponse> {
151 let body = PublishFlowRequest {
152 status: "PUBLISHED".to_string(),
153 };
154
155 let url = self.client.endpoint_url(flow_id);
156 self.client.post(&url, &body).await
157 }
158
159 pub async fn delete_flow(&self, flow_id: &str) -> Result<crate::types::SuccessResponse> {
165 let url = self.client.endpoint_url(flow_id);
166 self.client.delete(&url).await
167 }
168
169 pub async fn deprecate_flow(&self, flow_id: &str) -> Result<crate::types::SuccessResponse> {
175 let body = DeprecateFlowRequest {
176 status: "DEPRECATED".to_string(),
177 };
178
179 let url = self.client.endpoint_url(flow_id);
180 self.client.post(&url, &body).await
181 }
182
183 pub async fn get_preview(&self, flow_id: &str) -> Result<FlowPreviewResponse> {
189 let url = self.client.endpoint_url(&format!("{}/preview", flow_id));
190 self.client.get(&url).await
191 }
192}
193
194#[derive(Debug, Clone, Copy, PartialEq, Eq)]
196pub enum FlowAction {
197 Navigate,
199 DataExchange,
201}
202
203impl FlowAction {
204 fn as_str(&self) -> &'static str {
205 match self {
206 FlowAction::Navigate => "navigate",
207 FlowAction::DataExchange => "data_exchange",
208 }
209 }
210}
211
212#[derive(Debug, Clone, Copy, PartialEq, Eq)]
214pub enum FlowCategory {
215 SignUp,
216 SignIn,
217 Appointment,
218 LeadGeneration,
219 ContactUs,
220 CustomerSupport,
221 Survey,
222 Other,
223}
224
225impl FlowCategory {
226 fn as_str(&self) -> &'static str {
227 match self {
228 FlowCategory::SignUp => "SIGN_UP",
229 FlowCategory::SignIn => "SIGN_IN",
230 FlowCategory::Appointment => "APPOINTMENT_BOOKING",
231 FlowCategory::LeadGeneration => "LEAD_GENERATION",
232 FlowCategory::ContactUs => "CONTACT_US",
233 FlowCategory::CustomerSupport => "CUSTOMER_SUPPORT",
234 FlowCategory::Survey => "SURVEY",
235 FlowCategory::Other => "OTHER",
236 }
237 }
238}
239
240#[derive(Debug, Serialize)]
243struct SendFlowRequest {
244 messaging_product: String,
245 recipient_type: String,
246 to: String,
247 #[serde(rename = "type")]
248 message_type: String,
249 interactive: FlowInteractive,
250}
251
252#[derive(Debug, Serialize)]
253struct FlowInteractive {
254 #[serde(rename = "type")]
255 interactive_type: String,
256 #[serde(skip_serializing_if = "Option::is_none")]
257 header: Option<FlowHeader>,
258 body: FlowBody,
259 #[serde(skip_serializing_if = "Option::is_none")]
260 footer: Option<FlowFooter>,
261 action: FlowActionPayload,
262}
263
264#[derive(Debug, Serialize)]
265struct FlowHeader {
266 #[serde(rename = "type")]
267 header_type: String,
268 text: String,
269}
270
271#[derive(Debug, Serialize)]
272struct FlowBody {
273 text: String,
274}
275
276#[derive(Debug, Serialize)]
277struct FlowFooter {
278 text: String,
279}
280
281#[derive(Debug, Serialize)]
282struct FlowActionPayload {
283 name: String,
284 parameters: FlowParameters,
285}
286
287#[derive(Debug, Serialize)]
288struct FlowParameters {
289 flow_message_version: String,
290 flow_token: String,
291 flow_id: String,
292 flow_cta: String,
293 flow_action: String,
294 flow_action_payload: FlowActionPayloadData,
295}
296
297#[derive(Debug, Serialize)]
298struct FlowActionPayloadData {
299 screen: String,
300 #[serde(skip_serializing_if = "Option::is_none")]
301 data: Option<Value>,
302}
303
304#[derive(Debug, Serialize)]
305struct CreateFlowRequest {
306 name: String,
307 categories: Vec<String>,
308}
309
310#[derive(Debug, Serialize)]
311struct PublishFlowRequest {
312 status: String,
313}
314
315#[derive(Debug, Serialize)]
316struct DeprecateFlowRequest {
317 status: String,
318}
319
320#[derive(Debug, Clone, Serialize, Deserialize)]
324pub struct FlowsListResponse {
325 pub data: Vec<Flow>,
327 #[serde(skip_serializing_if = "Option::is_none")]
329 pub paging: Option<Paging>,
330}
331
332#[derive(Debug, Clone, Serialize, Deserialize)]
334pub struct Flow {
335 pub id: String,
337 pub name: String,
339 pub status: String,
341 #[serde(default)]
343 pub categories: Vec<String>,
344 #[serde(skip_serializing_if = "Option::is_none")]
346 pub validation_errors: Option<Vec<FlowValidationError>>,
347 #[serde(skip_serializing_if = "Option::is_none")]
349 pub json_version: Option<String>,
350 #[serde(skip_serializing_if = "Option::is_none")]
352 pub data_api_version: Option<String>,
353 #[serde(skip_serializing_if = "Option::is_none")]
355 pub endpoint_uri: Option<String>,
356 #[serde(skip_serializing_if = "Option::is_none")]
358 pub preview: Option<FlowPreview>,
359}
360
361#[derive(Debug, Clone, Serialize, Deserialize)]
363pub struct FlowValidationError {
364 pub error: String,
366 pub error_type: String,
368 #[serde(skip_serializing_if = "Option::is_none")]
370 pub line_start: Option<i32>,
371 #[serde(skip_serializing_if = "Option::is_none")]
373 pub line_end: Option<i32>,
374 #[serde(skip_serializing_if = "Option::is_none")]
376 pub column_start: Option<i32>,
377 #[serde(skip_serializing_if = "Option::is_none")]
379 pub column_end: Option<i32>,
380}
381
382#[derive(Debug, Clone, Serialize, Deserialize)]
384pub struct FlowPreview {
385 pub preview_url: String,
387 #[serde(skip_serializing_if = "Option::is_none")]
389 pub expires_at: Option<String>,
390}
391
392#[derive(Debug, Clone, Serialize, Deserialize)]
394pub struct CreateFlowResponse {
395 pub id: String,
397}
398
399#[derive(Debug, Clone, Serialize, Deserialize)]
401pub struct UpdateFlowResponse {
402 pub success: bool,
404 #[serde(skip_serializing_if = "Option::is_none")]
406 pub validation_errors: Option<Vec<FlowValidationError>>,
407}
408
409#[derive(Debug, Clone, Serialize, Deserialize)]
411pub struct FlowPreviewResponse {
412 pub preview_url: String,
414 #[serde(skip_serializing_if = "Option::is_none")]
416 pub expires_at: Option<String>,
417}
418
419#[derive(Debug, Clone, Serialize, Deserialize)]
421pub struct Paging {
422 #[serde(skip_serializing_if = "Option::is_none")]
424 pub cursors: Option<PagingCursors>,
425}
426
427#[derive(Debug, Clone, Serialize, Deserialize)]
429pub struct PagingCursors {
430 #[serde(skip_serializing_if = "Option::is_none")]
432 pub before: Option<String>,
433 #[serde(skip_serializing_if = "Option::is_none")]
435 pub after: Option<String>,
436}