tgbot/types/payment/invoice/
mod.rs

1use serde::{Deserialize, Serialize};
2use serde_json::Error as JsonError;
3
4use crate::{
5    api::{Method, Payload},
6    types::{ChatId, InlineKeyboardMarkup, Integer, Message, ReplyParameters},
7};
8
9#[cfg(test)]
10mod tests;
11
12/// Represents an invoice.
13#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd, Serialize)]
14pub struct Invoice {
15    /// Three-letter ISO 4217 currency code.
16    pub currency: String,
17    /// Product description.
18    pub description: String,
19    /// Unique bot deep-linking parameter that can be used to generate this invoice.
20    pub start_parameter: String,
21    /// Product name.
22    pub title: String,
23    /// Total price in the smallest units of the currency (integer, not float/double).
24    ///
25    /// For example, for a price of US$ 1.45 pass amount = 145.
26    /// See the exp parameter in [currencies.json][1], it shows the number of digits past
27    /// the decimal point for each currency (2 for the majority of currencies).
28    ///
29    /// [1]: https://core.telegram.org/bots/payments/currencies.json
30    pub total_amount: Integer,
31}
32
33impl Invoice {
34    /// Creates a new `Invoice`.
35    ///
36    /// # Arguments
37    ///
38    /// * `currency` - ISO 4217 currency code.
39    /// * `description` - Product description.
40    /// * `start_parameter` - Unique bot deep-linking parameter.
41    /// * `title` - Product name.
42    /// * `total_amount` - Total price.
43    pub fn new<A, B, C, D>(currency: A, description: B, start_parameter: C, title: D, total_amount: Integer) -> Self
44    where
45        A: Into<String>,
46        B: Into<String>,
47        C: Into<String>,
48        D: Into<String>,
49    {
50        Self {
51            currency: currency.into(),
52            description: description.into(),
53            start_parameter: start_parameter.into(),
54            title: title.into(),
55            total_amount,
56        }
57    }
58}
59
60/// Represents a portion of the price for goods or services.
61#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd, Serialize)]
62pub struct LabeledPrice {
63    amount: Integer,
64    label: String,
65}
66
67impl LabeledPrice {
68    /// Creates a new `LabeledPrice`.
69    ///
70    /// # Arguments
71    ///
72    /// * `amount` - Price of the product in the smallest units of the currency.
73    /// * `label` - Portion label.
74    pub fn new<T>(amount: Integer, label: T) -> Self
75    where
76        T: Into<String>,
77    {
78        Self {
79            amount,
80            label: label.into(),
81        }
82    }
83
84    /// Returns the amount.
85    pub fn amount(&self) -> Integer {
86        self.amount
87    }
88
89    /// Returns the portion label.
90    pub fn label(&self) -> &str {
91        &self.label
92    }
93}
94
95/// Represents an invoice parameters used in [`CreateInvoiceLink`] and [`SendInvoice`].
96#[serde_with::skip_serializing_none]
97#[derive(Clone, Debug, Default, Deserialize, PartialEq, PartialOrd, Serialize)]
98pub struct InvoiceParameters {
99    /// Indicates whether the final price depends on the shipping method.
100    pub is_flexible: Option<bool>,
101    /// The maximum accepted amount for tips in the smallest units of the currency.
102    ///
103    /// For example, for a maximum tip of US$ 1.45 pass max_tip_amount = 145.
104    /// See the exp parameter in [currencies.json][1],
105    /// it shows the number of digits past the decimal point for each currency
106    /// (2 for the majority of currencies).
107    ///
108    /// Defaults to 0.
109    ///
110    /// [1]: (https://core.telegram.org/bots/payments/currencies.json)
111    pub max_tip_amount: Option<Integer>,
112    /// Indicates whether the user's email address is required to complete the order.
113    pub need_email: Option<bool>,
114    /// Indicates whether the user's full name is required to complete the order.
115    pub need_name: Option<bool>,
116    /// Indicates whether the user's phone number is required to complete the order.
117    pub need_phone_number: Option<bool>,
118    /// Indicates whether the user's shipping address is required to complete the order.
119    pub need_shipping_address: Option<bool>,
120    /// Photo height.
121    pub photo_height: Option<Integer>,
122    /// Photo size in bytes.
123    pub photo_size: Option<Integer>,
124    /// URL of the product photo for the invoice.
125    ///
126    /// Can be a photo of the goods or a marketing image for a service.
127    pub photo_url: Option<String>,
128    /// Photo width.
129    pub photo_width: Option<Integer>,
130    /// Data about the invoice, which will be shared with the payment provider.
131    ///
132    /// A detailed description of required fields should be provided by the payment provider.
133    pub provider_data: Option<String>,
134    /// Payment provider token, obtained via @BotFather.
135    ///
136    /// Pass an empty string for payments in Telegram Stars.
137    pub provider_token: Option<String>,
138    /// Indicates whether the user's phone number should be sent to the provider.
139    pub send_phone_number_to_provider: Option<bool>,
140    /// Indicates whether the user's email address should be sent to the provider.
141    pub send_email_to_provider: Option<bool>,
142    /// An array of suggested amounts of tips in the smallest units of the currency.
143    ///
144    /// At most 4 suggested tip amounts can be specified.
145    /// The suggested tip amounts must be positive,
146    /// passed in a strictly increased order and must not exceed `max_tip_amount`.
147    pub suggested_tip_amounts: Option<Vec<Integer>>,
148}
149
150impl InvoiceParameters {
151    /// Sets a new value for the `is_flexible` flag.
152    ///
153    /// # Arguments
154    ///
155    /// * `value` - Indicates whether the final price depends on the shipping method.
156    pub fn with_flexible(mut self, value: bool) -> Self {
157        self.is_flexible = Some(value);
158        self
159    }
160
161    /// Sets a new max tip amount.
162    ///
163    /// # Arguments
164    ///
165    /// * `value` - The maximum accepted amount for tips in the smallest units of the currency.
166    pub fn with_max_tip_amount(mut self, value: Integer) -> Self {
167        self.max_tip_amount = Some(value);
168        self
169    }
170
171    /// Sets a new value for the `need_email` flag.
172    ///
173    /// # Arguments
174    ///
175    /// * `value` - Indicates whether the user's email address is required to complete the order.
176    pub fn with_need_email(mut self, value: bool) -> Self {
177        self.need_email = Some(value);
178        self
179    }
180
181    /// Sets a new value for the `need_name` flag.
182    ///
183    /// # Arguments
184    ///
185    /// * `value` - Indicates whether the user's full name is required to complete the order.
186    pub fn with_need_name(mut self, value: bool) -> Self {
187        self.need_name = Some(value);
188        self
189    }
190
191    /// Sets a new value for the `need_phone_number` flag.
192    ///
193    /// # Arguments
194    ///
195    /// * `value` - Indicates whether the user's phone number is required to complete the order.
196    pub fn with_need_phone_number(mut self, value: bool) -> Self {
197        self.need_phone_number = Some(value);
198        self
199    }
200
201    /// Sets a new value for the `need_shipping_address` flag.
202    ///
203    /// # Arguments
204    ///
205    /// * `value` - Indicates whether the user's shipping address is required to complete the order.
206    pub fn with_need_shipping_address(mut self, value: bool) -> Self {
207        self.need_shipping_address = Some(value);
208        self
209    }
210
211    /// Sets a new photo height.
212    ///
213    /// # Arguments
214    ///
215    /// * `value` - Photo height.
216    pub fn with_photo_height(mut self, value: Integer) -> Self {
217        self.photo_height = Some(value);
218        self
219    }
220
221    /// Sets a new photo size.
222    ///
223    /// # Arguments
224    ///
225    /// * `value` - Photo size in bytes.
226    pub fn with_photo_size(mut self, value: Integer) -> Self {
227        self.photo_size = Some(value);
228        self
229    }
230
231    /// Sets a new photo URL.
232    ///
233    /// # Arguments
234    ///
235    /// * `value` - Photo URL.
236    pub fn with_photo_url<T>(mut self, value: T) -> Self
237    where
238        T: Into<String>,
239    {
240        self.photo_url = Some(value.into());
241        self
242    }
243
244    /// Sets a new photo width.
245    ///
246    /// # Arguments
247    ///
248    /// * `value` - Photo width.
249    pub fn with_photo_width(mut self, value: Integer) -> Self {
250        self.photo_width = Some(value);
251        self
252    }
253
254    /// Sets a new provider data.
255    ///
256    /// # Arguments
257    ///
258    /// * `value` - Data about the invoice, which will be shared with the payment provider.
259    pub fn with_provider_data<T>(mut self, value: &T) -> Result<Self, JsonError>
260    where
261        T: Serialize,
262    {
263        self.provider_data = Some(serde_json::to_string(value)?);
264        Ok(self)
265    }
266
267    /// Sets a new provider token.
268    ///
269    /// # Arguments
270    ///
271    /// * `value` - Payment provider token, obtained via @BotFather.
272    ///   Pass an empty string for payments in Telegram Stars.
273    pub fn with_provider_token<T>(mut self, value: T) -> Self
274    where
275        T: Into<String>,
276    {
277        self.provider_token = Some(value.into());
278        self
279    }
280
281    /// Sets a new value for the `send_phone_number_to_provider` flag.
282    ///
283    /// # Arguments
284    ///
285    /// * `value` - Indicates whether the user's phone number should be sent to the provider.
286    pub fn with_send_phone_number_to_provider(mut self, value: bool) -> Self {
287        self.send_phone_number_to_provider = Some(value);
288        self
289    }
290
291    /// Sets a new value for the `send_email_to_provider` flag.
292    ///
293    /// # Arguments
294    ///
295    /// * `value` - Indicates whether the user's email address should be sent to the provider.
296    pub fn with_send_email_to_provider(mut self, value: bool) -> Self {
297        self.send_email_to_provider = Some(value);
298        self
299    }
300
301    /// Sets a new list of max tip amounts.
302    ///
303    /// # Arguments
304    ///
305    /// * `value` - An array of suggested amounts of tips in the smallest units of the currency.
306    pub fn with_suggested_tip_amounts<T>(mut self, value: T) -> Self
307    where
308        T: IntoIterator<Item = Integer>,
309    {
310        self.suggested_tip_amounts = Some(value.into_iter().collect());
311        self
312    }
313}
314
315/// Creates a link for an invoice.
316///
317/// Returns the created invoice link as String on success.
318#[serde_with::skip_serializing_none]
319#[derive(Clone, Debug, Serialize)]
320pub struct CreateInvoiceLink {
321    currency: String,
322    description: String,
323    payload: String,
324    prices: Vec<LabeledPrice>,
325    title: String,
326    business_connection_id: Option<String>,
327    subscription_period: Option<Integer>,
328    #[serde(flatten)]
329    parameters: Option<InvoiceParameters>,
330}
331
332impl CreateInvoiceLink {
333    /// Creates a new `CreateInvoiceLink`.
334    ///
335    /// # Arguments
336    ///
337    /// * `title` - Product name; 1-32 characters.
338    /// * `description` - Product description; 1-255 characters.
339    /// * `payload` - Bot-defined invoice payload; 1-128 bytes;
340    ///   this will not be displayed to the user;
341    ///   use for your internal processes.
342    /// * `currency` - Three-letter ISO 4217 currency code, see more on currencies.
343    /// * `prices` - Price breakdown
344    ///   (e.g. product price, tax, discount, delivery cost, delivery tax, bonus, etc.).
345    pub fn new<A, B, C, D, E>(title: A, description: B, payload: C, currency: D, prices: E) -> Self
346    where
347        A: Into<String>,
348        B: Into<String>,
349        C: Into<String>,
350        D: Into<String>,
351        E: IntoIterator<Item = LabeledPrice>,
352    {
353        Self {
354            currency: currency.into(),
355            description: description.into(),
356            payload: payload.into(),
357            prices: prices.into_iter().collect(),
358            title: title.into(),
359            business_connection_id: None,
360            subscription_period: None,
361            parameters: None,
362        }
363    }
364
365    /// Sets a new business connection ID.
366    ///
367    /// # Arguments
368    ///
369    /// * `value` - Unique identifier of the business connection on behalf of which the link will be created.
370    pub fn with_business_connection_id<T>(mut self, value: T) -> Self
371    where
372        T: Into<String>,
373    {
374        self.business_connection_id = Some(value.into());
375        self
376    }
377
378    /// Sets a new invoice parameters.
379    ///
380    /// # Arguments
381    ///
382    /// * `value` - Invoice parameters.
383    pub fn with_parameters(mut self, value: InvoiceParameters) -> Self {
384        self.parameters = Some(value);
385        self
386    }
387
388    /// Sets a new subscription period.
389    ///
390    /// # Arguments
391    ///
392    /// * `value` - The number of seconds the subscription will be active for before the next payment.
393    ///   The currency must be set to “XTR” (Telegram Stars) if the parameter is used.
394    ///   Currently, it must always be 2592000 (30 days) if specified.
395    pub fn with_subscription_period(mut self, value: Integer) -> Self {
396        self.subscription_period = Some(value);
397        self
398    }
399}
400
401impl Method for CreateInvoiceLink {
402    type Response = String;
403
404    fn into_payload(self) -> Payload {
405        Payload::json("createInvoiceLink", self)
406    }
407}
408
409/// Sends an invoice.
410#[serde_with::skip_serializing_none]
411#[derive(Clone, Debug, Serialize)]
412pub struct SendInvoice {
413    chat_id: ChatId,
414    currency: String,
415    description: String,
416    payload: String,
417    prices: Vec<LabeledPrice>,
418    title: String,
419    allow_paid_broadcast: Option<bool>,
420    disable_notification: Option<bool>,
421    message_effect_id: Option<String>,
422    message_thread_id: Option<Integer>,
423    #[serde(flatten)]
424    parameters: Option<InvoiceParameters>,
425    protect_content: Option<bool>,
426    reply_markup: Option<InlineKeyboardMarkup>,
427    reply_parameters: Option<ReplyParameters>,
428    start_parameter: Option<String>,
429}
430
431impl SendInvoice {
432    /// Creates a new `SendInvoice`.
433    ///
434    /// # Arguments
435    ///
436    /// * `chat_id` - Unique identifier of the target chat.
437    /// * `title` - Product name; 1-32 characters.
438    /// * `description` - Product description; 1-255 characters.
439    /// * `payload` - Bot-defined invoice payload; 1-128 bytes
440    ///   this will not be displayed to the user;
441    ///   use for your internal processes.
442    /// * `currency` - Three-letter ISO 4217 currency code, see more on currencies.
443    /// * `prices` - Price breakdown, a list of components
444    ///   (e.g. product price, tax, discount, delivery cost, delivery tax, bonus, etc.).
445    pub fn new<A, B, C, D, E, F>(chat_id: A, title: B, description: C, payload: D, currency: E, prices: F) -> Self
446    where
447        A: Into<ChatId>,
448        B: Into<String>,
449        C: Into<String>,
450        D: Into<String>,
451        E: Into<String>,
452        F: IntoIterator<Item = LabeledPrice>,
453    {
454        SendInvoice {
455            chat_id: chat_id.into(),
456            title: title.into(),
457            description: description.into(),
458            payload: payload.into(),
459            currency: currency.into(),
460            prices: prices.into_iter().collect(),
461            allow_paid_broadcast: None,
462            disable_notification: None,
463            message_effect_id: None,
464            message_thread_id: None,
465            parameters: None,
466            protect_content: None,
467            reply_markup: None,
468            reply_parameters: None,
469            start_parameter: None,
470        }
471    }
472
473    /// Sets a new value for the `allow_paid_broadcast` flag.
474    ///
475    /// # Arguments
476    ///
477    /// * `value` - Whether to allow up to 1000 messages per second, ignoring broadcasting limits
478    ///   for a fee of 0.1 Telegram Stars per message.
479    ///   The relevant Stars will be withdrawn from the bot's balance.
480    pub fn with_allow_paid_broadcast(mut self, value: bool) -> Self {
481        self.allow_paid_broadcast = Some(value);
482        self
483    }
484
485    /// Sets a new value for the `disable_notification` flag.
486    ///
487    /// # Arguments
488    ///
489    /// * `value` - Indicates whether to send the message silently or not;
490    ///   a user will receive a notification without sound.
491    pub fn with_disable_notification(mut self, value: bool) -> Self {
492        self.disable_notification = Some(value);
493        self
494    }
495
496    /// Sets a new message effect ID.
497    ///
498    /// # Arguments
499    ///
500    /// * `value` - Unique identifier of the message effect to be added to the message; for private chats only.
501    pub fn with_message_effect_id<T>(mut self, value: T) -> Self
502    where
503        T: Into<String>,
504    {
505        self.message_effect_id = Some(value.into());
506        self
507    }
508
509    /// Sets a new message thread ID.
510    ///
511    /// # Arguments
512    ///
513    /// * `value` - Unique identifier of the target message thread;
514    ///   supergroups only.
515    pub fn with_message_thread_id(mut self, value: Integer) -> Self {
516        self.message_thread_id = Some(value);
517        self
518    }
519
520    /// Sets a new invoice parameters.
521    ///
522    /// # Arguments
523    ///
524    /// * `value` - Invoice parameters.
525    pub fn with_parameters(mut self, value: InvoiceParameters) -> Self {
526        self.parameters = Some(value);
527        self
528    }
529
530    /// Sets a new value for the `protect_content` flag.
531    ///
532    /// # Arguments
533    ///
534    /// * `value` - Indicates whether to protect the contents
535    ///   of the sent message from forwarding and saving.
536    pub fn with_protect_content(mut self, value: bool) -> Self {
537        self.protect_content = Some(value);
538        self
539    }
540
541    /// Sets a new reply markup.
542    ///
543    /// # Arguments
544    ///
545    /// * `value` - Reply markup.
546    ///
547    /// If empty, one 'Pay total price' button will be shown.
548    /// If not empty, the first button must be a Pay button.
549    pub fn with_reply_markup<T>(mut self, value: T) -> Self
550    where
551        T: Into<InlineKeyboardMarkup>,
552    {
553        self.reply_markup = Some(value.into());
554        self
555    }
556
557    /// Sets new reply parameters.
558    ///
559    /// # Arguments
560    ///
561    /// * `value` - Description of the message to reply to.
562    pub fn with_reply_parameters(mut self, value: ReplyParameters) -> Self {
563        self.reply_parameters = Some(value);
564        self
565    }
566
567    /// Sets a new unique deep-linking parameter.
568    ///
569    /// # Arguments
570    ///
571    /// * `value` - Value of the parameter.
572    ///
573    /// If left empty, forwarded copies of the sent message will have a Pay button,
574    /// allowing multiple users to pay directly from the forwarded message, using the same invoice.
575    /// If non-empty, forwarded copies of the sent message will have a URL button
576    /// with a deep link to the bot (instead of a Pay button),
577    /// with the value used as the start parameter.
578    pub fn with_start_parameter<T>(mut self, value: T) -> Self
579    where
580        T: Into<String>,
581    {
582        self.start_parameter = Some(value.into());
583        self
584    }
585}
586
587impl Method for SendInvoice {
588    type Response = Message;
589
590    fn into_payload(self) -> Payload {
591        Payload::json("sendInvoice", self)
592    }
593}