All Downloads are FREE. Search and download functionalities are using the official Maven repository.

cl.transbank.pos.POS Maven / Gradle / Ivy

package cl.transbank.pos;

import cl.transbank.pos.exceptions.TransbankCannotOpenPortException;
import cl.transbank.pos.exceptions.TransbankInvalidPortException;
import cl.transbank.pos.exceptions.TransbankLinkException;
import cl.transbank.pos.exceptions.TransbankParseException;
import cl.transbank.pos.exceptions.TransbankPortNotConfiguredException;
import cl.transbank.pos.exceptions.TransbankUnexpectedError;
import cl.transbank.pos.helper.StringUtils;
import cl.transbank.pos.responses.CloseResponse;
import cl.transbank.pos.responses.DetailResponse;
import cl.transbank.pos.responses.RefundResponse;
import cl.transbank.pos.responses.KeysResponse;
import cl.transbank.pos.responses.SaleResponse;
import cl.transbank.pos.responses.TotalsResponse;
import cl.transbank.pos.utils.TbkBaudRate;
import cl.transbank.pos.utils.TbkReturn;
import cl.transbank.pos.utils.TransbankWrap;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import static cl.transbank.pos.helper.StringUtils.pad;

public class POS {

    private static final Logger logger = LogManager.getLogger(POS.class);

    public static final String NATIVE_TRANSBANK_WRAP = "NATIVE_TRANSBANK_WRAP";

    public static final String EMPTY_VARIABLE_ERROR = "The environment variable " + NATIVE_TRANSBANK_WRAP + " is empty. Please configure it correctly.";
    public static final String LIBRARY_LOAD_ERROR = "The Transbank native library could not be loaded. \n" +
            " To load this library the environment variable " + NATIVE_TRANSBANK_WRAP + " must point to the file (not the folder) with the native library. \n" +
            " Right now this variable " + NATIVE_TRANSBANK_WRAP + " has the value: ";
    public static final String CONFIGURE_BEFORE_TOTALS = "The port is not configured. Please configure it before trying to get the totals.";
    public static final String CONFIGURE_BEFORE_LAST_SALE = "The port is not configured. Please configure it before accessing the last sale.";
    public static final String CONFIGURE_BEFORE_SENDING_SALE = "The port is not configured. Please configure it before sending a sale.";
    public static final String CONFIGURE_BEFORE_REFUND_SALE = "The port is not configured. Please configure it before trying to refund a sale.";
    public static final String CONFIGURE_BEFORE_DETAILS = "The port is not configured. Please configure it before trying to obtain the sales details.";
    public static final String CONFIGURE_BEFORE_CLOSING = "The port is not configured. Please configure it before trying to close.";
    public static final String CONFIGURE_BEFORE_NORMAL = "The port is not configured. Please configure it before trying setting the POS to normal mode..";
    public static final String CONFIGURE_BEFORE_LOADING_KEYS = "The port is not configured. Please configure it before loading the keys.";
    public static final String CANNOT_OPEN_PORT = "Cannot open the port %s with baud rate %s.";
    public static final String CONFIGURE_BEFORE_POLL = "The port is not configured. Please configure it before polling the POS.";

    private static POS instance = null;

    private static final TbkBaudRate defaultBaudRate = TbkBaudRate.TBK_115200;

    private final String libraryPath;
    private final Port port;

    /**
     * The Constructor of the POS.
     * It's private so the user is supposed to use getInstance instead
     *
     * @param libraryPath the path of the dll (Windows) or .so (Linux) or dylib (Mac OS) with the native C Transbank library
     */
    private POS(String libraryPath) {
        port = new Port(null); //by setting the portname to null, we ensue the POS cannot be used just yet
        this.libraryPath = libraryPath;
    }

    /**
     * Factory method. It returns a POS instance. There should only be a single instance, so it returns a singleton.
     *
     * @return the POS singleton instance
     * @throws TransbankLinkException if it cannot find the native C Transbank library
     */
    public static POS getInstance() throws TransbankLinkException {
        if (instance == null) {
            String nativeTransbankWrapper = System.getenv(NATIVE_TRANSBANK_WRAP);
            logger.info("environment variable " + NATIVE_TRANSBANK_WRAP + " : " + nativeTransbankWrapper);
            if (StringUtils.isEmpty(nativeTransbankWrapper)) {
                throw new TransbankLinkException(EMPTY_VARIABLE_ERROR);
            }
            try {
                System.load(nativeTransbankWrapper);
                logger.debug("Native library loaded!");
            } catch (UnsatisfiedLinkError e) {
                throw new TransbankLinkException(LIBRARY_LOAD_ERROR + nativeTransbankWrapper, e);
            }
            instance = new POS(nativeTransbankWrapper);
        }
        return instance;
    }

    /**
     * It makes the POS load the keys from the Transbank servers. It does not actually return the keys to the caller.
     *
     * @return Whether the keys were loaded. Also the terminal id, function and commerce id
     * @throws TransbankPortNotConfiguredException if called before opening a port.
     */
    public KeysResponse loadKeys() throws TransbankPortNotConfiguredException {
        if (port.isConfigured()) {
            try {
                KeysResponse response = new KeysResponse(TransbankWrap.load_keys());
                logger.debug("load keys response: " + response);
                return response;
            } catch (Throwable e) {
                logger.error("Unexpected error when loading keys: " + e.getMessage(), e);
                throw new TransbankUnexpectedError("Unexpected error when loading keys: " + e.getMessage(), e);
            }
        } else {
            throw new TransbankPortNotConfiguredException(CONFIGURE_BEFORE_LOADING_KEYS);
        }
    }

    /**
     * This list the serial ports connected to the computer. The user should choose one to call open port.
     *
     * @return a list of port names
     * @throws TransbankLinkException if there are problems calling the native library
     */
    public List listPorts() throws TransbankLinkException {
        List result = new ArrayList<>();

        String list = null;
        try {
            list = TransbankWrap.list_ports();
        } catch (UnsatisfiedLinkError e) {
            logger.error(LIBRARY_LOAD_ERROR + libraryPath, e);
            throw new TransbankLinkException(LIBRARY_LOAD_ERROR + libraryPath, e);
        } catch (Throwable e) {
            logger.error("Unexpected error when listing ports: " + e.getMessage(), e);
            throw new TransbankUnexpectedError("Unexpected error when listing ports: " + e.getMessage(), e);
        }
        if (list != null) {
            String[] array = list.split("\\|");
            Collections.addAll(result, array);
        }
        return result;
    }

    /**
     * Just get the port that is opened right now.
     *
     * @return the portname currently opened
     * @throws TransbankLinkException in case it has to create the instance and the library load fails
     */
    public String getOpenPort() throws TransbankLinkException {
        return getInstance().port.getPortName();
    }

    /**
     * Opens a serial port with the default baud rate. Will silently succeed, not returning anything on success,
     * and throwing an exception on failure.
     *
     * @param portname the name of the port
     * @throws TransbankInvalidPortException    the port specified is empty or otherwise found invalid before trying to actually open
     * @throws TransbankCannotOpenPortException when there's a problem opening the port
     */
    public void openPort(String portname) throws TransbankInvalidPortException, TransbankCannotOpenPortException {
        openPort(portname, defaultBaudRate);
    }

    /**
     * Opens a serial port with the default baud rate. Will silently succeed, not returning anything on success,
     * and throwing an exception on failure.
     *
     * @param portname the name of the port
     * @param baudRate baud rate to use with the port
     * @throws TransbankInvalidPortException    the port specified is empty or otherwise found invalid before trying to actually open it
     * @throws TransbankCannotOpenPortException when there's a problem opening the port
     */
    public void openPort(String portname, TbkBaudRate baudRate) throws TransbankInvalidPortException, TransbankCannotOpenPortException {
        port.usePortname(portname);
        TbkReturn result = null;
        try {
            result = TransbankWrap.open_port(portname, baudRate.swigValue());
        } catch (Throwable e) {
            logger.error("Unexpected error when opening port: " + portname + ". Error message: " + e.getMessage(), e);
            throw new TransbankUnexpectedError("Unexpected error when opening port: " + portname + ". Error message: " + e.getMessage(), e);
        }
        if (result != TbkReturn.TBK_OK) {
            port.clearPortname();
            throw new TransbankCannotOpenPortException(String.format(CANNOT_OPEN_PORT, portname, baudRate));
        }
    }

    /**
     * This method just closes the port. The port name will be set null too.
     */
    public void closePort() {
        port.clearPortname();
        try {
            TransbankWrap.close_port();
        } catch (Throwable e) {
            logger.error("Unexpected error when closing port: " + e.getMessage(), e);
            throw new TransbankUnexpectedError("Unexpected error when closing port: " + e.getMessage(), e);
        }
    }

    /**
     * Checks whether the POS is nnected.
     *
     * @return boolean whether the POS is connected or not
     * @throws TransbankPortNotConfiguredException if called before opening a port.
     */
    public boolean poll() throws TransbankPortNotConfiguredException {
        if (port.isConfigured()) {
            TbkReturn response = null;
            try {
                response = TransbankWrap.poll();
            } catch (Throwable e) {
                logger.error("Unexpected error when polling the POS: " + e.getMessage(), e);
                throw new TransbankUnexpectedError("Unexpected error when polling the POS: " + e.getMessage(), e);
            }
            logger.debug("poll: response: " + response);
            return TbkReturn.TBK_OK.equals(response);
        } else {
            throw new TransbankPortNotConfiguredException(CONFIGURE_BEFORE_POLL);
        }
    }

    /**
     * Returns the total of the sales done with this POS.
     * As a side effect it will print it on the POS.
     *
     * @return Whether the operation succeeded, the amount of transactions, the total money transacted, the terminal and commerce id
     * @throws TransbankPortNotConfiguredException if called before opening a port.
     */
    public TotalsResponse getTotals() throws TransbankPortNotConfiguredException {
        if (port.isConfigured()) {
            TotalsResponse tresponse = null;
            try {
                tresponse = new TotalsResponse(TransbankWrap.get_totals());
            } catch (Throwable e) {
                logger.error("Unexpected error when obtaining the totals of the day: " + e.getMessage(), e);
                throw new TransbankUnexpectedError("Unexpected error when obtaining the totals of the day: " + e.getMessage(), e);
            }
            logger.debug("totals: response: " + tresponse);
            return tresponse;
        } else {
            throw new TransbankPortNotConfiguredException(CONFIGURE_BEFORE_TOTALS);
        }
    }

    /**
     * Returns the last sale done.
     * As a side effect, it prints it on the POS.
     *
     * @return the data of the last sale.
     * @throws TransbankPortNotConfiguredException if called before opening a port.
     */
    public SaleResponse getLastSale() throws TransbankPortNotConfiguredException {
        if (port.isConfigured()) {
            SaleResponse lsresponse = null;
            try {
                lsresponse = new SaleResponse(TransbankWrap.last_sale());
            } catch (Throwable e) {
                logger.error("Unexpected error when obtaining the last sale: " + e.getMessage(), e);
                throw new TransbankUnexpectedError("Unexpected error when obtaining the last sale: " + e.getMessage(), e);
            }
            logger.debug("last sale: response: " + lsresponse);
            return lsresponse;
        } else {
            throw new TransbankPortNotConfiguredException(CONFIGURE_BEFORE_LAST_SALE);
        }
    }

    /**
     * Facade method that receives an int. The ticket value can actually be anything, but some places assume an int
     *
     * @param amount
     * @param ticket
     * @return
     * @throws TransbankPortNotConfiguredException
     */
    public SaleResponse sale(int amount, int ticket) throws TransbankPortNotConfiguredException {
        return sale(amount, String.valueOf(ticket));
    }

    /**
     * Starts the sale process on the POS. Upon calling this, the final user must use the POS to sell something to a client.
     *
     * @param amount the amount sold, in whatever currency configured. Probably CLP.
     * @param ticket the number of the Boleta. it's a number, but in practice it will be padded / cut to six characters (padded with leftward 0s)
     * @return the data of the sale, including whether it succeeded at all
     * @throws TransbankPortNotConfiguredException if called before opening a port.
     */
    public SaleResponse sale(int amount, String ticket) throws TransbankPortNotConfiguredException {
        String strTicket = StringUtils.padStr(ticket, 6);
        if (port.isConfigured()) {
            SaleResponse sr = null;
            try {
                sr = new SaleResponse(TransbankWrap.sale(amount, strTicket, false));
            } catch (Throwable e) {
                logger.error("Unexpected error while selling: " + e.getMessage(), e);
                throw new TransbankUnexpectedError("Unexpected error while selling: " + e.getMessage(), e);
            }
            logger.debug("sale: response: " + sr);
            return sr;
        } else {
            throw new TransbankPortNotConfiguredException(CONFIGURE_BEFORE_SENDING_SALE);
        }
    }

    /**
     * Does a refund of a sale. It needs the final user to swipe the client's card through the POS and insert an authorization PIN.
     *
     * @param operationId the operation id of the sale. This was returned when the sale was done,
     *                    or it can be seen in the last sale result, or the details result
     * @return The data of the refund, including whether it succeeded, and some other data like the terminal id, the commerce id, etc.
     * @throws TransbankPortNotConfiguredException if called before opening a port.
     */
    public RefundResponse refund(int operationId) throws TransbankPortNotConfiguredException {
        if (port.isConfigured()) {
            RefundResponse rr = null;
            try {
                rr = new RefundResponse(TransbankWrap.refund(operationId));
            } catch (Throwable e) {
                logger.error("Unexpected error while refunding a sale: " + e.getMessage(), e);
                throw new TransbankUnexpectedError("Unexpected error while refunding a sale: " + e.getMessage(), e);
            }
            logger.debug("refund: response: " + rr);
            return rr;
        } else {
            throw new TransbankPortNotConfiguredException(CONFIGURE_BEFORE_REFUND_SALE);
        }
    }

    /**
     * Obtains the data of the last few sales. If the printOnPos param is true, it will use the POS's printer to print them
     * but it will return an empty list to the user. If the param is false, it won't print on the POS but it will actually
     * return the data in the List to the user.
     *
     * @param printOnPos whether to print the list on the POS or not. Printing on the POS and receiving the data electronically
     *                   are mutually exclusive.
     * @return a list of the sales done this (logical) day, but only if the printOnPos param is false. Otherwise, an empty list.
     * @throws TransbankPortNotConfiguredException if called before opening a port.
     */
    public List details(boolean printOnPos) throws TransbankPortNotConfiguredException {
        if (port.isConfigured()) {
            String data = null;
            try {
                data = TransbankWrap.sales_detail(printOnPos);
            } catch (Throwable e) {
                logger.error("Unexpected error while obtaining the details of all the sales: " + e.getMessage(), e);
                throw new TransbankUnexpectedError("Unexpected error while obtaining the details of all the sales: " + e.getMessage(), e);
            }
            logger.debug("details: raw line " + data + "\n;");
            String[] lines = (data == null) ? new String[]{} : data.split("\n");
            List ldr = new ArrayList<>();
            for (String line : lines) {
                if (StringUtils.notEmpty(line)) {
                    try {
                        DetailResponse dr = new DetailResponse(line);
                        logger.debug("refund: response: " + dr);
                        ldr.add(dr);
                    } catch (TransbankParseException e) {
                        //if the parsing of the line fails, let's just skip it and go to the next one. Still, we log it
                        logger.debug("Non fatal: parse error when parsing '" + line + "'", e);
                    }
                }
            }
            return ldr;
        } else {
            throw new TransbankPortNotConfiguredException(CONFIGURE_BEFORE_DETAILS);
        }
    }

    /**
     * Closes the day, wiping the sales done from the POS memory (so List and Last Sale will return empty) and loading the keys
     *
     * @return The same data as the load_keys operation
     * @throws TransbankPortNotConfiguredException if called before opening a port.
     */
    public CloseResponse close() throws TransbankPortNotConfiguredException {
        if (port.isConfigured()) {
            CloseResponse cr = null;
            try {
                cr = new CloseResponse(TransbankWrap.close());
            } catch (Throwable e) {
                logger.error("Unexpected error while closing the day: " + e.getMessage(), e);
                throw new TransbankUnexpectedError("Unexpected error while closing the day: " + e.getMessage(), e);
            }
            logger.debug("sale: response: " + cr);
            return cr;
        } else {
            throw new TransbankPortNotConfiguredException(CONFIGURE_BEFORE_CLOSING);
        }
    }

    /**
     * Takes the POS out of integrated mode, thus making impossible to keep using it through the SDK without going
     * through the "Comercio" menu on the physical POS
     *
     * @return whether it succeeded. It probably did unless it was disconnected already.
     * @throws TransbankPortNotConfiguredException if called before opening a port.
     */
    public boolean setNormalMode() throws TransbankPortNotConfiguredException {
        if (port.isConfigured()) {
            TbkReturn result = null;
            try {
                result = TransbankWrap.set_normal_mode();
            } catch (Throwable e) {
                logger.error("Unexpected error while disconnecting the POS and setting it to normal mode: " + e.getMessage(), e);
                throw new TransbankUnexpectedError("Unexpected error while disconnecting the POS and setting it to normal mode: " + e.getMessage(), e);
            }
            logger.debug("normal mode: response: " + result);
            return TbkReturn.TBK_OK.equals(result);
        } else {
            throw new TransbankPortNotConfiguredException(CONFIGURE_BEFORE_NORMAL);
        }
    }
}

class Port {
    public static final String EMPTY_PORT_NAME = "Empty port name specified";
    private String portName = null;

    protected String getPortName() {
        return portName;
    }

    protected void usePortname(String port) throws TransbankInvalidPortException {
        if (StringUtils.isEmpty(port)) {
            throw new TransbankInvalidPortException(EMPTY_PORT_NAME);
        }
        this.portName = port;
    }

    protected void clearPortname() {
        this.portName = null;
    }

    protected boolean isConfigured() {
        return StringUtils.notEmpty(portName);
    }

    protected Port(String port) {
        this.portName = port;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy