npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@yaebal/payments

v0.0.2

Published

yaebal payments — typed invoice builder, pre-checkout/successful-payment hooks and Telegram Stars subscriptions.

Readme

@yaebal/payments

typed invoice building and payment hooks for the Telegram Bot API. one fluent builder for both Telegram Stars and external payment providers, onPreCheckout/onSuccessfulPayment hooks that answer pre_checkout_query for you, and small helpers for the Stars Subscription API (Bot API 7.6+).

install

pnpm add @yaebal/payments

usage

import { invoice, payments } from "@yaebal/payments";

bot
	.install(
		payments({
			onPreCheckout: (ctx, query) => {
				// return true/nothing to approve, false or a string to decline
				return query.invoice_payload.startsWith("plan:");
			},
			onSuccessfulPayment: async (ctx, payment) => {
				await grantAccess(ctx.from!.id, payment.invoice_payload);
				await ctx.reply("thanks! access granted.");
			},
		}),
	)
	.command("buy", (ctx) => {
		const params = invoice("pro plan", "1 month of pro access", "plan:pro")
			.price("pro plan", 100)
			.toSendInvoiceParams(ctx.chat!.id);

		return ctx.api.sendInvoice(params);
	});

payments() only installs the handlers you configure — leave onPreCheckout unset and no pre_checkout_query listener is registered at all.

the invoice builder

invoice(title, description, payload) returns a fluent, provider-agnostic InvoiceBuilder. it defaults to Telegram Stars; call .provider(token, currency) to route through an external processor (Stripe, YooKassa, …) instead — the same builder, hooks and handler code work either way.

import { invoice } from "@yaebal/payments";

// stars (default)
invoice("tip", "buy me a coffee", "tip:1").price("tip", 25).toSendInvoiceParams(chatId);

// external provider
invoice("t-shirt", "black, medium", "shirt:1")
	.provider(process.env.PROVIDER_TOKEN!, "USD")
	.price("shirt", 1999)
	.flexible() // final price depends on shipping
	.needShippingAddress()
	.toSendInvoiceParams(chatId);

// stars subscription (Subscription API, Bot API 7.6+) — forces stars
invoice("pro", "monthly pro access", "sub:pro")
	.price("pro", 100)
	.subscription() // every 30 days — the only period Telegram currently accepts
	.toCreateInvoiceLinkParams();

.toSendInvoiceParams(chatId, extra?) builds params for api.sendInvoice; .toCreateInvoiceLinkParams(extra?) builds params for api.createInvoiceLink (a shareable link, no chat required). both accept an extra object for one-off fields (reply_markup, start_parameter, …) without a dedicated builder method.

hooks

payments({
	onPreCheckout: (ctx, query) => { ... },     // decide whether to proceed
	onSuccessfulPayment: (ctx, payment) => { ... }, // fulfil the order
	autoAnswerPreCheckout: true,                // default: answerPreCheckoutQuery for you
});

onPreCheckout's return value decides the answer Telegram gets:

| return | effect | |---|---| | true / nothing | { ok: true } | | false | { ok: false } (generic decline) | | a string | { ok: false, error_message: <string> } | | { ok, errorMessage? } | full control |

Telegram requires an answer within 10 seconds of pre_checkout_query, so keep the hook fast (defer anything slow to onSuccessfulPayment, which runs after the payment already went through). set autoAnswerPreCheckout: false to call ctx.api.answerPreCheckoutQuery yourself instead.

onSuccessfulPayment runs on message:successful_payment; payment.telegram_payment_charge_id is your key for refunds and subscription edits.

subscriptions (Subscription API, Bot API 7.6+)

import { cancelSubscription, isSubscriptionPayment, reactivateSubscription } from "@yaebal/payments";

if (isSubscriptionPayment(payment)) {
	await cancelSubscription(bot.api, {
		userId: ctx.from!.id,
		telegramPaymentChargeId: payment.telegram_payment_charge_id,
	});
}

cancelSubscription/reactivateSubscription wrap editUserStarSubscription — cancelling stops future billing but leaves the subscription active until the current period ends, matching Telegram's own semantics. isStarsPayment/isSubscriptionPayment are small guards over SuccessfulPayment for branching on currency/recurrence.

refunding a Stars payment (subscription or one-off) and listing the bot's Star ledger are already typed on Api directly — no wrapper needed: api.refundStarPayment(...), api.getStarTransactions(...).

testing

@yaebal/test's UserActor already models the payment flow: sendPreCheckoutQuery() emits the query, and sendSuccessfulPayment() runs it end-to-end — it throws if the bot never answered ok: true, exactly like real Telegram never firing successful_payment after a decline.

import { Composer, type Context } from "@yaebal/core";
import { createTestEnv } from "@yaebal/test";
import { payments } from "@yaebal/payments";

const bot = new Composer<Context>().install(payments({ onPreCheckout: () => true }));
const env = createTestEnv(bot);

await env.createUser().sendSuccessfulPayment({ invoice_payload: "plan:pro" });

part of yaebal — a type-safe, runtime-agnostic Telegram Bot API framework. MIT.