tinkoff_acquiring/
types.rs

1use serde::{Deserialize, Serialize};
2
3pub type Cents = i64;
4
5#[derive(Debug, thiserror::Error)]
6pub enum Error {
7    #[error("http: {0}")]
8    Http(#[from] reqwest::Error),
9    #[error("api error {code:?}: {message:?}")]
10    Api { code: Option<String>, message: Option<String> },
11    #[error("token verification failed")]
12    Token,
13    #[error("invalid receipt: {0}")]
14    Receipt(&'static str),
15}
16pub type Result<T> = std::result::Result<T, Error>;
17
18#[derive(Debug, Clone)]
19pub struct Config {
20    pub terminal_key: String,
21    pub password: String,
22    /// e.g. "https://securepay.tinkoff.ru"
23    pub base: String,
24    pub user_agent: Option<String>,
25    /// default request timeout (ms)
26    pub timeout_ms: u64,
27    /// retries
28    pub max_retries: usize,
29    pub initial_backoff_ms: u64,
30    /// заголовок для идемпотентности
31    pub idempotence_header: Option<String>,
32}
33impl Config {
34    pub fn new(terminal_key: impl Into<String>, password: impl Into<String>) -> Self {
35        Self {
36            terminal_key: terminal_key.into(),
37            password: password.into(),
38            base: "https://securepay.tinkoff.ru".into(),
39            user_agent: Some(format!("tinkoff-acquiring/{}", env!("CARGO_PKG_VERSION"))),
40            timeout_ms: 10_000,
41            max_retries: 3,
42            initial_backoff_ms: 250,
43            idempotence_header: Some("Idempotence-Key".into()),
44        }
45    }
46}
47
48/* ---------- Общие enum'ы ---------- */
49
50#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
51#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
52pub enum Status {
53    New,
54    ThreeDsChecking,
55    ThreeDsChecked,
56    Authorized,
57    Confirmed,
58    Rejected,
59    Canceled,
60    Refund,
61    PartiallyRefunded,
62    DeadlineExpired,
63    AuthorizationDeclined,
64    Reversed,
65    FormShowed,
66    Unknown,
67}
68impl Default for Status { fn default() -> Self { Status::Unknown } }
69
70#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
71#[serde(untagged)]
72pub enum ErrorCodeEnum {
73    Known(KnownError),
74    Unknown(String),
75}
76#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
77#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
78pub enum KnownError {
79    #[serde(alias="0")] Ok,
80    #[serde(alias="7")] InvalidToken,
81    #[serde(alias="8")] PaymentNotFound,
82    #[serde(alias="9999")] Unknown,
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
86#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
87pub enum FailureReason {
88    InsufficientFunds,
89    ExpiredCard,
90    IncorrectCvv,
91    BankDecline,
92    SuspectedFraud,
93    Unknown,
94}
95
96#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
97#[serde(rename_all = "lowercase")]
98pub enum Taxation { Osn, UsnIncome, UsnIncomeOutcome, Patent, Envd, Esn }
99
100#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
101#[serde(rename_all = "lowercase")]
102pub enum Tax { None, Vat0, Vat10, Vat20, Vat110, Vat120 }
103
104/* ---------- Типизированный чек (упрощённый) ---------- */
105
106#[derive(Debug, Clone, Serialize, Deserialize)]
107pub struct Receipt {
108    #[serde(rename="Email", skip_serializing_if="Option::is_none")]
109    pub email: Option<String>,
110    #[serde(rename="Phone", skip_serializing_if="Option::is_none")]
111    pub phone: Option<String>,
112    #[serde(rename="Taxation")]
113    pub taxation: Taxation,
114    #[serde(rename="Items")]
115    pub items: Vec<Item>,
116    #[serde(skip)]
117    pub currency: Option<String>,
118}
119impl Receipt {
120    pub fn validate(&self) -> Result<()> {
121        if self.items.is_empty() { return Err(Error::Receipt("empty items")); }
122        for it in &self.items {
123            // допускаем дробное количество (округление до копейки)
124            let expected = ((it.price as f64) * it.quantity).round() as i64;
125            if expected != it.amount {
126                return Err(Error::Receipt("item amount != price * quantity (rounded)"));
127            }
128            if it.price <= 0 || it.amount <= 0 {
129                return Err(Error::Receipt("non-positive price/amount"));
130            }
131        }
132        Ok(())
133    }
134}
135
136#[derive(Debug, Clone, Serialize, Deserialize)]
137pub struct Item {
138    #[serde(rename="Name")]
139    pub name: String,
140    #[serde(rename="Price")]
141    pub price: Cents,
142    #[serde(rename="Quantity")]
143    pub quantity: f64,
144    #[serde(rename="Amount")]
145    pub amount: Cents,
146    #[serde(rename="Tax")]
147    pub tax: Tax,
148}
149
150/* ---------- DTO ---------- */
151
152#[derive(Debug, Serialize)]
153pub struct InitRequest<'a> {
154    #[serde(rename="TerminalKey")] pub terminal_key: &'a str,
155    #[serde(rename="Amount")]      pub amount: Cents,
156    #[serde(rename="OrderId")]     pub order_id: &'a str,
157    #[serde(rename="Description", skip_serializing_if="Option::is_none")] pub description: Option<&'a str>,
158    #[serde(rename="PayType",     skip_serializing_if="Option::is_none")] pub pay_type: Option<&'a str>,
159    #[serde(rename="Receipt",     skip_serializing_if="Option::is_none")] pub receipt: Option<serde_json::Value>,
160    #[serde(rename="DATA",        skip_serializing_if="Option::is_none")] pub data: Option<serde_json::Value>,
161    #[serde(rename="SuccessURL",  skip_serializing_if="Option::is_none")] pub success_url: Option<&'a str>,
162    #[serde(rename="FailURL",     skip_serializing_if="Option::is_none")] pub fail_url: Option<&'a str>,
163    #[serde(rename="Recurrent",   skip_serializing_if="Option::is_none")] pub recurrent: Option<String>,
164    #[serde(rename="Token")]      pub token: String,
165}
166#[derive(Debug, Deserialize)]
167pub struct InitResponse {
168    #[serde(rename="Success")]   pub success: bool,
169    #[serde(rename="PaymentId")] pub payment_id: Option<String>,
170    #[serde(rename="PaymentURL")]pub payment_url: Option<String>,
171    #[serde(rename="ErrorCode")] pub error_code: Option<String>,
172    #[serde(rename="Message")]   pub message: Option<String>,
173}
174
175#[derive(Debug, Serialize)]
176pub struct GetStateRequest<'a> {
177    #[serde(rename="TerminalKey")] pub terminal_key: &'a str,
178    #[serde(rename="PaymentId")]   pub payment_id: &'a str,
179    #[serde(rename="Token")]       pub token: String,
180}
181#[derive(Debug, Deserialize, Default)]
182pub struct GetStateResponse {
183    #[serde(rename="Success")]   pub success: bool,
184    #[serde(rename="Status")]    pub status: Option<Status>,
185    #[serde(rename="ErrorCode")] pub error_code: Option<String>,
186    #[serde(rename="Message")]   pub message: Option<String>,
187    #[serde(rename="FailureReason")] pub failure_reason: Option<FailureReason>,
188}
189
190#[derive(Debug, Serialize)]
191pub struct ConfirmRequest<'a> {
192    #[serde(rename="TerminalKey")] pub terminal_key: &'a str,
193    #[serde(rename="PaymentId")]   pub payment_id: &'a str,
194    #[serde(rename="Amount", skip_serializing_if="Option::is_none")]  pub amount: Option<Cents>,
195    #[serde(rename="Receipt", skip_serializing_if="Option::is_none")] pub receipt: Option<serde_json::Value>,
196    #[serde(rename="Token")]       pub token: String,
197}
198#[derive(Debug, Deserialize)]
199pub struct ConfirmResponse {
200    #[serde(rename="Success")]   pub success: bool,
201    #[serde(rename="Status")]    pub status: Option<Status>,
202    #[serde(rename="ErrorCode")] pub error_code: Option<String>,
203    #[serde(rename="Message")]   pub message: Option<String>,
204}
205
206#[derive(Debug, Serialize)]
207pub struct CancelRequest<'a> {
208    #[serde(rename="TerminalKey")] pub terminal_key: &'a str,
209    #[serde(rename="PaymentId")]   pub payment_id: &'a str,
210    #[serde(rename="Amount", skip_serializing_if="Option::is_none")]  pub amount: Option<Cents>,
211    #[serde(rename="Receipt", skip_serializing_if="Option::is_none")] pub receipt: Option<serde_json::Value>,
212    #[serde(rename="Token")]       pub token: String,
213}
214#[derive(Debug, Deserialize)]
215pub struct CancelResponse {
216    #[serde(rename="Success")]   pub success: bool,
217    #[serde(rename="Status")]    pub status: Option<Status>,
218    #[serde(rename="ErrorCode")] pub error_code: Option<String>,
219    #[serde(rename="Message")]   pub message: Option<String>,
220}
221
222/* ---- charge ---- */
223#[derive(Debug, Serialize)]
224pub struct ChargeRequest<'a> {
225    #[serde(rename="TerminalKey")] pub terminal_key: &'a str,
226    #[serde(rename="PaymentId")]   pub payment_id: &'a str,
227    #[serde(rename="Token")]       pub token: String,
228}
229#[derive(Debug, Deserialize)]
230pub struct ChargeResponse {
231    #[serde(rename="Success")]   pub success: bool,
232    #[serde(rename="Status")]    pub status: Option<Status>,
233    #[serde(rename="ErrorCode")] pub error_code: Option<String>,
234    #[serde(rename="Message")]   pub message: Option<String>,
235}
236
237/* ---- cards ---- */
238#[derive(Debug, Serialize)]
239pub struct GetCardListRequest<'a> {
240    #[serde(rename="TerminalKey")] pub terminal_key: &'a str,
241    #[serde(rename="CustomerKey")] pub customer_key: &'a str,
242    #[serde(rename="Token")]       pub token: String,
243}
244#[derive(Debug, Deserialize)]
245pub struct GetCardListResponse {
246    #[serde(rename="Success")]   pub success: bool,
247    #[serde(rename="Cards")]     pub cards: Option<Vec<Card>>,
248    #[serde(rename="ErrorCode")] pub error_code: Option<String>,
249    #[serde(rename="Message")]   pub message: Option<String>,
250}
251#[derive(Debug, Deserialize)]
252pub struct Card {
253    #[serde(rename="Pan")]     pub pan: Option<String>,
254    #[serde(rename="CardId")]  pub card_id: Option<String>,
255    #[serde(rename="Status")]  pub status: Option<String>,
256    #[serde(rename="RebillId")]pub rebill_id: Option<String>,
257}
258#[derive(Debug, Serialize)]
259pub struct RemoveCardRequest<'a> {
260    #[serde(rename="TerminalKey")] pub terminal_key: &'a str,
261    #[serde(rename="CustomerKey")] pub customer_key: &'a str,
262    #[serde(rename="CardId")]     pub card_id: &'a str,
263    #[serde(rename="Token")]       pub token: String,
264}
265#[derive(Debug, Deserialize)]
266pub struct RemoveCardResponse {
267    #[serde(rename="Success")]   pub success: bool,
268    #[serde(rename="ErrorCode")] pub error_code: Option<String>,
269    #[serde(rename="Message")]   pub message: Option<String>,
270}
271
272/* ---- customers ---- */
273#[derive(Debug, Serialize)]
274pub struct AddCustomerRequest<'a> {
275    #[serde(rename="TerminalKey")] pub terminal_key: &'a str,
276    #[serde(rename="CustomerKey")] pub customer_key: &'a str,
277    #[serde(rename="Email", skip_serializing_if="Option::is_none")] pub email: Option<&'a str>,
278    #[serde(rename="Phone", skip_serializing_if="Option::is_none")] pub phone: Option<&'a str>,
279    #[serde(rename="Token")]       pub token: String,
280}
281#[derive(Debug, Deserialize)]
282pub struct AddCustomerResponse {
283    #[serde(rename="Success")]   pub success: bool,
284    #[serde(rename="ErrorCode")] pub error_code: Option<String>,
285    #[serde(rename="Message")]   pub message: Option<String>,
286}
287#[derive(Debug, Serialize)]
288pub struct GetCustomerRequest<'a> {
289    #[serde(rename="TerminalKey")] pub terminal_key: &'a str,
290    #[serde(rename="CustomerKey")] pub customer_key: &'a str,
291    #[serde(rename="Token")]       pub token: String,
292}
293#[derive(Debug, Deserialize)]
294pub struct GetCustomerResponse {
295    #[serde(rename="Success")]     pub success: bool,
296    #[serde(rename="CustomerKey")] pub customer_key: Option<String>,
297    #[serde(rename="Email")]       pub email: Option<String>,
298    #[serde(rename="Phone")]       pub phone: Option<String>,
299    #[serde(rename="ErrorCode")]   pub error_code: Option<String>,
300    #[serde(rename="Message")]     pub message: Option<String>,
301}
302#[derive(Debug, Serialize)]
303pub struct RemoveCustomerRequest<'a> {
304    #[serde(rename="TerminalKey")] pub terminal_key: &'a str,
305    #[serde(rename="CustomerKey")] pub customer_key: &'a str,
306    #[serde(rename="Token")]       pub token: String,
307}
308#[derive(Debug, Deserialize)]
309pub struct RemoveCustomerResponse {
310    #[serde(rename="Success")]   pub success: bool,
311    #[serde(rename="ErrorCode")] pub error_code: Option<String>,
312    #[serde(rename="Message")]   pub message: Option<String>,
313}
314
315/* ---- bind/finish authorize ---- */
316#[derive(Debug, Serialize)]
317pub struct BindCardRequest<'a> {
318    #[serde(rename="TerminalKey")] pub terminal_key: &'a str,
319    #[serde(rename="CustomerKey")] pub customer_key: &'a str,
320    #[serde(rename="Token")]       pub token: String,
321}
322#[derive(Debug, Deserialize)]
323pub struct BindCardResponse {
324    #[serde(rename="Success")]     pub success: bool,
325    #[serde(rename="RequestKey")]  pub request_key: Option<String>,
326    #[serde(rename="ErrorCode")]   pub error_code: Option<String>,
327    #[serde(rename="Message")]     pub message: Option<String>,
328}
329
330#[derive(Debug, Serialize)]
331pub struct FinishAuthorizeRequest<'a> {
332    #[serde(rename="TerminalKey")] pub terminal_key: &'a str,
333    #[serde(rename="PaymentId")]   pub payment_id: &'a str,
334    #[serde(rename="DATA")]        pub data: serde_json::Value,
335    #[serde(rename="Token")]       pub token: String,
336}
337#[derive(Debug, Deserialize)]
338pub struct FinishAuthorizeResponse {
339    #[serde(rename="Success")]   pub success: bool,
340    #[serde(rename="Status")]    pub status: Option<Status>,
341    #[serde(rename="ErrorCode")] pub error_code: Option<String>,
342    #[serde(rename="Message")]   pub message: Option<String>,
343}
344
345/* ---- QR ---- */
346#[derive(Debug, Serialize)]
347pub struct GetQrRequest<'a> {
348    #[serde(rename="TerminalKey")] pub terminal_key: &'a str,
349    #[serde(rename="PaymentId")]   pub payment_id: &'a str,
350    #[serde(rename="Token")]       pub token: String,
351}
352#[derive(Debug, Deserialize)]
353pub struct GetQrResponse {
354    #[serde(rename="Success")]   pub success: bool,
355    #[serde(rename="Data")]      pub data: Option<String>,
356    #[serde(rename="ErrorCode")] pub error_code: Option<String>,
357    #[serde(rename="Message")]   pub message: Option<String>,
358}