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

hbci4java.Hbci4JavaBanking Maven / Gradle / Ivy

There is a newer version: 5.5.43
Show newest version
package hbci4java;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import domain.*;
import exception.InvalidPinException;
import exception.PaymentException;
import org.apache.commons.lang3.StringUtils;
import org.kapott.hbci.GV.GVTAN2Step;
import org.kapott.hbci.GV.HBCIJob;
import org.kapott.hbci.GV.HBCIJobImpl;
import org.kapott.hbci.GV_Result.GVRDauerList;
import org.kapott.hbci.GV_Result.GVRKUms;
import org.kapott.hbci.GV_Result.GVRSaldoReq;
import org.kapott.hbci.exceptions.HBCI_Exception;
import org.kapott.hbci.manager.HBCIDialog;
import org.kapott.hbci.manager.HBCIHandler;
import org.kapott.hbci.manager.HBCIUtils;
import org.kapott.hbci.passport.HBCIPassport;
import org.kapott.hbci.status.HBCIExecStatus;
import org.kapott.hbci.structures.Konto;
import org.kapott.hbci.structures.Value;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import spi.OnlineBankingService;
import utils.Utils;

import java.io.IOException;
import java.io.InputStream;
import java.time.ZoneId;
import java.util.*;
import java.util.stream.Collectors;

public class Hbci4JavaBanking implements OnlineBankingService {

    private static final Logger LOG = LoggerFactory.getLogger(Hbci4JavaBanking.class);
    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    public Hbci4JavaBanking() {
        try (InputStream inputStream = HBCIUtils.class.getClassLoader().getResource("blz.properties").openStream()) {
            HBCIUtils.refreshBLZList(inputStream);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        OBJECT_MAPPER.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
        OBJECT_MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        OBJECT_MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        OBJECT_MAPPER.registerModule(new Jdk8Module());
    }

    @Override
    public BankApi bankApi() {
        return BankApi.HBCI;
    }

    @Override
    public boolean externalBankAccountRequired() {
        return false;
    }

    @Override
    public boolean userRegistrationRequired() {
        return false;
    }

    @Override
    public BankApiUser registerUser(String uid) {
        //no registration needed
        return null;
    }

    @Override
    public void removeUser(BankApiUser bankApiUser) {
        //not needed
    }

    @Override
    public boolean bookingsCategorized() {
        return false;
    }

    @Override
    public List loadBankAccounts(BankApiUser bankApiUser, BankAccess bankAccess, String bankCode, String pin, boolean storePin) {
        LOG.info("Loading Account list for access {}", bankAccess.getBankCode());
        HbciPassport hbciPassport = createPassport(bankAccess, bankCode, null, pin);
        HBCIHandler hbciHandler = null;
        try {
            hbciHandler = new HBCIHandler(hbciPassport);
            bankAccess.setBankName(hbciPassport.getInstName());
            List hbciAccounts = new ArrayList<>();
            for (Konto konto : hbciPassport.getAccounts()) {
                BankAccount bankAccount = HbciMapping.toBankAccount(konto);
                bankAccount.externalId(bankApi(), UUID.randomUUID().toString());
                bankAccount.bankName(bankAccess.getBankName());
                hbciAccounts.add(bankAccount);
            }
            if (hbciPassport.getState().isPresent()) {
                bankAccess.setHbciPassportState(hbciPassport.getState().get().toJson());
            }

            updateTanTransportTypes(bankAccess, hbciPassport);

            return hbciAccounts;
        } catch (HBCI_Exception e) {
            handleHbciException(e);
            return null;
        } finally {
            if (hbciHandler != null) {
                hbciHandler.close();
            }
        }
    }

    @Override
    public void removeBankAccount(BankAccount bankAccount, BankApiUser bankApiUser) {
        //not needed
    }

    @Override
    public LoadBookingsResponse loadBookings(BankApiUser bankApiUser, BankAccess bankAccess, String bankCode, BankAccount bankAccount, String pin) {
        HbciPassport hbciPassport = createPassport(bankAccess, bankCode, null, pin);
        HBCIHandler hbciHandler = new HBCIHandler(hbciPassport);
        try {
            Konto account = hbciPassport.getAccount(bankAccount.getAccountNumber());

            HBCIJob balanceJob = hbciHandler.newJob("SaldoReq");
            balanceJob.setParam("my", account);
            balanceJob.addToQueue();

            HBCIJob bookingsJob = hbciHandler.newJob("KUmsAll");
            bookingsJob.setParam("my", account);
            if (bankAccount.getLastSync() != null) {
                bookingsJob.setParam("startdate", Date.from(bankAccount.getLastSync().atZone(ZoneId.systemDefault()).toInstant()));
            }
            bookingsJob.addToQueue();

            HBCIJob standingOrdersJob = hbciHandler.newJob("DauerSEPAList");
            standingOrdersJob.setParam("src", account);
            if (((HBCIJobImpl) standingOrdersJob).getHBCICode() == null) {
                LOG.warn("GV DauerSEPAList not supported");
            } else {
                standingOrdersJob.addToQueue();
            }

            // Let the Handler execute all jobs in one batch
            HBCIExecStatus status = hbciHandler.execute();
            if (!status.isOK()) {
                LOG.error("Status of SaldoReq+KUmsAll+DauerSEPAList batch job not OK " + status);
            }

            if (hbciPassport.getState().isPresent()) {
                bankAccess.setHbciPassportState(hbciPassport.getState().get().toJson());
            }

            List bookings = HbciMapping.createBookings((GVRKUms) bookingsJob.getJobResult());

            List standingOrders = HbciMapping.createStandingOrders((GVRDauerList) standingOrdersJob.getJobResult());

            ArrayList bookingList = bookings.stream()
                    .collect(Collectors.collectingAndThen(Collectors.toCollection(
                            () -> new TreeSet<>(Comparator.comparing(Booking::getExternalId))), ArrayList::new));

            bookingList.forEach(booking -> {
                booking.setCreditorId((Utils.extractCreditorId(booking.getUsage())));
                booking.setMandateReference(Utils.extractMandateReference(booking.getUsage()));
            });

            updateTanTransportTypes(bankAccess, hbciPassport);

            return LoadBookingsResponse.builder()
                    .bookings(bookingList)
                    .bankAccountBalance(HbciMapping.createBalance((GVRSaldoReq) balanceJob.getJobResult()))
                    .standingOrders(standingOrders)
                    .build();
        } catch (HBCI_Exception e) {
            handleHbciException(e);
            return null;
        } finally {
            hbciHandler.close();
        }
    }

    @Override
    public void createPayment(BankApiUser bankApiUser, BankAccess bankAccess, String bankCode, BankAccount bankAccount, String pin, Payment payment) {
        HbciTanSubmit hbciTanSubmit = HbciTanSubmit.builder().build();

        HbciPassport hbciPassport = createPassport(bankAccess, bankCode, new HbciCallback() {

            @Override
            public boolean tanCallback(HBCIPassport passport, GVTAN2Step hktan) {
                return updateTanSubmit((HbciPassport) passport, hktan, hbciTanSubmit, payment);
            }

        }, pin);

        HBCIHandler hbciHandler = new HBCIHandler(hbciPassport, null, true, false);

        try {
            Konto src = hbciPassport.getAccount(bankAccount.getAccountNumber());

            Konto dst = new Konto();
            dst.name = payment.getReceiver();
            dst.bic = payment.getReceiverBic();
            dst.iban = payment.getReceiverIban();

            Value value = new Value(payment.getAmount());

            HBCIJobImpl uebSEPA = hbciHandler.newJob("UebSEPA");
            uebSEPA.setParam("src", src);
            uebSEPA.setParam("dst", dst);
            uebSEPA.setParam("btg", value);
            uebSEPA.setParam("usage", payment.getPurpose());
            uebSEPA.addToQueue();

            HBCIExecStatus status = hbciHandler.execute();
            if (!status.isOK()) {
                throw new PaymentException(status.getDialogStatus().getErrorString());
            }

            if (hbciPassport.getState().isPresent()) {
                bankAccess.setHbciPassportState(hbciPassport.getState().get().toJson());
            }

            hbciTanSubmit.setDialogId(hbciHandler.getDialog().getDialogID());
            hbciTanSubmit.setMsgNum(hbciHandler.getDialog().getMsgnum());
            payment.setTanSubmitExternal(hbciTanSubmit);
        } catch (HBCI_Exception e) {
            handleHbciException(e);
        } finally {
            hbciHandler.close();
        }
    }

    private boolean updateTanSubmit(HbciPassport passport, GVTAN2Step hktan, HbciTanSubmit hbciTanSubmit, Payment payment) {
        try {
            hbciTanSubmit.setGvTanSubmit(HbciGVTanSubmit.builder()
                    .properties(OBJECT_MAPPER.writeValueAsString(hktan.getLowlevelParams()))
                    .build());

            hbciTanSubmit.setHbciPassport(OBJECT_MAPPER.writeValueAsString(passport.clone()));

            Object pintan_challenge = passport.getPersistentData("pintan_challenge");
            if (pintan_challenge != null) {
                payment.setPaymentChallenge(PaymentChallenge.builder().title(pintan_challenge.toString()).build());
            }

            return true;
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void submitPayment(Payment payment, String tan) {
        HbciTanSubmit hbciTanSubmit = (HbciTanSubmit) payment.getTanSubmitExternal();

        HbciPassport hbciPassport;
        try {
            hbciPassport = OBJECT_MAPPER.readValue(hbciTanSubmit.getHbciPassport(), HbciPassport.class);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        hbciPassport.unsetComm();
        hbciPassport.setCallback(new HbciCallback());
        hbciPassport.getUPD().put("_fetchedMetaInfo", new Date());

        HBCIDialog hbciDialog = new HBCIDialog(hbciPassport, null);
        hbciDialog.setDialogid(hbciTanSubmit.getDialogId());
        hbciDialog.setMsgnum(hbciTanSubmit.getMsgNum());

        HBCIHandler handler = new HBCIHandler(hbciPassport, hbciDialog, false, true);
        hbciPassport.setParentHandlerData(handler);

        try {
            GVTAN2Step hktan = (GVTAN2Step) handler.newJob("TAN2Step");
            hktan.setLowlevelParams(OBJECT_MAPPER.readValue(hbciTanSubmit.getGvTanSubmit().getProperties(), Properties.class));
            handler.getDialog().getMessages().get(handler.getDialog().getMessages().size() - 1).add(hktan);
            hktan.getMainPassport().setCallback(new HbciCallback() {

                @Override
                public void callback(HBCIPassport passport, int reason, String msg, int datatype, StringBuffer retData) {
                    if (reason == NEED_PT_TAN) {
                        retData.append(tan);
                    } else {
                        super.callback(passport, reason, msg, datatype, retData);
                    }
                }

            });

            HBCIExecStatus status = hktan.getParentHandler().execute();
            if (!status.isOK()) {
                throw new PaymentException(status.getDialogStatus().getErrorString());
            }
        } catch (HBCI_Exception e) {
            handleHbciException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            handler.close();
        }
    }

    @Override
    public boolean bankSupported(String bankCode) {
        org.kapott.hbci.manager.BankInfo bankInfo = HBCIUtils.getBankInfo(bankCode);
        if (bankInfo == null || bankInfo.getPinTanVersion() == null) {
            return false;
        }
        return true;
    }

    private void updateTanTransportTypes(BankAccess bankAccess, HbciPassport hbciPassport) {
        if (bankAccess.getTanTransportTypes() == null) {
            bankAccess.setTanTransportTypes(new HashMap<>());
        }
        bankAccess.getTanTransportTypes().put(bankApi(), new ArrayList<>());

        if (hbciPassport.getUPD() != null) {
            hbciPassport.getAllowedTwostepMechanisms().forEach(id -> {
                Properties properties = hbciPassport.getTwostepMechanisms().get(id);

                if (properties != null) {
                    bankAccess.getTanTransportTypes().get(bankApi()).add(
                            TanTransportType.builder()
                                    .id(id)
                                    .name(properties.getProperty("name") != null ? properties.getProperty("name") : null)
                                    .medium(hbciPassport.getUPD().getProperty("tanmedia.names"))
                                    .build()
                    );
                } else {
                    LOG.warn("unable find transport type {} for bank code {}", id, bankAccess.getBankCode());
                }
            });
        } else {
            LOG.warn("missing passport upd, unable find transport types or bank code {}", bankAccess.getBankCode());
        }
    }

    private HbciPassport createPassport(BankAccess bankAccess, String bankCode, HbciCallback callback, String pin) {
        Properties properties = new Properties();
        properties.put("kernel.rewriter", "InvalidSegment,WrongStatusSegOrder,WrongSequenceNumbers,MissingMsgRef,HBCIVersion,SigIdLeadingZero,InvalidSuppHBCIVersion,SecTypeTAN,KUmsDelimiters,KUmsEmptyBDateSets");
        properties.put("log.loglevel.default", "2");
        properties.put("default.hbciversion", "FinTS3");
        properties.put("client.passport.PinTan.checkcert", "1");
        properties.put("client.passport.PinTan.init", "1");
        properties.put("client.errors.ignoreJobNotSupported", "yes");

        properties.put("client.passport.country", "DE");
        properties.put("client.passport.blz", bankCode != null ? bankCode : bankAccess.getBankCode());
        properties.put("client.passport.customerId", bankAccess.getBankLogin());
        properties.put("client.errors.ignoreCryptErrors", "yes");

        if (StringUtils.isNotBlank(bankAccess.getBankLogin2())) {
            properties.put("client.passport.userId", bankAccess.getBankLogin2());
        }

        HbciPassport passport = new HbciPassport(bankAccess.getHbciPassportState(), properties, callback, null);
        passport.setPIN(pin);

        return passport;
    }

    private void handleHbciException(HBCI_Exception e) throws InvalidPinException {
        Throwable processException = e;
        while (processException.getCause() != null && !(processException.getCause() instanceof InvalidPinException)) {
            processException = processException.getCause();
        }

        if (processException.getCause() != null && processException.getCause() instanceof InvalidPinException) {
            throw (InvalidPinException) processException.getCause();
        }

        throw e;
    }


}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy