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

net.codecrete.qrbill.generator.SwicoS1Decoder Maven / Gradle / Ivy

There is a newer version: 3.3.1
Show newest version
//
// 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