Tapaya 如何在一个下午为 POS 应用加上店内刷卡支付

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

中文
复制
How Tapaya added in-store card payments to an Expo POS app in an afternoon

本文由 Roman Kuchařík 撰写——他是 tapaya. 的联合创始人。这家位于布拉格的公司开发纯软件 SoftPOS 基础设施,让任何移动设备都能变成安全的支付终端。

每个开发 POS 应用的团队最终都会撞上同一个问题:如何受理线下刷卡支付。

产品能跑、商户愿意试用、路线图也很清晰——可一旦要接入支付,事情立刻复杂好几倍。PCI 合规、EMV 认证、收单机构关系、硬件依赖,再加上漫长的开发周期,离真正上线还差得远。

在 Tapaya,我们认为这套流程本该简单得多。

于是我们决定做个测试:用 Expo 搭一个真正的 POS 应用,再通过我们自己的 SDK 接入门店刷卡支付,看看能有多快。

结果:从有想法到第一笔支付获批,用了不到一个下午。

本文会讲:

  • 这个应用的灵感来自哪里

  • 我们做了什么

  • 以及我们究竟是怎样把刷卡支付集成进一个 Expo 应用的

Tapaya 应用

这个应用的点子从何而来?

最初的想法很简单。

我们想给附近一家常去的咖啡店做一个轻量级 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 编码代理提供了这些材料:

执行 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 应用

Tapaya 在后台处理了什么

尽管集成接口刻意保持精简,SDK 却在背后封装了大量复杂性,包括:

  • EMV 内核与 NFC 通信

  • Apple Tap to Pay 权限

  • 收单机构连接

  • 卡组织路由

  • KYB 入驻流程

  • 支付合规基础设施

这让从原型到可上线的线下支付所需的时间大幅缩短。

快速上手

要构建一个可用的集成:

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 越小通常越好。

来源: Expo Blog← 返回首页