Prerequisites
- Beta access from Rinne: registry credentials, a sandbox API key with the
tap_on_phone.*permission (or the*.*wildcard) and a merchant enabled for contactless capture. See how to request access. Looking transactions up afterwards requires thetransaction.listpermission on the same key, or a separate key that holds it. - A backend you control, where the API key lives. The app never holds it.
- A physical Android phone on Android 10 or newer with NFC and Google Play services.
- Any contactless card, phone or watch. The sandbox reads it and never charges it.
1
Add the SDK to your Gradle build
The SDK lives in a private registry. Use the registry credentials you received with your beta access and keep them in That one line brings the whole SDK, including
local.properties or an environment variable, never in version control.Payments, CheckoutConfig and PaymentResult. Pin the version named in your beta access email.If your release build runs R8, nothing more is needed: the SDK ships consumer keep rules that protect its public API.Gradle sync resolves the SDK. A
401 from the registry means the credentials are missing or wrong.2
Subclass RinneApplication
The SDK runs its cryptography in a separate process that re-enters
Application.onCreate. RinneApplication handles that for you. Put your own startup work in onRinneCreate, which runs only in the real app process.MyApp.kt
AndroidManifest.xml
The app starts and
onRinneCreate runs once. Startup work placed in onCreate of another base class would run twice, once in the crypto process.3
Mint an access token on your backend
Expose an endpoint your app can call with its own session. Behind it, exchange your API key for a Tap on Phone token. With a merchant’s key the body is empty. An organization key passes the Return the
company_id of the merchant the token is for; a merchant outside the organization answers 404.Response
access_token to the app. The token is valid for expires_in seconds, fifteen minutes by default, and cannot call any other endpoint. A key with neither tap_on_phone.* nor *.* gets 403. The full contract is in the API reference.The response carries an
access_token and a scope that grants the Tap on Phone permissions, tap_on_phone.bootstrap and tap_on_phone.config.4
Prepare the terminal
Call Always pass
Payments.getReady once, typically when the operator opens the sales screen. The token provider is a suspending lambda the SDK calls once per handshake, so fetch a fresh token inside it every time rather than caching one.SalesActivity.kt
environment = Environment.SANDBOX. Without it the SDK does not target the sandbox your API key belongs to, and getReady fails with ACCESS_TOKEN_REJECTED.getReady is idempotent: on a prepared terminal it returns Ready at once, so calling it on every resume is safe. CheckoutConfig is applied when the terminal is created, so to change branding call Payments.dispose() and then getReady again. Design the brand in the simulator first and paste the Kotlin it writes.The three failures worth handling at setup are:Every other code is listed in Results and errors.
The callback receives
ReadyResult.Ready and Payments.isReady is true. The first handshake takes a few seconds; later ones return at once.5
Launch the checkout
Register the contract once, then launch it with a
CheckoutRequest per sale. The result comes back through Android’s activity-result machinery, which is what keeps it safe when the system kills your process while the payment screen is up.SalesActivity.kt
paymentMethodis required. It is the credit-or-debit question every Brazilian point of sale asks before the tap, and it is recorded as the transaction’s payment method.requestIdis required on a purchase and is your idempotency key. Reuse the same value when you retry the same sale, and use it to find the transaction on the API.metadatais stored against the transaction and returned with it. It is capped at 4 KB and rejected when a key looks like card data.
launch never races the first.The checkout opens over your app with your brand colour in the header and the amount you passed.
6
Take a test payment
Run the app on the phone, prepare the terminal, launch a sale and hold the card against the back of the phone. The screens move through Getting ready, Tap to pay, Processing payment, the certified PIN pad when the card asks for one, and Payment approved. This is the same sequence the simulator plays:Confirm the transaction reached Rinne by looking it up with the
requestId you sent, through the transactions list. This call requires the transaction.list permission; a key that holds only tap_on_phone.* can mint tokens but gets 403 here.The response holds one transaction with the amount you charged,
payment_method matching what the operator chose, and your metadata. A transaction.created webhook fires at the same time if you have one configured.Bridges: React Native, Flutter and Cordova
Drive the contract without registerForActivityResult
Drive the contract without registerForActivityResult
A bridge that does not own an Activity at registration time can drive the same contract by hand. The result still arrives through Serialise
onActivityResult, so the process-death guarantee holds.PaymentResult to your bridge as the variant name plus code, transactionId and message; never forward raw to a web view.Before the first real sale
Open the antenna guide once so the merchant knows where to hold the card, and run the readiness check during onboarding rather than with a customer waiting. Both are covered in Device setup.Troubleshooting
Next steps
Simulator
Brand the screens, play every outcome and copy the Kotlin.
Customization
Every
CheckoutConfig field and how to override the copy.Refunds
Refund a sale with a card re-tap or from your backend.
Testing in the sandbox
Pick the outcome with the amount and verify every test on the API.

