Improve NFC history readers and prepare production build
This commit is contained in:
@ -16,88 +16,375 @@ public class MandiriEmoneyApi : UnifiedNfcApi {
|
||||
var start = 0
|
||||
var finish = 256
|
||||
var finish2 = 10
|
||||
private var historyRetryCount = 0
|
||||
private let useMandiriSpecHistoryReader = true
|
||||
private let mandiriSpecMaxPage = 4
|
||||
private let mandiriSpecEmptyPageStopThreshold = 2
|
||||
|
||||
public override init() {}
|
||||
|
||||
private func logMandiri(_ message: String) {
|
||||
_ = message
|
||||
// print("[Mandiri] \(message)")
|
||||
// debugLog("[Mandiri] \(message)")
|
||||
}
|
||||
|
||||
private func logMandiriApdu(_ label: String, command: NFCISO7816APDU) {
|
||||
logMandiri("APDU -> \(label) | \(command.toHexString())")
|
||||
}
|
||||
|
||||
private func logMandiriResponse(_ label: String, response: ApduResponse) {
|
||||
logMandiri(
|
||||
String(
|
||||
format: "APDU <- %@ | sw1=%02X sw2=%02X data=%@",
|
||||
label,
|
||||
response.sw1 ?? 0,
|
||||
response.sw2 ?? 0,
|
||||
response.getData().hexEncodedString()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
public func getCardNumber(){
|
||||
logMandiriApdu("MANDIRI_APDU01", command: EmoneyApduCommands.MANDIRI_APDU01)
|
||||
apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.MANDIRI_APDU01, completionHandler: {response in
|
||||
self.logMandiriResponse("MANDIRI_APDU01", response: response)
|
||||
if (response.sw1 == 0x90 && response.sw2 == 0x00){
|
||||
let raw = response.getData().hexEncodedString()
|
||||
self.emoney.setCardLabel("Mandiri e-Money")
|
||||
self.emoney.setCardNumber(response.getData().hexEncodedString().subString(from: 0, to: 16))
|
||||
self.cardType = response.getData().hexEncodedString().subString(from: 36, to: 38).hex2decimal()
|
||||
debugLog(self.cardType!)
|
||||
self.emoney.setCardNumber(raw.subString(from: 0, to: 16))
|
||||
self.cardType = raw.subString(from: 36, to: 38).hex2decimal()
|
||||
self.logMandiri("Card info raw=\(raw)")
|
||||
self.logMandiri("Card parsed | number=\(self.emoney.getCardNumber()) | cardType=\(self.cardType ?? -1)")
|
||||
self.getBalance()
|
||||
} else {
|
||||
self.logMandiri("Card info failed | status=\(response.sw1)-\(response.sw2)")
|
||||
self.apduRunner.invalidateSession(msg: "readFailed".localizeString(string: self.langCode!))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private func getBalance(){
|
||||
logMandiriApdu("MANDIRI_APDU02", command: EmoneyApduCommands.MANDIRI_APDU02)
|
||||
apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.MANDIRI_APDU02, completionHandler: {response in
|
||||
self.logMandiriResponse("MANDIRI_APDU02", response: response)
|
||||
if (response.sw1 == 0x90 && response.sw2 == 0x00){
|
||||
debugLog(response.getData().hexEncodedString())
|
||||
let balance = response.getData().hexEncodedString().subString(from: 0, to: 8)
|
||||
let raw = response.getData().hexEncodedString()
|
||||
let balance = raw.subString(from: 0, to: 8)
|
||||
self.emoney.setBalance(self.getRealBalance(reverseHexa: balance))
|
||||
self.logMandiri("Balance raw=\(raw)")
|
||||
self.logMandiri("Balance parsed=\(self.emoney.getBalance())")
|
||||
self.resetHistoryState()
|
||||
// self.updateScreen()
|
||||
if (self.cardType! == 131){
|
||||
self.getLogStep01(index: self.start)
|
||||
} else {
|
||||
self.getLogStep02(index: self.start)
|
||||
}
|
||||
self.historyRetryCount = 0
|
||||
self.startHistoryRead()
|
||||
} else {
|
||||
self.logMandiri("Balance read failed | status=\(response.sw1)-\(response.sw2)")
|
||||
self.apduRunner.invalidateSession(msg: "readFailed".localizeString(string: self.langCode!))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private func startHistoryRead() {
|
||||
resetHistoryState()
|
||||
logMandiri("Start history read | retry=\(historyRetryCount) | cardType=\(cardType ?? -1) | useSpec=\(useMandiriSpecHistoryReader)")
|
||||
if useMandiriSpecHistoryReader {
|
||||
startMandiriSpecHistoryRead()
|
||||
return
|
||||
}
|
||||
if (self.cardType == 131){
|
||||
self.getLogStep01(index: self.start)
|
||||
} else {
|
||||
self.getLogStep02(index: self.start)
|
||||
}
|
||||
}
|
||||
|
||||
private func retryHistoryRead(reason: String, retryable: Bool = true) {
|
||||
guard retryable else {
|
||||
logMandiri("History read not retryable | reason=\(reason)")
|
||||
finalizeHistoryResult()
|
||||
return
|
||||
}
|
||||
if historyRetryCount < 1 {
|
||||
historyRetryCount += 1
|
||||
logMandiri("Retrying history read | reason=\(reason) | retry=\(historyRetryCount)")
|
||||
startHistoryRead()
|
||||
} else {
|
||||
logMandiri("Retry exhausted | reason=\(reason)")
|
||||
finalizeHistoryResult()
|
||||
}
|
||||
}
|
||||
|
||||
private func finalizeHistoryResult() {
|
||||
self.start = 0
|
||||
self.finalizeHistory()
|
||||
self.emoney.setRiwayatList(self.riwayatList)
|
||||
self.emoney.setTampilRiwayat(!self.riwayatList.isEmpty)
|
||||
self.logMandiri("Finalize history | count=\(self.riwayatList.count)")
|
||||
self.updateScreen()
|
||||
self.apduRunner.sessionEx?.alertMessage = "readFinish".localizeString(string: self.langCode!)
|
||||
self.apduRunner.invalidateSession()
|
||||
}
|
||||
|
||||
// MARK: - New Mandiri Spec Reader
|
||||
// Toggle `useMandiriSpecHistoryReader` to rollback to the legacy implementation.
|
||||
|
||||
private func startMandiriSpecHistoryRead() {
|
||||
logMandiri("Start spec history read | pages=1...\(mandiriSpecMaxPage)")
|
||||
readMandiriSpecPage(page: 1, collectedChunks: [], emptyPageStreak: 0)
|
||||
}
|
||||
|
||||
private func readMandiriSpecPage(page: Int, collectedChunks: [String], emptyPageStreak: Int) {
|
||||
guard page <= mandiriSpecMaxPage else {
|
||||
probeMandiriSpecStop(collectedChunks: collectedChunks)
|
||||
return
|
||||
}
|
||||
|
||||
let command = NFCISO7816APDU(
|
||||
instructionClass: 0x00,
|
||||
instructionCode: 0xD1,
|
||||
p1Parameter: UInt8(page & 0xFF),
|
||||
p2Parameter: 0x00,
|
||||
data: Data(),
|
||||
expectedResponseLength: CommonConstants.LE_GET_ALL_RESPONSE_DATA
|
||||
)
|
||||
logMandiriApdu("MANDIRI_SPEC_PAGE[\(page)]", command: command)
|
||||
apduRunner.exchangeApdu(apduCommand: command, completionHandler: { response in
|
||||
self.logMandiriResponse("MANDIRI_SPEC_PAGE[\(page)]", response: response)
|
||||
if response.sw1 == 0x90 && response.sw2 == 0x00 {
|
||||
let chunk = response.getData().hexEncodedString()
|
||||
let isEmptyPage = self.isMandiriSpecPageEmpty(chunk)
|
||||
self.logMandiri("Spec page chunk | page=\(page) | rawLength=\(chunk.count) | empty=\(isEmptyPage)")
|
||||
var nextChunks = collectedChunks
|
||||
nextChunks.append(chunk)
|
||||
let nextEmptyPageStreak = isEmptyPage ? (emptyPageStreak + 1) : 0
|
||||
if nextEmptyPageStreak >= self.mandiriSpecEmptyPageStopThreshold {
|
||||
self.logMandiri("Spec page empty streak reached threshold | streak=\(nextEmptyPageStreak) | stopAfterPage=\(page)")
|
||||
self.probeMandiriSpecStop(collectedChunks: nextChunks)
|
||||
return
|
||||
}
|
||||
self.readMandiriSpecPage(page: page + 1, collectedChunks: nextChunks, emptyPageStreak: nextEmptyPageStreak)
|
||||
} else {
|
||||
self.logMandiri("Spec page read failed | page=\(page) | status=\(response.sw1)-\(response.sw2)")
|
||||
self.retryHistoryRead(reason: "spec page status \(response.sw1)-\(response.sw2) at page \(page)")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private func probeMandiriSpecStop(collectedChunks: [String]) {
|
||||
let probePage = mandiriSpecMaxPage + 1
|
||||
let command = NFCISO7816APDU(
|
||||
instructionClass: 0x00,
|
||||
instructionCode: 0xD1,
|
||||
p1Parameter: UInt8(probePage & 0xFF),
|
||||
p2Parameter: 0x00,
|
||||
data: Data(),
|
||||
expectedResponseLength: CommonConstants.LE_GET_ALL_RESPONSE_DATA
|
||||
)
|
||||
logMandiriApdu("MANDIRI_SPEC_PROBE[\(probePage)]", command: command)
|
||||
apduRunner.exchangeApdu(apduCommand: command, completionHandler: { response in
|
||||
self.logMandiriResponse("MANDIRI_SPEC_PROBE[\(probePage)]", response: response)
|
||||
if response.sw1 == 0x69 && response.sw2 == 0x86 {
|
||||
self.logMandiri("Spec probe stop confirmed | page=\(probePage)")
|
||||
} else {
|
||||
self.logMandiri("Spec probe unexpected status | page=\(probePage) | status=\(response.sw1)-\(response.sw2)")
|
||||
}
|
||||
|
||||
self.mapv1 = collectedChunks.joined()
|
||||
if self.parseMandiriSpecLog() {
|
||||
self.finalizeHistoryResult()
|
||||
} else {
|
||||
self.retryHistoryRead(reason: "invalid spec history payload", retryable: false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private func parseMandiriSpecLog() -> Bool {
|
||||
let logs = self.mapv1.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let recordHexLength = 48
|
||||
logMandiri("Parse spec log | rawLength=\(logs.count)")
|
||||
|
||||
guard logs.count >= recordHexLength, logs.count % recordHexLength == 0 else {
|
||||
logMandiri("Parse spec log invalid length | actual=\(logs.count) | recordHexLength=\(recordHexLength)")
|
||||
return false
|
||||
}
|
||||
|
||||
let total = logs.count / recordHexLength
|
||||
logMandiri("Parse spec log totalRecords=\(total)")
|
||||
for i in 0..<total {
|
||||
let start = i * recordHexLength
|
||||
let end = start + recordHexLength
|
||||
let recordHex = logs.subString(from: start, to: end)
|
||||
guard let riwayat = parseMandiriSpecRecord(recordHex, index: i) else {
|
||||
logMandiri("Skip spec log record | index=\(i) | raw=\(recordHex)")
|
||||
continue
|
||||
}
|
||||
riwayatList.append(riwayat)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private func parseMandiriSpecRecord(_ recordHex: String, index: Int) -> RiwayatCard? {
|
||||
guard recordHex.count == 48 else {
|
||||
logMandiri("Spec record invalid length | index=\(index) | length=\(recordHex.count)")
|
||||
return nil
|
||||
}
|
||||
|
||||
if isMandiriSpecRecordEmpty(recordHex) {
|
||||
logMandiri("Spec record empty | index=\(index)")
|
||||
return nil
|
||||
}
|
||||
|
||||
let bytes = recordHex.hex2byte().bytes
|
||||
guard bytes.count == 24 else {
|
||||
logMandiri("Spec record invalid byte count | index=\(index) | bytes=\(bytes.count)")
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let time = decodeMandiriSpecTimestamp(bytes, index: index) else {
|
||||
logMandiri("Spec record invalid timestamp | index=\(index) | raw=\(recordHex)")
|
||||
return nil
|
||||
}
|
||||
|
||||
let transactionType = bytes[15]
|
||||
let amount = decodeMandiriLittleEndianUInt32(Array(bytes[16...19]))
|
||||
let balanceAfter = decodeMandiriLittleEndianUInt32(Array(bytes[20...23]))
|
||||
let terminalData = Array(bytes[7...13]).map { String(format: "%02X", $0) }.joined()
|
||||
|
||||
let riwayat = RiwayatCard()
|
||||
riwayat.setTransactionTime(time)
|
||||
riwayat.setAmount(amount)
|
||||
riwayat.setDesk("BAL_AFTER=\(balanceAfter)")
|
||||
riwayat.setLocationId(terminalData)
|
||||
|
||||
if transactionType == 0x00 {
|
||||
riwayat.setProsesTipe(0)
|
||||
riwayat.setTitle("topup".localizeString(string: self.langCode!))
|
||||
} else if transactionType == 0x20 {
|
||||
riwayat.setProsesTipe(1)
|
||||
riwayat.setTitle("payment".localizeString(string: self.langCode!))
|
||||
} else {
|
||||
riwayat.setProsesTipe(-1)
|
||||
riwayat.setTitle("unknown".localizeString(string: self.langCode!))
|
||||
}
|
||||
|
||||
logMandiri(
|
||||
String(
|
||||
format: "Parsed spec log | index=%d | type=%02X | title=%@ | amount=%d | balanceAfter=%d | terminal=%@ | raw=%@",
|
||||
index,
|
||||
transactionType,
|
||||
riwayat.getTitle() ?? "-",
|
||||
amount,
|
||||
balanceAfter,
|
||||
terminalData,
|
||||
recordHex
|
||||
)
|
||||
)
|
||||
return riwayat
|
||||
}
|
||||
|
||||
private func isMandiriSpecPageEmpty(_ pageHex: String) -> Bool {
|
||||
guard !pageHex.isEmpty else {
|
||||
return true
|
||||
}
|
||||
return !pageHex.contains { $0 != "0" }
|
||||
}
|
||||
|
||||
private func isMandiriSpecRecordEmpty(_ recordHex: String) -> Bool {
|
||||
return !recordHex.contains { $0 != "0" }
|
||||
}
|
||||
|
||||
private func decodeMandiriSpecTimestamp(_ bytes: [UInt8], index: Int) -> Date? {
|
||||
guard bytes.count >= 7 else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let values = bytes.prefix(7).map { decodeBcd($0) }
|
||||
guard values.allSatisfy({ $0 >= 0 }) else {
|
||||
logMandiri("BCD decode failed | index=\(index) | bytes=\(bytes.prefix(7).map { String(format: "%02X", $0) }.joined())")
|
||||
return nil
|
||||
}
|
||||
|
||||
var components = DateComponents()
|
||||
components.calendar = Calendar(identifier: .gregorian)
|
||||
components.timeZone = TimeZone(identifier: "Asia/Jakarta") ?? .current
|
||||
components.year = 2000 + values[2]
|
||||
components.month = values[1]
|
||||
components.day = values[0]
|
||||
components.hour = values[3]
|
||||
components.minute = values[4]
|
||||
components.second = values[5]
|
||||
components.nanosecond = values[6] * 10_000_000
|
||||
return components.date
|
||||
}
|
||||
|
||||
private func decodeBcd(_ value: UInt8) -> Int {
|
||||
let high = Int((value & 0xF0) >> 4)
|
||||
let low = Int(value & 0x0F)
|
||||
guard high < 10, low < 10 else {
|
||||
return -1
|
||||
}
|
||||
return (high * 10) + low
|
||||
}
|
||||
|
||||
private func decodeMandiriLittleEndianUInt32(_ bytes: [UInt8]) -> Int {
|
||||
guard bytes.count >= 4 else {
|
||||
return 0
|
||||
}
|
||||
return Int(bytes[0])
|
||||
| (Int(bytes[1]) << 8)
|
||||
| (Int(bytes[2]) << 16)
|
||||
| (Int(bytes[3]) << 24)
|
||||
}
|
||||
|
||||
private func getLogStep01(index : Int){
|
||||
//00 d1 00 00 00
|
||||
let hex = String(index, radix: 16)
|
||||
let MANDIRI_LOG = NFCISO7816APDU(instructionClass : 0x00, instructionCode : 0xD1, p1Parameter : hex.hex2byte().bytes.first!, p2Parameter : 0x00, data : Data(), expectedResponseLength : CommonConstants.LE_GET_ALL_RESPONSE_DATA)
|
||||
let p1Parameter = hex.hex2byte().bytes.first ?? 0x00
|
||||
let MANDIRI_LOG = NFCISO7816APDU(instructionClass : 0x00, instructionCode : 0xD1, p1Parameter : p1Parameter, p2Parameter : 0x00, data : Data(), expectedResponseLength : CommonConstants.LE_GET_ALL_RESPONSE_DATA)
|
||||
logMandiriApdu("MANDIRI_LOG_NEW[\(index)]", command: MANDIRI_LOG)
|
||||
apduRunner.exchangeApdu(apduCommand: MANDIRI_LOG, completionHandler: {response in
|
||||
self.logMandiriResponse("MANDIRI_LOG_NEW[\(index)]", response: response)
|
||||
if (response.sw1 == 0x90 && response.sw2 == 0x00){
|
||||
self.mapv1.append(response.getData().hexEncodedString())
|
||||
let chunk = response.getData().hexEncodedString()
|
||||
self.mapv1.append(chunk)
|
||||
self.logMandiri("New log chunk | index=\(index) | rawLength=\(chunk.count) | accumulated=\(self.mapv1.count)")
|
||||
self.start+=1
|
||||
if (self.start < (self.finish)){
|
||||
self.getLogStep01(index: self.start)
|
||||
} else {
|
||||
self.parseNewLog()
|
||||
self.start = 0
|
||||
self.finalizeHistory()
|
||||
self.emoney.setRiwayatList(self.riwayatList)
|
||||
self.emoney.setTampilRiwayat(true)
|
||||
self.updateScreen()
|
||||
self.apduRunner.sessionEx?.alertMessage = "readFinish".localizeString(string: self.langCode!)
|
||||
self.apduRunner.invalidateSession()
|
||||
if self.parseNewLog() {
|
||||
self.finalizeHistoryResult()
|
||||
} else {
|
||||
self.retryHistoryRead(reason: "invalid new history payload", retryable: false)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.parseNewLog()
|
||||
self.start = 0
|
||||
self.finalizeHistory()
|
||||
self.emoney.setRiwayatList(self.riwayatList)
|
||||
self.emoney.setTampilRiwayat(true)
|
||||
self.updateScreen()
|
||||
self.apduRunner.sessionEx?.alertMessage = "readFinish".localizeString(string: self.langCode!)
|
||||
self.apduRunner.invalidateSession()
|
||||
self.logMandiri("New log read failed | index=\(index) | status=\(response.sw1)-\(response.sw2)")
|
||||
self.retryHistoryRead(reason: "new history status \(response.sw1)-\(response.sw2) at index \(index)")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func parseNewLog(){
|
||||
func parseNewLog() -> Bool {
|
||||
let logs = self.mapv1.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
logMandiri("Parse new log | rawLength=\(logs.count)")
|
||||
if (logs.count % 48 != 0){
|
||||
return
|
||||
logMandiri("Parse new log invalid length | length=\(logs.count)")
|
||||
return false
|
||||
}
|
||||
let total = logs.count/48
|
||||
logMandiri("Parse new log totalRecords=\(total)")
|
||||
for i in 0..<total {
|
||||
let start = i*48
|
||||
let end = start + 48
|
||||
let data = logs.subString(from: start, to: end)
|
||||
let riwayat = RiwayatCard()
|
||||
let time = self.getTransactionTime(formatDate: data.subString(from: 0, to: 6), formatTime: data.subString(from: 6, to: 12))
|
||||
riwayat.setTransactionTime(time!)
|
||||
guard let time else {
|
||||
self.logMandiri("Skip new log record | index=\(i) | reason=invalid_timestamp | raw=\(data)")
|
||||
continue
|
||||
}
|
||||
riwayat.setTransactionTime(time)
|
||||
let processType = Int(data.subString(from: 28, to: 32))
|
||||
if (processType == 100){
|
||||
riwayat.setProsesTipe(0)
|
||||
@ -109,69 +396,78 @@ public class MandiriEmoneyApi : UnifiedNfcApi {
|
||||
riwayat.setLocationId(data.subString(from: 12, to: 20))
|
||||
let amount = data.subString(from: 32, to: 40)
|
||||
riwayat.setAmount(getRealBalance(reverseHexa: amount))
|
||||
self.logMandiri("Parsed new log | index=\(i) | title=\(riwayat.getTitle() ?? "-") | processType=\(processType ?? -1) | locationId=\(riwayat.getLocationId() ?? "-") | amount=\(riwayat.getAmount()) | raw=\(data)")
|
||||
riwayatList.append(riwayat)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private func getLogStep02(index : Int){
|
||||
//00 b2 00 00 1e
|
||||
let hex = String(index, radix: 16)
|
||||
let MANDIRI_LOG = NFCISO7816APDU(instructionClass : 0x00, instructionCode : 0xB2, p1Parameter : hex.hex2byte().bytes.first!, p2Parameter : 0x00, data : Data(), expectedResponseLength : 30)
|
||||
let p1Parameter = hex.hex2byte().bytes.first ?? 0x00
|
||||
let MANDIRI_LOG = NFCISO7816APDU(instructionClass : 0x00, instructionCode : 0xB2, p1Parameter : p1Parameter, p2Parameter : 0x00, data : Data(), expectedResponseLength : 30)
|
||||
logMandiriApdu("MANDIRI_LOG_OLD[\(index)]", command: MANDIRI_LOG)
|
||||
apduRunner.exchangeApdu(apduCommand: MANDIRI_LOG, completionHandler: {response in
|
||||
self.logMandiriResponse("MANDIRI_LOG_OLD[\(index)]", response: response)
|
||||
if (response.sw1 == 0x90 && response.sw2 == 0x00){
|
||||
self.mapv1.append(response.getData().hexEncodedString())
|
||||
let chunk = response.getData().hexEncodedString()
|
||||
self.mapv1.append(chunk)
|
||||
self.logMandiri("Old log chunk | index=\(index) | rawLength=\(chunk.count) | accumulated=\(self.mapv1.count)")
|
||||
self.start+=1
|
||||
if (self.start < (self.finish2)){
|
||||
self.getLogStep02(index: self.start)
|
||||
} else {
|
||||
self.parseOldLog()
|
||||
self.start = 0
|
||||
self.finalizeHistory()
|
||||
self.emoney.setRiwayatList(self.riwayatList)
|
||||
self.emoney.setTampilRiwayat(true)
|
||||
self.updateScreen()
|
||||
self.apduRunner.sessionEx?.alertMessage = "readFinish".localizeString(string: self.langCode!)
|
||||
self.apduRunner.invalidateSession()
|
||||
if self.parseOldLog() {
|
||||
self.finalizeHistoryResult()
|
||||
} else {
|
||||
self.retryHistoryRead(reason: "invalid old history payload", retryable: false)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.parseOldLog()
|
||||
self.start = 0
|
||||
self.finalizeHistory()
|
||||
self.emoney.setRiwayatList(self.riwayatList)
|
||||
self.emoney.setTampilRiwayat(true)
|
||||
self.updateScreen()
|
||||
self.apduRunner.sessionEx?.alertMessage = "readFinish".localizeString(string: self.langCode!)
|
||||
self.apduRunner.invalidateSession()
|
||||
self.logMandiri("Old log read failed | index=\(index) | status=\(response.sw1)-\(response.sw2)")
|
||||
self.retryHistoryRead(reason: "old history status \(response.sw1)-\(response.sw2) at index \(index)")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func parseOldLog(){
|
||||
func parseOldLog() -> Bool {
|
||||
let logs = self.mapv1.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
logMandiri("Parse old log | rawLength=\(logs.count)")
|
||||
if (logs.count % 60 != 0){
|
||||
return
|
||||
logMandiri("Parse old log invalid length | length=\(logs.count)")
|
||||
return false
|
||||
}
|
||||
let total = logs.count/60
|
||||
logMandiri("Parse old log totalRecords=\(total)")
|
||||
for i in 0..<total {
|
||||
let start = i*60
|
||||
let end = start + 60
|
||||
let data = logs.subString(from: start, to: end)
|
||||
let riwayat = riwayatCard(data.hex2byte().bytes)
|
||||
riwayatList.append(riwayat!)
|
||||
if let riwayat = riwayatCard(data.hex2byte().bytes) {
|
||||
self.logMandiri("Parsed old log | index=\(i) | title=\(riwayat.getTitle() ?? "-") | amount=\(riwayat.getAmount()) | raw=\(data)")
|
||||
riwayatList.append(riwayat)
|
||||
} else {
|
||||
self.logMandiri("Skip old log record | index=\(i) | raw=\(data)")
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func getTransactionTime(formatDate : String, formatTime : String) -> Date?{
|
||||
let dateFormatter2 = DateFormatter()
|
||||
dateFormatter2.locale = Locale(identifier: "en_US_POSIX")
|
||||
dateFormatter2.dateFormat = "HHmmss"
|
||||
let date12 = dateFormatter2.date(from: formatTime)!
|
||||
guard let date12 = dateFormatter2.date(from: formatTime) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
dateFormatter2.dateFormat = "hh:mm a"
|
||||
let date22 = dateFormatter2.string(from: date12)
|
||||
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.locale = Locale(identifier: "en_US_POSIX") // set locale to reliable US_POSIX
|
||||
dateFormatter.dateFormat = "ddMMyy hh:mm a"
|
||||
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
|
||||
dateFormatter.dateFormat = "ddMMyy hh:mm a"
|
||||
return dateFormatter.date(from:(formatDate + " " + date22))
|
||||
}
|
||||
|
||||
@ -207,7 +503,18 @@ public class MandiriEmoneyApi : UnifiedNfcApi {
|
||||
}
|
||||
|
||||
private func finalizeHistory() {
|
||||
riwayatList = riwayatList.sorted(by: { $0.getTransationTime()?.compare($1.getTransationTime()!) == .orderedDescending })
|
||||
riwayatList = riwayatList.sorted { lhs, rhs in
|
||||
switch (lhs.getTransationTime(), rhs.getTransationTime()) {
|
||||
case let (left?, right?):
|
||||
return left.compare(right) == .orderedDescending
|
||||
case (_?, nil):
|
||||
return true
|
||||
case (nil, _?):
|
||||
return false
|
||||
case (nil, nil):
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func riwayatCard(_ bArr: [UInt8]) -> RiwayatCard? {
|
||||
@ -229,9 +536,9 @@ public class MandiriEmoneyApi : UnifiedNfcApi {
|
||||
|
||||
do {
|
||||
wrap.copyBytes(to: &bArr3, count: 16)
|
||||
debugLog("bArr3: \(bArr3.map { String(format: "%02X", $0) }.joined())")
|
||||
logMandiri("Old log detail bytes=\(bArr3.map { String(format: "%02X", $0) }.joined())")
|
||||
} catch {
|
||||
debugLog("Error: \(error.localizedDescription)")
|
||||
logMandiri("Old log detail error=\(error.localizedDescription)")
|
||||
}
|
||||
|
||||
var type = 0
|
||||
@ -252,8 +559,11 @@ public class MandiriEmoneyApi : UnifiedNfcApi {
|
||||
str2 += String(format: "%02X", bArr2[y])
|
||||
}
|
||||
|
||||
let transactionTime = self.getTransactionTime(formatDate: str.subString(from: 0, to: 6), formatTime: str.subString(from: 6, to: 12))
|
||||
riwayatCard.setTransactionTime(transactionTime!)
|
||||
guard let transactionTime = self.getTransactionTime(formatDate: str.subString(from: 0, to: 6), formatTime: str.subString(from: 6, to: 12)) else {
|
||||
logMandiri("Skip old log record | reason=invalid_timestamp | raw=\(str)")
|
||||
return nil
|
||||
}
|
||||
riwayatCard.setTransactionTime(transactionTime)
|
||||
riwayatCard.setAmount(Int(amount))
|
||||
|
||||
// if str.caseInsensitiveCompare("payment") == .orderedSame {
|
||||
@ -266,6 +576,7 @@ public class MandiriEmoneyApi : UnifiedNfcApi {
|
||||
riwayatCard.setTitle(str)
|
||||
|
||||
if type == -1 {
|
||||
logMandiri("Skip old log record | reason=unknown_type | len=\(len) | raw=\(str)")
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user