API Examples

Java API Examples Collection

To jump straight to the pure code of all the following API examples, see Java API Examples.

Building a Connector Class

In this section, for the sake of clarity, we define a simple wrapper class for the payment connection. We then use this class in all the examples. This class also demonstrates the very practical use of PaymentService.

The code for the wrapper class is as follows:

public class Connector {

    private PaymentService paymentService;

    Connector() {
        paymentService = new PaymentService();
    }
    
    public void setConnection(String hostname, int port) {
        paymentService.setHostname(hostname);
        paymentService.setPort(port);
    }

    public PaymentService getPaymentService() {
        return paymentService;
    }

    public void waitForConnection() throws InterruptedException {
        while(!paymentService.isConnected()) {
            System.out.println("NOT CONNECTED");
            Thread.sleep(1000);
        }
    }
}
String hostname = "localhost";
int port = 9990;

Connector connector = new Connector();
connector.setConnection(hostname, port);


PaymentService paymentService = connector.getPaymentService();

paymentService.connect(new ConnectionListener() {
    @Override
    public void onConnected() {

    }

    @Override
    public void onUnknownHost(UnknownHostException e) {

    }

    @Override
    public void onSocketFail(IOException e) {

    }
});

try {
    connector.waitForConnection();
} catch (InterruptedException e) {
    throw new RuntimeException(e);
}

try {
    paymentService.sendTransaction(...);
} catch (TransactionFieldException e) {
    throw new RuntimeException(e);
}

We are going to call waitForConnection() to ensure that we are ready to send transactions. However, we can also use ConnectionListener as follows:

paymentService.connect(new ConnectionListener() {
    @Override
    public void onConnected() {
        // move paymentService.sendTransaction(...) here
    }
    @Override
    public void onUnknownHost(UnknownHostException e) {
        e.printStackTrace();
    }
    @Override
    public void onSocketFail(IOException e) {
        System.out.println("Socket failed");
        e.printStackTrace();
    }
});

NOTE: connect(ConnectionListener listener) connects (asynchronously) to the specified host name and port.

For a list of PaymentService class methods, see https://doc.tecs.at/pubdoc/smartpos_sdk_javadoc/at/tecs/smartpos/PaymentService.html

Generating a Transaction ID

As we mentioned in GETTING_STARTED, it is a matter of personal choice how you generate the unique transaction ID. We recommend the following format:

String currentDateTime = new SimpleDateFormat("yyyyMMddHHmmss")
    .format(Calendar.getInstance().getTime());

StringBuilder postfix = new StringBuilder();

for(int i = 0; i < 3; i++) {
    Random rand = new Random();

    int randomNum = rand.nextInt(10);
    postfix.append(Integer.toString(randomNum));
}

Transaction transaction = new Transaction();
transaction.ID = currentDateTime + postfix.toString();
transaction.dateTime = currentDateTime;
...

Sale Transaction

  • Card present: YES
Transaction transaction = new Transaction();

transaction.amount = "100";
transaction.ID = currentDateTime + postfix.toString();
transaction.dateTime = currentDateTime;
transaction.terminalNum = "TERMINALID";
transaction.msgType = Transaction.MessageType.SALE;
transaction.currency = Transaction.Currency.EUR;
transaction.receiptNum = "1";
transaction.txOrigin = "1";
transaction.sourceID = "1";
transaction.langCode = "EN";

try {
    paymentService.sendTransaction(transaction);
} catch (TransactionFieldException e) {
    throw new RuntimeException(e);
}

// Alternatively, use the short variant: paymentService.sale(12300000, currentDateTime + postfix, currentDateTime, 100, "EUR");

paymentService.listen(new ResponseListener() {
    @Override
    public void onResponseReceived(Response response) {
        
        if(response.responseCode.equals("0000")) {
            System.out.println("Transaction Approved");
        	System.out.println("response: " + response.getResponse());
            transaction.toString(); // Transaction object in JSON format. After the transaction has  successfully completed, you can use this object to store the transaction in your application, thus enhancing the merchant's experience.
        }

    }
    @Override
    public void onReadFailed() {
        // ...
    }

    @Override
    public void onDisconnected() {
        // ...
    }

});

Ideally, you can use paymentService.sale(int, String, String, int, String) without having to initialize the Transaction object.

Pre-Auth Transaction

  • Card present: YES
Transaction transaction = new Transaction();

transaction.amount = "100";
transaction.ID = currentDateTime + postfix.toString();
transaction.dateTime = currentDateTime;
transaction.terminalNum = "TERMINALID";
transaction.msgType = Transaction.MessageType.PRE_AUTHORIZATION;
transaction.currency = Transaction.Currency.EUR;
transaction.receiptNum = "1";
transaction.txOrigin = "5";
transaction.sourceID = "1";
transaction.langCode = "EN";

try {
    paymentService.sendTransaction(transaction);
} catch (TransactionFieldException e) {
    throw new RuntimeException(e);
}

paymentService.listen(new ResponseListener() {
    @Override
    public void onResponseReceived(Response response) {
        
        if(response.responseCode.equals("0000")) {
            System.out.println("Transaction Approved");
        	System.out.println("response: " + response.getResponse());
            transaction.toString(); // Transaction object in JSON format. After the transaction has  successfully completed, you can use this object to store the transaction in your application, thus enhancing the merchant's experience.
        }

    }
    @Override
    public void onReadFailed() {
        // ...
    }

    @Override
    public void onDisconnected() {
        // ...
    }

});

For more on this type of transaction, see References.

Refund Transaction

  • Card present: YES

As we pointed out in Getting Started, a refund transaction is a whole other transaction that returns money to the customer's bank account in the event of a complaint or return. It is the same as a sale transaction, just with a different message type.

Transaction transaction = new Transaction();

transaction.amount = "100";
transaction.ID = currentDateTime + postfix.toString();
transaction.dateTime = currentDateTime;
transaction.terminalNum = "TERMINALID";
transaction.msgType = Transaction.MessageType.CREDIT_NOTE;
transaction.currency = Transaction.Currency.EUR;
transaction.receiptNum = "1";
transaction.txOrigin = "1";
transaction.sourceID = "1";
transaction.langCode = "EN";

try {
    paymentService.sendTransaction(transaction);
} catch (TransactionFieldException e) {
    throw new RuntimeException(e);
}

// Alternatively, you can use the short variant: paymentService.refund(12300000, currentDateTime + postfix, currentDateTime, 100, "EUR");

paymentService.listen(new ResponseListener() {
    @Override
    public void onResponseReceived(Response response) {
        
        if(response.responseCode.equals("0000")) {
            System.out.println("Transaction Approved");
        	System.out.println("response: " + response.getResponse());
            transaction.toString(); // Transaction object in JSON format. After the transaction has  successfully completed, you can use this object to store the transaction in your application, thus enhancing the merchant's experience.
        }

    }
    @Override
    public void onReadFailed() {
        // ...
    }

    @Override
    public void onDisconnected() {
        // ...
    }

});

Sale Plus Tip Transaction

  • Card present: YES
Transaction transaction = new Transaction();

transaction.amount = "100";
transaction.ID = currentDateTime + postfix.toString();
transaction.dateTime = currentDateTime;
transaction.terminalNum = "TERMINALID";
transaction.msgType = Transaction.MessageType.TIP_SALE;
transaction.currency = Transaction.Currency.EUR;
transaction.receiptNum = "1";
transaction.ecrdata = "TIP=10;"; // Tip in cents e.g. 10 = 0.10
transaction.txOrigin = "1";
transaction.sourceID = "1";
transaction.langCode = "EN";

try {
    paymentService.sendTransaction(transaction);
} catch (TransactionFieldException e) {
    throw new RuntimeException(e);
}

// Alternatively, you can use the short variant: paymentService.sale(12300000, currentDateTime + postfix, currentDateTime, 100, 10, "EUR");

paymentService.listen(new ResponseListener() {
    @Override
    public void onResponseReceived(Response response) {
        
        if(response.responseCode.equals("0000")) {
            System.out.println("Transaction Approved");
        	System.out.println("response: " + response.getResponse());
            transaction.toString(); // Transaction object in JSON format. After the transaction has  successfully completed, you can use this object to store the transaction in your application, thus enhancing the merchant's experience.
        }

    }
    @Override
    public void onReadFailed() {
        // ...
    }

    @Override
    public void onDisconnected() {
        // ...
    }

});

Capture Transaction

  • Card present: NO

NOTE: most card-not-present transactions require the original transaction ID to be specified.

public String padWithZeros(String str) {

    StringBuilder s = new StringBuilder();

    for(int i = 0; i < 20 - str.length(); i++) {
        s.append('0');
    }

    return s + str;
}

The exact original transaction ID is the identifier of the transaction being sent to the acquirer for authorization.

Transaction transaction = new Transaction();

transaction.amount = "100";
transaction.ID = currentDateTime + postfix.toString();
transaction.dateTime = currentDateTime;
transaction.terminalNum = "TERMINALID";
transaction.msgType = "0017";
transaction.currency = Transaction.Currency.EUR;
transaction.receiptNum = "1";
transaction.txOrigin = "4";
transaction.sourceID = "1";
transaction.langCode = "EN";
transaction.cardNum = "TXID" + padWithZeros("20240515230015056"); // The original transaction ID is padded with zeros in order to give it a length of 20.

try {
    paymentService.sendTransaction(transaction);
} catch (TransactionFieldException e) {
    throw new RuntimeException(e);
}

paymentService.listen(new ResponseListener() {
    @Override
    public void onResponseReceived(Response response) {
        
        if(response.responseCode.equals("0000")) {
            System.out.println("Transaction Approved");
        	System.out.println("response: " + response.getResponse());
            transaction.toString(); // Transaction object in JSON format. After the transaction has  successfully completed, you can use this object to store the transaction in your application, thus enhancing the merchant's experience.
        }

    }
    @Override
    public void onReadFailed() {
        // ...
    }

    @Override
    public void onDisconnected() {
        // ...
    }

});

Cancel Transaction

  • Card present: NO

NOTE: most card-not-present transactions require the original transaction ID to be specified.

To cancel a transaction, the original transaction ID needs to be specified. This ID is the exact identifier of the transaction being cancelled. The other fields, such as amount and currency, likewise have to be exact in order for the cancellation to be completed.

Transaction transaction = new Transaction();

transaction.amount = "100";
transaction.ID = currentDateTime + postfix.toString();
transaction.dateTime = currentDateTime;
transaction.terminalNum = "TERMINALID";
transaction.msgType = Transaction.MessageType.CANCEL;
transaction.currency = Transaction.Currency.EUR;
transaction.receiptNum = "1";
transaction.txOrigin = "2";
transaction.sourceID = "1";
transaction.originInd = "2"; // Transaction.TransactionOriginIndicator.MAIL_ORDER
transaction.langCode = "EN";
transaction.cardNum = "TXID" + padWithZeros("20240515230015056"); // The original transaction ID is padded with zeros in order to give it a length of 20.

try {
    paymentService.sendTransaction(transaction);
} catch (TransactionFieldException e) {
    throw new RuntimeException(e);
}

paymentService.listen(new ResponseListener() {
    @Override
    public void onResponseReceived(Response response) {
        
        if(response.responseCode.equals("0000")) {
            System.out.println("Transaction Cancelled");
        	System.out.println("response: " + response.getResponse());
            transaction.toString(); // Transaction object in JSON format. After the transaction has  successfully completed, you can use this object to store the transaction in your application, thus enhancing the merchant's experience.
        }

    }
    @Override
    public void onReadFailed() {
        // ...
    }

    @Override
    public void onDisconnected() {
        // ...
    }

});

Get Status

There are other types of requests beside transaction requests, such as terminal status, connection status and reconciliation request. These requests are treated as transaction requests as they have to be sent over TCP.

Terminal Status

Transaction transaction = new Transaction();

transaction.ID = currentDateTime + postfix.toString();
transaction.dateTime = currentDateTime;
transaction.msgType = Transaction.MessageType.GET_TERMINAL_STATUS;

try {
    paymentService.sendTransaction(transaction);
} catch (TransactionFieldException e) {
    throw new RuntimeException(e);
}

paymentService.listen(new ResponseListener() {
    @Override
    public void onResponseReceived(Response response) {
        System.out.println("response: " + response.getResponse());
    }
    @Override
    public void onReadFailed() {
        // ...
    }

    @Override
    public void onDisconnected() {
        // ...
    }

});

Connection Status

This is basically the same code as shown below except with a different message type constant: Transaction.MessageType.CONNECTION_STATUS.

Transaction transaction = new Transaction();

transaction.ID = currentDateTime + postfix.toString();
transaction.dateTime = currentDateTime;
transaction.msgType = Transaction.MessageType.CONNECTION_STATUS;

try {
    // Alternatively
    // paymentService.connectionStatus(String transactionID, String dateTime)
    paymentService.sendTransaction(transaction);
} catch (TransactionFieldException e) {
    throw new RuntimeException(e);
}

paymentService.listen(new ResponseListener() {
    @Override
    public void onResponseReceived(Response response) {
        System.out.println("response: " + response.getResponse());
    }
    @Override
    public void onReadFailed() {
        // ...
    }

    @Override
    public void onDisconnected() {
        // ...
    }

});

Reconciliation Request

  • Card present: NO

The reconciliation request feature makes it possible for the ECR to receive reconciliation data covering the time period from the last reconciliation to the current reconciliation. This functionality ensures that all transaction data within this period is accurately captured and processed, thus providing a clear and comprehensive overview of the financial activities and transactions that took place in the period in question.

Reconciliation data includes all sales, refunds and adjustments made during the specified period. By leveraging this feature, businesses can maintain accurate financial records, streamline their accounting processes and ensure compliance with financial reporting standards. This is especially beneficial for end-of-day or end-of-period financial reporting as it enables businesses to verify that all transactions have been correctly recorded and accounted for.

Transaction transaction = new Transaction();

transaction.ID = currentDateTime + postfix.toString();
transaction.dateTime = currentDateTime;
transaction.terminalNum = "TERMINALID";
transaction.msgType = Transaction.MessageType.RECONCILIATION_REQUEST;

try {
    paymentService.sendTransaction(transaction);
} catch (TransactionFieldException e) {
    throw new RuntimeException(e);
}

paymentService.listen(new ResponseListener() {
    @Override
    public void onResponseReceived(Response response) {
        
        if(response.responseCode.equals("0000")) {
            // Handle response.getReconciliationResponse()
			System.out.println(response.getReconciliationResponse());
        }

    }
    @Override
    public void onReadFailed() {
        // ...
    }

    @Override
    public void onDisconnected() {
        // ...
    }

});

The reconciliation response returned by getReconciliationResponse consists of the following JSON fields:

KeyDescription
"cardAID"Card brand AID (e.g. A0000000032020, A0000000043060, A000000025)
"cardProviderName"Card brand name (e.g. American Express, Visa, Mastercard, Bankaxept)
"numberOfDebit"Number of sale transactions
"debitAmount"Transaction amount (basic amount without refunds or tips)
"tip"Tip
"debitTotal"Total sum of basic amounts plus tips
"numberOfCredit"Number of refund transactions
"creditTotal"Total credit amount including refunds and tips

Here is an example of a reconciliation response:

{
  "cardtypeData":[
    {
      "cardAID":"A000000003",
      "cardProviderName":"VISA",
      "numberOfDebit":12345,
      "debitAmount":123.45,
      "tip":0.06,
      "debitTotal":1234.56,
      "numberOfCredit":9,
      "creditTotal":1234
    },
    {
      "cardAID":"A000000004",
      "cardProviderName":"MASTERCARD",
      "numberOfDebit":59,
      "debitAmount":0.12,
      "tip":0.03,
      "debitTotal":1.23,
      "numberOfCredit":5,
      "creditTotal":12
    },
    {
      "cardAID":"A000000025",
      "cardProviderName":"AMEX",
      "numberOfDebit":58,
      "debitAmount":0.11,
      "tip":0.03,
      "debitTotal":1.23,
      "numberOfCredit":5,
      "creditTotal":12
    },
    {
      "cardAID":"D5780000021010",
      "cardProviderName":"BANKAXEPT",
      "numberOfDebit":57,
      "debitAmount":0.1,
      "tip":0.03,
      "debitTotal":1.23,
      "numberOfCredit":5,
      "creditTotal":12
    }
  ],
  "numberOfDebitSum":99,
  "debitAmountSum":123.45,
  "tipSum":0.06,
  "debitSum":1234.56,
  "numberOfCreditSum":9,
  "creditAmountSum":1234
}

Abort Transaction

  • Card present: NO

By using SmartPOS on another ECR, this request allows a transaction that is in progress to be interrupted. The transaction in question is taking place on a terminal that is in card reader screen mode (as this is where, by default, NaTALI sits). 'AbortTransaction' is commonly used in Semi-Integrated Mode. For the detailed workflow of this use case, see Use Cases.

It is important to specify the terminal identifier for this request. This simulates the cancel button on the screen of that terminal.

Transaction transaction = new Transaction();

transaction.ID = currentDateTime + postfix.toString();
transaction.dateTime = currentDateTime;
transaction.terminalNum = "TERMINALID";
transaction.msgType = Transaction.MessageType.ABORT;
transaction.currency = Transaction.Currency.EUR; 
transaction.receiptNum = "1";
transaction.txOrigin = "1";
transaction.sourceID = "1";

try {
    paymentService.sendTransaction(transaction);
} catch (TransactionFieldException e) {
    throw new RuntimeException(e);
}

paymentService.listen(new ResponseListener() {
    @Override
    public void onResponseReceived(Response response) {
        
        if(response.responseCode.equals("0000")) {
            System.out.println("Transaction Aborted");
        	System.out.println("response: " + response.getResponse());
        }

    }
    @Override
    public void onReadFailed() {
        // ...
    }

    @Override
    public void onDisconnected() {
        // ...
    }

});

Card Balance Request

  • Card present: YES

If this request is successful, the balance amount is included in the response.

NOTE: this feature is only intended for specific voucher cards. It is not supported by standard payment cards.

Transaction transaction = new Transaction();

transaction.ID = currentDateTime + postfix.toString();
transaction.dateTime = currentDateTime;
transaction.terminalNum = "TERMINALID";
transaction.msgType = Transaction.MessageType.BALANCE;
transaction.originInd = "0"; // Transaction.TransactionOriginIndicator.PIN_PAD
transaction.txOrigin = "7";
transaction.sourceID = "1";

try {
    paymentService.sendTransaction(transaction);
} catch (TransactionFieldException e) {
    throw new RuntimeException(e);
}

paymentService.listen(new ResponseListener() {
    @Override
    public void onResponseReceived(Response response) {
        
        if(response.responseCode.equals("0000")) {
            String cardBalance = response.balanceAmount; // Received card balance.
            System.out.println("Card Balance: " + cardBalance);
        }

    }
    @Override
    public void onReadFailed() {
        // ...
    }

    @Override
    public void onDisconnected() {
        // ...
    }

});

QR and Barcode Scanner

  • Card present: NO
  • Supported devices:
    • SCANNER
      • N700
      • N910
    • SCANNER_CAMER
      • Any device with a camera

For this feature, NaTALI needs permission to access the camera. This can be set in Settings -> Apps -> NaTALI -> Permissions. The SCAN_ENABLED parameter in your terminal configuration also needs to be activated (by Bluefin TECS).

As well as some of the most common transactions, SmartPOS also supports QR and barcode scanning. This is intended for any kind of content that an ISV can make use of to provide their ECR applications and their customers with more features.

To get the scanned data, SmartPOS implements getScanData as a utility function of response class.

The following two scanner modes are used to triggered the scanner:

  • Silent scanner mode - the device scans QR codes and barcodes without displaying the camera view. Use message type - SCANNER to initiate this mode.
  • Camera view scanner mode - camera view is displayed when the scanner activates. Use message type - SCANNER_CAMERA to initiate this mode.
Transaction transaction = new Transaction();

transaction.ID = currentDateTime + postfix.toString();
transaction.dateTime = currentDateTime;
transaction.terminalNum = "TERMINALID";
transaction.msgType = Transaction.MessageType.SCANNER;

try {
    paymentService.sendTransaction(transaction);
} catch (TransactionFieldException e) {
    throw new RuntimeException(e);
}

paymentService.listen(new ResponseListener() {
    @Override
    public void onResponseReceived(Response response) {
        
        if(response.responseCode.equals("0000")) {
			String scanData = response.getScanData();
            // Use scanData
        }

    }
    @Override
    public void onReadFailed() {
        // ...
    }

    @Override
    public void onDisconnected() {
        // ...
    }

});

Offline Mode Transactions

  • Supported card reading methods:

    • Chip insert
  • Supported transactions:

    • Sale

Force Offline Mode

This transaction request allows an ECR to set the terminal to offline mode. In this mode, transactions such as sale, pre-auth and refund are added to the offline buffer and the offline transactions sit in the background. These can be retrieved using Offline Transation Pending Request. Once the device is rebooted or set back to online mode FTO=0, the pending offline transactions are sent to the payment gateway.

This feature can be turned on and off using the FTO parameter, as shown below.

Transaction transaction = new Transaction();

transaction.ID = currentDateTime + postfix.toString();
transaction.dateTime = currentDateTime;
transaction.terminalNum = "TERMINALID";
transaction.msgType = "0019";
transaction.ecrdata = "FTO=X;"; // where X: 1 - enable offline transactions, 0 - disable  

try {
    paymentService.sendTransaction(transaction);
} catch (TransactionFieldException e) {
    throw new RuntimeException(e);
}

paymentService.listen(new ResponseListener() {
    @Override
    public void onResponseReceived(Response response) {
        
        if(response.responseCode.equals("0000")) {
            System.out.println("Offline transactions turned on/off");
        	System.out.println("response: " + response.getResponse());
        }

    }
    @Override
    public void onReadFailed() {
        // ...
    }

    @Override
    public void onDisconnected() {
        // ...
    }

});
  • FTO=1
    • Force offline transactions; offline transactions enabled
  • FTO=0
    • Offline transactions disabled - back to online mode

Offline Transaction Pending Request

This transaction request receives the number of offline transactions waiting to be sent to the payment gateway.

Transaction transaction = new Transaction();

transaction.ID = currentDateTime + postfix.toString();
transaction.dateTime = currentDateTime;
transaction.terminalNum = "TERMINALID";
transaction.msgType = "0020";

try {
    paymentService.sendTransaction(transaction);
} catch (TransactionFieldException e) {
    throw new RuntimeException(e);
}

paymentService.listen(new ResponseListener() {
    @Override
    public void onResponseReceived(Response response) {
        
        if(response.responseCode.equals("0000")) {
			String receiptFooter = response.receiptFooter; // OFFLINE_TX:X, where X: the number of pending offline transactions
            // Use receiptFooter
        }

    }
    @Override
    public void onReadFailed() {
        // ...
    }

    @Override
    public void onDisconnected() {
        // ...
    }

});

SmartPOS Controller

Version 2.14.0

Java docs: https://doc.tecs.at/pubdoc/smartpos_sdk_javadoc/at/tecs/smartpos/SmartPOSController.html

The SmartPOS controller is supported from NaTALI version 1.48.0 on.

Supported by the SmartPOS SDK, the SmartPOS controller facilitates communication with the terminal's card module, thus enabling interaction with supported cards.

Importing SmartPOS Java Classes

import java.util.Arrays;
import java.util.ArrayList;

import at.tecs.smartpos.utils.Pair;
import at.tecs.ControlParser.Command;
import at.tecs.smartpos.CardControl;
import at.tecs.smartpos.SmartPOSController;
import at.tecs.smartpos.data.RFReturnCode;

import at.tecs.smartpos.data.PrinterPrintType;
import at.tecs.smartpos.data.PrinterReturnCode;

RF Communication

  • Supported devices:
    • All (with the exception of Sunmi devices, which are only able to read the UUID of an RF card).

To initiate communication with the card that is present, an RF interface is opened that waits for the card to be present. For Mifare cards, the user taps the card on or near the contactless card reader. The SmartPOSController class implements this using the asynchronous openCardReader function. This function sits in the background for the specified number of milliseconds without launching the card reader screen. The events in the lifecycle of this function are recorded using the OpenCardReaderListener interface methods.

Supported card types:

  • Mifare cards (Mifare Ultralight C and Mifare Classic) - commonly used for room cards and membership cards.
  • Command.CARD_TYPE.*
    • Card types A and B are high frequency (HF) RFID cards with a security layer or protocol, or payment cards.
    • Card types M0 and M1 are high frequency (HF) RFID cards without a security layer or protocol.

OpenListener Events Description

SmartPOS RF Communication - Read Block Example

SmartPOSController smartPOSController = new SmartPOSController();
// The RF interface opens for 10,000 milliseconds, accepting M0 card types.
// This operation is asynchronous. It is managed and its stages are handled using the OpenListener interface.
smartPOSController.openCardReader(10000, Command.CARD_TYPE.M0, new SmartPOSController.OpenCardReaderListener() {
    @Override
    public void onDetected(CardControl cardControl, int cardType, byte[] UUID) {
        // The CardControl instance enables communication with the card through the RF interface.
        RFReturnCode authenticationResult = cardControl.authenticateM0(new byte[] {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08});
        if(authenticationResult == RFReturnCode.SUCCESS) {
            // Writing test data to block with ID 0x01
            RFReturnCode writeResult = cardControl.writeBlock(0x01, new byte[] {0x01, 0x02, 0x03, 0x04, 0x05});
            if(writeResult == RFReturnCode.SUCCESS) {
                // Reading from block with ID 0x01
                Pair<RFReturnCode, byte[]> readResult = cardControl.readBlock(0x01);
                if(readResult.first == RFReturnCode.SUCCESS && readResult.second != null) {
                    // Printing read data
                    System.out.println("Read data : " + Arrays.toString(readResult.second));
                }
            }
        }
        // When the operations with RF communication are done, the interface is closed.
        cardControl.close();
    }
    @Override
    public void onError(RFReturnCode returnCode) {
        // Handle return code
    }
});

SmartPOS RF Communication - Reading UUID

The UUID (unique identifier) of a Mifare card can be used in several ways, depending on the application and the system it is integrated with. Some common practical uses are access control, public transportation, attendance tracking, loyalty programs, library systems, asset tracking and event management.

UUID reading is supported by all devices. Only Sunmi devices can do the reading; all other communication methods are excluded.

SmartPOSController smartPOSController = new SmartPOSController();

smartPOSController.openCardReader(10000, Command.CARD_TYPE.M1, new SmartPOSController.OpenCardReaderListener() {
    @Override
    public void onDetected(CardControl cardControl, int cardType, byte[] UUID) {
        StringBuilder hex_uuid = new StringBuilder();
        for (byte b : UUID) {
            hex_uuid.append(String.format("%02X ", b));
        }
        
        // Use UUID
        System.out.println(Arrays.toString(UUID));
        System.out.println(hex_uuid.toString());
        
        cardControl.close();
    }
    @Override
    public void onError(RFReturnCode returnCode) {
        // Handle return code
        System.out.println("Controller Error");
    }
});

SmartPOS RF Communication - RF Transmit Example

The transmit function handles raw transmission functionality, thus enabling bulk commands to be sent and their responses to be received. Authentication, using a key for a specific card, must happen before read/write commands.

Authentication and the CRC calculation are implemented in the SmartPOS Demo application.

SmartPOSController smartPOSController = new SmartPOSController();
// The RF interface opens for 10,000 milliseconds, accepting M0 card types.
// This operation is asynchronous and can be managed with the OpenListener interface for handling its stages.
smartPOSController.openCardReader(10000, Command.CARD_TYPE.M0, new SmartPOSController.OpenCardReaderListener() {
    @Override
    public void onDetected(CardControl cardControl, int cardType, byte[] UUID) {
        // The CardControl instance makes it possible to communicate with the card through the RF interface.
        ArrayList<byte[]> request = new ArrayList<>();
        // Read data from block 5 on Ultralight C card
        request.add(Utils.calcCrc(new byte[]{0x30,(byte) 0x05}));
        // Write data to block 5 on Ultralight C card
        request.add(Utils.calcCrc(new byte[]{(byte) 0xA2, 0x05, 0x01, 0x02, 0x03, 0x04}));
        // Perform communication with card
        Pair<RFReturnCode, ArrayList<byte[]>> responseTmp = cardControl.transmit(request);
        // The interface is closed when the operations with RF communication are done.
        cardControl.close();
    }
    @Override
    public void onError(RFReturnCode returnCode) {
        // Handle return code
    }
});

SmartPOS RF Communication - Close Example

You can close an open RF interface outside of the openCardReader scope using the closeCardReader function from the SmartPOSController class. This action also triggers the onError event in the OpenCardReaderListener, resulting in RFReturnCode.DISCONNECTED.

OpenCardReaderListener myListener = new SmartPOSController.OpenCardReaderListener() {
    // See above
});

// The RF interface opens for 10,000 milliseconds, accepting M0 card types
smartPOSController.openCardReader(10000, Command.CARD_TYPE.M0, myListener));
// Closing an RF interface that is outside the scope of RFOpen
if(smartPOSController.closeCardReader() == RFReturnCode.SUCCESS) {
    System.out.println("RF interface successfully closed!");
}

Printer

  • Supported devices
    • Newland N910
    • Newland N910 PRO
    • Newland N950

For a device to support this feature, DEVICE_CONTROL has to be enabled (by Bluefin TECS).

To initiate communication with the terminal printer, use the openPrinter function of the SmartPOSController class. Similarly to how the card reader is managed, printer operations are accessed through the PrinterControl instance. This instance is provided in the onOpened callback of OpenPrinterListener.

Here are some samples of what can be printed:

String imageBuffer = "Qk3+EgAAAAAAAD4AAAAoAAAAgAEAAGQAAAABAAEAAAAAAMAS...";
String receiptTicket = "         KUNDENBELEG\n\n\nTID: 88091137   MID: 102003557\nDate: 07/05/2021     Time: 15:30\n\n\nSALE                            \nVISA CREDIT                     \nPAN: ************0119          \nPAN SEQ NO: 01                  \nVISA ACQUIRER TEST/CARD 01     \n\n\nSTAN: 154714                   \nTXID: 20210507153032           \nORIG. TXID: 20210507153032     \nAPPROVAL CODE: 213031          \nINVOICE:1                      \nAC: F46E3CEA8232A966           \nAID: A0000000031010                \n\n\nEUR                        1.00\n\n\n           Authorized\n";

SmartPOSController smartPOSController = new SmartPOSController();
smartPOSController.openPrinter(new SmartPOSController.OpenPrinterListener() {
    @Override
    public void onOpened(PrinterControl printerControl) {
        PrinterReturnCode ret = printerControl.getStatus();
        if (ret != PrinterReturnCode.SUCCESS) {
            printerControl.close();
            return;
        }
        ret = printerControl.print(imageBuffer, PrinterPrintType.IMAGE);
        if (ret != PrinterReturnCode.SUCCESS) {
            printerControl.close();
            return;
        }
        printerControl.feedLine(2);
        ret = printerControl.print(receiptTicket, PrinterPrintType.TEXT);
        if (ret != PrinterReturnCode.SUCCESS) {
            printerControl.close();
            return;
        }
        printerControl.feedLine(2);
        ret = printerControl.print("https://www.tecs.at/", PrinterPrintType.QR_SMALL);
        if (ret != PrinterReturnCode.SUCCESS) {
            printerControl.close();
            return;
        }
        printerControl.feedLine(10);
        printerControl.close();
    }
    @Override
    public void onError(PrinterReturnCode printerReturnCode) {
        System.out.println("PRINTER ERROR");
        System.out.println(printerReturnCode);
        smartPOSController.closePrinter();
    }
});