Initial commit
This commit is contained in:
408
Emoney Info/Classes/api/BcaFlazzApi.swift
Executable file
408
Emoney Info/Classes/api/BcaFlazzApi.swift
Executable file
@ -0,0 +1,408 @@
|
||||
//
|
||||
// BcaFlazzApi.swift
|
||||
// Emoney Info
|
||||
//
|
||||
// Created by Wira Irawan on 27/07/24.
|
||||
//
|
||||
|
||||
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 = ""
|
||||
|
||||
public override init() {}
|
||||
|
||||
public func checkFlazzCard(){
|
||||
apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.BCA_APDU01, completionHandler: {response in
|
||||
if (response.sw1 == 0x90 && response.sw2 == 0x00){
|
||||
self.emoney.setCardLabel("BCA Flazz")
|
||||
self.getCardNumber()
|
||||
} 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!))!))
|
||||
self.getBalance()
|
||||
} else {
|
||||
self.apduRunner.invalidateSession()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
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){
|
||||
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!))
|
||||
} else {
|
||||
riwayat.setProsesTipe(0)
|
||||
riwayat.setTitle("topup".localizeString(string: self.langCode!))
|
||||
}
|
||||
|
||||
if (transactionTime > 0){
|
||||
riwayatList.append(riwayat)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func parseLogV201(){
|
||||
debugLog("log parseLogV201")
|
||||
let logs = self.mapv1.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if (logs.count % 120 != 0){
|
||||
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!))
|
||||
} else {
|
||||
riwayat.setProsesTipe(0)
|
||||
riwayat.setTitle("topup".localizeString(string: self.langCode!))
|
||||
}
|
||||
|
||||
if (transactionTime > 0){
|
||||
riwayatList.append(riwayat)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func parseLogV202(){
|
||||
debugLog("log parseLogV202")
|
||||
let logs = self.mapv2.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if (logs.count % 64 != 0){
|
||||
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!))
|
||||
} else {
|
||||
riwayat.setProsesTipe(0)
|
||||
riwayat.setAmount(amount)
|
||||
riwayat.setTitle("topup".localizeString(string: self.langCode!))
|
||||
}
|
||||
if (transactionTime > 0){
|
||||
riwayatList.append(riwayat)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func formatDate(seconds : Int) -> Date{
|
||||
// Specify date components
|
||||
var dateComponents = DateComponents()
|
||||
dateComponents.year = 1980
|
||||
dateComponents.month = 1
|
||||
dateComponents.day = 1
|
||||
dateComponents.timeZone = TimeZone(identifier: "Asia/Jakarta")!
|
||||
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!
|
||||
}
|
||||
|
||||
private func updateScreen(){
|
||||
if (self.apduRunner.callback != nil){
|
||||
self.apduRunner.callback?.complete(emoney: self.emoney)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
247
Emoney Info/Classes/api/BrizziApi.swift
Executable file
247
Emoney Info/Classes/api/BrizziApi.swift
Executable file
@ -0,0 +1,247 @@
|
||||
//
|
||||
// BrizziApi.swift
|
||||
// Emoney Info
|
||||
//
|
||||
// Created by Wira Irawan on 27/07/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreNFC
|
||||
|
||||
public class BrizziApi : UnifiedNfcApi {
|
||||
var emoney : Emoney = Emoney()
|
||||
var riwayatList: [RiwayatCard] = []
|
||||
var uid : String?
|
||||
var rawLog : String = ""
|
||||
|
||||
public override init() {}
|
||||
|
||||
public func getUid(){
|
||||
apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.BRI_UID01, completionHandler: {response in
|
||||
if (response.sw1 == 0x91 && response.sw2 == 0xAF){
|
||||
self.getUid02()
|
||||
} else {
|
||||
self.apduRunner.invalidateSession(msg: "readFailed".localizeString(string: self.langCode!))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private func getUid02(){
|
||||
apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.BRI_UID02, completionHandler: {response in
|
||||
if (response.sw1 == 0x91 && response.sw2 == 0xAF){
|
||||
self.getUid02()
|
||||
} else {
|
||||
self.uid = response.getData().hexEncodedString().subString(from: 0, to: 14)
|
||||
self.getCardNumber()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private func getCardNumber(){
|
||||
apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.BRI_APDU01, completionHandler: {response in
|
||||
if (response.sw1 == 0x91 && response.sw2 == 0x00){
|
||||
self.emoney.setCardLabel("Brizzi")
|
||||
self.emoney.setCardNumber(response.getData().hexEncodedString().subString(from: 6, to: 22))
|
||||
self.process01()
|
||||
} else {
|
||||
self.apduRunner.invalidateSession(msg: "readFailed".localizeString(string: self.langCode!))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private func process01(){
|
||||
apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.BRI_APDU02, completionHandler: {response in
|
||||
if (response.sw1 == 0x91 && response.sw2 == 0x00){
|
||||
self.process02()
|
||||
} else {
|
||||
self.apduRunner.invalidateSession(msg: "readFailed".localizeString(string: self.langCode!))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private func process02(){
|
||||
apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.BRI_APDU03, completionHandler: {response in
|
||||
if (response.sw1 == 0x91 && response.sw2 == 0x00) || (response.sw1 == 0x91 && response.sw2 == 0xAF){
|
||||
self.process03(data: response.getData())
|
||||
} else {
|
||||
self.apduRunner.invalidateSession(msg: "readFailed".localizeString(string: self.langCode!))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private func process03(data : Data){
|
||||
let brizziSamHelper = BrizziSamHelper()
|
||||
brizziSamHelper.keyCard = data.hexEncodedString()
|
||||
let random = "8DC0DC40FE1DC582CF7099E2AACFBC10".hex2byte()
|
||||
let command = self.emoney.getCardNumber() + self.uid! + "FF"
|
||||
let decrypted = BrizziSamHelper.decryptDeSeDe(random)?.hexEncodedString()
|
||||
|
||||
let decryptedFinal = decrypted?.subString(from: 0, to: 32)
|
||||
let encrypted = BrizziSamHelper.encryptDeSeDe(command, decryptedFinal!, "0000000000000000")?.hexEncodedString()
|
||||
|
||||
brizziSamHelper.encryptedKey = encrypted?.subString(from: 0, to: 32)
|
||||
|
||||
|
||||
let randomHex = "3C37029CA595FE4E7E62FCB2F7909B2C".hex2byte()
|
||||
let randomHexDecrypted = BrizziSamHelper.decryptDeSeDe(randomHex)
|
||||
|
||||
let randomHexFinal = randomHexDecrypted?.hexEncodedString().subString(from: 0, to: 32)
|
||||
let randomHexEncrypted = BrizziSamHelper.encryptDeSeDe(brizziSamHelper.encryptedKey!, randomHexFinal!, brizziSamHelper.authKey)
|
||||
brizziSamHelper.random = (randomHexEncrypted?.hexEncodedString())!.subString(from: 0, to: 32)
|
||||
|
||||
|
||||
let samChallenge = brizziSamHelper.generateSamRandom().subString(from: 0, to: 32)
|
||||
let BRI_APDU04 = NFCISO7816APDU(instructionClass : 0x90, instructionCode : 0xAF, p1Parameter : 0x00, p2Parameter : 0x00, data : Data(_: samChallenge.hex2byte()), expectedResponseLength : CommonConstants.LE_GET_ALL_RESPONSE_DATA)
|
||||
apduRunner.exchangeApdu(apduCommand: BRI_APDU04, completionHandler: {response in
|
||||
if (response.sw1 == 0x91 && response.sw2 == 0x00) || (response.sw1 == 0x91 && response.sw2 == 0xAF){
|
||||
self.process04()
|
||||
} else {
|
||||
self.apduRunner.invalidateSession(msg: "readFailed".localizeString(string: self.langCode!))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private func process04(){
|
||||
apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.BRI_APDU05, completionHandler: {response in
|
||||
if (response.sw1 == 0x91 && response.sw2 == 0x00) || (response.sw1 == 0x91 && response.sw2 == 0xAF){
|
||||
self.emoney.setBalance(self.getRealBalance(reverseHexa: response.getData().hexEncodedString().subString(from: 0, to: 8)))
|
||||
self.getLog()
|
||||
} else {
|
||||
self.apduRunner.invalidateSession(msg: "readFailed".localizeString(string: self.langCode!))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private func getLog(){
|
||||
apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.BRI_LOG01, completionHandler: {response in
|
||||
if (response.sw1 == 0x91 && response.sw2 == 0x00) || (response.sw1 == 0x91 && response.sw2 == 0xAF){
|
||||
self.rawLog.append(response.getData().hexEncodedString())
|
||||
self.getMoreLog()
|
||||
} else {
|
||||
self.updateScreen()
|
||||
self.apduRunner.sessionEx?.alertMessage = "readFinish".localizeString(string: self.langCode!)
|
||||
self.apduRunner.invalidateSession()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private func getMoreLog(){
|
||||
apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.BRI_LOG02, completionHandler: {response in
|
||||
if (response.sw1 == 0x91 && response.sw2 == 0xAF){
|
||||
self.rawLog.append(response.getData().hexEncodedString())
|
||||
self.getMoreLog()
|
||||
} else {
|
||||
self.rawLog.append(response.getData().hexEncodedString())
|
||||
|
||||
if (self.parseLog()){
|
||||
self.riwayatList = self.riwayatList.sorted(by: { $0.getTransationTime()?.compare($1.getTransationTime()!) == .orderedDescending })
|
||||
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 parseLog() -> Bool {
|
||||
let logs = self.rawLog.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if (logs.count % 64 != 0){
|
||||
return false
|
||||
}
|
||||
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()
|
||||
// riwayat.setLocationId(data.subString(from: 16, to: 32))
|
||||
// riwayat.setLocationName(data.subString(from: 0, to: 16))
|
||||
riwayat.setTitle(self.getCode(trxCode: data.subString(from: 44, to: 46)))
|
||||
riwayat.setProsesTipe(self.getTipe(trxCode: data.subString(from: 44, to: 46)))
|
||||
riwayat.setAmount(self.getRealBalance(reverseHexa: data.subString(from: 46, to: 52)))
|
||||
|
||||
let time = self.getTransactionTime(formatDate: data.subString(from: 32, to: 38), formatTime: data.subString(from: 38, to: 44))
|
||||
riwayat.setTransactionTime(time!)
|
||||
|
||||
riwayatList.append(riwayat)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func getTransactionTime(formatDate : String, formatTime : String) -> Date?{
|
||||
let dateFormatter2 = DateFormatter()
|
||||
dateFormatter2.dateFormat = "HHmmss"
|
||||
let date12 = dateFormatter2.date(from: formatTime)!
|
||||
|
||||
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"
|
||||
return dateFormatter.date(from:(formatDate + " " + date22))
|
||||
}
|
||||
|
||||
private func getCode(trxCode: String) -> String {
|
||||
let trxUp = trxCode.uppercased()
|
||||
if trxUp.contains("5F") {
|
||||
return "reactivation".localizeString(string: self.langCode!)
|
||||
} else if trxUp.contains("EB") {
|
||||
return "payment".localizeString(string: self.langCode!)
|
||||
} else if trxUp.contains("EC") {
|
||||
return "topup".localizeString(string: self.langCode!)
|
||||
} else if trxUp.contains("ED") {
|
||||
return "void".localizeString(string: self.langCode!)
|
||||
} else if trxUp.contains("EF") {
|
||||
return "updateBalance".localizeString(string: self.langCode!)
|
||||
} else {
|
||||
return "-"
|
||||
}
|
||||
}
|
||||
|
||||
private func getTipe(trxCode: String) -> Int {
|
||||
let trxUp = trxCode.uppercased()
|
||||
if trxUp.contains("5F") {
|
||||
return 2
|
||||
} else if trxUp.contains("EB") {
|
||||
return 1
|
||||
} else if trxUp.contains("EC") {
|
||||
return 0
|
||||
} else if trxUp.contains("ED") {
|
||||
return 0
|
||||
} else if trxUp.contains("EF") {
|
||||
return 1
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func getRealBalance(reverseHexa: String?) -> Int {
|
||||
guard let reverseHexa = reverseHexa?.trimmingCharacters(in: .whitespacesAndNewlines), !reverseHexa.isEmpty else {
|
||||
return 0
|
||||
}
|
||||
|
||||
if reverseHexa.count % 2 != 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
var sb = ""
|
||||
for l in stride(from: reverseHexa.count / 2, through: 1, by: -1) {
|
||||
let index1 = reverseHexa.index(reverseHexa.startIndex, offsetBy: l * 2 - 2)
|
||||
let index2 = reverseHexa.index(reverseHexa.startIndex, offsetBy: l * 2 - 1)
|
||||
sb.append(reverseHexa[index1])
|
||||
sb.append(reverseHexa[index2])
|
||||
}
|
||||
return sb.hex2decimal()
|
||||
}
|
||||
|
||||
private func updateScreen(){
|
||||
if (self.apduRunner.callback != nil){
|
||||
self.apduRunner.callback?.complete(emoney: self.emoney)
|
||||
}
|
||||
}
|
||||
}
|
||||
37
Emoney Info/Classes/api/JackCardApi.swift
Executable file
37
Emoney Info/Classes/api/JackCardApi.swift
Executable file
@ -0,0 +1,37 @@
|
||||
//
|
||||
// JackCardApi.swift
|
||||
// Emoney Info
|
||||
//
|
||||
// Created by Wira Irawan on 27/07/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public class JackCardApi : UnifiedNfcApi {
|
||||
var emoney : Emoney = Emoney()
|
||||
var riwayatList: [RiwayatCard] = []
|
||||
|
||||
public override init() {}
|
||||
|
||||
public func getBalance(resp : Data){
|
||||
self.emoney.setCardLabel("Jackcard")
|
||||
self.emoney.setCardNumber(resp.hexEncodedString().subString(from: 16, to: 32))
|
||||
apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.DKI_APDU01, completionHandler: {response in
|
||||
if (response.sw1 == 0x90 && response.sw2 == 0x00){
|
||||
self.emoney.setBalance(response.getData().hexEncodedString().hex2decimal())
|
||||
self.emoney.setTampilRiwayat(false)
|
||||
self.updateScreen()
|
||||
self.apduRunner.sessionEx?.alertMessage = "readFinish".localizeString(string: self.langCode!)
|
||||
self.apduRunner.invalidateSession()
|
||||
} else {
|
||||
self.apduRunner.invalidateSession(msg: "readFailed".localizeString(string: self.langCode!))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private func updateScreen(){
|
||||
if (self.apduRunner.callback != nil){
|
||||
self.apduRunner.callback?.complete(emoney: self.emoney)
|
||||
}
|
||||
}
|
||||
}
|
||||
263
Emoney Info/Classes/api/MandiriEmoneyApi.swift
Executable file
263
Emoney Info/Classes/api/MandiriEmoneyApi.swift
Executable file
@ -0,0 +1,263 @@
|
||||
//
|
||||
// MandiriEmoneyApi.swift
|
||||
// Emoney Info
|
||||
//
|
||||
// Created by Wira Irawan on 27/07/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreNFC
|
||||
|
||||
public class MandiriEmoneyApi : UnifiedNfcApi {
|
||||
var emoney : Emoney = Emoney()
|
||||
var riwayatList: [RiwayatCard] = []
|
||||
var cardType : Int?
|
||||
var mapv1 : String = ""
|
||||
var start = 0
|
||||
var finish = 256
|
||||
var finish2 = 10
|
||||
|
||||
public override init() {}
|
||||
|
||||
public func getCardNumber(){
|
||||
apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.MANDIRI_APDU01, completionHandler: {response in
|
||||
if (response.sw1 == 0x90 && response.sw2 == 0x00){
|
||||
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.getBalance()
|
||||
} else {
|
||||
self.apduRunner.invalidateSession(msg: "readFailed".localizeString(string: self.langCode!))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private func getBalance(){
|
||||
apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.MANDIRI_APDU02, completionHandler: {response in
|
||||
if (response.sw1 == 0x90 && response.sw2 == 0x00){
|
||||
debugLog(response.getData().hexEncodedString())
|
||||
let balance = response.getData().hexEncodedString().subString(from: 0, to: 8)
|
||||
self.emoney.setBalance(self.getRealBalance(reverseHexa: balance))
|
||||
// self.updateScreen()
|
||||
if (self.cardType! == 131){
|
||||
self.getLogStep01(index: self.start)
|
||||
} else {
|
||||
self.getLogStep02(index: self.start)
|
||||
}
|
||||
} else {
|
||||
self.apduRunner.invalidateSession(msg: "readFailed".localizeString(string: self.langCode!))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
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)
|
||||
apduRunner.exchangeApdu(apduCommand: MANDIRI_LOG, completionHandler: {response in
|
||||
if (response.sw1 == 0x90 && response.sw2 == 0x00){
|
||||
self.mapv1.append(response.getData().hexEncodedString())
|
||||
self.start+=1
|
||||
if (self.start < (self.finish)){
|
||||
self.getLogStep01(index: self.start)
|
||||
} else {
|
||||
self.parseNewLog()
|
||||
self.start = 0
|
||||
self.riwayatList = self.riwayatList.sorted(by: { $0.getTransationTime()?.compare($1.getTransationTime()!) == .orderedDescending })
|
||||
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.parseNewLog()
|
||||
self.start = 0
|
||||
self.riwayatList = self.riwayatList.sorted(by: { $0.getTransationTime()?.compare($1.getTransationTime()!) == .orderedDescending })
|
||||
self.emoney.setRiwayatList(self.riwayatList)
|
||||
self.emoney.setTampilRiwayat(true)
|
||||
self.updateScreen()
|
||||
self.apduRunner.sessionEx?.alertMessage = "readFinish".localizeString(string: self.langCode!)
|
||||
self.apduRunner.invalidateSession()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func parseNewLog(){
|
||||
let logs = self.mapv1.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if (logs.count % 48 != 0){
|
||||
return
|
||||
}
|
||||
let total = logs.count/48
|
||||
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!)
|
||||
let processType = Int(data.subString(from: 28, to: 32))
|
||||
if (processType == 100){
|
||||
riwayat.setProsesTipe(0)
|
||||
riwayat.setTitle("Top Up")
|
||||
} else {
|
||||
riwayat.setProsesTipe(1)
|
||||
riwayat.setTitle("Payment")
|
||||
}
|
||||
riwayat.setLocationId(data.subString(from: 12, to: 20))
|
||||
let amount = data.subString(from: 32, to: 40)
|
||||
riwayat.setAmount(getRealBalance(reverseHexa: amount))
|
||||
riwayatList.append(riwayat)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
apduRunner.exchangeApdu(apduCommand: MANDIRI_LOG, completionHandler: {response in
|
||||
if (response.sw1 == 0x90 && response.sw2 == 0x00){
|
||||
self.mapv1.append(response.getData().hexEncodedString())
|
||||
self.start+=1
|
||||
if (self.start < (self.finish2)){
|
||||
self.getLogStep02(index: self.start)
|
||||
} else {
|
||||
self.parseOldLog()
|
||||
self.start = 0
|
||||
self.riwayatList = self.riwayatList.sorted(by: { $0.getTransationTime()?.compare($1.getTransationTime()!) == .orderedDescending })
|
||||
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.parseOldLog()
|
||||
self.start = 0
|
||||
self.riwayatList = self.riwayatList.sorted(by: { $0.getTransationTime()?.compare($1.getTransationTime()!) == .orderedDescending })
|
||||
self.emoney.setRiwayatList(self.riwayatList)
|
||||
self.emoney.setTampilRiwayat(true)
|
||||
self.updateScreen()
|
||||
self.apduRunner.sessionEx?.alertMessage = "readFinish".localizeString(string: self.langCode!)
|
||||
self.apduRunner.invalidateSession()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func parseOldLog(){
|
||||
let logs = self.mapv1.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if (logs.count % 60 != 0){
|
||||
return
|
||||
}
|
||||
let total = logs.count/60
|
||||
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!)
|
||||
}
|
||||
}
|
||||
|
||||
func getTransactionTime(formatDate : String, formatTime : String) -> Date?{
|
||||
let dateFormatter2 = DateFormatter()
|
||||
dateFormatter2.dateFormat = "HHmmss"
|
||||
let date12 = dateFormatter2.date(from: formatTime)!
|
||||
|
||||
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"
|
||||
return dateFormatter.date(from:(formatDate + " " + date22))
|
||||
}
|
||||
|
||||
func getRealBalance(reverseHexa: String?) -> Int {
|
||||
guard let reverseHexa = reverseHexa?.trimmingCharacters(in: .whitespacesAndNewlines), !reverseHexa.isEmpty else {
|
||||
return 0
|
||||
}
|
||||
|
||||
if reverseHexa.count % 2 != 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
var sb = ""
|
||||
for l in stride(from: reverseHexa.count / 2, through: 1, by: -1) {
|
||||
let index1 = reverseHexa.index(reverseHexa.startIndex, offsetBy: l * 2 - 2)
|
||||
let index2 = reverseHexa.index(reverseHexa.startIndex, offsetBy: l * 2 - 1)
|
||||
sb.append(reverseHexa[index1])
|
||||
sb.append(reverseHexa[index2])
|
||||
}
|
||||
return sb.hex2decimal()
|
||||
}
|
||||
|
||||
private func updateScreen(){
|
||||
if (self.apduRunner.callback != nil){
|
||||
self.apduRunner.callback?.complete(emoney: self.emoney)
|
||||
}
|
||||
}
|
||||
|
||||
func riwayatCard(_ bArr: [UInt8]) -> RiwayatCard? {
|
||||
var str: String
|
||||
let riwayatCard = RiwayatCard()
|
||||
var wrap = Data(bArr)
|
||||
var bArr2 = [UInt8](repeating: 0, count: 6)
|
||||
var bArr3 = [UInt8](repeating: 0, count: 16)
|
||||
|
||||
wrap.copyBytes(to: &bArr2, count: 6)
|
||||
wrap.removeFirst(10)
|
||||
|
||||
let len = wrap.withUnsafeBytes { $0.load(as: Int32.self).bigEndian }
|
||||
wrap.removeFirst(4)
|
||||
let amount = wrap.withUnsafeBytes { $0.load(as: Int32.self).littleEndian }
|
||||
wrap.removeFirst(4)
|
||||
let desk = wrap.withUnsafeBytes { $0.load(as: Int32.self).littleEndian }
|
||||
wrap.removeFirst(4)
|
||||
|
||||
do {
|
||||
wrap.copyBytes(to: &bArr3, count: 16)
|
||||
debugLog("bArr3: \(bArr3.map { String(format: "%02X", $0) }.joined())")
|
||||
} catch {
|
||||
debugLog("Error: \(error.localizedDescription)")
|
||||
}
|
||||
|
||||
var type = 0
|
||||
if len == 288 {
|
||||
str = "payment".localizeString(string: self.langCode!)
|
||||
type = 1
|
||||
} else if len == 256 || len == 336 {
|
||||
type = 3
|
||||
str = "topup".localizeString(string: self.langCode!)
|
||||
} else {
|
||||
type = -1
|
||||
str = "unknown".localizeString(string: self.langCode!)
|
||||
}
|
||||
|
||||
|
||||
var str2 = ""
|
||||
for y in 0..<6 {
|
||||
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!)
|
||||
riwayatCard.setAmount(Int(amount))
|
||||
|
||||
// if str.caseInsensitiveCompare("payment") == .orderedSame {
|
||||
// type = 1
|
||||
// } else if str.caseInsensitiveCompare("topup") != .orderedSame {
|
||||
// type = 3
|
||||
// }
|
||||
|
||||
riwayatCard.setProsesTipe(type)
|
||||
riwayatCard.setTitle(str)
|
||||
|
||||
if type == -1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return riwayatCard
|
||||
}
|
||||
}
|
||||
58
Emoney Info/Classes/api/MegaCashApi.swift
Executable file
58
Emoney Info/Classes/api/MegaCashApi.swift
Executable file
@ -0,0 +1,58 @@
|
||||
//
|
||||
// MegaCashApi.swift
|
||||
// Emoney Info
|
||||
//
|
||||
// Created by Wira Irawan on 27/07/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public class MegaCashApi : UnifiedNfcApi {
|
||||
var emoney : Emoney = Emoney()
|
||||
var riwayatList: [RiwayatCard] = []
|
||||
|
||||
public override init() {}
|
||||
|
||||
public func getBalance(resp : Data){
|
||||
self.emoney.setCardLabel("MegaCash")
|
||||
let value = resp.hexEncodedString()
|
||||
self.emoney.setCardNumber(value.subString(from: 4, to: value.count))
|
||||
apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.MEGA_APDU02, completionHandler: {response in
|
||||
if (response.sw1 == 0x90 && response.sw2 == 0x00){
|
||||
let balance = response.getData().hexEncodedString()
|
||||
self.emoney.setBalance(self.getRealBalance(reverseHexa: balance))
|
||||
self.emoney.setTampilRiwayat(false)
|
||||
self.updateScreen()
|
||||
self.apduRunner.sessionEx?.alertMessage = "readFinish".localizeString(string: self.langCode!)
|
||||
self.apduRunner.invalidateSession()
|
||||
} else {
|
||||
self.apduRunner.invalidateSession(msg: "readFailed".localizeString(string: self.langCode!))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func getRealBalance(reverseHexa: String?) -> Int {
|
||||
guard let reverseHexa = reverseHexa?.trimmingCharacters(in: .whitespacesAndNewlines), !reverseHexa.isEmpty else {
|
||||
return 0
|
||||
}
|
||||
|
||||
if reverseHexa.count % 2 != 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
var sb = ""
|
||||
for l in stride(from: reverseHexa.count / 2, through: 1, by: -1) {
|
||||
let index1 = reverseHexa.index(reverseHexa.startIndex, offsetBy: l * 2 - 2)
|
||||
let index2 = reverseHexa.index(reverseHexa.startIndex, offsetBy: l * 2 - 1)
|
||||
sb.append(reverseHexa[index1])
|
||||
sb.append(reverseHexa[index2])
|
||||
}
|
||||
return sb.hex2decimal()
|
||||
}
|
||||
|
||||
private func updateScreen(){
|
||||
if (self.apduRunner.callback != nil){
|
||||
self.apduRunner.callback?.complete(emoney: self.emoney)
|
||||
}
|
||||
}
|
||||
}
|
||||
178
Emoney Info/Classes/api/TapCashApi.swift
Executable file
178
Emoney Info/Classes/api/TapCashApi.swift
Executable file
@ -0,0 +1,178 @@
|
||||
//
|
||||
// TapCashApi.swift
|
||||
// Emoney Info
|
||||
//
|
||||
// Created by Wira Irawan on 26/07/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreNFC
|
||||
|
||||
public class TapCashApi : UnifiedNfcApi {
|
||||
var emoney : Emoney = Emoney()
|
||||
var tapCashData : TapCashData = TapCashData()
|
||||
var riwayatList: [RiwayatCard] = []
|
||||
var start = 0
|
||||
var totalLog = 0
|
||||
|
||||
public override init() {}
|
||||
|
||||
public func checkBalance(){
|
||||
apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.TAPCASH_APDU01, completionHandler: {response in
|
||||
if (response.sw1 == 0x90 && response.sw2 == 0x00){
|
||||
self.emoney.setCardLabel("BNI TapCash")
|
||||
self.tapCashData.setPurseData(response.getData().bytes)
|
||||
let balance = self.tapCashData.getPurseBalance()?.hexString().hex2decimal()
|
||||
self.emoney.setBalance(balance!)
|
||||
self.emoney.setCardNumber(self.tapCashData.getCAN()!.hexString())
|
||||
self.totalLog = (self.tapCashData.getTotalRecords()?.hexString().hex2decimal())!
|
||||
if (self.totalLog > 10){
|
||||
self.totalLog = 10
|
||||
}
|
||||
debugLog("total log " + String(self.totalLog))
|
||||
self.getHistory(index: 0)
|
||||
} else {
|
||||
self.emoney.setTampilRiwayat(false)
|
||||
self.updateScreen()
|
||||
self.apduRunner.sessionEx?.alertMessage = "readFinish".localizeString(string: self.langCode!)
|
||||
self.apduRunner.invalidateSession()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private func getHistory(index : Int){
|
||||
let st = String(index).leftPad(with: "0", length: 2)
|
||||
//90 32 03 00 01 00 10
|
||||
let TAPCASH_LOG = NFCISO7816APDU(instructionClass : 0x90, instructionCode : 0x32, p1Parameter : 0x03, p2Parameter : 0x00, data : Data(_ : st.stringToBytes()!), expectedResponseLength : 16)
|
||||
debugLog(TAPCASH_LOG.toHexString())
|
||||
apduRunner.exchangeApdu(apduCommand: TAPCASH_LOG, completionHandler: {response in
|
||||
if (response.sw1 == 0x90 && response.sw2 == 0x00){
|
||||
self.addRiwayatTransaksi(data: response.getData().bytes)
|
||||
}
|
||||
self.start+=1
|
||||
if (self.start < (self.totalLog)){
|
||||
self.getHistory(index: self.start)
|
||||
} else {
|
||||
self.updateScreen()
|
||||
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.apduRunner.sessionEx?.alertMessage = "readFinish".localizeString(string: self.langCode!)
|
||||
self.apduRunner.invalidateSession()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private func updateScreen(){
|
||||
if (self.apduRunner.callback != nil){
|
||||
self.apduRunner.callback?.complete(emoney: self.emoney)
|
||||
}
|
||||
}
|
||||
|
||||
private func addRiwayatTransaksi(data: [UInt8]) {
|
||||
let trxType = Array(data[0..<1])
|
||||
let trxAmount = Array(data[1..<4])
|
||||
let trxDateTimes = Array(data[4..<8])
|
||||
//let trxUserData = Array(data[8..<16])
|
||||
|
||||
let title = trxType.hexString()
|
||||
var amount: Int
|
||||
if title.lowercased() == "01" || title.lowercased() == "05" || title.lowercased() == "07" || title.lowercased() == "10" || title.lowercased() == "20" {
|
||||
amount = trxAmount.hexString().secondComplementsAmount()
|
||||
} else {
|
||||
amount = trxType.hexString().hex2decimal()
|
||||
}
|
||||
|
||||
let riwayatCard = RiwayatCard()
|
||||
riwayatCard.setTitle(getStatementTitle(header: trxType.hexString()))
|
||||
riwayatCard.setProsesTipe(getTranscationType(header: trxType.hexString()))
|
||||
riwayatCard.setAmount(amount)
|
||||
let transactionTime = self.getTransactionTime(julian: trxDateTimes.hexString())
|
||||
riwayatCard.setTransactionTime(transactionTime)
|
||||
riwayatList.append(riwayatCard)
|
||||
}
|
||||
|
||||
private func getStatementTitle(header: String) -> String {
|
||||
var title = ""
|
||||
switch header.uppercased() {
|
||||
case "01":
|
||||
title = "payment".localizeString(string: self.langCode!)
|
||||
case "02":
|
||||
title = "Black List Card"
|
||||
case "03":
|
||||
title = "topup".localizeString(string: self.langCode!)
|
||||
case "04":
|
||||
title = "topup".localizeString(string: self.langCode!)
|
||||
case "05":
|
||||
title = "statementFee".localizeString(string: self.langCode!)
|
||||
case "06":
|
||||
title = "updateBalance".localizeString(string: self.langCode!)
|
||||
case "07":
|
||||
title = "gracePeriod".localizeString(string: self.langCode!)
|
||||
case "10":
|
||||
title = "refund".localizeString(string: self.langCode!)
|
||||
case "20":
|
||||
title = "refund".localizeString(string: self.langCode!)
|
||||
case "22":
|
||||
title = "close".localizeString(string: self.langCode!)
|
||||
case "F0":
|
||||
title = "atu".localizeString(string: self.langCode!)
|
||||
default:
|
||||
break
|
||||
}
|
||||
return title
|
||||
}
|
||||
|
||||
private func getTranscationType(header: String) -> Int {
|
||||
switch header.uppercased() {
|
||||
case "01":
|
||||
return 1
|
||||
case "02":
|
||||
return 2
|
||||
case "03":
|
||||
return 0
|
||||
case "04":
|
||||
return 0
|
||||
case "05":
|
||||
return 1
|
||||
case "06":
|
||||
return 6
|
||||
case "07":
|
||||
return 7
|
||||
case "10":
|
||||
return 10
|
||||
case "20":
|
||||
return 20
|
||||
case "22":
|
||||
return 22
|
||||
default:
|
||||
break
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func getTransactionTime(julian: String) -> Date {
|
||||
let dec = julian.hex2decimal()
|
||||
|
||||
let cal = Calendar.current
|
||||
// set to 1st January 1995
|
||||
var dateComponents = DateComponents()
|
||||
dateComponents.year = 1995
|
||||
dateComponents.month = 1
|
||||
dateComponents.day = 1
|
||||
dateComponents.hour = 0
|
||||
dateComponents.minute = 0
|
||||
dateComponents.second = 0
|
||||
|
||||
if let date = cal.date(from: dateComponents) {
|
||||
let newDate = date.addingTimeInterval(Double(dec))
|
||||
return newDate
|
||||
}
|
||||
|
||||
return Date()
|
||||
}
|
||||
|
||||
}
|
||||
463
Emoney Info/Classes/api/UnifiedNfcApi.swift
Executable file
463
Emoney Info/Classes/api/UnifiedNfcApi.swift
Executable file
@ -0,0 +1,463 @@
|
||||
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] = [:]
|
||||
|
||||
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
|
||||
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)
|
||||
}
|
||||
|
||||
func readFelicaCard(tag: NFCFeliCaTag){
|
||||
let kmt = Emoney()
|
||||
|
||||
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.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.apduRunner.nfcApi?.stopCheckCard(message: "Error Status: \(status1) \(status2)")
|
||||
return
|
||||
}
|
||||
for (index, data) in blockData.enumerated() {
|
||||
debugLog("Data Blok \(index): \(data.map { String(format: "%02X", $0) }.joined())")
|
||||
if let cardNumberString = String(data: data, encoding: .utf8) {
|
||||
kmt.setCardLabel("KMT")
|
||||
kmt.setCardNumber(cardNumberString)
|
||||
self.readFelicaBalance(tag: tag, kmt: kmt)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.apduRunner.nfcApi?.stopCheckCard(message: "Gagal membaca: \(error.localizedDescription)")
|
||||
return
|
||||
}
|
||||
|
||||
if status1 == 0x00 && status2 == 0x00 {
|
||||
let cardBalance = [UInt8](blockData[0])
|
||||
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
|
||||
debugLog("balance")
|
||||
kmt.setBalance(y)
|
||||
if let balance = formatter.string(from: NSNumber(value: y)) {
|
||||
debugLog("Saldo: \(balance)") // Hasil contoh: "67.305.985"
|
||||
}
|
||||
self.readFelicaCardHistory(tag: tag, kmt: kmt)
|
||||
// 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 {
|
||||
debugLog("Error: \(error.localizedDescription)")
|
||||
return
|
||||
}
|
||||
|
||||
if status1 == 0x00 && status2 == 0x00 {
|
||||
debugLog("Berhasil membaca 15 blok!")
|
||||
// blockData akan berisi array of Data, masing-masing 16 byte
|
||||
for (index, data) in blockData.enumerated() {
|
||||
let riwayat = RiwayatCard()
|
||||
var normal = true
|
||||
let subId = data.subdata(in: 8..<10)
|
||||
|
||||
let uid = self.convert(bytes: [UInt8](subId))
|
||||
debugLog("station: \(uid)")
|
||||
|
||||
debugLog("Blok \(index): \(data.map { String(format: "%02X", $0) }.joined())")
|
||||
if (uid == 0){
|
||||
normal = false
|
||||
}
|
||||
if (data.count > 10){
|
||||
let type = data[10]
|
||||
debugLog(type)
|
||||
switch type {
|
||||
case 0x01:
|
||||
riwayat.setProsesTipe(1)
|
||||
riwayat.setTitle("payment".localizeString(string: self.langCode!))
|
||||
debugLog("Pembayaran")
|
||||
case 0x00, 0x03:
|
||||
riwayat.setProsesTipe(0)
|
||||
riwayat.setTitle("topup".localizeString(string: self.langCode!))
|
||||
debugLog("Topup")
|
||||
default:
|
||||
riwayat.setProsesTipe(1)
|
||||
riwayat.setTitle("payment".localizeString(string: self.langCode!))
|
||||
debugLog("Other")
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
if let station = self.stationMap[uid]{
|
||||
debugLog("station", 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)
|
||||
|
||||
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"
|
||||
|
||||
let dateString = formatter.string(from: date!)
|
||||
debugLog("Hasil Konversi: \(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)) {
|
||||
debugLog("amount: \(balance)") // Hasil contoh: "67.305.985"
|
||||
}
|
||||
debugLog("")
|
||||
} else {
|
||||
debugLog("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)
|
||||
debugLog(result) // Hasil: 29-01-2026 16:00:44
|
||||
} else {
|
||||
debugLog("Format string tidak cocok")
|
||||
}
|
||||
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)) {
|
||||
debugLog("amount: \(balance)") // Hasil contoh: "67.305.985"
|
||||
}
|
||||
}
|
||||
debugLog("")
|
||||
riwayatList.append(riwayat)
|
||||
}
|
||||
kmt.setRiwayatList(riwayatList)
|
||||
kmt.setTampilRiwayat(true)
|
||||
self.apduRunner.callback?.complete(emoney: kmt)
|
||||
self.apduRunner.sessionEx?.alertMessage = "readFinish".localizeString(string: self.langCode!)
|
||||
self.apduRunner.invalidateSession()
|
||||
} else {
|
||||
debugLog("Gagal. Status: \(status1), \(status2)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getDate(data: [UInt8]) -> Date? {
|
||||
// 1. Tentukan TimeZone Jakarta
|
||||
let timeZone = TimeZone(identifier: "Asia/Jakarta")!
|
||||
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:
|
||||
fatalError("Data kosong")
|
||||
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 })
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
16
Emoney Info/Classes/api/callback/ApduCallback.swift
Executable file
16
Emoney Info/Classes/api/callback/ApduCallback.swift
Executable file
@ -0,0 +1,16 @@
|
||||
//
|
||||
// ApduCallback.swift
|
||||
// Emoney Info
|
||||
//
|
||||
// Created by Wira Irawan on 24/07/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreNFC
|
||||
|
||||
protocol ApduCallback {
|
||||
func connected(unifiedNfcApi : UnifiedNfcApi)
|
||||
func felicaConnected(unifiedNfcApi : UnifiedNfcApi, tag : NFCFeliCaTag)
|
||||
func complete(emoney: Emoney)
|
||||
func failed(error: NSError)
|
||||
}
|
||||
38
Emoney Info/Classes/api/nfc/ApduResponse.swift
Executable file
38
Emoney Info/Classes/api/nfc/ApduResponse.swift
Executable file
@ -0,0 +1,38 @@
|
||||
//
|
||||
// ApduResponse.swift
|
||||
// Emoney Info
|
||||
//
|
||||
// Created by Wira Irawan on 24/07/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class ApduResponse {
|
||||
var data : Data?
|
||||
var sw1 : UInt8?
|
||||
var sw2 : UInt8?
|
||||
|
||||
func setData(_data : Data){
|
||||
self.data = _data
|
||||
}
|
||||
|
||||
func setSw1(_sw1 : UInt8){
|
||||
self.sw1 = _sw1
|
||||
}
|
||||
|
||||
func setSw2(_sw2 : UInt8){
|
||||
self.sw2 = _sw2
|
||||
}
|
||||
|
||||
func getSw1() -> UInt8{
|
||||
return sw1!
|
||||
}
|
||||
|
||||
func getSw2() -> UInt8{
|
||||
return sw2!
|
||||
}
|
||||
|
||||
func getData() -> Data{
|
||||
return data!
|
||||
}
|
||||
}
|
||||
126
Emoney Info/Classes/api/nfc/ApduRunner.swift
Executable file
126
Emoney Info/Classes/api/nfc/ApduRunner.swift
Executable file
@ -0,0 +1,126 @@
|
||||
import Foundation
|
||||
import CoreNFC
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
extension NFCISO7816APDU {
|
||||
func toHexString() -> String {
|
||||
let dataFieldInHex = (self.data ?? Data(_ : [])).hexEncodedString()
|
||||
return String(format:"%02X %02X %02X %02X", self.instructionClass, self.instructionCode, self.p1Parameter, self.p2Parameter) + " " + dataFieldInHex + String(format:"%02X", self.expectedResponseLength) + " "
|
||||
}
|
||||
}
|
||||
|
||||
typealias CompletionHandler = (_ response:ApduResponse) -> Void
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
public class ApduRunner: NSObject, NFCTagReaderSessionDelegate {
|
||||
public static let NFC_TAG_CONNECTED_EVENT:String = "nfcTagConnected"
|
||||
|
||||
var sessionEx: NFCTagReaderSession?
|
||||
var callback: ApduCallback?
|
||||
var nfcApi : UnifiedNfcApi?
|
||||
|
||||
func setApduCallback(callback : ApduCallback) {
|
||||
self.callback = callback
|
||||
}
|
||||
|
||||
func setUnifiedNfcApi(nfcApi : UnifiedNfcApi) {
|
||||
self.nfcApi = nfcApi
|
||||
}
|
||||
|
||||
func startScan() {
|
||||
let langCode = Locale.current.languageCode
|
||||
self.sessionEx = NFCTagReaderSession(pollingOption: [.iso14443, .iso18092], delegate: self)
|
||||
self.sessionEx?.alertMessage = "scanMessage".localizeString(string: langCode!)
|
||||
self.sessionEx?.begin()
|
||||
}
|
||||
|
||||
|
||||
public func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) {
|
||||
guard self.callback != nil else {
|
||||
debugLog("NfcCallback is empty.")
|
||||
return
|
||||
}
|
||||
guard self.sessionEx != nil else {
|
||||
return
|
||||
}
|
||||
guard tags.count > 0 else {
|
||||
debugLog("Nfc Tag???.")
|
||||
return
|
||||
}
|
||||
if case NFCTag.iso7816(_) = tags.first! {
|
||||
sessionEx?.connect(to: tags.first!) { [self] (error: Error?) in
|
||||
if let err = error {
|
||||
debugLog("Error connecting to Nfc Tag" + err.localizedDescription)
|
||||
return
|
||||
}
|
||||
debugLog("Nfc Tag is connected.")
|
||||
if (self.callback != nil){
|
||||
self.callback!.connected(unifiedNfcApi: self.nfcApi!)
|
||||
}
|
||||
}
|
||||
} else if case .feliCa(let feliCaTag) = tags.first! {
|
||||
sessionEx?.connect(to: tags.first!) { [self] (error: Error?) in
|
||||
if let err = error {
|
||||
debugLog("Error connecting to Nfc Tag" + err.localizedDescription)
|
||||
return
|
||||
}
|
||||
// felicaTag = feliCaTag
|
||||
debugLog("Felica is connected.")
|
||||
if (self.callback != nil){
|
||||
debugLog("Felica is connected 2.")
|
||||
self.callback!.felicaConnected(unifiedNfcApi: self.nfcApi!, tag: feliCaTag)
|
||||
}
|
||||
// self.sendFelicaCommand(tag: feliCaTag, session: sessionEx!)
|
||||
|
||||
// let idm = feliCaTag.currentIDm.map { String(format: "%.2hhx", $0) }.joined()
|
||||
// let hexString = "0F06" + idm + "010B30018000"
|
||||
// print("hex: \(hexString)")
|
||||
// if let apduData = hexString.hexToData() {
|
||||
// feliCaTag.sendFeliCaCommand(commandPacket: apduData)
|
||||
// }
|
||||
|
||||
// if (self.callback != nil){
|
||||
// self.callback!.connected(felicaNfcApi: self.felicaNfcApi!)
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func exchangeApdu(apduCommand: NFCISO7816APDU, completionHandler: @escaping CompletionHandler) {
|
||||
if case let NFCTag.iso7816(nfcTag) = self.sessionEx!.connectedTag! {
|
||||
nfcTag.sendCommand(apdu: apduCommand) { (response: Data, sw1: UInt8, sw2: UInt8, error: Error?)
|
||||
in
|
||||
let resp = ApduResponse()
|
||||
debugLog("SW1-SW2: " + String(format: "%02X, %02X", sw1, sw2))
|
||||
resp.setSw1(_sw1: sw1)
|
||||
resp.setSw2(_sw2: sw2)
|
||||
resp.setData(_data: response)
|
||||
completionHandler(resp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func invalidateSession() {
|
||||
sessionEx?.invalidate()
|
||||
}
|
||||
|
||||
func invalidateSession(msg : String) {
|
||||
sessionEx?.invalidate(errorMessage: msg)
|
||||
}
|
||||
|
||||
public func tagReaderSessionDidBecomeActive(_ session: NFCTagReaderSession) {
|
||||
debugLog("Nfc session is active")
|
||||
}
|
||||
|
||||
public func tagReaderSession(_ session: NFCTagReaderSession, didInvalidateWithError error: Error) {
|
||||
debugLog("Error happend: " + error.localizedDescription)
|
||||
NotificationCenter.default.post(name: Notification.Name("stopTimer"), object: nil)
|
||||
if ((sessionEx?.isReady) != nil){
|
||||
self.invalidateSession(msg: error.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
66
Emoney Info/Classes/api/nfc/Emoney.swift
Executable file
66
Emoney Info/Classes/api/nfc/Emoney.swift
Executable file
@ -0,0 +1,66 @@
|
||||
//
|
||||
// Emoney.swift
|
||||
// Emoney Info
|
||||
//
|
||||
// Created by Wira Irawan on 26/07/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class Emoney {
|
||||
private var balance: Int = 0
|
||||
private var cardNumber: String?
|
||||
private var cardType: String?
|
||||
private var riwayatList: [RiwayatCard]?
|
||||
private var tampilRiwayat: Bool = false
|
||||
private var cardLabel: String?
|
||||
|
||||
func getBalance() -> Int {
|
||||
return self.balance
|
||||
}
|
||||
|
||||
func getCardNumber() -> String {
|
||||
return self.cardNumber!
|
||||
}
|
||||
|
||||
func getCardType() -> String {
|
||||
return self.cardType!
|
||||
}
|
||||
|
||||
func getRiwayatList() -> [RiwayatCard] {
|
||||
return self.riwayatList!
|
||||
}
|
||||
|
||||
func isTampilRiwayat() -> Bool {
|
||||
return self.tampilRiwayat
|
||||
}
|
||||
|
||||
func setBalance(_ j: Int) {
|
||||
self.balance = j
|
||||
}
|
||||
|
||||
func setCardNumber(_ str: String) {
|
||||
self.cardNumber = str
|
||||
}
|
||||
|
||||
func setCardType(_ str: String) {
|
||||
self.cardType = str
|
||||
}
|
||||
|
||||
func setRiwayatList(_ list: [RiwayatCard]) {
|
||||
self.riwayatList = list
|
||||
}
|
||||
|
||||
func setTampilRiwayat(_ z: Bool) {
|
||||
self.tampilRiwayat = z
|
||||
}
|
||||
|
||||
func setCardLabel(_ str: String) {
|
||||
self.cardLabel = str
|
||||
}
|
||||
|
||||
func getCardLabel() -> String {
|
||||
return self.cardLabel!
|
||||
}
|
||||
|
||||
}
|
||||
124
Emoney Info/Classes/api/nfc/RiwayatCard.swift
Executable file
124
Emoney Info/Classes/api/nfc/RiwayatCard.swift
Executable file
@ -0,0 +1,124 @@
|
||||
//
|
||||
// RiwayatCard.swift
|
||||
// Emoney Info
|
||||
//
|
||||
// Created by Wira Irawan on 25/07/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class RiwayatCard
|
||||
//: Comparable
|
||||
{
|
||||
// static func < (lhs: RiwayatCard, rhs: RiwayatCard) -> Bool {
|
||||
// return lhs.valueToCompare > rhs.valueToCompare
|
||||
// }
|
||||
//
|
||||
// static func == (lhs: RiwayatCard, rhs: RiwayatCard) -> Bool {
|
||||
// return false
|
||||
// }
|
||||
|
||||
private var amount: Int = 0
|
||||
private var desk: String?
|
||||
private var jam: String?
|
||||
private var locationId: String?
|
||||
private var locationName: String?
|
||||
private var prosesTipe: Int = 0
|
||||
private var tanggal: String?
|
||||
private var title: String?
|
||||
private var transactionTime : Date?
|
||||
private var ads : Bool = false
|
||||
|
||||
// private var valueToCompare: Date
|
||||
|
||||
// init(valueToCompare: Date) {
|
||||
// self.valueToCompare = valueToCompare
|
||||
// }
|
||||
|
||||
func getAmount() -> Int {
|
||||
return self.amount
|
||||
}
|
||||
|
||||
func setAmount(_ amount: Int) {
|
||||
self.amount = amount
|
||||
}
|
||||
|
||||
func getDesk() -> String? {
|
||||
return self.desk
|
||||
}
|
||||
|
||||
func getJam() -> String? {
|
||||
return self.jam
|
||||
}
|
||||
|
||||
func setJam(_ str: String) {
|
||||
self.jam = str
|
||||
}
|
||||
|
||||
func getLocationId() -> String? {
|
||||
return self.locationId
|
||||
}
|
||||
|
||||
func setLocationId(_ str: String) {
|
||||
self.locationId = str
|
||||
}
|
||||
|
||||
func getLocationName() -> String? {
|
||||
return self.locationName
|
||||
}
|
||||
|
||||
func setLocationName(_ str: String) {
|
||||
self.locationName = str
|
||||
}
|
||||
|
||||
func getProsesTipe() -> Int {
|
||||
return self.prosesTipe
|
||||
}
|
||||
|
||||
func setProsesTipe(_ proc: Int) {
|
||||
self.prosesTipe = proc
|
||||
}
|
||||
|
||||
func getTanggal() -> String? {
|
||||
return self.tanggal
|
||||
}
|
||||
|
||||
func setTanggal(_ str: String) {
|
||||
self.tanggal = str
|
||||
}
|
||||
|
||||
func getTitle() -> String? {
|
||||
return self.title
|
||||
}
|
||||
|
||||
func setTitle(_ str: String) {
|
||||
self.title = str
|
||||
}
|
||||
|
||||
func setTransactionTime(_ date : Date){
|
||||
self.transactionTime = date
|
||||
}
|
||||
|
||||
func getTransationTime() -> Date?{
|
||||
return self.transactionTime
|
||||
}
|
||||
|
||||
func setAds(_ ads : Bool){
|
||||
self.ads = ads
|
||||
}
|
||||
|
||||
func isAds() -> Bool{
|
||||
return self.ads
|
||||
}
|
||||
|
||||
|
||||
// func setValueToCompare(_ valueToCompare: Date) {
|
||||
// self.valueToCompare = valueToCompare
|
||||
// }
|
||||
//
|
||||
// func getValueToCompare() -> Date {
|
||||
// return self.valueToCompare
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
109
Emoney Info/Classes/api/utils/BrizziSamHelper.swift
Executable file
109
Emoney Info/Classes/api/utils/BrizziSamHelper.swift
Executable file
@ -0,0 +1,109 @@
|
||||
//
|
||||
// BrizziSamHelper.swift
|
||||
// Emoney Info
|
||||
//
|
||||
// Created by Wira Irawan on 27/07/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CommonCrypto
|
||||
|
||||
class BrizziSamHelper {
|
||||
public var encryptedKey: String?
|
||||
public var authKey = "0000030080000000"
|
||||
public var keyCard: String?
|
||||
public var random = ""
|
||||
|
||||
static func encryptDeSeDe(_ str: String, _ str2: String, _ str3: String) -> Data? {
|
||||
var key = str2
|
||||
if key.count != 48 {
|
||||
if key.count == 32 {
|
||||
key += key.prefix(16)
|
||||
} else if key.count == 16 {
|
||||
key += key + key
|
||||
} else {
|
||||
key = "00000000000000000000000000000000"
|
||||
}
|
||||
}
|
||||
let keyData = key.hex2byte()
|
||||
let ivData = str3.hex2byte()
|
||||
|
||||
return crypt(input: str.hex2byte(), keyData: keyData, ivData: ivData, operation: CCOperation(kCCEncrypt))
|
||||
}
|
||||
|
||||
static func decryptDeSeDe(_ datas: Data) -> Data? {
|
||||
let keyData = ("C152153D5807784C721A433B5B59636D" + "C152153D5807784C").hex2byte()
|
||||
let ivData = ("0000000000000000").hex2byte()
|
||||
|
||||
return crypt(input: datas, keyData: keyData, ivData: ivData, operation: CCOperation(kCCDecrypt))
|
||||
}
|
||||
|
||||
static func mix(_ bArr: [UInt8], _ bArr2: [UInt8]) -> [UInt8] {
|
||||
guard !bArr2.isEmpty else {
|
||||
fatalError("empty security key")
|
||||
}
|
||||
|
||||
var bArr3 = [UInt8](repeating: 0, count: bArr.count)
|
||||
var i = 0
|
||||
for y in 0..<bArr.count {
|
||||
bArr3[y] = bArr[y] ^ bArr2[i]
|
||||
i += 1
|
||||
if i >= bArr2.count {
|
||||
i = 0
|
||||
}
|
||||
}
|
||||
return bArr3
|
||||
}
|
||||
|
||||
static func decrypt(_ data: String, _ key: String) -> Data? {
|
||||
let keyData = key.hex2byte()
|
||||
|
||||
return crypt(input: data.hex2byte(), keyData: keyData, ivData: nil, operation: CCOperation(kCCDecrypt))
|
||||
}
|
||||
|
||||
static func encrypt(_ str: String, _ key: String) -> Data? {
|
||||
let substring = String(key.prefix(16))
|
||||
guard let decryptedData = decrypt(str, substring) else {
|
||||
return ("").hex2byte()
|
||||
}
|
||||
|
||||
let a9 = decryptedData.hexEncodedString()
|
||||
let keyData = String(key.dropFirst(16).prefix(16)).hex2byte()
|
||||
|
||||
guard let encryptedData = crypt(input: a9.hex2byte(), keyData: keyData, ivData: nil, operation: CCOperation(kCCEncrypt)) else {
|
||||
return ("").hex2byte()
|
||||
}
|
||||
|
||||
guard let finalDecryptedData = decrypt(encryptedData.hexEncodedString(), substring) else {
|
||||
return ("").hex2byte()
|
||||
}
|
||||
|
||||
return finalDecryptedData
|
||||
}
|
||||
|
||||
func generateSamRandom() -> String {
|
||||
let sam = BrizziSamHelper.mix(((BrizziSamHelper.encrypt(self.keyCard!, self.random)!).hexEncodedString()).hex2byte().bytes, ("0000000000000000").hex2byte().bytes).hexString().subString(from: 0, to: 16)
|
||||
let sams = sam[sam.index(sam.startIndex, offsetBy: 2)..<sam.index(sam.startIndex, offsetBy: 16)] + sam[sam.startIndex..<sam.index(sam.startIndex, offsetBy: 2)]
|
||||
let result = (BrizziSamHelper.encrypt((BrizziSamHelper.mix(("1122334455667788").hex2byte().bytes, (self.keyCard!).hex2byte().bytes).hexString()), self.random)!).hexEncodedString().subString(from: 0, to: 16)
|
||||
|
||||
return result + (BrizziSamHelper.encrypt((BrizziSamHelper.mix(String(sams).hex2byte().bytes, result.hex2byte().bytes)).hexString(), self.random)!).hexEncodedString()
|
||||
}
|
||||
|
||||
private static func crypt(input: Data, keyData: Data, ivData: Data?, operation: CCOperation) -> Data? {
|
||||
var outLength = Int(0)
|
||||
var outBytes = [UInt8](repeating: 0, count: input.count + kCCBlockSize3DES)
|
||||
var status: CCCryptorStatus
|
||||
|
||||
if let ivData = ivData {
|
||||
status = CCCrypt(operation, CCAlgorithm(kCCAlgorithm3DES), CCOptions(kCCOptionPKCS7Padding), keyData.bytes, kCCKeySize3DES, ivData.bytes, input.bytes, input.count, &outBytes, outBytes.count, &outLength)
|
||||
} else {
|
||||
status = CCCrypt(operation, CCAlgorithm(kCCAlgorithmDES), CCOptions(kCCOptionPKCS7Padding), keyData.bytes, kCCKeySizeDES, nil, input.bytes, input.count, &outBytes, outBytes.count, &outLength)
|
||||
}
|
||||
|
||||
guard status == kCCSuccess else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return Data(bytes: outBytes, count: outLength)
|
||||
}
|
||||
}
|
||||
41
Emoney Info/Classes/api/utils/ByteArrayAndHexHelper.swift
Executable file
41
Emoney Info/Classes/api/utils/ByteArrayAndHexHelper.swift
Executable file
@ -0,0 +1,41 @@
|
||||
import Foundation
|
||||
|
||||
|
||||
public class ByteArrayAndHexHelper {
|
||||
|
||||
public static func digitalStrIntoAsciiUInt8Array(digitalStr : String) -> [UInt8]{
|
||||
var bytes = [UInt8]()
|
||||
for s in digitalStr {
|
||||
if let byte = UInt8(String(s)) {
|
||||
bytes.append(0x30 + byte)
|
||||
}
|
||||
}
|
||||
return bytes
|
||||
}
|
||||
|
||||
public static func hexStrToUInt8Array(hexStr: String) -> [UInt8] {
|
||||
var startIndex = hexStr.startIndex
|
||||
return (0..<hexStr.count/2).compactMap { _ in
|
||||
let endIndex = hexStr.index(after: startIndex)
|
||||
defer { startIndex = hexStr.index(after: endIndex) }
|
||||
return UInt8(hexStr[startIndex...endIndex], radix: 16)
|
||||
}
|
||||
}
|
||||
|
||||
public static func hex(from string: String) -> Data {
|
||||
.init(stride(from: 0, to: string.count, by: 2).map {
|
||||
string[string.index(string.startIndex, offsetBy: $0) ... string.index(string.startIndex, offsetBy: $0 + 1)]
|
||||
}.map {
|
||||
UInt8($0, radix: 16)!
|
||||
})
|
||||
}
|
||||
|
||||
public static func makeShort(src: [UInt8], srcOff : Int) -> Int {
|
||||
// if (srcOff < 0 || src.length < (srcOff + 2))
|
||||
// throw new IllegalArgumentException("Bad args!");
|
||||
let b0 = Int(src[srcOff] & 0xFF);
|
||||
let b1 = Int(src[srcOff + 1] & 0xFF);
|
||||
return (b0 << 8) + b1
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user