TapayaがPOSアプリに店内カード決済を午後のうちに追加した方法
これはTapayaの共同創業者 Roman Kuchařík によるゲスト投稿です。Tapayaはソフトウェアのみで動作するSoftPOSインフラを構築するプラハのスタートアップで、あらゆるモバイル端末を安全な決済端末に変えます。
ほとんどのチームがPOSアプリを構築する際に最終的に直面する課題は同じです:対面でのカード決済の受け入れ。製品が動き、加盟店がテスト準備できていてロードマップがあっても、支払いを追加した瞬間に状況は大きく複雑になります。PCI要件、EMV認証、アクワイアラ関係、ハードウェア依存、そして何かを本番稼働させるまでの長い開発サイクルに直面します。
Tapayaではこのプロセスはもっとシンプルであるべきだと考えました。そこで、実際にExpoベースのPOSアプリを構築し、自社SDKを使って店内カード決済をどれだけ短時間で追加できるかを試しました。結果:アイデアから最初の承認済み決済まで、午後のうちに完了しました!
この投稿では以下を説明します:
- アプリの発想元
- 作ったもの
- Expoアプリにカード決済を統合した正確な手順
アプリの発想
発想はシンプルでした。お気に入りの地元のコーヒーショップ向けに、従来の決済ハードウェアの代わりに通常のスマホで使える、軽量で高速、最小限のPOSを作りたいと考えました。多くの小規模加盟店は、別々の端末やケーブル、充電ドック、複数のシステムを管理したくありません。求めるのは一台で次を実行できることです:
- 商品管理
- 取引処理
- 加盟店のオンボーディング
- 即時のタップ・トゥ・ペイ(tap to pay)カード決済の受け入れ
社内では別の大きな問いも検証したかった:開発者はExpoで支払いインフラに何ヶ月も費やさずに本番対応のSoftPOS体験を構築できるか? これが実験です。幸運にもSTRVとExpoが主催するプラハハッカソンでこの実験に取り組む機会がありました。
作ったアプリ
ExpoでシンプルなクロスプラットフォームPOSアプリを構築しました。アプリには以下を含みます:
- 商品在庫画面
- チェックアウトフロー
- 取引履歴
- 加盟店オンボーディング(KYB)
- 店頭のタップ・トゥ・ペイカード受け入れ
目標は複雑なエンタープライズPOSを作ることではなく、モダンな決済インフラをReact Nativeアプリへどれだけ速く統合できるかを示すクリーンなプロトタイプを作ることでした。午後の終わりにはアプリで次が可能になりました:
- 加盟店のオンボーディング
- Tapayaプラットフォームでの認証
- 非接触カード決済の受け入れ
- NFC対応のiPhoneやAndroid端末上でサンドボックス取引を承認処理
外部決済端末は不要でした。
なぜExpoを選んだのか
いくつかのフレームワークとワークフローを評価した結果、ネイティブのモバイルセットアップを大幅に簡素化する点でExpoを選びました。
- Expo Config PluginsはネイティブSDKのセットアップを簡易化
- app.jsonに1つのconfig pluginを追加し、
expo prebuildを実行するとiOSとAndroidのバインディングが自動生成され、SwiftやKotlinの手動設定は不要でした。
- EAS BuildはiOS Tap to Payのエンタイトルメント設定を簡素化
- Apple Tap to Payの手動設定は複雑になりがちですが、Expoのサービスがプロビジョニングとエンタイトルメントのワークフローを多く処理してくれました。
- EAS Updateは反復速度を向上
- UIやロジックのアップデートを素早くプッシュでき、テスト中にApp Store審査を何度も待つ必要がなくなりました。
- Expo Routerは開発を加速
- Sales、Transactions、KYB onboarding、Settings用の画面をほとんどボイラープレートなしで構築できました。
TapayaのSDKはExpo向けに作られている
統合はExpoのconfig pluginシステムと正確に動作し、セットアップは驚くほどスムーズでした。
どのようにアプリを作ったか
午前中にExpoでコアのPOSインターフェイスを構築しました:
UIが動作したら、店頭決済の追加に移りました。開発を高速化するためにAIコードエージェントに次を渡しました:
- 望む決済フロー
- Tapaya Quick Startのドキュメント
- POS体験の統合目標
expo prebuildを実行し実機にデプロイしてカードをタップしたところ、最初の承認済みトランザクションが返ってきました。サインアップからサンドボックスでの成功決済まで、フル統合はおよそ30分でした。
統合プロセスは次の通りです:
-
Config Pluginを追加
{ "expo": { "plugins": [ ["@tapayadot/accept-react-native"] ] } }
-
SDKをインストールしネイティブコードを生成
npm install @tapayadot/accept-react-native@latest
npx expo prebuild --clean
-
バックエンドを設定
モバイルアプリがSDKで認証する前に、バックエンドは各加盟店用の短期間有効なログイントークンを生成する必要があります。フローは次のとおりです:
- モバイルアプリが自分のバックエンドを呼ぶ
- バックエンドがTapaya APIをServer Secret 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: ログイントークンを生成(SDK初期化ごとに行う)
モバイルアプリは自分のバックエンドのログインエンドポイントを呼び、バックエンドはTapayaから一時的なログイントークンを要求して端末に返します。こうしてServer Secret Tokenは常にサーバーに留まります。
POST /merchant/auth/login
Authorization: Bearer YOUR_SERVER_SECRET_TOKEN
{ "merchantToken": "your_internal_db_id", "allowOnboarding": true }
Response
{ "token": "EesrFq4PUK1WxHUj93hkrKASDFp8GxJ0" }
このトークンをモバイルアプリに返し、直ちにSDKの認証に使用してください。トークンは:
- 一時的である
- merchantTokenに紐づく
- SDK初期化ごとに新しく取得するべき
- 端末に長期保存してはいけない
セキュリティの注意点
Server Secret TokenはTapayaプラットフォームアカウントへの完全なアクセス権を与えます。セキュリティのために:
- モバイルアプリに埋め込まない
- クライアントサイドのコードで露出させない
- ソース管理にコミットしない
- バックエンドの環境変数として安全に保管する
- 浸害が疑われたら直ちにローテーションする
また、正しいmerchantTokenでログイントークンを生成することが重要です。これがSDKがアクセスできる加盟店アカウントと資金を決定します。
-
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 />;
}
-
カード決済を開始
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;
}
フルの決済フローは主に3つの関数に集約されます:
- initialize
- authenticate
- startCardPayment
加盟店オンボーディングとKYB
支払いを受け入れることはPOSワークフローの一部に過ぎません。加盟店はオンボーディングとKYB確認を経てから取引を処理する必要があります。SDKには組み込みのオンボーディングフローが用意されています:
import AcceptSDK from '@tapayadot/accept-react-native';
await AcceptSDK.identity.presentKyb();
(あるいはREST APIとWebhook経由、またはTapayaプラットフォームの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をインストール
- app.jsonにconfig pluginを追加
- 実行:
- 実装:
- initialize
- authenticate
- startCardPayment
- 実機でアプリを実行
デバイス要件
Android
- Android 11+
- NFCサポート
- ハードウェアキーストア有効
- 非root端末
iPhone
- iPhone XS以降
- iOS 18+
- 非脱獄端末
次に作っているもの
支払いを統合したので、現在注力しているのは:
- Bluetoothサーマルプリンタ対応
- Expo SQLiteを使ったオフライン取引キュー
- 加盟店向けツールの拡張
- 追加のPOSワークフロー
支払いインフラが既に整っているため、支払いレールを再構築するのではなく加盟店体験の改善に集中できます。
最後に:ExpoでPOSを構築する考察
従来、POSチームは次のどちらかを選ばざるを得ませんでした:
- 迅速なクロスプラットフォーム開発
- または本番対応の店頭決済
Expoはアプリケーション層を簡素化し、Tapayaは決済インフラを簡素化しました。最終的に統合は3つの関数呼び出しに集約され、これは単なるデモではなくアプリを動かす実装です。このシンプルさはAI支援の開発ワークフローにも有効であり、決済インフラでは小さなAPIの方が大抵は優れています。