Initial commit

This commit is contained in:
Wira Basalamah
2026-04-24 04:55:24 +07:00
commit 8f0b001501
128 changed files with 9366 additions and 0 deletions

30
.gitignore vendored Normal file
View File

@ -0,0 +1,30 @@
.DS_Store
.claude/
# Xcode user-specific files
xcuserdata/
*.xcuserdatad/
*.xcuserstate
# Build artifacts
build/
DerivedData/
# Swift Package Manager
.build/
.swiftpm/
Package.resolved
# CocoaPods dependencies
Pods/
# Misc
*.moved-aside
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3

View File

@ -0,0 +1,727 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 56;
objects = {
/* Begin PBXBuildFile section */
530347112C577B1F00776F00 /* GradientView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 530347102C577B1F00776F00 /* GradientView.swift */; };
533746552C50DC9E00A08FD0 /* ApduResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 533746542C50DC9E00A08FD0 /* ApduResponse.swift */; };
533746572C512F8500A08FD0 /* ApduCallback.swift in Sources */ = {isa = PBXBuildFile; fileRef = 533746562C512F8500A08FD0 /* ApduCallback.swift */; };
533746592C513DAA00A08FD0 /* CommonConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 533746582C513DAA00A08FD0 /* CommonConstants.swift */; };
534964522C5472CB0063C392 /* BcaFlazzApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 534964512C5472CB0063C392 /* BcaFlazzApi.swift */; };
536C0F2E2C4E146B0076466F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 536C0F2D2C4E146B0076466F /* AppDelegate.swift */; };
536C0F302C4E146B0076466F /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 536C0F2F2C4E146B0076466F /* SceneDelegate.swift */; };
536C0F372C4E146D0076466F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 536C0F362C4E146D0076466F /* Assets.xcassets */; };
536C0F3A2C4E146D0076466F /* Base in Resources */ = {isa = PBXBuildFile; fileRef = 536C0F392C4E146D0076466F /* Base */; };
536C0F642C4E14C40076466F /* ApduRunner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 536C0F432C4E14C40076466F /* ApduRunner.swift */; };
536C0F652C4E14C40076466F /* ByteArrayAndHexHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 536C0F452C4E14C40076466F /* ByteArrayAndHexHelper.swift */; };
536C0F712C4E14C40076466F /* UnifiedNfcApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 536C0F522C4E14C40076466F /* UnifiedNfcApi.swift */; };
536C0F722C4E14C40076466F /* Array.swift in Sources */ = {isa = PBXBuildFile; fileRef = 536C0F542C4E14C40076466F /* Array.swift */; };
536C0F732C4E14C40076466F /* Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = 536C0F552C4E14C40076466F /* Data.swift */; };
536C0F742C4E14C40076466F /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 536C0F562C4E14C40076466F /* String.swift */; };
536C0F762C4E14C40076466F /* CardErrorCodes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 536C0F5A2C4E14C40076466F /* CardErrorCodes.swift */; };
536C0F7B2C4E14C40076466F /* ToastHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 536C0F602C4E14C40076466F /* ToastHelper.swift */; };
537BCE802C6C8CBD002A3127 /* AdsCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 537BCE7E2C6C8CBD002A3127 /* AdsCell.xib */; };
538EE4AC2C54D34A0033EDC8 /* MandiriEmoneyApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 538EE4AB2C54D34A0033EDC8 /* MandiriEmoneyApi.swift */; };
538EE4AE2C54FEC80033EDC8 /* JackCardApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 538EE4AD2C54FEC80033EDC8 /* JackCardApi.swift */; };
538EE4B02C5501F60033EDC8 /* MegaCashApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 538EE4AF2C5501F60033EDC8 /* MegaCashApi.swift */; };
538EE4B22C550A370033EDC8 /* BrizziApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 538EE4B12C550A370033EDC8 /* BrizziApi.swift */; };
538EE4B42C55147F0033EDC8 /* BrizziSamHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 538EE4B32C55147F0033EDC8 /* BrizziSamHelper.swift */; };
539A25212C4FFA38006997BE /* EmoneyApduCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = 539A25202C4FFA38006997BE /* EmoneyApduCommands.swift */; };
53A3AF4E2C58A7620072A8C8 /* AboutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53A3AF4D2C58A7620072A8C8 /* AboutViewController.swift */; };
53A3AF562C58C8810072A8C8 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 53A3AF542C58C8810072A8C8 /* Localizable.strings */; };
53BDEF432C6F507E00AF4766 /* logo.png in Resources */ = {isa = PBXBuildFile; fileRef = 53BDEF422C6F507E00AF4766 /* logo.png */; };
53E275C72C528378002C4C3B /* RiwayatCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53E275C62C528378002C4C3B /* RiwayatCard.swift */; };
53E275C92C52E2CB002C4C3B /* Emoney.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53E275C82C52E2CB002C4C3B /* Emoney.swift */; };
53E275CB2C52E4DD002C4C3B /* TapCashApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53E275CA2C52E4DD002C4C3B /* TapCashApi.swift */; };
53E275CD2C52E68D002C4C3B /* TapCashData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53E275CC2C52E68D002C4C3B /* TapCashData.swift */; };
53E275D02C535F04002C4C3B /* halter.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 53E275CF2C535F04002C4C3B /* halter.ttf */; };
66D1680E685BE7C994196F4A /* Pods_Emoney_Info.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1C0A2FB40E3C89EB399B18E5 /* Pods_Emoney_Info.framework */; };
8C4C92F72F82167D000F02ED /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C4C92F62F82167D000F02ED /* Theme.swift */; };
8C4C92F92F82175A000F02ED /* MainTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C4C92F82F82175A000F02ED /* MainTabView.swift */; };
8C4C92FB2F821877000F02ED /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C4C92FA2F821877000F02ED /* HomeView.swift */; };
8C4C92FD2F821943000F02ED /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C4C92FC2F821943000F02ED /* SettingsView.swift */; };
8C4C92FF2F821A04000F02ED /* HistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C4C92FE2F821A04000F02ED /* HistoryView.swift */; };
8C4C93012F8226C8000F02ED /* L10n.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C4C93002F8226C8000F02ED /* L10n.swift */; };
8C4C93042F825D65000F02ED /* FAQData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C4C93022F825D65000F02ED /* FAQData.swift */; };
8C4C93052F825D65000F02ED /* FAQViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C4C93032F825D65000F02ED /* FAQViewController.swift */; };
8C4C93082F8271AE000F02ED /* PrivacyPolicyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C4C93062F8271AE000F02ED /* PrivacyPolicyViewController.swift */; };
8C4C93092F8271AE000F02ED /* TermsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C4C93072F8271AE000F02ED /* TermsViewController.swift */; };
8C4C930D2F82A9BC000F02ED /* Station.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C4C930C2F82A9BC000F02ED /* Station.swift */; };
8CC07EA92F860E08009CFD0A /* header.png in Resources */ = {isa = PBXBuildFile; fileRef = 8CC07EA82F860E08009CFD0A /* header.png */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
1C0A2FB40E3C89EB399B18E5 /* Pods_Emoney_Info.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Emoney_Info.framework; sourceTree = BUILT_PRODUCTS_DIR; };
530347102C577B1F00776F00 /* GradientView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradientView.swift; sourceTree = "<group>"; };
533746542C50DC9E00A08FD0 /* ApduResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApduResponse.swift; sourceTree = "<group>"; };
533746562C512F8500A08FD0 /* ApduCallback.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApduCallback.swift; sourceTree = "<group>"; };
533746582C513DAA00A08FD0 /* CommonConstants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CommonConstants.swift; sourceTree = "<group>"; };
534964512C5472CB0063C392 /* BcaFlazzApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BcaFlazzApi.swift; sourceTree = "<group>"; };
536C0F2A2C4E146B0076466F /* Emoney Info.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Emoney Info.app"; sourceTree = BUILT_PRODUCTS_DIR; };
536C0F2D2C4E146B0076466F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
536C0F2F2C4E146B0076466F /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
536C0F362C4E146D0076466F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
536C0F392C4E146D0076466F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
536C0F3B2C4E146D0076466F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
536C0F432C4E14C40076466F /* ApduRunner.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApduRunner.swift; sourceTree = "<group>"; };
536C0F452C4E14C40076466F /* ByteArrayAndHexHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ByteArrayAndHexHelper.swift; sourceTree = "<group>"; };
536C0F522C4E14C40076466F /* UnifiedNfcApi.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnifiedNfcApi.swift; sourceTree = "<group>"; };
536C0F542C4E14C40076466F /* Array.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Array.swift; sourceTree = "<group>"; };
536C0F552C4E14C40076466F /* Data.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Data.swift; sourceTree = "<group>"; };
536C0F562C4E14C40076466F /* String.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = String.swift; sourceTree = "<group>"; };
536C0F5A2C4E14C40076466F /* CardErrorCodes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardErrorCodes.swift; sourceTree = "<group>"; };
536C0F602C4E14C40076466F /* ToastHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ToastHelper.swift; sourceTree = "<group>"; };
537BCE7E2C6C8CBD002A3127 /* AdsCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = AdsCell.xib; sourceTree = "<group>"; };
538EE4AB2C54D34A0033EDC8 /* MandiriEmoneyApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MandiriEmoneyApi.swift; sourceTree = "<group>"; };
538EE4AD2C54FEC80033EDC8 /* JackCardApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JackCardApi.swift; sourceTree = "<group>"; };
538EE4AF2C5501F60033EDC8 /* MegaCashApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MegaCashApi.swift; sourceTree = "<group>"; };
538EE4B12C550A370033EDC8 /* BrizziApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrizziApi.swift; sourceTree = "<group>"; };
538EE4B32C55147F0033EDC8 /* BrizziSamHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrizziSamHelper.swift; sourceTree = "<group>"; };
539A25202C4FFA38006997BE /* EmoneyApduCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmoneyApduCommands.swift; sourceTree = "<group>"; };
53A3AF4D2C58A7620072A8C8 /* AboutViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutViewController.swift; sourceTree = "<group>"; };
53A3AF552C58C8810072A8C8 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
53A3AF572C58C8BE0072A8C8 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = id.lproj/Localizable.strings; sourceTree = "<group>"; };
53BDEF422C6F507E00AF4766 /* logo.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = logo.png; sourceTree = "<group>"; };
53DFF4862C4F969E00235A56 /* Emoney Info.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Emoney Info.entitlements"; sourceTree = "<group>"; };
53E275C62C528378002C4C3B /* RiwayatCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RiwayatCard.swift; sourceTree = "<group>"; };
53E275C82C52E2CB002C4C3B /* Emoney.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Emoney.swift; sourceTree = "<group>"; };
53E275CA2C52E4DD002C4C3B /* TapCashApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TapCashApi.swift; sourceTree = "<group>"; };
53E275CC2C52E68D002C4C3B /* TapCashData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TapCashData.swift; sourceTree = "<group>"; };
53E275CF2C535F04002C4C3B /* halter.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = halter.ttf; sourceTree = "<group>"; };
8C4C92F62F82167D000F02ED /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = "<group>"; };
8C4C92F82F82175A000F02ED /* MainTabView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTabView.swift; sourceTree = "<group>"; };
8C4C92FA2F821877000F02ED /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = "<group>"; };
8C4C92FC2F821943000F02ED /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
8C4C92FE2F821A04000F02ED /* HistoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryView.swift; sourceTree = "<group>"; };
8C4C93002F8226C8000F02ED /* L10n.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = L10n.swift; sourceTree = "<group>"; };
8C4C93022F825D65000F02ED /* FAQData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAQData.swift; sourceTree = "<group>"; };
8C4C93032F825D65000F02ED /* FAQViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAQViewController.swift; sourceTree = "<group>"; };
8C4C93062F8271AE000F02ED /* PrivacyPolicyViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyPolicyViewController.swift; sourceTree = "<group>"; };
8C4C93072F8271AE000F02ED /* TermsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TermsViewController.swift; sourceTree = "<group>"; };
8C4C930C2F82A9BC000F02ED /* Station.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Station.swift; sourceTree = "<group>"; };
8CC07EA82F860E08009CFD0A /* header.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = header.png; sourceTree = "<group>"; };
B03D5F72678ED8DD7DFE3F40 /* Pods-Emoney Info.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Emoney Info.release.xcconfig"; path = "Target Support Files/Pods-Emoney Info/Pods-Emoney Info.release.xcconfig"; sourceTree = "<group>"; };
CFD118D4B349F6A8169BA732 /* Pods-Emoney Info.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Emoney Info.debug.xcconfig"; path = "Target Support Files/Pods-Emoney Info/Pods-Emoney Info.debug.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
536C0F272C4E146B0076466F /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
66D1680E685BE7C994196F4A /* Pods_Emoney_Info.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
29A1D7C82170743F7C68260A /* Pods */ = {
isa = PBXGroup;
children = (
CFD118D4B349F6A8169BA732 /* Pods-Emoney Info.debug.xcconfig */,
B03D5F72678ED8DD7DFE3F40 /* Pods-Emoney Info.release.xcconfig */,
);
path = Pods;
sourceTree = "<group>";
};
536C0F212C4E146B0076466F = {
isa = PBXGroup;
children = (
536C0F2C2C4E146B0076466F /* Emoney Info */,
536C0F2B2C4E146B0076466F /* Products */,
29A1D7C82170743F7C68260A /* Pods */,
E857A0B41B9A6DFF4C1AAD91 /* Frameworks */,
);
sourceTree = "<group>";
};
536C0F2B2C4E146B0076466F /* Products */ = {
isa = PBXGroup;
children = (
536C0F2A2C4E146B0076466F /* Emoney Info.app */,
);
name = Products;
sourceTree = "<group>";
};
536C0F2C2C4E146B0076466F /* Emoney Info */ = {
isa = PBXGroup;
children = (
8C4C93062F8271AE000F02ED /* PrivacyPolicyViewController.swift */,
8C4C93072F8271AE000F02ED /* TermsViewController.swift */,
8C4C93022F825D65000F02ED /* FAQData.swift */,
8C4C93032F825D65000F02ED /* FAQViewController.swift */,
8C4C92FE2F821A04000F02ED /* HistoryView.swift */,
8C4C92FC2F821943000F02ED /* SettingsView.swift */,
8C4C92FA2F821877000F02ED /* HomeView.swift */,
8C4C92F82F82175A000F02ED /* MainTabView.swift */,
537BCE7E2C6C8CBD002A3127 /* AdsCell.xib */,
53E275CE2C535EF2002C4C3B /* Resources */,
53DFF4862C4F969E00235A56 /* Emoney Info.entitlements */,
536C0F622C4E14C40076466F /* Classes */,
536C0F2D2C4E146B0076466F /* AppDelegate.swift */,
536C0F2F2C4E146B0076466F /* SceneDelegate.swift */,
536C0F362C4E146D0076466F /* Assets.xcassets */,
536C0F382C4E146D0076466F /* LaunchScreen.storyboard */,
536C0F3B2C4E146D0076466F /* Info.plist */,
53A3AF4D2C58A7620072A8C8 /* AboutViewController.swift */,
53A3AF542C58C8810072A8C8 /* Localizable.strings */,
);
path = "Emoney Info";
sourceTree = "<group>";
};
536C0F422C4E14C40076466F /* callback */ = {
isa = PBXGroup;
children = (
533746562C512F8500A08FD0 /* ApduCallback.swift */,
);
path = callback;
sourceTree = "<group>";
};
536C0F442C4E14C40076466F /* nfc */ = {
isa = PBXGroup;
children = (
536C0F432C4E14C40076466F /* ApduRunner.swift */,
533746542C50DC9E00A08FD0 /* ApduResponse.swift */,
53E275C62C528378002C4C3B /* RiwayatCard.swift */,
53E275C82C52E2CB002C4C3B /* Emoney.swift */,
);
path = nfc;
sourceTree = "<group>";
};
536C0F4B2C4E14C40076466F /* utils */ = {
isa = PBXGroup;
children = (
536C0F452C4E14C40076466F /* ByteArrayAndHexHelper.swift */,
538EE4B32C55147F0033EDC8 /* BrizziSamHelper.swift */,
);
path = utils;
sourceTree = "<group>";
};
536C0F532C4E14C40076466F /* api */ = {
isa = PBXGroup;
children = (
536C0F422C4E14C40076466F /* callback */,
536C0F442C4E14C40076466F /* nfc */,
536C0F4B2C4E14C40076466F /* utils */,
536C0F522C4E14C40076466F /* UnifiedNfcApi.swift */,
53E275CA2C52E4DD002C4C3B /* TapCashApi.swift */,
534964512C5472CB0063C392 /* BcaFlazzApi.swift */,
538EE4AB2C54D34A0033EDC8 /* MandiriEmoneyApi.swift */,
538EE4AD2C54FEC80033EDC8 /* JackCardApi.swift */,
538EE4AF2C5501F60033EDC8 /* MegaCashApi.swift */,
538EE4B12C550A370033EDC8 /* BrizziApi.swift */,
);
path = api;
sourceTree = "<group>";
};
536C0F572C4E14C40076466F /* extensions */ = {
isa = PBXGroup;
children = (
536C0F542C4E14C40076466F /* Array.swift */,
536C0F552C4E14C40076466F /* Data.swift */,
536C0F562C4E14C40076466F /* String.swift */,
);
path = extensions;
sourceTree = "<group>";
};
536C0F5F2C4E14C40076466F /* smartCard */ = {
isa = PBXGroup;
children = (
533746582C513DAA00A08FD0 /* CommonConstants.swift */,
536C0F5A2C4E14C40076466F /* CardErrorCodes.swift */,
539A25202C4FFA38006997BE /* EmoneyApduCommands.swift */,
53E275CC2C52E68D002C4C3B /* TapCashData.swift */,
);
path = smartCard;
sourceTree = "<group>";
};
536C0F612C4E14C40076466F /* utils */ = {
isa = PBXGroup;
children = (
8C4C930C2F82A9BC000F02ED /* Station.swift */,
8C4C93002F8226C8000F02ED /* L10n.swift */,
8C4C92F62F82167D000F02ED /* Theme.swift */,
536C0F602C4E14C40076466F /* ToastHelper.swift */,
530347102C577B1F00776F00 /* GradientView.swift */,
);
path = utils;
sourceTree = "<group>";
};
536C0F622C4E14C40076466F /* Classes */ = {
isa = PBXGroup;
children = (
536C0F532C4E14C40076466F /* api */,
536C0F572C4E14C40076466F /* extensions */,
536C0F5F2C4E14C40076466F /* smartCard */,
536C0F612C4E14C40076466F /* utils */,
);
path = Classes;
sourceTree = "<group>";
};
53E275CE2C535EF2002C4C3B /* Resources */ = {
isa = PBXGroup;
children = (
8CC07EA82F860E08009CFD0A /* header.png */,
53BDEF422C6F507E00AF4766 /* logo.png */,
53E275CF2C535F04002C4C3B /* halter.ttf */,
);
path = Resources;
sourceTree = "<group>";
};
E857A0B41B9A6DFF4C1AAD91 /* Frameworks */ = {
isa = PBXGroup;
children = (
1C0A2FB40E3C89EB399B18E5 /* Pods_Emoney_Info.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
536C0F292C4E146B0076466F /* Emoney Info */ = {
isa = PBXNativeTarget;
buildConfigurationList = 536C0F3E2C4E146D0076466F /* Build configuration list for PBXNativeTarget "Emoney Info" */;
buildPhases = (
A5C3045DA3FE995E648EAF8C /* [CP] Check Pods Manifest.lock */,
536C0F262C4E146B0076466F /* Sources */,
536C0F272C4E146B0076466F /* Frameworks */,
536C0F282C4E146B0076466F /* Resources */,
398370EEF67CDBA3C57E5750 /* [CP] Copy Pods Resources */,
EA56F63227B51D1105DA9374 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = "Emoney Info";
productName = "Emoney Info";
productReference = 536C0F2A2C4E146B0076466F /* Emoney Info.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
536C0F222C4E146B0076466F /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 1540;
LastUpgradeCheck = 1540;
TargetAttributes = {
536C0F292C4E146B0076466F = {
CreatedOnToolsVersion = 15.4;
};
};
};
buildConfigurationList = 536C0F252C4E146B0076466F /* Build configuration list for PBXProject "Emoney Info" */;
compatibilityVersion = "Xcode 14.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
id,
);
mainGroup = 536C0F212C4E146B0076466F;
productRefGroup = 536C0F2B2C4E146B0076466F /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
536C0F292C4E146B0076466F /* Emoney Info */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
536C0F282C4E146B0076466F /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
53A3AF562C58C8810072A8C8 /* Localizable.strings in Resources */,
536C0F372C4E146D0076466F /* Assets.xcassets in Resources */,
537BCE802C6C8CBD002A3127 /* AdsCell.xib in Resources */,
8CC07EA92F860E08009CFD0A /* header.png in Resources */,
536C0F3A2C4E146D0076466F /* Base in Resources */,
53E275D02C535F04002C4C3B /* halter.ttf in Resources */,
53BDEF432C6F507E00AF4766 /* logo.png in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
398370EEF67CDBA3C57E5750 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Emoney Info/Pods-Emoney Info-resources-${CONFIGURATION}-input-files.xcfilelist",
);
inputPaths = (
);
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Emoney Info/Pods-Emoney Info-resources-${CONFIGURATION}-output-files.xcfilelist",
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Emoney Info/Pods-Emoney Info-resources.sh\"\n";
showEnvVarsInLog = 0;
};
A5C3045DA3FE995E648EAF8C /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Emoney Info-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
EA56F63227B51D1105DA9374 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Emoney Info/Pods-Emoney Info-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
inputPaths = (
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Emoney Info/Pods-Emoney Info-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Emoney Info/Pods-Emoney Info-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
536C0F262C4E146B0076466F /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
536C0F712C4E14C40076466F /* UnifiedNfcApi.swift in Sources */,
533746572C512F8500A08FD0 /* ApduCallback.swift in Sources */,
536C0F652C4E14C40076466F /* ByteArrayAndHexHelper.swift in Sources */,
538EE4AC2C54D34A0033EDC8 /* MandiriEmoneyApi.swift in Sources */,
534964522C5472CB0063C392 /* BcaFlazzApi.swift in Sources */,
538EE4B42C55147F0033EDC8 /* BrizziSamHelper.swift in Sources */,
536C0F642C4E14C40076466F /* ApduRunner.swift in Sources */,
8C4C93042F825D65000F02ED /* FAQData.swift in Sources */,
8C4C93052F825D65000F02ED /* FAQViewController.swift in Sources */,
53E275CD2C52E68D002C4C3B /* TapCashData.swift in Sources */,
530347112C577B1F00776F00 /* GradientView.swift in Sources */,
8C4C92FB2F821877000F02ED /* HomeView.swift in Sources */,
53A3AF4E2C58A7620072A8C8 /* AboutViewController.swift in Sources */,
53E275CB2C52E4DD002C4C3B /* TapCashApi.swift in Sources */,
8C4C92FF2F821A04000F02ED /* HistoryView.swift in Sources */,
536C0F722C4E14C40076466F /* Array.swift in Sources */,
536C0F732C4E14C40076466F /* Data.swift in Sources */,
8C4C93012F8226C8000F02ED /* L10n.swift in Sources */,
53E275C72C528378002C4C3B /* RiwayatCard.swift in Sources */,
8C4C92F92F82175A000F02ED /* MainTabView.swift in Sources */,
8C4C92FD2F821943000F02ED /* SettingsView.swift in Sources */,
533746592C513DAA00A08FD0 /* CommonConstants.swift in Sources */,
538EE4B02C5501F60033EDC8 /* MegaCashApi.swift in Sources */,
8C4C93082F8271AE000F02ED /* PrivacyPolicyViewController.swift in Sources */,
8C4C93092F8271AE000F02ED /* TermsViewController.swift in Sources */,
536C0F2E2C4E146B0076466F /* AppDelegate.swift in Sources */,
533746552C50DC9E00A08FD0 /* ApduResponse.swift in Sources */,
8C4C92F72F82167D000F02ED /* Theme.swift in Sources */,
536C0F7B2C4E14C40076466F /* ToastHelper.swift in Sources */,
539A25212C4FFA38006997BE /* EmoneyApduCommands.swift in Sources */,
536C0F762C4E14C40076466F /* CardErrorCodes.swift in Sources */,
53E275C92C52E2CB002C4C3B /* Emoney.swift in Sources */,
538EE4AE2C54FEC80033EDC8 /* JackCardApi.swift in Sources */,
536C0F742C4E14C40076466F /* String.swift in Sources */,
538EE4B22C550A370033EDC8 /* BrizziApi.swift in Sources */,
536C0F302C4E146B0076466F /* SceneDelegate.swift in Sources */,
8C4C930D2F82A9BC000F02ED /* Station.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
536C0F382C4E146D0076466F /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
536C0F392C4E146D0076466F /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
53A3AF542C58C8810072A8C8 /* Localizable.strings */ = {
isa = PBXVariantGroup;
children = (
53A3AF552C58C8810072A8C8 /* en */,
53A3AF572C58C8BE0072A8C8 /* id */,
);
name = Localizable.strings;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
536C0F3C2C4E146D0076466F /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ALLOW_ENTITLEMENTS_MODIFICATION = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 17.5;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
536C0F3D2C4E146D0076466F /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ALLOW_ENTITLEMENTS_MODIFICATION = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 17.5;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
536C0F3F2C4E146D0076466F /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = CFD118D4B349F6A8169BA732 /* Pods-Emoney Info.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = "Emoney Info/Emoney Info.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 10;
DEVELOPMENT_TEAM = 6S5573WXX4;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "Emoney Info/Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = "Emoney Info";
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
INFOPLIST_KEY_NFCReaderUsageDescription = "Aplikasi memerlukan akses NFC untuk memindai kartu uang elektronik Anda (e-money, Flazz, dll.). This app requires NFC access to scan your cards and display your balance locally.";
INFOPLIST_KEY_NSUserTrackingUsageDescription = "Data Anda akan digunakan untuk menampilkan iklan yang relevan. This data is used to provide a personalized ad experience.";
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
INFOPLIST_KEY_UIRequiredDeviceCapabilities = nfc;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = UIInterfaceOrientationPortrait;
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.1;
PRODUCT_BUNDLE_IDENTIFIER = com.iiyh.emoneyinfo;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 1;
};
name = Debug;
};
536C0F402C4E146D0076466F /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = B03D5F72678ED8DD7DFE3F40 /* Pods-Emoney Info.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = "Emoney Info/Emoney Info.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 10;
DEVELOPMENT_TEAM = 6S5573WXX4;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "Emoney Info/Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = "Emoney Info";
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
INFOPLIST_KEY_NFCReaderUsageDescription = "Aplikasi memerlukan akses NFC untuk memindai kartu uang elektronik Anda (e-money, Flazz, dll.). This app requires NFC access to scan your cards and display your balance locally.";
INFOPLIST_KEY_NSUserTrackingUsageDescription = "Data Anda akan digunakan untuk menampilkan iklan yang relevan. This data is used to provide a personalized ad experience.";
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
INFOPLIST_KEY_UIRequiredDeviceCapabilities = nfc;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = UIInterfaceOrientationPortrait;
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.1;
PRODUCT_BUNDLE_IDENTIFIER = com.iiyh.emoneyinfo;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 1;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
536C0F252C4E146B0076466F /* Build configuration list for PBXProject "Emoney Info" */ = {
isa = XCConfigurationList;
buildConfigurations = (
536C0F3C2C4E146D0076466F /* Debug */,
536C0F3D2C4E146D0076466F /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
536C0F3E2C4E146D0076466F /* Build configuration list for PBXNativeTarget "Emoney Info" */ = {
isa = XCConfigurationList;
buildConfigurations = (
536C0F3F2C4E146D0076466F /* Debug */,
536C0F402C4E146D0076466F /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 536C0F222C4E146B0076466F /* Project object */;
}

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Emoney Info.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@ -0,0 +1,366 @@
// AboutViewController.swift
// Emoney Info
import UIKit
class AboutViewController: UIViewController {
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = Theme.Color.background
setupUI()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.setNavigationBarHidden(true, animated: animated)
(tabBarController as? MainTabBarController)?.setTabBarHidden(true, animated: animated)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
(tabBarController as? MainTabBarController)?.setTabBarHidden(false, animated: animated)
}
// MARK: - Setup
private func setupUI() {
let scrollView = UIScrollView()
let contentView = UIView()
scrollView.showsVerticalScrollIndicator = false
scrollView.translatesAutoresizingMaskIntoConstraints = false
contentView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(scrollView)
scrollView.addSubview(contentView)
NSLayoutConstraint.activate([
scrollView.topAnchor.constraint(equalTo: view.topAnchor),
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
contentView.topAnchor.constraint(equalTo: scrollView.topAnchor),
contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
contentView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor),
])
// MARK: Back button + nav title
let backButton = UIButton(type: .system)
let chevron = UIImage(systemName: "chevron.left",
withConfiguration: UIImage.SymbolConfiguration(pointSize: 16, weight: .semibold))
backButton.setImage(chevron, for: .normal)
backButton.tintColor = Theme.Color.textPrimary
backButton.addTarget(self, action: #selector(backTapped), for: .touchUpInside)
backButton.translatesAutoresizingMaskIntoConstraints = false
let navTitleLabel = UILabel()
navTitleLabel.text = L10n.aboutAppTitle
navTitleLabel.font = Theme.Font.caption(weight: .semibold)
navTitleLabel.textColor = Theme.Color.textSecondary
navTitleLabel.translatesAutoresizingMaskIntoConstraints = false
// MARK: App icon
let iconView = UIImageView(image: UIImage(named: "AppLogo"))
iconView.contentMode = .scaleAspectFit
iconView.layer.cornerRadius = 24
iconView.clipsToBounds = true
iconView.layer.shadowColor = UIColor.black.cgColor
iconView.layer.shadowOpacity = 0.12
iconView.layer.shadowOffset = CGSize(width: 0, height: 4)
iconView.layer.shadowRadius = 12
iconView.translatesAutoresizingMaskIntoConstraints = false
// MARK: App name + version
let appNameLabel = UILabel()
appNameLabel.text = "Emoney Info"
appNameLabel.font = .systemFont(ofSize: 28, weight: .bold)
appNameLabel.textColor = Theme.Color.textPrimary
appNameLabel.textAlignment = .center
appNameLabel.translatesAutoresizingMaskIntoConstraints = false
let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? ""
let versionLabel = UILabel()
let versionText = "VERSI \(appVersion)"
let versionAttr = NSAttributedString(string: versionText, attributes: [
.kern: 1.5,
.font: Theme.Font.caption(weight: .semibold),
.foregroundColor: Theme.Color.textSecondary,
])
versionLabel.attributedText = versionAttr
versionLabel.textAlignment = .center
versionLabel.translatesAutoresizingMaskIntoConstraints = false
// MARK: Description
let descLabel = UILabel()
descLabel.text = L10n.aboutAppDescription
descLabel.font = Theme.Font.body(weight: .regular)
descLabel.textColor = Theme.Color.textSecondary
descLabel.numberOfLines = 0
descLabel.textAlignment = .center
descLabel.translatesAutoresizingMaskIntoConstraints = false
// MARK: Feature chips
let chipsStack = UIStackView(arrangedSubviews: [
makeFeatureChip(L10n.aboutChipNfc),
makeFeatureChip(L10n.aboutChipRealtime),
makeFeatureChip(L10n.aboutChipMulti),
])
chipsStack.axis = .horizontal
chipsStack.spacing = 10
chipsStack.alignment = .center
chipsStack.translatesAutoresizingMaskIntoConstraints = false
// MARK: Legal rows card
let legalCard = makeCard()
let termsRow = makeLegalRow(icon: "doc.text", title: L10n.aboutTerms)
termsRow.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(termsTapped)))
termsRow.isUserInteractionEnabled = true
let separator = UIView()
separator.backgroundColor = Theme.Color.background
separator.translatesAutoresizingMaskIntoConstraints = false
separator.heightAnchor.constraint(equalToConstant: 1).isActive = true
let privacyRow = makeLegalRow(icon: "lock.shield", title: L10n.aboutPrivacy)
privacyRow.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(privacyTapped)))
privacyRow.isUserInteractionEnabled = true
let legalStack = UIStackView(arrangedSubviews: [termsRow, separator, privacyRow])
legalStack.axis = .vertical
legalStack.spacing = 0
legalStack.translatesAutoresizingMaskIntoConstraints = false
legalCard.addSubview(legalStack)
// MARK: Connect card (teal gradient)
let connectCard = UIView()
connectCard.layer.cornerRadius = 20
connectCard.clipsToBounds = true
connectCard.translatesAutoresizingMaskIntoConstraints = false
let gradientLayer = CAGradientLayer()
gradientLayer.colors = [
Theme.Color.primary.cgColor,
Theme.Color.secondary.cgColor,
]
gradientLayer.startPoint = CGPoint(x: 0, y: 0)
gradientLayer.endPoint = CGPoint(x: 1, y: 1)
connectCard.layer.insertSublayer(gradientLayer, at: 0)
let connectIcon = UIImageView(image: UIImage(systemName: "wave.3.right.circle.fill",
withConfiguration: UIImage.SymbolConfiguration(pointSize: 32, weight: .medium)))
connectIcon.tintColor = UIColor.white.withAlphaComponent(0.35)
connectIcon.contentMode = .scaleAspectFit
connectIcon.translatesAutoresizingMaskIntoConstraints = false
let connectTitle = UILabel()
connectTitle.text = L10n.aboutConnectTitle
connectTitle.font = Theme.Font.subtitle(weight: .bold)
connectTitle.textColor = .white
connectTitle.numberOfLines = 0
connectTitle.translatesAutoresizingMaskIntoConstraints = false
let connectDesc = UILabel()
connectDesc.text = L10n.aboutConnectDesc
connectDesc.font = Theme.Font.caption(weight: .regular)
connectDesc.textColor = UIColor.white.withAlphaComponent(0.85)
connectDesc.numberOfLines = 0
connectDesc.translatesAutoresizingMaskIntoConstraints = false
[connectIcon, connectTitle, connectDesc].forEach { connectCard.addSubview($0) }
// MARK: Copyright
let copyrightLabel = UILabel()
copyrightLabel.text = L10n.footerCopyright
copyrightLabel.font = Theme.Font.caption(weight: .regular)
copyrightLabel.textColor = Theme.Color.textSecondary
copyrightLabel.textAlignment = .center
copyrightLabel.translatesAutoresizingMaskIntoConstraints = false
// MARK: Add to contentView
[backButton, navTitleLabel, iconView, appNameLabel, versionLabel,
descLabel, chipsStack, legalCard, connectCard, copyrightLabel]
.forEach { contentView.addSubview($0) }
NSLayoutConstraint.activate([
// Nav row
backButton.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 56),
backButton.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
backButton.widthAnchor.constraint(equalToConstant: 32),
backButton.heightAnchor.constraint(equalToConstant: 32),
navTitleLabel.centerYAnchor.constraint(equalTo: backButton.centerYAnchor),
navTitleLabel.leadingAnchor.constraint(equalTo: backButton.trailingAnchor, constant: 8),
// Icon
iconView.topAnchor.constraint(equalTo: backButton.bottomAnchor, constant: 28),
iconView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
iconView.widthAnchor.constraint(equalToConstant: 96),
iconView.heightAnchor.constraint(equalToConstant: 96),
// Name + version
appNameLabel.topAnchor.constraint(equalTo: iconView.bottomAnchor, constant: 16),
appNameLabel.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
versionLabel.topAnchor.constraint(equalTo: appNameLabel.bottomAnchor, constant: 6),
versionLabel.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
// Desc
descLabel.topAnchor.constraint(equalTo: versionLabel.bottomAnchor, constant: 16),
descLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 32),
descLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -32),
// Chips
chipsStack.topAnchor.constraint(equalTo: descLabel.bottomAnchor, constant: 20),
chipsStack.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
// Legal card
legalCard.topAnchor.constraint(equalTo: chipsStack.bottomAnchor, constant: 28),
legalCard.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 24),
legalCard.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -24),
legalStack.topAnchor.constraint(equalTo: legalCard.topAnchor, constant: 4),
legalStack.leadingAnchor.constraint(equalTo: legalCard.leadingAnchor),
legalStack.trailingAnchor.constraint(equalTo: legalCard.trailingAnchor),
legalStack.bottomAnchor.constraint(equalTo: legalCard.bottomAnchor, constant: -4),
// Connect card
connectCard.topAnchor.constraint(equalTo: legalCard.bottomAnchor, constant: 20),
connectCard.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 24),
connectCard.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -24),
connectIcon.topAnchor.constraint(equalTo: connectCard.topAnchor, constant: 20),
connectIcon.trailingAnchor.constraint(equalTo: connectCard.trailingAnchor, constant: -20),
connectIcon.widthAnchor.constraint(equalToConstant: 44),
connectIcon.heightAnchor.constraint(equalToConstant: 44),
connectTitle.topAnchor.constraint(equalTo: connectCard.topAnchor, constant: 24),
connectTitle.leadingAnchor.constraint(equalTo: connectCard.leadingAnchor, constant: 20),
connectTitle.trailingAnchor.constraint(equalTo: connectIcon.leadingAnchor, constant: -12),
connectDesc.topAnchor.constraint(equalTo: connectTitle.bottomAnchor, constant: 10),
connectDesc.leadingAnchor.constraint(equalTo: connectCard.leadingAnchor, constant: 20),
connectDesc.trailingAnchor.constraint(equalTo: connectCard.trailingAnchor, constant: -20),
connectDesc.bottomAnchor.constraint(equalTo: connectCard.bottomAnchor, constant: -24),
// Copyright
copyrightLabel.topAnchor.constraint(equalTo: connectCard.bottomAnchor, constant: 24),
copyrightLabel.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
copyrightLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -32),
])
// Gradient frame set after layout
DispatchQueue.main.async {
gradientLayer.frame = connectCard.bounds
}
}
// MARK: - Helpers
private func makeCard() -> UIView {
let v = UIView()
v.backgroundColor = Theme.Color.card
v.layer.cornerRadius = 16
v.layer.shadowColor = UIColor.black.cgColor
v.layer.shadowOpacity = 0.06
v.layer.shadowOffset = CGSize(width: 0, height: 2)
v.layer.shadowRadius = 8
v.translatesAutoresizingMaskIntoConstraints = false
return v
}
private func makeFeatureChip(_ title: String) -> UIView {
let container = UIView()
container.backgroundColor = Theme.Color.primary.withAlphaComponent(0.12)
container.layer.cornerRadius = 12
container.translatesAutoresizingMaskIntoConstraints = false
let label = UILabel()
label.text = title
label.font = Theme.Font.caption(weight: .semibold)
label.textColor = Theme.Color.secondary
label.translatesAutoresizingMaskIntoConstraints = false
container.addSubview(label)
NSLayoutConstraint.activate([
label.topAnchor.constraint(equalTo: container.topAnchor, constant: 6),
label.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 12),
label.trailingAnchor.constraint(equalTo: container.trailingAnchor, constant: -12),
label.bottomAnchor.constraint(equalTo: container.bottomAnchor, constant: -6),
])
return container
}
private func makeLegalRow(icon: String, title: String) -> UIView {
let row = UIView()
row.translatesAutoresizingMaskIntoConstraints = false
let iconContainer = UIView()
iconContainer.backgroundColor = Theme.Color.background
iconContainer.layer.cornerRadius = 10
iconContainer.translatesAutoresizingMaskIntoConstraints = false
let iconView = UIImageView(image: UIImage(systemName: icon,
withConfiguration: UIImage.SymbolConfiguration(pointSize: 14, weight: .medium)))
iconView.tintColor = Theme.Color.secondary
iconView.contentMode = .scaleAspectFit
iconView.translatesAutoresizingMaskIntoConstraints = false
iconContainer.addSubview(iconView)
let titleLabel = UILabel()
titleLabel.text = title
titleLabel.font = Theme.Font.body(weight: .medium)
titleLabel.textColor = Theme.Color.textPrimary
titleLabel.translatesAutoresizingMaskIntoConstraints = false
let chevronImg = UIImage(systemName: "chevron.right",
withConfiguration: UIImage.SymbolConfiguration(pointSize: 12, weight: .semibold))
let chevron = UIImageView(image: chevronImg)
chevron.tintColor = Theme.Color.textSecondary
chevron.translatesAutoresizingMaskIntoConstraints = false
[iconContainer, titleLabel, chevron].forEach { row.addSubview($0) }
NSLayoutConstraint.activate([
iconContainer.widthAnchor.constraint(equalToConstant: 36),
iconContainer.heightAnchor.constraint(equalToConstant: 36),
iconView.centerXAnchor.constraint(equalTo: iconContainer.centerXAnchor),
iconView.centerYAnchor.constraint(equalTo: iconContainer.centerYAnchor),
iconView.widthAnchor.constraint(equalToConstant: 18),
iconView.heightAnchor.constraint(equalToConstant: 18),
iconContainer.leadingAnchor.constraint(equalTo: row.leadingAnchor, constant: 16),
iconContainer.centerYAnchor.constraint(equalTo: row.centerYAnchor),
titleLabel.leadingAnchor.constraint(equalTo: iconContainer.trailingAnchor, constant: 14),
titleLabel.centerYAnchor.constraint(equalTo: row.centerYAnchor),
titleLabel.trailingAnchor.constraint(lessThanOrEqualTo: chevron.leadingAnchor, constant: -8),
chevron.trailingAnchor.constraint(equalTo: row.trailingAnchor, constant: -16),
chevron.centerYAnchor.constraint(equalTo: row.centerYAnchor),
row.heightAnchor.constraint(equalToConstant: 60),
])
return row
}
// MARK: - Actions
@objc private func backTapped() {
navigationController?.popViewController(animated: true)
}
@objc private func termsTapped() {
let vc = TermsViewController()
navigationController?.pushViewController(vc, animated: true)
}
@objc private func privacyTapped() {
let vc = PrivacyPolicyViewController()
navigationController?.pushViewController(vc, animated: true)
}
}

24
Emoney Info/AdsCell.xib Executable file
View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22685"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" rowHeight="181" id="KGk-i7-Jjw" customClass="AdsCell" customModule="Emoney_Info" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="468" height="181"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM">
<rect key="frame" x="0.0" y="0.0" width="468" height="181"/>
<autoresizingMask key="autoresizingMask"/>
</tableViewCellContentView>
<viewLayoutGuide key="safeArea" id="njF-e1-oar"/>
<point key="canvasLocation" x="164.8854961832061" y="35.563380281690144"/>
</tableViewCell>
</objects>
</document>

33
Emoney Info/AppDelegate.swift Executable file
View File

@ -0,0 +1,33 @@
//
// AppDelegate.swift
// Emoney Info
//
// Created by Wira Irawan on 22/07/24.
//
import UIKit
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
return true
}
// MARK: UISceneSession Lifecycle
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}
}

View File

@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,14 @@
{
"images" : [
{
"filename" : "logos.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "logos.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "background.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

View File

@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "32.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "64.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "96.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,89 @@
{
"images" : [
{
"filename" : "info.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "light"
}
],
"filename" : "info 2.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "info 1.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "info@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "light"
}
],
"filename" : "info@2x 2.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "info@2x 1.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "info@3x.png",
"idiom" : "universal",
"scale" : "3x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "light"
}
],
"filename" : "info@3x 2.png",
"idiom" : "universal",
"scale" : "3x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "info@3x 1.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

View File

@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "32.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "64.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "96.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,89 @@
{
"images" : [
{
"filename" : "payment_black 1.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "light"
}
],
"filename" : "payment_black.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "payment_white.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "payment_black@2x 1.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "light"
}
],
"filename" : "payment_black@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "payment_white@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "payment_black@3x 1.png",
"idiom" : "universal",
"scale" : "3x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "light"
}
],
"filename" : "payment_black@3x.png",
"idiom" : "universal",
"scale" : "3x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "payment_white@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "32.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "64.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "96.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,89 @@
{
"images" : [
{
"filename" : "gear.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "light"
}
],
"filename" : "gear 1.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "gear 2.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "gear@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "light"
}
],
"filename" : "gear@2x 1.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "gear@2x 2.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "gear@3x.png",
"idiom" : "universal",
"scale" : "3x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "light"
}
],
"filename" : "gear@3x 1.png",
"idiom" : "universal",
"scale" : "3x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "gear@3x 2.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "simcard.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -0,0 +1,89 @@
{
"images" : [
{
"filename" : "creditcard_black 1.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "light"
}
],
"filename" : "creditcard_black.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "creditcard.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "creditcard_black@2x 1.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "light"
}
],
"filename" : "creditcard_black@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "creditcard@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "creditcard_black@3x 1.png",
"idiom" : "universal",
"scale" : "3x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "light"
}
],
"filename" : "creditcard_black@3x.png",
"idiom" : "universal",
"scale" : "3x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "creditcard@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 804 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

18
Emoney Info/BRIEF.md Normal file
View File

@ -0,0 +1,18 @@
# emoneyInfo - UI Brief
Reference in Design folder
## App Description
E-Money wallet app untuk cek saldo kartu e-money via NFC.
## Design System
- Primary color: Green (#7AD4D1)
- Success/Active: Green (#5D7D7B)
- Background: Light gray (#F3F3F8)
- Cards: White with rounded corners (16pt)
- Font: System font (San Francisco)
## Screens
1. Home - Saldo, NFC tap, promo, last transaction
2. History - List semua transaksi
3. Settings - Account settings, preferences
## Tech Stack
- SwiftUI
- Core NFC (existing)
- iOS 16+

View File

@ -0,0 +1,63 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22685"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Read Card Balance" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="f2o-LW-8aE">
<rect key="frame" x="105.66666666666669" y="296.66666666666669" width="182" height="28"/>
<fontDescription key="fontDescription" type="system" weight="thin" pointSize="23"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="logo.png" translatesAutoresizingMaskIntoConstraints="NO" id="eG8-3f-M4u">
<rect key="frame" x="76.666666666666686" y="374.66666666666669" width="240" height="128"/>
<constraints>
<constraint firstAttribute="width" constant="240" id="ZNH-31-9ZB"/>
<constraint firstAttribute="height" constant="128" id="hxE-7A-H81"/>
</constraints>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Emoney Info" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Qay-HP-vMh">
<rect key="frame" x="142.66666666666666" y="522.66666666666663" width="108" height="25.333333333333371"/>
<fontDescription key="fontDescription" type="system" weight="thin" pointSize="21"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstItem="eG8-3f-M4u" firstAttribute="centerY" secondItem="6Tk-OE-BBY" secondAttribute="centerY" id="dfD-yn-ABp"/>
<constraint firstItem="eG8-3f-M4u" firstAttribute="top" secondItem="f2o-LW-8aE" secondAttribute="bottom" constant="50" id="gfi-Qk-O6g"/>
<constraint firstItem="Qay-HP-vMh" firstAttribute="top" secondItem="eG8-3f-M4u" secondAttribute="bottom" constant="20" id="nwU-fJ-2bF"/>
<constraint firstItem="eG8-3f-M4u" firstAttribute="centerX" secondItem="6Tk-OE-BBY" secondAttribute="centerX" id="sXh-i2-W2n"/>
<constraint firstItem="Qay-HP-vMh" firstAttribute="centerX" secondItem="6Tk-OE-BBY" secondAttribute="centerX" id="xVm-AF-t8N"/>
<constraint firstItem="f2o-LW-8aE" firstAttribute="centerX" secondItem="6Tk-OE-BBY" secondAttribute="centerX" id="yLk-em-nQ2"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="logo.png" width="616.5" height="395.25"/>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
</resources>
</document>

View 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)
}
}
}

View 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)
}
}
}

View 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)
}
}
}

View 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
}
}

View 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)
}
}
}

View 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()
}
}

View 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")
}
})
}
}

View 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)
}

View 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!
}
}

View 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)
}
}
}

View 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!
}
}

View 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
// }
}

View 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)
}
}

View 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
}
}

View File

@ -0,0 +1,14 @@
import Foundation
extension Array {
subscript (range r: Range<Int>) -> Array {
return Array(self[r])
}
subscript (range r: ClosedRange<Int>) -> Array {
return Array(self[r])
}
}

View File

@ -0,0 +1,35 @@
import Foundation
import UIKit
extension Data {
public var bytes: [UInt8] {
return [UInt8](self)
}
func hexEncodedString() -> String {
return map { String(format: "%02hhx", $0) }.joined()
}
func makeDigitalString() -> String {
//todo: check numeric
var s: String = ""
for i in 0...bytes.count-1 {
if (bytes[i] >= 0 && bytes[i] <= 9) {
s += String(bytes[i])
}
}
return s
}
}
@available(iOS 14.0, *)
extension UISwitch {
func setOnValueChangeListener(onValueChanged :@escaping () -> Void){
self.addAction(UIAction(){ action in
onValueChanged()
}, for: .valueChanged)
}
}

View File

@ -0,0 +1,261 @@
import Foundation
extension StringProtocol {
func substring<S: StringProtocol>(from start: S, options: String.CompareOptions = []) -> SubSequence? {
guard let lower = range(of: start, options: options)?.upperBound
else { return nil }
return self[lower...]
}
func substring<S: StringProtocol, T: StringProtocol>(from start: S, to end: T, options: String.CompareOptions = []) -> SubSequence? {
guard let lower = range(of: start, options: options)?.upperBound,
let upper = self[lower...].range(of: end, options: options)?.lowerBound
else { return nil }
return self[lower..<upper]
}
func index(from: Int) -> Index {
return self.index(startIndex, offsetBy: from)
}
func substring(with r: Range<Int>) -> String {
let startIndex = index(from: r.lowerBound)
let endIndex = index(from: r.upperBound)
return String(self[startIndex..<endIndex])
}
func hex2decimal() -> Int {
Int(self, radix: 16)!
}
func hex2bin() -> String {
return String(Int(self, radix: 16)!, radix: 2)
}
func bin2decimal() -> Int {
return Int(self, radix: 2)!
}
func bin2hex() -> String {
return String(Int(self, radix: 2)!, radix: 16)
}
func secondComplementsAmount() -> Int{
let bin = self.hex2bin()
let characters = Array(bin)
var firstComplement = ""
for c in characters {
if (c == "0"){
firstComplement.append("1")
} else {
firstComplement.append("0")
}
}
let flipped = Array(firstComplement)
var isFinish = false
var secondComplement = ""
for index in stride(from: firstComplement.count - 1, through: 0, by: -1) {
let c = flipped[index]
if (!isFinish){
if (c == "0"){
secondComplement.append("1")
isFinish = true
} else {
secondComplement.append("0")
}
} else {
secondComplement.append(c)
}
}
let second = Array(secondComplement)
var flipback = ""
for index in stride(from: secondComplement.count - 1, through: 0, by: -1) {
let c = second[index]
flipback.append(c)
}
let binhex = flipback.bin2hex()
return binhex.hex2decimal()
}
func formatCardNumber() -> String {
let chars = Array(self)
var cardNumber = ""
for i in 0..<chars.count {
cardNumber.append(chars[i])
if (i == 3) {
cardNumber.append(" ")
} else if (i == 7){
cardNumber.append(" ")
} else if (i == 11){
cardNumber.append(" ")
}
}
return cardNumber
}
func formatMaskedCardNumber() -> String {
let chars = Array(self)
var cardNumber = ""
for i in 0..<chars.count {
if (i == 3) {
cardNumber.append(chars[i])
cardNumber.append(" ")
} else if (i == 7){
cardNumber.append(chars[i])
cardNumber.append(" ")
} else if (i == 8){
cardNumber.append("*")
} else if (i == 9){
cardNumber.append("*")
} else if (i == 10){
cardNumber.append("*")
} else if (i == 11){
cardNumber.append("*")
cardNumber.append(" ")
} else {
cardNumber.append(chars[i])
}
}
return cardNumber
}
}
extension Int {
func decimal2bin() -> String {
return String(self, radix: 2)
}
func decimal2hex() -> String {
return String(self, radix: 16)
}
var bin: String {
String(self, radix: 2).leftPad(with: "0", length: 8)
}
}
extension UInt8 {
var bin: String {
String(self, radix: 2).leftPad(with: "0", length: 8)
}
}
extension [UInt8] {
func hexString() -> String {
return map { String(format: "%02hhx", $0) }.joined()
}
}
extension String {
// static func hexString(_ data: Data) -> String {
// return data.map { String(format: "%02x", $0) }.joined()
// }
// static func hex2byte(_ str: String) -> Data {
// var data = Data()
// var tempStr = str
// while tempStr.count > 0 {
// let c = String(tempStr.prefix(2))
// tempStr = String(tempStr.dropFirst(2))
// if let num = UInt8(c, radix: 16) {
// data.append(num)
// }
// }
// return data
// }
func hex2byte() -> Data {
var data = Data()
var tempStr = self
while tempStr.count > 0 {
let c = String(tempStr.prefix(2))
tempStr = String(tempStr.dropFirst(2))
if let num = UInt8(c, radix: 16) {
data.append(num)
}
}
return data
}
func subString(from: Int, to: Int) -> String {
let startIndex = self.index(self.startIndex, offsetBy: from)
let endIndex = self.index(self.startIndex, offsetBy: to)
return String(self[startIndex..<endIndex])
}
func leftPad(with character: Character, length: UInt) -> String {
let maxLength = Int(length) - count
guard maxLength > 0 else {
return self
}
return String(repeating: String(character), count: maxLength) + self
}
func stringToBytes() -> [UInt8]? {
let length = self.count
if length & 1 != 0 {
return nil
}
var bytes = [UInt8]()
bytes.reserveCapacity(length/2)
var index = self.startIndex
for _ in 0..<length/2 {
let nextIndex = self.index(index, offsetBy: 2)
if let b = UInt8(self[index..<nextIndex], radix: 16) {
bytes.append(b)
} else {
return nil
}
index = nextIndex
}
return bytes
}
func localizeString(string: String) -> String {
let path = Bundle.main.path(forResource: string, ofType: "lproj")
let bundle = Bundle(path: path!)
return NSLocalizedString(self, tableName: nil, bundle: bundle!,
value: "", comment: "")
}
}
extension String: LocalizedError {
static let numbers: Set<Character> = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
static let hexSymbols: Set<Character> = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f", "A", "B", "C", "D", "E", "F" ]
public var errorDescription: String? { return self }
var isNumeric: Bool {
guard self.count > 0 else { return false }
return Set(self).isSubset(of: String.numbers)
}
var isHex: Bool {
guard self.count > 0 && self.count % 2 == 0 else { return false }
return Set(self).isSubset(of: String.hexSymbols)
}
subscript(_ range: CountableRange<Int>) -> String {
let start = index(startIndex, offsetBy: max(0, range.lowerBound))
let end = index(start, offsetBy: min(self.count - range.lowerBound,
range.upperBound - range.lowerBound))
return String(self[start..<end])
}
subscript(_ range: CountablePartialRangeFrom<Int>) -> String {
let start = index(startIndex, offsetBy: max(0, range.lowerBound))
return String(self[start...])
}
func deletingPrefix(_ prefix: String) -> String {
guard self.hasPrefix(prefix) else { return self }
return String(self.dropFirst(prefix.count))
}
func indexInt(of char: Character) -> Int? {
return firstIndex(of: char)?.utf16Offset(in: self)
}
}

View File

@ -0,0 +1,167 @@
/*
* Copyright 2018-2020 TON DEV SOLUTIONS LTD.
*
* Licensed under the SOFTWARE EVALUATION License (the "License"); you may not use
* this file except in compliance with the License.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific TON DEV software governing permissions and
* limitations under the License.
*/
import Foundation
class CardErrorCodes {
/* Standard status words that may be returned by any Java card */
static let SW_SUCCESS :UInt16 = 0x9000;
static let SW_WRONG_LENGTH :UInt16 = 0x6700;
static let SW_APPLET_SELECT_FAILED :UInt16 = 0x6999;
static let SW_RESPONSE_BYTES_REMAINING :UInt16 = 0x6100;
static let SW_CLA_NOT_SUPPORTED :UInt16 = 0x6E00;
static let SW_COMMAND_CHAINING_NOT_SUPPORTED :UInt16 = 0x6884;
static let SW_COMMAND_NOT_ALLOWED :UInt16 = 0x6986;
static let SW_CONDITIONS_OF_USE_NOT_SATISFIED :UInt16 = 0x6985;
static let SW_CORRECT_EXPECTED_LENGTH :UInt16 = 0x6C00;
static let SW_DATA_INVALID :UInt16 = 0x6984;
static let SW_NOT_ENOUGH_MEMORY_SPACE_IN_FILE :UInt16 = 0x6A84;
static let SW_FILE_INVALID :UInt16 = 0x6983;
static let SW_FILE_NOT_FOUND :UInt16 = 0x6A82;
static let SW_FUNCTION_NOT_SUPPORTED :UInt16 = 0x6A81;
static let SW_INCORRECT_P1_P2 :UInt16 = 0x6A86;
static let SW_INS_NOT_SUPPORTED :UInt16 = 0x6D00;
/*static let SW_LAST_COMMAND_IN_CHAIN_EXPECTED :UInt16 = 0x6883;*/
static let SW_LOGICAL_CHANNEL_NOT_SUPPORTED :UInt16 = 0x6881;
static let SW_RECORD_NOT_FOUND :UInt16 = 0x6883;
static let SW_SECURE_MESSAGING_NOT_SUPPORTED :UInt16 = 0x6882;
static let SW_SECURITY_CONDITION_NOT_SATISFIED :UInt16 = 0x6982;
static let SW_COMMAND_ABORTED :UInt16 = 0x6F00;
static let SW_WRONG_DATA :UInt16 = 0x6A80;
static let SW_WRONG_P1_P2 :UInt16 = 0x6B00;
/* Status words that may be returned by TonWalletApplet */
// Common errors
static let SW_INTERNAL_BUFFER_IS_NULL_OR_TOO_SMALL :UInt16 = 0x4F00;
static let SW_PERSONALIZATION_NOT_FINISHED :UInt16 = 0x4F01;
static let SW_INCORRECT_OFFSET :UInt16 = 0x4F02;
static let SW_INCORRECT_PAYLOAD :UInt16 = 0x4F03;
// Password authentication errors
static let SW_INCORRECT_PASSWORD_FOR_CARD_AUTHENICATION :UInt16 = 0x5F00;
static let SW_INCORRECT_PASSWORD_CARD_IS_BLOCKED :UInt16 = 0x5F01;
// Signature errors
static let SW_SET_COIN_TYPE_FAILED :UInt16 = 0x6F01;
static let SW_SET_CURVE_FAILED :UInt16 = 0x6F02;
static let SW_GET_COIN_PUB_DATA_FAILED :UInt16 = 0x6F03;
static let SW_SIGN_DATA_FAILED :UInt16 = 0x6F04;
// Pin verification errors
static let SW_COIN_MANAGER_INCORRECT_PIN :UInt16 = 0x9B01;
static let SW_COIN_MANAGER_UPDATE_PIN_ERROR :UInt16 = 0x9B02;
// static let SW_PIN_TRIES_EXPIRED :UInt16 = 0x9F08;
static let SW_INCORRECT_PIN :UInt16 = 0x6F07;
static let SW_PIN_TRIES_EXPIRED :UInt16 = 0x6F08;
static let SW_LOAD_SEED_ERROR :UInt16 = 0x9F03;
// Key chain errors
static let SW_INCORRECT_KEY_INDEX :UInt16 = 0x7F00;
static let SW_INCORRECT_KEY_CHUNK_START_OR_LEN :UInt16 = 0x7F01;
static let SW_INCORRECT_KEY_CHUNK_LEN :UInt16 = 0x7F02;
static let SW_NOT_ENOUGH_SPACE :UInt16 = 0x7F03;
static let SW_KEY_SIZE_UNKNOWN :UInt16 = 0x7F04;
static let SW_KEY_LEN_INCORRECT :UInt16 = 0x7F05;
static let SW_HMAC_EXISTS :UInt16 = 0x7F06;
static let SW_INCORRECT_KEY_INDEX_TO_CHANGE :UInt16 = 0x7F07;
static let SW_MAX_KEYS_NUMBER_EXCEEDED :UInt16 = 0x7F08;
static let SW_DELETE_KEY_CHUNK_IS_NOT_FINISHED :UInt16 = 0x7F09;
// Hmac errors
static let SW_INCORRECT_SAULT :UInt16 = 0x8F01;
static let SW_DATA_INTEGRITY_CORRUPTED :UInt16 = 0x8F02;
static let SW_INCORRECT_APDU_HMAC :UInt16 = 0x8F03;
static let SW_HMAC_VERIFICATION_TRIES_EXPIRED :UInt16 = 0x8F04;
// Recovery errors
static let SW_RECOVERY_DATA_TOO_LONG :UInt16 = 0x6F09;
static let SW_INCORRECT_START_POS_OR_LE :UInt16 = 0x6F0A;
static let SW_INTEGRITY_OF_RECOVERY_DATA_CORRUPTED :UInt16 = 0x6F0B;
static let SW_RECOVERY_DATA_ALREADY_EXISTS :UInt16 = 0x6F0C;
static let SW_RECOVERY_DATA_IS_NOT_SET:UInt16 = 0x6F0D;
static func convertSw1Sw2IntoOneSw(sw1 : UInt8, sw2 : UInt8) -> Int {
Int(256) * Int(sw1) + Int(sw2)
}
static let CARD_ERROR_MSGS = [SW_SUCCESS: "No error.",
SW_APPLET_SELECT_FAILED : "Applet select failed.",
SW_RESPONSE_BYTES_REMAINING : "Response bytes remaining.",
SW_CLA_NOT_SUPPORTED : "CLA value not supported.",
SW_COMMAND_CHAINING_NOT_SUPPORTED : "Command chaining not supported.",
SW_COMMAND_NOT_ALLOWED : "Command not allowed (no current EF).",
SW_CONDITIONS_OF_USE_NOT_SATISFIED : "Conditions of use not satisfied.",
SW_CORRECT_EXPECTED_LENGTH : "Correct Expected Length (Le).",
SW_DATA_INVALID : "Data invalid.",
SW_NOT_ENOUGH_MEMORY_SPACE_IN_FILE : "Not enough memory space in the file.",
SW_FILE_INVALID : "File invalid.",
SW_FILE_NOT_FOUND : "File not found.",
SW_FUNCTION_NOT_SUPPORTED : "Function not supported.",
SW_INCORRECT_P1_P2 : "Incorrect parameters (P1,P2).",
SW_INS_NOT_SUPPORTED : "INS value not supported.",
/* SW_LAST_COMMAND_IN_CHAIN_EXPECTED : "Last command in chain expected.",*/
SW_LOGICAL_CHANNEL_NOT_SUPPORTED : "Card does not support the operation on the specified logical channel.",
SW_RECORD_NOT_FOUND : "Record not found.",
SW_SECURE_MESSAGING_NOT_SUPPORTED : "Card does not support secure messaging.",
SW_SECURITY_CONDITION_NOT_SATISFIED : "Security condition not satisfied.",
SW_COMMAND_ABORTED : "Command aborted, No precise diagnosis.",
SW_WRONG_DATA : "Wrong data.",
SW_WRONG_LENGTH : "Wrong length.",
SW_WRONG_P1_P2 : "Wrong parameter(s) P1-P2",
SW_INTERNAL_BUFFER_IS_NULL_OR_TOO_SMALL : "Internal buffer is null or too small.",
SW_PERSONALIZATION_NOT_FINISHED : "Personalization is not finished.",
SW_INCORRECT_OFFSET : "Internal error: incorrect offset.",
SW_INCORRECT_PAYLOAD : "Internal error: incorrect payload value.",
SW_INCORRECT_PASSWORD_FOR_CARD_AUTHENICATION : "Incorrect password for card authentication.",
SW_INCORRECT_PASSWORD_CARD_IS_BLOCKED : "Incorrect password, card is locked.",
SW_SET_COIN_TYPE_FAILED : "Set coin type failed.",
SW_SET_CURVE_FAILED : "Set curve failed.",
SW_GET_COIN_PUB_DATA_FAILED : "Get coin pub data failed.",
SW_SIGN_DATA_FAILED : "Sign data failed.",
SW_INCORRECT_PIN : "Incorrect PIN.",
SW_COIN_MANAGER_INCORRECT_PIN : "Incorrect PIN.",
SW_COIN_MANAGER_UPDATE_PIN_ERROR : "Update PIN error (for CHANGE_PIN) or wallet status not support to export (for GENERATE SEED).",
SW_PIN_TRIES_EXPIRED : "PIN tries expired.",
SW_LOAD_SEED_ERROR : "Load seed error.",
SW_INCORRECT_KEY_INDEX : "Incorrect key index.",
SW_INCORRECT_KEY_CHUNK_START_OR_LEN : "Incorrect key chunk start or length.",
SW_INCORRECT_KEY_CHUNK_LEN : "Incorrect key chunk length.",
SW_NOT_ENOUGH_SPACE : "Not enough space.",
SW_KEY_SIZE_UNKNOWN : "Key size unknown.",
SW_KEY_LEN_INCORRECT : "Key length incorrect.",
SW_HMAC_EXISTS : "Hmac exists already.",
SW_INCORRECT_KEY_INDEX_TO_CHANGE: "Incorrect key index to change.",
SW_MAX_KEYS_NUMBER_EXCEEDED : "Max number of keys (1023) is exceeded.",
SW_DELETE_KEY_CHUNK_IS_NOT_FINISHED : "Delete key chunk is not finished.",
SW_INCORRECT_SAULT : "Incorrect sault.",
SW_DATA_INTEGRITY_CORRUPTED : "Data integrity corrupted.",
SW_INCORRECT_APDU_HMAC : "Incorrect apdu hmac. ",
SW_HMAC_VERIFICATION_TRIES_EXPIRED : "Apdu Hmac verification tries expired.",
SW_RECOVERY_DATA_TOO_LONG : "Too big length of recovery data.",
SW_INCORRECT_START_POS_OR_LE : "Incorrect start or length of recovery data piece in internal buffer.",
SW_INTEGRITY_OF_RECOVERY_DATA_CORRUPTED : "Hash of recovery data is incorrect. ",
SW_RECOVERY_DATA_ALREADY_EXISTS : "Recovery data already exists.",
SW_RECOVERY_DATA_IS_NOT_SET : "Recovery data does not exist"
]
static func getErrorMsg(sw : UInt16) -> String? {
return CARD_ERROR_MSGS[sw]
}
}

View File

@ -0,0 +1,39 @@
/*
* Copyright 2018-2020 TON DEV SOLUTIONS LTD.
*
* Licensed under the SOFTWARE EVALUATION License (the "License"); you may not use
* this file except in compliance with the License.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific TON DEV software governing permissions and
* limitations under the License.
*/
import Foundation
func debugLog(
_ items: Any...,
separator: String = " ",
terminator: String = "\n"
) {
#if DEBUG
let output = items.map { String(describing: $0) }.joined(separator: separator)
Swift.print(output, terminator: terminator)
#endif
}
/**
Here there are some contants related to all APDU commands
*/
public class CommonConstants {
static let CLA_SELECT: UInt8 = 0x00
static let INS_SELECT : UInt8 = 0xA4
static let SELECT_P1 : UInt8 = 0x04
static let SELECT_P2 : UInt8 = 0x00
static let LE_NO_RESPONSE_DATA = -1 // Use this LE if you do not wait any response data from card (except of status word)
static let LE_GET_ALL_RESPONSE_DATA = 256 // This is standard 0x00 value of LE. Use this LE if you want to take all response bytes from applet that it produced
}

View File

@ -0,0 +1,72 @@
//
// EmoneyApduCommands.swift
// Emoney Info
//
// Created by Wira Irawan on 23/07/24.
//
import Foundation
import CoreNFC
@available(iOS 13.0, *)
class EmoneyApduCommands{
static let TAPCASH_INIT_DATA : [UInt8] = [0xA0, 0x00, 0x42, 0x4e, 0x49, 0x10, 0x00, 0x01]
static let BRIZZI_INIT_DATA : [UInt8] = [0x01, 0x00, 0x00]
static let EMONEY_INIT_DATA : [UInt8] = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]
static let FLAZZ_INIT_DATA : [UInt8] = [0xA0, 0x00, 0x00, 0x00, 0x18, 0x0F, 0x00, 0x00, 0x01, 0x80, 0x01]
static let JACKCARD_INIT_DATA : [UInt8] = [0xA0, 0x00, 0x00, 0x05, 0x71, 0x4e, 0x4a, 0x43]
static let MEGA_DATA : [UInt8] = [0x01, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00]
static let BRI_DATA_01 : [UInt8] = [0x00, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00]
static let TAPCASH_INIT_APDU = NFCISO7816APDU(instructionClass : CommonConstants.CLA_SELECT, instructionCode : CommonConstants.INS_SELECT, p1Parameter : CommonConstants.SELECT_P1, p2Parameter : CommonConstants.SELECT_P2, data : Data(_ : TAPCASH_INIT_DATA), expectedResponseLength : CommonConstants.LE_NO_RESPONSE_DATA)
static let BRIZZI_INIT_APDU = NFCISO7816APDU(instructionClass : 0x90, instructionCode : 0x5A, p1Parameter : 0x00, p2Parameter : 0x00, data : Data(_ : BRIZZI_INIT_DATA), expectedResponseLength : CommonConstants.LE_GET_ALL_RESPONSE_DATA)
static let EMONEY_INIT_APDU = NFCISO7816APDU(instructionClass : CommonConstants.CLA_SELECT, instructionCode : CommonConstants.INS_SELECT, p1Parameter : CommonConstants.SELECT_P1, p2Parameter : CommonConstants.SELECT_P2, data : Data(_ : EMONEY_INIT_DATA), expectedResponseLength : CommonConstants.LE_NO_RESPONSE_DATA)
static let FLAZZ_INIT_APDU = NFCISO7816APDU(instructionClass : CommonConstants.CLA_SELECT, instructionCode : CommonConstants.INS_SELECT, p1Parameter : CommonConstants.SELECT_P1, p2Parameter : CommonConstants.SELECT_P2, data : Data(_ : FLAZZ_INIT_DATA), expectedResponseLength : CommonConstants.LE_NO_RESPONSE_DATA)
static let JACKCARD_INIT_APDU = NFCISO7816APDU(instructionClass : CommonConstants.CLA_SELECT, instructionCode : CommonConstants.INS_SELECT, p1Parameter : CommonConstants.SELECT_P1, p2Parameter : CommonConstants.SELECT_P2, data : Data(_ : JACKCARD_INIT_DATA), expectedResponseLength : CommonConstants.LE_NO_RESPONSE_DATA)
static let TAPCASH_APDU01 = NFCISO7816APDU(instructionClass : 0x90, instructionCode : 0x32, p1Parameter : 0x03, p2Parameter : 0x00, data : Data(), expectedResponseLength : CommonConstants.LE_GET_ALL_RESPONSE_DATA)
//00 a4 01 00 02 02 00
static let BCA_APDU01 = NFCISO7816APDU(instructionClass : 0x00, instructionCode : 0xA4, p1Parameter : 0x01, p2Parameter : 0x00, data : Data(_ : [0x02, 0x00]), expectedResponseLength : CommonConstants.LE_GET_ALL_RESPONSE_DATA)
//00 b0 81 00 8e
static let BCA_APDU02 = NFCISO7816APDU(instructionClass : 0x00, instructionCode : 0xb0, p1Parameter : 0x81, p2Parameter : 0x00, data : Data(), expectedResponseLength : 142)
//80 32 00 03 04 00 00 00 00 //00 b0 81 00 00
static let BCA_APDU03 = NFCISO7816APDU(instructionClass : 0x80, instructionCode : 0x32, p1Parameter : 0x00, p2Parameter : 0x03, data : Data(_ : [0x00, 0x00, 0x00, 0x00]), expectedResponseLength : CommonConstants.LE_GET_ALL_RESPONSE_DATA)
//00 b0 81 00 00
static let BCA_APDU04 = NFCISO7816APDU(instructionClass : 0x00, instructionCode : 0xB0, p1Parameter : 0x81, p2Parameter : 0x00, data : Data(), expectedResponseLength : CommonConstants.LE_GET_ALL_RESPONSE_DATA)
//00 b3 00 00 3f
static let MANDIRI_APDU01 = NFCISO7816APDU(instructionClass : 0x00, instructionCode : 0xB3, p1Parameter : 0x00, p2Parameter : 0x00, data : Data(), expectedResponseLength : 63)
//00 b5 00 00 0a
static let MANDIRI_APDU02 = NFCISO7816APDU(instructionClass : 0x00, instructionCode : 0xB5, p1Parameter : 0x00, p2Parameter : 0x00, data : Data(), expectedResponseLength : 10)
//90 4c 00 00 04
static let DKI_APDU01 = NFCISO7816APDU(instructionClass : 0x90, instructionCode : 0x4C, p1Parameter : 0x00, p2Parameter : 0x00, data : Data(), expectedResponseLength : 4)
//90 bd 00 00 07 01 00 00 00 0a 00 00 00
static let MEGA_APDU01 = NFCISO7816APDU(instructionClass : 0x90, instructionCode : 0xBD, p1Parameter : 0x00, p2Parameter : 0x00, data : Data(_ : MEGA_DATA), expectedResponseLength : CommonConstants.LE_GET_ALL_RESPONSE_DATA)
//90 6c 00 00 01 02 00
static let MEGA_APDU02 = NFCISO7816APDU(instructionClass : 0x90, instructionCode : 0x6C, p1Parameter : 0x00, p2Parameter : 0x00, data : Data(_ : [0x02]), expectedResponseLength : CommonConstants.LE_GET_ALL_RESPONSE_DATA)
//90 60 00 00 00
static let BRI_UID01 = NFCISO7816APDU(instructionClass : 0x90, instructionCode : 0x60, p1Parameter : 0x00, p2Parameter : 0x00, data : Data(), expectedResponseLength : CommonConstants.LE_GET_ALL_RESPONSE_DATA)
//90 AF 00 00 00
static let BRI_UID02 = NFCISO7816APDU(instructionClass : 0x90, instructionCode : 0xAF, p1Parameter : 0x00, p2Parameter : 0x00, data : Data(), expectedResponseLength : CommonConstants.LE_GET_ALL_RESPONSE_DATA)
//90 bd 00 00 07 00 00 00 00 17 00 00 00
static let BRI_APDU01 = NFCISO7816APDU(instructionClass : 0x90, instructionCode : 0xBD, p1Parameter : 0x00, p2Parameter : 0x00, data : Data(_: BRI_DATA_01), expectedResponseLength : CommonConstants.LE_GET_ALL_RESPONSE_DATA)
//90 5A 00 00 03 03 00 00 00
static let BRI_APDU02 = NFCISO7816APDU(instructionClass : 0x90, instructionCode : 0x5A, p1Parameter : 0x00, p2Parameter : 0x00, data : Data(_: [0x03, 0x00, 0x00]), expectedResponseLength : CommonConstants.LE_GET_ALL_RESPONSE_DATA)
//90 0a 00 00 01 00 00
static let BRI_APDU03 = NFCISO7816APDU(instructionClass : 0x90, instructionCode : 0x0A, p1Parameter : 0x00, p2Parameter : 0x00, data : Data(_: [0x00]), expectedResponseLength : CommonConstants.LE_GET_ALL_RESPONSE_DATA)
//90 6C 00 00 01 00 00
static let BRI_APDU05 = NFCISO7816APDU(instructionClass : 0x90, instructionCode : 0x6C, p1Parameter : 0x00, p2Parameter : 0x00, data : Data(_: [0x00]), expectedResponseLength : CommonConstants.LE_GET_ALL_RESPONSE_DATA)
//90 BB 00 00 07 01 00 00 00 00 00 00 00
static let BRI_LOG01 = NFCISO7816APDU(instructionClass : 0x90, instructionCode : 0xBB, p1Parameter : 0x00, p2Parameter : 0x00, data : Data(_: [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), expectedResponseLength : CommonConstants.LE_GET_ALL_RESPONSE_DATA)
//90 AF 00 00 00
static let BRI_LOG02 = NFCISO7816APDU(instructionClass : 0x90, instructionCode : 0xAF, p1Parameter : 0x00, p2Parameter : 0x00, data : Data(), expectedResponseLength : CommonConstants.LE_GET_ALL_RESPONSE_DATA)
}

View File

@ -0,0 +1,174 @@
//
// TapCashData.swift
// Emoney Info
//
// Created by Wira Irawan on 26/07/24.
//
import Foundation
class TapCashData {
var purseData: [UInt8]?
var purseStatus: [UInt8]?
var purseBalance: [UInt8]?
var CAN: [UInt8]?
var CSN: [UInt8]?
var purseExpiry: [UInt8]?
var lastCredirTRP: [UInt8]?
var lastCreditHeader: [UInt8]?
var lastTxnTRP: [UInt8]?
var lastTxnRecord: [UInt8]?
var BDC: [UInt8]?
var keySet: [UInt8]?
var maxCardBalance: [UInt8]?
var eData: [UInt8]?
var crcb: [UInt8]?
var lastTransactionSignCert: [UInt8]?
var lastCounterData: [UInt8]?
var version: [UInt8]?
var lastDebitOption: [UInt8]?
var totalRecords: [UInt8]?
func getPurseData() -> [UInt8]? {
return purseData
}
func setPurseData(_ purseData: [UInt8]) {
self.purseData = purseData
unpackPurseData(data: purseData)
}
func getPurseStatus() -> [UInt8]? {
return purseStatus
}
func setPurseStatus(_ purseStatus: [UInt8]) {
self.purseStatus = purseStatus
}
func getPurseBalance() -> [UInt8]? {
return purseBalance
}
func setPurseBalance(_ purseBalance: [UInt8]) {
self.purseBalance = purseBalance
}
func getCAN() -> [UInt8]? {
return CAN
}
func setCAN(_ CAN: [UInt8]) {
self.CAN = CAN
}
func getCSN() -> [UInt8]? {
return CSN
}
func setCSN(_ CSN: [UInt8]) {
self.CSN = CSN
}
func getPurseExpiry() -> [UInt8]? {
return purseExpiry
}
func setPurseExpiry(_ purseExpiry: [UInt8]) {
self.purseExpiry = purseExpiry
}
func getLastCredirTRP() -> [UInt8]? {
return lastCredirTRP
}
func setLastCredirTRP(_ lastCredirTRP: [UInt8]) {
self.lastCredirTRP = lastCredirTRP
}
func getLastCreditHeader() -> [UInt8]? {
return lastCreditHeader
}
func setLastCreditHeader(_ lastCreditHeader: [UInt8]) {
self.lastCreditHeader = lastCreditHeader
}
func getLastTxnTRP() -> [UInt8]? {
return lastTxnTRP
}
func setLastTxnTRP(_ lastTxnTRP: [UInt8]) {
self.lastTxnTRP = lastTxnTRP
}
func getLastTxnRecord() -> [UInt8]? {
return lastTxnRecord
}
func setLastTxnRecord(_ lastTxnRecord: [UInt8]) {
self.lastTxnRecord = lastTxnRecord
}
func getBDC() -> [UInt8]? {
return BDC
}
func setBDC(_ BDC: [UInt8]) {
self.BDC = BDC
}
func getKeySet() -> [UInt8]? {
return keySet
}
func setKeySet(_ keySet: [UInt8]) {
self.keySet = keySet
}
func getMaxCardBalance() -> [UInt8]? {
return maxCardBalance
}
func setMaxCardBalance(_ maxCardBalance: [UInt8]) {
self.maxCardBalance = maxCardBalance
}
func geteData() -> [UInt8]? {
return eData
}
func seteData(_ eData: [UInt8]) {
self.eData = eData
}
func getCrcb() -> [UInt8]? {
return crcb
}
func setCrcb(_ crcb: [UInt8]) {
self.crcb = crcb
}
func getTotalRecords() -> [UInt8]? {
return totalRecords
}
private func unpackPurseData(data: [UInt8]) {
version = Array(data[0..<1])
purseStatus = Array(data[1..<2])
purseBalance = Array(data[2..<5])
CAN = Array(data[8..<16])
CSN = Array(data[16..<24])
purseExpiry = Array(data[24..<26])
lastCredirTRP = Array(data[28..<32])
lastCreditHeader = Array(data[32..<40])
totalRecords = Array(data[40..<41])
lastTxnTRP = Array(data[42..<46])
lastTxnRecord = Array(data[46..<62])
BDC = Array(data[63..<64])
keySet = Array(data[71..<72])
maxCardBalance = Array(data[78..<81])
lastDebitOption = Array(data[94..<95])
}
}

View File

@ -0,0 +1,19 @@
//
// GradientView.swift
// Emoney Info
//
// Created by Wira Irawan on 29/07/24.
//
import Foundation
import UIKit
class GradientView: UIView {
override class var layerClass: AnyClass {
return CAGradientLayer.self
}
var gradientLayer: CAGradientLayer {
return self.layer as! CAGradientLayer
}
}

View File

@ -0,0 +1,177 @@
import Foundation
/// Type-safe localization helper.
/// Usage: L10n.checkBalance returns localized String automatically
/// Works in both UIKit (as String) and SwiftUI (via Text(L10n.xxx))
enum L10n {
// MARK: - Home
static var availableBalance: String { s("availableBalance") }
static var cardTapInstruction: String { s("cardTapInstruction") }
static var cardTypeDefault: String { s("cardTypeDefault") }
static var tapCardHere: String { s("tapCardHere") }
static var tapCardHint: String { s("tapCardHint") }
static var checkBalance: String { s("checkBalance") }
static var lastTransaction: String { s("lastTransaction") }
static var viewFullHistory: String { s("viewFullHistory") }
static var copiedToClipboard: String { s("copiedToClipboard") }
static var transactionDefault: String { s("transactionDefault") }
static var transactionStatusSuccess: String { s("transactionStatusSuccess") }
static var noCard: String { s("noCard") }
// MARK: - Card / NFC
static var cardType: String { s("cardType") }
static var cardNumber: String { s("cardNumber") }
static var balance: String { s("balance") }
static var scanMessage: String { s("scanMessage") }
static var readFinish: String { s("readFinish") }
static var readFailed: String { s("readFailed") }
static var updateBalance: String { s("updateBalance") }
static var payment: String { s("payment") }
static var topup: String { s("topup") }
static var unknown: String { s("unknown") }
static var void: String { s("void") }
static var reactivation: String { s("reactivation") }
static var statementFee: String { s("statementFee") }
static var gracePeriod: String { s("gracePeriod") }
static var refund: String { s("refund") }
static var close: String { s("close") }
static var atu: String { s("atu") }
// MARK: - History
static var historyTitle: String { s("historyTitle") }
static var recentActivity: String { s("recentActivity") }
static var filterAllTime: String { s("filterAllTime") }
static var filterToday: String { s("filterToday") }
static var filterThisMonth: String { s("filterThisMonth") }
static var filterThisWeek: String { s("filterThisWeek") }
static var noTransactionsFound: String { s("noTransactionsFound") }
static var exportPDF: String { s("exportPDF") }
static var transactionHistory: String { s("transactionHistory") }
// MARK: - Settings
static var settingsTitle: String { s("settingsTitle") }
static var premiumBadge: String { s("premiumBadge") }
static var premiumTitle: String { s("premiumTitle") }
static var premiumDesc: String { s("premiumDesc") }
static var upgradeNow: String { s("upgradeNow") }
static var sectionGeneral: String { s("sectionGeneral") }
static var languageTitle: String { s("languageTitle") }
static var languageValue: String { s("languageValue") }
static var showCardNumberTitle: String { s("showCardNumberTitle") }
static var showCardNumberDesc: String { s("showCardNumberDesc") }
static var sectionApp: String { s("sectionApp") }
static var notificationsTitle: String { s("notificationsTitle") }
static var notificationsDesc: String { s("notificationsDesc") }
static var helpCenterTitle: String { s("helpCenterTitle") }
static var helpCenterDesc: String { s("helpCenterDesc") }
static var aboutAppTitle: String { s("aboutAppTitle") }
static var aboutAppDesc: String { s("aboutAppDesc") }
// MARK: - Terms & Conditions
static var termsLastUpdated: String { s("termsLastUpdated") }
static var termsTitleRegular: String { s("termsTitleRegular") }
static var termsTitleBold: String { s("termsTitleBold") }
static var termsSubtitle: String { s("termsSubtitle") }
static var termsSec1Title: String { s("termsSec1Title") }
static var termsSec1Body: String { s("termsSec1Body") }
static var termsSec2Title: String { s("termsSec2Title") }
static var termsSec2Body: String { s("termsSec2Body") }
static var termsSec2Bullet1: String { s("termsSec2Bullet1") }
static var termsSec2Bullet2: String { s("termsSec2Bullet2") }
static var termsSec2Bullet3: String { s("termsSec2Bullet3") }
static var termsSec3Title: String { s("termsSec3Title") }
static var termsSec3Body: String { s("termsSec3Body") }
static var termsSec3Bullet1: String { s("termsSec3Bullet1") }
static var termsSec3Bullet2: String { s("termsSec3Bullet2") }
static var termsContactTitle: String { s("termsContactTitle") }
static var termsContactDesc: String { s("termsContactDesc") }
static var termsContactButton: String { s("termsContactButton") }
// MARK: - Privacy Policy
static var privacyLastUpdated: String { s("privacyLastUpdated") }
static var privacySectionNfcTitle: String { s("privacySectionNfcTitle") }
static var privacySectionNfcBody: String { s("privacySectionNfcBody") }
static var privacySectionNoStorageTitle: String { s("privacySectionNoStorageTitle") }
static var privacySectionNoStorageBody: String { s("privacySectionNoStorageBody") }
static var privacySectionReadOnlyTitle: String { s("privacySectionReadOnlyTitle") }
static var privacySectionReadOnlyBody: String { s("privacySectionReadOnlyBody") }
static var privacyContactTitle: String { s("privacyContactTitle") }
static var privacyContactDesc: String { s("privacyContactDesc") }
static var privacyContactButton: String { s("privacyContactButton") }
// MARK: - About
static var aboutAppDescription: String { s("aboutAppDescription") }
static var aboutChipNfc: String { s("aboutChipNfc") }
static var aboutChipRealtime: String { s("aboutChipRealtime") }
static var aboutChipMulti: String { s("aboutChipMulti") }
static var aboutTerms: String { s("aboutTerms") }
static var aboutPrivacy: String { s("aboutPrivacy") }
static var aboutConnectTitle: String { s("aboutConnectTitle") }
static var aboutConnectDesc: String { s("aboutConnectDesc") }
static var maskTitle: String { s("maskTitle") }
static var maskDesc: String { s("maskDesc") }
static var supportCardTitle: String { s("supportCardTitle") }
static var supportCardDesc: String { s("supportCardDesc") }
static var aboutTitle: String { s("aboutTitle") }
static var version: String { s("versi") }
static var footerCopyright: String { s("footerCopyright") }
static var reportIssue: String { s("reportIssue") }
// MARK: - FAQ
static var faqHeaderTitle: String { s("faqHeaderTitle") }
static var faqSearchPlaceholder: String { s("faqSearchPlaceholder") }
static var faqFilterAll: String { s("faqFilterAll") }
static var faqNoResults: String { s("faqNoResults") }
static var faqHelpCardTitle: String { s("faqHelpCardTitle") }
static var faqHelpCardDesc: String { s("faqHelpCardDesc") }
static var faqEmailSupport: String { s("faqEmailSupport") }
// FAQ Categories
static var faqCategoryCard: String { s("faqCategoryCard") }
static var faqCategoryTransaction: String { s("faqCategoryTransaction") }
static var faqCategoryBalance: String { s("faqCategoryBalance") }
static var faqCategoryApp: String { s("faqCategoryApp") }
// FAQ Questions & Answers Card
static var faqQ_cardCompatible: String { s("faqQ_cardCompatible") }
static var faqA_cardCompatible: String { s("faqA_cardCompatible") }
static var faqQ_cardNotDetected: String { s("faqQ_cardNotDetected") }
static var faqA_cardNotDetected: String { s("faqA_cardNotDetected") }
static var faqQ_cardReadFailed: String { s("faqQ_cardReadFailed") }
static var faqA_cardReadFailed: String { s("faqA_cardReadFailed") }
// FAQ Questions & Answers Transaction
static var faqQ_txNotShown: String { s("faqQ_txNotShown") }
static var faqA_txNotShown: String { s("faqA_txNotShown") }
static var faqQ_txExportPDF: String { s("faqQ_txExportPDF") }
static var faqA_txExportPDF: String { s("faqA_txExportPDF") }
// FAQ Questions & Answers Balance
static var faqQ_balanceWrong: String { s("faqQ_balanceWrong") }
static var faqA_balanceWrong: String { s("faqA_balanceWrong") }
static var faqQ_balanceTopup: String { s("faqQ_balanceTopup") }
static var faqA_balanceTopup: String { s("faqA_balanceTopup") }
// FAQ Questions & Answers App
static var faqQ_appLanguage: String { s("faqQ_appLanguage") }
static var faqA_appLanguage: String { s("faqA_appLanguage") }
static var faqQ_appMaskNumber: String { s("faqQ_appMaskNumber") }
static var faqA_appMaskNumber: String { s("faqA_appMaskNumber") }
// MARK: - Tab Bar
static var tabEmoney: String { s("tabEmoney") }
static var tabSettings: String { s("tabSettings") }
// MARK: - Private
private static func s(_ key: String) -> String {
NSLocalizedString(key, comment: "")
}
}

View File

@ -0,0 +1,15 @@
//
// Station.swift
// Emoney Info
//
// Created by Wira Basalamah on 05/04/26.
//
struct Station {
let id: Int
let name: String
let subName: String
let latitude: String
let longitude: String
}

View File

@ -0,0 +1,59 @@
import UIKit
enum Theme {
// MARK: - Colors
enum Color {
static let primary = UIColor(hex: "#7AD4D1")
static let secondary = UIColor(hex: "#5D7D7B")
static let success = UIColor(hex: "#34C759")
static let background = UIColor(hex: "#F3F3F8")
static let card = UIColor.white
static let textPrimary = UIColor(hex: "#1A1A2E")
static let textSecondary = UIColor(hex: "#8E8E93")
}
// MARK: - Font Sizes
enum FontSize {
static let title: CGFloat = 24
static let subtitle: CGFloat = 18
static let body: CGFloat = 16
static let caption: CGFloat = 12
}
// MARK: - Fonts
enum Font {
static func title(weight: UIFont.Weight = .bold) -> UIFont {
.systemFont(ofSize: FontSize.title, weight: weight)
}
static func subtitle(weight: UIFont.Weight = .semibold) -> UIFont {
.systemFont(ofSize: FontSize.subtitle, weight: weight)
}
static func body(weight: UIFont.Weight = .regular) -> UIFont {
.systemFont(ofSize: FontSize.body, weight: weight)
}
static func caption(weight: UIFont.Weight = .regular) -> UIFont {
.systemFont(ofSize: FontSize.caption, weight: weight)
}
}
}
// MARK: - UIColor hex initializer
private extension UIColor {
convenience init(hex: String) {
var sanitized = hex.trimmingCharacters(in: .whitespacesAndNewlines)
if sanitized.hasPrefix("#") { sanitized.removeFirst() }
var rgb: UInt64 = 0
Scanner(string: sanitized).scanHexInt64(&rgb)
let r = CGFloat((rgb >> 16) & 0xFF) / 255
let g = CGFloat((rgb >> 8) & 0xFF) / 255
let b = CGFloat( rgb & 0xFF) / 255
self.init(red: r, green: g, blue: b, alpha: 1)
}
}

View File

@ -0,0 +1,50 @@
//
// ToastHelper.swift
// NewTonNfcCardLib
//
// Created by Alina Alinovna on 23.10.2020.
// Copyright © 2020 Facebook. All rights reserved.
//
import Foundation
import UIKit
class ToastHelper {
static func showToast(message : String) {
let toastView = UILabel()
toastView.backgroundColor = UIColor.black.withAlphaComponent(0.7)
toastView.textColor = UIColor.white
toastView.textAlignment = .center
toastView.font = UIFont.preferredFont(forTextStyle: .caption1)
toastView.layer.cornerRadius = 25
toastView.layer.masksToBounds = true
toastView.text = message
toastView.numberOfLines = 0
toastView.alpha = 0
toastView.translatesAutoresizingMaskIntoConstraints = false
let window = UIApplication.shared.delegate?.window!
window?.addSubview(toastView)
let horizontalCenterContraint: NSLayoutConstraint = NSLayoutConstraint(item: toastView, attribute: .centerX, relatedBy: .equal, toItem: window, attribute: .centerX, multiplier: 1, constant: 0)
let widthContraint: NSLayoutConstraint = NSLayoutConstraint(item: toastView, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .width, multiplier: 1, constant: 275)
let verticalContraint: [NSLayoutConstraint] = NSLayoutConstraint.constraints(withVisualFormat: "V:|-(>=200)-[loginView(==50)]-68-|", options: [.alignAllCenterX, .alignAllCenterY], metrics: nil, views: ["loginView": toastView])
NSLayoutConstraint.activate([horizontalCenterContraint, widthContraint])
NSLayoutConstraint.activate(verticalContraint)
UIView.animate(withDuration: 0.5, delay: 0, options: .curveEaseIn, animations: {
toastView.alpha = 1
}, completion: nil)
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Double((Int64)(2 * NSEC_PER_SEC)) / Double(NSEC_PER_SEC), execute: {
UIView.animate(withDuration: 0.5, delay: 0, options: .curveEaseIn, animations: {
toastView.alpha = 0
}, completion: { finished in
toastView.removeFromSuperview()
})
})
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

Some files were not shown because too many files have changed in this diff Show More