-module(telega@payments). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/telega/payments.gleam"). -export([price/2, stars_invoice/4, invoice/6, with_photo/2, with_tips/3, with_flexible_shipping/1, with_provider_data/2, with_start_parameter/2, with_reply_markup/2, require_name/1, require_phone_number/1, require_email/1, send/2, create_link/2, answer_pre_checkout_ok/2, answer_pre_checkout_error/3, shipping_option/3, answer_shipping_ok/3, answer_shipping_error/3, wait_successful_payment/4]). -export_type([invoice/0]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. ?MODULEDOC( " Helpers for sending invoices and answering payment queries, on top of\n" " the raw Bot API methods in `telega/api`.\n" "\n" " ## Telegram Stars\n" "\n" " [Telegram Stars](https://core.telegram.org/bots/payments-stars) (`XTR`)\n" " are the first-class case: digital goods and services must be sold in\n" " Stars, no payment provider is required, and the invoice has a single\n" " price.\n" "\n" " ```gleam\n" " import telega/payments\n" "\n" " fn buy_handler(ctx, _command) {\n" " let assert Ok(_) =\n" " payments.stars_invoice(\n" " title: \"Premium\",\n" " description: \"Premium access for a month\",\n" " payload: \"premium:1m\",\n" " amount: 100,\n" " )\n" " |> payments.send(ctx)\n" " Ok(ctx)\n" " }\n" " ```\n" "\n" " `payload` is not shown to the user — it comes back in the pre-checkout\n" " query and in the successful payment message, so put your order identifier\n" " there.\n" "\n" " ## Regular currencies\n" "\n" " For physical goods, pass an ISO 4217 currency, a provider token from\n" " [@BotFather](https://t.me/BotFather), and a price breakdown in the\n" " smallest units of the currency:\n" "\n" " ```gleam\n" " payments.invoice(\n" " title: \"Order #42\",\n" " description: \"2 pizzas\",\n" " payload: \"order:42\",\n" " currency: \"USD\",\n" " provider_token: provider_token,\n" " prices: [\n" " payments.price(\"2x Pepperoni\", 2490),\n" " payments.price(\"Delivery\", 500),\n" " ],\n" " )\n" " |> payments.with_photo(\"https://example.com/pizza.jpg\")\n" " |> payments.require_email()\n" " |> payments.send(ctx)\n" " ```\n" "\n" " Other builder options: `with_tips`, `with_flexible_shipping`,\n" " `with_provider_data`, `with_start_parameter`, `with_reply_markup`,\n" " `require_name`, `require_phone_number`. `create_link` builds a shareable\n" " payment URL instead of sending a message.\n" "\n" " ## Answering the pre-checkout query\n" "\n" " After the user confirms the payment, Telegram sends a pre-checkout query\n" " that **must be answered within 10 seconds**, otherwise the payment fails:\n" "\n" " ```gleam\n" " import telega/router\n" "\n" " router.new(\"shop\")\n" " |> router.on_pre_checkout_query(fn(ctx, query) {\n" " let assert Ok(_) = case in_stock(query.invoice_payload) {\n" " True -> payments.answer_pre_checkout_ok(ctx, query)\n" " False -> payments.answer_pre_checkout_error(ctx, query, \"Out of stock\")\n" " }\n" " Ok(ctx)\n" " })\n" " ```\n" "\n" " ## Shipping queries\n" "\n" " For invoices created with `with_flexible_shipping`, Telegram asks the bot\n" " for shipping options once the user fills in an address:\n" "\n" " ```gleam\n" " router.on_shipping_query(router, fn(ctx, query) {\n" " let assert Ok(_) = case ships_to(query.shipping_address) {\n" " True ->\n" " payments.answer_shipping_ok(ctx, query, [\n" " payments.shipping_option(id: \"dhl\", title: \"DHL\", prices: [\n" " payments.price(\"Shipping\", 500),\n" " ]),\n" " ])\n" " False -> payments.answer_shipping_error(ctx, query, \"No delivery there\")\n" " }\n" " Ok(ctx)\n" " })\n" " ```\n" "\n" " ## Waiting for the payment in a conversation\n" "\n" " `wait_successful_payment` pauses the handler until the successful payment\n" " service message arrives, so the whole purchase reads top-to-bottom (see\n" " the [conversation guide](/docs/conversation.html)):\n" "\n" " ```gleam\n" " fn buy_handler(ctx, _command) {\n" " let assert Ok(_) =\n" " payments.stars_invoice(\n" " title: \"Premium\",\n" " description: \"Premium access for a month\",\n" " payload: \"premium:1m\",\n" " amount: 100,\n" " )\n" " |> payments.send(ctx)\n" "\n" " use ctx, payment <- payments.wait_successful_payment(\n" " ctx,\n" " or: None,\n" " timeout: Some(600_000),\n" " )\n" "\n" " // Store payment.telegram_payment_charge_id — it is needed for refunds\n" " reply.with_text(ctx, \"Thanks! Your order: \" <> payment.invoice_payload)\n" " }\n" " ```\n" "\n" " ## Refunds\n" "\n" " For Telegram Stars, refund through the raw API method with the charge id\n" " from the successful payment:\n" "\n" " ```gleam\n" " import telega/api\n" " import telega/model/types\n" "\n" " api.refund_star_payment(\n" " ctx.config.api_client,\n" " parameters: types.RefundStarPaymentParameters(\n" " user_id: user_id,\n" " telegram_payment_charge_id: charge_id,\n" " ),\n" " )\n" " ```\n" ). -opaque invoice() :: {invoice, binary(), binary(), binary(), binary(), list(telega@model@types:labeled_price()), gleam@option:option(binary()), gleam@option:option(binary()), gleam@option:option(binary()), gleam@option:option(binary()), gleam@option:option(integer()), gleam@option:option(list(integer())), gleam@option:option(boolean()), gleam@option:option(boolean()), gleam@option:option(boolean()), gleam@option:option(boolean()), gleam@option:option(boolean()), gleam@option:option(telega@model@types:inline_keyboard_markup())}. -file("src/telega/payments.gleam", 186). ?DOC( " A labeled portion of the invoice price. `amount` is in the smallest units\n" " of the currency (cents for USD, stars for XTR).\n" ). -spec price(binary(), integer()) -> telega@model@types:labeled_price(). price(Label, Amount) -> {labeled_price, Label, Amount}. -file("src/telega/payments.gleam", 226). -spec new_invoice( binary(), binary(), binary(), binary(), gleam@option:option(binary()), list(telega@model@types:labeled_price()) ) -> invoice(). new_invoice(Title, Description, Payload, Currency, Provider_token, Prices) -> {invoice, Title, Description, Payload, Currency, Prices, Provider_token, none, none, none, none, none, none, none, none, none, none, none}. -file("src/telega/payments.gleam", 191). ?DOC(" Invoice in Telegram Stars: no provider token, single price portion.\n"). -spec stars_invoice(binary(), binary(), binary(), integer()) -> invoice(). stars_invoice(Title, Description, Payload, Amount) -> new_invoice( Title, Description, Payload, <<"XTR"/utf8>>, none, [price(Title, Amount)] ). -file("src/telega/payments.gleam", 208). ?DOC(" Invoice in a regular currency through a payment provider.\n"). -spec invoice( binary(), binary(), binary(), binary(), binary(), list(telega@model@types:labeled_price()) ) -> invoice(). invoice(Title, Description, Payload, Currency, Provider_token, Prices) -> new_invoice( Title, Description, Payload, Currency, {some, Provider_token}, Prices ). -file("src/telega/payments.gleam", 256). ?DOC(" Product photo shown in the invoice.\n"). -spec with_photo(invoice(), binary()) -> invoice(). with_photo(Invoice, Url) -> {invoice, erlang:element(2, Invoice), erlang:element(3, Invoice), erlang:element(4, Invoice), erlang:element(5, Invoice), erlang:element(6, Invoice), erlang:element(7, Invoice), {some, Url}, erlang:element(9, Invoice), erlang:element(10, Invoice), erlang:element(11, Invoice), erlang:element(12, Invoice), erlang:element(13, Invoice), erlang:element(14, Invoice), erlang:element(15, Invoice), erlang:element(16, Invoice), erlang:element(17, Invoice), erlang:element(18, Invoice)}. -file("src/telega/payments.gleam", 262). ?DOC( " Allow tips up to `max` with suggested amounts (smallest currency units).\n" " Not supported for Telegram Stars.\n" ). -spec with_tips(invoice(), integer(), list(integer())) -> invoice(). with_tips(Invoice, Max, Suggested) -> {invoice, erlang:element(2, Invoice), erlang:element(3, Invoice), erlang:element(4, Invoice), erlang:element(5, Invoice), erlang:element(6, Invoice), erlang:element(7, Invoice), erlang:element(8, Invoice), erlang:element(9, Invoice), erlang:element(10, Invoice), {some, Max}, {some, Suggested}, erlang:element(13, Invoice), erlang:element(14, Invoice), erlang:element(15, Invoice), erlang:element(16, Invoice), erlang:element(17, Invoice), erlang:element(18, Invoice)}. -file("src/telega/payments.gleam", 277). ?DOC( " Request a shipping address and make the final price depend on the chosen\n" " shipping option — the bot will receive shipping queries, handle them with\n" " `router.on_shipping_query` and `answer_shipping_ok`/`answer_shipping_error`.\n" ). -spec with_flexible_shipping(invoice()) -> invoice(). with_flexible_shipping(Invoice) -> {invoice, erlang:element(2, Invoice), erlang:element(3, Invoice), erlang:element(4, Invoice), erlang:element(5, Invoice), erlang:element(6, Invoice), erlang:element(7, Invoice), erlang:element(8, Invoice), erlang:element(9, Invoice), erlang:element(10, Invoice), erlang:element(11, Invoice), erlang:element(12, Invoice), erlang:element(13, Invoice), erlang:element(14, Invoice), erlang:element(15, Invoice), {some, true}, {some, true}, erlang:element(18, Invoice)}. -file("src/telega/payments.gleam", 282). ?DOC(" JSON-serialized data for the payment provider.\n"). -spec with_provider_data(invoice(), binary()) -> invoice(). with_provider_data(Invoice, Data) -> {invoice, erlang:element(2, Invoice), erlang:element(3, Invoice), erlang:element(4, Invoice), erlang:element(5, Invoice), erlang:element(6, Invoice), erlang:element(7, Invoice), erlang:element(8, Invoice), erlang:element(9, Invoice), {some, Data}, erlang:element(11, Invoice), erlang:element(12, Invoice), erlang:element(13, Invoice), erlang:element(14, Invoice), erlang:element(15, Invoice), erlang:element(16, Invoice), erlang:element(17, Invoice), erlang:element(18, Invoice)}. -file("src/telega/payments.gleam", 290). ?DOC(" Deep-linking parameter to recreate the invoice via a `/start` link.\n"). -spec with_start_parameter(invoice(), binary()) -> invoice(). with_start_parameter(Invoice, Parameter) -> {invoice, erlang:element(2, Invoice), erlang:element(3, Invoice), erlang:element(4, Invoice), erlang:element(5, Invoice), erlang:element(6, Invoice), erlang:element(7, Invoice), erlang:element(8, Invoice), {some, Parameter}, erlang:element(10, Invoice), erlang:element(11, Invoice), erlang:element(12, Invoice), erlang:element(13, Invoice), erlang:element(14, Invoice), erlang:element(15, Invoice), erlang:element(16, Invoice), erlang:element(17, Invoice), erlang:element(18, Invoice)}. -file("src/telega/payments.gleam", 299). ?DOC( " Inline keyboard for the invoice message. The first button must be a Pay\n" " button, otherwise Telegram inserts one.\n" ). -spec with_reply_markup(invoice(), telega@model@types:inline_keyboard_markup()) -> invoice(). with_reply_markup(Invoice, Markup) -> {invoice, erlang:element(2, Invoice), erlang:element(3, Invoice), erlang:element(4, Invoice), erlang:element(5, Invoice), erlang:element(6, Invoice), erlang:element(7, Invoice), erlang:element(8, Invoice), erlang:element(9, Invoice), erlang:element(10, Invoice), erlang:element(11, Invoice), erlang:element(12, Invoice), erlang:element(13, Invoice), erlang:element(14, Invoice), erlang:element(15, Invoice), erlang:element(16, Invoice), erlang:element(17, Invoice), {some, Markup}}. -file("src/telega/payments.gleam", 307). ?DOC(" Require the user's full name to complete the order.\n"). -spec require_name(invoice()) -> invoice(). require_name(Invoice) -> {invoice, erlang:element(2, Invoice), erlang:element(3, Invoice), erlang:element(4, Invoice), erlang:element(5, Invoice), erlang:element(6, Invoice), erlang:element(7, Invoice), erlang:element(8, Invoice), erlang:element(9, Invoice), erlang:element(10, Invoice), erlang:element(11, Invoice), erlang:element(12, Invoice), {some, true}, erlang:element(14, Invoice), erlang:element(15, Invoice), erlang:element(16, Invoice), erlang:element(17, Invoice), erlang:element(18, Invoice)}. -file("src/telega/payments.gleam", 312). ?DOC(" Require the user's phone number to complete the order.\n"). -spec require_phone_number(invoice()) -> invoice(). require_phone_number(Invoice) -> {invoice, erlang:element(2, Invoice), erlang:element(3, Invoice), erlang:element(4, Invoice), erlang:element(5, Invoice), erlang:element(6, Invoice), erlang:element(7, Invoice), erlang:element(8, Invoice), erlang:element(9, Invoice), erlang:element(10, Invoice), erlang:element(11, Invoice), erlang:element(12, Invoice), erlang:element(13, Invoice), {some, true}, erlang:element(15, Invoice), erlang:element(16, Invoice), erlang:element(17, Invoice), erlang:element(18, Invoice)}. -file("src/telega/payments.gleam", 317). ?DOC(" Require the user's email to complete the order.\n"). -spec require_email(invoice()) -> invoice(). require_email(Invoice) -> {invoice, erlang:element(2, Invoice), erlang:element(3, Invoice), erlang:element(4, Invoice), erlang:element(5, Invoice), erlang:element(6, Invoice), erlang:element(7, Invoice), erlang:element(8, Invoice), erlang:element(9, Invoice), erlang:element(10, Invoice), erlang:element(11, Invoice), erlang:element(12, Invoice), erlang:element(13, Invoice), erlang:element(14, Invoice), {some, true}, erlang:element(16, Invoice), erlang:element(17, Invoice), erlang:element(18, Invoice)}. -file("src/telega/payments.gleam", 322). ?DOC(" Send the invoice to the current chat.\n"). -spec send(invoice(), telega@bot:context(any(), any(), any())) -> {ok, telega@model@types:message()} | {error, telega@error:telega_error()}. send(Invoice, Ctx) -> telega@api:send_invoice( erlang:element(5, erlang:element(4, Ctx)), {send_invoice_parameters, {int, erlang:element(3, erlang:element(3, Ctx))}, none, erlang:element(2, Invoice), erlang:element(3, Invoice), erlang:element(4, Invoice), erlang:element(7, Invoice), erlang:element(5, Invoice), erlang:element(6, Invoice), erlang:element(11, Invoice), erlang:element(12, Invoice), erlang:element(9, Invoice), erlang:element(10, Invoice), erlang:element(8, Invoice), none, none, none, erlang:element(13, Invoice), erlang:element(14, Invoice), erlang:element(15, Invoice), erlang:element(16, Invoice), none, none, erlang:element(17, Invoice), none, none, none, none, none, erlang:element(18, Invoice)} ). -file("src/telega/payments.gleam", 363). ?DOC(" Create a shareable payment link for the invoice.\n"). -spec create_link(invoice(), telega@bot:context(any(), any(), any())) -> {ok, binary()} | {error, telega@error:telega_error()}. create_link(Invoice, Ctx) -> telega@api:create_invoice_link( erlang:element(5, erlang:element(4, Ctx)), {create_invoice_link_parameters, none, erlang:element(2, Invoice), erlang:element(3, Invoice), erlang:element(4, Invoice), erlang:element(7, Invoice), erlang:element(5, Invoice), erlang:element(6, Invoice), none, erlang:element(11, Invoice), erlang:element(12, Invoice), erlang:element(10, Invoice), erlang:element(8, Invoice), none, none, none, erlang:element(13, Invoice), erlang:element(14, Invoice), erlang:element(15, Invoice), erlang:element(16, Invoice), none, none, erlang:element(17, Invoice)} ). -file("src/telega/payments.gleam", 398). ?DOC( " Confirm a pre-checkout query: the order can proceed.\n" " Must be sent within 10 seconds after the query arrives.\n" ). -spec answer_pre_checkout_ok( telega@bot:context(any(), any(), any()), telega@model@types:pre_checkout_query() ) -> {ok, boolean()} | {error, telega@error:telega_error()}. answer_pre_checkout_ok(Ctx, Query) -> telega@api:answer_pre_checkout_query( erlang:element(5, erlang:element(4, Ctx)), {answer_pre_checkout_query_parameters, erlang:element(2, Query), true, none} ). -file("src/telega/payments.gleam", 413). ?DOC(" Reject a pre-checkout query with a human-readable reason shown to the user.\n"). -spec answer_pre_checkout_error( telega@bot:context(any(), any(), any()), telega@model@types:pre_checkout_query(), binary() ) -> {ok, boolean()} | {error, telega@error:telega_error()}. answer_pre_checkout_error(Ctx, Query, Message) -> telega@api:answer_pre_checkout_query( erlang:element(5, erlang:element(4, Ctx)), {answer_pre_checkout_query_parameters, erlang:element(2, Query), false, {some, Message}} ). -file("src/telega/payments.gleam", 429). ?DOC(" A shipping option offered in response to a shipping query.\n"). -spec shipping_option( binary(), binary(), list(telega@model@types:labeled_price()) ) -> telega@model@types:shipping_option(). shipping_option(Id, Title, Prices) -> {shipping_option, Id, Title, Prices}. -file("src/telega/payments.gleam", 438). ?DOC(" Confirm delivery to the queried address with the available options.\n"). -spec answer_shipping_ok( telega@bot:context(any(), any(), any()), telega@model@types:shipping_query(), list(telega@model@types:shipping_option()) ) -> {ok, boolean()} | {error, telega@error:telega_error()}. answer_shipping_ok(Ctx, Query, Options) -> telega@api:answer_shipping_query( erlang:element(5, erlang:element(4, Ctx)), {answer_shipping_query_parameters, erlang:element(2, Query), true, {some, Options}, none} ). -file("src/telega/payments.gleam", 455). ?DOC(" Reject a shipping query with a human-readable reason shown to the user.\n"). -spec answer_shipping_error( telega@bot:context(any(), any(), any()), telega@model@types:shipping_query(), binary() ) -> {ok, boolean()} | {error, telega@error:telega_error()}. answer_shipping_error(Ctx, Query, Message) -> telega@api:answer_shipping_query( erlang:element(5, erlang:element(4, Ctx)), {answer_shipping_query_parameters, erlang:element(2, Query), false, none, {some, Message}} ). -file("src/telega/payments.gleam", 482). ?DOC( " Pauses the current chat actor's handler and waits for a successful payment\n" " service message. Other (non-payment) messages keep the conversation\n" " waiting, or go to the `or` handler if one is given.\n" "\n" " ```gleam\n" " let assert Ok(_) = payments.stars_invoice(...) |> payments.send(ctx)\n" " use ctx, payment <- payments.wait_successful_payment(ctx, or: None, timeout: None)\n" " reply.with_text(ctx, \"Thanks! Charge id: \" <> payment.telegram_payment_charge_id)\n" " ```\n" "\n" " See [conversation](/docs/conversation)\n" ). -spec wait_successful_payment( telega@bot:context(BNKR, BNKS, BNKT), gleam@option:option(telega@bot:handler(BNKR, BNKS, BNKT)), gleam@option:option(integer()), fun((telega@bot:context(BNKR, BNKS, BNKT), telega@model@types:successful_payment()) -> {ok, telega@bot:context(BNKR, BNKS, BNKT)} | {error, BNKS}) ) -> {ok, telega@bot:context(BNKR, BNKS, BNKT)} | {error, BNKS}. wait_successful_payment(Ctx, Handle_else, Timeout, Continue) -> Payment_handler = {handle_message, fun(Ctx@1, Message) -> case erlang:element(78, Message) of {some, Payment} -> Continue(Ctx@1, Payment); none -> wait_successful_payment( Ctx@1, Handle_else, Timeout, Continue ) end end}, telega@bot:wait_handler(Ctx, Payment_handler, Handle_else, Timeout).