React Native
The @geopagos/react-native-softpos-sdk package wraps the native ToPP (Tap on Phone with PIN) SDK for React Native applications. It exposes a TypeScript API to initialize the SDK, retrieve device and application metadata, and execute sale transactions through the certified standalone ToPP app.
This SDK targets Android exclusively. iOS is not supported.
Transaction flow
The following steps apply to both Android and React Native integrations:
- Initialize the SDK with your application key
- Request application data from the standalone ToPP app — returns device and app metadata required to create the session
- Create a session intent via your backend calling
POST /api/v4/payments/tap-to-phonewith the application data — the API returns an encrypted session payload - Start the transaction by passing the encrypted session to the SDK — the SDK launches the ToPP standalone app to handle the payment
- Verify the transaction result via your backend calling
PATCH /api/v4/payments/:ref_number/confirmbefore showing a success message to the user
Prerequisites
Before getting started, ensure the following requirements are met:
- React Native development environment is configured
- React Native >= 0.72.4
- React >= 18.2.0
- Expo SDK 52+ (if using Expo)
- A physical device that meets the general device requirements
- The ToPP standalone application installed on the device (provided by Geopagos — see below)
- An application key (
applicationKey) provided by Geopagos for your integration - A valid authorization token for the Geopagos API — see Authentication for details on how to generate it
About the ToPP standalone app
The ToPP standalone application is a certified, separate Android app that handles the actual card reading and PIN entry. It runs independently from your app and is launched by the SDK when a transaction is started.
This application is provisioned by Geopagos for each tenant. Contact the Geopagos team to obtain the standalone app and the distribution instructions for your target devices. The SDK provides the method isStandaloneAppInstalled() to verify its presence before starting a transaction.
About the authorization token
Several SDK methods and API calls in this guide require an authToken (also referred to as a JWT or Bearer token). This is the same token throughout the entire flow — from requestAppData through the session intent creation and the transaction itself.
To learn how to generate this token, refer to the Authentication documentation.
Configuration
-
Request access credentials
Contact the Geopagos team to obtain a username and password for accessing the private Nexus repository where the SDK is hosted. -
Encode credentials in Base64
Convert your credentials using the format{username:password}:echo -n 'username:password' | base64 -
Create the
.npmrcfile
In the root of your project, create a.npmrcfile with the following content:@geopagos:registry=https://nexus.geopagos.io/repository/geo-npm-hosted-internal/
//nexus.geopagos.io/repository/geo-npm-hosted-internal/:_auth=YOUR_BASE64_AUTH_TOKEN
always-auth=trueReplace
YOUR_BASE64_AUTH_TOKENwith the Base64 string from step 2. -
Proceed with installation
You can now install the SDK as shown in the next section.
Installation
Install the SDK in the root of your React Native project:
# Using npm
npm install @geopagos/react-native-softpos-sdk
# Using Yarn
yarn add @geopagos/react-native-softpos-sdk
Expo (managed / bare workflow)
Register the config plugin in your app.json or app.config.ts. The plugin automatically adds the GeoPagos Nexus Maven repository to android/build.gradle during expo prebuild, which is required to resolve the native ToPP SDK artifact.
{
"plugins": ["@geopagos/react-native-softpos-sdk"]
}
Bare React Native
If you are not using Expo, add the GeoPagos Nexus Maven repository to your project-level android/build.gradle:
allprojects {
repositories {
// ... other repositories
maven { url 'https://nexus.devops.geopagos.com/repository/android-geopagos_public/' }
}
}
Then run npx react-native run-android (or autolink will handle the rest during a normal build).
SDK initialization
Call softPosSdk.init(applicationKey) before performing any other SDK operation. The applicationKey is provided by Geopagos and identifies your integration.
import { softPosSdk } from '@geopagos/react-native-softpos-sdk'
await softPosSdk.init('YOUR_APPLICATION_KEY')
The SDK throws an error with code SDKAlreadyInitialized if init is called more than once without releasing first. Catch and ignore this error to make the call idempotent:
try {
await softPosSdk.init(applicationKey)
} catch (error: any) {
if (error?.code !== 'SDKAlreadyInitialized') throw error
}
Initialize the SDK as early as possible in your application lifecycle, ideally when the user logs in or when the app configuration is loaded. Early initialization reduces delays when the user initiates a payment.
Check standalone app installation
The ToPP flow depends on a certified standalone application installed on the device. Before attempting a transaction, verify it is present:
const isInstalled = await softPosSdk.isStandaloneAppInstalled()
if (!isInstalled) {
// Prompt the user to install the ToPP app from the store
}
isStandaloneAppInstalled() returns true if the standalone app matching the applicationKey used during init is installed, and false otherwise.
Check NFC support
Verify that the device supports NFC before starting a transaction:
const isSupported = await softPosSdk.isNFCSupported()
if (!isSupported) {
// Show an error — this device cannot process contactless payments
}
Request application data
requestAppData retrieves metadata from the standalone ToPP app: the app name, version, and a device fingerprint. This data is required to create the session intent on your backend.
const appData = await softPosSdk.requestAppData(authToken)
Parameters
| Parameter | Type | Description |
|---|---|---|
authToken | string | null | Optional. Authorization token for the ToPP service. Pass null if the user is not yet authenticated. |
Return value
interface AppData {
appName: string // Name of the standalone ToPP app
versionName: string // Version of the standalone ToPP app
deviceId: string // Device fingerprint identifier
}
On first usage, if the ToPP standalone app is not yet loaded in memory, this call may take up to 5 seconds while security checks run. You may receive a timeout error on the first call — retry at least once before treating it as a fatal error.
It is recommended to call requestAppData early (e.g. right after init) and cache the result. When starting a transaction, call it again to detect updates to the standalone app. If the app data has not changed, the response is immediate.
Get session intent
The session intent is an encrypted payment payload that must be created by your backend before the SDK can start a transaction. Your mobile app requests it by sending the AppData fields to your server, which in turn calls the Geopagos API.
Backend call: POST /api/v4/payments/tap-to-phone
The request body must include the device and app identifiers from AppData, along with the billing details:
{
"client_id": {
"application_name": "geopagos",
"application_version": "1.0.0",
"fingerprint_id": "61:A2:0B:70:70:D2:DB:43:52:61:CB:75:E5:04:53:40:A8:38:7C"
},
"billing_info": {
"currency": "ARS",
"amounts": [
{ "amount": 1500, "label": "Total" }
],
"taxes": [],
"tip": { "amount": 0 }
}
}
The application_name, application_version, and fingerprint_id values come directly from the AppData returned by requestAppData (appName, versionName, and deviceId respectively).
Response
A successful response returns a JSON:API object. The relevant fields are under data.attributes:
| Field | Type | Description |
|---|---|---|
reference_number | string | Unique transaction identifier — save this for the confirmation step and for support |
encrypted_data | string | Encrypted session payload |
hash | string | Integrity hash for the session |
encrypted_key | string | Encryption key for the session |
Save the reference_number from the response. It is required to confirm the transaction after the SDK returns, and it is the primary identifier for any support requests.
See the API reference for the full request and response schema.
Start transaction
Once you have the session payload from your backend, pass it to the SDK to launch the ToPP standalone app and process the payment.
When you call this method, the SDK transfers control to the ToPP standalone app. The standalone app displays its own UI to the user: it prompts the customer to tap their card on the device's NFC antenna, handles PIN entry if required, and processes the payment. Once the transaction completes (success or error), control returns to your app and the promise resolves or rejects.
Your app is in the background during this process — you do not need to build any card-reading or PIN-entry UI.
Use getTransactionContractWithToken when you have an auth token available (recommended), or getTransactionContract without one:
// Recommended: pass session data and auth token
const result = await softPosSdk.getTransactionContractWithToken(sessionData, authToken)
// Without auth token
const result = await softPosSdk.getTransactionContract(sessionData)
sessionData shape
The sessionData argument must be an object containing the three encrypted fields from the session intent response:
const sessionData = {
encrypted_data: 'eyJhbGci...',
hash: 'abc123...',
encrypted_key: 'xyz789...',
}
TransactionResult
On success, the SDK returns a TransactionResult object:
interface TransactionResult {
cvmResult: string // Cardholder verification method used (e.g. "NO_CVM", "SIGNATURE")
authorizationCode: string // Authorization code — always present, must be sent to the confirm API
}
The cvmResult field indicates how the cardholder was verified:
| Value | Description |
|---|---|
"NO_CVM" | No cardholder verification was required |
"SIGNATURE" | The cardholder must sign — prompt for a signature before confirming |
If cvmResult is "SIGNATURE", you must collect the cardholder's signature and upload it before calling the confirm API.
Upload signature (when required)
When cvmResult is "SIGNATURE", capture the cardholder's signature (e.g. using a signature pad component) and upload it to the Geopagos API before confirming the transaction:
Backend call: POST /api/v4/payments/:ref_number/signature
{
"signature_image": "<base64-encoded image>"
}
The ref_number is the reference_number from the session intent. The signature_image field must contain the signature as a Base64-encoded image string.
After uploading the signature, proceed to the Transaction verification step.
See the API reference for the full request and response schema.
Handle errors
When a transaction fails, the SDK rejects the promise with an error object that matches the following shape:
interface TransactionError {
code: string // Raw numeric or string error code from the native SDK
message: string // Error type name (same as errorType)
errorCode: string // Detailed error code
errorType: string // Error class name — use this to branch your error handling
errorHistory?: string[] // Ordered sequence of error events; only present when errorType is "TransactionAborted"
}
Known errorType values
errorType | Description |
|---|---|
TransactionAborted | Transaction was interrupted or timed out — errorHistory contains the sequence of events |
TransactionDenied | Card was declined by the issuer |
EULANotAccepted | User has not accepted the end-user license agreement in the ToPP app |
InvalidTransactionIntent | The session payload was malformed or expired |
ConfigurationError | SDK or standalone app configuration is invalid |
SecurityError | A security check failed (e.g. device integrity) |
UpdateRequired | The ToPP standalone app must be updated before proceeding |
NfcNotAvailable | NFC is not enabled or not available on the device |
InternalError | An unexpected internal error occurred |
Error handling example
try {
const result = await softPosSdk.getTransactionContractWithToken(sessionData, authToken)
// handle success
} catch (error: any) {
const errorType: string = error?.userInfo?.errorType ?? 'Unknown'
const errorHistory: string[] | undefined = error?.userInfo?.errorHistory
switch (errorType) {
case 'TransactionAborted':
// Show the errorHistory details to the user if available
console.log('Transaction aborted. History:', errorHistory)
break
case 'TransactionDenied':
// Inform the user the card was declined
break
case 'EULANotAccepted':
// Prompt the user to open the ToPP app and accept the EULA
break
case 'UpdateRequired':
// Prompt the user to update the ToPP standalone app
break
case 'NfcNotAvailable':
// Prompt the user to enable NFC in device settings
break
default:
// Generic error message for all other cases
break
}
}
Transaction verification
When the SDK returns a TransactionResult, you must confirm the transaction via your backend before showing a success message to the user. Skipping this step exposes the integration to fraud attempts.
Backend call: PATCH /api/v4/payments/:ref_number/confirm
Use the reference_number saved from the session intent step. The authorizationCode from TransactionResult is always present and must always be sent in the request body:
{
"authorization_code": "ABC123"
}
See the API reference for the full request and response schema.
Release
Calling release() is not recommended for most integrations. Only use it if you fully understand the SDK lifecycle and have a specific reason to deallocate its resources. In normal usage the SDK manages its own lifecycle and calling release() prematurely can lead to unexpected behavior.
Call release() when the SDK is no longer needed to free all native resources:
await softPosSdk.release()
After calling release(), any SDK objects obtained previously are no longer valid. You can call init(applicationKey) again to reinitialize the SDK when needed.
Complete integration example
The following example shows the full flow using the softPosSdk singleton. It assumes your backend exposes an endpoint to create the payment session and another to confirm it.
import { softPosSdk, AppData } from '@geopagos/react-native-softpos-sdk'
async function runPayment(
applicationKey: string,
authToken: string,
amount: number,
currency: string,
): Promise<void> {
// 1. Initialize the SDK (idempotent — safe to call on every payment attempt)
try {
await softPosSdk.init(applicationKey)
} catch (error: any) {
if (error?.code !== 'SDKAlreadyInitialized') throw error
}
// 2. Check that the standalone ToPP app is installed
const isInstalled = await softPosSdk.isStandaloneAppInstalled()
if (!isInstalled) {
throw new Error('The ToPP standalone application is not installed on this device.')
}
// 3. Check NFC availability
const nfcSupported = await softPosSdk.isNFCSupported()
if (!nfcSupported) {
throw new Error('NFC is not available on this device.')
}
// 4. Request application data
const appData: AppData = await softPosSdk.requestAppData(authToken)
// 5. Create the payment session on your backend
// Your backend calls POST /api/v4/payments/tap-to-phone and returns the encrypted session
const session = await createPaymentSessionOnBackend({
appName: appData.appName,
versionName: appData.versionName,
deviceId: appData.deviceId,
amount,
currency,
})
// session = { reference_number, encrypted_data, hash, encrypted_key }
// 6. Start the transaction
const result = await softPosSdk.getTransactionContractWithToken(
{
encrypted_data: session.encrypted_data,
hash: session.hash,
encrypted_key: session.encrypted_key,
},
authToken,
)
// 7. Confirm the transaction on your backend (required before showing success)
// Your backend calls PATCH /api/v4/payments/:ref_number/confirm
await confirmPaymentOnBackend(session.reference_number, result.authorizationCode)
console.log('Payment successful. CVM:', result.cvmResult)
}
Alternative: using the React Context
The SDK also provides a React Context (SoftPosSdkProvider) that manages the SDK lifecycle and exposes hooks for use within component trees. This is an alternative to calling the singleton directly.
Wrap your component tree
import { SoftPosSdkProvider } from '@geopagos/react-native-softpos-sdk'
function App() {
return (
<SoftPosSdkProvider applicationKey="YOUR_APPLICATION_KEY">
{/* rest of your app */}
</SoftPosSdkProvider>
)
}
The provider initializes the SDK and fetches initial application data automatically.
Consume the context in a component
import { useSoftPostSdk } from '@geopagos/react-native-softpos-sdk'
function PaymentButton({ session, authToken }) {
const { isInitialized, appData, startSaleWithToken } = useSoftPostSdk()
async function handlePress() {
if (!isInitialized) return
const result = await startSaleWithToken(session, authToken)
// result: TransactionResult
}
return <Button onPress={handlePress} title="Pay" />
}
Hook return values
| Field | Type | Description |
|---|---|---|
isInitialized | boolean | Whether the SDK has been successfully initialized |
appDataRequested | boolean | Whether the initial requestAppData call has completed |
appData | AppData | Application and device metadata from the standalone ToPP app |
startSale | (session) => Promise<TransactionResult> | Starts a sale without an auth token |
startSaleWithToken | (session, jwt) => Promise<TransactionResult> | Starts a sale with a JWT auth token (recommended) |
startSale / startSaleWithToken expect the session argument in the same shape described in the Start transaction section above.