Template Banking App

You can access Kotlin-based template application from the:

You can access Java-based template application from the:

1. What is the Application Template?

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
 */
public class CardRepository implements CardServiceListener {

public interface RepositoryCallback {
    void afterCardDataReceived(ICCCard card);
    void afterCardServiceConnected(Boolean isConnected);
    void setResponseMessage(ResponseCode responseCode);
    void setMessage(String message);
}
    
@Inject
public CardRepository() {
    this.cardServiceListener = this;
}

public void callbackInitializer(RepositoryCallback repositoryCallback) {
    this.repositoryCallback = repositoryCallback;
}

public void cardServiceBinder(MainActivity mainActivity) {
    this.mainActivity = mainActivity;
    this.cardServiceBinding = new CardServiceBinding(mainActivity, cardServiceListener);
}

public void setGIB(boolean GIB) {
    isGIB = GIB;
}

public CardServiceBinding getCardServiceBinding() {
    return cardServiceBinding;
}

/**
 * This reads the card and shows card read screen with amount.
 */
public void readCard(int amount, TransactionCode transactionCode) {
    try {
        if (!isApprove) {
            JSONObject obj = new JSONObject();
            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.
 */
public void onCardDataReceived(String cardData) {
    Log.d("Card Data", cardData);
    try {
        JSONObject json = new JSONObject(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 = new Gson().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.
 */
private void approveCard() {
    try {
        JSONObject obj = new JSONObject();
        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.
 */
private void getCard(int amount, String config) {
    try {
        JSONObject obj = new JSONObject(config);
        //TODO Developer, check from parameter
        boolean 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.
 */
@Override
public void onCardServiceConnected() {
    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.
 */
public void setEMVConfiguration() {
    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
 */
public void setConfig() {
    try {
        InputStream xmlStream = mainActivity.getApplicationContext().getAssets().open("custom_emv_config.xml");
        BufferedReader r = new BufferedReader(new InputStreamReader(xmlStream));
        StringBuilder total = new StringBuilder();
        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
 */
public void setCLConfig() {
    try {
        InputStream xmlCLStream = mainActivity.getApplicationContext().getAssets().open("custom_emv_cl_config.xml");
        BufferedReader rCL = new BufferedReader(new InputStreamReader(xmlCLStream));
        StringBuilder totalCL = new StringBuilder();
        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();
    }
  }
}

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.

public void ServiceRoutine(MainActivity mainActivity) {
    this.mainActivity = mainActivity;
    setDeviceInfo();
}

public void setDeviceInfo() {
    AppTemp appTemp = (AppTemp) mainActivity.getApplicationContext();
    DeviceInfo deviceInfo = new DeviceInfo(mainActivity.getApplicationContext());
    CountDownTimer deviceInfoTimer = new CountDownTimer(30000, 1000) {
        @Override
        public void onTick(long millisUntilFinished) { }

        @Override
        public void onFinish() {
            setInfoDialogLiveData(new InfoDialogData(InfoDialog.InfoType.Error, mainActivity.getString(R.string.device_info_service_Error)));
        }
    };
    deviceInfoTimer.start();
    deviceInfo.getFields(
            fields -> {
                Log.d("Device Info:", "Success");
                if (fields == null) {
                    setInfoDialogLiveData(new InfoDialogData(InfoDialog.InfoType.Error, mainActivity.getString(R.string.device_info_service_Error)));
                }

                appTemp.setCurrentFiscalID(fields[0]);
                appTemp.setCurrentDeviceMode(fields[1]);
                appTemp.setCurrentCardRedirection(fields[2]);

                deviceInfo.unbind();
                deviceInfoTimer.cancel();
                setKMS();
            },
            DeviceInfo.Field.FISCAL_ID, DeviceInfo.Field.OPERATION_MODE, DeviceInfo.Field.CARD_REDIRECTION
    );
}

public void setKMS() {
    TokenKMS tokenKMS = new TokenKMS();
    tokenKMS.init(mainActivity.getApplicationContext(), new KMSWrapperInterface.InitCallbacks() {
        @Override
        public void onInitSuccess() {
            Log.d("KMS Init Success:", "Init Success");
            setIsConnectedLiveData(true);
        }

        @Override
        public void onInitFailed() {
            Log.v("Token KMS onInitFailed", "KMS Init Failed");
            setInfoDialogLiveData(new InfoDialogData(InfoDialog.InfoType.Error, mainActivity.getString(R.string.kms_service_error)));
        }
    });
}
// Device Info Use Cases
// Examples Device Info
deviceInfo.getFields(
       fields -> {
           if (fields == null) return;

                Log.d("Example 0", "Fiscal ID:   "    + fields[0]);
                Log.d("Example 1", "IMEI Number: "    + fields[1]);
                Log.d("Example 2", "IMSI Number: "    + fields[2]);
                Log.d("Example 3", "Modem Version : " + fields[3]);
                Log.d("Example 4", "LYNX Number: "    + fields[4]);
                Log.d("Example 5", "POS Mode: "       + fields[5]);

                mainActivity.showInfoDialog(InfoDialog.InfoType.Info,
                        "Fiscal ID: "     +fields[0] +"\n"
                                +"IMEI Number: "   +fields[1] +"\n"
                                +"IMSI Number: "   +fields[2] +"\n"
                                +"Modem Version: " +fields[3] +"\n"
                                +"Lynx Version: "  +fields[4] +"\n"
                                +"Pos Mode: "      +fields[5],true);
                    deviceInfo.unbind();
                 },
                 // write requested fields
                 DeviceInfo.Field.FISCAL_ID,
                 DeviceInfo.Field.IMEI_NUMBER,
                 DeviceInfo.Field.IMSI_NUMBER,
                 DeviceInfo.Field.MODEM_VERSION,
                 DeviceInfo.Field.LYNX_VERSION,
                 DeviceInfo.Field.OPERATION_MODE
         );


// Activation ViewModel when activate the template
deviceInfo.setAppParams(success -> { //it informs atms with new terminal and merchant ID
   if (success) {
       PrintHelper.PrintSuccess(mainActivity.getApplicationContext());
   } else {
       PrintHelper.PrintError(mainActivity.getApplicationContext());
      }
   deviceInfo.unbind();
  }, activationRepository.getTerminalId(), activationRepository.getMerchantId());

2.2.2. Printer Service

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.

public void printSlip(String printText, MainActivity mainActivity) {
    StyledString styledText = new StyledString();
    styledText.addStyledText(printText);
    styledText.finishPrintingProcedure();
    styledText.print(PrinterService.getService(mainActivity.getApplicationContext()));
}

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
*/
private void setDeviceInfoParams(MainActivity mainActivity) {
    DeviceInfo deviceInfo = new DeviceInfo(mainActivity);
    deviceInfo.setAppParams(success -> { //it informs atms with new terminal and merchant ID
        if (success) {
            PrintHelper.PrintSuccess(mainActivity.getApplicationContext());
        } else {
            PrintHelper.PrintError(mainActivity.getApplicationContext());
        }
        deviceInfo.unbind();
    }, activationRepository.getTerminalId(), activationRepository.getMerchantId());
}

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
*/
public Intent prepareSaleIntent(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 Code
    bundle.putString("CardOwner", transaction.getBaCustomerName()); // Optional
    bundle.putString("CardNumber", cardNo); // Optional, Card No can be masked
    bundle.putInt("PaymentStatus", 0); // #2 Payment Status
    bundle.putInt("Amount", amount); // #3 Amount
    bundle.putInt("BatchNo", transaction.getBatchNo());
    bundle.putString("CardNo", StringHelper.MaskTheCardNo(transaction.getBaPAN()));
    bundle.putString("MID", receipt.getMerchantID()); //#6 Merchant ID
    bundle.putString("TID", receipt.getPosID()); //#7 Terminal ID
    bundle.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;
}

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
*/
public Intent prepareIntent(SampleReceipt receipt, MainActivity mainActivity, Transaction transaction,
                            TransactionCode transactionCode, ResponseCode responseCode) {
    Bundle bundle = new Bundle();
    Intent intent = new Intent();
    bundle.putInt("ResponseCode", responseCode.ordinal());
    prepareSlip(receipt, mainActivity, transaction, transactionCode, false);
    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
*/
public Intent prepareIntent(SampleReceipt receipt, MainActivity mainActivity, Transaction transaction,
                            TransactionCode transactionCode, ResponseCode responseCode) {
    Bundle bundle = new Bundle();
    Intent intent = new Intent();
    bundle.putInt("ResponseCode", responseCode.ordinal());
    prepareSlip(receipt, mainActivity, transaction, transactionCode, false);
    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
 */
public Intent prepareBatchIntent(BatchCloseResponse batchCloseResponse, MainActivity mainActivity, String slip) {
    BatchResult responseCode = batchCloseResponse.getBatchResult();
    Intent intent = new Intent();
    Bundle bundle = new Bundle();
    printSlip(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 explanation
Intent resultIntent = new Intent();
Bundle bundle = new Bundle();
String clConfigFile = "";

try {
    InputStream xmlCLStream = assetManager.open("custom_emv_cl_config.xml");
    BufferedReader rCL = new BufferedReader(new InputStreamReader(xmlCLStream));
    StringBuilder totalCL = new StringBuilder();
    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))));
new Handler(Looper.getMainLooper()).postDelayed(() -> mainHandler.post(() -> setIntentLiveData(resultIntent)), 2000);

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.

public class CheckSaleReceiver extends 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.
     */
    @Override
    public void onReceive(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 = new ActivationRepository(db.activationDao());
            BatchRepository batchRepository = new BatchRepository(db.batchDao());
            TransactionPrintHelper transactionPrintHelper = new TransactionPrintHelper();
            List<Transaction> transactionList = db.transactionDao().getTransactionsByUUID(uuid);
            Transaction transaction = transactionList.get(0);
            SampleReceipt receipt = new SampleReceipt(transaction, activationRepository, batchRepository, null);
            Intent resultIntent = new Intent();
            if (transaction != null) {
                Bundle bundle = new Bundle();
                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);
        }
    }
}

3.6. Examples

The Application Template has a feature called "Examples" that individually showcases our UI components. Additionally, device info and print functions are provided under this section. To access this section, you need to select "Examples" from the POS Transactions menu. For detailed information about this section, you can look at UIComponents.

4. Flows

4.1. GIB

The Application Template features a functionality called "GiB Operations," which allows for sales, cancellations, refunds, and end-of-day operations remotely. This feature operates by sending POST requests based on the device’s terminal ID using the Postman app.

4.1.1. Sale

To perform a sale, you need to send a POST request with the name "Sale" along with the required data. The request includes a "Sale Action" code, which is used to identify the action to be taken. The system examines this "Sale Action" code and the received card data in the "SaleActionReceived" function to determine whether the transaction is from GiB or PGW (Payment Gateway). After identifying the transaction coming from, the sale is processed.

4.1.2. Void

For void, you must have previously made a sale on the same day. Using the UUID of the sale transaction, you can send a POST request with the name "Cancel" to initiate a void. This request includes a "Refund Action" code. The system examines the "Refund Action" code and checks the date to distinguish between a refund and void using the "RefundActionReceived" function. Afterwards, the void process is executed.

4.1.3. Refund

To perform a refund, you should have made a sale on a different day from the refund request. Similar to void, the request is sent with the name "Cancel" with the sale’s UUID and it includes a "Refund Action" code. However, due to the difference in the date, it is recognized as a refund, and the process proceeds under the "Refund" category. It's worth noting that refunds are typically available for matched refunds only.

4.1.4. EndOfDay

The Batch Close operation is initiated using the "EndOfDay" POST request. This request includes a "Batch Close Action" code, indicating the intention to perform a batch close. Upon receiving this action, the system executes batch close procedures. If there is not a transaction today, inform the user as “No Transactions Found.” After completing the batch close process, a "Parameter Action" is sent, and parameter loading is performed. The "Parameter Uploading" section handles parameter loading.

4.2 Trigger

Triggering is usually initiated automatically at certain times of the day. This time is pre-assigned by ATMS, usually 00:20. There is also the option to set the trigger manually.

The Trigger process is performed by the Payment Gateway. It takes place in two phases for each bank. First, an intent containing "BatchClose_Action" as an action is sent to the relevant bank for closing its batch. After this process is completed, an intent containing "Parameter_Action" is sent to the same bank. In this way, the bank uploads its parameters to the Payment Gateway. Then the received parameters (bin ranges, configuration files, allowed operations and supported AID numbers) are recorded in the Payment Gateway's database.

After this process is implemented for all banks, Payment Gateway updates and sets the "superConfig" file that it will use for its own card reading process based on these received parameters. Then, the device is rebooted and the trigger process is completed.

Last updated