libeufin

Integration and sandbox testing for FinTech APIs and data formats
Log | Files | Refs | Submodules | README | LICENSE

commit fc25b48f7357f703c879c95b9e674057e0452df0
parent a257e22ef11443967453de884df12e9b818f3054
Author: Marcello Stanisci <stanisci.m@gmail.com>
Date:   Fri, 13 Mar 2020 14:31:10 +0100

6125.

Associating _international_ bank account number and bank
code with one subscriber.

Diffstat:
Mcli/python/libeufin-cli | 65++++++++++++++++++++++++++++++++++++++++++++---------------------
Mnexus/src/main/kotlin/tech/libeufin/nexus/Main.kt | 83++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
Mutil/src/main/kotlin/ebics_h004/EbicsTypes.kt | 4++--
3 files changed, 107 insertions(+), 45 deletions(-)

diff --git a/cli/python/libeufin-cli b/cli/python/libeufin-cli @@ -372,26 +372,6 @@ def hpd(obj, account_id, nexus_base_url): print(resp.content.decode("utf-8")) -@ebics.command(help="Send HKD message") -@click.pass_obj -@click.option( - "--account-id", - help="Numerical ID of the customer at the Nexus", - required=True -) -@click.argument( - "nexus-base-url" -) -def hkd(obj, account_id, nexus_base_url): - url = urljoin(nexus_base_url, "/ebics/subscribers/{}/sendHKD".format(account_id)) - try: - resp = post(url) - except Exception: - print("Could not reach the bank") - return - - print(resp.content.decode("utf-8")) - @ebics.command(help="Send C52 message") @click.pass_obj @click.option( @@ -690,7 +670,10 @@ def prepare_payment( print(resp.content.decode("utf-8")) -@ebics.command(help="Send HTD message") +@ebics.command( + help="Trigger the Nexus to download and store \ + bank accounts information (via a HTD message)" +) @click.pass_context @click.option( "--account-id", @@ -720,6 +703,46 @@ def fetch_accounts(ctx, account_id, prepare, nexus_base_url): print(resp.content.decode("utf-8")) +@ebics.command(help="Send HTD message") +@click.pass_obj +@click.option( + "--account-id", + help="Numerical ID of the customer at the Nexus", + required=True +) +@click.argument( + "nexus-base-url" +) +def htd(obj, account_id, nexus_base_url): + url = urljoin(nexus_base_url, "/ebics/subscribers/{}/sendHTD".format(account_id)) + try: + resp = get(url) + except Exception: + print("Could not reach the Nexus") + return + print(resp.content.decode("utf-8")) + + + +@ebics.command(help="Send HKD message") +@click.pass_obj +@click.option( + "--account-id", + help="Numerical ID of the customer at the Nexus", + required=True +) +@click.argument( + "nexus-base-url" +) +def hkd(obj, account_id, nexus_base_url): + url = urljoin(nexus_base_url, "/ebics/subscribers/{}/sendHKD".format(account_id)) + try: + resp = get(url) + except Exception: + print("Could not reach the Nexus") + return + print(resp.content.decode("utf-8")) + @ebics.command(help="Send HIA message") @click.pass_obj @click.option( diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt @@ -29,6 +29,7 @@ import io.ktor.features.StatusPages import io.ktor.gson.gson import io.ktor.http.ContentType import io.ktor.http.HttpStatusCode +import io.ktor.http.cio.websocket.CloseReason import io.ktor.request.receive import io.ktor.request.uri import io.ktor.response.respond @@ -49,7 +50,9 @@ import org.slf4j.Logger import org.slf4j.LoggerFactory import org.slf4j.event.Level import tech.libeufin.util.* +import tech.libeufin.util.ebics_h004.EbicsRequest import tech.libeufin.util.ebics_h004.EbicsTypes +import tech.libeufin.util.ebics_h004.HKDResponseOrderData import tech.libeufin.util.ebics_h004.HTDResponseOrderData import java.lang.StringBuilder import java.security.interfaces.RSAPublicKey @@ -109,6 +112,40 @@ fun getBankAccountDetailsFromAcctid(id: String): EbicsAccountInfoElement { ) } } + +/** + * Skip national only-numeric bank account ids, and return the first IBAN in list + */ +fun extractFirstIban(bankAccounts: List<EbicsTypes.AbstractAccountNumber>?): String? { + if (bankAccounts == null) + return null + + for (item in bankAccounts) { + if (item is EbicsTypes.GeneralAccountNumber) { + if (item.international) + return item.value + } + } + return null +} + +/** + * Skip national only-numeric codes, and returns the first BIC in list + */ +fun extractFirstBic(bankCodes: List<EbicsTypes.AbstractBankCode>?): String? { + if (bankCodes == null) + return null + + for (item in bankCodes) { + if (item is EbicsTypes.GeneralBankCode) { + if (item.international) + return item.value + } + } + + return null +} + fun getSubscriberDetailsFromBankAccount(bankAccountId: String): EbicsClientSubscriberDetails { return transaction { val accountInfo = EbicsAccountInfoEntity.findById(bankAccountId) ?: throw NexusError(HttpStatusCode.NotFound, "Bank account ($bankAccountId) not managed by Nexus") @@ -243,7 +280,8 @@ fun createPain001document(pain001Entity: Pain001Entity): String { element("CdtTrfTxInf") { element("PmtId") { element("EndToEndId") { - text(pain001Entity.id.value.toString()) + // text(pain001Entity.id.value.toString()) + text("NOTPROVIDED") } } element("Amt/InstdAmt") { @@ -488,17 +526,18 @@ fun main() { }.firstOrNull() ?: throw NexusError(HttpStatusCode.Accepted, reason = "No ready payments found") kotlin.Pair(createPain001document(entity), entity.debtorAccount) } - logger.info("Processing payment for bank account: ${debtorAccount}") + logger.debug("Processing payment for bank account: ${debtorAccount}") + logger.debug("Uploading PAIN.001: ${painDoc}") val subscriberDetails = getSubscriberDetailsFromBankAccount(debtorAccount) doEbicsUploadTransaction( client, subscriberDetails, - "CCC", + "CCT", painDoc.toByteArray(Charsets.UTF_8), EbicsStandardOrderParams() ) call.respondText( - "CCC message submitted to the bank", + "CCT message submitted to the bank", ContentType.Text.Plain, HttpStatusCode.OK ) @@ -627,12 +666,15 @@ fun main() { return@post } - post("/ebics/subscribers/{id}/sendHtd") { + get("/ebics/subscribers/{id}/sendHTD") { val customerIdAtNexus = expectId(call.parameters["id"]) - val paramsJson = call.receive<EbicsStandardOrderParamsJson>() - val orderParams = paramsJson.toOrderParams() val subscriberData = getSubscriberDetailsFromId(customerIdAtNexus) - val response = doEbicsDownloadTransaction(client, subscriberData, "HTD", orderParams) + val response = doEbicsDownloadTransaction( + client, + subscriberData, + "HTD", + EbicsStandardOrderParams() + ) when (response) { is EbicsDownloadSuccessResult -> { call.respondText( @@ -648,7 +690,7 @@ fun main() { ) } } - return@post + return@get } post("/ebics/subscribers/{id}/sendHAA") { @@ -741,10 +783,15 @@ fun main() { return@post } - post("/ebics/subscribers/{id}/sendHKD") { + get("/ebics/subscribers/{id}/sendHKD") { val id = expectId(call.parameters["id"]) val subscriberData = getSubscriberDetailsFromId(id) - val response = doEbicsDownloadTransaction(client, subscriberData, "HKD", EbicsStandardOrderParams()) + val response = doEbicsDownloadTransaction( + client, + subscriberData, + "HKD", + EbicsStandardOrderParams() + ) when (response) { is EbicsDownloadSuccessResult -> { call.respondText( @@ -760,7 +807,7 @@ fun main() { ) } } - return@post + return@get } post("/ebics/subscribers/{id}/sendTSD") { @@ -1133,16 +1180,8 @@ fun main() { EbicsAccountInfoEntity.new(id = it.id) { this.subscriber = getSubscriberEntityFromId(customerIdAtNexus) accountHolder = it.accountHolder - iban = when (val firstAccount = it.accountNumberList?.get(0)) { - is EbicsTypes.GeneralAccountNumber -> firstAccount.value - is EbicsTypes.NationalAccountNumber -> firstAccount.value - else -> throw NexusError(HttpStatusCode.NotFound, reason = "Unknown bank account type because of IBAN type") - } - bankCode = when (val firstBankCode = it.bankCodeList?.get(0)) { - is EbicsTypes.GeneralBankCode -> firstBankCode.value - is EbicsTypes.NationalBankCode -> firstBankCode.value - else -> throw NexusError(HttpStatusCode.NotFound, reason = "Unknown bank account type because of BIC type") - } + iban = extractFirstIban(it.accountNumberList) ?: throw NexusError(HttpStatusCode.NotFound, reason = "bank gave no IBAN") + bankCode = extractFirstBic(it.bankCodeList) ?: throw NexusError(HttpStatusCode.NotFound, reason = "bank gave no BIC") } } } diff --git a/util/src/main/kotlin/ebics_h004/EbicsTypes.kt b/util/src/main/kotlin/ebics_h004/EbicsTypes.kt @@ -362,7 +362,7 @@ object EbicsTypes { @XmlAccessorType(XmlAccessType.NONE) class GeneralAccountNumber : AbstractAccountNumber { @get:XmlAttribute(name = "international") - var international: Boolean = false + var international: Boolean = true @get:XmlValue lateinit var value: String @@ -385,7 +385,7 @@ object EbicsTypes { var prefix: String? = null @get:XmlAttribute(name = "international") - var international: Boolean = false + var international: Boolean = true @get:XmlValue lateinit var value: String