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

@dodicandra/minesec-rn

v1.0.4

Published

Expo Native Module bridge for Minesec Headless SoftPOS SDK (Android)

Downloads

53

Readme

@dodicandra/minesec-rn

Expo Native Module (Android) untuk MineSec Headless SoftPOS SDK — menerima pembayaran kartu contactless (Tap to Phone) langsung dari aplikasi React Native / Expo tanpa hardware EDC tambahan.

Module ini membungkus MineSec Headless SDK v1 (com.theminesec.sdk:headless) dengan API JavaScript yang sederhana dan fully-typed, plus Expo Config Plugin yang mengurus seluruh konfigurasi native (AndroidManifest, Gradle, Maven repository, file lisensi) secara otomatis saat expo prebuild.

⚠️ Android only. SoftPOS membutuhkan NFC dan MineSec SDK hanya tersedia untuk Android (minSdk 30 / Android 11+). Di iOS dan web, module tidak melakukan apa-apa.


Daftar Isi


Prasyarat

Sebelum memakai module ini kamu membutuhkan:

| Kebutuhan | Keterangan | |---|---| | Akun MineSec | Kredensial Maven (username + token) untuk mengunduh SDK dari GitHub Packages TheMinesec/ms-registry-client | | File lisensi .license | Diterbitkan MineSec, terikat ke applicationId aplikasi kamu. Lisensi stage ≠ lisensi live | | Profile ID | ID profil pembayaran (prof_...) dari backend MineSec untuk routing transaksi | | Expo SDK 52+ | Proyek Expo dengan development build (module ini tidak jalan di Expo Go) | | Perangkat Android | Android 11+ (API 30) dengan NFC, perangkat fisik (bukan emulator) untuk tap kartu |

Instalasi

npx expo install @dodicandra/minesec-rn

atau dengan package manager langsung:

npm install @dodicandra/minesec-rn
# atau
yarn add @dodicandra/minesec-rn

Karena berisi kode native, aplikasi harus di-build ulang sebagai development build setelah instalasi — Expo Go tidak mendukung native module pihak ketiga.

Konfigurasi

1. Config plugin di app.json

Daftarkan plugin di app.json / app.config.js:

{
  "expo": {
    "plugins": [
      [
        "@dodicandra/minesec-rn",
        {
          "enableNfc": true,
          "licenseFileName": "mnc.license"
        }
      ]
    ]
  }
}

Opsi plugin yang tersedia:

| Opsi | Tipe | Default | Keterangan | |---|---|---|---| | enableNfc | boolean | true | Menambahkan permission android.permission.NFC dan feature android.hardware.nfc (required). Set false jika konflik dengan library lain | | licenseFileName | string | — | Nama file lisensi di project root; otomatis dicopy ke android/app/src/main/assets/ saat prebuild | | username | string | — | Maven username MineSec; ditulis ke gradle.properties. Hanya untuk development — lihat catatan keamanan di bawah | | usertoken | string | — | Maven token MineSec; ditulis ke gradle.properties. Hanya untuk development | | applyBouncycastleFix | boolean | true | Substitusi bcprov/bcutil-jdk15to18 → bcprov/bcutil-jdk18on:1.77 di app build.gradle, mencegah duplicate class BouncyCastle vs expo-updates. Set false jika kamu punya resolusi dependency sendiri | | pinKotlinxDatetime | boolean | true | Force kotlinx-datetime:0.6.1 di app build.gradle, mencegah NoClassDefFoundError runtime vs expo-dev-launcher. Set false jika kamu punya resolusi dependency sendiri | | mode | "dev" \| "release" | "dev" | Varian SDK MineSec untuk semua build type (debug, release, UAT release). "dev" = headless-stage (server staging/UAT, license test/stage), "release" = headless (server live, license production). Ditulis ke gradle.properties sebagai minesecSdkMode. Harus cocok dengan environment license yang dipakai — mismatch menyebabkan SDK init gagal (mis. error 60112/61001) |

Catatan mode: varian SDK tidak lagi mengikuti build type Gradle. Release build tetap memakai headless-stage selama mode masih "dev" — cocok untuk staging/UAT release. Set "mode": "release" (lalu prebuild ulang) hanya saat pindah ke environment live.

2. Kredensial Maven MineSec

SDK native diunduh dari GitHub Packages milik MineSec, sehingga Gradle butuh kredensial. Ada tiga cara, urut dari yang paling direkomendasikan:

a. Environment variables (direkomendasikan untuk CI/production):

export MINESEC_MAVEN_USER="username-anda"
export MINESEC_MAVEN_TOKEN="ghp_xxxxxxxxxxxx"

b. Global ~/.gradle/gradle.properties (direkomendasikan untuk mesin development):

username=username-anda
usertoken=ghp_xxxxxxxxxxxx

c. Opsi plugin username/usertoken di app.json:

Praktis untuk eksperimen cepat, tapi jangan commit kredensial ke repository. app.json biasanya ikut ter-commit — gunakan app.config.js yang membaca dari env var jika tetap ingin memakai jalur ini:

// app.config.js
export default {
  expo: {
    plugins: [
      [
        '@dodicandra/minesec-rn',
        {
          licenseFileName: 'mnc.license',
          username: process.env.MINESEC_MAVEN_USER,
          usertoken: process.env.MINESEC_MAVEN_TOKEN,
        },
      ],
    ],
  },
};

3. File lisensi

Letakkan file .license dari MineSec di root proyek aplikasi (sejajar dengan app.json):

my-app/
├── app.json
├── mnc.license        ← di sini
├── package.json
└── ...

Saat prebuild, plugin akan menyalinnya ke android/app/src/main/assets/. Nama file inilah yang nanti kamu berikan ke initSoftPos().

Penting: lisensi terikat ke applicationId dan environment (stage/live). Lisensi test/stage tidak akan valid di SDK varian live, dan sebaliknya (error 61001).

4. Prebuild & build

npx expo prebuild --platform android
npx expo run:android

atau dengan EAS:

eas build --platform android --profile development

Cara Penggunaan

Alur dasar

App launch ──▶ initSoftPos()          (setiap kali app dibuka)
                    │
Instalasi baru ──▶ initialSetup()     (cukup sekali per instalasi)
                    │
Pembayaran   ──▶ launchTransaction()  (membuka UI tap kartu NFC)
                    │
Manajemen    ──▶ createAction()       (void/refund/adjust — tanpa UI)
                 getTransaction()     (query status transaksi)

Inisialisasi SDK

Panggil initSoftPos() setiap kali aplikasi dibuka, misalnya di komponen root:

import Minesecrn from '@dodicandra/minesec-rn';

const result = await Minesecrn.initSoftPos('mnc.license');

if (result.success) {
  console.log('SDK siap:', result.value.sdkVersion, result.value.sdkId);
} else {
  console.error(`Init gagal [${result.code}]: ${result.message}`);
}

Initial setup (sekali per instalasi)

Setelah init berhasil, jalankan initialSetup() satu kali per instalasi aplikasi. Proses ini mengunduh parameter EMV, CAPK, konfigurasi terminal, dan meng-inject key dari server MineSec:

// withTestCapk: true untuk environment staging/development
const setup = await Minesecrn.initialSetup(true);

if (setup.sdkRes.success) {
  console.log('Setup selesai');
} else {
  console.error('Setup gagal:', setup.sdkRes);
}

Simpan flag (misalnya di AsyncStorage) agar setup tidak diulang di launch berikutnya.

Transaksi SALE (tap kartu NFC)

launchTransaction() membuka activity native full-screen untuk tap kartu, lalu resolve dengan hasil transaksi:

const result = await Minesecrn.launchTransaction({
  type: 'ActionNew',
  tranType: 'SALE',
  amount: { value: '10000', currencyCode: 'IDR' },
  profileId: 'prof_01XXXXXXXXXXXXXXXXXXXXXXXX',
  primaryTid: '30157001',
  posReference: `POS-${Date.now()}`,
  description: 'Pembelian kopi',
});

if (result.success) {
  const tx = result.value;
  console.log(`${tx.status} — ${tx.paymentMethod} ${tx.accountMasked}`);
  console.log('tranId:', tx.id, 'approval:', tx.approvalCode);
} else {
  console.error(`Transaksi gagal [${result.code}]: ${result.message}`);
}

Hanya satu transaksi boleh berjalan pada satu waktu. Memanggil launchTransaction() lagi saat transaksi lain masih berjalan (misalnya karena double-tap navigasi) akan langsung reject dengan code TRANSACTION_IN_PROGRESS tanpa membuka UI baru — transaksi pertama tetap berjalan normal:

try {
  const result = await Minesecrn.launchTransaction(request);
  // ...
} catch (error) {
  if ((error as { code?: string }).code === 'TRANSACTION_IN_PROGRESS') {
    // Abaikan — sudah ada transaksi yang sedang diproses
    return;
  }
  throw error;
}

tranType yang didukung: 'SALE', 'AUTH' (pre-authorization), 'REFUND'.

Field opsional lain di ActionNew: cvmSignatureMode ('ELECTRONIC_SIGNATURE' | 'SIGN_ON_PAPER'), tapToOwnDevice, installmentPlan, dan extra (metadata key-value).

Void, Refund, Adjust, Auth Completion

Aksi terhadap transaksi yang sudah ada tidak butuh tap kartu. Bisa lewat createAction() (background, tanpa UI) atau launchTransaction() (dengan UI):

// Void — membatalkan transaksi sebelum settlement
await Minesecrn.createAction({
  type: 'ActionVoid',
  tranId: 'tran_01XXXXXXXXXXXXXXXXXXXXXXXX',
});

// Linked refund — refund tanpa tap ulang (hanya gateway MPGS)
await Minesecrn.createAction({
  type: 'ActionLinkedRefund',
  tranId: 'tran_01XXX...',
  amount: { value: '5000', currencyCode: 'IDR' }, // omit = refund penuh
});

// Adjust — ubah jumlah (tip / incremental auth)
await Minesecrn.createAction({
  type: 'ActionAdjust',
  tranId: 'tran_01XXX...',
  amount: { value: '12000', currencyCode: 'IDR' }, // total baru
});

// Auth completion — capture transaksi pre-authorized
await Minesecrn.createAction({
  type: 'ActionAuthComp',
  tranId: 'tran_01XXX...',
  amount: { value: '10000', currencyCode: 'IDR' },
});

Query transaksi

// Berdasarkan tranId (prefix "tran_")
const byId = await Minesecrn.getTransaction('tranId', 'tran_01XXX...');

// Atau berdasarkan posReference milik kamu sendiri
const byRef = await Minesecrn.getTransaction('posReference', 'POS-1720000000000');

Penanganan error

Semua method mengembalikan MinesecResult<T> — discriminated union yang memaksa pengecekan success sebelum mengakses data:

const result = await Minesecrn.launchTransaction(request);

if (result.success) {
  // result.value: Transaction — aman diakses
} else {
  // result.code: number     — kode error MineSec
  // result.message: string  — pesan error
  // result.contextual?: string
  // result.extra?: Record<string, string>
}

Kode error yang umum ditemui:

| Kode | Arti | Solusi | |---|---|---| | 41002 | Cannot get payment profile | Cek profileId valid dan profil server cocok dengan versi SDK | | 41100 | License file tidak ditemukan | Nama file di initSoftPos() harus sama persis dengan file di assets | | 60112 | Key attestation gagal | Varian SDK (stage/live) tidak cocok dengan environment server | | 61001 | License tidak valid | Lisensi untuk produk/environment berbeda (mis. lisensi v2 dipakai di SDK v1, atau stage vs live) |

Selain envelope MinesecResult, launchTransaction() juga bisa reject (throw) dengan code string berikut:

| Code | Arti | Solusi | |---|---|---| | TRANSACTION_IN_PROGRESS | launchTransaction dipanggil saat transaksi lain masih berjalan | Tunggu promise transaksi pertama selesai; abaikan panggilan duplikat (double-tap) | | INVALID_REQUEST | PoiRequest tidak valid | Periksa field request (type, amount, tranId, dst.) | | NO_ACTIVITY | Tidak ada activity aktif | Panggil hanya saat app di foreground | | LAUNCH_TRANSACTION_ERROR | Gagal membuka HeadlessActivity | Cek pesan error untuk detail |

Referensi API

Import default adalah instance module, siap pakai:

import Minesecrn from '@dodicandra/minesec-rn';

| Method | Signature | Keterangan | |---|---|---| | initSoftPos | (licenseFileName: string) => Promise<MinesecResult<SdkInitResult>> | Inisialisasi SDK. Wajib dipanggil setiap app launch, sebelum method lain | | initialSetup | (withTestCapk?: boolean) => Promise<SetupResult> | Download EMV params, CAPK, terminal config, inject keys. Sekali per instalasi. withTestCapk: true untuk staging | | launchTransaction | (request: PoiRequest) => Promise<MinesecResult<Transaction>> | Membuka UI native (NFC tap untuk ActionNew). Mendukung semua tipe PoiRequest | | createAction | (request: PoiRequestBackground) => Promise<MinesecResult<Transaction>> | Eksekusi aksi background tanpa UI — semua tipe kecuali ActionNew | | getTransaction | (refType: 'tranId' \| 'posReference', refValue: string) => Promise<MinesecResult<Transaction>> | Query transaksi berdasarkan referensi |

Referensi Tipe

Semua tipe di-export dari package:

import type {
  Amount,
  TranType,
  CvmSignatureMode,
  PoiRequest,
  PoiRequestActionNew,
  PoiRequestActionVoid,
  PoiRequestActionLinkedRefund,
  PoiRequestActionAdjust,
  PoiRequestActionAuthComp,
  PoiRequestBackground,
  TransactionReferenceType,
  MinesecResult,
  SdkInitResult,
  SetupResult,
  Transaction,
} from '@dodicandra/minesec-rn';

Tipe-tipe kunci:

type Amount = {
  value: string;        // desimal string, mis. "10000" atau "1.00"
  currencyCode: string; // ISO 4217, mis. "IDR", "HKD", "USD"
};

type MinesecResult<T> =
  | { success: true; value: T }
  | { success: false; code: number; message: string; contextual?: string; extra?: Record<string, string> };

type Transaction = {
  id: string;                    // "tran_..."
  status: string | null;         // mis. "APPROVED"
  tranType: string | null;
  amount: Amount | null;
  posReference: string | null;
  approvalCode: string | null;
  paymentMethod: string | null;  // "VISA", "MASTERCARD", dst.
  accountMasked: string | null;  // nomor kartu tersamar
  rrn: string | null;
  batchNo: string | null;
  createdAt: string | null;
  updatedAt: string | null;
};

Apa yang Dilakukan Config Plugin

Saat expo prebuild, plugin @dodicandra/minesec-rn otomatis:

  1. AndroidManifest — menambahkan permission INTERNET (+ NFC dan feature android.hardware.nfc jika enableNfc), serta mendaftarkan MinesecrnHeadlessActivity dengan launchMode="singleTask".
  2. Root build.gradle — meng-inject Maven repository MineSec (maven.pkg.github.com/TheMinesec/ms-registry-client) lengkap dengan kredensial dari gradle properties / env vars, dan membersihkan blok repository lama jika ada.
  3. App build.gradle — memastikan minSdkVersion >= 30, menambahkan packaging exclusions META-INF yang dibutuhkan SDK, meng-inject dependency substitution BouncyCastle (jdk15to18 → jdk18on:1.77, kecuali applyBouncycastleFix: false), dan mem-force kotlinx-datetime:0.6.1 (kecuali pinKotlinxDatetime: false). Versi 1.77 dan 0.6.1 mengikuti pin MineSec SDK 1.2.57.
  4. gradle.properties — menulis username/usertoken jika diberikan lewat opsi plugin.
  5. License — menyalin file lisensi dari project root ke android/app/src/main/assets/.

Semua langkah idempoten — aman menjalankan prebuild berkali-kali.

Staging vs Production

Module memakai dua varian dependency SDK sesuai build type:

debugImplementation   'com.theminesec.sdk:headless-stage:1.2.57'  // server staging
releaseImplementation 'com.theminesec.sdk:headless:1.2.57'        // server live

Konsekuensinya:

  • Debug build terhubung ke server staging MineSec → gunakan lisensi test/stage dan initialSetup(true).
  • Release build terhubung ke server live → gunakan lisensi production dan initialSetup(false).
  • Lisensi, profile ID, dan varian SDK harus berasal dari environment yang sama — kombinasi silang menghasilkan error 60112/61001.

Troubleshooting

Build gagal: Could not resolve com.theminesec.sdk:headless... Kredensial Maven tidak terbaca. Cek env var MINESEC_MAVEN_USER/MINESEC_MAVEN_TOKEN atau ~/.gradle/gradle.properties, lalu jalankan ulang prebuild.

Error 41100 saat initSoftPos() Nama file lisensi tidak cocok. Pastikan licenseFileName di app.json, nama file di project root, dan argumen initSoftPos() ketiganya identik, lalu prebuild ulang agar file tercopy ke assets.

Error 61001 (invalid license) Lisensi tidak cocok dengan varian SDK atau environment. Lisensi stage hanya untuk debug build (headless-stage), lisensi live hanya untuk release build.

Error 60112 (key attestation) Aplikasi debug menghubungi server live atau sebaliknya. Pastikan varian SDK sesuai environment lisensi.

Build gagal: Duplicate class org.bouncycastle.x509.util.LDAPStoreHelper found in modules bcprov-jdk15to18-1.81 ... and bcprov-jdk18on-1.77 ... (task checkDebugDuplicateClasses) Konflik BouncyCastle: MineSec SDK membawa bcprov-jdk18on:1.77, sedangkan expo-updates membawa bcprov-jdk15to18:1.81 — kedua varian berisi kelas org.bouncycastle.* identik dengan koordinat Maven berbeda, sehingga Gradle tidak melakukan conflict resolution. Plugin ini sudah menanganinya otomatis via dependency substitution (jdk15to18 → jdk18on:1.77) di app build.gradle — jalankan ulang npx expo prebuild -p android setelah update package. Jika kamu punya resolusi sendiri, opt-out dengan applyBouncycastleFix: false.

Runtime error saat initialSetup(): Call to function 'Minesecrn.initialSetup' has been rejected. → Caused by: java.lang.NoClassDefFoundError: com.theminesec.sdk.headless.util.JsonKt Di logcat tampak NoClassDefFoundError: Failed resolution of: Lkotlinx/datetime/Instant;. Penyebab: expo-dev-launcher (dependency debug-only di Expo SDK 55) membawa kotlinx-datetime:0.7.1, sedangkan MineSec SDK dikompilasi terhadap 0.6.1; versi 0.7.0 menghapus kelas kotlinx.datetime.Instant sehingga JsonKt.<clinit> crash. Hanya menimpa debug build — release/EAS build tidak menyertakan expo-dev-launcher. Plugin ini sudah menanganinya otomatis dengan mem-force kotlinx-datetime:0.6.1 di app build.gradle. Opt-out via pinKotlinxDatetime: false. Trade-off yang diketahui: di debug build, formatting tanggal pada tab Updates UI expo-dev-launcher (DateFormat.formatUpdateDate, memakai API day() yang baru ada di kotlinx-datetime 0.7) bisa melempar NoSuchMethodError. Fungsi inti dev launcher tidak terpengaruh; release/EAS build sama sekali tidak terdampak.

Module tidak ditemukan / Cannot find native module 'Minesecrn' Kamu menjalankan di Expo Go, atau belum rebuild setelah instalasi. Jalankan npx expo prebuild && npx expo run:android.

NFC tidak merespons saat tap Gunakan perangkat fisik dengan NFC aktif — emulator tidak mendukung NFC. Cek juga NFC di system settings.

Menjalankan Contoh (example/)

Repo ini menyertakan aplikasi contoh lengkap di example/:

cd example
yarn install
# letakkan file lisensi di example/ sesuai app.json
npx expo prebuild --platform android
npx expo run:android

Aplikasi contoh mendemonstrasikan seluruh alur: init → setup → SALE → void → query. Lihat example/App.tsx.

Lisensi

MIT © dodicandra

Catatan: MineSec SDK sendiri adalah software proprietary milik MineSec. Package ini hanya bridge — kamu tetap membutuhkan perjanjian dan lisensi resmi dari MineSec untuk memakainya.