::Users·Development

How Tapaya added in-store card payments to a POS app in an afternoon

In one short hackathon in Prague this team added credit card payments to their POS system.

Roman Kuchařík

Roman Kuchařík

Guest Author

How Tapaya added in-store card payments to an Expo POS app in an afternoon

This is a guest post from Roman Kuchařík - the co-founder of tapaya., a Prague-based startup building software-only SoftPOS infrastructure that turns any mobile device into a secure payment terminal.

Every team building a POS app eventually runs into the same problem: accepting in-person card payments.

You can have a working product, merchants ready to test, and a clear roadmap, but the moment you add payments, things become significantly more complicated. Suddenly you are dealing with PCI requirements, EMV certifications, acquirer relationships, hardware dependencies, and long development cycles before anything is production ready.

At Tapaya, we believe this process should be much simpler.

So we decided to test how quickly we could build a real Expo-based POS application and add in-store card payments using our own SDK.

The result: from idea to first approved payment took less than an afternoon!

This post explains:

  • what inspired the app
  • what we built
  • and exactly how we integrated card payments into an Expo app

What inspired the app idea?

The original idea was simple.

We wanted to build a lightweight POS system for one of our favorite local coffee shops, something fast, minimal, and easy to use on a regular phone instead of traditional payment hardware.

Most small merchants do not want to manage separate terminals, cables, charging docks, and multiple systems just to accept payments. They want a single device that can:

  • manage products
  • handle transactions
  • onboard merchants
  • accept tap to pay card payments instantly

We also wanted to test a broader question internally:

Could a developer build a production ready SoftPOS experience in Expo without spending months on payment infrastructure?

That became the experiment. And we had an incredible opportunity to take on this experiment at a Hackathon in Prague hosted by STRV and Expo!

What app did we build?

We built a simple cross platform POS application using Expo.

The app included:

  • a product inventory screen
  • a checkout flow
  • transaction history
  • merchant onboarding (KYB)
  • in-person tap to pay card acceptance

The goal was not to build a complex enterprise POS system. The goal was to create a clean functional prototype that demonstrated how quickly modern payment infrastructure can be integrated into a React Native application.

By the end of the afternoon, the app could:

  • onboard a merchant
  • authenticate with the Tapaya platform
  • accept contactless card payments
  • process approved sandbox transactions directly on an NFC enabled iPhone or Android device

No external payment terminal required.

Why we chose Expo

Before building the app, we evaluated several frameworks and workflows. We ultimately chose Expo because it dramatically simplified the native mobile setup required for payments.

Expo Config Plugins simplified Native SDK setup

By adding a single config plugin to app.json and running expo prebuild, the native iOS and Android bindings were generated automatically.

No manual Swift or Kotlin setup was required.

EAS Build simplified iOS Tap to Pay entitlements

Setting up Apple Tap to Pay manually can become complicated quickly. Expo’s Services handled much of the provisioning and entitlement workflow for us.

EAS Update improved iteration speed

We could push UI and logic updates rapidly without repeatedly waiting for App Store review cycles during testing.

Expo Router accelerated development

We built screens for:

  • Sales
  • Transactions
  • KYB onboarding
  • Settings

The process required very little boilerplate.

Tapaya’s SDK was built for Expo

The integration worked exactly as expected with Expo’s config plugin system, which made the setup surprisingly smooth.

Here’s how we built the app:

We started by building the core POS interface in Expo during the morning:

  • simple inventory management
  • checkout screen
  • transaction handling

Once the UI flow was working, we moved on to adding in-person payments.

To speed up development, we provided an AI coding agent with:

After running expo prebuild, deploying to a physical device, and tapping a payment card, we received our first approved transaction.

From signup to successful sandbox payment, the full integration took roughly 30 minutes.

This is what the integration process looked like:

1. Add the Config Plugin

{
"expo": {
"plugins": [
["@tapayadot/accept-react-native"]
]
}
}

2. Install the SDK and generate Native code

npm install @tapayadot/accept-react-native@latest
npx expo prebuild --clean

3. Set up your backend

Before the mobile app can authenticate with the SDK, your backend must generate a short lived login token for each merchant.

The flow works like this:

  1. The mobile app calls your backend
  2. Your backend calls the Tapaya API using your Server Secret Token
  3. Your backend returns a temporary login token to the mobile app

Your Server Secret Token should never be exposed to the client.

Step A: Register the merchant

This is typically done once during merchant sign up.

POST /merchant/auth/register
Authorization: Bearer YOUR_SERVER_SECRET_TOKEN
{
"merchantToken": "your_internal_db_id",
"merchantName": "Acme Coffee",
"email": "owner@acme.com"
}

This creates the merchant record that will later be linked to SDK authentication and payment processing.

Step B: Generate a login token

This step happens every time the SDK initializes.

Your mobile app should call your own backend login endpoint. Your backend then requests a temporary login token from Tapaya and forwards it back to the device.

This ensures your Server Secret Token always remains securely on the server.

POST /merchant/auth/login
Authorization: Bearer YOUR_SERVER_SECRET_TOKEN
{
"merchantToken": "your_internal_db_id",
"allowOnboarding": true
}

Response

{
"token": "EesrFq4PUK1WxHUj93hkrKASDFp8GxJ0"
}

Return this token to the mobile app and immediately use it to authenticate the SDK.

The token:

  • is temporary
  • is tied to the merchant identified by merchantToken
  • should be fetched fresh during every SDK initialization
  • should never be stored long term on the device

Security notes

Your Server Secret Token grants full access to your Tapaya platform account.

For security reasons:

  • never embed it inside the mobile app
  • never expose it in client side code
  • never commit it to source control
  • store it securely as an environment variable on your backend
  • rotate it immediately if it is ever compromised

It is also critical to generate login tokens using the correct merchantToken, since that determines which merchant account and funds the SDK can access.

4. Initialize the 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. Start a card payment

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;
}

The full payment flow came down to three core functions:

  • initialize
  • authenticate
  • startCardPayment

Merchant onboarding and KYB

Accepting payments is only part of a real POS workflow.

Merchants also need onboarding and KYB verification before they can process transactions.

The SDK includes a built in onboarding flow:

import AcceptSDK from '@tapayadot/accept-react-native';
await AcceptSDK.identity.presentKyb();
// or via REST API and Webhooks
// or via Tapaya Platform on the Web

Processing payments

Once onboarding is complete, startCardPayment() opens the native tap to pay interface.

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');
}

Amounts use the smallest currency unit:

15000 = 150.00 USD

What Tapaya handles behind the scenes

Although the integration surface is intentionally small, the SDK abstracts a large amount of complexity, including:

  • EMV kernel and NFC communication
  • Apple Tap to Pay entitlements
  • Acquirer connectivity
  • Card network routing
  • KYB onboarding workflows
  • Payment compliance infrastructure

This dramatically reduces the time required to move from prototype to production ready in-person payments.

Getting Started

To build a working integration:

Terminal
npm install @tapayadot/accept-react-native@latest
  • Add the config plugin to app.json
  • Run:
Terminal
npx expo prebuild --clean
  • Implement:
    • initialize
    • authenticate
    • startCardPayment
  • Run the app on a physical device

Device Requirements

Android

  • Android 11+
  • NFC support
  • Hardware keystore enabled
  • Non-rooted device

iPhone

  • iPhone XS or newer
  • iOS 18+
  • Non-jailbroken

What we’re building next

With payments fully integrated, we are now focusing on:

  • Bluetooth thermal printer support
  • Offline transaction queuing using Expo SQLite
  • Expanded merchant tooling
  • Additional POS workflows

Because the payment infrastructure is already in place, we can now focus on improving the broader merchant experience rather than rebuilding payment rails.

Final thoughts on building a POS with Expo

Historically, POS teams often had to choose between:

  • Fast cross platform development
  • Or production grade in-person payments

Expo simplified the application layer and Tapaya simplified the payment infrastructure layer.

In the end, the integration came down to three function calls, not as a simplified demo, but as the actual implementation powering the app.

That simplicity also makes the SDK particularly effective for AI assisted development workflows.

And in payments infrastructure, smaller APIs are usually better.

POS

Share article