525 lines
26 KiB
Swift
Executable File
525 lines
26 KiB
Swift
Executable File
import Foundation
|
|
import CoreNFC
|
|
|
|
|
|
extension CFArray {
|
|
func toSwiftArray<T>() -> [T] {
|
|
let array = Array<AnyObject>(_immutableCocoaArray: self)
|
|
return array.compactMap { $0 as? T }
|
|
}
|
|
}
|
|
|
|
extension Dictionary where Key == String, Value == Any {
|
|
var account: String? {
|
|
guard let account = self[kSecAttrAccount as String] as? String else {
|
|
return nil
|
|
}
|
|
return account
|
|
}
|
|
}
|
|
|
|
@available(iOS 13.0, *)
|
|
public class UnifiedNfcApi {
|
|
var stationMap: [Int: Station] = [:]
|
|
var felicaHistoryRetryCount = 0
|
|
|
|
func parseData() {
|
|
stationMap = [
|
|
0: Station(id: 0, name: "PARKIR RESKA", subName: "PARKIR RESKA", latitude: "0", longitude: "0"),
|
|
1: Station(id: 1, name: "Tanah Abang", subName: "Tanah Abang", latitude: "-6.18574476", longitude: "106.8108382"),
|
|
67: Station(id: 67, name: "C-Access", subName: "C-Access", latitude: "0", longitude: "0"),
|
|
257: Station(id: 257, name: "Bogor", subName: "Bogor", latitude: "-6.59561005", longitude: "106.7904379"),
|
|
258: Station(id: 258, name: "Cilebut", subName: "Cilebut", latitude: "-6.53050343", longitude: "106.8005885"),
|
|
259: Station(id: 259, name: "Bojonggede", subName: "Bojonggede", latitude: "-6.49326562", longitude: "106.7949173"),
|
|
260: Station(id: 260, name: "Citayam", subName: "Citayam", latitude: "-6.44879141", longitude: "106.8024588"),
|
|
261: Station(id: 261, name: "Depok", subName: "Depok", latitude: "-6.40493394", longitude: "106.8172447"),
|
|
262: Station(id: 262, name: "Depok Baru", subName: "Depok Baru", latitude: "-6.39113047", longitude: "106.821707"),
|
|
263: Station(id: 263, name: "Pondok Cina", subName: "Pondok Cina", latitude: "-6.36905168", longitude: "106.8322114"),
|
|
264: Station(id: 264, name: "Univ. Indonesia", subName: "Univ. Indonesia", latitude: "-6.36075528", longitude: "106.8317544"),
|
|
265: Station(id: 265, name: "Univ. Pancasila", subName: "Univ. Pancasila", latitude: "-6.33894476", longitude: "106.8344241"),
|
|
272: Station(id: 272, name: "Lenteng Agung", subName: "Lenteng Agung", latitude: "-6.33065157", longitude: "106.8349938"),
|
|
273: Station(id: 273, name: "Tanjung Barat", subName: "Tanjung Barat", latitude: "-6.30780817", longitude: "106.8388513"),
|
|
274: Station(id: 274, name: "Pasar Minggu", subName: "Pasar Minggu", latitude: "-6.28440597", longitude: "106.8445384"),
|
|
275: Station(id: 275, name: "Pasar Minggu Baru", subName: "Pasar Minggu Baru", latitude: "-6.26278132", longitude: "106.8518598"),
|
|
276: Station(id: 276, name: "Duren Kalibata", subName: "Duren Kalibata", latitude: "-6.25534623", longitude: "106.8550195"),
|
|
277: Station(id: 277, name: "Cawang", subName: "Cawang", latitude: "-6.24266069", longitude: "106.8588196"),
|
|
278: Station(id: 278, name: "Tebet", subName: "Tebet", latitude: "-6.22606896", longitude: "106.8583004"),
|
|
279: Station(id: 279, name: "Manggarai", subName: "Manggarai", latitude: "-6.20992352", longitude: "106.8502129"),
|
|
280: Station(id: 280, name: "Cikini", subName: "Cikini", latitude: "-6.19856352", longitude: "106.8412599"),
|
|
281: Station(id: 281, name: "Gondangdia", subName: "Gondangdia", latitude: "-6.18594019", longitude: "106.8325942"),
|
|
288: Station(id: 288, name: "Juanda", subName: "Juanda", latitude: "-6.16672229", longitude: "106.8304674"),
|
|
289: Station(id: 289, name: "Sawah Besar", subName: "Sawah Besar", latitude: "-6.16063965", longitude: "106.8276397"),
|
|
290: Station(id: 290, name: "Mangga Besar", subName: "Mangga Besar", latitude: "-6.14979667", longitude: "106.8269796"),
|
|
291: Station(id: 291, name: "Jayakarta", subName: "Jayakarta", latitude: "-6.14134112", longitude: "106.8230834"),
|
|
292: Station(id: 292, name: "Jakarta Kota", subName: "Jakarta Kota", latitude: "-6.13761335", longitude: "106.8146308"),
|
|
293: Station(id: 293, name: "Bekasi", subName: "Bekasi", latitude: "-6.23614485", longitude: "106.9994173"),
|
|
294: Station(id: 294, name: "Kranji", subName: "Kranji", latitude: "-6.22433352", longitude: "106.9793992"),
|
|
295: Station(id: 295, name: "Cakung", subName: "Cakung", latitude: "-6.21929974", longitude: "106.9521357"),
|
|
296: Station(id: 296, name: "Klender Baru", subName: "Klender Baru", latitude: "-6.21743543", longitude: "106.9396893"),
|
|
297: Station(id: 297, name: "Buaran", subName: "Buaran", latitude: "-6.21615092", longitude: "106.9283069"),
|
|
304: Station(id: 304, name: "Klender", subName: "Klender", latitude: "-6.21335877", longitude: "106.8998889"),
|
|
305: Station(id: 305, name: "Jatinegara", subName: "Jatinegara", latitude: "-6.21513342", longitude: "106.8703259"),
|
|
313: Station(id: 313, name: "Tangerang", subName: "Tangerang", latitude: "-6.17679787", longitude: "106.63272688"),
|
|
327: Station(id: 327, name: "Karet", subName: "Karet", latitude: "-6.2008165", longitude: "106.8159002"),
|
|
328: Station(id: 328, name: "Sudirman", subName: "Sudirman", latitude: "-6.202438", longitude: "106.8234505"),
|
|
329: Station(id: 329, name: "Tanah Abang", subName: "Tanah Abang", latitude: "-6.18574476", longitude: "106.8108382"),
|
|
336: Station(id: 336, name: "Palmerah", subName: "Palmerah", latitude: "-6.20740425", longitude: "106.7974463"),
|
|
337: Station(id: 337, name: "Kebayoran", subName: "Kebayoran", latitude: "-6.23718958", longitude: "106.782542"),
|
|
338: Station(id: 338, name: "Pondok Ranji", subName: "Pondok Ranji", latitude: "-6.27633762", longitude: "106.7449376"),
|
|
339: Station(id: 339, name: "Jurang Mangu", subName: "Jurang Mangu", latitude: "-6.28876225", longitude: "106.7291141"),
|
|
340: Station(id: 340, name: "Sudimara", subName: "Sudimara", latitude: "-6.29694285", longitude: "106.7127952"),
|
|
341: Station(id: 341, name: "Rawabuntu", subName: "Rawabuntu", latitude: "-6.31500105", longitude: "106.6761968"),
|
|
342: Station(id: 342, name: "Serpong", subName: "Serpong", latitude: "-6.32004857", longitude: "106.6655717"),
|
|
343: Station(id: 343, name: "Cisauk", subName: "Cisauk", latitude: "-6.3249995", longitude: "106.6407467"),
|
|
344: Station(id: 344, name: "Cicayur", subName: "Cicayur", latitude: "-6.32951436", longitude: "106.6189624"),
|
|
345: Station(id: 345, name: "Parung Panjang", subName: "Parung Panjang", latitude: "-6.34420808", longitude: "106.5698061"),
|
|
352: Station(id: 352, name: "Cilejit", subName: "Cilejit", latitude: "-6.35434367", longitude: "106.5097328"),
|
|
353: Station(id: 353, name: "Daru", subName: "Daru", latitude: "-6.33800742", longitude: "106.4923913"),
|
|
354: Station(id: 354, name: "Tenjo", subName: "Tenjo", latitude: "-6.32725713", longitude: "106.4613542"),
|
|
355: Station(id: 355, name: "Tigaraksa", subName: "Tigaraksa", latitude: "-6.32846118", longitude: "106.4347451"),
|
|
356: Station(id: 356, name: "Maja", subName: "Maja", latitude: "-6.33230387", longitude: "106.3965692"),
|
|
357: Station(id: 357, name: "Citeras", subName: "Citeras", latitude: "-6.33492764", longitude: "106.3327125"),
|
|
358: Station(id: 358, name: "Rangkasbitung", subName: "Rangkasbitung", latitude: "-6.3526711", longitude: "106.251502"),
|
|
374: Station(id: 374, name: "Bekasi Timur", subName: "Bekasitimur", latitude: "-6.246845", longitude: "107.0181248"),
|
|
376: Station(id: 376, name: "Cikarang", subName: "Cikarang", latitude: "-6.2553926", longitude: "107.1451293")
|
|
]
|
|
}
|
|
var langCode : String?
|
|
var apduRunner = ApduRunner()
|
|
|
|
public init() {
|
|
langCode = Locale.current.languageCode ?? "en"
|
|
parseData()
|
|
}
|
|
|
|
func setCallback(apduCallback : ApduCallback){
|
|
apduRunner.setApduCallback(callback: apduCallback)
|
|
apduRunner.setUnifiedNfcApi(nfcApi: self)
|
|
}
|
|
|
|
func setApduRunner(apRunner : ApduRunner){
|
|
self.apduRunner = apRunner
|
|
}
|
|
|
|
public func checkIfNfcSupported() -> Bool {
|
|
return NFCTagReaderSession.readingAvailable
|
|
}
|
|
|
|
public func searchCard(){
|
|
apduRunner.startScan()
|
|
}
|
|
|
|
public func checkCard(){
|
|
apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.BRIZZI_INIT_APDU, completionHandler: {response in
|
|
if (response.sw1 == 0x91 && response.sw2 == 0x00){
|
|
debugLog("brizzi card")
|
|
let brizzi = BrizziApi()
|
|
brizzi.setApduRunner(apRunner: self.apduRunner)
|
|
brizzi.getUid()
|
|
} else {
|
|
self.checkNext01()
|
|
}
|
|
})
|
|
}
|
|
|
|
public func checkFelicaCard(tag: NFCFeliCaTag){
|
|
readFelicaCard(tag: tag)
|
|
}
|
|
|
|
private func logFelica(_ message: String) {
|
|
_ = message
|
|
// print("[FeliCa] \(message)")
|
|
// debugLog("[FeliCa] \(message)")
|
|
}
|
|
|
|
func readFelicaCard(tag: NFCFeliCaTag){
|
|
let kmt = Emoney()
|
|
logFelica("Start read | currentIDm=\(tag.currentIDm.map { String(format: "%02X", $0) }.joined()) | currentSystemCode=\(tag.currentSystemCode.map { String(format: "%02X", $0) }.joined())")
|
|
|
|
let serviceCode = Data([0x0B, 0x30])
|
|
let blockList = Data([0x80, 0x00])
|
|
|
|
tag.readWithoutEncryption(
|
|
serviceCodeList: [serviceCode],
|
|
blockList: [blockList]
|
|
) { (status1, status2, blockData, error) in
|
|
|
|
if let error = error {
|
|
self.logFelica("Card info read failed | error=\(error.localizedDescription)")
|
|
self.apduRunner.nfcApi?.stopCheckCard(message: "Gagal membaca: \(error.localizedDescription)")
|
|
return
|
|
}
|
|
|
|
// Cek status keberhasilan dari kartu (0x00 0x00 berarti sukses)
|
|
guard status1 == 0x00 && status2 == 0x00 else {
|
|
self.logFelica("Card info read failed | status1=\(status1) | status2=\(status2)")
|
|
self.apduRunner.nfcApi?.stopCheckCard(message: "Error Status: \(status1) \(status2)")
|
|
return
|
|
}
|
|
for (index, data) in blockData.enumerated() {
|
|
let hex = data.map { String(format: "%02X", $0) }.joined()
|
|
self.logFelica("Card info block \(index) raw=\(hex)")
|
|
if let cardNumberString = String(data: data, encoding: .utf8) {
|
|
self.logFelica("Card number parsed=\(cardNumberString)")
|
|
kmt.setCardLabel("KMT")
|
|
kmt.setCardNumber(cardNumberString)
|
|
self.readFelicaBalance(tag: tag, kmt: kmt)
|
|
} else {
|
|
self.logFelica("Card number parse failed for block \(index)")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func readFelicaBalance(tag: NFCFeliCaTag, kmt: Emoney){
|
|
let serviceCode = Data([0x17, 0x10])
|
|
let blockList = Data([0x80, 0x00])
|
|
|
|
tag.readWithoutEncryption(
|
|
serviceCodeList: [serviceCode],
|
|
blockList: [blockList]
|
|
) { (status1, status2, blockData, error) in
|
|
if let error = error {
|
|
self.logFelica("Balance read failed | error=\(error.localizedDescription)")
|
|
self.apduRunner.nfcApi?.stopCheckCard(message: "Gagal membaca: \(error.localizedDescription)")
|
|
return
|
|
}
|
|
|
|
if status1 == 0x00 && status2 == 0x00 {
|
|
guard let firstBlock = blockData.first, firstBlock.count >= 4 else {
|
|
self.logFelica("Balance block missing or too short")
|
|
return
|
|
}
|
|
self.logFelica("Balance raw=\(firstBlock.map { String(format: "%02X", $0) }.joined())")
|
|
let cardBalance = [UInt8](firstBlock)
|
|
var y: Int = 0
|
|
for x in 0..<4 {
|
|
y += Int(cardBalance[x]) << (x * 8)
|
|
}
|
|
|
|
let formatter = NumberFormatter()
|
|
formatter.locale = Locale(identifier: "id_ID")
|
|
formatter.numberStyle = .decimal
|
|
kmt.setBalance(y)
|
|
if let balance = formatter.string(from: NSNumber(value: y)) {
|
|
self.logFelica("Balance parsed=\(y) | formatted=\(balance)")
|
|
}
|
|
self.felicaHistoryRetryCount = 0
|
|
self.readFelicaCardHistory(tag: tag, kmt: kmt)
|
|
} else {
|
|
self.logFelica("Balance read failed | status1=\(status1) | status2=\(status2)")
|
|
// kmt.setTampilRiwayat(false)
|
|
// self.apduRunner.callback?.complete(emoney: kmt)
|
|
// self.apduRunner.sessionEx?.alertMessage = "readFinish".localizeString(string: self.langCode!)
|
|
// self.apduRunner.invalidateSession()
|
|
}
|
|
}
|
|
}
|
|
|
|
func readFelicaCardHistory(tag: NFCFeliCaTag, kmt: Emoney){
|
|
let serviceCode = Data([0x0F, 0x20])
|
|
|
|
// 2. Buat daftar 15 blok (Blok 0 sampai 14) secara otomatis
|
|
var blockList = [Data]()
|
|
for i in 0..<15 {
|
|
blockList.append(Data([0x80, UInt8(i)]))
|
|
}
|
|
|
|
// 3. Panggil fungsi pembacaan
|
|
tag.readWithoutEncryption(
|
|
serviceCodeList: [serviceCode],
|
|
blockList: blockList
|
|
) { (status1, status2, blockData, error) in
|
|
var riwayatList: [RiwayatCard] = []
|
|
if let error = error {
|
|
self.logFelica("History read failed | error=\(error.localizedDescription)")
|
|
self.retryFelicaHistoryRead(tag: tag, kmt: kmt, reason: "error \(error.localizedDescription)")
|
|
return
|
|
}
|
|
|
|
if status1 == 0x00 && status2 == 0x00 {
|
|
self.logFelica("History read success | blocks=\(blockData.count)")
|
|
// blockData akan berisi array of Data, masing-masing 16 byte
|
|
for (index, data) in blockData.enumerated() {
|
|
let riwayat = RiwayatCard()
|
|
var normal = true
|
|
let rawHex = data.map { String(format: "%02X", $0) }.joined()
|
|
self.logFelica("History block \(index) raw=\(rawHex)")
|
|
guard data.count >= 12 else {
|
|
self.logFelica("History block \(index) skipped | reason=too_short | count=\(data.count)")
|
|
continue
|
|
}
|
|
let subId = data.subdata(in: 8..<10)
|
|
|
|
let uid = self.convert(bytes: [UInt8](subId))
|
|
self.logFelica("History block \(index) stationId=\(uid)")
|
|
if (uid == 0){
|
|
normal = false
|
|
}
|
|
if data.count >= 13 {
|
|
let transactionKind = self.felicaTransactionKind(for: [UInt8](data), stationId: uid)
|
|
riwayat.setProsesTipe(transactionKind.prosesTipe)
|
|
riwayat.setTitle(transactionKind.title.localizeString(string: self.langCode!))
|
|
self.logFelica("History block \(index) signature=\(transactionKind.signature) | kind=\(transactionKind.logLabel)")
|
|
}
|
|
if let station = self.stationMap[uid]{
|
|
self.logFelica("History block \(index) stationName=\(station.name)")
|
|
riwayat.setLocationName(station.name.uppercased(with: .autoupdatingCurrent))
|
|
}
|
|
// let station = self.stationMap[uid]
|
|
// print("station", station!)
|
|
// riwayat.setPlace(self.stationMap[uid]!.name)
|
|
if (normal){
|
|
|
|
let subData = data.subdata(in: 0..<4)
|
|
|
|
if let date = self.getDate(data: [UInt8](subData)) {
|
|
riwayat.setTransactionTime(date)
|
|
}
|
|
let formatter = DateFormatter()
|
|
formatter.locale = Locale(identifier: "id_ID") // Format Indonesia
|
|
formatter.dateFormat = "dd MMMM yyyy, HH:mm"
|
|
|
|
if let date = riwayat.getTransationTime() {
|
|
let dateString = formatter.string(from: date)
|
|
self.logFelica("History block \(index) timestamp=\(dateString)")
|
|
}
|
|
|
|
let amn = data.subdata(in: 4..<8)
|
|
let amount = amn.withUnsafeBytes { $0.load(as: Int32.self).bigEndian }
|
|
|
|
//print("Amount: \(amount)")
|
|
|
|
// if (data.count > 10){
|
|
// let type = data[10]
|
|
// print(type)
|
|
// switch type {
|
|
// case 0x01:
|
|
// print("Pembayaran")
|
|
// case 0x00:
|
|
// print("Topup")
|
|
// default:
|
|
// print("Other")
|
|
// }
|
|
//
|
|
// let subId = data.subdata(in: 8..<10)
|
|
//
|
|
// let uid = self.convert(bytes: [UInt8](subId))
|
|
// print("station: \(uid)")
|
|
// }
|
|
riwayat.setAmount(Int(amount))
|
|
let nformatter = NumberFormatter()
|
|
nformatter.locale = Locale(identifier: "id_ID")
|
|
nformatter.numberStyle = .decimal
|
|
if let balance = nformatter.string(from: NSNumber(value: amount)) {
|
|
self.logFelica("History block \(index) amount=\(amount) | formatted=\(balance)")
|
|
}
|
|
} else {
|
|
self.logFelica("History block \(index) mode=RESKA_PARKIR")
|
|
|
|
let stringData = data.map { String(format: "%02X", $0) }.joined()
|
|
|
|
let inputFormatter = DateFormatter()
|
|
inputFormatter.dateFormat = "ddMMyyyyHHmmssSS"
|
|
|
|
let finalData = stringData.prefix(16)
|
|
// 2. Konversi String ke objek Date
|
|
if let date = inputFormatter.date(from: String(finalData)) {
|
|
|
|
// 3. Inisialisasi Formatter untuk mengubah ke format tujuan
|
|
let outputFormatter = DateFormatter()
|
|
outputFormatter.dateFormat = "dd MMM yyyy HH:mm"
|
|
|
|
let result = outputFormatter.string(from: date)
|
|
riwayat.setTransactionTime(date)
|
|
self.logFelica("History block \(index) timestamp=\(result)")
|
|
} else {
|
|
self.logFelica("History block \(index) timestamp_parse_failed")
|
|
}
|
|
let amn = data.subdata(in: 8..<12)
|
|
let amount = amn.withUnsafeBytes { $0.load(as: Int32.self).bigEndian }
|
|
riwayat.setAmount(Int(amount))
|
|
//print("Amount: \(amount)")
|
|
|
|
|
|
let nformatter = NumberFormatter()
|
|
nformatter.locale = Locale(identifier: "id_ID")
|
|
nformatter.numberStyle = .decimal
|
|
if let balance = nformatter.string(from: NSNumber(value: amount)) {
|
|
self.logFelica("History block \(index) amount=\(amount) | formatted=\(balance)")
|
|
}
|
|
}
|
|
let title = riwayat.getTitle() ?? "-"
|
|
let location = riwayat.getLocationName() ?? "-"
|
|
self.logFelica("History block \(index) summary | title=\(title) | prosesTipe=\(riwayat.getProsesTipe()) | location=\(location)")
|
|
riwayatList.append(riwayat)
|
|
}
|
|
kmt.setRiwayatList(riwayatList)
|
|
kmt.setTampilRiwayat(true)
|
|
self.logFelica("History parsed total=\(riwayatList.count)")
|
|
self.apduRunner.callback?.complete(emoney: kmt)
|
|
self.apduRunner.sessionEx?.alertMessage = "readFinish".localizeString(string: self.langCode!)
|
|
self.apduRunner.invalidateSession()
|
|
} else {
|
|
self.logFelica("History read failed | status1=\(status1) | status2=\(status2)")
|
|
self.retryFelicaHistoryRead(tag: tag, kmt: kmt, reason: "status \(status1)-\(status2)")
|
|
}
|
|
}
|
|
}
|
|
|
|
private func retryFelicaHistoryRead(tag: NFCFeliCaTag, kmt: Emoney, reason: String) {
|
|
if felicaHistoryRetryCount < 1 {
|
|
felicaHistoryRetryCount += 1
|
|
logFelica("Retrying history read | reason=\(reason)")
|
|
readFelicaCardHistory(tag: tag, kmt: kmt)
|
|
} else {
|
|
apduRunner.callback?.complete(emoney: kmt)
|
|
apduRunner.sessionEx?.alertMessage = "readFinish".localizeString(string: self.langCode!)
|
|
apduRunner.invalidateSession()
|
|
}
|
|
}
|
|
|
|
func getDate(data: [UInt8]) -> Date? {
|
|
// 1. Tentukan TimeZone Jakarta
|
|
let timeZone = TimeZone(identifier: "Asia/Jakarta") ?? .current
|
|
var calendar = Calendar(identifier: .gregorian)
|
|
calendar.timeZone = timeZone
|
|
|
|
// 2. Set tanggal dasar (1 Januari 2000, 07:00:00)
|
|
var components = DateComponents()
|
|
components.year = 2000
|
|
components.month = 1
|
|
components.day = 1
|
|
components.hour = 7
|
|
components.minute = 0
|
|
components.second = 0
|
|
|
|
guard let baseDate = calendar.date(from: components) else { return nil }
|
|
|
|
// 3. Ambil selisih detik dari data byte
|
|
let secondsToAdd = convert(bytes: data)
|
|
|
|
// 4. Tambahkan detik ke baseDate
|
|
let finalDate = calendar.date(byAdding: .second, value: secondsToAdd, to: baseDate)
|
|
|
|
return finalDate
|
|
}
|
|
|
|
func convert(bytes: [UInt8]) -> Int {
|
|
switch bytes.count {
|
|
case 0:
|
|
return 0
|
|
case 1:
|
|
return Int(bytes[0])
|
|
case 2:
|
|
// Big-endian: (byte[0] << 8) | byte[1]
|
|
return (Int(bytes[0]) << 8) | Int(bytes[1])
|
|
case 3:
|
|
// (byte[0] << 16) | (byte[1] << 8) | byte[2]
|
|
return (Int(bytes[0]) << 16) | (Int(bytes[1]) << 8) | Int(bytes[2])
|
|
default:
|
|
// Padanan ByteBuffer.wrap(bArr).getInt() (Big-endian)
|
|
return Int(bytes.withUnsafeBytes { $0.load(as: Int32.self).bigEndian })
|
|
}
|
|
}
|
|
|
|
private func felicaTransactionKind(for bytes: [UInt8], stationId: Int) -> (prosesTipe: Int, title: String, logLabel: String, signature: String) {
|
|
let signatureBytes = Array(bytes[10...12])
|
|
let signature = signatureBytes.map { String(format: "%02X", $0) }.joined(separator: " ")
|
|
|
|
if stationId == 0 {
|
|
return (1, "payment", "Parkir", signature)
|
|
}
|
|
|
|
switch signatureBytes {
|
|
case [0x00, 0x02, 0x00],
|
|
[0x02, 0x01, 0x00],
|
|
[0x03, 0x01, 0x00]:
|
|
return (0, "topup", "Topup", signature)
|
|
case [0x01, 0x01, 0x01],
|
|
[0x01, 0x58, 0x01],
|
|
[0x03, 0x61, 0x01]:
|
|
return (1, "payment", "Pembayaran", signature)
|
|
default:
|
|
let terminalMarker = bytes[12]
|
|
switch terminalMarker {
|
|
case 0x00:
|
|
return (0, "topup", "Topup (fallback sig[2])", signature)
|
|
case 0x01:
|
|
return (1, "payment", "Pembayaran (fallback sig[2])", signature)
|
|
default:
|
|
return (1, "payment", "Other", signature)
|
|
}
|
|
}
|
|
}
|
|
|
|
public func stopCheckCard(message : String){
|
|
apduRunner.sessionEx?.invalidate(errorMessage: message)
|
|
}
|
|
|
|
private func checkNext01(){
|
|
apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.FLAZZ_INIT_APDU, completionHandler: {response in
|
|
if (response.sw1 == 0x90 && response.sw2 == 0x00){
|
|
debugLog("flazz card")
|
|
let flazz = BcaFlazzApi()
|
|
flazz.setApduRunner(apRunner: self.apduRunner)
|
|
flazz.checkFlazzCard()
|
|
} else {
|
|
self.checkNext02()
|
|
}
|
|
})
|
|
}
|
|
|
|
private func checkNext02(){
|
|
apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.TAPCASH_INIT_APDU, completionHandler: {response in
|
|
if (response.sw1 == 0x90 && response.sw2 == 0x00){
|
|
debugLog("tapcash card")
|
|
let tapCash = TapCashApi()
|
|
tapCash.setApduRunner(apRunner: self.apduRunner)
|
|
tapCash.checkBalance()
|
|
} else {
|
|
self.checkNext03()
|
|
}
|
|
})
|
|
}
|
|
|
|
private func checkNext03(){
|
|
apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.EMONEY_INIT_APDU, completionHandler: {response in
|
|
if (response.sw1 == 0x90 && response.sw2 == 0x00){
|
|
debugLog("emoney card")
|
|
let emoney = MandiriEmoneyApi()
|
|
emoney.setApduRunner(apRunner: self.apduRunner)
|
|
emoney.getCardNumber()
|
|
} else {
|
|
self.checkNext04()
|
|
}
|
|
})
|
|
}
|
|
|
|
private func checkNext04(){
|
|
apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.JACKCARD_INIT_APDU, completionHandler: {response in
|
|
if (response.sw1 == 0x90 && response.sw2 == 0x00){
|
|
debugLog("jack card")
|
|
let jack = JackCardApi()
|
|
jack.setApduRunner(apRunner: self.apduRunner)
|
|
jack.getBalance(resp: response.getData())
|
|
} else {
|
|
self.checkNext05()
|
|
}
|
|
})
|
|
}
|
|
|
|
private func checkNext05(){
|
|
apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.MEGA_APDU01, completionHandler: {response in
|
|
if (response.sw1 == 0x90 && response.sw2 == 0x00){
|
|
debugLog("megacash")
|
|
let mega = MegaCashApi()
|
|
mega.setApduRunner(apRunner: self.apduRunner)
|
|
mega.getBalance(resp: response.getData())
|
|
} else {
|
|
self.apduRunner.invalidateSession(msg: "Card not supported")
|
|
}
|
|
})
|
|
}
|
|
}
|