net.codecrete.qrbill.generator.SwicoS1Decoder Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of qrbill-generator Show documentation
Show all versions of qrbill-generator Show documentation
Java library for generating Swiss QR bills
//
// Swiss QR Bill Generator
// Copyright (c) 2018 Manuel Bleichenbacher
// Licensed under MIT License
// https://opensource.org/licenses/MIT
//
package net.codecrete.qrbill.generator;
///
/// Decodes structured bill information according to Swico S1 syntax.
///
/// The encoded bill information can be found in a Swiss QR bill in th field StrdBkgInf .
///
///
/// Also see http://swiss-qr-invoice.org/downloads/qr-bill-s1-syntax-de.pdf
///
///
import net.codecrete.qrbill.generator.SwicoBillInformation.PaymentCondition;
import net.codecrete.qrbill.generator.SwicoBillInformation.RateDetail;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.ParsePosition;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
/**
* Decodes structured bill information according to Swico S1 syntax.
*
* The encoded bill information can be found in a Swiss QR bill in th field {@code StrdBkgInf}.
*
*
* Also see http://swiss-qr-invoice.org/downloads/qr-bill-s1-syntax-de.pdf
*
*/
public class SwicoS1Decoder {
private static final int INVOICE_NUMBER_TAG = 10;
private static final int INVOICE_DATE_TAG = 11;
private static final int CUSTOMER_REFERENCE_TAG = 20;
private static final int VAT_NUMBER_TAG = 30;
private static final int VAT_DATE_TAG = 31;
private static final int VAT_RATE_DETAILS_TAG = 32;
private static final int VAT_IMPORT_TAXES_TAG = 33;
private static final int PAYMENT_CONDITIONS_TAG = 40;
private SwicoS1Decoder() {
// don't instantiate
}
/**
* Decodes the specified text.
*
* As much data as possible is decoded. Invalid data is silently ignored.
*
*
* @param billInfoText the encoded structured bill information text
* @return the decoded bill information (or {@code null} if no valid Swico bill information is found)
*/
static SwicoBillInformation decode(String billInfoText) {
if (billInfoText == null || !billInfoText.startsWith("//S1/"))
return null;
// Split text at slashes
String[] parts = split(billInfoText.substring(5));
// Create a list of tuples (tag, value)
List tuples = new ArrayList<>();
int len = parts.length;
for (int i = 0; i < len - 1; i += 2) {
try {
int tag = Integer.parseInt(parts[i]);
tuples.add(new InfoTuple(tag, parts[i + 1]));
} catch (NumberFormatException e) {
// ignore
}
}
// Process the tuples and assign them to bill information
SwicoBillInformation billInformation = new SwicoBillInformation();
for (InfoTuple tuple : tuples)
decodeElement(billInformation, tuple.tag, tuple.value);
return billInformation;
}
private static void decodeElement(SwicoBillInformation billInformation, int tag, String value) {
if (value.length() == 0)
return;
switch (tag) {
case INVOICE_NUMBER_TAG:
billInformation.setInvoiceNumber(value);
break;
case INVOICE_DATE_TAG:
billInformation.setInvoiceDate(getDateValue(value));
break;
case CUSTOMER_REFERENCE_TAG:
billInformation.setCustomerReference(value);
break;
case VAT_NUMBER_TAG:
billInformation.setVatNumber(value);
break;
case VAT_DATE_TAG:
setVatDates(billInformation, value);
break;
case VAT_RATE_DETAILS_TAG:
setVatRateDetails(billInformation, value);
break;
case VAT_IMPORT_TAXES_TAG:
billInformation.setVatImportTaxes(parseDetailList(value));
break;
case PAYMENT_CONDITIONS_TAG:
setPaymentConditions(billInformation, value);
break;
default:
// ignore unknown tags
}
}
private static void setVatDates(SwicoBillInformation billInformation, String value) {
if (value.length() != 6 && value.length() != 12)
return;
if (value.length() == 6) {
// Single VAT date
LocalDate date = getDateValue(value);
if (date != null) {
billInformation.setVatDate(date);
billInformation.setVatStartDate(null);
billInformation.setVatEndDate(null);
}
} else {
// VAT date range
LocalDate startDate = getDateValue(value.substring(0, 6));
LocalDate endDate = getDateValue(value.substring(6, 12));
if (startDate != null && endDate != null) {
billInformation.setVatStartDate(startDate);
billInformation.setVatEndDate(endDate);
billInformation.setVatDate(null);
}
}
}
private static void setVatRateDetails(SwicoBillInformation billInformation, String value) {
// Test for single VAT rate vs list of tuples
if (!value.contains(":") && !value.contains(";")) {
billInformation.setVatRate(getDecimalValue(value));
billInformation.setVatRateDetails(null);
} else {
billInformation.setVatRateDetails(parseDetailList(value));
billInformation.setVatRate(null);
}
}
private static void setPaymentConditions(SwicoBillInformation billInformation, String value) {
// Split into tuples
String[] tuples = value.split(";");
List list = new ArrayList<>();
for (String listEntry : tuples) {
// Split into tuple (discount, days)
String[] detail = listEntry.split(":");
if (detail.length != 2)
continue;
BigDecimal discount = getDecimalValue(detail[0]);
Integer days = getIntValue(detail[1]);
if (discount != null && days != null)
list.add(new PaymentCondition(discount, days));
}
if (!list.isEmpty())
billInformation.setPaymentConditions(list);
}
private static List parseDetailList(String text) {
// Split into tuples
String[] tuples = text.split(";");
List list = new ArrayList<>();
for (String vatEntry : tuples) {
// Split into tuple (rate, amount)
String[] vatDetails = vatEntry.split(":");
if (vatDetails.length != 2)
continue;
BigDecimal vatRate = getDecimalValue(vatDetails[0]);
BigDecimal vatAmount = getDecimalValue(vatDetails[1]);
if (vatRate != null && vatAmount != null)
list.add(new RateDetail(vatRate, vatAmount));
}
return list.isEmpty() ? null : list;
}
private static final DateTimeFormatter SWICO_DATE_FORMAT_SPECIFICATION
= DateTimeFormatter.ofPattern("yyMMdd", Locale.UK);
private static final DateTimeFormatter SWICO_DATE_FORMAT_WILDERNESS_1
= DateTimeFormatter.ofPattern("yyMMddHHmmss", Locale.UK);
private static final DateTimeFormatter SWICO_DATE_FORMAT_WILDERNESS_2
= DateTimeFormatter.ofPattern("yyMMddHHmm", Locale.UK);
private static LocalDate getDateValue(String dateText) {
if (dateText.length() == 6) { // Consistent with specification
try {
return LocalDate.parse(dateText, SWICO_DATE_FORMAT_SPECIFICATION);
} catch (DateTimeParseException e) {
// fall through to default
}
} else if (dateText.length() == 12) { // Not consistent with specifications but seen in production (year, month, day, hour, minute, second)
try {
return LocalDate.parse(dateText, SWICO_DATE_FORMAT_WILDERNESS_1);
} catch (DateTimeParseException e) {
// fall through to default
}
} else if (dateText.length() == 10) { // Not consistent with specifications but seen in production (year, month, day, hour, minute)
try {
return LocalDate.parse(dateText, SWICO_DATE_FORMAT_WILDERNESS_2);
} catch (DateTimeParseException e) {
// fall through to default
}
}
return null;
}
private static Integer getIntValue(String intText) {
try {
return Integer.parseInt(intText);
} catch (NumberFormatException e) {
return null;
}
}
private static final DecimalFormat SWICO_NUMBER_FORMAT;
static {
SWICO_NUMBER_FORMAT = new DecimalFormat("0.###", new DecimalFormatSymbols(Locale.UK));
SWICO_NUMBER_FORMAT.setParseBigDecimal(true);
}
private static BigDecimal getDecimalValue(String decimalText) {
ParsePosition position = new ParsePosition(0);
BigDecimal decimal = (BigDecimal) SWICO_NUMBER_FORMAT.parse(decimalText, position);
return (position.getIndex() == decimalText.length()) ? decimal : null;
}
/**
* Splits the text at slash characters.
*
* Additonally, the escaping with back slahes is undone.
*
*
* @param text the text to split
* @return array of substrings
*/
private static String[] split(String text) {
// Use placeholders for escaped characters (outside of valid QR bill character set)
// and undo back slash escaping.
text = text.replace("\\\\", "☁").replace("\\/", "★");
// Split
String[] parts = text.split("/");
// Fix placeholders
for (int i = 0; i < parts.length; i++) {
parts[i] = parts[i].replace('★', '/').replace('☁', '\\');
}
return parts;
}
static class InfoTuple {
int tag;
String value;
InfoTuple(int tag, String value) {
this.tag = tag;
this.value = value;
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy