Native Stripe Terminal Bridge — Build Brief

CSV → Google Sheets: File → Import → Upload. PDF → in the print dialog pick “Save as PDF” as the destination.

Claude Code build brief — OTHE CRM native Stripe Terminal shell

Published app: https://lead-dispatch-flow.base44.app · Backend function: stripeTerminal

1. Overview

GoalBuild a thin native Android Capacitor shell that wraps the LIVE Base44 CRM URL and adds card-present payments (Tap to Pay on Android + M2 Bluetooth reader) via the Stripe Terminal Android SDK, exposed to the WebView through a JS bridge.
Base44 side statusALREADY implemented — backend `stripeTerminal` + web `NativeTapToPay.jsx`. This brief builds ONLY the native shell + plugin.
ScopeDo NOT add login screens, CRM UI, or business logic. All of that ships from Base44.

2. Stack

ShellCapacitor 6 (Android only for now)
LanguageKotlin
Stripe SDKStripe Terminal Android SDK 4.7.x (core + taptopay + bluetooth modules)
Min SDK26
Coroutineskotlinx-coroutines-android 1.8.1
HTTPOkHttp 4.12.0 (connection token fetch)

3. Step 1 — Scaffold

mkdirmkdir othe-crm-shell && cd othe-crm-shell
initnpm init -y
installnpm install @capacitor/core @capacitor/cli @capacitor/android
cap initnpx cap init "OTHE CRM" "com.othe.crm" --web-dir=www
wwwmkdir www && echo '<meta http-equiv="refresh" content="0; url=https://lead-dispatch-flow.base44.app">' > www/index.html
add androidnpx cap add android
config.tsappId: com.othe.crm; appName: OTHE CRM; webDir: www; server.url: https://lead-dispatch-flow.base44.app; server.cleartext: false; android.allowMixedContent: false; SplashScreen launchShowDuration: 1200

4. Step 2 — Gradle deps

coreimplementation 'com.stripe:stripeterminal-core:4.7.0'
taptopayimplementation 'com.stripe:stripeterminal-taptopay:4.7.0'
bluetoothimplementation 'com.stripe:stripeterminal-bluetooth:4.7.0'
okhttpimplementation 'com.squareup.okhttp3:okhttp:4.12.0'
coroutinesimplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1'
reposmavenCentral() (default)

5. Step 3 — Manifest

INTERNET<uses-permission android:name="android.permission.INTERNET" />
NFC<uses-permission android:name="android.permission.NFC" /> + <uses-feature android:name="android.hardware.nfc" android:required="false" />
BT legacy<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" /> + BLUETOOTH_ADMIN
BT modern<uses-permission android:name="android.permission.BLUETOOTH_SCAN" /> + BLUETOOTH_CONNECT
Location<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

6. Step 4 — Plugin

Interface (frozen)window.StripeTerminal = { isAvailable(), connect({mode, locationId?, isSimulated?}), discoverReaders(), connectReader(serial), collectPayment({amount, currency}), cancelCollect(), getConnectionStatus(), disconnect(), onReadersDiscovered(readers) }
ErrorsReject as { code: string, message: string }. Amounts in cents.
Packagecom.othe.crm.terminal
Annotation@CapacitorPlugin(name = "StripeTerminal")
MethodsisAvailable, connect, discoverReaders, connectReader, collectPayment, cancelCollect, getConnectionStatus, disconnect
TokenProviderSessionTokenProvider.fetchToken → POST stripeTerminal {action:get_connection_token} → {secret}
tap_to_pay connectTapToPayDiscoveryConfiguration(isSimulated) → discoverReaders → auto-connect built-in reader with TapToPayConnectionConfiguration
m2 connectBluetoothDiscoveryConfiguration(isSimulated) → discoverReaders → user selects → connectReader(serial, BluetoothConnectionConfiguration(locationId))
collect flowcreatePaymentIntent(amount,currency,AUTOMATIC) → collectPaymentMethod → processPayment → return {paymentIntentId,status,amount,currency,card{brand,last4}}
discovery emitonUpdateDiscoveredReaders → evaluateJavascript(window.__stripeTerminalOnReaders?.(arr)) → calls window.StripeTerminal.onReadersDiscovered
MainActivityregisterPlugin(StripeTerminalPlugin::class.java) before super.onCreate
Imports noteTapToPayConnectionConfiguration, BluetoothConnectionConfiguration, PaymentIntentParameters, CaptureMethod, TerminalException, JSArray — adjust to exact 4.7.x names

7. Step 5 — Permissions

RuntimeRequest Bluetooth + location on first M2 use; ensure NFC for Tap to Pay. Handle onRequestPermissionsResult. Re-ask on denial.

8. Step 6 — Build

syncnpx cap sync android
AABAndroid Studio → Generate Signed Bundle (AAB) → upload to Google Play Console
Update cadenceONE-TIME upload. CRM updates ship automatically via Base44 publish (WebView loads live URL). Re-upload AAB only on shell/bridge changes.

9. Contract rules

1Load the LIVE Base44 URL — never bundle a static web copy (keeps CRM auto-updating).
2Freeze the bridge interface — add methods, never rename/reshape existing ones.
3Connection tokens stay server-side — shell only calls get_connection_token; capture + invoice updates happen in Base44 backend.

10. Backend contract

get_connection_tokenPOST /functions/stripeTerminal {action:get_connection_token} → {secret}
capture_paymentPOST /functions/stripeTerminal {action:capture_payment, payment_intent_id, invoice_id?, test_mode?} → {status, payment_intent_id, amount, card{brand,last4}, invoice_updated}
callerWeb app calls capture_payment AFTER collectPayment resolves succeeded.

11. Testing

1 SimulatedisSimulated: true, test card 4242 4242 4242 4242.
2 Tap to Pay liveAfter Stripe Terminal activated on the account.
3 M2 liveReal reader, $1 test charge, refund from Stripe Dashboard.
4 FallbackConfirm web app degrades gracefully when bridge absent (browser).

12. Deliverable

AABSigned AAB ready for Google Play.
SourceCapacitor shell source committed to its own repo.
No web changesAll CRM/web changes ship from Base44.
Generated from the OTHE CRM Base44 app · src/lib/nativeBridgeBriefData.js