|
|
|
|
@ -9,382 +9,286 @@ import Foundation
|
|
|
|
|
import CoreNFC
|
|
|
|
|
|
|
|
|
|
public class BcaFlazzApi : UnifiedNfcApi {
|
|
|
|
|
var emoney : Emoney = Emoney()
|
|
|
|
|
var riwayatList: [RiwayatCard] = []
|
|
|
|
|
var start = 0
|
|
|
|
|
var finishV2 = 5
|
|
|
|
|
var finish2V202 = 256
|
|
|
|
|
var finishV1 = 16
|
|
|
|
|
var mapv1 : String = ""
|
|
|
|
|
var mapv2 : String = ""
|
|
|
|
|
private let emoney: Emoney = Emoney()
|
|
|
|
|
private var ef84Records: [RiwayatCard] = []
|
|
|
|
|
private var extendedRecords: [RiwayatCard] = []
|
|
|
|
|
|
|
|
|
|
private let ef84MaxSlots = 10
|
|
|
|
|
private let extendedMaxRecords = 256
|
|
|
|
|
|
|
|
|
|
public override init() {}
|
|
|
|
|
|
|
|
|
|
public func checkFlazzCard(){
|
|
|
|
|
apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.BCA_APDU01, completionHandler: {response in
|
|
|
|
|
if (response.sw1 == 0x90 && response.sw2 == 0x00){
|
|
|
|
|
public func checkFlazzCard() {
|
|
|
|
|
selectDirectoryFile(useFallbackLe: false)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private func selectDirectoryFile(useFallbackLe: Bool) {
|
|
|
|
|
let command = NFCISO7816APDU(
|
|
|
|
|
instructionClass: 0x00,
|
|
|
|
|
instructionCode: 0xA4,
|
|
|
|
|
p1Parameter: 0x01,
|
|
|
|
|
p2Parameter: 0x00,
|
|
|
|
|
data: Data([0x02, 0x00]),
|
|
|
|
|
expectedResponseLength: useFallbackLe ? 256 : -1
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
apduRunner.exchangeApdu(apduCommand: command, completionHandler: { response in
|
|
|
|
|
if response.sw1 == 0x90 && response.sw2 == 0x00 {
|
|
|
|
|
self.emoney.setCardLabel("BCA Flazz")
|
|
|
|
|
self.getCardNumber()
|
|
|
|
|
} else if !useFallbackLe {
|
|
|
|
|
self.selectDirectoryFile(useFallbackLe: true)
|
|
|
|
|
} else {
|
|
|
|
|
self.apduRunner.invalidateSession(msg: "readFailed".localizeString(string: self.langCode!))
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private func getCardNumber(){
|
|
|
|
|
apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.BCA_APDU02, completionHandler: {response in
|
|
|
|
|
if (response.sw1 == 0x90 && response.sw2 == 0x00){
|
|
|
|
|
let raw = String(data: response.getData(), encoding: .isoLatin1)
|
|
|
|
|
let start = raw!.firstIndex(of: ";")?.utf16Offset(in: raw!)
|
|
|
|
|
let end = raw!.firstIndex(of: "=")?.utf16Offset(in: raw!)
|
|
|
|
|
self.emoney.setCardNumber(String((raw?.subString(from: (start! + 1), to: end!))!))
|
|
|
|
|
private func getCardNumber() {
|
|
|
|
|
apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.BCA_APDU02, completionHandler: { response in
|
|
|
|
|
if response.sw1 == 0x90 && response.sw2 == 0x00 {
|
|
|
|
|
guard let raw = String(data: response.getData(), encoding: .isoLatin1) else {
|
|
|
|
|
self.apduRunner.invalidateSession(msg: "readFailed".localizeString(string: self.langCode!))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let cardNumber = self.extractPan(from: raw) {
|
|
|
|
|
self.emoney.setCardNumber(cardNumber)
|
|
|
|
|
self.getBalance()
|
|
|
|
|
} else {
|
|
|
|
|
self.apduRunner.invalidateSession()
|
|
|
|
|
self.apduRunner.invalidateSession(msg: "readFailed".localizeString(string: self.langCode!))
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private func getBalance(){
|
|
|
|
|
apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.BCA_APDU03, completionHandler: {response in
|
|
|
|
|
if (response.sw1 == 0x90 && response.sw2 == 0x00){
|
|
|
|
|
let raw = response.getData().hexEncodedString()
|
|
|
|
|
let balance = raw.subString(from: 2, to: 8)
|
|
|
|
|
self.emoney.setBalance(balance.hex2decimal())
|
|
|
|
|
self.checkLog()
|
|
|
|
|
} else {
|
|
|
|
|
self.apduRunner.invalidateSession(msg: "readFailed".localizeString(string: self.langCode!))
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private func checkLog(){
|
|
|
|
|
apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.BCA_APDU04, completionHandler: {response in
|
|
|
|
|
if (response.sw1 == 0x90 && response.sw2 == 0x00){
|
|
|
|
|
debugLog("log v2")
|
|
|
|
|
// self.updateScreen()
|
|
|
|
|
self.getLogV2step01(index: self.start)
|
|
|
|
|
} else {
|
|
|
|
|
debugLog("log v1")
|
|
|
|
|
// self.updateScreen()
|
|
|
|
|
self.getLogV1step01(index: self.start)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private func getLogV2step01(index : Int){
|
|
|
|
|
debugLog("log getLogV2step01")
|
|
|
|
|
//00 B0 85 00 78
|
|
|
|
|
let st = index*60
|
|
|
|
|
let hex = String(st, radix: 16)
|
|
|
|
|
let BCAV2_LOG = NFCISO7816APDU(instructionClass : 0x00, instructionCode : 0xB0, p1Parameter : 0x85, p2Parameter : hex.hex2byte().bytes.first!, data : Data(), expectedResponseLength : 120)
|
|
|
|
|
apduRunner.exchangeApdu(apduCommand: BCAV2_LOG, completionHandler: {response in
|
|
|
|
|
if (response.sw1 == 0x90 && response.sw2 == 0x00){
|
|
|
|
|
debugLog("log data", response.getData().hexEncodedString())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self.mapv1.append(response.getData().hexEncodedString())
|
|
|
|
|
self.start+=1
|
|
|
|
|
if (self.start < (self.finishV2)){
|
|
|
|
|
self.getLogV2step01(index: self.start)
|
|
|
|
|
} else {
|
|
|
|
|
//mapping first
|
|
|
|
|
self.parseLogV201()
|
|
|
|
|
self.start = 0
|
|
|
|
|
self.getLogV2step02(index: self.start)
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
self.parseLogV201()
|
|
|
|
|
self.start = 0
|
|
|
|
|
self.getLogV2step02(index: self.start)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private func getLogV1step01(index : Int){
|
|
|
|
|
debugLog("log getLogV1step01")
|
|
|
|
|
//00 b0 84 00 3c
|
|
|
|
|
let st = index*15
|
|
|
|
|
let hex = String(st, radix: 16)
|
|
|
|
|
let BCAV2_LOG = NFCISO7816APDU(instructionClass : 0x00, instructionCode : 0xB0, p1Parameter : 0x84, p2Parameter : hex.hex2byte().bytes.first!, data : Data(), expectedResponseLength : 60)
|
|
|
|
|
apduRunner.exchangeApdu(apduCommand: BCAV2_LOG, completionHandler: {response in
|
|
|
|
|
if (response.sw1 == 0x90 && response.sw2 == 0x00){
|
|
|
|
|
self.mapv1.append(response.getData().hexEncodedString())
|
|
|
|
|
self.start+=1
|
|
|
|
|
if (self.start < (self.finishV1)){
|
|
|
|
|
self.getLogV1step01(index: self.start)
|
|
|
|
|
} else {
|
|
|
|
|
self.start = 0
|
|
|
|
|
self.getLogV1step02(index: self.start)
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
self.start = 0
|
|
|
|
|
self.getLogV1step02(index: self.start)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private func getLogV1step02(index : Int){
|
|
|
|
|
debugLog("log getLogV1step02")
|
|
|
|
|
//00b085003c
|
|
|
|
|
let st = index*15
|
|
|
|
|
let hex = String(st, radix: 16)
|
|
|
|
|
let BCAV2_LOG = NFCISO7816APDU(instructionClass : 0x00, instructionCode : 0xB0, p1Parameter : 0x85, p2Parameter : hex.hex2byte().bytes.first!, data : Data(), expectedResponseLength : 60)
|
|
|
|
|
apduRunner.exchangeApdu(apduCommand: BCAV2_LOG, completionHandler: {response in
|
|
|
|
|
if (response.sw1 == 0x90 && response.sw2 == 0x00){
|
|
|
|
|
self.mapv1.append(response.getData().hexEncodedString())
|
|
|
|
|
self.start+=1
|
|
|
|
|
if (self.start < (self.finishV1)){
|
|
|
|
|
self.getLogV1step02(index: self.start)
|
|
|
|
|
} else {
|
|
|
|
|
self.parseLogV101()
|
|
|
|
|
self.riwayatList = self.riwayatList.sorted(by: { $0.getTransationTime()?.compare($1.getTransationTime()!) == .orderedDescending })
|
|
|
|
|
|
|
|
|
|
if (self.riwayatList.count > 0){
|
|
|
|
|
self.emoney.setRiwayatList(self.riwayatList)
|
|
|
|
|
self.emoney.setTampilRiwayat(true)
|
|
|
|
|
}
|
|
|
|
|
self.updateScreen()
|
|
|
|
|
self.apduRunner.sessionEx?.alertMessage = "readFinish".localizeString(string: self.langCode!)
|
|
|
|
|
self.apduRunner.invalidateSession()
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
self.parseLogV101()
|
|
|
|
|
self.riwayatList = self.riwayatList.sorted(by: { $0.getTransationTime()?.compare($1.getTransationTime()!) == .orderedDescending })
|
|
|
|
|
|
|
|
|
|
if (self.riwayatList.count > 0){
|
|
|
|
|
self.emoney.setRiwayatList(self.riwayatList)
|
|
|
|
|
self.emoney.setTampilRiwayat(true)
|
|
|
|
|
}
|
|
|
|
|
self.updateScreen()
|
|
|
|
|
self.apduRunner.sessionEx?.alertMessage = "readFinish".localizeString(string: self.langCode!)
|
|
|
|
|
self.apduRunner.invalidateSession()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private func getLogV2step02(index : Int){
|
|
|
|
|
debugLog("log getLogV2step02")
|
|
|
|
|
//00b08400f0
|
|
|
|
|
let st = index*60
|
|
|
|
|
let hex = String(st, radix: 16)
|
|
|
|
|
let BCAV2_LOG = NFCISO7816APDU(instructionClass : 0x00, instructionCode : 0xB0, p1Parameter : 0x84, p2Parameter : hex.hex2byte().bytes.first!, data : Data(), expectedResponseLength : 240)
|
|
|
|
|
apduRunner.exchangeApdu(apduCommand: BCAV2_LOG, completionHandler: {response in
|
|
|
|
|
if (response.sw1 == 0x90 && response.sw2 == 0x00){
|
|
|
|
|
self.mapv1.append(response.getData().hexEncodedString())
|
|
|
|
|
self.start+=1
|
|
|
|
|
if (self.start < (self.finishV2)){
|
|
|
|
|
self.getLogV2step02(index: self.start)
|
|
|
|
|
} else {
|
|
|
|
|
self.start = 0
|
|
|
|
|
self.getLogV2step03()
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
self.start = 0
|
|
|
|
|
self.getLogV2step03()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private func getLogV2step03(){
|
|
|
|
|
debugLog("log getLogV2step03")
|
|
|
|
|
//00 84 00 00 08
|
|
|
|
|
let BCAV2_LOG = NFCISO7816APDU(instructionClass : 0x00, instructionCode : 0x84, p1Parameter : 0x00, p2Parameter : 0x00, data : Data(), expectedResponseLength : 8)
|
|
|
|
|
apduRunner.exchangeApdu(apduCommand: BCAV2_LOG, completionHandler: {response in
|
|
|
|
|
if (response.sw1 == 0x90 && response.sw2 == 0x00){
|
|
|
|
|
self.getLogV2step04(data: response.getData())
|
|
|
|
|
} else {
|
|
|
|
|
self.apduRunner.sessionEx?.alertMessage = "readFinish".localizeString(string: self.langCode!)
|
|
|
|
|
self.apduRunner.invalidateSession()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private func getLogV2step04(data: Data){
|
|
|
|
|
debugLog("log getLogV2step04")
|
|
|
|
|
//90 32 03 00 0A 0801 0000000000000000 29
|
|
|
|
|
let send = "0801" + data.hexEncodedString()
|
|
|
|
|
let BCAV2_LOG = NFCISO7816APDU(instructionClass : 0x90, instructionCode : 0x32, p1Parameter : 0x03, p2Parameter : 0x00, data : Data(_: send.hex2byte()), expectedResponseLength : 41)
|
|
|
|
|
apduRunner.exchangeApdu(apduCommand: BCAV2_LOG, completionHandler: {response in
|
|
|
|
|
if (response.sw1 == 0x90 && response.sw2 == 0x00){
|
|
|
|
|
self.getLogV2step05(index: self.start)
|
|
|
|
|
} else {
|
|
|
|
|
self.apduRunner.sessionEx?.alertMessage = "readFinish".localizeString(string: self.langCode!)
|
|
|
|
|
self.apduRunner.invalidateSession()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private func getLogV2step05(index : Int){
|
|
|
|
|
debugLog("log getLogV2step05")
|
|
|
|
|
//00 B0 89 00 40
|
|
|
|
|
let st = index
|
|
|
|
|
let hex = String(st, radix: 16)
|
|
|
|
|
let BCAV2_LOG = NFCISO7816APDU(instructionClass : 0x00, instructionCode : 0xB0, p1Parameter : 0x89, p2Parameter : hex.hex2byte().bytes.first!, data : Data(), expectedResponseLength : 64)
|
|
|
|
|
apduRunner.exchangeApdu(apduCommand: BCAV2_LOG, completionHandler: {response in
|
|
|
|
|
if (response.sw1 == 0x90 && response.sw2 == 0x00){
|
|
|
|
|
self.mapv2.append(response.getData().hexEncodedString())
|
|
|
|
|
self.start+=1
|
|
|
|
|
if (self.start < (self.finish2V202)){
|
|
|
|
|
self.getLogV2step05(index: self.start)
|
|
|
|
|
} else {
|
|
|
|
|
self.start = 0
|
|
|
|
|
self.getLogV2step06(index: self.start)
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
self.start = 0
|
|
|
|
|
self.getLogV2step06(index: self.start)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private func getLogV2step06(index : Int){
|
|
|
|
|
debugLog("log getLogV2step06")
|
|
|
|
|
//90 32 03 00 01 00 20
|
|
|
|
|
let st = index
|
|
|
|
|
let hex = String(st, radix: 16)
|
|
|
|
|
let BCAV2_LOG = NFCISO7816APDU(instructionClass : 0x90, instructionCode : 0x32, p1Parameter : 0x03, p2Parameter : 0x00, data : Data(_: hex.hex2byte()), expectedResponseLength : 32)
|
|
|
|
|
apduRunner.exchangeApdu(apduCommand: BCAV2_LOG, completionHandler: {response in
|
|
|
|
|
if (response.sw1 == 0x90 && response.sw2 == 0x00){
|
|
|
|
|
self.mapv2.append(response.getData().hexEncodedString())
|
|
|
|
|
self.start+=1
|
|
|
|
|
if (self.start < (self.finish2V202)){
|
|
|
|
|
self.getLogV2step06(index: self.start)
|
|
|
|
|
} else {
|
|
|
|
|
//mapping the data
|
|
|
|
|
self.parseLogV202()
|
|
|
|
|
self.riwayatList = self.riwayatList.sorted(by: { $0.getTransationTime()?.compare($1.getTransationTime()!) == .orderedDescending })
|
|
|
|
|
|
|
|
|
|
if (self.riwayatList.count > 0){
|
|
|
|
|
self.emoney.setRiwayatList(self.riwayatList)
|
|
|
|
|
self.emoney.setTampilRiwayat(true)
|
|
|
|
|
}
|
|
|
|
|
self.updateScreen()
|
|
|
|
|
self.apduRunner.sessionEx?.alertMessage = "readFinish".localizeString(string: self.langCode!)
|
|
|
|
|
self.apduRunner.invalidateSession()
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
//mapping the data
|
|
|
|
|
self.parseLogV202()
|
|
|
|
|
self.riwayatList = self.riwayatList.sorted(by: { $0.getTransationTime()?.compare($1.getTransationTime()!) == .orderedDescending })
|
|
|
|
|
|
|
|
|
|
if (self.riwayatList.count > 0){
|
|
|
|
|
self.emoney.setRiwayatList(self.riwayatList)
|
|
|
|
|
self.emoney.setTampilRiwayat(true)
|
|
|
|
|
}
|
|
|
|
|
self.updateScreen()
|
|
|
|
|
self.apduRunner.sessionEx?.alertMessage = "readFinish".localizeString(string: self.langCode!)
|
|
|
|
|
self.apduRunner.invalidateSession()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private func parseLogV101(){
|
|
|
|
|
debugLog("log parseLogV101")
|
|
|
|
|
let logs = self.mapv1.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
|
|
|
if (logs.count % 120 != 0){
|
|
|
|
|
private func getBalance() {
|
|
|
|
|
apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.BCA_APDU03, completionHandler: { response in
|
|
|
|
|
if response.sw1 == 0x90 && response.sw2 == 0x00 {
|
|
|
|
|
let bytes = response.getData().bytes
|
|
|
|
|
guard bytes.count >= 4 else {
|
|
|
|
|
self.apduRunner.invalidateSession(msg: "readFailed".localizeString(string: self.langCode!))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
let total = logs.count/120
|
|
|
|
|
for i in 0..<total {
|
|
|
|
|
let start = i*120
|
|
|
|
|
let end = start + 120
|
|
|
|
|
let data = logs.subString(from: start, to: end)
|
|
|
|
|
let riwayat = RiwayatCard()
|
|
|
|
|
let location = data.subString(from: 60, to: 76).hex2byte()
|
|
|
|
|
let locationId = String(data: location, encoding: .utf8)!
|
|
|
|
|
riwayat.setLocationId(locationId)
|
|
|
|
|
// riwayat.setLocationName(data.subString(from: 0, to: 16))
|
|
|
|
|
let amount = data.subString(from: 12, to: 18).hex2decimal()
|
|
|
|
|
riwayat.setAmount(amount)
|
|
|
|
|
let transactionTime = data.subString(from: 76, to: 84).hex2decimal()
|
|
|
|
|
riwayat.setTransactionTime(formatDate(seconds: transactionTime))
|
|
|
|
|
let type = data.subString(from: 0, to: 4).hex2decimal()
|
|
|
|
|
if (type == 1024){
|
|
|
|
|
riwayat.setProsesTipe(1)
|
|
|
|
|
riwayat.setTitle("payment".localizeString(string: self.langCode!))
|
|
|
|
|
|
|
|
|
|
self.emoney.setBalance(self.uint32BE(bytes, offset: 0))
|
|
|
|
|
self.readHistoryCheck()
|
|
|
|
|
} else {
|
|
|
|
|
riwayat.setProsesTipe(0)
|
|
|
|
|
riwayat.setTitle("topup".localizeString(string: self.langCode!))
|
|
|
|
|
self.apduRunner.invalidateSession(msg: "readFailed".localizeString(string: self.langCode!))
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (transactionTime > 0){
|
|
|
|
|
riwayatList.append(riwayat)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
private func readHistoryCheck() {
|
|
|
|
|
apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.BCA_APDU04, completionHandler: { response in
|
|
|
|
|
let shouldReadExtended = response.sw1 == 0x90 && response.sw2 == 0x00
|
|
|
|
|
self.readEf84Record(slot: 0, shouldReadExtended: shouldReadExtended)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private func parseLogV201(){
|
|
|
|
|
debugLog("log parseLogV201")
|
|
|
|
|
let logs = self.mapv1.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
|
|
|
if (logs.count % 120 != 0){
|
|
|
|
|
private func readEf84Record(slot: Int, shouldReadExtended: Bool) {
|
|
|
|
|
guard slot < ef84MaxSlots else {
|
|
|
|
|
if shouldReadExtended {
|
|
|
|
|
getChallenge()
|
|
|
|
|
} else {
|
|
|
|
|
finishReading()
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
let total = logs.count/120
|
|
|
|
|
for i in 0..<total {
|
|
|
|
|
let start = i*120
|
|
|
|
|
let end = start + 120
|
|
|
|
|
let data = logs.subString(from: start, to: end)
|
|
|
|
|
let riwayat = RiwayatCard()
|
|
|
|
|
let location = data.subString(from: 60, to: 76).hex2byte()
|
|
|
|
|
let locationId = String(data: location, encoding: .utf8)!
|
|
|
|
|
riwayat.setLocationId(locationId)
|
|
|
|
|
// riwayat.setLocationName(data.subString(from: 0, to: 16))
|
|
|
|
|
let amount = data.subString(from: 12, to: 18).hex2decimal()
|
|
|
|
|
riwayat.setAmount(amount)
|
|
|
|
|
let transactionTime = data.subString(from: 76, to: 84).hex2decimal()
|
|
|
|
|
riwayat.setTransactionTime(formatDate(seconds: transactionTime))
|
|
|
|
|
let type = data.subString(from: 0, to: 4).hex2decimal()
|
|
|
|
|
if (type == 1024){
|
|
|
|
|
riwayat.setProsesTipe(1)
|
|
|
|
|
riwayat.setTitle("payment".localizeString(string: self.langCode!))
|
|
|
|
|
|
|
|
|
|
let command = NFCISO7816APDU(
|
|
|
|
|
instructionClass: 0x00,
|
|
|
|
|
instructionCode: 0xB0,
|
|
|
|
|
p1Parameter: 0x84,
|
|
|
|
|
p2Parameter: UInt8(slot * 0x0F),
|
|
|
|
|
data: Data(),
|
|
|
|
|
expectedResponseLength: 60
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
apduRunner.exchangeApdu(apduCommand: command, completionHandler: { response in
|
|
|
|
|
if response.sw1 == 0x90 && response.sw2 == 0x00 {
|
|
|
|
|
if let record = self.parseEf84Record(response.getData()) {
|
|
|
|
|
self.ef84Records.append(record)
|
|
|
|
|
}
|
|
|
|
|
self.readEf84Record(slot: slot + 1, shouldReadExtended: shouldReadExtended)
|
|
|
|
|
} else if response.sw1 == 0x6B && response.sw2 == 0x00 {
|
|
|
|
|
if shouldReadExtended {
|
|
|
|
|
self.getChallenge()
|
|
|
|
|
} else {
|
|
|
|
|
riwayat.setProsesTipe(0)
|
|
|
|
|
riwayat.setTitle("topup".localizeString(string: self.langCode!))
|
|
|
|
|
self.finishReading()
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if shouldReadExtended {
|
|
|
|
|
self.getChallenge()
|
|
|
|
|
} else {
|
|
|
|
|
self.finishReading()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (transactionTime > 0){
|
|
|
|
|
riwayatList.append(riwayat)
|
|
|
|
|
}
|
|
|
|
|
private func getChallenge() {
|
|
|
|
|
let command = NFCISO7816APDU(
|
|
|
|
|
instructionClass: 0x00,
|
|
|
|
|
instructionCode: 0x84,
|
|
|
|
|
p1Parameter: 0x00,
|
|
|
|
|
p2Parameter: 0x00,
|
|
|
|
|
data: Data(),
|
|
|
|
|
expectedResponseLength: 8
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
apduRunner.exchangeApdu(apduCommand: command, completionHandler: { response in
|
|
|
|
|
if response.sw1 == 0x90 && response.sw2 == 0x00 {
|
|
|
|
|
self.readExtendedRecord(index: 0)
|
|
|
|
|
} else {
|
|
|
|
|
self.finishReading()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private func parseLogV202(){
|
|
|
|
|
debugLog("log parseLogV202")
|
|
|
|
|
let logs = self.mapv2.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
|
|
|
if (logs.count % 64 != 0){
|
|
|
|
|
private func readExtendedRecord(index: Int) {
|
|
|
|
|
guard index < extendedMaxRecords else {
|
|
|
|
|
finishReading()
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
let total = logs.count/64
|
|
|
|
|
for i in 0..<total {
|
|
|
|
|
let start = i*64
|
|
|
|
|
let end = start + 64
|
|
|
|
|
let data = logs.subString(from: start, to: end)
|
|
|
|
|
let riwayat = RiwayatCard()
|
|
|
|
|
// let location = data.subString(from: 28, to: 44).hex2byte()
|
|
|
|
|
// let locationId = String(data: location, encoding: .utf8)!
|
|
|
|
|
// riwayat.setLocationId(locationId)
|
|
|
|
|
let transactionTime = data.subString(from: 8, to: 16).hex2decimal()
|
|
|
|
|
riwayat.setTransactionTime(formatDate(seconds: transactionTime))
|
|
|
|
|
let type = data.subString(from: 0, to: 2).hex2decimal()
|
|
|
|
|
let amount = data.subString(from: 2, to: 8).hex2decimal()
|
|
|
|
|
if (type == 4){
|
|
|
|
|
riwayat.setProsesTipe(1)
|
|
|
|
|
riwayat.setAmount(16777216 - amount)
|
|
|
|
|
riwayat.setTitle("payment".localizeString(string: self.langCode!))
|
|
|
|
|
|
|
|
|
|
let command = NFCISO7816APDU(
|
|
|
|
|
instructionClass: 0x90,
|
|
|
|
|
instructionCode: 0x32,
|
|
|
|
|
p1Parameter: 0x03,
|
|
|
|
|
p2Parameter: 0x00,
|
|
|
|
|
data: Data([UInt8(index)]),
|
|
|
|
|
expectedResponseLength: 32
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
apduRunner.exchangeApdu(apduCommand: command, completionHandler: { response in
|
|
|
|
|
if response.sw1 == 0x90 && response.sw2 == 0x00 {
|
|
|
|
|
if let record = self.parseExtendedRecord(response.getData()) {
|
|
|
|
|
self.extendedRecords.append(record)
|
|
|
|
|
}
|
|
|
|
|
self.readExtendedRecord(index: index + 1)
|
|
|
|
|
} else if response.sw1 == 0x6A && response.sw2 == 0x80 {
|
|
|
|
|
self.finishReading()
|
|
|
|
|
} else {
|
|
|
|
|
riwayat.setProsesTipe(0)
|
|
|
|
|
riwayat.setAmount(amount)
|
|
|
|
|
riwayat.setTitle("topup".localizeString(string: self.langCode!))
|
|
|
|
|
}
|
|
|
|
|
if (transactionTime > 0){
|
|
|
|
|
riwayatList.append(riwayat)
|
|
|
|
|
}
|
|
|
|
|
self.finishReading()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private func formatDate(seconds : Int) -> Date{
|
|
|
|
|
// Specify date components
|
|
|
|
|
private func finishReading() {
|
|
|
|
|
var history = ef84Records + extendedRecords
|
|
|
|
|
history.sort {
|
|
|
|
|
($0.getTransationTime() ?? Date.distantPast) > ($1.getTransationTime() ?? Date.distantPast)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !history.isEmpty {
|
|
|
|
|
emoney.setRiwayatList(history)
|
|
|
|
|
emoney.setTampilRiwayat(true)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updateScreen()
|
|
|
|
|
apduRunner.sessionEx?.alertMessage = "readFinish".localizeString(string: self.langCode!)
|
|
|
|
|
apduRunner.invalidateSession()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private func parseEf84Record(_ data: Data) -> RiwayatCard? {
|
|
|
|
|
let bytes = data.bytes
|
|
|
|
|
guard bytes.count >= 60 else {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let item = RiwayatCard()
|
|
|
|
|
let typeRaw = (Int(bytes[0]) << 8) | Int(bytes[1])
|
|
|
|
|
let amount = uint24BE(bytes, offset: 6)
|
|
|
|
|
let transactionTime = uint32BE(bytes, offset: 38)
|
|
|
|
|
let terminalId = asciiString(bytes, start: 30, length: 8)
|
|
|
|
|
let isPayment = typeRaw == 0x0400
|
|
|
|
|
|
|
|
|
|
item.setAmount(amount)
|
|
|
|
|
item.setLocationId(terminalId)
|
|
|
|
|
item.setLocationName(isPayment ? "payment".localizeString(string: self.langCode!) : "topup".localizeString(string: self.langCode!))
|
|
|
|
|
item.setTransactionTime(formatFlazzTimestamp(seconds: transactionTime))
|
|
|
|
|
|
|
|
|
|
if isPayment {
|
|
|
|
|
item.setProsesTipe(1)
|
|
|
|
|
item.setTitle("payment".localizeString(string: self.langCode!))
|
|
|
|
|
} else {
|
|
|
|
|
item.setProsesTipe(0)
|
|
|
|
|
item.setTitle("topup".localizeString(string: self.langCode!))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return transactionTime > 0 ? item : nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private func parseExtendedRecord(_ data: Data) -> RiwayatCard? {
|
|
|
|
|
let bytes = data.bytes
|
|
|
|
|
guard bytes.count >= 32 else {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let item = RiwayatCard()
|
|
|
|
|
let type = bytes[0]
|
|
|
|
|
let rawTimestamp = Array(bytes[4..<8]).hexString().uppercased()
|
|
|
|
|
let timestampSeconds = uint32BE(bytes, offset: 4)
|
|
|
|
|
let rawAmount = uint24BE(bytes, offset: 1)
|
|
|
|
|
let terminalId = asciiString(bytes, start: 14, length: 8)
|
|
|
|
|
let isPayment = type == 0x04
|
|
|
|
|
let amount = isPayment ? 0x1000000 - rawAmount : rawAmount
|
|
|
|
|
|
|
|
|
|
item.setAmount(amount)
|
|
|
|
|
item.setLocationId(terminalId)
|
|
|
|
|
item.setLocationName(isPayment ? "payment".localizeString(string: self.langCode!) : "topup".localizeString(string: self.langCode!))
|
|
|
|
|
item.setDesk("RAW TS: \(rawTimestamp)")
|
|
|
|
|
item.setTransactionTime(formatFlazzTimestamp(seconds: timestampSeconds))
|
|
|
|
|
|
|
|
|
|
if isPayment {
|
|
|
|
|
item.setProsesTipe(1)
|
|
|
|
|
item.setTitle("payment".localizeString(string: self.langCode!))
|
|
|
|
|
} else {
|
|
|
|
|
item.setProsesTipe(0)
|
|
|
|
|
item.setTitle("topup".localizeString(string: self.langCode!))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return timestampSeconds > 0 ? item : nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private func extractPan(from raw: String) -> String? {
|
|
|
|
|
guard let start = raw.firstIndex(of: ";") else {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let tail = raw[raw.index(after: start)...]
|
|
|
|
|
guard let end = tail.firstIndex(of: "=") else {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return String(tail[..<end])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private func uint24BE(_ bytes: [UInt8], offset: Int) -> Int {
|
|
|
|
|
guard bytes.count >= offset + 3 else {
|
|
|
|
|
return 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (Int(bytes[offset]) << 16)
|
|
|
|
|
| (Int(bytes[offset + 1]) << 8)
|
|
|
|
|
| Int(bytes[offset + 2])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private func uint32BE(_ bytes: [UInt8], offset: Int) -> Int {
|
|
|
|
|
guard bytes.count >= offset + 4 else {
|
|
|
|
|
return 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (Int(bytes[offset]) << 24)
|
|
|
|
|
| (Int(bytes[offset + 1]) << 16)
|
|
|
|
|
| (Int(bytes[offset + 2]) << 8)
|
|
|
|
|
| Int(bytes[offset + 3])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private func formatFlazzTimestamp(seconds: Int) -> Date {
|
|
|
|
|
var dateComponents = DateComponents()
|
|
|
|
|
dateComponents.year = 1980
|
|
|
|
|
dateComponents.month = 1
|
|
|
|
|
@ -393,16 +297,22 @@ public class BcaFlazzApi : UnifiedNfcApi {
|
|
|
|
|
dateComponents.hour = 0
|
|
|
|
|
dateComponents.minute = 0
|
|
|
|
|
dateComponents.second = seconds
|
|
|
|
|
// Create date from components
|
|
|
|
|
let userCalendar = Calendar.current // user calendar
|
|
|
|
|
let someDateTime = userCalendar.date(from: dateComponents)
|
|
|
|
|
return someDateTime!
|
|
|
|
|
return Calendar.current.date(from: dateComponents)!
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private func updateScreen(){
|
|
|
|
|
if (self.apduRunner.callback != nil){
|
|
|
|
|
private func asciiString(_ bytes: [UInt8], start: Int, length: Int) -> String {
|
|
|
|
|
guard bytes.count >= start + length else {
|
|
|
|
|
return ""
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let slice = Array(bytes[start..<(start + length)])
|
|
|
|
|
return String(bytes: slice, encoding: .ascii)?
|
|
|
|
|
.trimmingCharacters(in: .whitespacesAndNewlines.union(.controlCharacters)) ?? ""
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private func updateScreen() {
|
|
|
|
|
if self.apduRunner.callback != nil {
|
|
|
|
|
self.apduRunner.callback?.complete(emoney: self.emoney)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|