Skip to main content
logo

BharatpePG

Drop-in UPI payments for any Telegram bot — pay by QR, verify by UTR, deliver automatically.

Python python-telegram-bot UPI License



✨ What it does

A customer pays the exact amount by scanning a branded QR, sends you the 12-digit UTR, and the bot verifies it against your own BharatPe account — then runs your delivery logic automatically. No payment gateway, no 2% fee, no website.

💳 Collect 🔐 Verify 📦 Deliver
Branded UPI QR for the exact amount 5 safety guards + reuse protection on_verified hook fires once per sale

🚀 Feature highlights

  • 🧾 /pay flow — amount picker → professional QR card → UTR submission → verified
  • 🔑 /login (OTP) — authenticate your BharatPe session from your phone; merchant ID auto-discovered
  • 🧮 verify_utr() — standalone one-call verification for your own custom UI
  • 🪝 on_verified hook — deliver files, group invites, license keys, or credits — automatically
  • 🛡️ Outage-safe — background health monitor, new payments paused while down, in-flight UTRs queued and auto-delivered on recovery
  • 🗄️ Zero-setup storage — stdlib SQLite by default; Postgres is one env var
  • ⌨️ Inline + reply keyboards — both, out of the box
  • ☁️ Deploy anywhere — Railway, Render, or a plain VPS

📦 Installation

pip install bharatpe-pg
Postgres instead of the default SQLite file
pip install "bharatpe-pg[postgres]"

Not yet on PyPI? Install from source: pip install git+https://github.com/AshuXD-X/BharatpePG


⚡ Quickstart

Add it to any python-telegram-bot app with three calls — your own handlers stay untouched:

from telegram.ext import Application
from bharatpe_pg import PaymentConfig, init_db, register_payment_handlers, register_admin_handlers

cfg = PaymentConfig(
    upi_id        = "yourname@yesbankltd",
    merchant_name = "My Store",
    admin_ids     = [123456789],          # your Telegram user ID
    # merchant_id is optional — auto-discovered when the admin runs /login
)

app = Application.builder().token("BOT_TOKEN").build()
init_db(cfg)                              # SQLite file store — no DB server needed
register_payment_handlers(app, cfg)       # /pay flow
register_admin_handlers(app, cfg)         # /login + /admin
app.run_polling()

🏃 Runnable examples: minimal_bot.py (defaults) · credits_bot.py (top-up wallet) · themed_bot.py (fully rebranded UI)


🔄 How it works

flowchart LR
    A([User taps 💳 Pay]) --> B[Branded UPI QR<br/>exact amount]
    B --> C([User pays via<br/>any UPI app])
    C --> D([Sends 12-digit UTR])
    D --> E{Verify against<br/>BharatPe}
    E -->|✓ all guards pass| F[Claim UTR<br/>once-only]
    F --> G[[on_verified<br/>deliver product]]
    E -->|session down| Q[(Queue)]
    Q -.->|auto on recovery| F

🔑 The /login OTP flow

BharatPe login is phone + OTP (no password). An admin runs it once:

  1. Send /login → bot asks for the 10-digit mobile
  2. BharatPe texts the OTP → admin types it into the chat
  3. Bot stores the session and auto-discovers the merchant ID (BharatPe never shows it in the UI)

The OTP is entered manually every time — the bot never reads SMS. If the session expires later, just /login again.


📦 Delivering the product — the on_verified hook

Set one callback; the plugin runs it exactly once per paid order, the moment a payment verifies — instantly or after an outage. This is where you hand over the goods.

async def on_verified(bot, order):
    # order = {"user_id":…, "amount":…, "order_id":…, "utr":…}
    await bot.send_document(order["user_id"], open("product.pdf", "rb"))

cfg = PaymentConfig(upi_id="…", merchant_name="…", on_verified=on_verified)

🧮 Real example — a credits top-up bot

Pay ₹100 → get 100 credits (rate set by you). See examples/credits_bot.py.

CREDIT_RATE = 1   # credits per ₹ — your rule (1, 2, 10, …)

async def on_verified(bot, order):
    credits = int(order["amount"] * CREDIT_RATE)
    add_credits(order["user_id"], credits)          # your own balance store
    await bot.send_message(order["user_id"], f"🎉 Added {credits} credits.")

💡 Use cases this unlocks: file / ebook / zip delivery · license & game keys · paid group invite links · premium feature unlocks · timed subscriptions · pay-per-use credits · donation / tip jars · reseller fulfillment pings.


🎨 Customize the UI — brand everything

Nothing is hard-coded. Colours, text, buttons, and amount presets are all config — and if you want total control, pass your own QR renderer.

QR card — colours (hex or RGB), tagline, footer, fonts:

from bharatpe_pg import PaymentConfig, QRTheme

cfg = PaymentConfig(
    upi_id="…", merchant_name="GlowStore",
    qr_theme=QRTheme(
        bg_top="#2d1b4e", bg_bottom="#0f0524", accent="#ff6b9d",
        tagline="PAY & GLOW", footer_hint="UPI only", show_dots=False,
        font_bold="Montserrat-Bold.ttf",   # optional custom font
    ),
)

Buttons + amount grid:

from bharatpe_pg import UIButtons

cfg = PaymentConfig(
    upi_id="…", merchant_name="…",
    amount_presets=[49, 99, 199, 499],          # your own quick-pay amounts
    ui=UIButtons(menu_pay="🛒 Buy", custom_label="💬 Other amount", amounts_per_row=2),
)

Every message — templates with {amount}, {order_id}, {utr}, {payer}:

from bharatpe_pg import Messages

cfg = PaymentConfig(
    upi_id="…", merchant_name="…",
    messages=Messages(
        pay_caption="🧾 Send *₹{amount:.2f}* to the QR, then paste the UTR.\n`{order_id}`",
        verified="🎉 Done! ₹{amount:.2f} received from {payer}.",
    ),
)

Full control — bypass the built-in card with your own renderer:

def my_qr(amount, order_id, cfg):
    ...                      # build any PNG you like
    return png_bytesio       # io.BytesIO

cfg = PaymentConfig(upi_id="…", merchant_name="…", qr_renderer=my_qr)

All of these are optional — omit them and you get the polished defaults.


🧮 Standalone verification — verify_utr()

Already have your own payment UI? Call one function. It runs all five guards plus reuse protection and returns a result — no need for the /pay handlers:

from bharatpe_pg import verify_utr, init_db
init_db(cfg)

r = verify_utr("664700063288", 100.0, order_id="topup-42", user_id=123, cfg=cfg)
if r.ok:                       # r.reason: verified | bad_utr | not_found | reused | gateway_down | error
    add_credits(123, 100)

🛡️ Verification guards

Every UTR must clear all of these before an order is marked paid:

# Guard Protects against
1 Exists in your BharatPe transactions fake UTRs
2 Type PAYMENT_RECV + status SUCCESS refunds / failed txns
3 Amount matches the order underpaying
4 Not already used (utr is UNIQUE) replaying one payment
5 Recent, within utr_window_sec recycling old UTRs

⚙️ What happens during an outage

The BharatPe session expires periodically (it's a login session, not an API key). The plugin handles it so you don't have to:

  • 🔭 Background monitor checks health every few minutes → DMs admins once when it expires
  • 🚫 While down, new /pay is refused ("gateway down, try later") — no QR for a payment you can't verify
  • 📥 A UTR paid during the window is queued; on recovery it's verified and on_verified fires — product delivered automatically, no resend

🛠️ Managing your bot

Everything you need after deploy is done from inside Telegram, as an admin — no server access, no BharatPe dashboard. Open /admin (admins only).

Admin controls

Button / command What it does
/login · 🔑 Login Connect BharatPe: send mobile → enter the OTP from your phone. Merchant ID is auto-discovered.
🔌 Status Live connection check — 🟢 logged in · 🔴 not logged in / expired · 🟡 BharatPe unreachable.
💰 Recent Last 10 orders with status + UTR.
🔍 Search Look up a payment by order ID or UTR.
/cancel Abort an in-progress login.

Automatic vs. manual

✅ Automatic (you do nothing) 🙋 Manual (you act)
Verifying UTRs when users submit them One-time /login per session (OTP from your phone)
Running on_verified to deliver goods/credits Re-login when the session expires — you're DMed first
Detecting session expiry (background monitor) —
Pausing payments during an outage —
Queuing + delivering payments after recovery —
Restoring the session after a redeploy —

You're alerted before customers are affected: the monitor DMs admins the moment the session expires, so you re-login in ~10 seconds and nothing breaks downstream.

⚠️ Keep your session across redeploys — use durable storage

The session is saved to your storage so a redeploy doesn't force a re-login. But that only holds if the storage itself survives:

  • VPS — the default SQLite file persists on disk → nothing to do. ✅

  • Railway / Render — the default disk is wiped on redeploy, taking the SQLite file (and your session + payment records) with it. Use one of:

    • a mounted volume with DB_PATH pointing into it, or
    • Postgres via DATABASE_URL.

    Without durable storage you'll have to /login again after every deploy, and payment history resets.

Command names clash with your bot's? Rename them.

The plugin's four commands — pay, admin, login, cancel — are configurable. If your bot already uses any of those, just rename the plugin's in PaymentConfig (give the bare word, no slash):

cfg = PaymentConfig(
    upi_id="…", merchant_name="…",
    cmd_pay="buy",        # /buy instead of /pay
    cmd_admin="panel",    # /panel instead of /admin
    cmd_login="bplogin",
    cmd_cancel="abort",
)

Why this matters: python-telegram-bot runs only the first handler that matches a command within a group, so two handlers on the same command would shadow each other. Renaming removes the clash entirely — no shared command, no shadowing.

Free-text messages don't clash. The plugin's text handlers live in groups 1 and 2 (not the default group 0) and only act on their own state (awaiting a UTR / an admin input) — your own group-0 text handlers run normally alongside.

Prefer to use none of the plugin's commands? Skip register_admin_handlers and drive everything from your own UI with the building blocks:

from bharatpe_pg.bharatpe import start_login, complete_login, check_credentials, has_session
from bharatpe_pg.database import admin_recent, admin_search

The payment flow (register_payment_handlers) is independent of the admin panel — you can ship /pay + on_verified without the plugin's /admin at all.


🔧 Configuration

PaymentConfig is a dataclass — pass values directly or from env. Only upi_id and merchant_name are required.

Field Default Meaning
upi_id — (required) Your UPI VPA, encoded into the QR
merchant_name — (required) Shown on the QR card
admin_ids [] Telegram IDs allowed to run the admin + login commands
on_verified None Async hook run once per paid order
cmd_pay / cmd_admin / cmd_login / cmd_cancel pay / admin / login / cancel Rename the plugin's commands to avoid clashing with yours
merchant_id "" Auto-discovered at /login — usually leave blank
auth_host enterprise.bharatpe.in Login/OTP host (configurable)
api_host payments-tesseract.bharatpe.in Transactions host (configurable)
db_url "" Set a postgres://… DSN to use Postgres
db_path payments.db SQLite file path (when db_url is empty)
utr_window_sec 1800 Reject UTRs older than this (30 min)
session_check_sec 300 Background health-check interval
min_amount / max_amount 1 / 50000 Allowed range (₹)

☁️ Deploy

Platform Notes
Run python examples/minimal_bot.py, set env vars. Mount a volume + set DB_PATH so SQLite survives redeploys, or use Postgres.
Same — add a persistent disk for durable storage, or set DATABASE_URL.
Run under systemd / pm2; the default SQLite file just works.

Postgres (opt-in): pip install "bharatpe-pg[postgres]" and set DATABASE_URL=postgres://…. The same utr UNIQUE schema is created automatically — recommended for multi-process setups.


🔒 Security

  • 🚫 Never commit your .env, session files, or any Burp/HAR capture — all gitignored.
  • 🔑 Credentials come from the live /login session, never from source.
  • 🧷 utr UNIQUE makes "one UTR, one order" a storage invariant — copied UTRs can't be replayed.
  • 👮 /login and /admin are locked to admin_ids.

🧪 Tests

Four stdlib assert self-checks (no pytest, no network) guard the money-critical logic:

python tests/test_utr_reuse.py        # one UTR can't pay two orders
python tests/test_queue_drain.py      # outage queue verifies + delivers on recovery
python tests/test_verify_and_hook.py  # verify_utr guards + on_verified fires once
python tests/test_session_persist.py  # session survives a redeploy (no re-login)

MIT Licensed · Built for small Telegram sellers who want 0% UPI collection without a gateway.

Not affiliated with or endorsed by BharatPe. Use responsibly with your own merchant account.

Metadata

Release files for bharatpe-pg 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for bharatpe-pg 0.1.1
File Size Uploaded
bharatpe_pg-0.1.1.tar.gz 34.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for bharatpe-pg 0.1.1
File Interpreter ABI Platform
bharatpe_pg-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 64.8 kB

Release files / bharatpe_pg-0.1.1.tar.gz

Download URL bharatpe_pg-0.1.1.tar.gz
Size 34.5 kB
Tags Source
SHA-256 checksum
How to use checksums
bf6aa1360f4ff489b858c608928fb4b8488cd089858693f421168f243512826f
BLAKE2b-256 checksum
How to use checksums
c46220e95b249ac6295e72790814043714751dce77265809f3af632b80098e01
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.10.0

Release files / bharatpe_pg-0.1.1-py3-none-any.whl

Download URL bharatpe_pg-0.1.1-py3-none-any.whl
Size 30.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c1f4c22cc038cd0d49a181e0b45c586f01c8b39ad1ba3b839f399150caf88795
BLAKE2b-256 checksum
How to use checksums
c4a6ed4dd0454bedbfdafe59ca7a137173e8489e9f0ec08d7231d85e67886b01
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.10.0

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page