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}
86
87/// Builder for the [`sendInvoice`](https://core.telegram.org/bots/api#sendinvoice) method.
88pub struct SendInvoice {
89    client: BotClient,
90    params: SendInvoiceParams,
91}
92
93impl SendInvoice {
94    pub(crate) fn new(
95        client: BotClient,
96        chat_id: impl Into<ChatId>,
97        title: impl Into<String>,
98        description: impl Into<String>,
99        payload: impl Into<String>,
100        currency: impl Into<String>,
101        prices: Vec<LabeledPrice>,
102    ) -> Self {
103        Self {
104            client,
105            params: SendInvoiceParams {
106                chat_id: chat_id.into(),
107                title: title.into(),
108                description: description.into(),
109                payload: payload.into(),
110                currency: currency.into(),
111                prices,
112                message_thread_id: None,
113                direct_messages_topic_id: None,
114                provider_token: None,
115                max_tip_amount: None,
116                suggested_tip_amounts: None,
117                start_parameter: None,
118                provider_data: None,
119                photo_url: None,
120                photo_size: None,
121                photo_width: None,
122                photo_height: None,
123                need_name: None,
124                need_phone_number: None,
125                need_email: None,
126                need_shipping_address: None,
127                send_phone_number_to_provider: None,
128                send_email_to_provider: None,
129                is_flexible: None,
130                disable_notification: None,
131                protect_content: None,
132                allow_paid_broadcast: None,
133                reply_parameters: None,
134                reply_markup: None,
135                suggested_post_parameters: None,
136            },
137        }
138    }
139    /// Forum topic thread ID.
140    pub fn message_thread_id(mut self, id: i64) -> Self {
141        self.params.message_thread_id = Some(id);
142        self
143    }
144    /// Identifier of a direct messages chat topic.
145    pub fn direct_messages_topic_id(mut self, id: i64) -> Self {
146        self.params.direct_messages_topic_id = Some(id);
147        self
148    }
149    /// Sets the payment provider token. Not required for Telegram Stars (`XTR`).
150    pub fn provider_token(mut self, t: impl Into<String>) -> Self {
151        self.params.provider_token = Some(t.into());
152        self
153    }
154    /// Requests the buyer's full name during checkout.
155    pub fn need_name(mut self, v: bool) -> Self {
156        self.params.need_name = Some(v);
157        self
158    }
159    /// Requests the buyer's shipping address during checkout.
160    pub fn need_shipping_address(mut self, v: bool) -> Self {
161        self.params.need_shipping_address = Some(v);
162        self
163    }
164    /// Indicates that the final price depends on the shipping method.
165    pub fn is_flexible(mut self, v: bool) -> Self {
166        self.params.is_flexible = Some(v);
167        self
168    }
169    /// Attaches a reply markup (inline keyboard, reply keyboard, etc.).
170    pub fn reply_markup(mut self, m: rustigram_types::keyboard::InlineKeyboardMarkup) -> Self {
171        self.params.reply_markup = Some(m);
172        self
173    }
174    /// Suggested post parameters for channel direct messages chats.
175    pub fn suggested_post_parameters(mut self, params: SuggestedPostParameters) -> Self {
176        self.params.suggested_post_parameters = Some(params);
177        self
178    }
179}
180
181impl_into_future!(SendInvoice, Message, "sendInvoice");
182
183// ─── createInvoiceLink ────────────────────────────────────────────────────────
184
185#[derive(Serialize)]
186struct CreateInvoiceLinkParams {
187    title: String,
188    description: String,
189    payload: String,
190    currency: String,
191    prices: Vec<LabeledPrice>,
192    #[serde(skip_serializing_if = "Option::is_none")]
193    business_connection_id: Option<String>,
194    #[serde(skip_serializing_if = "Option::is_none")]
195    provider_token: Option<String>,
196    #[serde(skip_serializing_if = "Option::is_none")]
197    subscription_period: Option<i64>,
198    #[serde(skip_serializing_if = "Option::is_none")]
199    max_tip_amount: Option<i64>,
200    #[serde(skip_serializing_if = "Option::is_none")]
201    suggested_tip_amounts: Option<Vec<i64>>,
202    #[serde(skip_serializing_if = "Option::is_none")]
203    provider_data: Option<String>,
204    #[serde(skip_serializing_if = "Option::is_none")]
205    photo_url: Option<String>,
206    #[serde(skip_serializing_if = "Option::is_none")]
207    photo_size: Option<i64>,
208    #[serde(skip_serializing_if = "Option::is_none")]
209    photo_width: Option<i64>,
210    #[serde(skip_serializing_if = "Option::is_none")]
211    photo_height: Option<i64>,
212    #[serde(skip_serializing_if = "Option::is_none")]
213    need_name: Option<bool>,
214    #[serde(skip_serializing_if = "Option::is_none")]
215    need_phone_number: Option<bool>,
216    #[serde(skip_serializing_if = "Option::is_none")]
217    need_email: Option<bool>,
218    #[serde(skip_serializing_if = "Option::is_none")]
219    need_shipping_address: Option<bool>,
220    #[serde(skip_serializing_if = "Option::is_none")]
221    send_phone_number_to_provider: Option<bool>,
222    #[serde(skip_serializing_if = "Option::is_none")]
223    send_email_to_provider: Option<bool>,
224    #[serde(skip_serializing_if = "Option::is_none")]
225    is_flexible: Option<bool>,
226}
227
228/// Builder for the [`createInvoiceLink`](https://core.telegram.org/bots/api#createinvoicelink) method.
229///
230/// Creates a shareable payment link. Returns the link as a `String`.
231pub struct CreateInvoiceLink {
232    client: BotClient,
233    params: CreateInvoiceLinkParams,
234}
235
236impl CreateInvoiceLink {
237    pub(crate) fn new(
238        client: BotClient,
239        title: impl Into<String>,
240        description: impl Into<String>,
241        payload: impl Into<String>,
242        currency: impl Into<String>,
243        prices: Vec<LabeledPrice>,
244    ) -> Self {
245        Self {
246            client,
247            params: CreateInvoiceLinkParams {
248                title: title.into(),
249                description: description.into(),
250                payload: payload.into(),
251                currency: currency.into(),
252                prices,
253                business_connection_id: None,
254                provider_token: None,
255                subscription_period: None,
256                max_tip_amount: None,
257                suggested_tip_amounts: None,
258                provider_data: None,
259                photo_url: None,
260                photo_size: None,
261                photo_width: None,
262                photo_height: None,
263                need_name: None,
264                need_phone_number: None,
265                need_email: None,
266                need_shipping_address: None,
267                send_phone_number_to_provider: None,
268                send_email_to_provider: None,
269                is_flexible: None,
270            },
271        }
272    }
273    /// Business connection ID; for Telegram Stars payments only.
274    pub fn business_connection_id(mut self, id: impl Into<String>) -> Self {
275        self.params.business_connection_id = Some(id.into());
276        self
277    }
278    /// Sets the payment provider token. Not required for Telegram Stars (`XTR`).
279    pub fn provider_token(mut self, t: impl Into<String>) -> Self {
280        self.params.provider_token = Some(t.into());
281        self
282    }
283    /// The number of seconds the subscription will be active before the next payment.
284    /// Currently must always be `2592000` (30 days) if specified.
285    pub fn subscription_period(mut self, secs: i64) -> Self {
286        self.params.subscription_period = Some(secs);
287        self
288    }
289    /// Sets the maximum accepted tip amount in the smallest currency units.
290    pub fn max_tip_amount(mut self, v: i64) -> Self {
291        self.params.max_tip_amount = Some(v);
292        self
293    }
294    /// Sets the URL of the product photo.
295    pub fn photo_url(mut self, url: impl Into<String>) -> Self {
296        self.params.photo_url = Some(url.into());
297        self
298    }
299    /// Requests the buyer's full name during checkout.
300    pub fn need_name(mut self, v: bool) -> Self {
301        self.params.need_name = Some(v);
302        self
303    }
304    /// Requests the buyer's phone number during checkout.
305    pub fn need_phone_number(mut self, v: bool) -> Self {
306        self.params.need_phone_number = Some(v);
307        self
308    }
309    /// Requests the buyer's email address during checkout.
310    pub fn need_email(mut self, v: bool) -> Self {
311        self.params.need_email = Some(v);
312        self
313    }
314    /// Requests the buyer's shipping address during checkout.
315    pub fn need_shipping_address(mut self, v: bool) -> Self {
316        self.params.need_shipping_address = Some(v);
317        self
318    }
319    /// Forwards the buyer's phone number to the provider.
320    pub fn send_phone_number_to_provider(mut self, v: bool) -> Self {
321        self.params.send_phone_number_to_provider = Some(v);
322        self
323    }
324    /// Forwards the buyer's email address to the provider.
325    pub fn send_email_to_provider(mut self, v: bool) -> Self {
326        self.params.send_email_to_provider = Some(v);
327        self
328    }
329    /// Indicates that the final price depends on the shipping method.
330    pub fn is_flexible(mut self, v: bool) -> Self {
331        self.params.is_flexible = Some(v);
332        self
333    }
334}
335
336impl_into_future!(CreateInvoiceLink, String, "createInvoiceLink");
337
338// ─── answerShippingQuery ──────────────────────────────────────────────────────
339
340#[derive(Serialize)]
341struct AnswerShippingQueryParams {
342    shipping_query_id: String,
343    ok: bool,
344    #[serde(skip_serializing_if = "Option::is_none")]
345    shipping_options: Option<Vec<ShippingOption>>,
346    #[serde(skip_serializing_if = "Option::is_none")]
347    error_message: Option<String>,
348}
349
350/// Builder for the [`answerShippingQuery`](https://core.telegram.org/bots/api#answershippingquery) method.
351///
352/// Respond to a shipping query from a user. Must be called when the invoice
353/// has `is_flexible = true`.
354pub struct AnswerShippingQuery {
355    client: BotClient,
356    params: AnswerShippingQueryParams,
357}
358
359impl AnswerShippingQuery {
360    pub(crate) fn new(client: BotClient, shipping_query_id: impl Into<String>, ok: bool) -> Self {
361        Self {
362            client,
363            params: AnswerShippingQueryParams {
364                shipping_query_id: shipping_query_id.into(),
365                ok,
366                shipping_options: None,
367                error_message: None,
368            },
369        }
370    }
371    /// Required when `ok = true` — the available shipping options to present to the user.
372    pub fn shipping_options(mut self, opts: Vec<ShippingOption>) -> Self {
373        self.params.shipping_options = Some(opts);
374        self
375    }
376    /// Required when `ok = false` — a human-readable reason why delivery is not possible.
377    pub fn error_message(mut self, msg: impl Into<String>) -> Self {
378        self.params.error_message = Some(msg.into());
379        self
380    }
381}
382
383impl_into_future!(AnswerShippingQuery, bool, "answerShippingQuery");
384
385// ─── answerPreCheckoutQuery ───────────────────────────────────────────────────
386
387#[derive(Serialize)]
388struct AnswerPreCheckoutQueryParams {
389    pre_checkout_query_id: String,
390    ok: bool,
391    #[serde(skip_serializing_if = "Option::is_none")]
392    error_message: Option<String>,
393}
394
395/// Builder for the [`answerPreCheckoutQuery`](https://core.telegram.org/bots/api#answerprecheckoutquery) method.
396///
397/// Confirm or reject a pre-checkout query. Must be called within **10 seconds**
398/// of receiving the query — no exceptions.
399pub struct AnswerPreCheckoutQuery {
400    client: BotClient,
401    params: AnswerPreCheckoutQueryParams,
402}
403
404impl AnswerPreCheckoutQuery {
405    pub(crate) fn new(
406        client: BotClient,
407        pre_checkout_query_id: impl Into<String>,
408        ok: bool,
409    ) -> Self {
410        Self {
411            client,
412            params: AnswerPreCheckoutQueryParams {
413                pre_checkout_query_id: pre_checkout_query_id.into(),
414                ok,
415                error_message: None,
416            },
417        }
418    }
419    /// Required when `ok = false` — a human-readable reason for the failure
420    /// displayed to the user (e.g. `"Sorry, the item is out of stock"`).
421    pub fn error_message(mut self, msg: impl Into<String>) -> Self {
422        self.params.error_message = Some(msg.into());
423        self
424    }
425}
426
427impl_into_future!(AnswerPreCheckoutQuery, bool, "answerPreCheckoutQuery");
428
429// ─── refundStarPayment ────────────────────────────────────────────────────────
430
431#[derive(Serialize)]
432struct RefundStarPaymentParams {
433    user_id: i64,
434    telegram_payment_charge_id: String,
435}
436
437/// Builder for the [`refundStarPayment`](https://core.telegram.org/bots/api#refundstarpayment) method.
438///
439/// Refunds a successful Telegram Stars payment to the user.
440pub struct RefundStarPayment {
441    client: BotClient,
442    params: RefundStarPaymentParams,
443}
444
445impl RefundStarPayment {
446    pub(crate) fn new(
447        client: BotClient,
448        user_id: i64,
449        telegram_payment_charge_id: impl Into<String>,
450    ) -> Self {
451        Self {
452            client,
453            params: RefundStarPaymentParams {
454                user_id,
455                telegram_payment_charge_id: telegram_payment_charge_id.into(),
456            },
457        }
458    }
459}
460
461impl_into_future!(RefundStarPayment, bool, "refundStarPayment");
462
463// ─── editUserStarSubscription ─────────────────────────────────────────────────
464
465#[derive(Serialize)]
466struct EditUserStarSubscriptionParams {
467    user_id: i64,
468    telegram_payment_charge_id: String,
469    is_canceled: bool,
470}
471
472/// Builder for the [`editUserStarSubscription`](https://core.telegram.org/bots/api#edituserstarsubscription) method.
473///
474/// Cancels or re-enables a Telegram Stars subscription.
475/// Pass `is_canceled = true` to cancel; `false` to re-enable a previously
476/// cancelled subscription. The subscription must be active until the end of
477/// the current period to be cancelled.
478pub struct EditUserStarSubscription {
479    client: BotClient,
480    params: EditUserStarSubscriptionParams,
481}
482
483impl EditUserStarSubscription {
484    pub(crate) fn new(
485        client: BotClient,
486        user_id: i64,
487        telegram_payment_charge_id: impl Into<String>,
488        is_canceled: bool,
489    ) -> Self {
490        Self {
491            client,
492            params: EditUserStarSubscriptionParams {
493                user_id,
494                telegram_payment_charge_id: telegram_payment_charge_id.into(),
495                is_canceled,
496            },
497        }
498    }
499}
500
501impl_into_future!(EditUserStarSubscription, bool, "editUserStarSubscription");
502
503// ─── getMyStarBalance ─────────────────────────────────────────────────────────
504
505/// Builder for the [`getMyStarBalance`](https://core.telegram.org/bots/api#getmystarbalance) method.
506pub struct GetMyStarBalance {
507    client: BotClient,
508}
509
510impl GetMyStarBalance {
511    pub(crate) fn new(client: BotClient) -> Self {
512        Self { client }
513    }
514}
515
516impl IntoFuture for GetMyStarBalance {
517    type Output = Result<StarAmount>;
518    type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
519    fn into_future(self) -> Self::IntoFuture {
520        Box::pin(async move {
521            self.client
522                .post_json("getMyStarBalance", &serde_json::json!({}))
523                .await
524        })
525    }
526}
527
528// ─── getStarTransactions ──────────────────────────────────────────────────────
529
530#[derive(Serialize, Default)]
531struct GetStarTransactionsParams {
532    #[serde(skip_serializing_if = "Option::is_none")]
533    offset: Option<u32>,
534    #[serde(skip_serializing_if = "Option::is_none")]
535    limit: Option<u32>,
536}
537
538/// Builder for the [`getStarTransactions`](https://core.telegram.org/bots/api#getstartransactions) method.
539pub struct GetStarTransactions {
540    client: BotClient,
541    params: GetStarTransactionsParams,
542}
543
544impl GetStarTransactions {
545    pub(crate) fn new(client: BotClient) -> Self {
546        Self {
547            client,
548            params: Default::default(),
549        }
550    }
551    /// Skips the first N transactions in the result.
552    pub fn offset(mut self, v: u32) -> Self {
553        self.params.offset = Some(v);
554        self
555    }
556    /// Limits the number of transactions returned (1–100, default 100).
557    pub fn limit(mut self, v: u32) -> Self {
558        self.params.limit = Some(v);
559        self
560    }
561}
562
563impl_into_future!(GetStarTransactions, StarTransactions, "getStarTransactions");