Implemented EpcQrCodeParser that extracts credit transfer data from decoded EPC QR code
This commit is contained in:
parent
eff4e131df
commit
4ef8e7330f
Binary file not shown.
Binary file not shown.
|
@ -71,8 +71,10 @@ project(':fints4kRest').projectDir = "$rootDir/rest/fints4kRest/" as File
|
||||||
include ':BankFinder'
|
include ':BankFinder'
|
||||||
include ':LuceneBankFinder'
|
include ':LuceneBankFinder'
|
||||||
include ':BankListCreator'
|
include ':BankListCreator'
|
||||||
|
include ':EpcQrCodeParser'
|
||||||
|
|
||||||
|
|
||||||
project(':BankFinder').projectDir = "$rootDir/tools/BankFinder/" as File
|
project(':BankFinder').projectDir = "$rootDir/tools/BankFinder/" as File
|
||||||
project(':LuceneBankFinder').projectDir = "$rootDir/tools/LuceneBankFinder/" as File
|
project(':LuceneBankFinder').projectDir = "$rootDir/tools/LuceneBankFinder/" as File
|
||||||
project(':BankListCreator').projectDir = "$rootDir/tools/BankListCreator/" as File
|
project(':BankListCreator').projectDir = "$rootDir/tools/BankListCreator/" as File
|
||||||
|
project(':EpcQrCodeParser').projectDir = "$rootDir/tools/EpcQrCodeParser/" as File
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
# EPC QR code Parser
|
||||||
|
|
||||||
|
The [EPC QR code](https://en.wikipedia.org/wiki/EPC_QR_code), marketed as GiroCode, Scan2Pay or Zahlen mit Code amongst others, is a QR code
|
||||||
|
defined by the European Payments Council which contains all data to initiate a SEPA credit transfer.
|
||||||
|
|
||||||
|
This library is a multi platform implementation to extract the credit transfer data from decoded QR code.
|
||||||
|
(So it does not implement decoding the QR code itself, but extracting the data from decoded QR code text.)
|
|
@ -0,0 +1,159 @@
|
||||||
|
plugins {
|
||||||
|
id 'org.jetbrains.kotlin.multiplatform'
|
||||||
|
// id 'java-library'
|
||||||
|
id "com.android.library" // TODO: get rid off, use java-library instead
|
||||||
|
id "maven-publish"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ext.artifactName = "epc-qr-code-parser"
|
||||||
|
|
||||||
|
def frameworkName = "EpcQrCodeParser"
|
||||||
|
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
|
||||||
|
targets {
|
||||||
|
final def iOSTarget = iOSIsRealDevice ? presets.iosArm64 : presets.iosX64
|
||||||
|
|
||||||
|
fromPreset(iOSTarget, 'ios') {
|
||||||
|
binaries {
|
||||||
|
framework {
|
||||||
|
baseName = frameworkName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jvm()
|
||||||
|
|
||||||
|
js {
|
||||||
|
|
||||||
|
nodejs {
|
||||||
|
testTask {
|
||||||
|
enabled = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
browser {
|
||||||
|
testTask {
|
||||||
|
enabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
commonMain {
|
||||||
|
dependencies {
|
||||||
|
implementation kotlin('stdlib-common')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
commonTest {
|
||||||
|
dependencies {
|
||||||
|
implementation kotlin('test-common')
|
||||||
|
implementation kotlin('test-annotations-common')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jvmMain {
|
||||||
|
dependencies {
|
||||||
|
implementation kotlin('stdlib-jdk8')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jvmTest {
|
||||||
|
dependencies {
|
||||||
|
implementation kotlin('test')
|
||||||
|
implementation kotlin('test-junit')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jsMain {
|
||||||
|
dependencies {
|
||||||
|
implementation kotlin('stdlib-js')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jsTest {
|
||||||
|
dependencies {
|
||||||
|
implementation kotlin('test-js')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
iosMain {
|
||||||
|
}
|
||||||
|
iosTest {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Task to generate iOS framework for xcode projects.
|
||||||
|
task packForXcode(type: Sync) {
|
||||||
|
|
||||||
|
final File frameworkDir = new File(buildDir, "xcode-frameworks")
|
||||||
|
final String mode = project.findProperty("XCODE_CONFIGURATION")?.toUpperCase() ?: 'DEBUG'
|
||||||
|
|
||||||
|
final def framework = kotlin.targets.ios.binaries.getFramework("", mode)
|
||||||
|
|
||||||
|
inputs.property "mode", mode
|
||||||
|
dependsOn framework.linkTask
|
||||||
|
|
||||||
|
from { framework.outputFile.parentFile }
|
||||||
|
into frameworkDir
|
||||||
|
|
||||||
|
doLast {
|
||||||
|
new File(frameworkDir, 'gradlew').with {
|
||||||
|
text = "#!/bin/bash\nexport 'JAVA_HOME=${System.getProperty("java.home")}'\ncd '${rootProject.rootDir}'\n./gradlew \$@\n"
|
||||||
|
setExecutable(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run packForXcode when building.
|
||||||
|
tasks.build.dependsOn packForXcode
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: get rid of this
|
||||||
|
android {
|
||||||
|
compileSdkVersion androidCompileSdkVersion
|
||||||
|
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdkVersion androidMinSdkVersion
|
||||||
|
targetSdkVersion androidTargetSdkVersion
|
||||||
|
|
||||||
|
versionName version
|
||||||
|
versionCode appVersionCode
|
||||||
|
|
||||||
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled false
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
packagingOptions {
|
||||||
|
pickFirst 'META-INF/ktor-http.kotlin_module'
|
||||||
|
pickFirst 'META-INF/kotlinx-io.kotlin_module'
|
||||||
|
pickFirst 'META-INF/atomicfu.kotlin_module'
|
||||||
|
pickFirst 'META-INF/ktor-utils.kotlin_module'
|
||||||
|
pickFirst 'META-INF/kotlinx-coroutines-io.kotlin_module'
|
||||||
|
pickFirst 'META-INF/ktor-client-core.kotlin_module'
|
||||||
|
pickFirst 'META-INF/DEPENDENCIES'
|
||||||
|
pickFirst 'META-INF/NOTICE'
|
||||||
|
pickFirst 'META-INF/LICENSE'
|
||||||
|
pickFirst 'META-INF/LICENSE.txt'
|
||||||
|
pickFirst 'META-INF/NOTICE.txt'
|
||||||
|
}
|
||||||
|
|
||||||
|
lintOptions {
|
||||||
|
abortOnError false
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
package net.codinux.banking.tools.epcqrcode
|
||||||
|
|
||||||
|
|
||||||
|
open class EpcQrCode(
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 3 bytes.
|
||||||
|
* Always(?) has value "BCD".
|
||||||
|
*/
|
||||||
|
open val serviceTag: String,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 3 bytes.
|
||||||
|
* Has either value "001" or "002".
|
||||||
|
*/
|
||||||
|
open val version: EpcQrCodeVersion,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1 byte.
|
||||||
|
* The values 1,2,3,4,5,6,7 and 8 determine the interpretation of data to be used.
|
||||||
|
* In that order they qualify UTF-8, ISO 8895-1, ISO 8895-2, ISO 8895-4, ISO 8895-5, ISO 8895- 7, ISO 8895-10 and ISO 8895-15
|
||||||
|
*/
|
||||||
|
open val coding: EpcQrCodeCharacterSet,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 3 bytes.
|
||||||
|
* The Function is defined by its key value: SCT - SEPA Credit Transfer
|
||||||
|
*/
|
||||||
|
open val function: String,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receiver's BIC.
|
||||||
|
* Mandatory in Version 001, optional in Version 002.
|
||||||
|
* Either 8 or 11 bytes.
|
||||||
|
*/
|
||||||
|
open val bic: String?,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receiver name.
|
||||||
|
* Max. 70 characters
|
||||||
|
*/
|
||||||
|
open val receiverName: String,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receiver's IBAN.
|
||||||
|
* Max. 34 bytes.
|
||||||
|
*/
|
||||||
|
open val iban: String,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Three capital letter currency code.
|
||||||
|
* Only set if amount is also set.
|
||||||
|
*/
|
||||||
|
open val currencyCode: String?,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional amount.
|
||||||
|
* Max. 12 bytes.
|
||||||
|
*/
|
||||||
|
open val amount: Double?, // TODO: use BigDecimal
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional purpose code.
|
||||||
|
* Max. 4 bytes.
|
||||||
|
*/
|
||||||
|
open val purposeCode: String?,
|
||||||
|
|
||||||
|
open val remittanceReference: String?,
|
||||||
|
|
||||||
|
open val remittanceText: String?,
|
||||||
|
|
||||||
|
open val originatorInformation: String?
|
||||||
|
|
||||||
|
) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [remittanceReference] and [remittanceText] are mutual exclusive, that means one of both has to be set
|
||||||
|
* but they are never set both at the same time.
|
||||||
|
*
|
||||||
|
* remittance returns the one that is set.
|
||||||
|
*/
|
||||||
|
open val remittance: String
|
||||||
|
get() = remittanceReference ?: remittanceText ?: "" // they should never be both null
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "$receiverName $amount $currencyCode ${remittance}"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package net.codinux.banking.tools.epcqrcode
|
||||||
|
|
||||||
|
|
||||||
|
enum class EpcQrCodeCharacterSet(val code: Int) {
|
||||||
|
|
||||||
|
UTF_8(1),
|
||||||
|
|
||||||
|
ISO_8895_1(2),
|
||||||
|
|
||||||
|
ISO_8895_2(3),
|
||||||
|
|
||||||
|
ISO_8895_4(4),
|
||||||
|
|
||||||
|
ISO_8895_5(5),
|
||||||
|
|
||||||
|
ISO_8895_7(6),
|
||||||
|
|
||||||
|
ISO_8895_10(7),
|
||||||
|
|
||||||
|
ISO_8895_15(8)
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,121 @@
|
||||||
|
package net.codinux.banking.tools.epcqrcode
|
||||||
|
|
||||||
|
|
||||||
|
open class EpcQrCodeParser {
|
||||||
|
|
||||||
|
open fun parseEpcQrCode(decodedQrCode: String): ParseEpcQrCodeResult {
|
||||||
|
try {
|
||||||
|
val lines = decodedQrCode.split("\n", "\r\n").dropLastWhile { isNotSet(it) }
|
||||||
|
|
||||||
|
if (lines.size < 10 || lines.size > 12) {
|
||||||
|
return createInvalidFormatResult(decodedQrCode, "A EPC-QR-Code consists of 10 to 12 lines, but passed string has ${lines.size} lines")
|
||||||
|
}
|
||||||
|
if (lines[0] != "BCD") {
|
||||||
|
return createInvalidFormatResult(decodedQrCode, "A EPC-QR-Code's first lines has to be exactly 'BCD'")
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseEpcQrAfterSuccessfulFormatCheck(decodedQrCode, lines)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
return ParseEpcQrCodeResult(decodedQrCode, ParseEpcQrCodeResultCode.UnpredictedErrorOccurred, null, e.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected open fun parseEpcQrAfterSuccessfulFormatCheck(decodedQrCode: String, lines: List<String>): ParseEpcQrCodeResult {
|
||||||
|
val validVersionCodes = EpcQrCodeVersion.values().map { it.code }
|
||||||
|
if (lines[1].length != 3 || validVersionCodes.contains(lines[1]) == false) {
|
||||||
|
return createInvalidFormatResult(decodedQrCode, "The second line has to be exactly one of these values: ${validVersionCodes.joinToString(", ")}")
|
||||||
|
}
|
||||||
|
|
||||||
|
val version = EpcQrCodeVersion.values().first { it.code == lines[1] }
|
||||||
|
|
||||||
|
val codingCode = lines[2].toIntOrNull()
|
||||||
|
if (lines[2].length != 1 || codingCode == null || codingCode < 1 || codingCode > 8) {
|
||||||
|
return createInvalidFormatResult(decodedQrCode, "The third line has to be exactly one of these values: ${EpcQrCodeCharacterSet.values().map { it.code }.joinToString(", ")}")
|
||||||
|
}
|
||||||
|
|
||||||
|
val coding = EpcQrCodeCharacterSet.values().first { it.code == codingCode }
|
||||||
|
|
||||||
|
if (lines[3].length != 3) {
|
||||||
|
return createInvalidFormatResult(decodedQrCode, "The fourth line, ${lines[3]}, has to be exactly three characters long.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version == EpcQrCodeVersion.Version1 && isNotSet(lines[4])) {
|
||||||
|
return createInvalidFormatResult(decodedQrCode, "The BIC line 5 may only be omitted in EPC-QR-Code version 002")
|
||||||
|
}
|
||||||
|
if (lines[4].length != 8 && lines[4].length != 11 && (version == EpcQrCodeVersion.Version1 || isNotSet(lines[4]) == false)) {
|
||||||
|
return createInvalidFormatResult(decodedQrCode, "The BIC line 5, ${lines[4]}, must be either 8 or 11 characters long.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNotSet(lines[5])) {
|
||||||
|
return createInvalidFormatResult(decodedQrCode, "The receiver in line 6 may not be omitted.")
|
||||||
|
}
|
||||||
|
|
||||||
|
val receiver = parseWithCoding(lines[5], coding)
|
||||||
|
if (receiver.length > 70) { // omit check for parsing
|
||||||
|
return createInvalidFormatResult(decodedQrCode, "The receiver in line 6, ${lines[5]}, may not be longer than 70 characters.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNotSet(lines[6])) {
|
||||||
|
return createInvalidFormatResult(decodedQrCode, "The IBAN in line 7 may not be omitted.")
|
||||||
|
}
|
||||||
|
// if (lines[6].length > 34) { // omit check for parsing
|
||||||
|
// return createInvalidFormatResult("The IBAN in line 7, ${lines[6]}, may not be longer than 70 characters.")
|
||||||
|
// }
|
||||||
|
|
||||||
|
var currencyCode: String? = null
|
||||||
|
var amount: Double? = null
|
||||||
|
val currencyCodeAndAmount = lines[7]
|
||||||
|
if (currencyCodeAndAmount.length > 3) {
|
||||||
|
// TODO: check if the first three characters are letter
|
||||||
|
// if (currencyCodeAndAmount.length > 15) {
|
||||||
|
// return createInvalidFormatResult("The eighth line has to start with three upper case letters currency code and amount may not have more than 12 digits (including a dot as decimal separator).")
|
||||||
|
// }
|
||||||
|
|
||||||
|
currencyCode = currencyCodeAndAmount.substring(0, 3)
|
||||||
|
amount = currencyCodeAndAmount.substring(3).toDouble()
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (lines[8].length > 4) { // omit check for parsing
|
||||||
|
// return createInvalidFormatResult("The purpose code in line 9, ${lines[8]}, may not be longer than 4 characters.")
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (lines[9].length > 35) { // omit check for parsing
|
||||||
|
// return createInvalidFormatResult("The reconciliation reference in line 10, ${lines[9]}, may not be longer than 35 characters.")
|
||||||
|
// }
|
||||||
|
|
||||||
|
val reconciliationText = if (lines.size < 11) null else parseToNullableString(lines[10])?.let { parseWithCoding(it, coding) }
|
||||||
|
// if ((reconciliationText?.length ?: 0) > 140) { // omit check for parsing
|
||||||
|
// return createInvalidFormatResult("The reconciliation text in line 11, ${lines[10]}, may not be longer than 140 characters.")
|
||||||
|
// }
|
||||||
|
|
||||||
|
val displayText = if (lines.size < 12) null else lines[11]
|
||||||
|
// if (displayText != null && displayText.length > 70) { // omit check for parsing
|
||||||
|
// return createInvalidFormatResult("The display text in line 12, ${displayText}, may not be longer than 70 characters.")
|
||||||
|
// }
|
||||||
|
|
||||||
|
return ParseEpcQrCodeResult(
|
||||||
|
decodedQrCode, ParseEpcQrCodeResultCode.Success, EpcQrCode(
|
||||||
|
lines[0], version, coding, lines[3], parseToNullableString(lines[4]), lines[5], lines[6],
|
||||||
|
currencyCode, amount, parseToNullableString(lines[8]), parseToNullableString(lines[9]), reconciliationText, displayText
|
||||||
|
)
|
||||||
|
, null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected open fun parseWithCoding(line: String, coding: EpcQrCodeCharacterSet): String {
|
||||||
|
return line // TODO: does encoding work out of the box? // TODO: are there any encodings with more than one byte per characters allowed per specification
|
||||||
|
}
|
||||||
|
|
||||||
|
protected open fun parseToNullableString(lines: String): String? {
|
||||||
|
return if (isNotSet(lines)) null else lines
|
||||||
|
}
|
||||||
|
|
||||||
|
protected open fun isNotSet(line: String): Boolean {
|
||||||
|
return line.isBlank()
|
||||||
|
}
|
||||||
|
|
||||||
|
protected open fun createInvalidFormatResult(decodedQrCode: String, error: String): ParseEpcQrCodeResult {
|
||||||
|
return ParseEpcQrCodeResult(decodedQrCode, ParseEpcQrCodeResultCode.NotAValidEpcQrCode, null, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package net.codinux.banking.tools.epcqrcode
|
||||||
|
|
||||||
|
|
||||||
|
enum class EpcQrCodeVersion(val code: String) {
|
||||||
|
|
||||||
|
Version1("001"), // cannot name it '1'
|
||||||
|
|
||||||
|
Version2("002")
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package net.codinux.banking.tools.epcqrcode
|
||||||
|
|
||||||
|
|
||||||
|
open class ParseEpcQrCodeResult(
|
||||||
|
open val decodedQrCode: String,
|
||||||
|
open val resultCode: ParseEpcQrCodeResultCode,
|
||||||
|
open val epcQrCode: EpcQrCode?,
|
||||||
|
open val error: String?
|
||||||
|
) {
|
||||||
|
|
||||||
|
open val successful: Boolean
|
||||||
|
get() = resultCode == ParseEpcQrCodeResultCode.Success
|
||||||
|
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return if (successful) {
|
||||||
|
"Success: $epcQrCode"
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
"Error: $error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package net.codinux.banking.tools.epcqrcode
|
||||||
|
|
||||||
|
|
||||||
|
enum class ParseEpcQrCodeResultCode {
|
||||||
|
|
||||||
|
Success,
|
||||||
|
|
||||||
|
NotAValidEpcQrCode,
|
||||||
|
|
||||||
|
UnpredictedErrorOccurred
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,173 @@
|
||||||
|
package net.codinux.banking.tools.epcqrcode
|
||||||
|
|
||||||
|
import kotlin.test.*
|
||||||
|
|
||||||
|
|
||||||
|
class EpcQrCodeParserTest {
|
||||||
|
|
||||||
|
private val underTest = EpcQrCodeParser()
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun wikipediaExampleBelgianRedCross() {
|
||||||
|
|
||||||
|
// when
|
||||||
|
val result = underTest.parseEpcQrCode("""
|
||||||
|
BCD
|
||||||
|
001
|
||||||
|
1
|
||||||
|
SCT
|
||||||
|
BPOTBEB1
|
||||||
|
Red Cross
|
||||||
|
BE72000000001616
|
||||||
|
EUR1
|
||||||
|
CHAR
|
||||||
|
|
||||||
|
Urgency fund
|
||||||
|
Sample EPC QR Code
|
||||||
|
""".trim())
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertParsingSuccessful(result)
|
||||||
|
|
||||||
|
assertEpcQrCode(result, EpcQrCodeVersion.Version1, EpcQrCodeCharacterSet.UTF_8, "SCT", "BPOTBEB1", "Red Cross",
|
||||||
|
"BE72000000001616", "EUR", 1.0, "CHAR", null,
|
||||||
|
"Urgency fund", "Sample EPC QR Code")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun spendeAnAerzteOhneGrenzen() {
|
||||||
|
|
||||||
|
// when
|
||||||
|
val result = underTest.parseEpcQrCode("""
|
||||||
|
BCD
|
||||||
|
001
|
||||||
|
1
|
||||||
|
SCT
|
||||||
|
BFSWDE33XXX
|
||||||
|
Ärzte ohne Grenzen e.V.
|
||||||
|
DE72370205000009709700
|
||||||
|
EUR100
|
||||||
|
|
||||||
|
|
||||||
|
Spende
|
||||||
|
Danke für Ihre Spende
|
||||||
|
""".trim())
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertParsingSuccessful(result)
|
||||||
|
|
||||||
|
assertEpcQrCode(result, EpcQrCodeVersion.Version1, EpcQrCodeCharacterSet.UTF_8, "SCT", "BFSWDE33XXX", "Ärzte ohne Grenzen e.V.",
|
||||||
|
"DE72370205000009709700", "EUR", 100.00, null, null,
|
||||||
|
"Spende", "Danke für Ihre Spende")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun stuzzaExample01() {
|
||||||
|
|
||||||
|
// when
|
||||||
|
val result = underTest.parseEpcQrCode("""
|
||||||
|
BCD
|
||||||
|
001
|
||||||
|
1
|
||||||
|
SCT
|
||||||
|
BICVXXDD123
|
||||||
|
35 Zeichen langer Empfängername zum
|
||||||
|
XX17LandMitLangerIBAN2345678901234
|
||||||
|
EUR12345689.01
|
||||||
|
|
||||||
|
35ZeichenLangeREFzurZuordnungBeimBe
|
||||||
|
|
||||||
|
Netter Text für den Zahlenden, damit dieser weiß, was er zahlt und auc
|
||||||
|
""".trim())
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertParsingSuccessful(result)
|
||||||
|
|
||||||
|
assertEpcQrCode(result, EpcQrCodeVersion.Version1, EpcQrCodeCharacterSet.UTF_8, "SCT", "BICVXXDD123", "35 Zeichen langer Empfängername zum",
|
||||||
|
"XX17LandMitLangerIBAN2345678901234", "EUR", 12345689.01, null, "35ZeichenLangeREFzurZuordnungBeimBe",
|
||||||
|
null, "Netter Text für den Zahlenden, damit dieser weiß, was er zahlt und auc")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun stuzzaExample02() {
|
||||||
|
|
||||||
|
// when
|
||||||
|
val result = underTest.parseEpcQrCode("""
|
||||||
|
BCD
|
||||||
|
001
|
||||||
|
1
|
||||||
|
SCT
|
||||||
|
GIBAATWW
|
||||||
|
Max Mustermann
|
||||||
|
AT682011131032423628
|
||||||
|
EUR1456.89
|
||||||
|
|
||||||
|
457845789452
|
||||||
|
|
||||||
|
Diverse Autoteile, Re 789452 KN 457845
|
||||||
|
""".trim())
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertParsingSuccessful(result)
|
||||||
|
|
||||||
|
assertEpcQrCode(result, EpcQrCodeVersion.Version1, EpcQrCodeCharacterSet.UTF_8, "SCT", "GIBAATWW", "Max Mustermann",
|
||||||
|
"AT682011131032423628", "EUR", 1456.89, null, "457845789452",
|
||||||
|
null, "Diverse Autoteile, Re 789452 KN 457845")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun stuzzaExample07() {
|
||||||
|
|
||||||
|
// when
|
||||||
|
val result = underTest.parseEpcQrCode("""
|
||||||
|
BCD
|
||||||
|
002
|
||||||
|
2
|
||||||
|
SCT
|
||||||
|
|
||||||
|
35 Zeichen langer Empfängername zum
|
||||||
|
XX17LandMitLangerIBAN2345678901234
|
||||||
|
EUR12345689.01
|
||||||
|
|
||||||
|
35ZeichenLangeREFzurZuordnungBeimBe
|
||||||
|
|
||||||
|
Netter Text für den Zahlenden, damit dieser weiß, was er zahlt und auc
|
||||||
|
""".trim())
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertParsingSuccessful(result)
|
||||||
|
|
||||||
|
assertEpcQrCode(result, EpcQrCodeVersion.Version2, EpcQrCodeCharacterSet.ISO_8895_1, "SCT", null, "35 Zeichen langer Empfängername zum",
|
||||||
|
"XX17LandMitLangerIBAN2345678901234", "EUR", 12345689.01, null, "35ZeichenLangeREFzurZuordnungBeimBe",
|
||||||
|
null, "Netter Text für den Zahlenden, damit dieser weiß, was er zahlt und auc")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun assertParsingSuccessful(result: ParseEpcQrCodeResult) {
|
||||||
|
assertTrue(result.successful)
|
||||||
|
assertNull(result.error)
|
||||||
|
assertNotNull(result.epcQrCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun assertEpcQrCode(result: ParseEpcQrCodeResult, version: EpcQrCodeVersion, coding: EpcQrCodeCharacterSet, function: String,
|
||||||
|
bic: String?, receiver: String, iban: String, currency: String?, amount: Double?,
|
||||||
|
purposeCode: String?, reference: String?, text: String?, displayText: String?) {
|
||||||
|
|
||||||
|
result.epcQrCode?.let { epcQrCode ->
|
||||||
|
assertEquals(version, epcQrCode.version)
|
||||||
|
assertEquals(coding, epcQrCode.coding)
|
||||||
|
assertEquals(function, epcQrCode.function)
|
||||||
|
assertEquals(bic, epcQrCode.bic)
|
||||||
|
assertEquals(receiver, epcQrCode.receiverName)
|
||||||
|
assertEquals(iban, epcQrCode.iban)
|
||||||
|
assertEquals(currency, epcQrCode.currencyCode)
|
||||||
|
assertEquals(amount, epcQrCode.amount)
|
||||||
|
assertEquals(purposeCode, epcQrCode.purposeCode)
|
||||||
|
assertEquals(reference, epcQrCode.remittanceReference)
|
||||||
|
assertEquals(text, epcQrCode.remittanceText)
|
||||||
|
assertEquals(displayText, epcQrCode.originatorInformation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue