跳到主要内容

Telegram 支付机器人开发小记

· 阅读需 7 分钟
小学后生
Full Stack Developer

随着 Telegram 迈向区块链&小程序时代,Telegram 内部已经与 TON 钱包做了集成,并为了应对 Apple 和 Google 关于数字产品销售的政策监管需要上线了 Telegram Stars 作为支付方式。依托 Telegram 生态的数亿用户,存在着大量机遇,并为区块链走向 Mass Adoption 铺设了一条新的高速公路。本文基于 grammY 框架,分享支付机器人开发过程中的心得,助你成功。

支付机器人

点击此处访问完整 Demo 地址

初始化

在使用测试环境进行机器人开发时,创建 Bot 实例,需要将environment指定为test,否则将会产生401 Unauthorized错误。

另外如果当前的网络环境需要使用科学上网才能访问 Telegram,还需要配置baseFetchConfig.agent为你的代理地址。

Bot Init
new Bot(process.env.BOT_TOKEN!, {
client: {
baseFetchConfig: {
agent: isDevEnv ? new HttpsProxyAgent('http://127.0.0.1:7890') : null
},
environment: isDevEnv ? 'test' : 'prod'
}
})

Stars 支付流程

Pay With Stars
// 1. 调用 `sendInvoice` 发送发票,currency 参数指定为`XTR`
ctx.api.sendInvoice(ctx.chat!.id, 'Title', 'Description', `payload`, 'XTR', [{ label: 'Label', amount: 1 }])

// 2. 检查发票,等待字段 `pre_checkout_query` 的更新
bot.on('pre_checkout_query', (ctx) => {

// 3. 通过 `answerPreCheckoutQuery` 批准或取消订单
ctx.answerPreCheckoutQuery(true)
// ctx.answerPreCheckoutQuery(false, {
// error_message: 'An unexpected error occurred. Please try again later.'
// })
})

// 4. 等待字段 `successful_payment` 的更新
bot.on(':successful_payment', ctx => {

// 5. 支付成功回调,存储成功支付的 `telegram_payment_charge_id`(未来可能需要用它来发起退款)
console.log(ctx.message?.successful_payment.telegram_payment_charge_id)

// 6. 向用户交付其所购买的商品和服务,业务逻辑...
ctx.reply('payment-success').catch(console.error)
})

TON 支付流程

  1. 生成指定钱包的支付链接
function generatePaymentLink(
toWallet: string,
amount: number | string | bigint,
comment: string,
app: "tonhub" | "tonkeeper"
) {
if (app === "tonhub") {
return `https://tonhub.com/transfer/${toWallet}?amount=${toNano(
amount
)}&text=${comment}`;
}

return `https://app.tonkeeper.com/transfer/${toWallet}?amount=${toNano(
amount
)}&text=${comment}`;
}
  1. 将生成的链接以菜单形式返回给用户,并提供check_transaction事件用于检查交易
const tonhubPaymentLink = generatePaymentLink(process.env.OWNER_WALLET!, amount, comment, 'tonhub')
const tonkeeperPaymentLink = generatePaymentLink(process.env.OWNER_WALLET!, amount, comment, 'tonkeeper')

const menu = new InlineKeyboard()
.url("Click to pay in TonHub", tonhubPaymentLink)
.row()
.url("Click to pay in TonKeeper", tonkeeperPaymentLink)
.row()
.text(`I sent ${amount} TON`, "check_transaction");

await ctx.reply(
`Tips`,
{ reply_markup: menu, parse_mode: "HTML" }
);
  1. 监听check_transaction事件,校验支付状态,处理支付成功的逻辑
bot.callbackQuery("check_transaction", checkTransaction);

async function checkTransaction(ctx) {
await verifyTransactionExistance(
process.env.OWNER_WALLET,
ctx.session.amount,
ctx.session.comment
);
}

async function verifyTransactionExistance(
toWallet: Address,
value: number,
comment: string
) {
const endpoint =
process.env.NETWORK === "mainnet"
? "https://toncenter.com/api/v2/jsonRPC"
: "https://testnet.toncenter.com/api/v2/jsonRPC";

const httpClient = new HttpApi(endpoint, {
apiKey: process.env.TONCENTER_TOKEN,
});

const transactions = await httpClient.getTransactions(toWallet, {
limit: 100,
});

let incomingTransactions = transactions.filter(
(tx) => Object.keys(tx.out_msgs).length === 0
);

for (let i = 0; i < incomingTransactions.length; i++) {
let tx = incomingTransactions[i];
// Skip the transaction if there is no comment in it
if (!tx.in_msg?.msg_data) {
continue;
}

// Convert transaction value from nano
let txValue = fromNano(tx.in_msg.value);
// Get transaction comment
let txComment = tx.in_msg.message;
if (txComment === comment && txValue === value.toString()) {
return true;
}
}

return false;
}

注意事项

  • 测试环境账号注册

    在 Telegram 的账号体系中,测试环境与主环境完全隔离,因此在进行测试环境登录时,无法直接使用现有账号进行登录,在扫码时会提示AUTH_TOKEN_INVALID2错误,以及无法收到验证码的情况。 所以你需要先注册一个测试账号,截止 2024 年 8 月,测试账号只能通过 iPhone 端 Telegram 进行。具体操作流程如下:

    1、登录 Telegram iPhone 2、多次点击右下角SettingTab 进入 Debug 页面 3、点击操作列表中的Accounts项 4、点击Login to another account选择Test环境,完成账号注册

    账号注册完成后,就可以按官方流程进入测试环境。在使用测试环境时,您可以采用未加密的 HTTP 链接来测试您的 Web 应用或 Web 登录功能。

    另外测试环境的 Telegram Star 也需要进行购买,不过可以参考下文使用 stripe 提供的测试信用卡无限制进行购买。

  • 信用卡测试支付

    在您的机器人支付功能仍在开发和测试阶段时,请使用 “Stripe 测试模式” 提供商。在此模式下,您可以进行支付操作而不会实际计费任何账户。测试模式中无法使用真实信用卡,但您可以使用测试卡,如 4242 4242 4242 4242 (完整测试卡列表)。您可以随意在测试模式与实时模式间切换,但在正式上线前,请务必查阅上线检查清单

引用参考

视差滚动实践

· 阅读需 8 分钟
小学后生
Full Stack Developer

视差滚动是一种在网页设计和视频游戏中常见的视觉效果技术,它通过在不同速度上移动页面或屏幕上的多层图像,创造出深度感和动感。 这种效果通过前景、中景和背景以不同的速度移动来实现,使得近处的对象看起来移动得更快,而远处的对象移动得较慢。

parallax-scroll

实现方式

1、background-attachment

通过配置该 CSS 属性值为fixed可以达到背景图像的位置相对于视口固定,其他元素正常滚动的效果。但该方法的视觉表现单一,没有纵深,缺少动感。

.parallax-box {
width: 100%;
height: 100vh;
background-image: url("https://picsum.photos/800");
background-size: cover;
background-attachment: fixed;

display: flex;
justify-content: center;
align-items: center;
}

2、Transform 3D

在 CSS 中使用 3D 变换效果,通过将元素划分至不同的纵深层级,在滚动时相对视口不同距离的元素,滚动所产生的位移在视觉上就会呈现越近的元素滚动速度越快,相反越远的元素滚动速度就越慢。

为方便理解,你可以想象正开车行驶在公路上,汽车向前移动,你转头看向窗外,近处的树木一闪而过,远方的群山和风景慢慢的渐行渐远,逐渐的在视野中消失,而天边的太阳却只会在很长的一段距离细微的移动。

.parallax {
perspective: 1px; /* 设置透视效果,为3D变换创造深度感 */
overflow-x: hidden;
overflow-y: auto;
height: 100vh;
}

.parallax__group {
transform-style: preserve-3d; /* 保留子元素3D变换效果 */
position: relative;
height: 100vh;
}

.parallax__layer {
position: absolute;
inset: 0;
display: flex;
justify-content: center;
align-items: center;
}

/* 背景层样式,设置为最远的层 */
.parallax__layer--back {
transform: translateZ(-2px) scale(3);
z-index: 1;
}

/* 中间层样式,设置为中等距离的层 */
.parallax__layer--base {
transform: translateZ(-1px) scale(2);
z-index: 2;
}

/* 前景层样式,设置为最近的层 */
.parallax__layer--front {
transform: translateZ(0px);
z-index: 3;
}

principle

通过设置 perspective 属性,为整个容器创建一个 3D 空间。

使用 transform-style: preserve-3d 保持子元素的 3D 变换效果。

将内容分为多个层(背景、中间、前景),使用 translateZ() 将它们放置在 3D 空间的不同深度。

对于较远的层(如背景层),使用 scale() 进行放大,以补偿由于距离产生的视觉缩小效果。

当用户滚动页面时,由于各层位于不同的 Z 轴位置,它们会以不同的速度移动,从而产生视差效果。

3、ReactScrollParallax

想得到更炫酷的滚动视差效果,纯 CSS 的实现方式就会有些吃力。

如下是在 React 中实现示例,通过监听滚动事件,封装统一的视差组件,来达到多样的动画效果。

const Parallax = ({ children, effects = [], speed = 1, style = {} }) => {
// 状态hooks:用于存储动画效果的当前值
const [transform, setTransform] = useState("");

useEffect(() => {
if (!Array.isArray(effects) || effects.length === 0) {
console.warn("ParallaxElement: effects should be a non-empty array");
return;
}

const handleScroll = () => {
// 计算滚动进度
const scrollProgress =
(window.scrollY /
(document.documentElement.scrollHeight - window.innerHeight)) *
speed;

let transformString = "";

// 处理每个效果
effects.forEach((effect) => {
const { property, startValue, endValue, unit = "" } = effect;
const value =
startValue +
(endValue - startValue) * Math.min(Math.max(scrollProgress, 0), 1);

switch (property) {
case "translateX":
case "translateY":
transformString += `${property}(${value}${unit}) `;
break;
case "scale":
transformString += `scale(${value}) `;
break;
case "rotate":
transformString += `rotate(${value}${unit}) `;
break;
// 更多的动画效果...
default:
console.warn(`Unsupported effect property: ${property}`);
}
});

// 更新状态
setTransform(transformString);
};

window.addEventListener("scroll", handleScroll);
// 初始化位置
handleScroll();

return () => {
window.removeEventListener("scroll", handleScroll);
};
}, [effects, speed]);

// 渲染带有计算样式的子元素
return <div style={{ ...style, transform }}>{children}</div>;
};

在此基础上你可以添加缓动函数使动画效果更加平滑;以及使用requestAnimationFrame获得更高的动画性能。

requestAnimationFrame 带来的性能提升

同步浏览器渲染周期:requestAnimationFrame 会在浏览器下一次重绘之前调用指定的回调函数。这确保了动画更新与浏览器的渲染周期同步,从而产生更流畑的动画效果。

提高性能:与使用 setInterval 或 setTimeout 相比,requestAnimationFrame 可以更高效地管理动画。它只在浏览器准备好进行下一次重绘时才会执行,避免了不必要的计算和重绘。

优化电池使用:在不可见的标签页或最小化的窗口中,requestAnimationFrame 会自动暂停,这可以节省 CPU 周期和电池寿命。

适应显示器刷新率:requestAnimationFrame 会自动适应显示器的刷新率。这意味着在 60Hz、120Hz 或其他刷新率的显示器上,动画都能保持流畑。

避免丢帧:由于与浏览器的渲染周期同步,使用 requestAnimationFrame 可以减少丢帧现象,特别是在高负荷情况下。

更精确的时间控制:requestAnimationFrame 提供了一个时间戳参数,允许更精确地控制动画的时间。

4、组件库方案

在当前成熟的前端生态中,想要获得精彩的视差动画效果,你可以通过现有的开源组件库来高效的完成开发。

以下是一些你可以尝试的主流组件库:

引用参考

MDN - background-attachment

MDN - transform-style

Pure CSS Parallax Websites

How to create parallax scrolling with CSS

视差滚动实践

移动端调试指南

· 阅读需 5 分钟
小学后生
Full Stack Developer

当移动端 web 项目部署在生产环境,项目部署在 Android/iOS 或其他移动设备下发生问题, 或想要模拟请求与拦截响应数据时,你会通过什么方式来进行处理与调试?

当项目是移动端的普通网页时,你可以通过 Chrome 浏览器自带的 chrome://inspect 功能,通过数据线连接真机设备以实现开发调试。

但当遇到需要模拟特定请求或响应内容、注入脚本、修改请求响应头的场景时,inspect 就难以应对了。又或者,你的项目作为 WebView 或 JsBridge 页面嵌入在其他的第三方应用内,在这几种场景下,你就可以使用代理抓包工具来完成更高级的调试操作。

常见的主流代理工具有:Fiddler、Charles,本文主要讲述利用Whistle进行调试操作,Whistle 基于 Node 实现跨平台,无需第三方安装包,更符合前端的操作习惯。

使用 chrome://inspect

设备开启 USB 调试

在访问调试工具之前,你需要对设备开放基础的调试配置,在 Android 和 iOS 下有不同的开启方式,你可以访问下面链接了解更多:

访问调试工具

在完成设备的配置后,请在浏览器地址栏中输入 chrome://inspect 访问开发者工具。

inspect page

dev-tools

使用 Whistle

官方介绍

whistle(读音[ˈwɪsəl],拼音[wēisǒu]) 基于 Node 实现的跨平台 web 调试代理工具,类似的工具有 Windows 平台上的 Fiddler,主要用于查看、修改 HTTP、HTTPS、Websocket 的请求、响应,也可以作为 HTTP 代理服务器使用,不同于 Fiddler 通过断点修改请求响应的方式,whistle 采用的是类似配置系统 hosts 的方式,一切操作都可以通过配置实现,支持域名、路径、正则表达式、通配符、通配路径等多种匹配方式,且可以通过 Node 模块扩展功能

安装&启动

步骤: 安装 Node > 安装 whistle > 启动 whistle > 配置代理 > 安装根证书

# 安装
npm install -g whistle

# 检查: 执行下方命令后如果正常输出whistle帮助信息, 代表安装成功
w2 help

# 启动: 运行后默认访问地址为 http://127.0.0.1:8899/
w2 start

更多命令见 官方文档

代理&证书配置

在使用工具前,请在移动设备上安装工具的 HTTPS 证书,以获取完整的调试能力。

当代理工具拦截 HTTPS 请求时,它会充当客户端和服务器之间的中间人(MITM),并生成一个伪造的证书来替换原有的服务器证书。如果客户端没有信任这个伪造的证书,就会出现证书错误。

qrcode

network

原理&流程

principle

常用规则

# 修改请求列表显示Style
style://color=@fff&fontStyle=italic&bgColor=red
# 修改UserAgent
ua://{ua}
# 修改状态码
statusCode://[statusCode]
# 修改请求头/响应头
reqHeaders://{value} resHeaders://{value}
# 修改请求内容
reqBody://{request.json}
# 修改响应内容
resBody://{response.json}
# 模拟延时
reqDelay://[delayTime]
# 处理跨域
resCors://*
# 修改Host配置
[originHost] [targetHost]

引用参考

Whistle