Telegram Mini AppでStar課金を実装する方法

# Telegram Mini AppのStar課金について

Telegram Mini Appでは、Telegramのアプリ内通貨「Star」を使用してデジタルアイテムを販売できます。

この記事では、Star課金の実装方法について詳しく解説します。

# 事前準備

# Starの購入

まず、Star課金で使用するStarを購入しましょう。Telegramのテスト環境と本番環境では購入方法が異なります。

## テスト環境でStarを無料で入手する方法

Telegramのテスト環境では、テスト用のクレジットカードを使用する事でStarを無料で入手可能です。

### **テスト環境への切り替え方法**

Telegramアプリでのテスト環境への切り替え方法はプラットフォームごとに異なります。一部のプラットフォーム（Android版、Web版など）ではテスト環境は利用できません。

- **Telegram iOSアプリ版**
    
    設定アイコンを10回タップ → アカウント → 別のアカウントにログイン → テスト
    
- **[Telegram デスクトップ版](https://desktop.telegram.org/)**
    
    メニュー（☰） → [Settings] → [MyAccount] を開き、Shift + Alt + [アカウントの追加] を右クリック→ [テスト サーバー] を選択
    
- **[Telegram macOS版](https://macos.telegram.org/)**
    
    設定アイコンを 10 回クリックしてデバッグ メニューを開き、⌘ キーを押しながら「アカウントの追加」をクリック → 電話番号でログイン
    

---

### **Starの購入方法**

デスクトップ版またはmacOS版のTelegramアプリを使用してテスト環境に切り替え、設定画面の「My Stars」を開きます。クレジットカード情報を入力してStarを購入します。
詳しくは
クレジットカード番号は[ドキュメントに記載されている番号](https://core.telegram.org/bots/payments#testing-payments-the-39stripe-test-mode-39-provider)を入力して、カード番号以外の情報は任意で構いません。

*カード情報の入力*
![スクリーンショット 2024-11-23 3.38.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/216057/e8db5b26-db36-f782-9cfe-6a72475f83ab.png)

（注意）iOS版TelegramではクレジットカードでのStar購入ができないため、無料での取得方法はありません

---

### テスト環境でMini Appを作成する

テスト環境で購入したStarは、テスト環境で作成したMini Appでのみ利用できます。
以下のリンクからテスト環境のBotFatherにアクセスし、Mini Appを作成してください。

- [BotFather](https://t.me/botfather)
- [テスト環境についてのドキュメント](https://core.telegram.org/bots/features#dedicated-test-environment)

### テスト環境のAPIエンドポイントについて

テスト環境では、Telegram APIのエンドポイントが以下のように変わります。

- **本番環境のAPIエンドポイント**
`https://api.telegram.org/bot<token>/METHOD_NAME`
- **テスト環境のAPIエンドポイント**
`https://api.telegram.org/bot<token>/test/METHOD_NAME`

例えば、[pyTelegramBotAPI](https://pypi.org/project/pyTelegramBotAPI/)を使用する場合、以下のようにエンドポイントを変更します。

```python
from telebot import apihelper
apihelper.API_URL = "https://api.telegram.org/bot{0}/test/{1}"
```

## 本番環境でStarを購入する方法

本番環境では、iOS/Android版のTelegramアプリからStarを購入できます。ただし、プラットフォーム手数料が上乗せされるため、割高になります。

**Fragment**という外部サイトでは、Starを\\$TON（Toncoin）で販売しており、より安価に購入可能です。

- [Fragment公式サイト](https://fragment.com/stars)

FragmentではStarのほか、Telegram Premium、ユーザー名、電話番号なども\\$TONで販売しています。

:::note info
\\$TONとは？
TONは、Telegramが開発を始めたブロックチェーンで、プロジェクト開始当初は「Telegram Open Network」という名称で進められていましたが、現在は「The Open Network」と改称され、非営利団体であるTON Foundationによって開発・運営されています。
\\$TON（Toncoin）は、TONのネイティブトークンです。
https://ton.org/
:::


### テスト環境、本番環境どちらで開発すべき？

テスト環境ではStarを無料で利用できるため初期コストを抑えられます。一方、本番環境での開発にはStarの購入費用がかかりますが、後述する返金処理でMiniAppに支払ったStarは回収可能です。どちらを選ぶかは、プロジェクトの要件に応じて判断してください。

---

# Star課金の実装方法

## Telegramからの課金イベント通知用Webhookの登録

Star課金では、Telegramサーバーからの課金イベント通知をWebhookで受け取る必要があります。そのため、まずWebhookの登録を行います。

以下は、Flaskと[pyTelegramBotAPI](https://pypi.org/project/pyTelegramBotAPI/)を使ったサンプルコードです。

### WebhookのURLの登録

WebhookのURLには、推測されにくいようにTOKENを含めることを推奨します。

```python
# webhook urlを設定
webhook_url = f"https://your-domain.com/{telegram_token}/webhook"
bot.set_webhook(
  url=webhook_url,
  allowed_updates=['message', 'callback_query', 'pre_checkout_query'] # 受信するメッセージ
)
```

URLだけでは不安な場合は、シークレットトークンを設定することも可能です。シークレットトークンを設定すると、Webhookリクエストに「X-Telegram-Bot-Api-Secret-Token」ヘッダーが追加されます。

```python
bot.set_webhook(
  url=webhook_url,
  allowed_updates=['message', 'callback_query', 'pre_checkout_query'],
  secret_token="XXXXXXXXXXXX" # シークレットトークンを設定
)
```

### Webhookの受信処理

Webhookに設定したURLの受信処理を以下のように実装します。

```python
@app.route(f"/{telegram_token}/webhook", methods=['POST'])
def webhook():
  update = telebot.types.Update.de_json(request.stream.read().decode("utf-8"))
  self.bot.process_new_updates([update])
  return "OK"
```

## Star課金処理の流れ

### ①購入ボタン→請求画面表示

ユーザーが購入ボタンを押してから請求画面を表示するまでのフローを説明します。

```mermaid
sequenceDiagram
    actor User
    User->>Client: 購入ボタン押下
    activate Client
    Client->>Server: 請求書URLを要求
    activate Server
    Server->>Telegram: 請求書URLを発行[Payload:商品情報]
    activate Telegram
    Telegram-->>Server: 請求書URL
    deactivate Telegram
    Server-->>Client: 請求書URL
    deactivate Server
    Client->>Client: 請求API
    Client-->>User: 請求画面表示
    deactivate Client
    
```

1: ユーザーがアイテムの購入ボタンを押します。

2: ClientがServerAPIを呼び出して請求書URLを要求します。

3: Serverは、Telegram APIの`create_invoice_link`関数を使用して請求書URLを生成します。
`create_invoice_link`関数のパラメーターには販売するアイテム名や価格情報などを含めます。
payloadはユーザーには公開されないデータで、支払いプロセス中に参照できるデータなので、ここに販売するアイテム情報などを含めましょう。payloadは128バイトまでの容量制限があります。
発行した請求書URLをClientに渡します。

*サンプルコード：請求書URLの生成*

```python
title = "ポーション"
description = "体力を10回復する"
provider_token = "" # Star支払いの場合は空文字
currency = "XTR"  # Star支払いの場合はXTR
amount = 100 # 販売価格
prices = [telebot.types.LabeledPrice(label=title, amount=amount)]

# payloadに販売情報を含める
payload = urllib.parse.urlencode({'item_id': 5})

# 請求書URLを発行
bot = telebot.TeleBot(telegram_token)
invoice_link = bot.create_invoice_link(
  title, description, payload, provider_token, currency, prices
)

# クライアントに請求書URLを渡す
return invoice_link
```

4: ClientはServerから受け取った請求書URLを使って請求画面を表示します。
[twa-dev/sdk](https://www.npmjs.com/package/@twa-dev/sdk)を使用している場合は、`WebApp.openInvoice`メソッドを使用して、ユーザーに支払い確認画面を表示できます。

*サンプルコード：請求画面の表示*

```tsx
WebApp.openInvoice(
  invoice_link,
	function (status) {
	  // 購入後のコールバック
      switch (status) {
	    case "paid":
	    case "failed":
	    case "cancelled":
	    case "pending":
	  }
  });
});
```

支払い確認画面
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/216057/031a087c-0393-d4d7-07e6-48584590a30b.png" width="50%" />

### ②ユーザーが請求書を確認して支払いを行う

請求書画面が表示された後、ユーザーが支払いを完了するまでのフローは以下の通りです。

```mermaid
sequenceDiagram
    actor User
    participant AppServer
    participant Database
    participant Telegram
    User->>Telegram: Starで支払い実行
    Telegram->>AppServer: pre_checkout_query
    activate AppServer
    AppServer->>AppServer: バリデーション
    AppServer-->>Telegram: answer_pre_checkout_query[承認]
    deactivate AppServer
    Telegram->>AppServer: successful_payment
    activate AppServer
    AppServer->>Database: telegram_payment_charge_idを保存
    AppServer->>Database: アイテム付与
    deactivate AppServer

```

1 ユーザーが請求書画面でStar支払いを行います。

2 Telegramが支払い直前イベント通知（[PreCheckoutQuery](https://core.telegram.org/bots/api#precheckoutquery)）をAppServerに送信します。
このリクエストには購入者・請求情報・payloadなどの支払い内容が含まれます。
3 AppServer側で、受信した支払い内容を検証し、[answerPreCheckoutQuery](https://core.telegram.org/bots/api#answerprecheckoutquery)で支払いの承認もしくはキャンセルを行います。

*サンプルコード：支払い直前イベント通知（PreCheckoutQuery）の受信処理*

```python
bot = telebot.TeleBot(telegram_token)

def pre_checkout_query_filter(query):
  payment_info = dict(urllib.parse.parse_qsl(query.invoice_payload))
  return True

@bot.pre_checkout_query_handler(func=pre_checkout_query_filter)
def handle_pre_checkout_query(query):
  if is_valid(query):
    # 支払い承認通知を送信
    bot.answer_pre_checkout_query(query.id, ok=True)
  else:
    # 支払いキャンセル通知を送信
    bot.answer_pre_checkout_query(query.id, ok=False, error_message="無効な商品情報です。")
```

4 支払いが完了するとTelegramが支払い完了イベント通知（[SuccessfulPayment](https://core.telegram.org/bots/api#successfulpayment)）をAppServerに送信します。

リクエストパラメーターには以下のような情報が含まれるので必要に応じてデータベースなどに保存します。
請求書URL発行時のpayloadには購入したアイテム情報が含めているので、購入者にアイテムの付与を行います。
telegram_payment_charge_idは返金処理で必要になるので必ず保存しておきましょう。


**リクエストパラメーター**
- 購入者情報
- 販売価格
- telegram_payment_charge_id
- 請求書URL発行時のpayload

*サンプルコード：支払い完了イベント通知（successful_payment）の受信処理*

```python
@bot.message_handler(content_types=["successful_payment"])
def handle_successful_payment(message):
    user_id = message.from_user.id
    telegram_payment_charge_id = (
        message.successful_payment.telegram_payment_charge_id
    )
    amount = message.successful_payment.total_amount
    payment_info = dict(parse_qsl(message.successful_payment.invoice_payload))
    
    # telegram_payment_charge_idをDBに保存
    add_payment_info(user_id, telegram_payment_charge_id, amount)
    
    # 購入者にアイテムを付与
		add_item_to_user(user_id, payment_info.get("item_id"))
```

参考

- https://core.telegram.org/bots/payments-stars#implementing-payments


## 返金処理の実装

### アプリ側で返金処理を行う方法

1. ユーザーから取引で発行されたTransactionIDを教えてもらいます。TransactionIDはTelegramの設定画面にあるスターの取引履歴から確認できます
2. アイテム販売時の支払い完了イベント通知（SuccessfulPayment）で受け取った`telegram_payment_charge_id`と、1で取得したTransactionIDが一致するかを確認します
3. 返金は、[**refundStarPayment**関数](https://core.telegram.org/bots/api#refundstarpayment)を使用して行います。この関数に返金対象の`user_id`と1で取得したTransactionIDをパラメータとして渡します
4. 必要に応じて、返金後にデジタルアイテムの没収などを行います

### 返金完了イベント通知

Telegram Bot APIの仕様によると、`refunded_payment`というメッセージがWebhookで届くようですが、どのような操作を行った場合にこの通知が発生するのか、ドキュメントでは見つかりませんでした。

もし詳細をご存知の方がいれば、ぜひ教えてください。

参考　
- [Message](https://core.telegram.org/bots/api#message)の[successful_payment](https://core.telegram.org/bots/api#successfulpayment)・[refunded_payment](https://core.telegram.org/bots/api#refundedpayment)

## 売上を受け取る方法

Starの売上は、Apple/Googleのプラットフォーム手数料（30%）およびTelegramの手数料を差し引いた額が、\\$TONで受け取れます。現在、1スターあたり0.013米ドル相当の\\$TONが支払われます。

受け取り方法は、Telegramアプリ（Web版以外）のMiniAppの詳細画面から「スター残高ページ」に遷移することで確認・操作が可能です。

ただし、**1スター以上の売上**がない場合、この「スター残高ページ」に遷移するためのボタンは表示されませんのでご注意ください。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/216057/05342fea-c9c5-2846-6348-3af21b6db3f1.png" width="40%" /> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/216057/7c180917-718c-bd93-d3af-797cbba63192.png" width="40%" />


参考
- [Star Pricing](https://core.telegram.org/bots/payments-stars#star-pricing)
- [Telegram Bot Developers - 6.2.4. Rewards for Stars](https://telegram.org/tos/bot-developers#6-2-4-rewards-for-stars)
- https://coinmarketcap.com/currencies/toncoin/

## サポート窓口

Telegram Mini Appでは、ユーザーからの支払いに関する問い合わせを受け付けるため、チャットコマンド `/paysupport` を実装する必要があります。

*サンプルコード：/paysupportの処理*
```python
@bot.message_handler(commands=["paysupport"])
def handle_paysupport(message):
    bot.send_message(
        message.chat.id,
        """
お支払いに関するお問い合わせは、以下のフォームよりご連絡ください。
http://example.com/paysupport/form
""".strip(),
    )
```

参考

- [6.2.1. Payment Disputes for Digital Goods and Services](https://telegram.org/tos/bot-developers#6-2-1-payment-disputes-for-digital-goods-and-services)

## その他の注意事項

Star課金はTelegram側の審査を必要としませんが、Apple/Googleのアプリ内購入（IAP）のガイドラインに従う必要があります。

支払いに関するガイドラインの詳細については、以下のリンクをご参照ください：
[Telegram Bot Developers - Payments](https://telegram.org/tos/bot-developers#6-payments)

## 開発中にハマった事
当初vercel(Hobby Plan)を使って開発していましたが、Webhookの通知が届かない問題に悩まされていました。
おそらくvercelが共有サーバーのため？なのか、他のvercelユーザーの影響でTelegram側からアクセス制限がかかっている可能性が考えられます。
同様の問題が起こった場合は、aws等でインスタンスを用意する事をおすすめします。



# 参考

- https://core.telegram.org/bots/payments-stars
- https://core.telegram.org/bots/payments
- https://telegram.org/tos/bot-developers#6-payments