@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.
- package/LICENSE +21 -0
- package/README.md +444 -0
- package/android/build.gradle +54 -0
- package/android/src/main/AndroidManifest.xml +12 -0
- package/android/src/main/java/expo/modules/minesecrn/MinesecrnHeadlessActivity.kt +5 -0
- package/android/src/main/java/expo/modules/minesecrn/MinesecrnModule.kt +87 -0
- package/android/src/main/java/expo/modules/minesecrn/PoiRequestMapper.kt +101 -0
- package/android/src/main/java/expo/modules/minesecrn/ResultMappers.kt +52 -0
- package/app.plugin.js +1 -0
- package/build/Minesecrn.types.d.ts +91 -0
- package/build/Minesecrn.types.d.ts.map +1 -0
- package/build/Minesecrn.types.js +3 -0
- package/build/Minesecrn.types.js.map +1 -0
- package/build/MinesecrnModule.d.ts +36 -0
- package/build/MinesecrnModule.d.ts.map +1 -0
- package/build/MinesecrnModule.js +3 -0
- package/build/MinesecrnModule.js.map +1 -0
- package/build/MinesecrnModule.web.d.ts +11 -0
- package/build/MinesecrnModule.web.d.ts.map +1 -0
- package/build/MinesecrnModule.web.js +22 -0
- package/build/MinesecrnModule.web.js.map +1 -0
- package/build/index.d.ts +3 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +3 -0
- package/build/index.js.map +1 -0
- package/expo-module.config.json +10 -0
- package/ios/Minesecrn.podspec +24 -0
- package/ios/MinesecrnModule.swift +37 -0
- package/package.json +80 -0
- package/plugin/build/index.d.ts +22 -0
- package/plugin/build/index.d.ts.map +1 -0
- package/plugin/build/index.js +219 -0
- package/plugin/src/index.ts +239 -0
- package/plugin/tsconfig.json +17 -0
- package/plugin/tsconfig.tsbuildinfo +1 -0
- package/src/Minesecrn.types.ts +126 -0
- package/src/MinesecrnModule.ts +52 -0
- package/src/MinesecrnModule.web.ts +27 -0
- 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,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
|
+
}
|