@dodicandra/minesec-rn
v1.0.4
Published
Expo Native Module bridge for Minesec Headless SoftPOS SDK (Android)
Downloads
53
Maintainers
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
- Instalasi
- Konfigurasi
- Cara Penggunaan
- Referensi API
- Referensi Tipe
- Apa yang Dilakukan Config Plugin
- Staging vs Production
- Troubleshooting
- Menjalankan Contoh (example/)
- Lisensi
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-rnatau dengan package manager langsung:
npm install @dodicandra/minesec-rn
# atau
yarn add @dodicandra/minesec-rnKarena 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 memakaiheadless-stageselamamodemasih"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_xxxxxxxxxxxxc. 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
applicationIddan environment (stage/live). Lisensi test/stage tidak akan valid di SDK varian live, dan sebaliknya (error61001).
4. Prebuild & build
npx expo prebuild --platform android
npx expo run:androidatau dengan EAS:
eas build --platform android --profile developmentCara 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:
- AndroidManifest — menambahkan permission
INTERNET(+NFCdan featureandroid.hardware.nfcjikaenableNfc), serta mendaftarkanMinesecrnHeadlessActivitydenganlaunchMode="singleTask". - 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. - App
build.gradle— memastikanminSdkVersion >= 30, menambahkan packaging exclusionsMETA-INFyang dibutuhkan SDK, meng-inject dependency substitution BouncyCastle (jdk15to18→jdk18on:1.77, kecualiapplyBouncycastleFix: false), dan mem-forcekotlinx-datetime:0.6.1(kecualipinKotlinxDatetime: false). Versi 1.77 dan 0.6.1 mengikuti pin MineSec SDK 1.2.57. gradle.properties— menulisusername/usertokenjika diberikan lewat opsi plugin.- 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 liveKonsekuensinya:
- 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:androidAplikasi 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.
