diff --git a/README.md b/README.md index 0cb7a1a..1cff162 100644 --- a/README.md +++ b/README.md @@ -28,9 +28,9 @@ Create `Spayd` instance. The only mandatory parameter is `account`. Example: ```kotlin val spayd = Spayd( - account = Account(iban = "CZ7603000000000076327632"), - amount = 500.00, - currency = "CZK", + bankAccount = BankAccount(iban = "CZ7603000000000076327632"), + amount = "500.00".toBigDecimal(), + currencyCode = "CZK", message = "Clovek v tisni", ) ``` @@ -46,9 +46,9 @@ This will validate data and possibly throw `ValidationException` with a short me ### Alternative constructors ```kotlin val spayd = Spayd( - Key.ACCOUNT to Account(iban = "CZ7603000000000076327632"), - Key.AMOUNT to 500.00, - Key.CURRENCY to "CZK", + Key.BANK_ACCOUNT to BankAccount(iban = "CZ7603000000000076327632"), + Key.AMOUNT to "500.00".toBigDecimal(), + Key.CURRENCY_CODE to "CZK", Key.MESSAGE to "Clovek v tisni", ) ``` @@ -57,9 +57,9 @@ or ```kotlin val parameters: Map = mapOf( - Key.ACCOUNT to Account(iban = "CZ7603000000000076327632"), - Key.AMOUNT to 500.00, - Key.CURRENCY to "CZK", + Key.BANK_ACCOUNT to BankAccount(iban = "CZ7603000000000076327632"), + Key.AMOUNT to "500.00".toBigDecimal(), + Key.CURRENCY_CODE to "CZK", Key.MESSAGE to "Clovek v tisni", ) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 32ecd1a..2308814 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,6 +2,7 @@ agp = "8.2.2" android-compileSdk = "34" android-minSdk = "23" +bignum = "0.3.10" compose-plugin = "1.6.11" kotlin = "2.0.21" kotlinx-datetime = "0.6.1" @@ -21,6 +22,7 @@ skie = { id = "co.touchlab.skie", version.ref = "skie" } mavenDeployer = { id = "io.deepmedia.tools.deployer", version.ref = "maven-deployer"} [libraries] +bignum = { module = "com.ionspin.kotlin:bignum", version.ref = "bignum" } kotlin_junit = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } kotlin_test = { module = "org.jetbrains.kotlin:kotlin-test-common", version.ref = "kotlin" } kotlin_test_annotations = { module = "org.jetbrains.kotlin:kotlin-test-annotations-common", version.ref = "kotlin" } diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts index df1e166..b199ad4 100644 --- a/shared/build.gradle.kts +++ b/shared/build.gradle.kts @@ -35,7 +35,8 @@ kotlin { sourceSets { commonMain.dependencies { - implementation(libs.kotlinx.datetime) + api(libs.bignum) + api(libs.kotlinx.datetime) implementation(libs.ktor.http) implementation(libs.okio) implementation(libs.urlencoder) diff --git a/shared/src/commonMain/kotlin/io/stepuplabs/spaydkmp/Spayd.kt b/shared/src/commonMain/kotlin/io/stepuplabs/spaydkmp/Spayd.kt index 2657e92..422b5c3 100644 --- a/shared/src/commonMain/kotlin/io/stepuplabs/spaydkmp/Spayd.kt +++ b/shared/src/commonMain/kotlin/io/stepuplabs/spaydkmp/Spayd.kt @@ -1,12 +1,13 @@ package io.stepuplabs.spaydkmp -import io.stepuplabs.spaydkmp.common.Account -import io.stepuplabs.spaydkmp.common.AccountList +import com.ionspin.kotlin.bignum.decimal.BigDecimal +import io.stepuplabs.spaydkmp.common.BankAccount +import io.stepuplabs.spaydkmp.common.BankAccountList import io.stepuplabs.spaydkmp.common.Key import io.stepuplabs.spaydkmp.common.NotificationType +import io.stepuplabs.spaydkmp.common.PaymentType import io.stepuplabs.spaydkmp.common.Validator import io.stepuplabs.spaydkmp.exception.* -import kotlin.math.log10 import kotlinx.datetime.LocalDate import kotlinx.datetime.format import net.thauvin.erik.urlencoder.UrlEncoderUtil @@ -25,42 +26,41 @@ class Spayd( // Convenience constructor that accepts all values in form of named parameters constructor( - account: Account, - alternateAccounts: AccountList? = null, - currency: String? = null, - amount: Double? = null, - date: LocalDate? = null, - senderReference: Int? = null, + bankAccount: BankAccount, + alternativeBankAccounts: BankAccountList? = null, + currencyCode: String? = null, + amount: BigDecimal? = null, + dueDate: LocalDate? = null, + referenceForRecipient: Int? = null, recipientName: String? = null, - paymentType: String? = null, + paymentType: PaymentType? = null, message: String? = null, notificationType: NotificationType? = null, notificationAddress: String? = null, - repeat: Int? = null, + daysToRepeatIfUnsuccessfull: Int? = null, variableSymbol: Long? = null, specificSymbol: Long? = null, constantSymbol: Long? = null, - identifier: String? = null, + referenceForSender: String? = null, url: String? = null, ): this( parameters = arrayOf( - Key.ACCOUNT to account, - alternateAccounts?.let { Key.ALTERNATE_ACCOUNTS to it }, - alternateAccounts?.let { Key.ALTERNATE_ACCOUNTS to it }, - currency?.let { Key.CURRENCY to it }, + Key.BANK_ACCOUNT to bankAccount, + alternativeBankAccounts?.let { Key.ALTERNATIVE_BANK_ACCOUNTS to it }, + currencyCode?.let { Key.CURRENCY_CODE to it }, amount?.let { Key.AMOUNT to it }, - date?.let { Key.DATE to it }, - senderReference?.let { Key.SENDER_REFERENCE to it }, + dueDate?.let { Key.DUE_DATE to it }, + referenceForRecipient?.let { Key.REFERENCE_FOR_RECIPIENT to it }, recipientName?.let { Key.RECIPIENT_NAME to it }, paymentType?.let { Key.PAYMENT_TYPE to it }, message?.let { Key.MESSAGE to it }, notificationType?.let { Key.NOTIFY_TYPE to it }, notificationAddress?.let { Key.NOTIFY_ADDRESS to it }, - repeat?.let { Key.REPEAT to it }, + daysToRepeatIfUnsuccessfull?.let { Key.DAYS_TO_REPEAT_IF_UNSUCCESSFUL to it }, variableSymbol?.let { Key.VARIABLE_SYMBOL to it }, specificSymbol?.let { Key.SPECIFIC_SYMBOL to it }, constantSymbol?.let { Key.CONSTANT_SYMBOL to it }, - identifier?.let { Key.IDENTIFIER to it }, + referenceForSender?.let { Key.REFERENCE_FOR_SENDER to it }, url?.let { Key.URL to it }, ) ) @@ -77,7 +77,7 @@ class Spayd( // payment parameters for (parameter in parameters.filterNotNull()) { - getEntry(parameter.first.key, parameter.second)?.let { parts.add(it) } + getEntry(parameter.first, parameter.second)?.let { parts.add(it) } } // merge into one string @@ -109,7 +109,7 @@ class Spayd( validator.validate(key = parameter.first, value = parameter.second) when (parameter.first) { - Key.ACCOUNT -> hasAccount = true + Key.BANK_ACCOUNT -> hasAccount = true Key.NOTIFY_TYPE -> hasNotificationType = true Key.NOTIFY_ADDRESS -> hasNotificationAddress = true else -> continue @@ -126,12 +126,18 @@ class Spayd( } // Get parameter:value key for SPAYD - private fun getEntry(parameter: String, value: Any?): String? { + private fun getEntry(parameter: Key, value: Any?): String? { if (value == null) { return null } - return "$parameter:$value" + val valStr = when (parameter.type) { + LocalDate::class -> (value as LocalDate).format(LocalDate.Formats.ISO_BASIC) + BigDecimal::class -> (value as BigDecimal).toStringExpanded() + else -> sanitize("$value") + } + + return "${parameter.key}:$valStr" } // Get parameter:value key for SPAYD @@ -147,44 +153,16 @@ class Spayd( } entries.append( - escape(value.toString()), + sanitize(value.toString()), ) } return "$parameter:$entries" } - // Get parameter:value key for SPAYD - private fun getEntry(parameter: String, date: LocalDate?): String? { - if (date == null) { - return null - } - - return "$parameter:${date.format(LocalDate.Formats.ISO_BASIC)}" - } - // Sanitize values for SPAYD - private fun escape(value: String): String { - val escapedValue = StringBuilder() - - for (char in value) { - if (char.code > 127) { - escapedValue.append(UrlEncoderUtil.encode(char.toString())) - } else { - if (char.compareTo('*') == 0) { // spayd value separator - escapedValue.append("%2A") - } else if (char.compareTo('+') == 0) { - escapedValue.append("%2B") - } else if (char.compareTo('%') == 0) { - escapedValue.append("%25") - } else { - escapedValue.append(char) - } - } - } - - return escapedValue.toString() - } + private fun sanitize(value: String): String = Regex("[^A-Za-z0-9 @$%+\\-/:.,]") + .replace(value, "") companion object { const val MIME_TYPE: String = "application/x-shortpaymentdescriptor" diff --git a/shared/src/commonMain/kotlin/io/stepuplabs/spaydkmp/common/Account.kt b/shared/src/commonMain/kotlin/io/stepuplabs/spaydkmp/common/BankAccount.kt similarity index 94% rename from shared/src/commonMain/kotlin/io/stepuplabs/spaydkmp/common/Account.kt rename to shared/src/commonMain/kotlin/io/stepuplabs/spaydkmp/common/BankAccount.kt index 62db2d3..000c3f5 100644 --- a/shared/src/commonMain/kotlin/io/stepuplabs/spaydkmp/common/Account.kt +++ b/shared/src/commonMain/kotlin/io/stepuplabs/spaydkmp/common/BankAccount.kt @@ -3,7 +3,7 @@ package io.stepuplabs.spaydkmp.common /* Account representation */ -data class Account( +data class BankAccount( val iban: String, val bic: String? = null, ) { diff --git a/shared/src/commonMain/kotlin/io/stepuplabs/spaydkmp/common/AccountList.kt b/shared/src/commonMain/kotlin/io/stepuplabs/spaydkmp/common/BankAccountList.kt similarity index 76% rename from shared/src/commonMain/kotlin/io/stepuplabs/spaydkmp/common/AccountList.kt rename to shared/src/commonMain/kotlin/io/stepuplabs/spaydkmp/common/BankAccountList.kt index 8beea7d..b541b80 100644 --- a/shared/src/commonMain/kotlin/io/stepuplabs/spaydkmp/common/AccountList.kt +++ b/shared/src/commonMain/kotlin/io/stepuplabs/spaydkmp/common/BankAccountList.kt @@ -3,12 +3,12 @@ package io.stepuplabs.spaydkmp.common /* Representation of multiple Accounts */ -data class AccountList( - val accounts: List +data class BankAccountList( + val bankAccounts: List ) { override fun toString(): String { val builder = StringBuilder() - for (account in accounts) { + for (account in bankAccounts) { if (builder.isNotEmpty()) { builder.append(",") } diff --git a/shared/src/commonMain/kotlin/io/stepuplabs/spaydkmp/common/Key.kt b/shared/src/commonMain/kotlin/io/stepuplabs/spaydkmp/common/Key.kt index 53d54b9..1c38b93 100644 --- a/shared/src/commonMain/kotlin/io/stepuplabs/spaydkmp/common/Key.kt +++ b/shared/src/commonMain/kotlin/io/stepuplabs/spaydkmp/common/Key.kt @@ -1,5 +1,6 @@ package io.stepuplabs.spaydkmp.common +import com.ionspin.kotlin.bignum.decimal.BigDecimal import kotlinx.datetime.LocalDate import kotlin.reflect.KClass @@ -15,21 +16,26 @@ enum class Key( val minLength: Int? = null, val maxLength: Int? = null, ) { - DATE(key = "DT", type = LocalDate::class), - CURRENCY(key = "CC", type = String::class, minLength = 3, maxLength = 3), - AMOUNT(key = "AM", type = Double::class, minValue = 0.00, maxValue = 9_999_999.99), - ACCOUNT(key = "ACC", type = Account::class), - ALTERNATE_ACCOUNTS(key = "ALT-ACC", type = AccountList::class, maxLength = 2), - SENDER_REFERENCE(key = "RF", type = Int::class, maxLength = 16), + DUE_DATE(key = "DT", type = LocalDate::class), + CURRENCY_CODE(key = "CC", type = String::class, minLength = 3, maxLength = 3), + AMOUNT(key = "AM", type = BigDecimal::class, minValue = 0.00, maxValue = 9_999_999.99), + BANK_ACCOUNT(key = "ACC", type = BankAccount::class), + ALTERNATIVE_BANK_ACCOUNTS(key = "ALT-ACC", type = BankAccountList::class, maxLength = 2), + REFERENCE_FOR_RECIPIENT(key = "RF", type = Int::class, maxLength = 16), RECIPIENT_NAME(key = "RN", type = String::class, maxLength = 35), - PAYMENT_TYPE(key = "PT", type = String::class, maxLength = 3), + PAYMENT_TYPE(key = "PT", type = PaymentType::class, maxLength = 3), MESSAGE(key = "MSG", type = String::class, maxLength = 60), NOTIFY_TYPE(key = "NT", type = NotificationType::class), NOTIFY_ADDRESS(key = "NTA", type = String::class, maxLength = 320), - REPEAT(key = "X-PER", type = Int::class, minValue = 0.0, maxValue = 30.0), + DAYS_TO_REPEAT_IF_UNSUCCESSFUL( + key = "X-PER", + type = Int::class, + minValue = 0.0, + maxValue = 30.0, + ), VARIABLE_SYMBOL(key = "X-VS", type = Long::class, maxLength = 10), SPECIFIC_SYMBOL(key = "X-SS", type = Long::class, maxLength = 10), CONSTANT_SYMBOL(key = "X-KS", type = Long::class, maxLength = 10), - IDENTIFIER(key = "X-ID", type = String::class, maxLength = 20), + REFERENCE_FOR_SENDER(key = "X-ID", type = String::class, maxLength = 20), URL(key = "X-URL", type = String::class, maxLength = 40), } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/io/stepuplabs/spaydkmp/common/PaymentType.kt b/shared/src/commonMain/kotlin/io/stepuplabs/spaydkmp/common/PaymentType.kt new file mode 100644 index 0000000..666c2d8 --- /dev/null +++ b/shared/src/commonMain/kotlin/io/stepuplabs/spaydkmp/common/PaymentType.kt @@ -0,0 +1,11 @@ +package io.stepuplabs.spaydkmp.common + +/* +Payment type representation + */ +@Suppress("UNUSED") +enum class PaymentType(val key: String) { + IMMEDIATE_PAYMENT(key = "IP"); + + override fun toString(): String = key +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/io/stepuplabs/spaydkmp/common/Validator.kt b/shared/src/commonMain/kotlin/io/stepuplabs/spaydkmp/common/Validator.kt index 7e0b78c..0bedab2 100644 --- a/shared/src/commonMain/kotlin/io/stepuplabs/spaydkmp/common/Validator.kt +++ b/shared/src/commonMain/kotlin/io/stepuplabs/spaydkmp/common/Validator.kt @@ -1,5 +1,6 @@ package io.stepuplabs.spaydkmp.common +import com.ionspin.kotlin.bignum.decimal.BigDecimal import io.stepuplabs.spaydkmp.exception.ValidationException import kotlinx.datetime.LocalDate import kotlin.math.log10 @@ -19,8 +20,9 @@ internal class Validator { when (key.type) { LocalDate::class -> return true - Account::class -> return true + BankAccount::class -> return true NotificationType::class -> return true + PaymentType::class -> return true Int::class -> { val typedValue = value as Int @@ -89,6 +91,23 @@ internal class Validator { // length for double doesn't make much sense } + BigDecimal::class -> { + val typedValue = value as BigDecimal + + key.minValue?.let { + if (typedValue < it) { + throw ValidationException("$key is lower than allowed minimum value ($it)") + } + } + key.maxValue?.let { + if (typedValue > it) { + throw ValidationException("$key is higher than allowed maximum value ($it)") + } + } + + // length for big decimal doesn't make much sense + } + String::class -> { val typedValue = value as String @@ -106,18 +125,18 @@ internal class Validator { } } - AccountList::class -> { - val typedValue = value as AccountList + BankAccountList::class -> { + val typedValue = value as BankAccountList // min/max value for list doesn't make much sense key.minLength?.let { - if (typedValue.accounts.count() < it) { + if (typedValue.bankAccounts.count() < it) { throw ValidationException("$key is shorter than allowed minimum length ($it)") } } key.maxLength?.let { - if (typedValue.accounts.count() > it) { + if (typedValue.bankAccounts.count() > it) { throw ValidationException("$key is longer than allowed maximum length ($it)") } } diff --git a/shared/src/commonMain/kotlin/io/stepuplabs/spaydkmp/exception/ValidationException.kt b/shared/src/commonMain/kotlin/io/stepuplabs/spaydkmp/exception/ValidationException.kt index 56823a1..108fbdb 100644 --- a/shared/src/commonMain/kotlin/io/stepuplabs/spaydkmp/exception/ValidationException.kt +++ b/shared/src/commonMain/kotlin/io/stepuplabs/spaydkmp/exception/ValidationException.kt @@ -5,4 +5,4 @@ Exception that represents failed parameter validation effort */ class ValidationException( override val message: String?, -): Exception() \ No newline at end of file +): Throwable() \ No newline at end of file diff --git a/shared/src/commonTest/kotlin/io/stepuplabs/spaydkmp/SpaydTest.kt b/shared/src/commonTest/kotlin/io/stepuplabs/spaydkmp/SpaydTest.kt index eedf917..83995d0 100644 --- a/shared/src/commonTest/kotlin/io/stepuplabs/spaydkmp/SpaydTest.kt +++ b/shared/src/commonTest/kotlin/io/stepuplabs/spaydkmp/SpaydTest.kt @@ -1,9 +1,12 @@ package io.stepuplabs.spaydkmp -import io.stepuplabs.spaydkmp.common.Account -import io.stepuplabs.spaydkmp.common.AccountList +import com.ionspin.kotlin.bignum.decimal.BigDecimal +import com.ionspin.kotlin.bignum.decimal.toBigDecimal +import io.stepuplabs.spaydkmp.common.BankAccount +import io.stepuplabs.spaydkmp.common.BankAccountList import io.stepuplabs.spaydkmp.common.Key import io.stepuplabs.spaydkmp.common.NotificationType +import io.stepuplabs.spaydkmp.common.PaymentType import io.stepuplabs.spaydkmp.exception.ValidationException import kotlinx.datetime.LocalDate import kotlin.test.Test @@ -23,7 +26,7 @@ internal class SpaydTest { val expected = "SPD*1.0*ACC:CZ7603000000000076327632" val spayd = Spayd( - Key.ACCOUNT to Account("CZ7603000000000076327632"), + Key.BANK_ACCOUNT to BankAccount("CZ7603000000000076327632"), ) val actual = spayd.toString() @@ -33,30 +36,30 @@ internal class SpaydTest { @Test fun fullParameterSetPrimaryConstructor() { // copy of what's mentioned in README.md - val expected = "SPD*1.0*DT:2025-12-11*CC:CZK*AM:25.0*ACC:CZ7603000000000076327632*ALT-ACC:CZ7603000000000076327632,CZ7603000000000076327632*RF:1000001*RN:CLOVEK V TISNI*PT:TYP*MSG:DONATION*NT:P*NTA:+420321654987*X-PER:3*X-VS:9*X-SS:9*X-KS:9*X-ID:ID*X-URL:HTTPS://STEPUPLABS.IO" + val expected = "SPD*1.0*DT:20251211*CC:CZK*AM:25.12*ACC:CZ7603000000000076327632*ALT-ACC:CZ7603000000000076327632,CZ7603000000000076327632*RF:1000001*RN:CLOVEK V TISNI*PT:IP*MSG:DONATION*NT:P*NTA:+420321654987*X-PER:3*X-VS:9*X-SS:9*X-KS:9*X-ID:ID*X-URL:HTTPS://STEPUPLABS.IO" - val altAccounts: List = listOf( - Account("CZ7603000000000076327632"), - Account("CZ7603000000000076327632"), + val altBankAccounts: List = listOf( + BankAccount("CZ7603000000000076327632"), + BankAccount("CZ7603000000000076327632"), ) val spayd = Spayd( - Key.DATE to LocalDate(2025, 12, 11), - Key.CURRENCY to "CZK", - Key.AMOUNT to 25.00, - Key.ACCOUNT to Account("CZ7603000000000076327632"), - Key.ALTERNATE_ACCOUNTS to AccountList(altAccounts), - Key.SENDER_REFERENCE to 1000001, + Key.DUE_DATE to LocalDate(2025, 12, 11), + Key.CURRENCY_CODE to "CZK", + Key.AMOUNT to "25.12".toBigDecimal(), + Key.BANK_ACCOUNT to BankAccount("CZ7603000000000076327632"), + Key.ALTERNATIVE_BANK_ACCOUNTS to BankAccountList(altBankAccounts), + Key.REFERENCE_FOR_RECIPIENT to 1000001, Key.RECIPIENT_NAME to "CLOVEK V TISNI", - Key.PAYMENT_TYPE to "TYP", + Key.PAYMENT_TYPE to PaymentType.IMMEDIATE_PAYMENT, Key.MESSAGE to "DONATION", Key.NOTIFY_TYPE to NotificationType.PHONE, Key.NOTIFY_ADDRESS to "+420321654987", - Key.REPEAT to 3, + Key.DAYS_TO_REPEAT_IF_UNSUCCESSFUL to 3, Key.VARIABLE_SYMBOL to 9L, Key.SPECIFIC_SYMBOL to 9L, Key.CONSTANT_SYMBOL to 9L, - Key.IDENTIFIER to "ID", + Key.REFERENCE_FOR_SENDER to "ID", Key.URL to "https://stepuplabs.io", ) val actual = spayd.toString() @@ -67,33 +70,47 @@ internal class SpaydTest { @Test fun fullParameterSetSecondaryConstructor() { // copy of what's mentioned in README.md - val expected = "SPD*1.0*ACC:CZ7603000000000076327632*ALT-ACC:CZ7603000000000076327632,CZ7603000000000076327632*ALT-ACC:CZ7603000000000076327632,CZ7603000000000076327632*CC:CZK*AM:25.0*DT:2025-12-11*RF:1000001*RN:CLOVEK V TISNI*PT:TYP*MSG:DONATION*NT:P*NTA:+420321654987*X-PER:3*X-VS:9*X-SS:9*X-KS:9*X-ID:ID*X-URL:HTTPS://STEPUPLABS.IO" + val expected = "SPD*1.0*ACC:CZ7603000000000076327632*ALT-ACC:CZ7603000000000076327632,CZ7603000000000076327632*CC:CZK*AM:25.12*DT:20251211*RF:1000001*RN:CLOVEK V TISNI*PT:IP*MSG:DONATION*NT:P*NTA:+420321654987*X-PER:3*X-VS:9*X-SS:9*X-KS:9*X-ID:ID*X-URL:HTTPS://STEPUPLABS.IO" - val altAccounts: List = listOf( - Account("CZ7603000000000076327632"), - Account("CZ7603000000000076327632"), + val altBankAccounts: List = listOf( + BankAccount("CZ7603000000000076327632"), + BankAccount("CZ7603000000000076327632"), ) val spayd = Spayd( - account = Account("CZ7603000000000076327632"), - alternateAccounts = AccountList(altAccounts), - currency = "CZK", - amount = 25.00, - date = LocalDate(2025, 12, 11), - senderReference = 1000001, + bankAccount = BankAccount("CZ7603000000000076327632"), + alternativeBankAccounts = BankAccountList(altBankAccounts), + currencyCode = "CZK", + amount = "25.12".toBigDecimal(), + dueDate = LocalDate(2025, 12, 11), + referenceForRecipient = 1000001, recipientName = "CLOVEK V TISNI", - paymentType = "TYP", + paymentType = PaymentType.IMMEDIATE_PAYMENT, message = "DONATION", notificationType = NotificationType.PHONE, notificationAddress = "+420321654987", - repeat = 3, + daysToRepeatIfUnsuccessfull = 3, variableSymbol = 9L, specificSymbol = 9L, constantSymbol = 9L, - identifier = "ID", + referenceForSender = "ID", url = "https://stepuplabs.io", ) val actual = spayd.toString() assertEquals(expected, actual) } + + @Test + fun notAllowedCharactersInMessage() { + // copy of what's mentioned in README.md + val expected = "SPD*1.0*ACC:CZ7603000000000076327632*MSG:PLI LUOUK K, VOLE" + + val spayd = Spayd( + Key.BANK_ACCOUNT to BankAccount("CZ7603000000000076327632"), + Key.MESSAGE to "Příliš žluťoučký kůň, vole", + ) + val actual = spayd.toString() + + assertEquals(expected, actual) + } } \ No newline at end of file diff --git a/shared/src/commonTest/kotlin/io/stepuplabs/spaydkmp/common/ValidatorTest.kt b/shared/src/commonTest/kotlin/io/stepuplabs/spaydkmp/common/ValidatorTest.kt index bd26c28..ef38bc8 100644 --- a/shared/src/commonTest/kotlin/io/stepuplabs/spaydkmp/common/ValidatorTest.kt +++ b/shared/src/commonTest/kotlin/io/stepuplabs/spaydkmp/common/ValidatorTest.kt @@ -1,5 +1,6 @@ package io.stepuplabs.spaydkmp.common +import com.ionspin.kotlin.bignum.decimal.toBigDecimal import io.stepuplabs.spaydkmp.exception.ValidationException import kotlin.test.Test import kotlin.test.assertEquals @@ -11,35 +12,35 @@ internal class ValidatorTest { @Test fun type() { assertEquals( - validator.validate(value = Account(iban = "XXX"), Key.ACCOUNT), + validator.validate(value = BankAccount(iban = "XXX"), Key.BANK_ACCOUNT), true, ) assertFailsWith(ValidationException::class) { - validator.validate(value = "IBAN", Key.ACCOUNT) // too short + validator.validate(value = "IBAN", Key.BANK_ACCOUNT) // too short } } @Test fun currency() { assertEquals( - validator.validate(value = "CZE", Key.CURRENCY), + validator.validate(value = "CZE", Key.CURRENCY_CODE), true, ) assertFailsWith(ValidationException::class) { - validator.validate(value = "CZ", Key.CURRENCY) // too short + validator.validate(value = "CZ", Key.CURRENCY_CODE) // too short } assertFailsWith(ValidationException::class) { - validator.validate(value = "CZECH", Key.CURRENCY) // too long + validator.validate(value = "CZECH", Key.CURRENCY_CODE) // too long } } @Test fun amount() { assertEquals( - validator.validate(value = 500.0, Key.AMOUNT), + validator.validate(value = "500.00".toBigDecimal(), Key.AMOUNT), true, ) @@ -56,32 +57,32 @@ internal class ValidatorTest { fun alternateAccounts() { assertEquals( validator.validate( - value = AccountList(accounts = listOf()), - Key.ALTERNATE_ACCOUNTS + value = BankAccountList(bankAccounts = listOf()), + Key.ALTERNATIVE_BANK_ACCOUNTS ), true, ) assertEquals( validator.validate( - value = AccountList( - accounts = listOf(Account(iban = "XXX"), Account(iban = "YYY")), + value = BankAccountList( + bankAccounts = listOf(BankAccount(iban = "XXX"), BankAccount(iban = "YYY")), ), - Key.ALTERNATE_ACCOUNTS + Key.ALTERNATIVE_BANK_ACCOUNTS ), true, ) assertFailsWith(ValidationException::class) { validator.validate( - value = AccountList( - accounts = listOf( - Account(iban = "XXX"), - Account(iban = "YYY"), - Account(iban = "ZZZZ"), + value = BankAccountList( + bankAccounts = listOf( + BankAccount(iban = "XXX"), + BankAccount(iban = "YYY"), + BankAccount(iban = "ZZZZ"), ), ), - Key.ALTERNATE_ACCOUNTS + Key.ALTERNATIVE_BANK_ACCOUNTS ) } } @@ -89,12 +90,12 @@ internal class ValidatorTest { @Test fun senderReference() { assertEquals( - validator.validate(value = 123456, Key.SENDER_REFERENCE), + validator.validate(value = 123456, Key.REFERENCE_FOR_RECIPIENT), true, ) assertFailsWith(ValidationException::class) { - validator.validate(value = 12345678901234567, Key.SENDER_REFERENCE) // too long + validator.validate(value = 12345678901234567, Key.REFERENCE_FOR_RECIPIENT) // too long } } @@ -113,18 +114,6 @@ internal class ValidatorTest { } } - @Test - fun paymentType() { - assertEquals( - validator.validate(value = "PTY", Key.PAYMENT_TYPE), - true, - ) - - assertFailsWith(ValidationException::class) { - validator.validate(value = "Payment TYpe", Key.PAYMENT_TYPE) // too long - } - } - @Test fun message() { assertEquals( @@ -158,16 +147,16 @@ internal class ValidatorTest { @Test fun repeat() { assertEquals( - validator.validate(value = 3, Key.REPEAT), + validator.validate(value = 3, Key.DAYS_TO_REPEAT_IF_UNSUCCESSFUL), true, ) assertFailsWith(ValidationException::class) { - validator.validate(value = -1, Key.REPEAT) // too low + validator.validate(value = -1, Key.DAYS_TO_REPEAT_IF_UNSUCCESSFUL) // too low } assertFailsWith(ValidationException::class) { - validator.validate(value = 33, Key.REPEAT) // too high + validator.validate(value = 33, Key.DAYS_TO_REPEAT_IF_UNSUCCESSFUL) // too high } } @@ -210,12 +199,12 @@ internal class ValidatorTest { @Test fun identifier() { assertEquals( - validator.validate(value = "IDENTIFIER", Key.IDENTIFIER), + validator.validate(value = "IDENTIFIER", Key.REFERENCE_FOR_SENDER), true, ) assertFailsWith(ValidationException::class) { - validator.validate(value = "ABCDEFGHIJKLMNOPQRSTUVWXYZ", Key.IDENTIFIER) // too long + validator.validate(value = "ABCDEFGHIJKLMNOPQRSTUVWXYZ", Key.REFERENCE_FOR_SENDER) // too long } }