npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@tapayadot/accept-react-native

v1.16.0

Published

Accept SDK for React Native/Expo (Android)

Readme

Tapaya Accept

Tapaya Accept SDK for React Native

Accept card payments on Android terminals from a React Native or Expo app.

This package is a thin wrapper over the Accept SDK for Android (com.tapaya:accept), which in turn drives the Accept plugin app — the companion installed on the terminal that owns the card reader and the payment UI. Every surface here mirrors the Android SDK 1:1, so its reference documentation applies unchanged.

Full documentation: docs.tapaya.com

Requirements

  • Android only. There is no iOS implementation. The package is safe to import on iOS, but every call throws, so guard them with Platform.OS === 'android'.
  • minSdkVersion 30 (Android 11). The config plugin raises the host app to match.
  • Expo SDK 54+, or a bare React Native app using expo-modules-core. Verified against SDK 57 / React Native 0.86 on an Android 16 terminal.
  • A custom development build — this package contains native code and does not run in Expo Go.

Installation

npx expo install @tapayadot/accept-react-native

Add the config plugin to app.json:

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

Then rebuild the native project:

npx expo prebuild --clean
npx expo run:android

Config plugin options

| Option | Default | What it does | | --- | --- | --- | | selfInstall | false | Lets the SDK install the plugin app itself on terminals with no app store. See Installing the plugin app. |

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

selfInstall adds the optional com.tapaya:accept-installer artifact, which carries REQUEST_INSTALL_PACKAGES. That permission merges into your app's manifest and Play requires you to justify it on your listing, so leave the option off unless your terminals are storeless. Changing it takes effect on the next npx expo prebuild.

Beyond those options the plugin only raises minSdkVersion to 30. Everything else the SDK needs — the INTERNET, ACCESS_NETWORK_STATE, and location permissions, the <queries> entries that make the plugin app visible on API 30+, and the activities that run a payment on a customer-facing display — ships in the Android SDK's own manifest and merges in automatically.

Quick start

import Accept from '@tapayadot/accept-react-native';

// 1. Initialize once, at app startup.
Accept.initialize({ isProduction: false }); // false = sandbox

// 2. Authenticate the merchant.
await Accept.auth.authenticate(merchantToken);

// 3. Make sure the plugin app is installed and its terminal is activated.
if (!Accept.plugin.isInstalled()) {
  await Accept.plugin.install(); // store listing, or a direct install on a storeless terminal
}
if ((await Accept.plugin.status()) !== 'readyForPayments') {
  await Accept.plugin.activateTerminal();
}

// 4. Take a payment. Amounts are in the currency's minor unit.
const result = await Accept.payments.payAsync({ amount: 1000, currency: 'USD' });
if (result.type === 'success') {
  console.log(result.paymentToken, result.receiptUrl);
}

Location permission

A payment requires a device location fix, and the host app must request the permission — the SDK does not. Request it before the first payment, or it fails with LocationPermissionRequired:

import { PermissionsAndroid } from 'react-native';

await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION);

API

Everything hangs off the default export.

| Surface | Access | Purpose | | -------- | ------------------ | ------------------------------------------------------------------------------ | | State | Accept.state | 'idle' \| 'initialized' \| 'authenticated', plus addStateListener() | | Auth | Accept.auth | authenticate(merchantToken) | | SDK | Accept.sdk | version, isProduction, deviceId(), minimumAmounts() | | Merchant | Accept.merchant | info(), config(), onboardingStatus(), availableCurrencies() | | Payments | Accept.payments | pay(), payAsync(), status(), cancel(), refund(), setOptions() | | Plugin | Accept.plugin | isInstalled(), install(), activateTerminal(), status(), updateInfo(), logout() | | Displays | Accept.displays | all(), customerFacing(), isDualScreen |

Top-level: initialize(), clear(), setTheme(), getTheme(), createLocalThemeImageUri(), setLogLevel(), setDebugLoggingEnabled(), setLogger().

Lifecycle

Accept.state moves idle → initialized → authenticated:

const subscription = Accept.addStateListener((state) => {
  if (state === 'authenticated') loadMerchant();
});

// later
subscription.remove();

Payments as a stream

payAsync() is a convenience over pay(), which reports the whole lifecycle. Use it when the UI reflects the intermediate states:

const subscription = Accept.payments.pay({ amount: 1000, currency: 'USD' }, (event) => {
  switch (event.type) {
    case 'creating':
      setStatus('Creating payment…');
      break;
    case 'created':
      // The payment token and receipt URL exist before the plugin is launched.
      setReceiptUrl(event.receiptUrl);
      break;
    case 'launched':
      setStatus('Waiting for the card…');
      break;
    case 'result':
      setResult(event.payResult); // success | declined | canceled | failed
      break;
    case 'creationFailed':
      setError(event.error); // an AcceptError payload; the payment never started
      break;
  }
});

pay() never throws — a failure to create the payment arrives as creationFailed. The stream ends after result or creationFailed and the subscription removes itself; call subscription.remove() to stop listening early (the plugin's UI stays on screen).

Payment options

Per payment, or session-wide via Accept.payments.setOptions():

Accept.payments.setOptions({
  receiptConfig: {
    type: 'show',
    showQrCodeReceipt: true,
    dismissBehavior: { type: 'delayed', delayMs: 5000 },
    printBehavior: 'onDemand',
  },
  nfcPosition: { type: 'onDevice', surface: 'back', xFraction: 0.5, yFraction: 0.25 },
  transactionTimeoutMs: 60_000, // 0…120000
});

await Accept.payments.payAsync({
  amount: 2500,
  currency: 'EUR',
  tip: 250, // skips the plugin's own tip screen
  metadata: { orderId: 'A-1024' }, // echoed back on the result
});

Only the keys you pass are changed, so one default can be set without disturbing the others.

Dual-screen terminals

On dual-sided hardware, run the payment UI on the customer-facing panel while your app keeps the merchant panel:

if (Accept.displays.isDualScreen) {
  Accept.payments.setOptions({ display: { type: 'customerFacing' } });
}

Set this before authenticate() — the terminal warm-up it fires follows the same target, and you don't want the warm-up flashing on the merchant's screen.

Installing the plugin app

Accept.plugin.install() takes whichever route the device has.

With an app store, it opens the plugin's listing — branded when the authenticated organization configured one — and needs no merchant session to do it. PluginStoreUnavailable means the listing could not be opened on this device.

With no store, the SDK downloads the APK and installs it through the system installer. The APK is verified against its SHA-256, declared package and version code, a pinned signer, and the installed plugin's own signer before anything is committed. That route needs three things, each of which has its own error when missing:

| Error | Missing | Fix | | --- | --- | --- | | PluginSelfInstallUnavailable | the installer artifact | set selfInstall: true on the config plugin and re-run npx expo prebuild | | PluginInstallAuthRequired | an authenticated merchant | call Accept.auth.authenticate() first | | PluginInstallPermissionRequired | the user's "Install unknown apps" grant | send them to Android's MANAGE_UNKNOWN_APP_SOURCES screen, then call install() again |

import { Linking } from 'react-native';

try {
  await Accept.plugin.install();
} catch (error) {
  if (!AcceptError.is(error)) throw error;

  if (error.code === 'PluginInstallPermissionRequired') {
    await Linking.sendIntent('android.settings.MANAGE_UNKNOWN_APP_SOURCES', [
      { key: 'android.provider.extra.APP_PACKAGE', value: 'com.yourcompany.yourapp' },
    ]);
  }
}

PluginDownloadNotPermitted is the one that is not fixed in code: the merchant is not enabled for the non-store distribution channel. Point them at their account manager or [email protected].

Accept.plugin.updateInfo() answers from the backend's release metadata, unauthenticated and against the installed package, so it works during device setup, with no store, and before the plugin is installed at all.

Errors

Every rejection is an AcceptError whose code names the failure, carrying that case's fields:

import Accept, { AcceptError } from '@tapayadot/accept-react-native';

try {
  await Accept.payments.payAsync({ amount: 10, currency: 'USD' });
} catch (error) {
  if (!AcceptError.is(error)) throw error;

  switch (error.code) {
    case 'AmountBelowMinimum':
      alert(`Minimum is ${error.minimum} ${error.currency}`);
      break;
    case 'PluginNotReadyForPayments':
      await Accept.plugin.activateTerminal();
      break;
    case 'LocationPermissionRequired':
      await requestLocationPermission();
      break;
    case 'NoInternetConnection':
      alert('No connection.');
      break;
    default:
      throw error;
  }
}

Theming

Branding for the plugin's activation, status, and payment screens. Session-wide, and pushed to the plugin the next time it is bound or launched:

Accept.setTheme({
  light: {
    colors: { primary: '#0B5FFF', onPrimary: '#FFFFFF', surface: '#FFFFFF' },
    images: { logoSymbol: 'https://example.com/logo.png' },
  },
  dark: {
    colors: { primary: '#6E9BFF', onPrimary: '#001B3D', surface: '#101418' },
  },
});

For a logo bundled with the app rather than hosted, convert it and hand the plugin a content:// URL:

import { assetToBase64 } from '@tapayadot/accept-react-native/utils';

const base64 = await assetToBase64(require('./assets/logo.png'));
const uri = Accept.createLocalThemeImageUri(base64, 'png');

Accept.setTheme({ light: { images: { logoSymbol: uri } } });

assetToBase64 needs expo-asset and expo-file-system, both present in managed Expo projects. Bare React Native consumers must install them.

Logging

Accept.setLogLevel('DEBUG'); // NONE | ERROR | WARN | INFO | DEBUG | VERBOSE
Accept.setLogger((entry) => Sentry.addBreadcrumb({ message: entry.message }));

Logging defaults to DEBUG against sandbox and NONE against production. Pass null to setLogger to restore the default Logcat sink.

Logout

await Accept.plugin.logout(); // sign the merchant out of the plugin app
await Accept.clear(); // clear the local token, device id, cached config, and theme

Running the example app

example/ is a demo harness mirroring the Accept SDK for Android's own example module: one control per public API call, and a log pane showing what each one returned.

npm install                       # installs the root package and the example workspace
cp example/.env.example example/.env.local   # optional: prefill credentials
npm run android                   # prebuilds and runs on a connected device

.env.local is gitignored. Leave it out and type the API key and merchant token into the app's Credentials fields instead — nothing is persisted either way. Never put a production key in it: Expo inlines EXPO_PUBLIC_* variables into the bundle at build time.

The example needs a real device with the Accept plugin app installed; an emulator has no card reader. Tap Login to mint a merchant token and authenticate, then activateTerminal() once before the first payment.

License

Apache-2.0. See LICENSE and NOTICE.