1use std::fmt;
6
7use derive_builder::Builder;
8use serde::{Deserialize, Serialize};
9
10use crate::utils::string_or_number_to_u32;
11use crate::{Currency, Domain, Subscription};
12
13#[derive(Clone, Default, Debug, Serialize, Deserialize, Builder)]
16pub struct PlanRequest {
17 pub name: String,
19 pub amount: String,
21 pub interval: Interval,
23 #[builder(setter(strip_option))]
25 #[serde(skip_serializing_if = "Option::is_none")]
26 pub description: Option<String>,
27 #[builder(setter(strip_option), default)]
29 #[serde(skip_serializing_if = "Option::is_none")]
30 pub send_invoices: Option<bool>,
31 #[builder(setter(strip_option), default)]
34 #[serde(skip_serializing_if = "Option::is_none")]
35 pub send_sms: Option<bool>,
36 #[builder(setter(strip_option), default)]
39 #[serde(skip_serializing_if = "Option::is_none")]
40 pub currency: Option<Currency>,
41 #[builder(setter(strip_option), default)]
44 #[serde(skip_serializing_if = "Option::is_none")]
45 pub invoice_limit: Option<u8>,
46}
47
48#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq)]
50#[serde(rename_all = "lowercase")]
51pub enum Interval {
52 Daily,
53 Weekly,
54 #[default]
55 Monthly,
56 Quarterly,
57 Biannually,
59 Annually,
60}
61
62impl fmt::Display for Interval {
63 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
64 let interval = match self {
65 Interval::Daily => "daily",
66 Interval::Weekly => "weekly",
67 Interval::Monthly => "monthly",
68 Interval::Quarterly => "quarterly",
69 Interval::Biannually => "biannually",
70 Interval::Annually => "annually",
71 };
72 write!(f, "{interval}")
73 }
74}
75
76#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq)]
78#[serde(rename_all = "lowercase")]
79#[non_exhaustive]
80pub enum PlanStatus {
81 #[default]
82 Active,
83 Archived,
84 Deleted,
85}
86
87impl fmt::Display for PlanStatus {
88 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
89 let plan_status = match self {
90 PlanStatus::Active => "Active",
91 PlanStatus::Archived => "Archived",
92 PlanStatus::Deleted => "Deleted",
93 };
94 write!(f, "{plan_status}")
95 }
96}
97
98#[derive(Debug, Clone, Default, Serialize, Deserialize, Builder)]
101#[builder(setter(strip_option), default)]
102pub struct PlanUpdateRequest {
103 #[serde(skip_serializing_if = "Option::is_none")]
105 pub name: Option<String>,
106 #[serde(skip_serializing_if = "Option::is_none")]
108 pub amount: Option<String>,
109 #[serde(skip_serializing_if = "Option::is_none")]
111 pub interval: Option<Interval>,
112 #[serde(skip_serializing_if = "Option::is_none")]
114 pub description: Option<String>,
115 #[serde(skip_serializing_if = "Option::is_none")]
117 pub send_invoices: Option<bool>,
118 #[serde(skip_serializing_if = "Option::is_none")]
120 pub send_sms: Option<bool>,
121 #[serde(skip_serializing_if = "Option::is_none")]
123 pub currency: Option<Currency>,
124 #[serde(skip_serializing_if = "Option::is_none")]
126 pub invoice_limit: Option<u8>,
127 #[serde(skip_serializing_if = "Option::is_none")]
131 pub update_existing_subscriptions: Option<bool>,
132}
133
134#[derive(Clone, Debug, Serialize, Deserialize, Default)]
136pub struct PlanResponseData {
137 pub subscriptions: Option<Vec<Subscription>>,
138 pub name: String,
139 #[serde(deserialize_with = "string_or_number_to_u32")]
140 pub amount: u32,
141 pub interval: Interval,
142 pub integration: u32,
143 pub domain: Domain,
144 pub plan_code: String,
145 pub description: Option<String>,
146 pub send_invoices: Option<bool>,
147 pub send_sms: bool,
148 pub hosted_page: bool,
149 pub hosted_page_url: Option<String>,
150 pub hosted_page_summary: Option<String>,
151 pub currency: Currency,
152 pub id: u32,
153 #[serde(rename = "createdAt")]
154 pub created_at: String,
155 #[serde(rename = "updatedAt")]
156 pub updated_at: String,
157}
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162 use std::error::Error;
163
164 #[test]
165 fn can_create_plan_request_with_builder() -> Result<(), Box<dyn Error>> {
166 let plan = PlanRequestBuilder::default()
167 .name("test plan".to_string())
168 .amount("100000".to_string())
169 .interval(Interval::Monthly)
170 .description("some description".to_string())
171 .build()?;
172
173 assert_eq!(plan.name, "test plan");
174 assert_eq!(plan.amount, "100000");
175 assert_eq!(plan.interval, Interval::Monthly);
176 assert_eq!(plan.description, Some("some description".to_string()));
177
178 Ok(())
179 }
180
181 #[test]
182 fn cannot_create_plan_request_without_compulsory_field() -> Result<(), Box<dyn Error>> {
183 let plan = PlanRequestBuilder::default()
184 .currency(Currency::XOF)
185 .build();
186
187 assert!(plan.is_err());
188
189 Ok(())
190 }
191}