pix_api_client/
cob.rs

1use reqwest::{Method, RequestBuilder};
2use serde::{Deserialize, Serialize};
3
4use crate::{ApiRequest, PixClient};
5
6pub struct CobEndpoint<'a> {
7    inner: &'a PixClient,
8}
9
10#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
11#[allow(non_camel_case_types)]
12pub enum CobrancaStatus {
13    ATIVA,
14    CONCLUIDA,
15    REMOVIDA_PELO_USUARIO_RECEBEDOR,
16    REMOVIDA_PELO_PSP,
17}
18
19#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
20pub struct CobrancaImediata {
21    /// Por default, expira em 3600 segundos, i.e 1h
22    pub calendario: Calendario,
23    pub devedor: Devedor,
24    /// Valor em string
25    pub valor: Valor,
26
27    /// Campo da chave PIX do recebedor desta cobrança.
28    #[serde(rename = "chave")]
29    pub chave_pix_recebedor: String,
30
31    /// Id da Transação. Exclusivo como resposta.
32    #[serde(skip_serializing_if = "Option::is_none")]
33    pub txid: Option<String>,
34
35    #[serde(rename = "loc")]
36    #[serde(skip_serializing_if = "Option::is_none")]
37    pub location: Option<Location>,
38
39    #[serde(skip_serializing_if = "Option::is_none")]
40    pub status: Option<String>,
41
42    /// Campo que será para que o pagador desta cobrança insira uma informação.
43    /// Sua implementação depende do PSP do pagador. Não é garantido seu preenchimento. Verifique.
44    #[serde(rename = "solicitacaoPagador")]
45    #[serde(skip_serializing_if = "Option::is_none")]
46    pub solicitacao_pagador: Option<String>,
47    #[serde(skip_serializing_if = "Option::is_none")]
48    #[serde(rename = "infoAdicionais")]
49    pub info_adicionais: Option<Vec<InfoAdicionais>>,
50}
51
52impl CobrancaImediata {
53    /// Creates a new
54    pub fn new(valor: f64, chave_pix_recebedor: String, devedor: Devedor) -> CobrancaImediata {
55        let valor = Valor::new(valor, false);
56
57        Self {
58            calendario: Default::default(),
59            devedor,
60            valor,
61            chave_pix_recebedor,
62            txid: None,
63            location: None,
64            status: None,
65            solicitacao_pagador: None,
66            info_adicionais: None,
67        }
68    }
69}
70
71#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
72pub struct Calendario {
73    #[serde(skip_serializing_if = "Option::is_none")]
74    pub criacao: Option<String>,
75    #[serde(skip_serializing_if = "Option::is_none")]
76    pub apresentacao: Option<String>,
77    /// Segundos para a expiração da cobrança, a partir do campo `Calendario.criacao`.
78    pub expiracao: i64,
79}
80
81impl Default for Calendario {
82    fn default() -> Self {
83        Self {
84            criacao: None,
85            apresentacao: None,
86            expiracao: 3600,
87        }
88    }
89}
90
91#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
92pub struct Devedor {
93    #[serde(skip_serializing_if = "Option::is_none")]
94    cnpj: Option<String>,
95    #[serde(skip_serializing_if = "Option::is_none")]
96    cpf: Option<String>,
97    nome: String,
98}
99
100impl Devedor {
101    pub fn new_pessoa_juridica(cnpj: String, nome: String) -> Self {
102        Self {
103            cnpj: Some(cnpj),
104            cpf: None,
105            nome,
106        }
107    }
108
109    pub fn new_pessoa_fisica(cpf: String, nome: String) -> Self {
110        Self {
111            cnpj: None,
112            cpf: Some(cpf),
113            nome,
114        }
115    }
116}
117
118#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
119pub struct Valor {
120    pub original: String,
121    /// A ausencia deste campo, que é o mesmo que 0, significa que a cobrança não poderá ter seu valor alterado.
122    /// No caso do valor de 1, significa que o valor final poderá ser alterado pelo pagador.
123    #[serde(rename = "modalidadeAlteracao")]
124    #[serde(skip_serializing_if = "Option::is_none")]
125    pub permite_alteracao: Option<i32>,
126}
127
128impl Valor {
129    // Valor com no máximo duas casas decimais. Caso houver mais que 2 casas, o valor será truncado.
130    pub fn new(valor_original: f64, permite_alteracao: bool) -> Valor {
131        let permite_alteracao = if permite_alteracao { Some(1) } else { None };
132        Self {
133            original: format!("{:.2}", valor_original),
134            permite_alteracao,
135        }
136    }
137}
138
139#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
140pub struct Location {
141    id: i64,
142    #[serde(rename = "location")]
143    pub url: String,
144    #[serde(rename = "tipoCob")]
145    tipo_cob: String,
146    criacao: String,
147}
148
149#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
150pub struct InfoAdicionais {
151    pub nome: String,
152    pub valor: String,
153}
154
155impl PixClient {
156    pub fn cob(&self) -> CobEndpoint {
157        CobEndpoint { inner: self }
158    }
159}
160
161impl<'a> CobEndpoint<'a> {
162    pub fn criar_cobranca_txid(&self, txid: String, payload: CobrancaImediata) -> ApiRequest<CobrancaImediata> {
163        let endpoint = format!("{}/cob/{}", &*self.inner.base_endpoint, txid);
164        self.inner.request_with_headers(Method::PUT, &*endpoint, payload)
165    }
166
167    pub fn consultar_cobranca_txid(&self, txid: String, payload: CobrancaImediata) -> ApiRequest<CobrancaImediata> {
168        let endpoint = format!("{}/cob/{}", &*self.inner.base_endpoint, txid);
169        self.inner.request_with_headers(Method::GET, &*endpoint, payload)
170    }
171    pub fn revisar_cobranca_txid(&self, txid: String, payload: CobrancaImediata) -> ApiRequest<CobrancaImediata> {
172        let endpoint = format!("{}/cob/{}", &*self.inner.base_endpoint, txid);
173        self.inner.request_with_headers(Method::POST, &*endpoint, payload)
174    }
175
176    /// Criar uma cobrança imediata.
177    /// Diferente de `criar_cobranca_imediata`, o `txid` é definido pelo PSP.
178    pub fn criar_cobranca_imediata(&self, payload: CobrancaImediata) -> ApiRequest<CobrancaImediata> {
179        let endpoint = format!("{}/cob", &*self.inner.base_endpoint);
180        self.inner.request_with_headers(Method::POST, &*endpoint, payload)
181    }
182
183    pub fn consultar_cobrancas(&self) -> RequestBuilder {
184        let endpoint = format!("{}/cob", &*self.inner.base_endpoint);
185        self.inner.inner_client.request(Method::GET, &endpoint)
186    }
187}
188
189#[cfg(test)]
190mod tests {
191    use super::*;
192
193    #[test]
194    fn t_value() {
195        let new_value = Valor::new(512.614, false);
196        println!(": {:?}", new_value);
197
198        let new_integer_value = Valor::new(400f64, false);
199        println!(": {:?}", new_integer_value);
200    }
201}