@dodicandra/minesec-rn 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +444 -0
  3. package/android/build.gradle +54 -0
  4. package/android/src/main/AndroidManifest.xml +12 -0
  5. package/android/src/main/java/expo/modules/minesecrn/MinesecrnHeadlessActivity.kt +5 -0
  6. package/android/src/main/java/expo/modules/minesecrn/MinesecrnModule.kt +87 -0
  7. package/android/src/main/java/expo/modules/minesecrn/PoiRequestMapper.kt +101 -0
  8. package/android/src/main/java/expo/modules/minesecrn/ResultMappers.kt +52 -0
  9. package/app.plugin.js +1 -0
  10. package/build/Minesecrn.types.d.ts +91 -0
  11. package/build/Minesecrn.types.d.ts.map +1 -0
  12. package/build/Minesecrn.types.js +3 -0
  13. package/build/Minesecrn.types.js.map +1 -0
  14. package/build/MinesecrnModule.d.ts +36 -0
  15. package/build/MinesecrnModule.d.ts.map +1 -0
  16. package/build/MinesecrnModule.js +3 -0
  17. package/build/MinesecrnModule.js.map +1 -0
  18. package/build/MinesecrnModule.web.d.ts +11 -0
  19. package/build/MinesecrnModule.web.d.ts.map +1 -0
  20. package/build/MinesecrnModule.web.js +22 -0
  21. package/build/MinesecrnModule.web.js.map +1 -0
  22. package/build/index.d.ts +3 -0
  23. package/build/index.d.ts.map +1 -0
  24. package/build/index.js +3 -0
  25. package/build/index.js.map +1 -0
  26. package/expo-module.config.json +10 -0
  27. package/ios/Minesecrn.podspec +24 -0
  28. package/ios/MinesecrnModule.swift +37 -0
  29. package/package.json +80 -0
  30. package/plugin/build/index.d.ts +22 -0
  31. package/plugin/build/index.d.ts.map +1 -0
  32. package/plugin/build/index.js +219 -0
  33. package/plugin/src/index.ts +239 -0
  34. package/plugin/tsconfig.json +17 -0
  35. package/plugin/tsconfig.tsbuildinfo +1 -0
  36. package/src/Minesecrn.types.ts +126 -0
  37. package/src/MinesecrnModule.ts +52 -0
  38. package/src/MinesecrnModule.web.ts +27 -0
  39. package/src/index.ts +2 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015-present 650 Industries, Inc. (aka Expo)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,444 @@
1
+ # @dodicandra/minesec-rn
2
+
3
+ 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.
4
+
5
+ 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`.
6
+
7
+ > ⚠️ **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.
8
+
9
+ ---
10
+
11
+ ## Daftar Isi
12
+
13
+ - [Prasyarat](#prasyarat)
14
+ - [Instalasi](#instalasi)
15
+ - [Konfigurasi](#konfigurasi)
16
+ - [1. Config plugin di app.json](#1-config-plugin-di-appjson)
17
+ - [2. Kredensial Maven MineSec](#2-kredensial-maven-minesec)
18
+ - [3. File lisensi](#3-file-lisensi)
19
+ - [4. Prebuild & build](#4-prebuild--build)
20
+ - [Cara Penggunaan](#cara-penggunaan)
21
+ - [Alur dasar](#alur-dasar)
22
+ - [Inisialisasi SDK](#inisialisasi-sdk)
23
+ - [Initial setup (sekali per instalasi)](#initial-setup-sekali-per-instalasi)
24
+ - [Transaksi SALE (tap kartu NFC)](#transaksi-sale-tap-kartu-nfc)
25
+ - [Void, Refund, Adjust, Auth Completion](#void-refund-adjust-auth-completion)
26
+ - [Query transaksi](#query-transaksi)
27
+ - [Penanganan error](#penanganan-error)
28
+ - [Referensi API](#referensi-api)
29
+ - [Referensi Tipe](#referensi-tipe)
30
+ - [Apa yang Dilakukan Config Plugin](#apa-yang-dilakukan-config-plugin)
31
+ - [Staging vs Production](#staging-vs-production)
32
+ - [Troubleshooting](#troubleshooting)
33
+ - [Menjalankan Contoh (example/)](#menjalankan-contoh-example)
34
+ - [Lisensi](#lisensi)
35
+
36
+ ---
37
+
38
+ ## Prasyarat
39
+
40
+ Sebelum memakai module ini kamu membutuhkan:
41
+
42
+ | Kebutuhan | Keterangan |
43
+ |---|---|
44
+ | Akun MineSec | Kredensial Maven (username + token) untuk mengunduh SDK dari GitHub Packages `TheMinesec/ms-registry-client` |
45
+ | File lisensi `.license` | Diterbitkan MineSec, terikat ke `applicationId` aplikasi kamu. Lisensi stage ≠ lisensi live |
46
+ | Profile ID | ID profil pembayaran (`prof_...`) dari backend MineSec untuk routing transaksi |
47
+ | Expo SDK 52+ | Proyek Expo dengan development build (module ini **tidak jalan di Expo Go**) |
48
+ | Perangkat Android | Android 11+ (API 30) dengan NFC, perangkat fisik (bukan emulator) untuk tap kartu |
49
+
50
+ ## Instalasi
51
+
52
+ ```sh
53
+ npx expo install @dodicandra/minesec-rn
54
+ ```
55
+
56
+ atau dengan package manager langsung:
57
+
58
+ ```sh
59
+ npm install @dodicandra/minesec-rn
60
+ # atau
61
+ yarn add @dodicandra/minesec-rn
62
+ ```
63
+
64
+ Karena berisi kode native, aplikasi harus di-build ulang sebagai [development build](https://docs.expo.dev/develop/development-builds/introduction/) setelah instalasi — Expo Go tidak mendukung native module pihak ketiga.
65
+
66
+ ## Konfigurasi
67
+
68
+ ### 1. Config plugin di app.json
69
+
70
+ Daftarkan plugin di `app.json` / `app.config.js`:
71
+
72
+ ```json
73
+ {
74
+ "expo": {
75
+ "plugins": [
76
+ [
77
+ "@dodicandra/minesec-rn",
78
+ {
79
+ "enableNfc": true,
80
+ "licenseFileName": "mnc.license"
81
+ }
82
+ ]
83
+ ]
84
+ }
85
+ }
86
+ ```
87
+
88
+ Opsi plugin yang tersedia:
89
+
90
+ | Opsi | Tipe | Default | Keterangan |
91
+ |---|---|---|---|
92
+ | `enableNfc` | `boolean` | `true` | Menambahkan permission `android.permission.NFC` dan feature `android.hardware.nfc` (required). Set `false` jika konflik dengan library lain |
93
+ | `licenseFileName` | `string` | — | Nama file lisensi di **project root**; otomatis dicopy ke `android/app/src/main/assets/` saat prebuild |
94
+ | `username` | `string` | — | Maven username MineSec; ditulis ke `gradle.properties`. **Hanya untuk development** — lihat catatan keamanan di bawah |
95
+ | `usertoken` | `string` | — | Maven token MineSec; ditulis ke `gradle.properties`. **Hanya untuk development** |
96
+
97
+ ### 2. Kredensial Maven MineSec
98
+
99
+ SDK native diunduh dari GitHub Packages milik MineSec, sehingga Gradle butuh kredensial. Ada tiga cara, urut dari yang paling direkomendasikan:
100
+
101
+ **a. Environment variables (direkomendasikan untuk CI/production):**
102
+
103
+ ```sh
104
+ export MINESEC_MAVEN_USER="username-anda"
105
+ export MINESEC_MAVEN_TOKEN="ghp_xxxxxxxxxxxx"
106
+ ```
107
+
108
+ **b. Global `~/.gradle/gradle.properties` (direkomendasikan untuk mesin development):**
109
+
110
+ ```properties
111
+ username=username-anda
112
+ usertoken=ghp_xxxxxxxxxxxx
113
+ ```
114
+
115
+ **c. Opsi plugin `username`/`usertoken` di `app.json`:**
116
+
117
+ 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:
118
+
119
+ ```js
120
+ // app.config.js
121
+ export default {
122
+ expo: {
123
+ plugins: [
124
+ [
125
+ '@dodicandra/minesec-rn',
126
+ {
127
+ licenseFileName: 'mnc.license',
128
+ username: process.env.MINESEC_MAVEN_USER,
129
+ usertoken: process.env.MINESEC_MAVEN_TOKEN,
130
+ },
131
+ ],
132
+ ],
133
+ },
134
+ };
135
+ ```
136
+
137
+ ### 3. File lisensi
138
+
139
+ Letakkan file `.license` dari MineSec di **root proyek aplikasi** (sejajar dengan `app.json`):
140
+
141
+ ```
142
+ my-app/
143
+ ├── app.json
144
+ ├── mnc.license ← di sini
145
+ ├── package.json
146
+ └── ...
147
+ ```
148
+
149
+ Saat prebuild, plugin akan menyalinnya ke `android/app/src/main/assets/`. Nama file inilah yang nanti kamu berikan ke `initSoftPos()`.
150
+
151
+ > Penting: lisensi terikat ke `applicationId` **dan** environment (stage/live). Lisensi test/stage tidak akan valid di SDK varian live, dan sebaliknya (error `61001`).
152
+
153
+ ### 4. Prebuild & build
154
+
155
+ ```sh
156
+ npx expo prebuild --platform android
157
+ npx expo run:android
158
+ ```
159
+
160
+ atau dengan EAS:
161
+
162
+ ```sh
163
+ eas build --platform android --profile development
164
+ ```
165
+
166
+ ## Cara Penggunaan
167
+
168
+ ### Alur dasar
169
+
170
+ ```
171
+ App launch ──▶ initSoftPos() (setiap kali app dibuka)
172
+
173
+ Instalasi baru ──▶ initialSetup() (cukup sekali per instalasi)
174
+
175
+ Pembayaran ──▶ launchTransaction() (membuka UI tap kartu NFC)
176
+
177
+ Manajemen ──▶ createAction() (void/refund/adjust — tanpa UI)
178
+ getTransaction() (query status transaksi)
179
+ ```
180
+
181
+ ### Inisialisasi SDK
182
+
183
+ Panggil `initSoftPos()` setiap kali aplikasi dibuka, misalnya di komponen root:
184
+
185
+ ```tsx
186
+ import Minesecrn from '@dodicandra/minesec-rn';
187
+
188
+ const result = await Minesecrn.initSoftPos('mnc.license');
189
+
190
+ if (result.success) {
191
+ console.log('SDK siap:', result.value.sdkVersion, result.value.sdkId);
192
+ } else {
193
+ console.error(`Init gagal [${result.code}]: ${result.message}`);
194
+ }
195
+ ```
196
+
197
+ ### Initial setup (sekali per instalasi)
198
+
199
+ 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:
200
+
201
+ ```ts
202
+ // withTestCapk: true untuk environment staging/development
203
+ const setup = await Minesecrn.initialSetup(true);
204
+
205
+ if (setup.sdkRes.success) {
206
+ console.log('Setup selesai');
207
+ } else {
208
+ console.error('Setup gagal:', setup.sdkRes);
209
+ }
210
+ ```
211
+
212
+ Simpan flag (misalnya di AsyncStorage) agar setup tidak diulang di launch berikutnya.
213
+
214
+ ### Transaksi SALE (tap kartu NFC)
215
+
216
+ `launchTransaction()` membuka activity native full-screen untuk tap kartu, lalu resolve dengan hasil transaksi:
217
+
218
+ ```ts
219
+ const result = await Minesecrn.launchTransaction({
220
+ type: 'ActionNew',
221
+ tranType: 'SALE',
222
+ amount: { value: '10000', currencyCode: 'IDR' },
223
+ profileId: 'prof_01XXXXXXXXXXXXXXXXXXXXXXXX',
224
+ primaryTid: '30157001',
225
+ posReference: `POS-${Date.now()}`,
226
+ description: 'Pembelian kopi',
227
+ });
228
+
229
+ if (result.success) {
230
+ const tx = result.value;
231
+ console.log(`${tx.status} — ${tx.paymentMethod} ${tx.accountMasked}`);
232
+ console.log('tranId:', tx.id, 'approval:', tx.approvalCode);
233
+ } else {
234
+ console.error(`Transaksi gagal [${result.code}]: ${result.message}`);
235
+ }
236
+ ```
237
+
238
+ `tranType` yang didukung: `'SALE'`, `'AUTH'` (pre-authorization), `'REFUND'`.
239
+
240
+ Field opsional lain di `ActionNew`: `cvmSignatureMode` (`'ELECTRONIC_SIGNATURE'` | `'SIGN_ON_PAPER'`), `tapToOwnDevice`, `installmentPlan`, dan `extra` (metadata key-value).
241
+
242
+ ### Void, Refund, Adjust, Auth Completion
243
+
244
+ Aksi terhadap transaksi yang sudah ada tidak butuh tap kartu. Bisa lewat `createAction()` (background, tanpa UI) atau `launchTransaction()` (dengan UI):
245
+
246
+ ```ts
247
+ // Void — membatalkan transaksi sebelum settlement
248
+ await Minesecrn.createAction({
249
+ type: 'ActionVoid',
250
+ tranId: 'tran_01XXXXXXXXXXXXXXXXXXXXXXXX',
251
+ });
252
+
253
+ // Linked refund — refund tanpa tap ulang (hanya gateway MPGS)
254
+ await Minesecrn.createAction({
255
+ type: 'ActionLinkedRefund',
256
+ tranId: 'tran_01XXX...',
257
+ amount: { value: '5000', currencyCode: 'IDR' }, // omit = refund penuh
258
+ });
259
+
260
+ // Adjust — ubah jumlah (tip / incremental auth)
261
+ await Minesecrn.createAction({
262
+ type: 'ActionAdjust',
263
+ tranId: 'tran_01XXX...',
264
+ amount: { value: '12000', currencyCode: 'IDR' }, // total baru
265
+ });
266
+
267
+ // Auth completion — capture transaksi pre-authorized
268
+ await Minesecrn.createAction({
269
+ type: 'ActionAuthComp',
270
+ tranId: 'tran_01XXX...',
271
+ amount: { value: '10000', currencyCode: 'IDR' },
272
+ });
273
+ ```
274
+
275
+ ### Query transaksi
276
+
277
+ ```ts
278
+ // Berdasarkan tranId (prefix "tran_")
279
+ const byId = await Minesecrn.getTransaction('tranId', 'tran_01XXX...');
280
+
281
+ // Atau berdasarkan posReference milik kamu sendiri
282
+ const byRef = await Minesecrn.getTransaction('posReference', 'POS-1720000000000');
283
+ ```
284
+
285
+ ### Penanganan error
286
+
287
+ Semua method mengembalikan `MinesecResult<T>` — discriminated union yang memaksa pengecekan `success` sebelum mengakses data:
288
+
289
+ ```ts
290
+ const result = await Minesecrn.launchTransaction(request);
291
+
292
+ if (result.success) {
293
+ // result.value: Transaction — aman diakses
294
+ } else {
295
+ // result.code: number — kode error MineSec
296
+ // result.message: string — pesan error
297
+ // result.contextual?: string
298
+ // result.extra?: Record<string, string>
299
+ }
300
+ ```
301
+
302
+ Kode error yang umum ditemui:
303
+
304
+ | Kode | Arti | Solusi |
305
+ |---|---|---|
306
+ | `41002` | Cannot get payment profile | Cek `profileId` valid dan profil server cocok dengan versi SDK |
307
+ | `41100` | License file tidak ditemukan | Nama file di `initSoftPos()` harus sama persis dengan file di assets |
308
+ | `60112` | Key attestation gagal | Varian SDK (stage/live) tidak cocok dengan environment server |
309
+ | `61001` | License tidak valid | Lisensi untuk produk/environment berbeda (mis. lisensi v2 dipakai di SDK v1, atau stage vs live) |
310
+
311
+ ## Referensi API
312
+
313
+ Import default adalah instance module, siap pakai:
314
+
315
+ ```ts
316
+ import Minesecrn from '@dodicandra/minesec-rn';
317
+ ```
318
+
319
+ | Method | Signature | Keterangan |
320
+ |---|---|---|
321
+ | `initSoftPos` | `(licenseFileName: string) => Promise<MinesecResult<SdkInitResult>>` | Inisialisasi SDK. Wajib dipanggil setiap app launch, sebelum method lain |
322
+ | `initialSetup` | `(withTestCapk?: boolean) => Promise<SetupResult>` | Download EMV params, CAPK, terminal config, inject keys. Sekali per instalasi. `withTestCapk: true` untuk staging |
323
+ | `launchTransaction` | `(request: PoiRequest) => Promise<MinesecResult<Transaction>>` | Membuka UI native (NFC tap untuk `ActionNew`). Mendukung semua tipe `PoiRequest` |
324
+ | `createAction` | `(request: PoiRequestBackground) => Promise<MinesecResult<Transaction>>` | Eksekusi aksi background tanpa UI — semua tipe kecuali `ActionNew` |
325
+ | `getTransaction` | `(refType: 'tranId' \| 'posReference', refValue: string) => Promise<MinesecResult<Transaction>>` | Query transaksi berdasarkan referensi |
326
+
327
+ ## Referensi Tipe
328
+
329
+ Semua tipe di-export dari package:
330
+
331
+ ```ts
332
+ import type {
333
+ Amount,
334
+ TranType,
335
+ CvmSignatureMode,
336
+ PoiRequest,
337
+ PoiRequestActionNew,
338
+ PoiRequestActionVoid,
339
+ PoiRequestActionLinkedRefund,
340
+ PoiRequestActionAdjust,
341
+ PoiRequestActionAuthComp,
342
+ PoiRequestBackground,
343
+ TransactionReferenceType,
344
+ MinesecResult,
345
+ SdkInitResult,
346
+ SetupResult,
347
+ Transaction,
348
+ } from '@dodicandra/minesec-rn';
349
+ ```
350
+
351
+ Tipe-tipe kunci:
352
+
353
+ ```ts
354
+ type Amount = {
355
+ value: string; // desimal string, mis. "10000" atau "1.00"
356
+ currencyCode: string; // ISO 4217, mis. "IDR", "HKD", "USD"
357
+ };
358
+
359
+ type MinesecResult<T> =
360
+ | { success: true; value: T }
361
+ | { success: false; code: number; message: string; contextual?: string; extra?: Record<string, string> };
362
+
363
+ type Transaction = {
364
+ id: string; // "tran_..."
365
+ status: string | null; // mis. "APPROVED"
366
+ tranType: string | null;
367
+ amount: Amount | null;
368
+ posReference: string | null;
369
+ approvalCode: string | null;
370
+ paymentMethod: string | null; // "VISA", "MASTERCARD", dst.
371
+ accountMasked: string | null; // nomor kartu tersamar
372
+ rrn: string | null;
373
+ batchNo: string | null;
374
+ createdAt: string | null;
375
+ updatedAt: string | null;
376
+ };
377
+ ```
378
+
379
+ ## Apa yang Dilakukan Config Plugin
380
+
381
+ Saat `expo prebuild`, plugin `@dodicandra/minesec-rn` otomatis:
382
+
383
+ 1. **AndroidManifest** — menambahkan permission `INTERNET` (+ `NFC` dan feature `android.hardware.nfc` jika `enableNfc`), serta mendaftarkan `MinesecrnHeadlessActivity` dengan `launchMode="singleTask"`.
384
+ 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.
385
+ 3. **App `build.gradle`** — memastikan `minSdkVersion >= 30` dan menambahkan packaging exclusions `META-INF` yang dibutuhkan SDK.
386
+ 4. **`gradle.properties`** — menulis `username`/`usertoken` jika diberikan lewat opsi plugin.
387
+ 5. **License** — menyalin file lisensi dari project root ke `android/app/src/main/assets/`.
388
+
389
+ Semua langkah idempoten — aman menjalankan prebuild berkali-kali.
390
+
391
+ ## Staging vs Production
392
+
393
+ Module memakai dua varian dependency SDK sesuai build type:
394
+
395
+ ```groovy
396
+ debugImplementation 'com.theminesec.sdk:headless-stage:1.2.57' // server staging
397
+ releaseImplementation 'com.theminesec.sdk:headless:1.2.57' // server live
398
+ ```
399
+
400
+ Konsekuensinya:
401
+
402
+ - **Debug build** terhubung ke server **staging** MineSec → gunakan lisensi test/stage dan `initialSetup(true)`.
403
+ - **Release build** terhubung ke server **live** → gunakan lisensi production dan `initialSetup(false)`.
404
+ - Lisensi, profile ID, dan varian SDK harus berasal dari environment yang **sama** — kombinasi silang menghasilkan error `60112`/`61001`.
405
+
406
+ ## Troubleshooting
407
+
408
+ **Build gagal: `Could not resolve com.theminesec.sdk:headless...`**
409
+ Kredensial Maven tidak terbaca. Cek env var `MINESEC_MAVEN_USER`/`MINESEC_MAVEN_TOKEN` atau `~/.gradle/gradle.properties`, lalu jalankan ulang prebuild.
410
+
411
+ **Error `41100` saat `initSoftPos()`**
412
+ 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.
413
+
414
+ **Error `61001` (invalid license)**
415
+ Lisensi tidak cocok dengan varian SDK atau environment. Lisensi stage hanya untuk debug build (`headless-stage`), lisensi live hanya untuk release build.
416
+
417
+ **Error `60112` (key attestation)**
418
+ Aplikasi debug menghubungi server live atau sebaliknya. Pastikan varian SDK sesuai environment lisensi.
419
+
420
+ **Module tidak ditemukan / `Cannot find native module 'Minesecrn'`**
421
+ Kamu menjalankan di Expo Go, atau belum rebuild setelah instalasi. Jalankan `npx expo prebuild && npx expo run:android`.
422
+
423
+ **NFC tidak merespons saat tap**
424
+ Gunakan perangkat fisik dengan NFC aktif — emulator tidak mendukung NFC. Cek juga NFC di system settings.
425
+
426
+ ## Menjalankan Contoh (example/)
427
+
428
+ Repo ini menyertakan aplikasi contoh lengkap di [`example/`](example/):
429
+
430
+ ```sh
431
+ cd example
432
+ yarn install
433
+ # letakkan file lisensi di example/ sesuai app.json
434
+ npx expo prebuild --platform android
435
+ npx expo run:android
436
+ ```
437
+
438
+ Aplikasi contoh mendemonstrasikan seluruh alur: init → setup → SALE → void → query. Lihat [`example/App.tsx`](example/App.tsx).
439
+
440
+ ## Lisensi
441
+
442
+ MIT © [dodicandra](https://github.com/dodicandra)
443
+
444
+ > Catatan: MineSec SDK sendiri adalah software proprietary milik [MineSec](https://theminesec.com). Package ini hanya bridge — kamu tetap membutuhkan perjanjian dan lisensi resmi dari MineSec untuk memakainya.
@@ -0,0 +1,54 @@
1
+ plugins {
2
+ id 'com.android.library'
3
+ id 'expo-module-gradle-plugin'
4
+ }
5
+
6
+ group = 'expo.modules.minesecrn'
7
+ version = '0.1.0'
8
+
9
+ android {
10
+ namespace "expo.modules.minesecrn"
11
+ defaultConfig {
12
+ minSdk 30
13
+ versionCode 1
14
+ versionName "0.1.0"
15
+ }
16
+ packaging {
17
+ resources {
18
+ excludes += [
19
+ "/META-INF/{AL2.0,LGPL2.1}",
20
+ "META-INF/LICENSE*",
21
+ "META-INF/NOTICE*",
22
+ "META-INF/DEPENDENCIES",
23
+ "*.kotlin_module",
24
+ "META-INF/versions/9/previous-compilation-data.bin"
25
+ ]
26
+ }
27
+ }
28
+ lintOptions {
29
+ abortOnError false
30
+ }
31
+ }
32
+
33
+ repositories {
34
+ maven {
35
+ url = uri("https://maven.pkg.github.com/TheMinesec/ms-registry-client")
36
+ credentials {
37
+ username = (findProperty("username") ?: System.getenv("MINESEC_MAVEN_USER") ?: "").toString()
38
+ password = (findProperty("usertoken") ?: System.getenv("MINESEC_MAVEN_TOKEN") ?: "").toString()
39
+ }
40
+ content {
41
+ includeGroupByRegex "com\\.theminesec\\..*"
42
+ }
43
+ }
44
+ }
45
+
46
+ dependencies {
47
+ // Varian SDK harus cocok dengan environment license yang dipakai:
48
+ // headless-stage = server staging (license test/stage), headless = live.
49
+ debugImplementation 'com.theminesec.sdk:headless-stage:1.2.57'
50
+ releaseImplementation 'com.theminesec.sdk:headless:1.2.57'
51
+ implementation 'androidx.activity:activity-ktx:1.8.2'
52
+ implementation 'org.jetbrains.kotlinx:kotlinx-datetime:0.6.1'
53
+ implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'
54
+ }
@@ -0,0 +1,12 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ <uses-permission android:name="android.permission.NFC" />
3
+ <uses-permission android:name="android.permission.INTERNET" />
4
+ <uses-feature android:name="android.hardware.nfc" android:required="true" />
5
+
6
+ <application>
7
+ <activity
8
+ android:name=".MinesecrnHeadlessActivity"
9
+ android:exported="false"
10
+ android:launchMode="singleTask" />
11
+ </application>
12
+ </manifest>
@@ -0,0 +1,5 @@
1
+ package expo.modules.minesecrn
2
+
3
+ import com.theminesec.sdk.headless.HeadlessActivity
4
+
5
+ class MinesecrnHeadlessActivity : HeadlessActivity()
@@ -0,0 +1,87 @@
1
+ package expo.modules.minesecrn
2
+
3
+ import android.app.Application
4
+ import com.theminesec.sdk.headless.HeadlessActivity
5
+ import com.theminesec.sdk.headless.HeadlessService
6
+ import com.theminesec.sdk.headless.HeadlessSetup
7
+ import com.theminesec.sdk.headless.model.WrappedResult
8
+ import com.theminesec.lib.dto.poi.Referencable
9
+ import expo.modules.kotlin.Promise
10
+ import expo.modules.kotlin.modules.Module
11
+ import expo.modules.kotlin.modules.ModuleDefinition
12
+ import kotlinx.coroutines.runBlocking
13
+
14
+ class MinesecrnModule : Module() {
15
+ companion object {
16
+ const val REQ_TRANSACTION = 42001
17
+ }
18
+
19
+ private var pendingPromise: Promise? = null
20
+
21
+ override fun definition() = ModuleDefinition {
22
+ Name("Minesecrn")
23
+
24
+ AsyncFunction("initSoftPos") { licenseFileName: String ->
25
+ val app = appContext.reactContext!!.applicationContext as Application
26
+ runBlocking { HeadlessSetup.initSoftPos(app, licenseFileName) }.toResultMap()
27
+ }
28
+
29
+ AsyncFunction("initialSetup") { withTestCapk: Boolean ->
30
+ val ctx = appContext.reactContext!!
31
+ runBlocking {
32
+ HeadlessSetup.initialSetup(ctx) { this.withTestCapk = withTestCapk }
33
+ }.toSetupResultMap()
34
+ }
35
+
36
+ AsyncFunction("launchTransaction") { request: Map<String, Any?>, promise: Promise ->
37
+ val activity = appContext.currentActivity
38
+ ?: return@AsyncFunction promise.reject("NO_ACTIVITY", "No current activity available", null)
39
+ try {
40
+ val poiRequest = PoiRequestMapper.toPoiRequest(request)
41
+ pendingPromise = promise
42
+ val contract = HeadlessActivity.contract(MinesecrnHeadlessActivity::class.java)
43
+ activity.startActivityForResult(
44
+ contract.createIntent(activity, poiRequest),
45
+ REQ_TRANSACTION
46
+ )
47
+ } catch (e: IllegalArgumentException) {
48
+ promise.reject("INVALID_REQUEST", e.message ?: "Invalid launchTransaction request", e)
49
+ } catch (e: Exception) {
50
+ pendingPromise = null
51
+ promise.reject("LAUNCH_TRANSACTION_ERROR", e.message ?: "Failed to launch transaction", e)
52
+ }
53
+ }
54
+
55
+ OnActivityResult { _, payload ->
56
+ if (payload.requestCode != REQ_TRANSACTION) return@OnActivityResult
57
+ val contract = HeadlessActivity.contract(MinesecrnHeadlessActivity::class.java)
58
+ val result = contract.parseResult(payload.resultCode, payload.data)
59
+ val p = pendingPromise.also { pendingPromise = null }
60
+ when (result) {
61
+ is WrappedResult.Success<*>,
62
+ is WrappedResult.Failure -> p?.resolve(result.toResultMap())
63
+ null -> p?.reject("NULL_RESULT", "No result returned from HeadlessActivity", null)
64
+ }
65
+ }
66
+
67
+ AsyncFunction("createAction") { request: Map<String, Any?> ->
68
+ val poiRequest = PoiRequestMapper.toServiceRequest(request)
69
+ runBlocking { HeadlessService.createAction(poiRequest) }.fold(
70
+ onSuccess = { mapOf("success" to true, "value" to it.toMap()) },
71
+ onFailure = { mapOf("success" to false, "code" to -1, "message" to (it.message ?: "Unknown error")) }
72
+ )
73
+ }
74
+
75
+ AsyncFunction("getTransaction") { refType: String, refValue: String ->
76
+ val ref = when (refType) {
77
+ "tranId" -> Referencable.TranId(refValue)
78
+ "posReference" -> Referencable.PosReference(refValue)
79
+ else -> throw IllegalArgumentException("Unknown reference type: $refType. Use 'tranId' or 'posReference'")
80
+ }
81
+ runBlocking { HeadlessService.getTransaction(ref) }.fold(
82
+ onSuccess = { mapOf("success" to true, "value" to it.toMap()) },
83
+ onFailure = { mapOf("success" to false, "code" to -1, "message" to (it.message ?: "Unknown error")) }
84
+ )
85
+ }
86
+ }
87
+ }