Skip to main content

rustigram_api/methods/
payments.rs

1use crate::client::BotClient;
2use crate::error::Result;
3use rustigram_types::message::{Message, ReplyParameters};
4use rustigram_types::payments::{LabeledPrice, ShippingOption, StarAmount, StarTransactions};
5use rustigram_types::suggested_post::SuggestedPostParameters;
6use rustigram_types::user::ChatId;
7use serde::Serialize;
8use std::future::{Future, IntoFuture};
9use std::pin::Pin;
10
11// ─── Helper macro ─────────────────────────────────────────────────────────────
12
13/// Generates an `IntoFuture` impl that calls `BotClient::post_json`.
14macro_rules! impl_into_future {
15    ($builder:ident, $return_ty:ty, $method:literal) => {
16        impl IntoFuture for $builder {
17            type Output = Result<$return_ty>;
18            type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
19
20            fn into_future(self) -> Self::IntoFuture {
21                Box::pin(async move { self.client.post_json($method, &self.params).await })
22            }
23        }
24    };
25}
26
27// ─── sendInvoice ──────────────────────────────────────────────────────────────
28
29#[derive(Serialize)]
30struct SendInvoiceParams {
31    chat_id: ChatId,
32    title: String,
33    description: String,
34    payload: String,
35    currency: String,
36    prices: Vec<LabeledPrice>,
37    #[serde(skip_serializing_if = "Option::is_none")]
38    message_thread_id: Option<i64>,
39    #[serde(skip_serializing_if = "Option::is_none")]
40    direct_messages_topic_id: Option<i64>,
41    #[serde(skip_serializing_if = "Option::is_none")]
42    provider_token: Option<String>,
43    #[serde(skip_serializing_if = "Option::is_none")]
44    max_tip_amount: Option<u32>,
45    #[serde(skip_serializing_if = "Option::is_none")]
46    suggested_tip_amounts: Option<Vec<u32>>,
47    #[serde(skip_serializing_if = "Option::is_none")]
48    start_parameter: Option<String>,
49    #[serde(skip_serializing_if = "Option::is_none")]
50    provider_data: Option<String>,
51    #[serde(skip_serializing_if = "Option::is_none")]
52    photo_url: Option<String>,
53    #[serde(skip_serializing_if = "Option::is_none")]
54    photo_size: Option<u32>,
55    #[serde(skip_serializing_if = "Option::is_none")]
56    photo_width: Option<u32>,
57    #[serde(skip_serializing_if = "Option::is_none")]
58    photo_height: Option<u32>,
59    #[serde(skip_serializing_if = "Option::is_none")]
60    need_name: Option<bool>,
61    #[serde(skip_serializing_if = "Option::is_none")]
62    need_phone_number: Option<bool>,
63    #[serde(skip_serializing_if = "Option::is_none")]
64    need_email: Option<bool>,
65    #[serde(skip_serializing_if = "Option::is_none")]
66    need_shipping_address: Option<bool>,
67    #[serde(skip_serializing_if = "Option::is_none")]
68    send_phone_number_to_provider: Option<bool>,
69    #[serde(skip_serializing_if = "Option::is_none")]
70    send_email_to_provider: Option<bool>,
71    #[serde(skip_serializing_if = "Option::is_none")]
72    is_flexible: Option<bool>,
73    #[serde(skip_serializing_if = "Option::is_none")]
74    disable_notification: Option<bool>,
75    #[serde(skip_serializing_if = "Option::is_none")]
76    protect_content: Option<bool>,
77    #[serde(skip_serializing_if = "Option::is_none")]
78    allow_paid_broadcast: Option<bool>,
79    #[serde(skip_serializing_if = "Option::is_none")]
80    reply_parameters: Option<ReplyParameters>,
81    #[serde(skip_serializing_if = "Option::is_none")]
82    reply_markup: Option<rustigram_types::keyboard::InlineKeyboardMarkup>,
83    #[serde(skip_serializing_if = "Option::is_none")]
84    suggested_post_parameters: Option<SuggestedPostParameters>,
85    #[serde(skip_serializing_if = "Option::is_none")]
86    message_effect_id: Option<String>,
87}
88
89/// Builder for the [`sendInvoice`](https://core.telegram.org/bots/api#sendinvoice) method.
90pub struct SendInvoice {
91    client: BotClient,
92    params: SendInvoiceParams,
93}
94
95impl SendInvoice {
96    pub(crate) fn new(
97        client: BotClient,
98        chat_id: impl Into<ChatId>,
99        title: impl Into<String>,
100        description: impl Into<String>,
101        payload: impl Into<String>,
102        currency: impl Into<String>,
103        prices: Vec<LabeledPrice>,
104    ) -> Self {
105        Self {
106            client,
107            params: SendInvoiceParams {
108                chat_id: chat_id.into(),
109                title: title.into(),
110                description: description.into(),
111                payload: payload.into(),
112                currency: currency.into(),
113                prices,
114                message_thread_id: None,
115                direct_messages_topic_id: None,
116                provider_token: None,
117                max_tip_amount: None,
118                suggested_tip_amounts: None,
119                start_parameter: None,
120                provider_data: None,
121                photo_url: None,
122                photo_size: None,
123                photo_width: None,
124                photo_height: None,
125                need_name: None,
126                need_phone_number: None,
127                need_email: None,
128                need_shipping_address: None,
129                send_phone_number_to_provider: None,
130                send_email_to_provider: None,
131                is_flexible: None,
132                disable_notification: None,
133                protect_content: None,
134                allow_paid_broadcast: None,
135                reply_parameters: None,
136                reply_markup: None,
137                suggested_post_parameters: None,
138                message_effect_id: None,
139            },
140        }
141    }
142    /// Forum topic thread ID.
143    pub fn message_thread_id(mut self, id: i64) -> Self {
144        self.params.message_thread_id = Some(id);
145        self
146    }
147    /// Identifier of a direct messages chat topic.
148    pub fn direct_messages_topic_id(mut self, id: i64) -> Self {
149        self.params.direct_messages_topic_id = Some(id);
150        self
151    }
152    /// Sets the payment provider token. Not required for Telegram Stars (`XTR`).
153    pub fn provider_token(mut self, t: impl Into<String>) -> Self {
154        self.params.provider_token = Some(t.into());
155        self
156    }
157    /// Requests the buyer's full name during checkout.
158    pub fn need_name(mut self, v: bool) -> Self {
159        self.params.need_name = Some(v);
160        self
161    }
162    /// Requests the buyer's shipping address during checkout.
163    pub fn need_shipping_address(mut self, v: bool) -> Self {
164        self.params.need_shipping_address = Some(v);
165        self
166    }
167    /// Indicates that the final price depends on the shipping method.
168    pub fn is_flexible(mut self, v: bool) -> Self {
169        self.params.is_flexible = Some(v);
170        self
171    }
172    /// Attaches a reply markup (inline keyboard, reply keyboard, etc.).
173    pub fn reply_markup(mut self, m: rustigram_types::keyboard::InlineKeyboardMarkup) -> Self {
174        self.params.reply_markup = Some(m);
175        self
176    }
177    /// Suggested post parameters for channel direct messages chats.
178    pub fn suggested_post_parameters(mut self, params: SuggestedPostParameters) -> Self {
179        self.params.suggested_post_parameters = Some(params);
180        self
181    }
182    /// Attaches a message effect (animated emoji reaction) to the message.
183    pub fn message_effect_id(mut self, v: impl Into<String>) -> Self {
184        self.params.message_effect_id = Some(v.into());
185        self
186    }
187    /// Sets `max_tip_amount`.
188    pub fn max_tip_amount(mut self, v: u32) -> Self {
189        self.params.max_tip_amount = Some(v);
190        self
191    }
192    /// Sets `suggested_tip_amounts`.
193    pub fn suggested_tip_amounts(mut self, v: Vec<u32>) -> Self {
194        self.params.suggested_tip_amounts = Some(v);
195        self
196    }
197    /// Sets `start_parameter`.
198    pub fn start_parameter(mut self, v: impl Into<String>) -> Self {
199        self.params.start_parameter = Some(v.into());
200        self
201    }
202    /// Sets `provider_data`.
203    pub fn provider_data(mut self, v: impl Into<String>) -> Self {
204        self.params.provider_data = Some(v.into());
205        self
206    }
207    /// Sets `photo_url`.
208    pub fn photo_url(mut self, v: impl Into<String>) -> Self {
209        self.params.photo_url = Some(v.into());
210        self
211    }
212    /// Sets `photo_size`.
213    pub fn photo_size(mut self, v: u32) -> Self {
214        self.params.photo_size = Some(v);
215        self
216    }
217    /// Sets `photo_width`.
218    pub fn photo_width(mut self, v: u32) -> Self {
219        self.params.photo_width = Some(v);
220        self
221    }
222    /// Sets `photo_height`.
223    pub fn photo_height(mut self, v: u32) -> Self {
224        self.params.photo_height = Some(v);
225        self
226    }
227    /// Sets `need_phone_number`.
228    pub fn need_phone_number(mut self, v: bool) -> Self {
229        self.params.need_phone_number = Some(v);
230        self
231    }
232    /// Sets `need_email`.
233    pub fn need_email(mut self, v: bool) -> Self {
234        self.params.need_email = Some(v);
235        self
236    }
237    /// Sets `send_phone_number_to_provider`.
238    pub fn send_phone_number_to_provider(mut self, v: bool) -> Self {
239        self.params.send_phone_number_to_provider = Some(v);
240        self
241    }
242    /// Sets `send_email_to_provider`.
243    pub fn send_email_to_provider(mut self, v: bool) -> Self {
244        self.params.send_email_to_provider = Some(v);
245        self
246    }
247    /// Sends the message silently — the recipient receives no notification sound.
248    pub fn disable_notification(mut self, v: bool) -> Self {
249        self.params.disable_notification = Some(v);
250        self
251    }
252    /// Protects the message from being forwarded or saved.
253    pub fn protect_content(mut self, v: bool) -> Self {
254        self.params.protect_content = Some(v);
255        self
256    }
257    /// Allows sending to large audiences at the cost of Telegram Stars.
258    pub fn allow_paid_broadcast(mut self, v: bool) -> Self {
259        self.params.allow_paid_broadcast = Some(v);
260        self
261    }
262    /// Reply parameters for this message.
263    pub fn reply_parameters(mut self, v: ReplyParameters) -> Self {
264        self.params.reply_parameters = Some(v);
265        self
266    }
267}
268
269impl_into_future!(SendInvoice, Message, "sendInvoice");
270
271// ─── createInvoiceLink ────────────────────────────────────────────────────────
272
273#[derive(Serialize)]
274struct CreateInvoiceLinkParams {
275    title: String,
276    description: String,
277    payload: String,
278    currency: String,
279    prices: Vec<LabeledPrice>,
280    #[serde(skip_serializing_if = "Option::is_none")]
281    business_connection_id: Option<String>,
282    #[serde(skip_serializing_if = "Option::is_none")]
283    provider_token: Option<String>,
284    #[serde(skip_serializing_if = "Option::is_none")]
285    subscription_period: Option<i64>,
286    #[serde(skip_serializing_if = "Option::is_none")]
287    max_tip_amount: Option<i64>,
288    #[serde(skip_serializing_if = "Option::is_none")]
289    suggested_tip_amounts: Option<Vec<i64>>,
290    #[serde(skip_serializing_if = "Option::is_none")]
291    provider_data: Option<String>,
292    #[serde(skip_serializing_if = "Option::is_none")]
293    photo_url: Option<String>,
294    #[serde(skip_serializing_if = "Option::is_none")]
295    photo_size: Option<i64>,
296    #[serde(skip_serializing_if = "Option::is_none")]
297    photo_width: Option<i64>,
298    #[serde(skip_serializing_if = "Option::is_none")]
299    photo_height: Option<i64>,
300    #[serde(skip_serializing_if = "Option::is_none")]
301    need_name: Option<bool>,
302    #[serde(skip_serializing_if = "Option::is_none")]
303    need_phone_number: Option<bool>,
304    #[serde(skip_serializing_if = "Option::is_none")]
305    need_email: Option<bool>,
306    #[serde(skip_serializing_if = "Option::is_none")]
307    need_shipping_address: Option<bool>,
308    #[serde(skip_serializing_if = "Option::is_none")]
309    send_phone_number_to_provider: Option<bool>,
310    #[serde(skip_serializing_if = "Option::is_none")]
311    send_email_to_provider: Option<bool>,
312    #[serde(skip_serializing_if = "Option::is_none")]
313    is_flexible: Option<bool>,
314}
315
316/// Builder for the [`createInvoiceLink`](https://core.telegram.org/bots/api#createinvoicelink) method.
317///
318/// Creates a shareable payment link. Returns the link as a `String`.
319pub struct CreateInvoiceLink {
320    client: BotClient,
321    params: CreateInvoiceLinkParams,
322}
323
324impl CreateInvoiceLink {
325    pub(crate) fn new(
326        client: BotClient,
327        title: impl Into<String>,
328        description: impl Into<String>,
329        payload: impl Into<String>,
330        currency: impl Into<String>,
331        prices: Vec<LabeledPrice>,
332    ) -> Self {
333        Self {
334            client,
335            params: CreateInvoiceLinkParams {
336                title: title.into(),
337                description: description.into(),
338                payload: payload.into(),
339                currency: currency.into(),
340                prices,
341                business_connection_id: None,
342                provider_token: None,
343                subscription_period: None,
344                max_tip_amount: None,
345                suggested_tip_amounts: None,
346                provider_data: None,
347                photo_url: None,
348                photo_size: None,
349                photo_width: None,
350                photo_height: None,
351                need_name: None,
352                need_phone_number: None,
353                need_email: None,
354                need_shipping_address: None,
355                send_phone_number_to_provider: None,
356                send_email_to_provider: None,
357                is_flexible: None,
358            },
359        }
360    }
361    /// Business connection ID; for Telegram Stars payments only.
362    pub fn business_connection_id(mut self, id: impl Into<String>) -> Self {
363        self.params.business_connection_id = Some(id.into());
364        self
365    }
366    /// Sets the payment provider token. Not required for Telegram Stars (`XTR`).
367    pub fn provider_token(mut self, t: impl Into<String>) -> Self {
368        self.params.provider_token = Some(t.into());
369        self
370    }
371    /// The number of seconds the subscription will be active before the next payment.
372    /// Currently must always be `2592000` (30 days) if specified.
373    pub fn subscription_period(mut self, secs: i64) -> Self {
374        self.params.subscription_period = Some(secs);
375        self
376    }
377    /// Sets the maximum accepted tip amount in the smallest currency units.
378    pub fn max_tip_amount(mut self, v: i64) -> Self {
379        self.params.max_tip_amount = Some(v);
380        self
381    }
382    /// Sets the URL of the product photo.
383    pub fn photo_url(mut self, url: impl Into<String>) -> Self {
384        self.params.photo_url = Some(url.into());
385        self
386    }
387    /// Requests the buyer's full name during checkout.
388    pub fn need_name(mut self, v: bool) -> Self {
389        self.params.need_name = Some(v);
390        self
391    }
392    /// Requests the buyer's phone number during checkout.
393    pub fn need_phone_number(mut self, v: bool) -> Self {
394        self.params.need_phone_number = Some(v);
395        self
396    }
397    /// Requests the buyer's email address during checkout.
398    pub fn need_email(mut self, v: bool) -> Self {
399        self.params.need_email = Some(v);
400        self
401    }
402    /// Requests the buyer's shipping address during checkout.
403    pub fn need_shipping_address(mut self, v: bool) -> Self {
404        self.params.need_shipping_address = Some(v);
405        self
406    }
407    /// Forwards the buyer's phone number to the provider.
408    pub fn send_phone_number_to_provider(mut self, v: bool) -> Self {
409        self.params.send_phone_number_to_provider = Some(v);
410        self
411    }
412    /// Forwards the buyer's email address to the provider.
413    pub fn send_email_to_provider(mut self, v: bool) -> Self {
414        self.params.send_email_to_provider = Some(v);
415        self
416    }
417    /// Indicates that the final price depends on the shipping method.
418    pub fn is_flexible(mut self, v: bool) -> Self {
419        self.params.is_flexible = Some(v);
420        self
421    }
422    /// Sets `suggested_tip_amounts`.
423    pub fn suggested_tip_amounts(mut self, v: Vec<i64>) -> Self {
424        self.params.suggested_tip_amounts = Some(v);
425        self
426    }
427    /// Sets `provider_data`.
428    pub fn provider_data(mut self, v: impl Into<String>) -> Self {
429        self.params.provider_data = Some(v.into());
430        self
431    }
432    /// Sets `photo_size`.
433    pub fn photo_size(mut self, v: i64) -> Self {
434        self.params.photo_size = Some(v);
435        self
436    }
437    /// Sets `photo_width`.
438    pub fn photo_width(mut self, v: i64) -> Self {
439        self.params.photo_width = Some(v);
440        self
441    }
442    /// Sets `photo_height`.
443    pub fn photo_height(mut self, v: i64) -> Self {
444        self.params.photo_height = Some(v);
445        self
446    }
447}
448
449impl_into_future!(CreateInvoiceLink, String, "createInvoiceLink");
450
451// ─── answerShippingQuery ──────────────────────────────────────────────────────
452
453#[derive(Serialize)]
454struct AnswerShippingQueryParams {
455    shipping_query_id: String,
456    ok: bool,
457    #[serde(skip_serializing_if = "Option::is_none")]
458    shipping_options: Option<Vec<ShippingOption>>,
459    #[serde(skip_serializing_if = "Option::is_none")]
460    error_message: Option<String>,
461}
462
463/// Builder for the [`answerShippingQuery`](https://core.telegram.org/bots/api#answershippingquery) method.
464///
465/// Respond to a shipping query from a user. Must be called when the invoice
466/// has `is_flexible = true`.
467pub struct AnswerShippingQuery {
468    client: BotClient,
469    params: AnswerShippingQueryParams,
470}
471
472impl AnswerShippingQuery {
473    pub(crate) fn new(client: BotClient, shipping_query_id: impl Into<String>, ok: bool) -> Self {
474        Self {
475            client,
476            params: AnswerShippingQueryParams {
477                shipping_query_id: shipping_query_id.into(),
478                ok,
479                shipping_options: None,
480                error_message: None,
481            },
482        }
483    }
484    /// Required when `ok = true` — the available shipping options to present to the user.
485    pub fn shipping_options(mut self, opts: Vec<ShippingOption>) -> Self {
486        self.params.shipping_options = Some(opts);
487        self
488    }
489    /// Required when `ok = false` — a human-readable reason why delivery is not possible.
490    pub fn error_message(mut self, msg: impl Into<String>) -> Self {
491        self.params.error_message = Some(msg.into());
492        self
493    }
494}
495
496impl_into_future!(AnswerShippingQuery, bool, "answerShippingQuery");
497
498// ─── answerPreCheckoutQuery ───────────────────────────────────────────────────
499
500#[derive(Serialize)]
501struct AnswerPreCheckoutQueryParams {
502    pre_checkout_query_id: String,
503    ok: bool,
504    #[serde(skip_serializing_if = "Option::is_none")]
505    error_message: Option<String>,
506}
507
508/// Builder for the [`answerPreCheckoutQuery`](https://core.telegram.org/bots/api#answerprecheckoutquery) method.
509///
510/// Confirm or reject a pre-checkout query. Must be called within **10 seconds**
511/// of receiving the query — no exceptions.
512pub struct AnswerPreCheckoutQuery {
513    client: BotClient,
514    params: AnswerPreCheckoutQueryParams,
515}
516
517impl AnswerPreCheckoutQuery {
518    pub(crate) fn new(
519        client: BotClient,
520        pre_checkout_query_id: impl Into<String>,
521        ok: bool,
522    ) -> Self {
523        Self {
524            client,
525            params: AnswerPreCheckoutQueryParams {
526                pre_checkout_query_id: pre_checkout_query_id.into(),
527                ok,
528                error_message: None,
529            },
530        }
531    }
532    /// Required when `ok = false` — a human-readable reason for the failure
533    /// displayed to the user (e.g. `"Sorry, the item is out of stock"`).
534    pub fn error_message(mut self, msg: impl Into<String>) -> Self {
535        self.params.error_message = Some(msg.into());
536        self
537    }
538}
539
540impl_into_future!(AnswerPreCheckoutQuery, bool, "answerPreCheckoutQuery");
541
542// ─── refundStarPayment ────────────────────────────────────────────────────────
543
544#[derive(Serialize)]
545struct RefundStarPaymentParams {
546    user_id: i64,
547    telegram_payment_charge_id: String,
548}
549
550/// Builder for the [`refundStarPayment`](https://core.telegram.org/bots/api#refundstarpayment) method.
551///
552/// Refunds a successful Telegram Stars payment to the user.
553pub struct RefundStarPayment {
554    client: BotClient,
555    params: RefundStarPaymentParams,
556}
557
558impl RefundStarPayment {
559    pub(crate) fn new(
560        client: BotClient,
561        user_id: i64,
562        telegram_payment_charge_id: impl Into<String>,
563    ) -> Self {
564        Self {
565            client,
566            params: RefundStarPaymentParams {
567                user_id,
568                telegram_payment_charge_id: telegram_payment_charge_id.into(),
569            },
570        }
571    }
572}
573
574impl_into_future!(RefundStarPayment, bool, "refundStarPayment");
575
576// ─── editUserStarSubscription ─────────────────────────────────────────────────
577
578#[derive(Serialize)]
579struct EditUserStarSubscriptionParams {
580    user_id: i64,
581    telegram_payment_charge_id: String,
582    is_canceled: bool,
583}
584
585/// Builder for the [`editUserStarSubscription`](https://core.telegram.org/bots/api#edituserstarsubscription) method.
586///
587/// Cancels or re-enables a Telegram Stars subscription.
588/// Pass `is_canceled = true` to cancel; `false` to re-enable a previously
589/// cancelled subscription. The subscription must be active until the end of
590/// the current period to be cancelled.
591pub struct EditUserStarSubscription {
592    client: BotClient,
593    params: EditUserStarSubscriptionParams,
594}
595
596impl EditUserStarSubscription {
597    pub(crate) fn new(
598        client: BotClient,
599        user_id: i64,
600        telegram_payment_charge_id: impl Into<String>,
601        is_canceled: bool,
602    ) -> Self {
603        Self {
604            client,
605            params: EditUserStarSubscriptionParams {
606                user_id,
607                telegram_payment_charge_id: telegram_payment_charge_id.into(),
608                is_canceled,
609            },
610        }
611    }
612}
613
614impl_into_future!(EditUserStarSubscription, bool, "editUserStarSubscription");
615
616// ─── getMyStarBalance ─────────────────────────────────────────────────────────
617
618/// Builder for the [`getMyStarBalance`](https://core.telegram.org/bots/api#getmystarbalance) method.
619pub struct GetMyStarBalance {
620    client: BotClient,
621}
622
623impl GetMyStarBalance {
624    pub(crate) fn new(client: BotClient) -> Self {
625        Self { client }
626    }
627}
628
629impl IntoFuture for GetMyStarBalance {
630    type Output = Result<StarAmount>;
631    type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
632    fn into_future(self) -> Self::IntoFuture {
633        Box::pin(async move {
634            self.client
635                .post_json("getMyStarBalance", &serde_json::json!({}))
636                .await
637        })
638    }
639}
640
641// ─── getStarTransactions ──────────────────────────────────────────────────────
642
643#[derive(Serialize, Default)]
644struct GetStarTransactionsParams {
645    #[serde(skip_serializing_if = "Option::is_none")]
646    offset: Option<u32>,
647    #[serde(skip_serializing_if = "Option::is_none")]
648    limit: Option<u32>,
649}
650
651/// Builder for the [`getStarTransactions`](https://core.telegram.org/bots/api#getstartransactions) method.
652pub struct GetStarTransactions {
653    client: BotClient,
654    params: GetStarTransactionsParams,
655}
656
657impl GetStarTransactions {
658    pub(crate) fn new(client: BotClient) -> Self {
659        Self {
660            client,
661            params: Default::default(),
662        }
663    }
664    /// Skips the first N transactions in the result.
665    pub fn offset(mut self, v: u32) -> Self {
666        self.params.offset = Some(v);
667        self
668    }
669    /// Limits the number of transactions returned (1–100, default 100).
670    pub fn limit(mut self, v: u32) -> Self {
671        self.params.limit = Some(v);
672        self
673    }
674}
675
676impl_into_future!(GetStarTransactions, StarTransactions, "getStarTransactions");