Added functionality to fetch account statement as MT940 from LNBits wallet transactions

This commit is contained in:
Martin Berger 2023-02-12 12:09:21 +01:00
parent c4bac38036
commit 508c05da22
9 changed files with 303 additions and 17 deletions

View file

@ -9,7 +9,7 @@ Fueli FinTS is a server implementing the financial transaction services (FinTS)
4. Added example configuration files and db script to set up a rudimentary database.
5. Some minor refactoring and cleanups
**Please find a complete list of all changes to the original implementation by this fork with this [Github Compare](https://github.com/petafuel/FueliFinTS/compare/main...drmartinberger:main) view.**
**Please find a complete list of all changes to the original implementation by this fork with this [Github Compare](https://github.com/petafuel/FueliFinTS/compare/main...drmartinberger:lnbits-via-mt940) view.**
# Setup Steps

View file

@ -0,0 +1,4 @@
lnbitsUrl = <url to LNBits instance>
uriWallet = /api/v1/wallet
uriPayments = /api/v1/payments
xApiKey = <x-api-key of LNBits wallet>

View file

@ -119,6 +119,11 @@
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.9.1</version>
</dependency>
<dependency>
<groupId>com.prowidesoftware</groupId>
<artifactId>pw-swift-core</artifactId>
<version>SRU2022-9.3.12</version>
</dependency>
</dependencies>
<build>
<defaultGoal>clean install</defaultGoal>

View file

@ -0,0 +1,64 @@
package lnbits;
import com.prowidesoftware.swift.model.field.Field61;
import com.prowidesoftware.swift.model.field.Field86;
import org.apache.commons.lang3.StringUtils;
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
import org.codehaus.jackson.annotate.JsonProperty;
import java.text.SimpleDateFormat;
import java.util.Date;
@JsonIgnoreProperties(ignoreUnknown = true)
public class LnTransaction {
public LnTransaction() { super(); }
public Field61 toField61() {
// id: max 16 chars
String id = StringUtils.left(checkingId, 7) + "..." + StringUtils.right(checkingId, 6);
return new Field61()
.setValueDate(new SimpleDateFormat("yyMMdd").format(getTime()))
.setDebitCreditMark(this.amount >= 0 ? "C" : "D")
.setAmount(Math.abs(this.amount+this.fee)/1000L)
.setTransactionType("NMSC")
.setIdentificationCode(id);
}
public Field86 toField86(){
return new Field86().setComponent1(this.memo);
}
public Long getAmount() {
return amount;
}
public Long getFee() {
return fee;
}
public Date getTime() {
return new Date(this.time*1000);
}
public Boolean getPending() { return pending; }
@JsonProperty("amount")
private Long amount;
@JsonProperty("fee")
private Long fee;
@JsonProperty("memo")
private String memo;
// time in seconds since epoch
@JsonProperty("time")
private Long time;
@JsonProperty("checking_id")
private String checkingId;
@JsonProperty("pending")
private Boolean pending;
}

View file

@ -0,0 +1,150 @@
package lnbits;
import com.prowidesoftware.swift.model.field.*;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHeaders;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.type.TypeReference;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Properties;
import java.util.stream.Collectors;
public class LnbitsHelper {
private static String lnbitsUrl;
private static String uriWallet;
private static String uriPayments;
private static String xApiKey;
private static void loadConfiguration() {
Properties properties = new Properties();
try {
BufferedInputStream stream = new BufferedInputStream(new FileInputStream("config/lnbits.properties"));
properties.load(stream);
stream.close();
lnbitsUrl = properties.getProperty("lnbitsUrl");
uriWallet = properties.getProperty("uriWallet");
uriPayments = properties.getProperty("uriPayments");
xApiKey = properties.getProperty("xApiKey");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static Collection<Field> getMt940Fields(java.util.Date from, java.util.Date to) throws IOException {
loadConfiguration();
Collection<Field> mt940Fields = new ArrayList<>();
Long currentBalance = getCurrentBalance();
Collection<LnTransaction> afterFromSettledPayments = getPayments()
.stream()
.filter(tx -> tx.getTime().after(from) && !tx.getPending())
.collect(Collectors.toList());
Long fromBalance = currentBalance - afterFromSettledPayments
.stream()
.map(tx -> tx.getAmount() + tx.getFee())
.reduce(0L, Long::sum);
Collection<LnTransaction> fromToPayments = afterFromSettledPayments
.stream()
.filter(tx -> tx.getTime().before(to))
.collect(Collectors.toList());
Long toBalance = fromBalance + fromToPayments
.stream()
.map(tx -> tx.getAmount() + tx.getFee())
.reduce(0L, Long::sum);
// initialBalance
String simpleFrom = new SimpleDateFormat("yyMMdd").format(from);
mt940Fields.add(new Field60F()
.setDCMark("C")
.setDate(simpleFrom)
.setCurrency("EUR")
.setAmount(fromBalance/1000L)
);
for (LnTransaction tx : fromToPayments) {
mt940Fields.add(tx.toField61());
mt940Fields.add(tx.toField86());
}
// finalBalance
String simpleTo = new SimpleDateFormat("yyMMdd").format(from);
mt940Fields.add(new Field62F()
.setDCMark("C")
.setDate(simpleTo)
.setCurrency("EUR")
.setAmount(toBalance/1000L)
);
return mt940Fields;
}
private static Collection<LnTransaction> getPayments() throws IOException {
CloseableHttpClient httpClient = HttpClients.createDefault();
try {
HttpGet request = new HttpGet(lnbitsUrl + uriPayments);
request.addHeader("X-API-KEY", xApiKey);
request.addHeader(HttpHeaders.ACCEPT, "application/json");
CloseableHttpResponse response = httpClient.execute(request);
try {
HttpEntity entity = response.getEntity();
if (entity != null && response.getStatusLine().getStatusCode() == 200) {
String result = EntityUtils.toString(entity);
return new ObjectMapper().readValue(result, new TypeReference<List<LnTransaction>>(){});
} else {
throw new RuntimeException("Request failed");
}
} finally {
response.close();
}
} catch (ClientProtocolException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
httpClient.close();
}
}
private static Long getCurrentBalance() throws IOException {
CloseableHttpClient httpClient = HttpClients.createDefault();
try {
HttpGet request = new HttpGet(lnbitsUrl + uriWallet);
request.addHeader("X-API-KEY", xApiKey);
request.addHeader(HttpHeaders.ACCEPT, "application/json");
CloseableHttpResponse response = httpClient.execute(request);
try {
HttpEntity entity = response.getEntity();
if (entity != null && response.getStatusLine().getStatusCode() == 200) {
String result = EntityUtils.toString(entity);
WalletDetails walletDetails = new ObjectMapper().readValue(result, WalletDetails.class);
return walletDetails.getBalance();
} else {
throw new RuntimeException("Request failed");
}
} finally {
response.close();
}
} catch (ClientProtocolException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
httpClient.close();
}
}
}

View file

@ -0,0 +1,16 @@
package lnbits;
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
import org.codehaus.jackson.annotate.JsonProperty;
@JsonIgnoreProperties(ignoreUnknown = true)
public class WalletDetails {
public WalletDetails() { super(); }
public Long getBalance() {
return balance;
}
@JsonProperty("balance")
private Long balance;
}

View file

@ -20,6 +20,7 @@ import net.petafuel.jsepa.exception.SEPAParsingException;
import net.petafuel.jsepa.model.*;
import net.petafuel.jsepa.util.BankDateCalculator;
import net.petafuel.jsepa.util.SepaUtils;
import net.petafuel.mt94x.Mt940Helper;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@ -1311,7 +1312,11 @@ public class Banking2Access implements DataAccessFacade {
*/
@Override
public byte[] getGebuchteUmsaetze(String kontonummer, Date vonDatum, Date bisDatum, LegitimationInfo legitimationsInfo) {
return database.getGebuchteUmsaetze(kontonummer, vonDatum, bisDatum, legitimationsInfo);
try {
return Mt940Helper.getMT940s(kontonummer, vonDatum, bisDatum);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**

View file

@ -0,0 +1,37 @@
package net.petafuel.mt94x;
import com.prowidesoftware.swift.model.BIC;
import com.prowidesoftware.swift.model.field.*;
import com.prowidesoftware.swift.model.mt.mt9xx.MT940;
import lnbits.LnbitsHelper;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Mt940Helper {
public static byte[] getMT940s(String kontonummer, java.util.Date vonDatum, java.util.Date bisDatum) throws IOException {
if(vonDatum == null) {
vonDatum = new Date(0);
}
if(bisDatum == null) {
bisDatum = new Date();
}
return getMT940(kontonummer, vonDatum, bisDatum).getBytes();
}
private static String getMT940(String kontonummer, java.util.Date vonDatum, java.util.Date bisDatum) throws IOException {
String simpleDate = new SimpleDateFormat("yyMMdd").format(bisDatum);
MT940 a = new MT940();
a.setSender(new BIC("NOOODE00BIC"));
a.setReceiver(new BIC("NOOODE00BIC"));
a.addField(new Field20(simpleDate)); // Transaktionsnummer
a.addField(new Field21("NONREF")); // Bezugsreferenznummer
a.addField(new Field25(kontonummer)); // Kontobezeichnung
a.addField(new Field28C((simpleDate))); // Auszugsnummer
for(Field f : LnbitsHelper.getMt940Fields(vonDatum, bisDatum)) {
a.addField(f);
}
return a.message();
}
}

35
third_party/README.md vendored
View file

@ -1,6 +1,6 @@
# Third Party Licenses
Fueli FinTS is build on top of other open source projects. Their licence files are included in the source code's 'third_party' folder. The third party software included and/or used by Fueli FinTS is: (in alphabetical order)
This fork of Fueli FinTS is build on top of other open source projects. Their licence files are included in the source code's 'third_party' folder. The third party software included and/or used by Fueli FinTS is: (in alphabetical order)
- Apache Commons Lang
Copyright 2001-2021 The Apache Software Foundation
@ -58,27 +58,32 @@ Fueli FinTS is build on top of other open source projects. Their licence files a
- MySQL Connector/J
Copyright (c) 1998, 2021 Oracle and/or its affiliates.
GNU General Public License Version 2.0
https://dev.mysql.com/doc/connector-j/5.1/en/
https://dev.mysql.com/doc/connector-j/8.0/en/
- OWASP Dependency-Check
Copyright (c) 2012-2021 Jeremy Long. All Rights Reserved.
Apache-2.0 License
https://owasp.org/www-project-dependency-check/
- petaFuel DBUtils
Copyright 2021 petaFuel GmbH
DBUtils Software License Agreement
https://github.com/petafuel
# The following commented libraries are no longer publicly available:
[//]: # (- petaFuel DBUtils )
[//]: # ( Copyright 2021 petaFuel GmbH )
[//]: # ( DBUtils Software License Agreement )
[//]: # ( https://github.com/petafuel)
[//]: # ()
[//]: # (- petaFuel JSEPA )
[//]: # ( Copyright 2021 petaFuel GmbH )
[//]: # ( JSEPA Software License Agreement )
[//]: # ( https://github.com/petafuel)
[//]: # ()
[//]: # (- petaFuel Mt94x )
[//]: # ( Copyright 2021 petaFuel GmbH )
[//]: # ( Mt94x Software License Agreement )
[//]: # ( https://github.com/petafuel)
- petaFuel JSEPA
Copyright 2021 petaFuel GmbH
JSEPA Software License Agreement
https://github.com/petafuel
- petaFuel Mt94x
Copyright 2021 petaFuel GmbH
Mt94x Software License Agreement
https://github.com/petafuel
- Prowide Core
Apache-2.0 License
https://www.prowidesoftware.com/products/core
- RESTEasy
Apache-2.0 License