Tapaya 如何在一个下午为 POS 应用加上店内刷卡支付
用 Expo 搭 POS 应用并通过 Tapaya SDK 接入刷卡支付,从想法到首笔支付获批不到一个下午。 <Figure src="/media/fig-1.png" alt="Tapaya 应用" width="1920" height="1200" /
中文
复制

本文由 Roman Kuchařík 撰写——他是 tapaya. 的联合创始人。这家位于布拉格的公司开发纯软件 SoftPOS 基础设施,让任何移动设备都能变成安全的支付终端。
…
每个开发 POS 应用的团队最终都会撞上同一个问题:如何受理线下刷卡支付。
产品能跑、商户愿意试用、路线图也很清晰——可一旦要接入支付,事情立刻复杂好几倍。PCI 合规、EMV 认证、收单机构关系、硬件依赖,再加上漫长的开发周期,离真正上线还差得远。
在 Tapaya,我们认为这套流程本该简单得多。
于是我们决定做个测试:用 Expo 搭一个真正的 POS 应用,再通过我们自己的 SDK 接入门店刷卡支付,看看能有多快。
结果:从有想法到第一笔支付获批,用了不到一个下午。
本文会讲:
-
这个应用的灵感来自哪里
-
我们做了什么
-
以及我们究竟是怎样把刷卡支付集成进一个 Expo 应用的

这个应用的点子从何而来?
最初的想法很简单。
我们想给附近一家常去的咖啡店做一个轻量级 POS 系统——快、极简,在普通手机上就能用,不需要传统的支付硬件。
大多数小商户不想为了收款去管理一堆终端、线缆、充电底座和好几套系统。他们想要的是一台设备就能搞定:
-
管理商品
-
处理交易
-
商户入驻
-
即时接受拍卡支付
我们还想在内部验证一个更大的问题:
开发者能不能在 Expo 里做出一个可上生产的 SoftPOS 体验,而不用在支付基础设施上耗上几个月?
这就成了那次实验。而我们恰好有一个绝佳的机会,在布拉格由 STRV 和 Expo 主办的 Hackathon 上把这个实验做出来!

我们构建了什么应用?
我们用 Expo 构建了一款简单的跨平台 POS 应用。

这个应用包含:
-
商品库存界面
-
结账流程
-
交易记录
-
商户入驻(KYB)
-
线下拍卡收款
目标不是做一个复杂的企业级 POS 系统,而是做一个干净、能跑通的原型,展示现代支付基础设施能以多快的速度集成进 React Native 应用。
到下午结束时,这个应用已经可以:
-
完成商户入驻
-
通过 Tapaya 平台完成认证
-
接受非接触式银行卡支付
-
在支持 NFC 的 iPhone 或 Android 设备上直接处理沙箱环境中已批准的交易
不需要外接支付终端。
为什么选 Expo
动手之前,我们评估了几种框架和工作流,最终选了 Expo,因为它把支付所需的原生移动端配置大幅简化了。
Expo Config Plugin 简化了原生 SDK 的接入
在 app.json 里加一个 config plugin,再跑一次 expo prebuild,iOS 和 Android 的原生绑定就自动生成了。
不需要手动写 Swift 或 Kotlin。
EAS Build 简化了 iOS Tap to Pay 的 entitlement
手动配置 Apple Tap to Pay 很快就会变得很麻烦。Expo 的 Services 帮我们处理了大部分 provisioning 和 entitlement 流程。
EAS Update 提升了迭代速度
测试期间,我们可以快速推送 UI 和逻辑更新,不用反复等 App Store 的审核周期。
Expo Router 加快了开发
我们搭了这几个界面:
-
销售
-
交易
-
KYB 入驻
-
设置
几乎没有写什么模板代码。
Tapaya 的 SDK 就是为 Expo 做的
集成过程和 Expo 的 config plugin 体系配合得完全符合预期,配置顺利得出乎意料。
我们是这样搭出这个应用的:
上午先用 Expo 搭好核心 POS 界面:
-
简单的库存管理
-
结账界面
-
交易处理
UI 流程跑通之后,我们接着接入线下支付。
为了加快开发,我们给 AI 编码代理提供了这些材料:
-
我们期望的支付流程
-
POS 体验的集成目标
执行 expo prebuild、部署到真机、再拍一张支付卡,我们就拿到了第一笔审批通过的交易。
从注册到沙箱支付成功,整个集成大约花了 30 分钟。
集成过程是这样的:
1. 添加 Config Plugin
{
"expo": {
"plugins": [
["@tapayadot/accept-react-native"]
]
}
}
2. 安装 SDK 并生成原生代码
npm install @tapayadot/accept-react-native@latest
npx expo prebuild --clean
3. 搭建后端
移动端应用要能用 SDK 完成认证,后端必须先为每个商户生成一个短期有效的登录 token。
流程如下:
移动端应用调用你的后端
后端用 Server Secret Token 调用 Tapaya API
后端把临时登录 token 返回给移动端应用
Server Secret Token 绝不能暴露给客户端。
步骤 A:注册商户
这一步通常只在商户注册时做一次。
POST /merchant/auth/register
Authorization: Bearer YOUR_SERVER_SECRET_TOKEN
{
"merchantToken": "your_internal_db_id",
"merchantName": "Acme Coffee",
"email": "owner@acme.com"
}
这会创建商户记录,后续的 SDK 认证和支付处理都会关联到它。
步骤 B:生成登录 token
每次 SDK 初始化时都要执行这一步。
移动端应用应调用你自己的后端登录接口,后端再向 Tapaya 请求一个临时登录 token,并转发回设备。
这样 Server Secret Token 始终安全地留在服务端。
POST /merchant/auth/login
Authorization: Bearer YOUR_SERVER_SECRET_TOKEN
{
"merchantToken": "your_internal_db_id",
"allowOnboarding": true
}
响应
{
"token": "EesrFq4PUK1WxHUj93hkrKASDFp8GxJ0"
}
把这个 token 返回给移动端应用,并立即用它完成 SDK 认证。
该 token:
-
是临时的
-
与 merchantToken 标识的商户绑定
-
每次 SDK 初始化时都应重新获取
-
不应长期存储在设备上
安全提示
Server Secret Token 拥有你 Tapaya 平台账号的完整访问权限。
出于安全考虑:
-
绝不能把它硬编码进移动应用
-
绝不能把它暴露在客户端代码中
-
绝不能把它提交到版本控制
-
在后端以环境变量的形式安全保存
-
一旦泄露,立即轮换
生成登录 token 时也必须使用正确的 merchantToken,因为它决定了 SDK 能访问哪个商户账户和资金。
4. 初始化 SDK
import AcceptSDK from '@tapayadot/accept-react-native';
import { useEffect } from 'react';
export default function RootLayout() {
useEffect(() => {
async function boot() {
await AcceptSDK.initialize(true);
const merchantToken = await myBackend.login();
await AcceptSDK.authenticate(merchantToken);
}
boot();
}, []);
return <Slot />;
}
5. 发起一笔银行卡支付
import AcceptSDK, { CardPaymentIntent } from '@tapayadot/accept-react-native';
import * as Crypto from 'expo-crypto';
async function handleCharge(amountCents) {
const intent = {
paymentIntentId: Crypto.randomUUID(),
amount: amountCents,
requestedCurrency: 'USD',
};
const result = await AcceptSDK.payments.startCardPayment(
intent,
(status) => console.log('Status:', status),
(msg, err) => console.error(msg, err),
);
return result;
}
整个支付流程归结为三个核心函数:
-
initialize
-
authenticate
-
startCardPayment
商户入驻与 KYB
收款只是真实 POS 工作流的一部分。
商户还需要完成入驻和 KYB 验证,才能处理交易。
SDK 内置了入驻流程:
import AcceptSDK from '@tapayadot/accept-react-native';
await AcceptSDK.identity.presentKyb();
// or via REST API and Webhooks
// or via Tapaya Platform on the Web
处理支付
入驻完成后,startCardPayment() 会打开原生的 tap to pay 界面。
const result = await AcceptSDK.payments.startCardPayment(
{
paymentIntentId: Crypto.randomUUID(),
amount: 15000,
requestedCurrency: 'USD',
},
(status) => setPaymentStatus(status),
(msg, err) => setError(`${msg}: ${err}`),
);
if (result.status === 'APPROVED') {
router.push('/receipt');
}
金额使用最小货币单位:
15000 = 150.00 USD

Tapaya 在后台处理了什么
尽管集成接口刻意保持精简,SDK 却在背后封装了大量复杂性,包括:
-
EMV 内核与 NFC 通信
-
Apple Tap to Pay 权限
-
收单机构连接
-
卡组织路由
-
KYB 入驻流程
-
支付合规基础设施
这让从原型到可上线的线下支付所需的时间大幅缩短。
快速上手
要构建一个可用的集成:
-
在 Tapaya Sandbox Platform 创建沙箱账号
-
安装 SDK
pm install @tapayadot/accept-react-native@latest
-
在 app.json 中添加 config plugin
-
运行:
px expo prebuild --clean
-
实现:
-
initialize
-
authenticate
-
startCardPayment
-
-
在真机上运行应用
设备要求
Android
-
Android 11+
-
支持 NFC
-
启用硬件 keystore
-
设备未 root
iPhone
-
iPhone XS 或更新机型
-
iOS 18+
-
未越狱
接下来要做的
支付已经完整集成,我们现在把精力放在:
-
蓝牙热敏打印机支持
-
基于 Expo SQLite 的离线交易队列
-
扩展商户工具
-
更多 POS 流程
支付基础设施已经就位,所以我们现在可以专注于改善整体商户体验,而不必重建支付通道。
用 Expo 构建 POS 的几点体会
过去,POS 团队往往要在两者之间做选择:
-
快速的跨平台开发
-
还是生产级的线下支付
Expo 简化了应用层,Tapaya 简化了支付基础设施层。
最终,整个集成落到三次函数调用上——不是简化过的 demo,而是真正驱动这个应用的实现。
这种简洁也让 SDK 特别适合 AI 辅助的开发流程。
而在支付基础设施里,API 越小通常越好。