Application Template is a sample Android application written in Kotlin and Java, following the MVVM (Model-View-ViewModel) and single-activity architecture to optimize user experience and improve application performance. It provides a starting point for developers who are building a Banking Application. The application integrates various libraries and technologies commonly used in Android development.
The app focuses on enabling various operations such as Sale, Void, Refund, and Batch Close on a POS device. Furthermore, the app promotes ease of testing and interaction by allowing developers to execute operations through Postman requests (GİB Operations). Notably, it facilitates the generation of slips post each operation, adding to the user experience. The local database integration utilizes Room technology, ensuring efficient storage of all transactions.
Indeed, the Application Template boasts a range of essential service connections that significantly enhance its functionality. Notably, it incorporates crucial elements such as "Device Info," "KMS" (Key Management System), and "CardService." Moreover, the template underscores the importance of proper activation and configuration of (dummy) bank host settings.
The Application Template offers more than just code structure. It provides practical insights by illustrating real-world use cases through an example menu. Showcasing the implementation of specific library functions, offers valuable guidance to developers navigating through the app's functionalities.
2. Token SDK Integration
2.1. Pay SDK
2.1.1. Bank App Protocol
Application Template is a sample application that implements a protocol called "Bank App Protocol". Details on how this protocol is implemented are clearly shown in the sections introducing the functionality. For more information, you can access the Bank App Protocol details at this link.
2.1.2. Card Service
The Application Template for bank developers focuses on the CardService, an important component used to manage financial transactions. This service is activated at the beginning of the Application Template after a successful connection with the KMS (Key Management Service) and DeviceInfo services. The user is presented with a menu that allows him/her to take action. Meanwhile, the Application Template tries to connect asynchronously to the CardService to perform card transactions. It is particularly noteworthy that the main thread is not stopped during this process. This allows you to maintain the user experience without freezes.
The attempt to connect to CardService takes place within a specific time window. Usually, this is 30 seconds. If a successful connection to CardService cannot be established within 30 seconds, this is recorded and the user is notified via a dialog. However, the CardService may not be needed for every transaction, so the Application Template will continue to function without interruption. If a connection to CardService is required, this connection is checked beforehand. If a successful connection has already been made, the transaction will continue. Otherwise, another attempt is made to establish a connection to the CardService.
Application Template is designed according to the MVVM (Model-View-ViewModel) architecture. Therefore, CardService-related operations are performed in a repository called CardRepository. CardRepository takes care of card operations such as CardService connection, readCard, EMVConfiguration and stores data passed to CardViewModel. The results of these operations are passed to other UI layers using LiveData.
For more information, you can access the Card Service details at this link.
/** * This class is for managing data related card operations * It implements CardServiceListener interface's methods which are card service binding lib's methods. * It implements RepositoryCallback interface for communicate with CardViewModel */publicclassCardRepositoryimplementsCardServiceListener {publicinterfaceRepositoryCallback {voidafterCardDataReceived(ICCCard card);voidafterCardServiceConnected(Boolean isConnected);voidsetResponseMessage(ResponseCode responseCode);voidsetMessage(String message);}@InjectpublicCardRepository() {this.cardServiceListener=this;}publicvoidcallbackInitializer(RepositoryCallback repositoryCallback) {this.repositoryCallback= repositoryCallback;}publicvoidcardServiceBinder(MainActivity mainActivity) {this.mainActivity= mainActivity;this.cardServiceBinding=newCardServiceBinding(mainActivity, cardServiceListener);}publicvoidsetGIB(boolean GIB) { isGIB = GIB;}publicCardServiceBindinggetCardServiceBinding() {return cardServiceBinding;}/** * This reads the card and shows card read screen with amount. */publicvoidreadCard(int amount,TransactionCode transactionCode) {try {if (!isApprove) {JSONObject obj =newJSONObject();obj.put("forceOnline",1);obj.put("zeroAmount",0);obj.put("reqEMVData","575A5F245F204F84959F12");if (transactionCode !=TransactionCode.SALE&& transactionCode !=TransactionCode.VOID) {obj.put("emvProcessType",EmvProcessType.FULL_EMV.ordinal()); } else {obj.put("emvProcessType",EmvProcessType.READ_CARD.ordinal()); }obj.put("showAmount", (transactionCode ==TransactionCode.VOID) ?0:1);obj.put("cardReadTypes",6);if (isGIB) {obj.put("showCardScreen",0); }this.amount= amount;this.transactionCode= transactionCode;getCard(amount,obj.toString()); } else {approveCard(); } } catch (Exception e) {repositoryCallback.setResponseMessage(ResponseCode.ERROR);e.printStackTrace(); }}/** * This method is triggered after reading card, if card couldn't be read successfully a callback message is arranged * If card read successfully, it parses @param cardData and creates ICCCard object. Finally, communicate with CardViewModel
* for set cardLiveData to card object that we created before related to cardData. */publicvoidonCardDataReceived(String cardData) {Log.d("Card Data", cardData);try {JSONObject json =newJSONObject(cardData);int resultCode =json.getInt("resultCode");if (resultCode ==CardServiceResult.USER_CANCELLED.resultCode()) {Log.d("CardDataReceived","Card Result Code: User Cancelled");repositoryCallback.setResponseMessage(ResponseCode.CANCELLED); }if (resultCode ==CardServiceResult.ERROR_TIMEOUT.resultCode()) {Log.d("CardDataReceived","Card Result Code: TIMEOUT");repositoryCallback.setResponseMessage(ResponseCode.ERROR); }if (resultCode ==CardServiceResult.ERROR.resultCode()) {Log.d("CardDataReceived","Card Result Code: ERROR");repositoryCallback.setResponseMessage(ResponseCode.ERROR); }if (resultCode ==CardServiceResult.SUCCESS.resultCode()) {int type =json.getInt("mCardReadType");ICCCard card =newGson().fromJson(cardData,ICCCard.class);if (type ==CardReadType.ICC.value) {if (!isApprove && transactionCode ==TransactionCode.SALE) { isApprove =true; } }repositoryCallback.afterCardDataReceived(card); } } catch (Exception e) {repositoryCallback.setResponseMessage(ResponseCode.ERROR);e.printStackTrace(); }}/** * This method for read card with Continue_Emv. */privatevoidapproveCard() {try {JSONObject obj =newJSONObject();obj.put("forceOnline",1);obj.put("zeroAmount",0);obj.put("showAmount",1);obj.put("emvProcessType",EmvProcessType.CONTINUE_EMV.ordinal());getCard(amount,obj.toString()); } catch (Exception e) {repositoryCallback.setResponseMessage(ResponseCode.ERROR);e.printStackTrace(); }}/** * This method is putting some arguments to JSONObject for card read and calls getCard() method in cardService. */privatevoidgetCard(int amount,String config) {try {JSONObject obj =newJSONObject(config);//TODO Developer, check from parameterboolean isKeyInAllowed =true;boolean isAskCVVAllowed =true;boolean isFallbackAllowed =true;boolean isQrPayAllowed =true;obj.put("keyIn", isKeyInAllowed ?1:0);obj.put("askCVV", isAskCVVAllowed ?1:0);obj.put("fallback", isFallbackAllowed ?1:0);obj.put("qrPay", isQrPayAllowed ?1:0);cardServiceBinding.getCard(amount,30,obj.toString()); } catch (Exception e) {repositoryCallback.setResponseMessage(ResponseCode.ERROR);e.printStackTrace(); }}/** * If card service connect successful, this function will be triggered and communicate with CardViewModel * for set cardServiceResultLiveData to true. */@OverridepublicvoidonCardServiceConnected() {setEMVConfiguration();repositoryCallback.afterCardServiceConnected(true);}/** * This function only works in installation, it calls setConfig and setCLConfig * It also called from onCardServiceConnected method of Card Service Library, if Configs couldn't set in first_run * (it is checked from sharedPreferences), again it setConfigurations, else do nothing. */publicvoidsetEMVConfiguration() {SharedPreferences sharedPreference =mainActivity.getSharedPreferences("myprefs",Context.MODE_PRIVATE);SharedPreferences.Editor editor =sharedPreference.edit();boolean firstTimeBoolean =sharedPreference.getBoolean("FIRST_RUN",false);if (!firstTimeBoolean) {setConfig();setCLConfig();editor.putBoolean("FIRST_RUN",true);Log.d("setEMVConfiguration","ok");editor.apply(); }}/** * It sets custom_emv_config.xml with setEMVConfiguration method in card service */publicvoidsetConfig() {try {InputStream xmlStream =mainActivity.getApplicationContext().getAssets().open("custom_emv_config.xml");BufferedReader r =newBufferedReader(new InputStreamReader(xmlStream));StringBuilder total =newStringBuilder();for (String line; (line =r.readLine()) !=null; ) {Log.d("emv_config","conf line: "+ line);total.append(line).append('\n'); }int setConfigResult =getCardServiceBinding().setEMVConfiguration(total.toString());repositoryCallback.setMessage("setEMVConfiguration res="+ setConfigResult);Log.d("emv_config","setEMVConfiguration: "+ setConfigResult); } catch (Exception e) {mainActivity.responseMessage(ResponseCode.ERROR,"EMV Configuration Error");e.printStackTrace(); }}/** * It sets custom_emv_cl_config.xml with setEMVCLConfiguration method in card service */publicvoidsetCLConfig() {try {InputStream xmlCLStream =mainActivity.getApplicationContext().getAssets().open("custom_emv_cl_config.xml");BufferedReader rCL =newBufferedReader(new InputStreamReader(xmlCLStream));StringBuilder totalCL =newStringBuilder();for (String line; (line =rCL.readLine()) !=null; ) {Log.d("emv_config","conf line: "+ line);totalCL.append(line).append('\n'); }int setCLConfigResult =getCardServiceBinding().setEMVCLConfiguration(totalCL.toString());repositoryCallback.setMessage("setEMVCLConfiguration res="+ setCLConfigResult);Log.d("emv_config","setEMVCLConfiguration: "+ setCLConfigResult); } catch (Exception e) {mainActivity.responseMessage(ResponseCode.ERROR,"EMV CL Configuration Error");e.printStackTrace(); } }}
// Look at cardRepository for more detailed code this code block // shows basic exammples// Override functions comes from CardServiceListener// functions with cardServiceBinding variables are cardService functionsclassCardRepository@Injectconstructor() :CardServiceListener {var cardServiceBinding =CardServiceBinding(activity, this) //initializing cardServicefunreadCard(amount: Int, transactionCode: Int) {val obj =JSONObject()try { obj.put("forceOnline", 1) obj.put("zeroAmount", 0)// TODO Developer: Check from Allowed Operations Parameterval isKeyInAllowed =trueval isAskCVVAllowed =trueval isFallbackAllowed =trueval isQrPayAllowed =true obj.put("keyIn", if (isKeyInAllowed) 1else0) obj.put("askCVV", if (isAskCVVAllowed) 1else0) obj.put("fallback", if (isFallbackAllowed) 1else0) obj.put("qrPay", if (isQrPayAllowed) 1else0) obj.put("reqEMVData", "575A5F245F204F84959F12") cardServiceBinding?.getCard(amount, 30, obj.toString()) } catch (e: JSONException) {setCallBackMessage(ResponseCode.ERROR) e.printStackTrace() } }overridefunonCardDataReceived(cardData: String?) {//get the ICC cardModel from cardDataval card: ICCCard=Gson().fromJson(cardData, ICCCard::class.java) }overridefunonCardServiceConnected() {setEMVConfiguration() }funsetEMVConfiguration() {val sharedPreference = mainActivity.getSharedPreferences("myprefs", Context.MODE_PRIVATE)val editor = sharedPreference.edit()val firstTimeBoolean = sharedPreference.getBoolean("FIRST_RUN", false)if (!firstTimeBoolean) {setConfig()setCLConfig() editor.putBoolean("FIRST_RUN", true) editor.apply() } }privatefunsetConfig() {try {val xmlStream = mainActivity.assets.open("emv_config.xml")val r =BufferedReader(InputStreamReader(xmlStream))val total =StringBuilder()var line: String? = r.readLine()while (line !=null) { Log.d("emv_config", "conf line: $line") total.append(line).append('\n') line = r.readLine() } cardServiceBinding!!.setEMVConfiguration(total.toString()) } catch (e: Exception) { e.printStackTrace() } }privatefunsetCLConfig() {try {val xmlCLStream = mainActivity.assets.open("emv_cl_config.xml")val rCL =BufferedReader(InputStreamReader(xmlCLStream))val totalCL = java.lang.StringBuilder()var line: String? = rCL.readLine()while (line !=null) { Log.d("emv_config", "conf line: $line") totalCL.append(line).append('\n') line = rCL.readLine() }val setCLConfigResult: Int= cardServiceBinding!!.setEMVCLConfiguration(totalCL.toString()) toastMessage.postValue("setEMVCLConfiguration res=$setCLConfigResult") Log.i("emv_config", "setEMVCLConfiguration: $setCLConfigResult") } catch (e: java.lang.Exception) { e.printStackTrace() } }overridefunonPinReceived(s: String) {}overridefunonICCTakeOut() {}}
2.2. System & Security
2.2.1. KMS and Device Info
The Application Template provided for the Android Devices used by bank developers includes two essential service connections: Key Management Service (KMS) and DeviceInfo services. These services are crucial for the uninterrupted operation of financial institutions. The Application Template utilizes a Model-View-ViewModel (MVVM) architecture to ensure a seamless experience each time the Application Template is powered on.
The Application Template begins by establishing connections to these asynchronous services. It first attempts to connect to the DeviceInfo service. If this connection succeeds, the device is redirected to the KMS service. When both connections are successfully established, the application proceeds with its normal flow and displays the user interface.
However, if the connection to either the DeviceInfo or KMS services fails, the Application Template detects this and opens a dialog to interact with the user. The user is informed about the connection issue. Subsequently, the application is closed, as a secure and functional experience cannot be provided without connectivity to these essential services.
For more information, you can access the KMS Service details at this link. For DeviceInfo, you can access this link.
// Here is the basic examples of how it connects services// Look at serviceViewModel for more detailed explanationfun serviceRoutine(){setDeviceInfo() }private fun setDeviceInfo() { val applicationContext =mainActivity.applicationContext val appTemp = applicationContext as AppTemp val deviceInfo =DeviceInfo(applicationContext) val timer: CountDownTimer = object :CountDownTimer(30000,1000) { override fun onTick(millisUntilFinished: Long) {} override fun onFinish() {} }timer.start()deviceInfo.getFields( { fields:Array<String?>?->if (fields ==null) {uiState.postValue(ServiceUIState.ErrorDeviceInfo) }appTemp.setCurrentFiscalID(fields!![0])appTemp.setCurrentDeviceMode(fields[1]!!)appTemp.setCurrentCardRedirection(fields[2]!!)deviceInfo.unbind()timer.cancel()Log.i("Connected","Device Info")connectKMSService() },DeviceInfo.Field.FISCAL_ID,DeviceInfo.Field.OPERATION_MODE,DeviceInfo.Field.CARD_REDIRECTION ) } /** In this method, Application Template tries to connect KMS service * It will update uiState with respect to result */private fun connectKMSService(){ val applicationContext =mainActivity.applicationContext val tokenKMS =TokenKMS()//connecting KMS Service with this flowtokenKMS.init(applicationContext, object :KMSWrapperInterface.InitCallbacks { override fun onInitSuccess() {Log.i("Token KMS onInitSuccess","KMS Init OK") } override fun onInitFailed() {Log.i("Token KMS onInitFailed","KMS Init Failed") } }) }}
Since the Application Template offers a sample implementation of banking transactions on Beko Android POS devices, it is equipped with Print Service. Thanks to this service, users can easily create their slips. The utilities provided by the Print Service can be accessed through the classes under the "printHelper" tab in the "utils" package. For more information, you can access the Printer Service details with this link.
// Here is the basic example of printing, you can view how styledString// can be prepared from classes under utils > printHelpers sectionfunprint(printText: String?, mainActivity: MainActivity) {val styledText =StyledString() styledText.addStyledText(printText) styledText.finishPrintingProcedure() styledText.print(PrinterService.getService(mainActivity.applicationContext))}
2.2.3. UI Components
Application Template presents a sample banking application available for Android devices and aims to provide a similar interface for all banks. This comprehensive application template includes a specialized library called "UIComponents". The use of UI components is documented in detail in the "Examples" section inside the "UI" package. These examples help developers understand the full potential of the library and allow them to get started quickly.
3. Functionalities
3.1. Pos Settings
Application Template contains a menu for managing bank settings. This menu offers two options: "Setup" and "Host Settings."
In the Setup section, the user is expected to enter the 10-digit merchant ID and the 8-character terminal ID specific to the device. Once this information has been entered, pressing the "Save" button will automatically launch the function called "setupRoutine". SetupRoutine performs some critical operations asynchronously in the background while updating the screen. If the card service has already been connected, it configurates EMV. If not, it first connects to the CardService and then completes the EMV configuration. Afterwards, the DeviceInfo library is updated with the entered merchant ID and terminal ID. In this way, the Application Template is activated and the terminal ID and merchant ID of the device become visible on the ATMS system. For more information, you can refer to Pos Settings section under Bank app Protocol via this link.
//TODO Developer: If you don't implement this function in your application couldn't be activated and couldn't seen in atms
/** * It tries to connect deviceInfo. If it connects then set terminal ID and merchant ID into device Info and * print success slip else print error slip*/privatevoidsetDeviceInfoParams(MainActivity mainActivity) {DeviceInfo deviceInfo =newDeviceInfo(mainActivity);deviceInfo.setAppParams(success -> { //it informs atms with new terminal and merchant IDif (success) {PrintHelper.PrintSuccess(mainActivity.getApplicationContext()); } else {PrintHelper.PrintError(mainActivity.getApplicationContext()); }deviceInfo.unbind(); },activationRepository.getTerminalId(),activationRepository.getMerchantId());}
//TODO Developer: If you don't implement this function in your application couldn't be activated and couldn't seen in atms
/** * It tries to connect deviceInfo. If it connects then set terminal ID and merchant ID into device Info and * print success slip else print error slip */privatesuspendfunsetDeviceInfoParams(mainActivity: MainActivity,terminalId: String?,merchantId: String?){withContext(Dispatchers.Main) {val deviceInfo =DeviceInfo(mainActivity) deviceInfo.setAppParams({ success ->//it informs atms with new terminal and merchant IDif (success) {PrintHelper().printSuccess(mainActivity.applicationContext) } else {PrintHelper().printError(mainActivity.applicationContext) } deviceInfo.unbind() }, terminalId, merchantId) }}
All these operations are performed suitably by MVVM architecture in the Repository named ActivationRepository. Inputs are saved in the table named ActivationTable in Room database. Depending on the result of this process, the dialogues shown in the user interface are controlled by observing and updating them through a structure called UIState.
In the Host Settings section, an IP address and Port number are entered by the user. After these values are entered, when the saved button is clicked by the user, the entered information is saved in the Activation Table.
3.2. Sale
In the Application Template, 'Sale' is a Transaction type and this application offers a total of three different Sales Flows. While there are some minor differences between these flows, most points are similar. For more information, you can access the Sale details in Bank App Protocol at this link.
/** * This method puts required values to bundle (something like contentValues for data transferring). * After that, an intent will be created with this bundle to provide communication between GiB and Application Template via IPC
*/publicIntentprepareSaleIntent(SampleReceipt receipt,MainActivity mainActivity,Transaction transaction, TransactionCode transactionCode, ResponseCode responseCode, String ZNO, String receiptNo) {
Bundle bundle =new Bundle();Intent intent =new Intent();int amount =transaction.getUlAmount();SlipType slipType =SlipType.BOTH_SLIPS;String cardNo =transaction.getBaPAN();bundle.putInt("ResponseCode",responseCode.ordinal()); // #1 Response Codebundle.putString("CardOwner",transaction.getBaCustomerName()); // Optionalbundle.putString("CardNumber", cardNo); // Optional, Card No can be maskedbundle.putInt("PaymentStatus",0); // #2 Payment Statusbundle.putInt("Amount", amount); // #3 Amountbundle.putInt("BatchNo",transaction.getBatchNo());bundle.putString("CardNo",StringHelper.MaskTheCardNo(transaction.getBaPAN()));bundle.putString("MID",receipt.getMerchantID()); //#6 Merchant IDbundle.putString("TID",receipt.getPosID()); //#7 Terminal IDbundle.putInt("TxnNo",transaction.getUlGUP_SN());bundle.putInt("SlipType",slipType.value);bundle.putBoolean("IsSlip",true);bundle.putString("RefNo",transaction.getRefNo());bundle.putString("AuthNo",transaction.getAuthCode());bundle.putInt("PaymentType",PaymentTypes.CREDITCARD.getType());TransactionPrintHelper transactionPrintHelper =new TransactionPrintHelper(); if (responseCode == ResponseCode.CANCELLED || responseCode == ResponseCode.UNABLE_DECLINE || responseCode == ResponseCode.OFFLINE_DECLINE) {
slipType =SlipType.NO_SLIP;//TODO Developer, no slip or cancel slip. } else {if (slipType ==SlipType.CARDHOLDER_SLIP|| slipType ==SlipType.BOTH_SLIPS) { bundle.putString("customerSlipData", transactionPrintHelper.getFormattedText(receipt, transaction, transactionCode, SlipType.CARDHOLDER_SLIP, mainActivity, ZNO, receiptNo, false));
}if (slipType ==SlipType.MERCHANT_SLIP|| slipType ==SlipType.BOTH_SLIPS) { bundle.putString("merchantSlipData", transactionPrintHelper.getFormattedText(receipt, transaction, transactionCode, SlipType.MERCHANT_SLIP, mainActivity, ZNO, receiptNo, false));
}bundle.putString("RefundInfo", getRefundInfo(transaction, cardNo, amount, receipt)); if (transactionCode == TransactionCode.MATCHED_REFUND || transactionCode == TransactionCode.CASH_REFUND || transactionCode == TransactionCode.INSTALLMENT_REFUND || transactionCode == TransactionCode.VOID) {
printSlip(bundle.getString("customerSlipData"), mainActivity); printSlip(bundle.getString("merchantSlipData"), mainActivity); } }intent.putExtras(bundle);return intent;}
/** This method puts required values to bundle (something like contentValues for data transferring). * After that, an intent will be created with this bundle to provide communication between GiB and Application Template via IPC
*/fun prepareSaleIntent(transactionResponse: TransactionResponse, card: ICCCard, mainActivity: MainActivity, receipt: SampleReceipt, zNO: String?, receiptNo: Int?)
: Intent{ Log.i("Transaction/Response","responseCode:${transactionResponse.responseCode} ContentValues: ${transactionResponse.contentVal}")
val responseCode = transactionResponse.responseCodeval batchNo: Int= receipt.batchNo.toInt()val groupSN: Int= receipt.groupSerialNo.toInt()val merchantID: String? = receipt.merchantIDval terminalID: String? = receipt.terminalIDval amount = card.mTranAmount1val intent =Intent()if (responseCode == ResponseCode.SUCCESS){val bundle =Bundle() bundle.putInt("ResponseCode", responseCode.ordinal) bundle.putString("CardOwner", card.ownerName) // Optional bundle.putInt("PaymentStatus", 0) // #2 Payment Status bundle.putInt("Amount", amount ) // #3 Amount bundle.putBoolean("IsSlip", true) bundle.putInt("BatchNo", batchNo) bundle.putString("CardNo", if (card.mCardNumber != null) StringHelper().maskCardForBundle(card.mCardNumber!!) else null) // Optional, Card No can be masked
bundle.putString("MID", merchantID.toString()) bundle.putString("TID", terminalID.toString()) bundle.putInt("TxnNo",groupSN) bundle.putInt("PaymentType", PaymentType.CREDITCARD.type)var slipType: SlipType= SlipType.NO_SLIP if (responseCode == ResponseCode.CANCELED || responseCode == ResponseCode.UNABLE_DECLINE || responseCode == ResponseCode.OFFLINE_DECLINE) { //TODO if onlineResponse not successful
slipType = SlipType.NO_SLIP }else {if (transactionResponse.responseCode == ResponseCode.SUCCESS){val printHelper =TransactionPrintHelper() bundle.putString("customerSlipData", printHelper.getFormattedText( receipt, SlipType.CARDHOLDER_SLIP,transactionResponse.contentVal!!, transactionResponse.transactionCode, mainActivity,zNO, receiptNo,false))
bundle.putString("merchantSlipData", printHelper.getFormattedText( receipt, SlipType.MERCHANT_SLIP,transactionResponse.contentVal!!, transactionResponse.transactionCode, mainActivity,zNO, receiptNo,false))
bundle.putString("RefundInfo", getRefundInfo(ContentValHelper().getTransaction(transactionResponse.contentVal!!),card,receipt))
if(transactionResponse.contentVal !=null) { bundle.putString("RefNo", transactionResponse.contentVal!!.getAsString( TransactionCols.Col_RefNo)) bundle.putString("AuthNo", transactionResponse.contentVal!!.getAsString( TransactionCols.Col_AuthCode)) } } } bundle.putInt("SlipType", slipType.value) intent.putExtras(bundle) }return intent}
3.2.1. Application Template Selected as a Bank Flow
This scenario occurs if the device has only one Application Template bank. If the device has more than one bank, this scenario occurs when the Application Template is selected as the bank after pressing the 'back' button on the card reading screen of Payment Gateway. In this scenario; if the demo mode is off, the user is shown the Sales Fragment and then the card reading screen is opened after pressing the 'Sales' button. If demo mode is on, the card reading screen appears directly without entering this screen. The flow after the card is read varies depending on the card read type.
3.2.1.1. Contactless Card Reading Flow
If the card has been read contactless, go to the 'doSale' function and process the information such as 'uuid', if available 'zNo' and 'receiptNo', and proceed to the 'transactionRoutine' flow. At this time, a dialog box about a dummy connection is displayed on the screen and a connection to a dummy host is established. Then a 'Transaction Entity' is created and this data is inserted into the 'TransactionTable' table. Then the slip data is prepared and the main activity is finished with an intent that describes the sale.
3.2.1.2 ICC Flow
If the card is read with a chip, a new screen opens without asking the user for the card password. On this screen, when the 'Sale' button is pressed, the password is asked and then the contactless flow is started. If the 'Installment Sale' button is pressed, the instalment amount is selected and then the contactless flow is started by asking for the password.
3.2.1.3. QR Code Reading
If the QR code at the bottom of the card reading screen is clicked, dialogs such as 'Waiting', 'Waiting for QR Code to be Read' and 'QR Transaction Successful' are displayed respectively. Then mainActivity is finished.
3.2.2. Card Routing
Payment Gateway manages the contactless card reading process on devices with multiple active bank applications and active card Routing. After the card is read, this process routes the sales transaction to the appropriate bank according to the card routing flow. If Application Template is the appropriate bank as a result of this flow (such as default contactless bank, issuer bank or brand bank), it routes the sale transaction with the card information to Application Template.
3.2.3. GIB Sale Flow
Application Template performs sales transactions from remote servers with Postman. This process is called GIB (Gelir İdaresi Başkanlığı) flow. After the request is sent via Postman by filling in the required fields (device number, transaction number, etc.), this request reaches the Application Template with the appropriate data. Then, the Application Template reads the card and executes the first flow.
3.3. Pos Transactions
In the Application Template, important financial transactions such as sales, cancellations, and refund transactions are comprehensively handled under the heading "Transaction." The data of all these transactions is stored in a table called "TransactionTable". Operations related to these transactions are carried out properly in accordance with the MVVM architecture in the "TransactionRepository" and "TransactionViewModel" components.
This transaction process starts with entering the information required for the transaction. Then the user clicks on the corresponding button to initiate the transaction. If the card service has already been connected, the card reading screen is displayed immediately. However, if it hasn't been connected yet, the connection to the card service is established first, and then the card reading screen is displayed. After the card has been successfully read, a function called the "transactionRoutine" is called, which coordinates all of these operations.
This function first updates the connection screen and then, in the background, asynchronously accesses a dummy host and receives the online transaction response. Critical information such as the authorization code and reference number is generated at this point. A "TransactionResponse" is prepared depending on the transaction type, and the "TransactionTable" is updated accordingly. Then, the receipt data and the result intent are prepared and printed. When the transaction completes, the mainActivity finishes.
3.3.1. Void
Void Transaction is a transaction type used for cancelling transactions performed during the day. In this transaction type, the sale or return transaction is cancelled and this void is recorded in the TransactionTable. There are two different Void flows: Standard Void Transaction and GIB Void Transaction. You can access more information about Void flow in Bank App via this link.
/** * It prepares refund and void intent for only gib and print slip*/publicIntentprepareIntent(SampleReceipt receipt,MainActivity mainActivity,Transaction transaction,TransactionCode transactionCode,ResponseCode responseCode) {Bundle bundle =newBundle();Intent intent =newIntent();bundle.putInt("ResponseCode",responseCode.ordinal());prepareSlip(receipt, mainActivity, transaction, transactionCode,false);intent.putExtras(bundle);return intent;}
/** * It prepares refund intent both for gib and normal refund and also print the slip */fun prepareRefundVoidIntent(transactionResponse: TransactionResponse, mainActivity: MainActivity, receipt: SampleReceipt,
zNO: String?, receiptNo: Int?): Intent{ Log.d("TransactionResponse/Refund", "responseCode:${transactionResponse.responseCode} ContentValues: ${transactionResponse.contentVal}")
val printHelper =TransactionPrintHelper() val customerSlip = printHelper.getFormattedText(receipt, SlipType.CARDHOLDER_SLIP,transactionResponse.contentVal!!, transactionResponse.transactionCode, mainActivity,zNO, receiptNo,false)
val merchantSlip = printHelper.getFormattedText(receipt, SlipType.MERCHANT_SLIP,transactionResponse.contentVal!!, transactionResponse.transactionCode, mainActivity,zNO, receiptNo,false)
print(customerSlip, mainActivity)print(merchantSlip, mainActivity)val responseCode = transactionResponse.responseCodeval intent =Intent()val bundle =Bundle() bundle.putInt("ResponseCode", responseCode.ordinal) intent.putExtras(bundle)return intent}
3.3.1.1. Standard Void Transaction
In this flow, the user selects the cancel option from the transactions menu. If the application is connected to the card service, the card reading screen opens. If there is no connection, the connection is first established and then the card reading screen opens.
After the card is read, the transactions other than void performed with the same card on that day (before the batch closes) are listed. If no transaction (other than cancellation) has been made with this card, the "No Transaction Found" warning is displayed and the main activity is closed.
3.3.1.2. GIB Void
The GIB Void Process starts with a request sent to the device via Postman with the UUID, which is the unique ID of a transaction recorded for that day. This request is received by the Application Template with an intent as "RefundInfo". If the batch number (batch no) in RefundInfo matches the batch number of current day, we consider this transaction as a void transaction.
Then the UUID of the transaction is retrieved from RefundInfo and the card reading screen opens. When the card is read, it is first checked whether a transaction was made with the same card on that day. If no transaction has been made with the card on that day, the "No Transaction Found" warning is displayed and the main activity is terminated.
However, if a transaction has been made, the TransactionTable is searched for the transaction containing this UUID. If there is no transaction related to this UUID, the main activity is terminated with the warning "No Transaction Found".
However, if there is a transaction related to this UUID, the card number in the transaction is compared with the card number scanned. If the card numbers do not match, the main activity is terminated with the warning "Card Numbers Do Not Match". If the card numbers match, the "transactionRoutine" flow in the standard flow is applied.
3.3.2. Refund
A refund transaction refers to the cancellation of a sales transaction that was not made on the same day (made on previous days) and the refund of all or part of the amount as a result of this cancellation. There are two different refund flows in the Application Template, standard and GIB. You can access more information about Refund flow in Bank App via this link.
/** * It prepares refund and void intent for only gib and print slip*/publicIntentprepareIntent(SampleReceipt receipt,MainActivity mainActivity,Transaction transaction,TransactionCode transactionCode,ResponseCode responseCode) {Bundle bundle =newBundle();Intent intent =newIntent();bundle.putInt("ResponseCode",responseCode.ordinal());prepareSlip(receipt, mainActivity, transaction, transactionCode,false);intent.putExtras(bundle);return intent;}
/** * It prepares refund intent both for gib and normal refund and also print the slip */fun prepareRefundVoidIntent(transactionResponse: TransactionResponse, mainActivity: MainActivity, receipt: SampleReceipt,
zNO: String?, receiptNo: Int?): Intent{ Log.d("TransactionResponse/Refund", "responseCode:${transactionResponse.responseCode} ContentValues: ${transactionResponse.contentVal}")
val printHelper =TransactionPrintHelper() val customerSlip = printHelper.getFormattedText(receipt, SlipType.CARDHOLDER_SLIP,transactionResponse.contentVal!!, transactionResponse.transactionCode, mainActivity,zNO, receiptNo,false)
val merchantSlip = printHelper.getFormattedText(receipt, SlipType.MERCHANT_SLIP,transactionResponse.contentVal!!, transactionResponse.transactionCode, mainActivity,zNO, receiptNo,false)
print(customerSlip, mainActivity)print(merchantSlip, mainActivity)val responseCode = transactionResponse.responseCodeval intent =Intent()val bundle =Bundle() bundle.putInt("ResponseCode", responseCode.ordinal) intent.putExtras(bundle)return intent}
3.3.2.1. Standart Refund Flow
Application Template includes various refund options, namely matched refund, instalment refund, and cash refund. The loyalty refund feature has not been implemented within the template.
To initiate a refund operation, you first need to select "refund" from the POS Transactions menu. After selecting the type of refund, you can enter the required information and proceed with the refund process. This information typically includes the refund amount, original amount, reference number, approval code, and the date of the transaction. These details are necessary to locate the correct sale. Of course, there is no actual sale transaction because these refund processes are performed as dummy transactions.
3.3.2.2. GIB Refund Flow
The GIB Refund Process starts with a request sent to the device via Postman with the UUID, which is the unique ID of a transaction recorded. This request is received by the Application Template with an intent as "RefundInfo". If the batch number (batch no) in RefundInfo does not match the batch number of the current day, we consider this transaction as a refund transaction.
Authentication Code, Reference Number, Amount, Transaction Date and Card Number used in the transaction to be refunded are retrieved with refundInfo. Then, the card reading screen opens automatically. After the card is read, the card number received from refundInfo is compared with the card number read. If these numbers do not match, the "Card Numbers Do Not Match" warning is displayed and the main activity is closed. However, if the numbers match, it continues with the same flow in "transactionRoutine" in the standard refund flow.
3.3.3 Batch Close
Within the Application Template, there is a feature for performing a Batch Close operation. To execute the Batch Close process, you must navigate to the "POS Transactions" menu and select "Batch Close." Once this feature is selected, a confirmation dialog will appear, providing users with the option to proceed with the Batch Close if there have been transactions during the day. If there have been no transactions, an info dialog will display a message indicating "No Transactions Found."
If the user clicks the green checkmark button in the confirmation dialog, the batch close process is initiated. The Batch Close process is designed to execute asynchronously in the background, specifically in the IO Thread. This approach ensures that the main thread remains unblocked, allowing users to continue using the application without interruption. In the Batch Close process, we increment group no by 1 and set the serial no column to 1. Additionally, save this batch close slip to the previous batch close slip column for the “Batch Close Repetition” process. After this process is complete, a slip is printed that contains details about the day's transactions and group. At the end of batch close, the Application Template prepares a result Intent and it prints a slip. You can access more information about Batch Close in Bank App via this link.
/** * It finishes the batch operation via printing slip with respect to * @param batchCloseResponse * and passes the response code as a liveData intent which is observed from its fragment and finishes the mainActivity */publicIntentprepareBatchIntent(BatchCloseResponse batchCloseResponse,MainActivity mainActivity,String slip) {BatchResult responseCode =batchCloseResponse.getBatchResult();Intent intent =newIntent();Bundle bundle =newBundle();printSlip(slip, mainActivity);bundle.putInt("ResponseCode",responseCode.ordinal());intent.putExtras(bundle);return intent;}
/** * It finishes the batch operation via printing slip with respect to * @param batchCloseResponse * and passes the response code as a liveData intent which is observed from its fragment and finishes the mainActivity */funprepareBatchIntent(batchCloseResponse: BatchCloseResponse, mainActivity: MainActivity, slip: String): Intent { Log.d("finishBatch","${batchCloseResponse.batchResult}")val responseCode = batchCloseResponse.batchResultval intent =Intent()val bundle =Bundle()print(slip,mainActivity) bundle.putInt("ResponseCode", responseCode.ordinal) intent.putExtras(bundle)return intent}
3.3.4 Slip Repetition
In summary, the "GiB Operations" feature facilitates sales, voids, refunds, and EndofDay by processing POST requests remotely, interpreting action codes, and distinguishing between various types of transactions based on action codes and dates.
The Application Template features a "Slip Menu" option that displays a list of transactions, can print transaction slips, a Batch Close Repetition slip for the previous day and a Transaction List slip that contains transactions performed on the current day. In this feature, we use RecyclerView to list all transactions for the current day, including void operations. When you select any transaction from the list, the corresponding transaction slip is printed. This process occurs in the background thread to avoid freezing the main thread, and it displays a "Printing Slip" screen to inform the user of the ongoing slip printing process. In addition, this feature includes buttons for "Batch Close Repetition" which is the previous day’s batch close slip and "Transaction List" which is a list of transactions performed until that time. When these buttons are pressed, the slips are generated in the IO Thread. If there have been no transactions on the current day or if the batch number is 1, the user is presented with an info dialog displaying the message "No Transactions Found."
Overall, this feature enhances the functionality of the application by providing a convenient way to view transaction details and print individual transaction slips, Transaction List slips and previous Batch Close slips while ensuring a smooth user experience by offloading resource-intensive tasks to background threads.
Slip Repetition:
Transaction List:
3.3.5 Demo Mode
The Application Template includes a "Demo Mode" feature. We designed this feature to ensure that the device doesn't appear to be in developer mode during presentations. To implement this feature, we used SharedPreferences. SharedPreferences is an Android mechanism for storing simple data values persistently across app runs. In this case, it's used to store the state of the Demo Mode (whether it's on or off) so that the application can behave differently depending on this setting.
3.4. Parameter Uploading
Parameter uploading occurs at Trigger time. This operation involves the Application Template uploading its Card Routing Parameters to the Payment Gateway. These parameters include Bin Ranges, Allowed Operations, Contactless Configuration File and Supported AID numbers.
The Application Template loads these parameters into intent in the background (on the IO Thread) while displaying a dialog with "Loading Parameters" on the screen. After the parameters are successfully loaded into the intent, the "Parameters Loaded Successfully" dialog is displayed and the main activity is terminated with this intent. In this way, the loaded parameters are successfully transferred to the Payment Gateway. You can access more information about Parameters in Bank App via this link.
// Here is the basic code you can visit TriggerViewModel for more detailed explanationIntent resultIntent =newIntent();Bundle bundle =newBundle();String clConfigFile ="";try {InputStream xmlCLStream =assetManager.open("custom_emv_cl_config.xml");BufferedReader rCL =newBufferedReader(new InputStreamReader(xmlCLStream));StringBuilder totalCL =newStringBuilder();String line;while ((line =rCL.readLine()) !=null) {totalCL.append(line).append('\n'); } clConfigFile =totalCL.toString();} catch (Exception e) {e.printStackTrace();}bundle.putString("clConfigFile", clConfigFile);String bins = "[{\"cardRangeStart\":\"1111110000000\",\"cardRangeEnd\":\"1111119999999\",\"OwnerShip\":\"ISSUER\",\"CardType\":\"C\"}," +
"{\"cardRangeStart\":\"2222220000000\",\"cardRangeEnd\":\"2222229999999\",\"OwnerShip\":\"NONE\",\"CardType\":\"C\"}," +
"{\"cardRangeStart\":\"3333330000000\",\"cardRangeEnd\":\"3333339999999\",\"OwnerShip\":\"BRAND\",\"CardType\":\"C\"}]";
bundle.putString("BINS", bins);bundle.putString("AllowedOperations","{\"QrAllowed\":1,\"KeyInAllowed\":1}");bundle.putString("SupportedAIDs","[A0000000031010, A0000000041010, A0000000032010]");//TODO Developer, Dummy response for test, check from Parameter DB.resultIntent.putExtras(bundle);mainHandler.post(() -> setInfoDialogLiveData(new InfoDialogData(InfoDialog.InfoType.Confirmed, mainActivity.getApplicationContext().getString(R.string.parameter_load_successful))));
newHandler(Looper.getMainLooper()).postDelayed(() ->mainHandler.post(() ->setIntentLiveData(resultIntent)),2000);
// Here is the basic code you can visit TriggerViewModel for more detailed explanationval resultIntent =Intent()val bundle =Bundle()var clConfigFile =""try {val xmlCLStream: InputStream= assetManager.open("emv_cl_config.xml")val rCL =BufferedReader(InputStreamReader(xmlCLStream))val totalCL =StringBuilder()var line: String?while (withContext(Dispatchers.IO) { rCL.readLine() }.also { line = it } !=null) { totalCL.append(line).append('\n') } clConfigFile = totalCL.toString()} catch (e: Exception) { e.printStackTrace()}bundle.putString("clConfigFile", clConfigFile)val bins = "[{\"cardRangeStart\":\"1111110000000\",\"cardRangeEnd\":\"1111119999999\",\"OwnerShip\":\"ISSUER\",\"CardType\":\"C\"}," +
"{\"cardRangeStart\":\"2222220000000\",\"cardRangeEnd\":\"2222229999999\",\"OwnerShip\":\"NONE\",\"CardType\":\"C\"}," +
"{\"cardRangeStart\":\"3333330000000\",\"cardRangeEnd\":\"3333339999999\",\"OwnerShip\":\"BRAND\",\"CardType\":\"C\"}]"
bundle.putString("BINS", bins)bundle.putString("AllowedOperations", "{"+"\"QrAllowed\""+":"+"1"+","+"\"KeyInAllowed\""+":"+"1"+"}")bundle.putString("SupportedAIDs", "[A0000000031010, A0000000041010, A0000000032010]")resultIntent.putExtras(bundle)mainActivity.setResult(resultIntent)
3.5 Powercut
Application Template has a feature called "powercut receiver," which is designed to handle situations when there is an electrical outage. Its primary function is to determine whether a sale should proceed or not based on whether the sale data has been inserted into the transaction database.
The system checks the continuity of a sale by looking up the necessary UUID in our transaction database. If the UUID exists in the database and matches the one received from the PGW (Payment Gateway), it signifies that the sale has been successfully inserted, and the transaction is considered as completed. In this case, the application triggers the necessary actions, such as sending an intent to the PGW to confirm the sale and printing a slip.
For more detailed information on how this process works, you can refer to the "Powercut Inquiry" section on the Bank App Protocol page.
publicclassCheckSaleReceiverextendsBroadcastReceiver { /** * This class for receive the UUID from successful transaction performed via * battery run out flow. It takes UUID from @param intent and control the transaction * is successful or not. If it's successful, it creates intent again and send to PGW * for print it. When the sending intent, used sendBroadcast function for communicate * with PGW. */ @OverridepublicvoidonReceive(Context context,Intent intent) {if (intent.hasExtra("UUID")) {Log.d("UUID",intent.getExtras().getString("UUID"));String uuid =intent.getExtras().getString("UUID");AppTempDB db =AppTempDB.getDatabase(context);ActivationRepository activationRepository =newActivationRepository(db.activationDao());BatchRepository batchRepository =newBatchRepository(db.batchDao());TransactionPrintHelper transactionPrintHelper =newTransactionPrintHelper();List<Transaction> transactionList =db.transactionDao().getTransactionsByUUID(uuid);Transaction transaction =transactionList.get(0);SampleReceipt receipt =newSampleReceipt(transaction, activationRepository, batchRepository,null);Intent resultIntent =newIntent();if (transaction !=null) {Bundle bundle =newBundle();bundle.putInt("ResponseCode",ResponseCode.SUCCESS.ordinal());bundle.putInt("PaymentStatus",0);bundle.putInt("Amount",transaction.getUlAmount()); bundle.putString("customerSlipData", transactionPrintHelper.getFormattedText(receipt, transaction, TransactionCode.SALE, SlipType.CARDHOLDER_SLIP, context, null, null, false));
bundle.putString("merchantSlipData", transactionPrintHelper.getFormattedText(receipt, transaction, TransactionCode.SALE, SlipType.MERCHANT_SLIP, context, null, null, false));
bundle.putInt("BatchNo",transaction.getBatchNo());bundle.putInt("TxnNo",transaction.getUlGUP_SN());bundle.putInt("SlipType",SlipType.BOTH_SLIPS.value);bundle.putBoolean("IsSlip",true);resultIntent.putExtras(bundle); }resultIntent.setAction("check_sale_result");resultIntent.setPackage("com.tokeninc.sardis.paymentgateway");Log.d("intent_control",resultIntent.toString());context.sendBroadcast(resultIntent); } }}
classCheckSaleReceiver : BroadcastReceiver() {/** * This class for receive the UUID from successful transaction performed via * battery run out flow. It takes UUID from * @param intent and control the transaction * is successful or not. If it's successful, it creates intent again and send to PGW * for print it. When the sending intent, used sendBroadcast function for communicate * with PGW. */