useInvoice
提供 Telegram 原生支付功能的 Hook,让你的 Mini App 可以接收支付并处理订单。
导入
typescript
import { useInvoice } from '@xcloud/ui-telegram';示例
API
useInvoice
typescript
const { openInvoice, status, loading, error, isAvailable } = useInvoice(options);参数
| 参数 | 类型 | 必填 | 描述 |
|---|---|---|---|
| options | InvoiceOptions | 否 | Hook 配置选项 |
InvoiceOptions
| 属性 | 类型 | 描述 |
|---|---|---|
| onSuccess | () => void | 支付成功回调 |
| onFailure | (status: InvoiceStatus) => void | 支付失败回调 |
| onCancel | () => void | 支付取消回调 |
返回值
| 属性 | 类型 | 描述 |
|---|---|---|
| openInvoice | (params: InvoiceParams) => Promise<InvoiceStatus | null> | 打开支付发票 |
| status | InvoiceStatus | null | 当前支付状态 |
| loading | boolean | 是否正在处理支付 |
| error | Error | null | 错误信息 |
| isAvailable | () => boolean | 检查支付功能是否可用 |
InvoiceParams
| 属性 | 类型 | 必填 | 描述 |
|---|---|---|---|
| slug | string | 否* | 发票 slug(与 url 二选一) |
| url | string | 否* | 发票 URL(与 slug 二选一) |
InvoiceStatus
支付状态类型:
| 值 | 描述 |
|---|---|
'paid' | 支付成功 |
'failed' | 支付失败 |
'pending' | 支付待处理 |
'cancelled' | 用户取消支付 |
使用示例
基础支付 - 使用 Slug
使用产品 slug 发起支付:
tsx
import { useInvoice } from '@xcloud/ui-telegram';
function ProductPage({ productSlug }: { productSlug: string }) {
const { openInvoice, loading } = useInvoice();
const handlePurchase = async () => {
const result = await openInvoice({ slug: productSlug });
if (result === 'paid') {
alert('购买成功!');
}
};
return (
<button onClick={handlePurchase} disabled={loading}>
{loading ? '处理中...' : '立即购买'}
</button>
);
}基础支付 - 使用 URL
使用发票 URL 发起支付(通常来自扫码或链接):
tsx
import { useInvoice } from '@xcloud/ui-telegram';
function PaymentPage({ invoiceUrl }: { invoiceUrl: string }) {
const { openInvoice, loading } = useInvoice();
const handlePay = async () => {
const result = await openInvoice({ url: invoiceUrl });
if (result === 'paid') {
// 支付成功处理
confirmOrder();
}
};
return (
<button onClick={handlePay} disabled={loading}>
确认支付
</button>
);
}使用回调处理支付结果
通过回调函数处理不同的支付状态:
tsx
import { useInvoice } from '@xcloud/ui-telegram';
function CheckoutButton() {
const { openInvoice } = useInvoice({
onSuccess: () => {
console.log('支付成功!');
showSuccessMessage();
redirectToOrderPage();
},
onFailure: (status) => {
console.error('支付失败:', status);
showErrorMessage('支付失败,请重试');
},
onCancel: () => {
console.log('用户取消了支付');
showInfoMessage('支付已取消');
},
});
const handleCheckout = async () => {
await openInvoice({ slug: 'product_123' });
};
return <button onClick={handleCheckout}>结账</button>;
}电商购物车支付
完整的购物车支付流程:
tsx
import { useInvoice } from '@xcloud/ui-telegram';
import { useState } from 'react';
function ShoppingCart({ items, totalAmount }: CartProps) {
const [orderId, setOrderId] = useState<string | null>(null);
const { openInvoice, loading } = useInvoice({
onSuccess: () => {
// 标记订单为已支付
markOrderAsPaid(orderId);
// 清空购物车
clearCart();
// 跳转到订单详情
navigate(`/orders/${orderId}`);
},
onFailure: () => {
// 取消订单
cancelOrder(orderId);
},
});
const handleCheckout = async () => {
// 1. 创建订单
const order = await createOrder(items);
setOrderId(order.id);
// 2. 生成发票
const invoice = await generateInvoice(order.id);
// 3. 打开支付
await openInvoice({ slug: invoice.slug });
};
return (
<div>
<CartItems items={items} />
<Total amount={totalAmount} />
<button onClick={handleCheckout} disabled={loading}>
{loading ? '处理中...' : `支付 $${totalAmount.toFixed(2)}`}
</button>
</div>
);
}订阅支付
处理订阅或会员支付:
tsx
import { useInvoice } from '@xcloud/ui-telegram';
function SubscriptionPlans() {
const { openInvoice, loading } = useInvoice({
onSuccess: () => {
// 激活订阅
activateSubscription();
showSuccessToast('订阅已激活!');
},
});
const handleSubscribe = async (plan: 'monthly' | 'yearly') => {
// 生成订阅发票
const invoice = await createSubscriptionInvoice(plan);
// 打开支付
const result = await openInvoice({ slug: invoice.slug });
if (result === 'paid') {
// 记录订阅开始时间
recordSubscriptionStart(plan);
}
};
return (
<div>
<PlanCard
name="月度订阅"
price={9.99}
onSelect={() => handleSubscribe('monthly')}
loading={loading}
/>
<PlanCard
name="年度订阅"
price={99.99}
onSelect={() => handleSubscribe('yearly')}
loading={loading}
/>
</div>
);
}虚拟商品购买
购买应用内虚拟商品:
tsx
import { useInvoice } from '@xcloud/ui-telegram';
function CoinShop() {
const { openInvoice, loading } = useInvoice({
onSuccess: () => {
// 发放虚拟货币
addCoins(selectedPackage.coins);
showReward(`+${selectedPackage.coins} 金币`);
},
});
const coinPackages = [
{ id: 1, coins: 100, price: 0.99 },
{ id: 2, coins: 500, price: 3.99 },
{ id: 3, coins: 1200, price: 8.99 },
];
const handlePurchase = async (pkg: CoinPackage) => {
const invoice = await createCoinPurchaseInvoice(pkg.id);
await openInvoice({ slug: invoice.slug });
};
return (
<div>
{coinPackages.map((pkg) => (
<PackageCard
key={pkg.id}
coins={pkg.coins}
price={pkg.price}
onBuy={() => handlePurchase(pkg)}
loading={loading}
/>
))}
</div>
);
}捐赠/打赏功能
实现创作者打赏功能:
tsx
import { useInvoice } from '@xcloud/ui-telegram';
import { useState } from 'react';
function TipCreator({ creatorId }: { creatorId: string }) {
const [amount, setAmount] = useState(5);
const { openInvoice, loading } = useInvoice({
onSuccess: () => {
showThankYouMessage();
sendNotificationToCreator(creatorId, amount);
},
});
const handleTip = async () => {
const invoice = await createTipInvoice(creatorId, amount);
await openInvoice({ slug: invoice.slug });
};
const presetAmounts = [5, 10, 20, 50];
return (
<div>
<h3>打赏创作者</h3>
<div>
{presetAmounts.map((preset) => (
<button
key={preset}
onClick={() => setAmount(preset)}
className={amount === preset ? 'selected' : ''}
>
${preset}
</button>
))}
</div>
<button onClick={handleTip} disabled={loading}>
{loading ? '处理中...' : `打赏 $${amount}`}
</button>
</div>
);
}处理支付错误
完整的错误处理:
tsx
import { useInvoice } from '@xcloud/ui-telegram';
function RobustCheckout() {
const { openInvoice, loading, error } = useInvoice({
onFailure: (status) => {
if (status === 'failed') {
showError('支付处理失败,请稍后重试');
}
},
});
const handlePay = async () => {
try {
const result = await openInvoice({ slug: 'product_123' });
if (!result) {
// 用户可能关闭了支付窗口
console.log('Payment was not completed');
return;
}
if (result === 'paid') {
// 支付成功
processOrder();
} else if (result === 'cancelled') {
// 用户明确取消
showInfo('您已取消支付');
}
} catch (err) {
console.error('Payment error:', err);
showError('支付过程中出现错误');
}
};
return (
<div>
<button onClick={handlePay} disabled={loading}>
支付
</button>
{error && <ErrorMessage>{error.message}</ErrorMessage>}
</div>
);
}检查可用性
在不支持的环境中提供替代方案:
tsx
import { useInvoice } from '@xcloud/ui-telegram';
function PaymentButton() {
const { openInvoice, isAvailable } = useInvoice();
if (!isAvailable()) {
return (
<div>
<p>当前环境不支持 Telegram 支付</p>
<button onClick={redirectToWebPayment}>
使用网页支付
</button>
</div>
);
}
const handlePay = async () => {
await openInvoice({ slug: 'product_123' });
};
return <button onClick={handlePay}>Telegram 支付</button>;
}与 QR 扫码集成
扫码支付流程:
tsx
import { useInvoice, useQRScanner } from '@xcloud/ui-telegram';
function QRPayment() {
const { open: openScanner } = useQRScanner();
const { openInvoice, loading } = useInvoice({
onSuccess: () => {
showSuccess('支付成功!');
},
});
const handleScanAndPay = async () => {
// 1. 扫描二维码
const invoiceUrl = await openScanner('扫描支付二维码');
if (!invoiceUrl) {
return; // 用户取消扫描
}
// 2. 验证是否为有效的支付链接
if (!invoiceUrl.startsWith('https://t.me/$')) {
showError('无效的支付二维码');
return;
}
// 3. 打开支付
await openInvoice({ url: invoiceUrl });
};
return (
<button onClick={handleScanAndPay} disabled={loading}>
{loading ? '处理中...' : '扫码支付'}
</button>
);
}后端集成
生成发票
在后端创建支付发票:
typescript
// 后端 API 端点
async function createInvoice(productId: string, userId: string) {
// 1. 验证产品和用户
const product = await getProduct(productId);
const user = await getUser(userId);
// 2. 创建订单
const order = await createOrder({
userId,
productId,
amount: product.price,
status: 'pending',
});
// 3. 通过 Telegram Bot API 创建发票
const invoice = await telegramBot.createInvoiceLink({
title: product.name,
description: product.description,
payload: order.id, // 用于回调时识别订单
provider_token: process.env.PAYMENT_PROVIDER_TOKEN,
currency: 'USD',
prices: [{ label: product.name, amount: product.price * 100 }], // 价格以分为单位
});
return {
invoiceUrl: invoice.url,
invoiceSlug: extractSlugFromUrl(invoice.url),
};
}处理支付回调
接收并处理支付成功的回调:
typescript
// Webhook 端点
async function handlePaymentCallback(update: TelegramUpdate) {
if (update.pre_checkout_query) {
// 预检查支付
const { id, invoice_payload } = update.pre_checkout_query;
const orderId = invoice_payload;
// 验证订单
const order = await getOrder(orderId);
if (!order || order.status !== 'pending') {
// 拒绝支付
await telegramBot.answerPreCheckoutQuery(id, false, '订单无效');
return;
}
// 允许支付
await telegramBot.answerPreCheckoutQuery(id, true);
}
if (update.message?.successful_payment) {
// 支付成功
const { invoice_payload, total_amount, currency } =
update.message.successful_payment;
const orderId = invoice_payload;
// 更新订单状态
await updateOrder(orderId, {
status: 'paid',
paidAt: new Date(),
amount: total_amount / 100, // 转换回实际金额
});
// 发放商品/服务
await fulfillOrder(orderId);
// 发送确认消息
await telegramBot.sendMessage(
update.message.chat.id,
'支付成功!您的订单已处理。',
);
}
}最佳实践
1. 始终验证支付结果
不要仅依赖客户端的支付状态:
tsx
const handlePay = async () => {
const result = await openInvoice({ slug: productSlug });
if (result === 'paid') {
// 向后端确认支付状态
const verified = await verifyPayment(orderId);
if (verified) {
// 确认支付成功
processOrder();
}
}
};2. 提供清晰的用户反馈
让用户知道支付的每一步:
tsx
const { openInvoice, loading } = useInvoice({
onSuccess: () => {
showSuccess('支付成功!正在处理您的订单...');
},
onFailure: () => {
showError('支付失败,请重试或联系客服');
},
onCancel: () => {
showInfo('您已取消支付');
},
});3. 处理网络错误
支付过程可能因网络问题中断:
tsx
const handlePay = async () => {
try {
await openInvoice({ slug: productSlug });
} catch (error) {
if (error.message.includes('network')) {
showError('网络连接失败,请检查网络后重试');
} else {
showError('支付出现问题,请稍后重试');
}
}
};4. 防止重复支付
避免用户多次点击造成重复支付:
tsx
function CheckoutButton() {
const [isPaying, setIsPaying] = useState(false);
const { openInvoice, loading } = useInvoice();
const handlePay = async () => {
if (isPaying) return;
setIsPaying(true);
try {
await openInvoice({ slug: productSlug });
} finally {
setIsPaying(false);
}
};
return (
<button onClick={handlePay} disabled={loading || isPaying}>
{loading || isPaying ? '处理中...' : '支付'}
</button>
);
}5. 记录支付日志
便于追踪和调试:
tsx
const { openInvoice } = useInvoice({
onSuccess: () => {
logPaymentEvent('payment_success', { orderId, amount });
},
onFailure: (status) => {
logPaymentEvent('payment_failure', { orderId, status });
},
});注意事项
- 支付功能仅在 Telegram 环境中可用
- 需要 Bot 配置支付提供商(如 Stripe、YooMoney 等)
- 货币和金额由后端控制,前端只负责发起支付
- 支付结果应该在后端通过 webhook 验证
- 不要在客户端存储敏感的支付信息
- 测试时使用 Telegram 提供的测试支付环境
支持的支付提供商
Telegram Payments 支持多个支付提供商:
- Stripe
- YooMoney (formerly Yandex.Money)
- Sberbank
- Tranzzo
- PayMaster
- 等等
具体可用的支付方式取决于你的 Bot 配置和用户所在地区。
相关 API
- useQRScanner - QR 扫码
- useInitData - 获取用户信息