[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[libeufin] branch master updated: move towards new API
From: |
gnunet |
Subject: |
[libeufin] branch master updated: move towards new API |
Date: |
Wed, 20 May 2020 23:21:39 +0200 |
This is an automated email from the git hooks/post-receive script.
dold pushed a commit to branch master
in repository libeufin.
The following commit(s) were added to refs/heads/master by this push:
new b06990b move towards new API
b06990b is described below
commit b06990b56aceda9bfe5769e198a2f8ba7f467fee
Author: Florian Dold <address@hidden>
AuthorDate: Thu May 21 02:51:19 2020 +0530
move towards new API
---
integration-tests/test-ebics.py | 47 +-
nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt | 46 +-
.../main/kotlin/tech/libeufin/nexus/EbicsClient.kt | 80 +-
.../src/main/kotlin/tech/libeufin/nexus/Helpers.kt | 151 +--
nexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt | 39 +-
nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt | 721 +++++++------
nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt | 1112 ++++++++++----------
nexus/src/test/kotlin/authentication.kt | 7 +-
nexus/src/test/kotlin/taler.kt | 44 +-
util/src/main/kotlin/Ebics.kt | 8 +
10 files changed, 1133 insertions(+), 1122 deletions(-)
diff --git a/integration-tests/test-ebics.py b/integration-tests/test-ebics.py
index ac633aa..0837813 100755
--- a/integration-tests/test-ebics.py
+++ b/integration-tests/test-ebics.py
@@ -19,7 +19,7 @@ import base64
# -> (a) Make a Nexus user, (b) make a EBICS subscriber
# associated to that user
#
-# 2 Prepare the Ebics transport for the nexus user.
+# 2 Prepare the Ebics bank connection for the nexus user.
# -> (a) Upload keys from Nexus to the Bank (INI & HIA),
# (b) Download key from the Bank (HPB) to the Nexus,
# and (c) Fetch the bank account owned by that subscriber
@@ -178,12 +178,15 @@ assertResponse(
)
)
-# 1.b, make a ebics transport for the new user.
+print("creating bank connection")
+
+# 1.b, make a ebics bank connection for the new user.
assertResponse(
post(
- "http://localhost:5001/bank-transports",
+ "http://localhost:5001/bank-connections",
json=dict(
- transport=dict(name="my-ebics", type="ebics"),
+ name="my-ebics",
+ type="ebics",
data=dict(
ebicsURL=EBICS_URL, hostID=HOST_ID, partnerID=PARTNER_ID,
userID=USER_ID
),
@@ -192,19 +195,21 @@ assertResponse(
)
)
+print("sending ini")
+
# 2.a, upload keys to the bank (INI & HIA)
assertResponse(
post(
- "http://localhost:5001/bank-transports/sendINI",
- json=dict(type="ebics", name="my-ebics"),
+ "http://localhost:5001/bank-connections/my-ebics/ebics/send-ini",
+ json=dict(),
headers=dict(Authorization=USER_AUTHORIZATION_HEADER),
)
)
assertResponse(
post(
- "http://localhost:5001/bank-transports/sendHIA",
- json=dict(type="ebics", name="my-ebics"),
+ "http://localhost:5001/bank-connections/my-ebics/ebics/send-hia",
+ json=dict(),
headers=dict(Authorization=USER_AUTHORIZATION_HEADER),
)
)
@@ -212,8 +217,8 @@ assertResponse(
# 2.b, download keys from the bank (HPB)
assertResponse(
post(
- "http://localhost:5001/bank-transports/syncHPB",
- json=dict(type="ebics", name="my-ebics"),
+ "http://localhost:5001/bank-connections/my-ebics/ebics/send-hpb",
+ json=dict(),
headers=dict(Authorization=USER_AUTHORIZATION_HEADER),
)
)
@@ -221,8 +226,8 @@ assertResponse(
# 2.c, fetch bank account information
assertResponse(
post(
- "http://localhost:5001/bank-transports/syncHTD",
- json=dict(type="ebics", name="my-ebics"),
+
"http://localhost:5001/bank-connections/my-ebics/ebics/import-accounts",
+ json=dict(),
headers=dict(Authorization=USER_AUTHORIZATION_HEADER),
)
)
@@ -230,8 +235,8 @@ assertResponse(
# 3, ask nexus to download history
assertResponse(
post(
- "http://localhost:5001/bank-accounts/collected-transactions",
- json=dict(transport=dict(type="ebics", name="my-ebics")),
+
f"http://localhost:5001/bank-accounts/{BANK_ACCOUNT_LABEL}/fetch-transactions",
+ json=dict(),
headers=dict(Authorization=USER_AUTHORIZATION_HEADER),
)
)
@@ -239,9 +244,7 @@ assertResponse(
# 4, make sure history is empty
resp = assertResponse(
get(
- "http://localhost:5001/bank-accounts/{}/collected-transactions".format(
- BANK_ACCOUNT_LABEL
- ),
+
f"http://localhost:5001/bank-accounts/{BANK_ACCOUNT_LABEL}/transactions",
headers=dict(Authorization=USER_AUTHORIZATION_HEADER),
)
)
@@ -271,8 +274,8 @@ if PREPARED_PAYMENT_UUID == None:
# 5.b, submit prepared statement
assertResponse(
post(
- "http://localhost:5001/bank-accounts/prepared-payments/submit",
- json=dict(uuid=PREPARED_PAYMENT_UUID),
+
f"http://localhost:5001/bank-accounts/{BANK_ACCOUNT_LABEL}/prepared-payments/{PREPARED_PAYMENT_UUID}/submit",
+ json=dict(),
headers=dict(Authorization=USER_AUTHORIZATION_HEADER),
)
)
@@ -280,7 +283,7 @@ assertResponse(
# 6, request history after payment submission
assertResponse(
post(
- "http://localhost:5001/bank-accounts/collected-transactions",
+
f"http://localhost:5001/bank-accounts/{BANK_ACCOUNT_LABEL}/fetch-transactions",
json=dict(),
headers=dict(Authorization=USER_AUTHORIZATION_HEADER),
)
@@ -288,9 +291,7 @@ assertResponse(
resp = assertResponse(
get(
- "http://localhost:5001/bank-accounts/{}/collected-transactions".format(
- BANK_ACCOUNT_LABEL
- ),
+
f"http://localhost:5001/bank-accounts/{BANK_ACCOUNT_LABEL}/transactions",
headers=dict(Authorization=USER_AUTHORIZATION_HEADER),
)
)
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt
b/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt
index d44a514..e0d026a 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt
@@ -8,10 +8,6 @@ import org.jetbrains.exposed.sql.StdOutSqlLogger
import org.jetbrains.exposed.sql.addLogger
import org.jetbrains.exposed.sql.transactions.TransactionManager
import org.jetbrains.exposed.sql.transactions.transaction
-import tech.libeufin.nexus.EbicsSubscribersTable.entityId
-import tech.libeufin.nexus.EbicsSubscribersTable.primaryKey
-import tech.libeufin.nexus.NexusUsersTable.entityId
-import tech.libeufin.nexus.NexusUsersTable.primaryKey
import tech.libeufin.util.amount
import java.sql.Connection
@@ -152,9 +148,6 @@ object PreparedPaymentsTable : IdTable<String>() {
* this state can be reached when the payment gets listed in a CRZ
* response OR when the payment doesn't show up in a C52/C53 response */
val invalid = bool("invalid").default(false)
-
- /** never really used, but it makes sure the user always exists */
- val nexusUser = reference("nexusUser", NexusUsersTable)
}
class PreparedPaymentEntity(id: EntityID<String>) : Entity<String>(id) {
@@ -175,7 +168,6 @@ class PreparedPaymentEntity(id: EntityID<String>) :
Entity<String>(id) {
var creditorName by PreparedPaymentsTable.creditorName
var submitted by PreparedPaymentsTable.submitted
var invalid by PreparedPaymentsTable.invalid
- var nexusUser by NexusUserEntity referencedOn
PreparedPaymentsTable.nexusUser
}
/**
@@ -186,6 +178,7 @@ object BankAccountsTable : IdTable<String>() {
val accountHolder = text("accountHolder")
val iban = text("iban")
val bankCode = text("bankCode")
+ val defaultBankConnection = reference("defaultBankConnection",
NexusBankConnectionsTable).nullable()
}
class BankAccountEntity(id: EntityID<String>) : Entity<String>(id) {
@@ -194,10 +187,10 @@ class BankAccountEntity(id: EntityID<String>) :
Entity<String>(id) {
var accountHolder by BankAccountsTable.accountHolder
var iban by BankAccountsTable.iban
var bankCode by BankAccountsTable.bankCode
+ var defaultBankConnection by NexusBankConnectionEntity
optionalReferencedOn BankAccountsTable.defaultBankConnection
}
-object EbicsSubscribersTable : IdTable<String>() {
- override val id = varchar("id", ID_MAX_LENGTH).entityId().primaryKey()
+object EbicsSubscribersTable : IntIdTable() {
val ebicsURL = text("ebicsURL")
val hostID = text("hostID")
val partnerID = text("partnerID")
@@ -208,11 +201,11 @@ object EbicsSubscribersTable : IdTable<String>() {
val authenticationPrivateKey = blob("authenticationPrivateKey")
val bankEncryptionPublicKey = blob("bankEncryptionPublicKey").nullable()
val bankAuthenticationPublicKey =
blob("bankAuthenticationPublicKey").nullable()
- var nexusUser = reference("nexusUser", NexusUsersTable)
+ val nexusBankConnection = reference("nexusBankConnection",
NexusBankConnectionsTable)
}
-class EbicsSubscriberEntity(id: EntityID<String>) : Entity<String>(id) {
- companion object : EntityClass<String,
EbicsSubscriberEntity>(EbicsSubscribersTable)
+class EbicsSubscriberEntity(id: EntityID<Int>) : IntEntity(id) {
+ companion object :
IntEntityClass<EbicsSubscriberEntity>(EbicsSubscribersTable)
var ebicsURL by EbicsSubscribersTable.ebicsURL
var hostID by EbicsSubscribersTable.hostID
@@ -224,7 +217,7 @@ class EbicsSubscriberEntity(id: EntityID<String>) :
Entity<String>(id) {
var authenticationPrivateKey by
EbicsSubscribersTable.authenticationPrivateKey
var bankEncryptionPublicKey by
EbicsSubscribersTable.bankEncryptionPublicKey
var bankAuthenticationPublicKey by
EbicsSubscribersTable.bankAuthenticationPublicKey
- var nexusUser by NexusUserEntity referencedOn
EbicsSubscribersTable.nexusUser
+ var nexusBankConnection by NexusBankConnectionEntity referencedOn
EbicsSubscribersTable.nexusBankConnection
}
object NexusUsersTable : IdTable<String>() {
@@ -240,23 +233,16 @@ class NexusUserEntity(id: EntityID<String>) :
Entity<String>(id) {
var superuser by NexusUsersTable.superuser
}
-object BankAccountMapsTable : IntIdTable() {
- val ebicsSubscriber = reference("ebicsSubscriber", EbicsSubscribersTable)
- val bankAccount = reference("bankAccount", BankAccountsTable)
- val nexusUser = reference("nexusUser", NexusUsersTable)
-}
-
-class BankAccountMapEntity(id: EntityID<Int>) : IntEntity(id) {
- companion object :
IntEntityClass<BankAccountMapEntity>(BankAccountMapsTable)
-
- var ebicsSubscriber by EbicsSubscriberEntity referencedOn
BankAccountMapsTable.ebicsSubscriber
- var bankAccount by BankAccountEntity referencedOn
BankAccountMapsTable.bankAccount
- var nexusUser by NexusUserEntity referencedOn
BankAccountMapsTable.nexusUser
+object NexusBankConnectionsTable : IdTable<String>() {
+ override val id =
NexusBankConnectionsTable.text("id").entityId().primaryKey()
+ val type = text("type")
+ val owner = reference("user", NexusUsersTable)
}
-object NexusBankConnectionsTable : IdTable<String>() {
- override val id = EbicsSubscribersTable.text("id").entityId().primaryKey()
-
+class NexusBankConnectionEntity(id: EntityID<String>) : Entity<String>(id) {
+ companion object : EntityClass<String,
NexusBankConnectionEntity>(NexusBankConnectionsTable)
+ var type by NexusBankConnectionsTable.type
+ var owner by NexusUserEntity referencedOn NexusBankConnectionsTable.owner
}
fun dbCreateTables() {
@@ -272,7 +258,7 @@ fun dbCreateTables() {
RawBankTransactionsTable,
TalerIncomingPayments,
TalerRequestedPayments,
- BankAccountMapsTable
+ NexusBankConnectionsTable
)
}
}
\ No newline at end of file
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsClient.kt
b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsClient.kt
index 055c67c..2e28737 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsClient.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsClient.kt
@@ -1,3 +1,6 @@
+/**
+ * High-level interface for the EBICS protocol.
+ */
package tech.libeufin.nexus
import io.ktor.client.HttpClient
@@ -6,7 +9,7 @@ import io.ktor.http.HttpStatusCode
import tech.libeufin.util.*
import java.util.*
-suspend inline fun HttpClient.postToBank(url: String, body: String): String {
+private suspend inline fun HttpClient.postToBank(url: String, body: String):
String {
logger.debug("Posting: $body")
val response: String = try {
this.post<String>(
@@ -58,7 +61,10 @@ suspend fun doEbicsDownloadTransaction(
// Success, nothing to do!
}
else -> {
- throw NexusError(HttpStatusCode.InternalServerError, "unexpected
return code ${initResponse.technicalReturnCode}")
+ throw NexusError(
+ HttpStatusCode.InternalServerError,
+ "unexpected return code ${initResponse.technicalReturnCode}"
+ )
}
}
@@ -73,13 +79,19 @@ suspend fun doEbicsDownloadTransaction(
}
val transactionID =
- initResponse.transactionID ?: throw
NexusError(HttpStatusCode.InternalServerError, "initial response must contain
transaction ID")
+ initResponse.transactionID ?: throw NexusError(
+ HttpStatusCode.InternalServerError,
+ "initial response must contain transaction ID"
+ )
val encryptionInfo = initResponse.dataEncryptionInfo
?: throw NexusError(HttpStatusCode.InternalServerError, "initial
response did not contain encryption info")
val initOrderDataEncChunk = initResponse.orderDataEncChunk
- ?: throw NexusError(HttpStatusCode.InternalServerError,"initial
response for download transaction does not contain data transfer")
+ ?: throw NexusError(
+ HttpStatusCode.InternalServerError,
+ "initial response for download transaction does not contain data
transfer"
+ )
payloadChunks.add(initOrderDataEncChunk)
@@ -97,7 +109,7 @@ suspend fun doEbicsDownloadTransaction(
EbicsReturnCode.EBICS_DOWNLOAD_POSTPROCESS_DONE -> {
}
else -> {
- throw NexusError(HttpStatusCode.InternalServerError,"unexpected
return code")
+ throw NexusError(HttpStatusCode.InternalServerError, "unexpected
return code")
}
}
return EbicsDownloadSuccessResult(respPayload)
@@ -124,7 +136,10 @@ suspend fun doEbicsUploadTransaction(
}
val transactionID =
- initResponse.transactionID ?: throw
NexusError(HttpStatusCode.InternalServerError,"init response must have
transaction ID")
+ initResponse.transactionID ?: throw NexusError(
+ HttpStatusCode.InternalServerError,
+ "init response must have transaction ID"
+ )
logger.debug("INIT phase passed!")
/* now send actual payload */
@@ -147,7 +162,58 @@ suspend fun doEbicsUploadTransaction(
EbicsReturnCode.EBICS_OK -> {
}
else -> {
- throw NexusError(HttpStatusCode.InternalServerError,"unexpected
return code")
+ throw NexusError(HttpStatusCode.InternalServerError, "unexpected
return code")
}
}
+}
+
+suspend fun doEbicsHostVersionQuery(client: HttpClient, ebicsBaseUrl: String,
ebicsHostId: String): EbicsHevDetails {
+ val ebicsHevRequest = makeEbicsHEVRequestRaw(ebicsHostId)
+ val resp = client.postToBank(ebicsBaseUrl, ebicsHevRequest)
+ val versionDetails = parseEbicsHEVResponse(resp)
+ return versionDetails
+}
+
+suspend fun doEbicsIniRequest(
+ client: HttpClient,
+ subscriberDetails: EbicsClientSubscriberDetails
+): EbicsKeyManagementResponseContent {
+ val request = makeEbicsIniRequest(subscriberDetails)
+ val respStr = client.postToBank(
+ subscriberDetails.ebicsUrl,
+ request
+ )
+ val resp = parseAndDecryptEbicsKeyManagementResponse(subscriberDetails,
respStr)
+ return resp
+}
+
+suspend fun doEbicsHiaRequest(
+ client: HttpClient,
+ subscriberDetails: EbicsClientSubscriberDetails
+): EbicsKeyManagementResponseContent {
+ val request = makeEbicsHiaRequest(subscriberDetails)
+ val respStr = client.postToBank(
+ subscriberDetails.ebicsUrl,
+ request
+ )
+ val resp = parseAndDecryptEbicsKeyManagementResponse(subscriberDetails,
respStr)
+ return resp
+}
+
+
+suspend fun doEbicsHpbRequest(
+ client: HttpClient,
+ subscriberDetails: EbicsClientSubscriberDetails
+): HpbResponseData {
+ val request = makeEbicsHpbRequest(subscriberDetails)
+ val respStr = client.postToBank(
+ subscriberDetails.ebicsUrl,
+ request
+ )
+ val parsedResponse =
parseAndDecryptEbicsKeyManagementResponse(subscriberDetails, respStr)
+ val orderData = parsedResponse.orderData ?: throw NexusError(
+ HttpStatusCode.InternalServerError,
+ "Cannot find data in a HPB response"
+ )
+ return parseEbicsHpbOrder(orderData)
}
\ No newline at end of file
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt
b/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt
index 5967af0..0c450a3 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt
@@ -1,10 +1,9 @@
package tech.libeufin.nexus
-import com.fasterxml.jackson.databind.JsonNode
-import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
-import com.fasterxml.jackson.module.kotlin.treeToValue
+import io.ktor.application.ApplicationCall
import io.ktor.client.HttpClient
import io.ktor.http.HttpStatusCode
+import io.ktor.request.ApplicationRequest
import org.jetbrains.exposed.sql.and
import org.jetbrains.exposed.sql.transactions.transaction
import org.joda.time.DateTime
@@ -58,49 +57,6 @@ fun extractFirstBic(bankCodes:
List<EbicsTypes.AbstractBankCode>?): String? {
return null
}
-fun getTransportFromJsonObject(jo: JsonNode): Transport {
- return jacksonObjectMapper().treeToValue(jo.get("transport"),
Transport::class.java)
-}
-
-/**
- * Retrieve bank account details, only if user owns it.
- */
-fun getBankAccount(userId: String, accountId: String): BankAccountEntity {
- return transaction {
- val bankAccountMap = BankAccountMapEntity.find {
- BankAccountMapsTable.nexusUser eq userId
- }.firstOrNull() ?: throw NexusError(
- HttpStatusCode.NotFound,
- "Bank account '$accountId' not found"
- )
- bankAccountMap.bankAccount
- }
-}
-
-/**
- * Given a nexus user id, returns the _list_ of bank accounts associated to it.
- *
- * @param id the subscriber id
- * @return the (non-empty) list of bank accounts associated with this user.
- */
-fun getBankAccountsFromNexusUserId(id: String): MutableList<BankAccountEntity>
{
- logger.debug("Looking up bank account of user '$id'")
- val ret = mutableListOf<BankAccountEntity>()
- transaction {
- BankAccountMapEntity.find {
- BankAccountMapsTable.nexusUser eq id
- }.forEach {
- ret.add(it.bankAccount)
- }
- }
- if (ret.isEmpty()) {
- throw NexusError(
- HttpStatusCode.NotFound,
- "Such user '$id' does not have any bank account associated"
- )
- }
- return ret
-}
fun getEbicsSubscriberDetailsInternal(subscriber: EbicsSubscriberEntity):
EbicsClientSubscriberDetails {
var bankAuthPubValue: RSAPublicKey? = null
@@ -130,32 +86,18 @@ fun getEbicsSubscriberDetailsInternal(subscriber:
EbicsSubscriberEntity): EbicsC
)
}
-fun getEbicsTransport(userId: String, transportId: String? = null):
EbicsSubscriberEntity {
- val transport = transaction {
- if (transportId == null) {
- return@transaction EbicsSubscriberEntity.find {
- EbicsSubscribersTable.nexusUser eq userId
- }.firstOrNull()
- }
- return@transaction EbicsSubscriberEntity.find {
- EbicsSubscribersTable.id eq transportId and
(EbicsSubscribersTable.nexusUser eq userId)
- }.firstOrNull()
- }
- ?: throw NexusError(
- HttpStatusCode.NotFound,
- "Could not find ANY Ebics transport for user $userId"
- )
- return transport
-}
-
/**
* Retrieve Ebics subscriber details given a Transport
* object and handling the default case (when this latter is null).
*/
-fun getEbicsSubscriberDetails(userId: String, transportId: String?):
EbicsClientSubscriberDetails {
- val transport = getEbicsTransport(userId, transportId)
+fun getEbicsSubscriberDetails(userId: String, transportId: String):
EbicsClientSubscriberDetails {
+ val transport = NexusBankConnectionEntity.findById(transportId)
+ if (transport == null) {
+ throw NexusError(HttpStatusCode.NotFound, "transport not found")
+ }
+ val subscriber = EbicsSubscriberEntity.find {
EbicsSubscribersTable.nexusBankConnection eq transport.id }.first()
// transport exists and belongs to caller.
- return getEbicsSubscriberDetailsInternal(transport)
+ return getEbicsSubscriberDetailsInternal(subscriber)
}
suspend fun downloadAndPersistC5xEbics(
@@ -164,9 +106,8 @@ suspend fun downloadAndPersistC5xEbics(
userId: String,
start: String?, // dashed date YYYY-MM(01-12)-DD(01-31)
end: String?, // dashed date YYYY-MM(01-12)-DD(01-31)
- transportId: String?
+ subscriberDetails: EbicsClientSubscriberDetails
) {
- val subscriberDetails = getEbicsSubscriberDetails(userId, transportId)
val orderParamsJson = EbicsStandardOrderParamsJson(
EbicsDateRangeJson(start, end)
)
@@ -188,6 +129,10 @@ suspend fun downloadAndPersistC5xEbics(
val fileName = it.first
val camt53doc = XMLUtil.parseStringIntoDom(it.second)
transaction {
+ val user = NexusUserEntity.findById(userId)
+ if (user == null) {
+ throw NexusError(HttpStatusCode.NotFound, "user not
found")
+ }
RawBankTransactionEntity.new {
bankAccount = getBankAccountFromIban(
camt53doc.pickString(
@@ -203,7 +148,7 @@ suspend fun downloadAndPersistC5xEbics(
status =
camt53doc.pickString("//*[local-name()='Ntry']//*[local-name()='Sts']")
bookingDate =
parseDashedDate(camt53doc.pickString("//*[local-name()='BookgDt']//*[local-name()='Dt']")).millis
- nexusUser = extractNexusUser(userId)
+ nexusUser = user
counterpartIban =
camt53doc.pickString("//*[local-name()='${if
(this.transactionType == "DBIT") "CdtrAcct" else
"DbtrAcct"}']//*[local-name()='IBAN']")
counterpartName =
@@ -222,22 +167,6 @@ suspend fun downloadAndPersistC5xEbics(
}
}
-suspend fun submitPaymentEbics(
- client: HttpClient,
- userId: String,
- transportId: String?,
- pain001document: String
-) {
- logger.debug("Uploading PAIN.001: ${pain001document}")
- doEbicsUploadTransaction(
- client,
- getEbicsSubscriberDetails(userId, transportId),
- "CCT",
- pain001document.toByteArray(Charsets.UTF_8),
- EbicsStandardOrderParams()
- )
-}
-
/**
* Create a PAIN.001 XML document according to the input data.
@@ -394,10 +323,9 @@ fun getNexusUser(id: String): NexusUserEntity {
* it will be the account whose money will pay the wire transfer being defined
* by this pain document.
*/
-fun addPreparedPayment(paymentData: Pain001Data, nexusUser: NexusUserEntity):
PreparedPaymentEntity {
+fun addPreparedPayment(paymentData: Pain001Data, debitorAccount:
BankAccountEntity): PreparedPaymentEntity {
val randomId = Random().nextLong()
return transaction {
- val debitorAccount = getBankAccount(nexusUser.id.value,
paymentData.debitorAccount)
PreparedPaymentEntity.new(randomId.toString()) {
subject = paymentData.subject
sum = paymentData.sum
@@ -410,7 +338,6 @@ fun addPreparedPayment(paymentData: Pain001Data, nexusUser:
NexusUserEntity): Pr
preparationDate = DateTime.now().millis
paymentId = randomId
endToEndId = randomId
- this.nexusUser = nexusUser
}
}
}
@@ -421,25 +348,12 @@ fun ensureNonNull(param: String?): String {
)
}
-/* Needs a transaction{} block to be called */
-fun extractNexusUser(param: String?): NexusUserEntity {
- if (param == null) {
- throw NexusError(HttpStatusCode.BadRequest, "Null Id given")
- }
- return transaction {
- NexusUserEntity.findById(param) ?: throw NexusError(
- HttpStatusCode.NotFound,
- "Subscriber: $param not found"
- )
- }
-}
-
/**
* This helper function parses a Authorization:-header line, decode the
credentials
* and returns a pair made of username and hashed (sha256) password. The
hashed value
* will then be compared with the one kept into the database.
*/
-fun extractUserAndHashedPassword(authorizationHeader: String): Pair<String,
String> {
+fun extractUserAndPassword(authorizationHeader: String): Pair<String, String> {
logger.debug("Authenticating: $authorizationHeader")
val (username, password) = try {
val split = authorizationHeader.split(" ")
@@ -461,11 +375,12 @@ fun extractUserAndHashedPassword(authorizationHeader:
String): Pair<String, Stri
* @param authorization the Authorization:-header line.
* @return user id
*/
-fun authenticateRequest(authorization: String?): NexusUserEntity {
+fun authenticateRequest(request: ApplicationRequest): NexusUserEntity {
+ val authorization = request.headers["Authorization"]
val headerLine = if (authorization == null) throw NexusError(
HttpStatusCode.BadRequest, "Authentication:-header line not found"
) else authorization
- val (username, password) = extractUserAndHashedPassword(headerLine)
+ val (username, password) = extractUserAndPassword(headerLine)
val user = NexusUserEntity.find {
NexusUsersTable.id eq username
}.firstOrNull()
@@ -479,22 +394,6 @@ fun authenticateRequest(authorization: String?):
NexusUserEntity {
}
-/**
- * Check if the subscriber has the right to use the (claimed) bank account.
- * @param subscriber id of the EBICS subscriber to check
- * @param bankAccount id of the claimed bank account
- * @return true if the subscriber can use the bank account.
- */
-fun subscriberHasRights(subscriber: EbicsSubscriberEntity, bankAccount:
BankAccountEntity): Boolean {
- val row = transaction {
- BankAccountMapEntity.find {
- BankAccountMapsTable.bankAccount eq bankAccount.id and
- (BankAccountMapsTable.ebicsSubscriber eq subscriber.id)
- }.firstOrNull()
- }
- return row != null
-}
-
fun getBankAccountFromIban(iban: String): BankAccountEntity {
return transaction {
BankAccountEntity.find {
@@ -508,12 +407,6 @@ fun getBankAccountFromIban(iban: String):
BankAccountEntity {
/** Check if the nexus user is allowed to use the claimed bank account. */
fun userHasRights(nexusUser: NexusUserEntity, iban: String): Boolean {
- val row = transaction {
- val bankAccount = getBankAccountFromIban(iban)
- BankAccountMapEntity.find {
- BankAccountMapsTable.bankAccount eq bankAccount.id and
- (BankAccountMapsTable.nexusUser eq nexusUser.id)
- }.firstOrNull()
- }
- return row != null
+ // FIXME: implement permissions
+ return true
}
\ No newline at end of file
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt
b/nexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt
index 4dd2a63..4a9d986 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt
@@ -80,11 +80,25 @@ data class RawPayments(
* API types (used as requests/responses types) *
*************************************************/
data class BankTransport(
- val transport: Transport,
+ val transport: String,
val backup: Any? = null,
val data: Any?
)
+data class BankConnectionInfo(
+ val name: String,
+ val type: String
+)
+
+data class BankConnectionsList(
+ val bankConnections: List<BankConnectionInfo>
+)
+
+data class EbicsHostTestRequest(
+ val ebicsBaseUrl: String,
+ val ebicsHostId: String
+)
+
/**
* This object is used twice: as a response to the backup request,
* and as a request to the backup restore. Note: in the second case
@@ -137,30 +151,9 @@ data class Transactions(
val transactions: MutableList<Transaction> = mutableListOf()
)
-/** Specifies the transport to use. */
-data class Transport(
- /**
- * Must match one of the types implemented by nexus:
- * 'ebics', 'local', possibly 'fints' in the near future!
- */
- val type: String,
- /**
- * A mnemonic identifier given by the user to one
- * transport instance.
- */
- val name: String
-)
-
-/** Request type of "POST /prepared-payments/submit" */
-data class SubmitPayment(
- val uuid: String,
- /** Policy to pick the default transport is still work in progress. */
- val transport: tech.libeufin.nexus.Transport?
-)
-
/** Request type of "POST /collected-transactions" */
data class CollectedTransaction(
- val transport: Transport?,
+ val transport: String?,
val start: String?,
val end: String?
)
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
index 933cb17..b898cc5 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
@@ -55,9 +55,7 @@ import io.ktor.routing.post
import io.ktor.routing.routing
import io.ktor.server.engine.embeddedServer
import io.ktor.server.netty.Netty
-import io.ktor.util.KtorExperimentalAPI
import io.ktor.utils.io.ByteReadChannel
-import io.ktor.utils.io.core.ExperimentalIoApi
import io.ktor.utils.io.jvm.javaio.toByteReadChannel
import io.ktor.utils.io.jvm.javaio.toInputStream
import org.jetbrains.exposed.sql.and
@@ -69,110 +67,16 @@ import org.slf4j.event.Level
import tech.libeufin.util.*
import tech.libeufin.util.CryptoUtil.hashpw
import tech.libeufin.util.ebics_h004.HTDResponseOrderData
+import java.util.*
import java.util.zip.InflaterInputStream
import javax.crypto.EncryptedPrivateKeyInfo
import javax.sql.rowset.serial.SerialBlob
-data class NexusError(val statusCode: HttpStatusCode, val reason: String) :
Exception()
+data class NexusError(val statusCode: HttpStatusCode, val reason: String) :
+ Exception("${reason} (HTTP status $statusCode)")
val logger: Logger = LoggerFactory.getLogger("tech.libeufin.nexus")
-suspend fun handleEbicsSendMSG(
- httpClient: HttpClient,
- userId: String,
- transportId: String?,
- msg: String,
- sync: Boolean
-): String {
- val subscriber = getEbicsSubscriberDetails(userId, transportId)
- val response = when (msg.toUpperCase()) {
- "HIA" -> {
- val request = makeEbicsHiaRequest(subscriber)
- httpClient.postToBank(
- subscriber.ebicsUrl,
- request
- )
- }
- "INI" -> {
- val request = makeEbicsIniRequest(subscriber)
- httpClient.postToBank(
- subscriber.ebicsUrl,
- request
- )
- }
- "HPB" -> {
- /** should NOT put bank's keys into any table. */
- val request = makeEbicsHpbRequest(subscriber)
- val response = httpClient.postToBank(
- subscriber.ebicsUrl,
- request
- )
- if (sync) {
- val parsedResponse =
parseAndDecryptEbicsKeyManagementResponse(subscriber, response)
- val orderData = parsedResponse.orderData ?: throw NexusError(
- HttpStatusCode.InternalServerError,
- "Cannot find data in a HPB response"
- )
- val hpbData = parseEbicsHpbOrder(orderData)
- transaction {
- val transport = getEbicsTransport(userId, transportId)
- transport.bankAuthenticationPublicKey =
SerialBlob(hpbData.authenticationPubKey.encoded)
- transport.bankEncryptionPublicKey =
SerialBlob(hpbData.encryptionPubKey.encoded)
- }
- }
- return response
- }
- "HTD" -> {
- val response = doEbicsDownloadTransaction(
- httpClient, subscriber, "HTD", EbicsStandardOrderParams()
- )
- when (response) {
- is EbicsDownloadBankErrorResult -> {
- throw NexusError(
- HttpStatusCode.BadGateway,
- response.returnCode.errorCode
- )
- }
- is EbicsDownloadSuccessResult -> {
- val payload =
XMLUtil.convertStringToJaxb<HTDResponseOrderData>(
- response.orderData.toString(Charsets.UTF_8)
- )
- if (sync) {
- transaction {
- payload.value.partnerInfo.accountInfoList?.forEach
{
- val bankAccount = BankAccountEntity.new(id =
it.id) {
- accountHolder = it.accountHolder ?:
"NOT-GIVEN"
- 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"
- )
- }
- BankAccountMapEntity.new {
- ebicsSubscriber =
getEbicsTransport(userId, transportId)
- this.nexusUser = getNexusUser(userId)
- this.bankAccount = bankAccount
- }
- }
- }
- }
- response.orderData.toString(Charsets.UTF_8)
- }
- }
- }
- "HEV" -> {
- val request = makeEbicsHEVRequest(subscriber)
- httpClient.postToBank(subscriber.ebicsUrl, request)
- }
- else -> throw NexusError(
- HttpStatusCode.NotFound,
- "Message $msg not found"
- )
- }
- return response
-}
-
class NexusCommand : CliktCommand() {
override fun run() = Unit
}
@@ -214,7 +118,7 @@ fun main(args: Array<String>) {
.main(args)
}
-suspend inline fun <reified T : Any>ApplicationCall.receiveJson(): T {
+suspend inline fun <reified T : Any> ApplicationCall.receiveJson(): T {
try {
return this.receive<T>();
} catch (e: MissingKotlinParameterException) {
@@ -224,8 +128,93 @@ suspend inline fun <reified T :
Any>ApplicationCall.receiveJson(): T {
}
}
-@ExperimentalIoApi
-@KtorExperimentalAPI
+fun createEbicsBankConnection(bankConnectionName: String, user:
NexusUserEntity, body: JsonNode) {
+ val bankConn = NexusBankConnectionEntity.new(bankConnectionName) {
+ owner = user
+ type = "ebics"
+ }
+ if (body.get("backup") != null) {
+ val backup = jacksonObjectMapper().treeToValue(body,
EbicsKeysBackupJson::class.java)
+ val (authKey, encKey, sigKey) = try {
+ Triple(
+ CryptoUtil.decryptKey(
+ EncryptedPrivateKeyInfo(base64ToBytes(backup.authBlob)),
+ backup.passphrase
+ ),
+ CryptoUtil.decryptKey(
+ EncryptedPrivateKeyInfo(base64ToBytes(backup.encBlob)),
+ backup.passphrase
+ ),
+ CryptoUtil.decryptKey(
+ EncryptedPrivateKeyInfo(base64ToBytes(backup.sigBlob)),
+ backup.passphrase
+ )
+ )
+ } catch (e: Exception) {
+ e.printStackTrace()
+ logger.info("Restoring keys failed, probably due to wrong
passphrase")
+ throw NexusError(
+ HttpStatusCode.BadRequest,
+ "Bad backup given"
+ )
+ }
+ try {
+ EbicsSubscriberEntity.new() {
+ ebicsURL = backup.ebicsURL
+ hostID = backup.hostID
+ partnerID = backup.partnerID
+ userID = backup.userID
+ signaturePrivateKey = SerialBlob(sigKey.encoded)
+ encryptionPrivateKey = SerialBlob(encKey.encoded)
+ authenticationPrivateKey = SerialBlob(authKey.encoded)
+ nexusBankConnection = bankConn
+ }
+ } catch (e: Exception) {
+ throw NexusError(
+ HttpStatusCode.BadRequest,
+ "exception: $e"
+ )
+ }
+ return
+ }
+ if (body.get("data") != null) {
+ val data =
+ jacksonObjectMapper().treeToValue((body.get("data")),
EbicsNewTransport::class.java)
+ val pairA = CryptoUtil.generateRsaKeyPair(2048)
+ val pairB = CryptoUtil.generateRsaKeyPair(2048)
+ val pairC = CryptoUtil.generateRsaKeyPair(2048)
+ EbicsSubscriberEntity.new() {
+ ebicsURL = data.ebicsURL
+ hostID = data.hostID
+ partnerID = data.partnerID
+ userID = data.userID
+ systemID = data.systemID
+ signaturePrivateKey = SerialBlob(pairA.private.encoded)
+ encryptionPrivateKey = SerialBlob(pairB.private.encoded)
+ authenticationPrivateKey = SerialBlob(pairC.private.encoded)
+ nexusBankConnection = bankConn
+ }
+ return
+ }
+ throw NexusError(
+ HttpStatusCode.BadRequest,
+ "Neither restore or new transport were specified."
+ )
+}
+
+fun requireBankConnection(call: ApplicationCall, parameterKey: String):
NexusBankConnectionEntity {
+ val name = call.parameters[parameterKey]
+ if (name == null) {
+ throw NexusError(HttpStatusCode.InternalServerError, "no parameter for
bank connection")
+ }
+ val conn = NexusBankConnectionEntity.findById(name)
+ if (conn == null) {
+ throw NexusError(HttpStatusCode.NotFound, "bank connection '$name' not
found")
+ }
+ return conn
+}
+
+
fun serverMain() {
dbCreateTables()
val client = HttpClient() {
@@ -237,6 +226,7 @@ fun serverMain() {
this.level = Level.DEBUG
this.logger = tech.libeufin.nexus.logger
}
+
install(ContentNegotiation) {
jackson {
enable(SerializationFeature.INDENT_OUTPUT)
@@ -248,6 +238,7 @@ fun serverMain() {
//registerModule(JavaTimeModule())
}
}
+
install(StatusPages) {
exception<NexusError> { cause ->
logger.error("Exception while handling '${call.request.uri}'",
cause)
@@ -282,6 +273,10 @@ fun serverMain() {
return@intercept finish()
}
}
+
+ /**
+ * Allow request body compression. Needed by Taler.
+ */
receivePipeline.intercept(ApplicationReceivePipeline.Before) {
if (this.context.request.headers["Content-Encoding"] == "deflate")
{
logger.debug("About to inflate received data")
@@ -293,13 +288,14 @@ fun serverMain() {
proceed()
return@intercept
}
+
routing {
/**
* Shows information about the requesting user.
*/
get("/user") {
val ret = transaction {
- val currentUser =
authenticateRequest(call.request.headers["Authorization"])
+ val currentUser = authenticateRequest(call.request)
UserResponse(
username = currentUser.id.value,
superuser = currentUser.superuser
@@ -321,13 +317,14 @@ fun serverMain() {
call.respond(HttpStatusCode.OK, usersResp)
return@get
}
+
/**
* Add a new ordinary user in the system (requires superuser
privileges)
*/
post("/users") {
val body = call.receiveJson<User>()
transaction {
- val currentUser =
authenticateRequest(call.request.headers["Authorization"])
+ val currentUser = authenticateRequest(call.request)
if (!currentUser.superuser) {
throw NexusError(HttpStatusCode.Forbidden, "only
superuser can do that")
}
@@ -343,95 +340,112 @@ fun serverMain() {
)
return@post
}
+
get("/bank-connection-protocols") {
call.respond(HttpStatusCode.OK,
BankProtocolsResponse(listOf("ebics", "loopback")))
return@get
}
+
+ post("/bank-connection-protocols/ebics/test-host") {
+ val r = call.receiveJson<EbicsHostTestRequest>()
+ val qr = doEbicsHostVersionQuery(client, r.ebicsBaseUrl,
r.ebicsHostId)
+ call.respond(HttpStatusCode.OK, qr)
+ return@post
+ }
+
/**
* Shows the bank accounts belonging to the requesting user.
*/
get("/bank-accounts") {
- val userId = transaction {
authenticateRequest(call.request.headers["Authorization"]).id.value }
val bankAccounts = BankAccounts()
- getBankAccountsFromNexusUserId(userId).forEach {
- bankAccounts.accounts.add(
- BankAccount(
- holder = it.accountHolder,
- iban = it.iban,
- bic = it.bankCode,
- account = it.id.value
- )
- )
+ transaction {
+ authenticateRequest(call.request)
+ // FIXME(dold): Only return accounts the user has at least
read access to?
+ BankAccountEntity.all().forEach {
+
bankAccounts.accounts.add(BankAccount(it.accountHolder, it.iban, it.bankCode,
it.id.value))
+ }
}
+ call.respond(bankAccounts)
return@get
}
+
/**
- * Submit one particular payment at the bank.
+ * Submit one particular payment to the bank.
*/
- post("/bank-accounts/prepared-payments/submit") {
- val userId = transaction {
authenticateRequest(call.request.headers["Authorization"]).id.value }
- val body = call.receive<SubmitPayment>()
- val preparedPayment = getPreparedPayment(body.uuid)
- transaction {
- if (preparedPayment.nexusUser.id.value != userId) throw
NexusError(
- HttpStatusCode.Forbidden,
- "No rights over such payment"
- )
+ post("/bank-accounts/{accountid}/prepared-payments/{uuid}/submit")
{
+ val uuid = ensureNonNull(call.parameters["uuid"])
+ val accountId = ensureNonNull(call.parameters["accountid"])
+ val res = transaction {
+ val user = authenticateRequest(call.request)
+ val preparedPayment = getPreparedPayment(uuid)
if (preparedPayment.submitted) {
throw NexusError(
HttpStatusCode.PreconditionFailed,
- "Payment ${body.uuid} was submitted already"
+ "Payment ${uuid} was submitted already"
)
}
-
+ val bankAccount = BankAccountEntity.findById(accountId)
+ if (bankAccount == null) {
+ throw NexusError(HttpStatusCode.NotFound, "unknown
bank account")
+ }
+ val defaultBankConnection =
bankAccount.defaultBankConnection
+ if (defaultBankConnection == null) {
+ throw NexusError(HttpStatusCode.NotFound, "needs a
default connection")
+ }
+ val subscriberDetails =
getEbicsSubscriberDetails(user.id.value, defaultBankConnection.id.value);
+ return@transaction object {
+ val pain001document =
createPain001document(preparedPayment)
+ val bankConnectionType = defaultBankConnection.type
+ val subscriberDetails = subscriberDetails
+ }
}
- val pain001document = createPain001document(preparedPayment)
- if (body.transport != null) {
- // type and name aren't null
- when (body.transport.type) {
- "ebics" -> {
- submitPaymentEbics(
- client, userId, body.transport.name,
pain001document
- )
- }
- else -> throw NexusError(
- HttpStatusCode.NotFound,
- "Transport type '${body.transport.type}' not
implemented"
+ // type and name aren't null
+ when (res.bankConnectionType) {
+ "ebics" -> {
+ logger.debug("Uploading PAIN.001:
${res.pain001document}")
+ doEbicsUploadTransaction(
+ client,
+ res.subscriberDetails,
+ "CCT",
+ res.pain001document.toByteArray(Charsets.UTF_8),
+ EbicsStandardOrderParams()
)
}
- } else {
- // default to ebics and "first" transport from user
- submitPaymentEbics(
- client, userId, null, pain001document
+ else -> throw NexusError(
+ HttpStatusCode.NotFound,
+ "Transport type '${res.bankConnectionType}' not
implemented"
)
}
transaction {
+ val preparedPayment = getPreparedPayment(uuid)
preparedPayment.submitted = true
}
- call.respondText("Payment ${body.uuid} submitted")
+ call.respondText("Payment ${uuid} submitted")
return@post
}
+
/**
* Shows information about one particular prepared payment.
*/
get("/bank-accounts/{accountid}/prepared-payments/{uuid}") {
- val userId = transaction {
authenticateRequest(call.request.headers["Authorization"]).id.value }
- val preparedPayment =
getPreparedPayment(ensureNonNull(call.parameters["uuid"]))
- if (preparedPayment.nexusUser.id.value != userId) throw
NexusError(
- HttpStatusCode.Forbidden,
- "No rights over such payment"
- )
+ val res = transaction {
+ val user = authenticateRequest(call.request)
+ val preparedPayment =
getPreparedPayment(ensureNonNull(call.parameters["uuid"]))
+ return@transaction object {
+ val preparedPayment = preparedPayment
+ }
+ }
call.respond(
PaymentStatus(
- uuid = preparedPayment.id.value,
- submitted = preparedPayment.submitted,
- creditorName = preparedPayment.creditorName,
- creditorBic = preparedPayment.creditorBic,
- creditorIban = preparedPayment.creditorIban,
- amount =
"${preparedPayment.sum}:${preparedPayment.currency}",
- subject = preparedPayment.subject,
- submissionDate =
DateTime(preparedPayment.submissionDate).toDashedDate(),
- preparationDate =
DateTime(preparedPayment.preparationDate).toDashedDate()
+ uuid = res.preparedPayment.id.value,
+ submitted = res.preparedPayment.submitted,
+ creditorName = res.preparedPayment.creditorName,
+ creditorBic = res.preparedPayment.creditorBic,
+ creditorIban = res.preparedPayment.creditorIban,
+ amount =
"${res.preparedPayment.sum}:${res.preparedPayment.currency}",
+ subject = res.preparedPayment.subject,
+ submissionDate =
DateTime(res.preparedPayment.submissionDate).toDashedDate(),
+ preparationDate =
DateTime(res.preparedPayment.preparationDate).toDashedDate()
)
)
return@get
@@ -440,78 +454,104 @@ fun serverMain() {
* Adds a new prepared payment.
*/
post("/bank-accounts/{accountid}/prepared-payments") {
- val userId = transaction {
authenticateRequest(call.request.headers["Authorization"]).id.value }
- val bankAccount = getBankAccount(userId,
ensureNonNull(call.parameters["accountid"]))
val body = call.receive<PreparedPaymentRequest>()
- val amount = parseAmount(body.amount)
- val paymentEntity = addPreparedPayment(
- Pain001Data(
- creditorIban = body.iban,
- creditorBic = body.bic,
- creditorName = body.name,
- debitorAccount = bankAccount.id.value,
- sum = amount.amount,
- currency = amount.currency,
- subject = body.subject
- ),
- extractNexusUser(userId)
- )
+ val accountId = ensureNonNull(call.parameters["accountid"])
+ val res = transaction {
+ authenticateRequest(call.request)
+ val bankAccount = BankAccountEntity.findById(accountId)
+ if (bankAccount == null) {
+ throw NexusError(HttpStatusCode.NotFound, "unknown
bank account")
+ }
+ val amount = parseAmount(body.amount)
+ val paymentEntity = addPreparedPayment(
+ Pain001Data(
+ creditorIban = body.iban,
+ creditorBic = body.bic,
+ creditorName = body.name,
+ debitorAccount = bankAccount.id.value,
+ sum = amount.amount,
+ currency = amount.currency,
+ subject = body.subject
+ ),
+ bankAccount
+ )
+ return@transaction object {
+ val uuid = paymentEntity.id.value
+ }
+ }
call.respond(
HttpStatusCode.OK,
- PreparedPaymentResponse(uuid = paymentEntity.id.value)
+ PreparedPaymentResponse(uuid = res.uuid)
)
return@post
}
+
/**
* Downloads new transactions from the bank.
- *
- * NOTE: 'accountid' is not used. Transaction are asked on
- * the basis of a transport subscriber (regardless of their
- * bank account details)
*/
- post("/bank-accounts/collected-transactions") {
- val userId = transaction {
authenticateRequest(call.request.headers["Authorization"]).id.value }
- val body = call.receive<CollectedTransaction>()
- if (body.transport != null) {
- when (body.transport.type) {
- "ebics" -> {
- downloadAndPersistC5xEbics(
- "C53",
- client,
- userId,
- body.start,
- body.end,
- body.transport.name
- )
- }
- else -> throw NexusError(
+ post("/bank-accounts/{accountid}/fetch-transactions") {
+ val accountid = call.parameters["accountid"]
+ if (accountid == null) {
+ throw NexusError(
+ HttpStatusCode.BadRequest,
+ "Account id missing"
+ )
+ }
+ val res = transaction {
+ val user = authenticateRequest(call.request)
+ val acct = BankAccountEntity.findById(accountid)
+ if (acct == null) {
+ throw NexusError(
+ HttpStatusCode.NotFound,
+ "Account not found"
+ )
+ }
+ val conn = acct.defaultBankConnection
+ if (conn == null) {
+ throw NexusError(
HttpStatusCode.BadRequest,
- "Transport type '${body.transport.type}' not
implemented"
+ "No default bank connection (explicit connection
not yet supported)"
+ )
+ }
+ val subscriberDetails =
getEbicsSubscriberDetails(user.id.value, conn.id.value)
+ return@transaction object {
+ val connectionType = conn.type
+ val connectionName = conn.id.value
+ val userId = user.id.value
+ val subscriberDetails = subscriberDetails
+ }
+ }
+ val body = call.receive<CollectedTransaction>()
+ when (res.connectionType) {
+ "ebics" -> {
+ downloadAndPersistC5xEbics(
+ "C53",
+ client,
+ res.userId,
+ body.start,
+ body.end,
+ res.subscriberDetails
)
}
- } else {
- downloadAndPersistC5xEbics(
- "C53",
- client,
- userId,
- body.start,
- body.end,
- null
+ else -> throw NexusError(
+ HttpStatusCode.BadRequest,
+ "Connection type '${res.connectionType}' not
implemented"
)
}
call.respondText("Collection performed")
return@post
}
+
/**
* Asks list of transactions ALREADY downloaded from the bank.
*/
- get("/bank-accounts/{accountid}/collected-transactions") {
- val userId = transaction {
authenticateRequest(call.request.headers["Authorization"]).id.value }
+ get("/bank-accounts/{accountid}/transactions") {
val bankAccount = expectNonNull(call.parameters["accountid"])
val start = call.request.queryParameters["start"]
val end = call.request.queryParameters["end"]
val ret = Transactions()
transaction {
+ val userId = transaction {
authenticateRequest(call.request).id.value }
RawBankTransactionEntity.find {
RawBankTransactionsTable.nexusUser eq userId and
(RawBankTransactionsTable.bankAccount eq
bankAccount) and
@@ -536,151 +576,180 @@ fun serverMain() {
call.respond(ret)
return@get
}
+
/**
* Adds a new bank transport.
*/
- post("/bank-transports") {
- val userId = transaction {
authenticateRequest(call.request.headers["Authorization"]).id.value }
+ post("/bank-connections") {
// user exists and is authenticated.
val body = call.receive<JsonNode>()
- val transport: Transport = getTransportFromJsonObject(body)
- when (transport.type) {
- "ebics" -> {
- if (body.get("backup") != null) {
- val backup =
jacksonObjectMapper().treeToValue(body, EbicsKeysBackupJson::class.java)
- val (authKey, encKey, sigKey) = try {
- Triple(
- CryptoUtil.decryptKey(
-
EncryptedPrivateKeyInfo(base64ToBytes(backup.authBlob)),
- backup.passphrase
- ),
- CryptoUtil.decryptKey(
-
EncryptedPrivateKeyInfo(base64ToBytes(backup.encBlob)),
- backup.passphrase
- ),
- CryptoUtil.decryptKey(
-
EncryptedPrivateKeyInfo(base64ToBytes(backup.sigBlob)),
- backup.passphrase
- )
- )
- } catch (e: Exception) {
- e.printStackTrace()
- logger.info("Restoring keys failed, probably
due to wrong passphrase")
- throw NexusError(
- HttpStatusCode.BadRequest,
- "Bad backup given"
- )
- }
- logger.info("Restoring keys, creating new user:
$userId")
- try {
- transaction {
- EbicsSubscriberEntity.new(transport.name) {
- this.nexusUser =
extractNexusUser(userId)
- ebicsURL = backup.ebicsURL
- hostID = backup.hostID
- partnerID = backup.partnerID
- userID = backup.userID
- signaturePrivateKey =
SerialBlob(sigKey.encoded)
- encryptionPrivateKey =
SerialBlob(encKey.encoded)
- authenticationPrivateKey =
SerialBlob(authKey.encoded)
- }
- }
- } catch (e: Exception) {
- print(e)
- call.respond(
- NexusErrorJson("Could not store the new
account into database")
- )
- return@post
- }
- call.respondText("Backup restored")
-
- return@post
+ val bankConnectionName = body.get("name").textValue()
+ val bankConnectionType = body.get("type").textValue()
+ transaction {
+ val user = authenticateRequest(call.request)
+ when (bankConnectionType) {
+ "ebics" -> {
+ createEbicsBankConnection(bankConnectionName,
user, body)
}
- if (body.get("data") != null) {
- val data =
-
jacksonObjectMapper().treeToValue((body.get("data")),
EbicsNewTransport::class.java)
- val pairA = CryptoUtil.generateRsaKeyPair(2048)
- val pairB = CryptoUtil.generateRsaKeyPair(2048)
- val pairC = CryptoUtil.generateRsaKeyPair(2048)
- transaction {
- EbicsSubscriberEntity.new(transport.name) {
- nexusUser = extractNexusUser(userId)
- ebicsURL = data.ebicsURL
- hostID = data.hostID
- partnerID = data.partnerID
- userID = data.userID
- systemID = data.systemID
- signaturePrivateKey =
SerialBlob(pairA.private.encoded)
- encryptionPrivateKey =
SerialBlob(pairB.private.encoded)
- authenticationPrivateKey =
SerialBlob(pairC.private.encoded)
- }
- }
- call.respondText("EBICS user successfully created")
- return@post
+ else -> {
+ throw NexusError(
+ HttpStatusCode.BadRequest,
+ "Invalid bank connection type
'${bankConnectionType}'"
+ )
}
- throw NexusError(
- HttpStatusCode.BadRequest,
- "Neither restore or new transport were specified."
- )
}
- else -> {
+ }
+ call.respond(object {})
+ }
+
+ get("/bank-connections") {
+ val connList = mutableListOf<BankConnectionInfo>()
+ transaction {
+ NexusBankConnectionEntity.all().forEach {
+ connList.add(BankConnectionInfo(it.id.value, it.type))
+ }
+ }
+ call.respond(BankConnectionsList(connList))
+ }
+
+ post("/bank-connections/{connid}/connect") {
+ throw NotImplementedError()
+ }
+
+ post("/bank-connections/{connid}/ebics/send-ini") {
+ val subscriber = transaction {
+ val user = authenticateRequest(call.request)
+ val conn = requireBankConnection(call, "connid")
+ if (conn.type != "ebics") {
throw NexusError(
HttpStatusCode.BadRequest,
- "Invalid transport type '${transport.type}'"
+ "bank connection is not of type 'ebics' (but
'${conn.type}')"
)
}
+ getEbicsSubscriberDetails(user.id.value, conn.id.value)
}
+ val resp = doEbicsIniRequest(client, subscriber)
+ call.respond(resp)
}
+
+ post("/bank-connections/{connid}/ebics/send-hia") {
+ val subscriber = transaction {
+ val user = authenticateRequest(call.request)
+ val conn = requireBankConnection(call, "connid")
+ if (conn.type != "ebics") {
+ throw NexusError(HttpStatusCode.BadRequest, "bank
connection is not of type 'ebics'")
+ }
+ getEbicsSubscriberDetails(user.id.value, conn.id.value)
+ }
+ val resp = doEbicsHiaRequest(client, subscriber)
+ call.respond(resp)
+ }
+
+ post("/bank-connections/{connid}/ebics/send-hpb") {
+ val subscriberDetails = transaction {
+ val user = authenticateRequest(call.request)
+ val conn = requireBankConnection(call, "connid")
+ if (conn.type != "ebics") {
+ throw NexusError(HttpStatusCode.BadRequest, "bank
connection is not of type 'ebics'")
+ }
+ getEbicsSubscriberDetails(user.id.value, conn.id.value)
+ }
+ val hpbData = doEbicsHpbRequest(client, subscriberDetails)
+ transaction {
+ val conn = requireBankConnection(call, "connid")
+ val subscriber =
+ EbicsSubscriberEntity.find {
EbicsSubscribersTable.nexusBankConnection eq conn.id }.first()
+ subscriber.bankAuthenticationPublicKey =
SerialBlob(hpbData.authenticationPubKey.encoded)
+ subscriber.bankEncryptionPublicKey =
SerialBlob(hpbData.encryptionPubKey.encoded)
+ }
+ call.respond(object { })
+ }
+
/**
- * Sends to the bank a message "MSG" according to the transport
- * "transportName". Does not modify any DB table.
+ * Directly import accounts. Used for testing.
*/
- post("/bank-transports/send{MSG}") {
- val userId = transaction {
authenticateRequest(call.request.headers["Authorization"]).id.value }
- val body = call.receive<Transport>()
- when (body.type) {
- "ebics" -> {
- val response = handleEbicsSendMSG(
- httpClient = client,
- userId = userId,
- transportId = body.name,
- msg = ensureNonNull(call.parameters["MSG"]),
- sync = true
+ post("/bank-connections/{connid}/ebics/import-accounts") {
+ val subscriberDetails = transaction {
+ val user = authenticateRequest(call.request)
+ val conn = requireBankConnection(call, "connid")
+ if (conn.type != "ebics") {
+ throw NexusError(HttpStatusCode.BadRequest, "bank
connection is not of type 'ebics'")
+ }
+ getEbicsSubscriberDetails(user.id.value, conn.id.value)
+ }
+ val response = doEbicsDownloadTransaction(
+ client, subscriberDetails, "HTD",
EbicsStandardOrderParams()
+ )
+ when (response) {
+ is EbicsDownloadBankErrorResult -> {
+ throw NexusError(
+ HttpStatusCode.BadGateway,
+ response.returnCode.errorCode
)
- call.respondText(response)
}
- else -> throw NexusError(
- HttpStatusCode.NotImplemented,
- "Transport '${body.type}' not implemented. Use
'ebics'"
- )
+ is EbicsDownloadSuccessResult -> {
+ val payload =
XMLUtil.convertStringToJaxb<HTDResponseOrderData>(
+ response.orderData.toString(Charsets.UTF_8)
+ )
+ transaction {
+ val conn = requireBankConnection(call, "connid")
+ payload.value.partnerInfo.accountInfoList?.forEach
{
+ val bankAccount = BankAccountEntity.new(id =
it.id) {
+ accountHolder = it.accountHolder ?:
"NOT-GIVEN"
+ 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"
+ )
+ defaultBankConnection = conn
+ }
+ }
+ }
+ response.orderData.toString(Charsets.UTF_8)
+ }
}
- return@post
+ call.respond(object { })
}
- /**
- * Sends the bank a message "MSG" according to the transport
- * "transportName". DOES alterate DB tables.
- */
- post("/bank-transports/sync{MSG}") {
- val userId = transaction {
authenticateRequest(call.request.headers["Authorization"]).id.value }
- val body = call.receive<Transport>()
- when (body.type) {
- "ebics" -> {
- val response = handleEbicsSendMSG(
- httpClient = client,
- userId = userId,
- transportId = body.name,
- msg = ensureNonNull(call.parameters["MSG"]),
- sync = true
+
+ post("/bank-connections/{connid}/ebics/download/{msgtype}") {
+ val orderType =
requireNotNull(call.parameters["msgtype"]).toUpperCase(Locale.ROOT)
+ if (orderType.length != 3) {
+ throw NexusError(HttpStatusCode.BadRequest, "ebics order
type must be three characters")
+ }
+ val paramsJson = call.receive<EbicsStandardOrderParamsJson>()
+ val orderParams = paramsJson.toOrderParams()
+ val subscriberDetails = transaction {
+ val user = authenticateRequest(call.request)
+ val conn = requireBankConnection(call, "connid")
+ if (conn.type != "ebics") {
+ throw NexusError(HttpStatusCode.BadRequest, "bank
connection is not of type 'ebics'")
+ }
+ getEbicsSubscriberDetails(user.id.value, conn.id.value)
+ }
+ val response = doEbicsDownloadTransaction(
+ client,
+ subscriberDetails,
+ orderType,
+ orderParams
+ )
+ when (response) {
+ is EbicsDownloadSuccessResult -> {
+ call.respondText(
+ response.orderData.toString(Charsets.UTF_8),
+ ContentType.Text.Plain,
+ HttpStatusCode.OK
+ )
+ }
+ is EbicsDownloadBankErrorResult -> {
+ call.respond(
+ HttpStatusCode.BadGateway,
+ EbicsErrorJson(EbicsErrorDetailJson("bankError",
response.returnCode.errorCode))
)
- call.respondText(response)
}
- else -> throw NexusError(
- HttpStatusCode.NotImplemented,
- "Transport '${body.type}' not implemented. Use
'ebics'"
- )
}
- return@post
}
+
/**
* Hello endpoint.
*/
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt
b/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt
index 7aeacff..c15b370 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt
@@ -20,559 +20,559 @@ import tech.libeufin.util.*
import kotlin.math.abs
import kotlin.math.min
-class Taler(app: Route) {
-
- /** Payment initiating data structures: one endpoint "$BASE_URL/transfer".
*/
- private data class TalerTransferRequest(
- val request_uid: String,
- val amount: String,
- val exchange_base_url: String,
- val wtid: String,
- val credit_account: String
- )
- private data class TalerTransferResponse(
- // point in time when the nexus put the payment instruction into the
database.
- val timestamp: GnunetTimestamp,
- val row_id: Long
- )
-
- /** History accounting data structures */
- private data class TalerIncomingBankTransaction(
- val row_id: Long,
- val date: GnunetTimestamp, // timestamp
- val amount: String,
- val credit_account: String, // payto form,
- val debit_account: String,
- val reserve_pub: String
- )
- private data class TalerIncomingHistory(
- var incoming_transactions: MutableList<TalerIncomingBankTransaction> =
mutableListOf()
- )
- private data class TalerOutgoingBankTransaction(
- val row_id: Long,
- val date: GnunetTimestamp, // timestamp
- val amount: String,
- val credit_account: String, // payto form,
- val debit_account: String,
- val wtid: String,
- val exchange_base_url: String
- )
- private data class TalerOutgoingHistory(
- var outgoing_transactions: MutableList<TalerOutgoingBankTransaction> =
mutableListOf()
- )
-
- /** Test APIs' data structures. */
- private data class TalerAdminAddIncoming(
- val amount: String,
- val reserve_pub: String,
- /**
- * This account is the one giving money to the exchange. It doesn't
- * have to be 'created' as it might (and normally is) simply be a
payto://
- * address pointing to a bank account hosted in a different financial
- * institution.
- */
- val debit_account: String
- )
-
- private data class GnunetTimestamp(
- val t_ms: Long
- )
- private data class TalerAddIncomingResponse(
- val timestamp: GnunetTimestamp,
- val row_id: Long
- )
-
- /**
- * Helper data structures.
- */
- data class Payto(
- val name: String = "NOTGIVEN",
- val iban: String,
- val bic: String = "NOTGIVEN"
- )
- /**
- * Helper functions
- */
- fun parsePayto(paytoUri: String): Payto {
- /**
- * First try to parse a "iban"-type payto URI. If that fails,
- * then assume a test is being run under the "x-taler-bank" type.
- * If that one fails too, throw exception.
- *
- * Note: since the Nexus doesn't have the notion of "x-taler-bank",
- * such URIs must yield a iban-compatible tuple of values. Therefore,
- * the plain bank account number maps to a "iban", and the <bank
hostname>
- * maps to a "bic".
- */
-
-
- /**
- * payto://iban/BIC?/IBAN?name=<name>
- * payto://x-taler-bank/<bank hostname>/<plain account number>
- */
-
- val ibanMatch =
Regex("payto://iban/([A-Z0-9]+/)?([A-Z0-9]+)\\?name=(\\w+)").find(paytoUri)
- if (ibanMatch != null) {
- val (bic, iban, name) = ibanMatch.destructured
- return Payto(name, iban, bic.replace("/", ""))
- }
- val xTalerBankMatch =
Regex("payto://x-taler-bank/localhost/([0-9]+)").find(paytoUri)
- if (xTalerBankMatch != null) {
- val xTalerBankAcctNo = xTalerBankMatch.destructured.component1()
- return Payto("Taler Exchange", xTalerBankAcctNo, "localhost")
- }
-
- throw NexusError(HttpStatusCode.BadRequest, "invalid payto URI
($paytoUri)")
- }
-
- /** Sort query results in descending order for negative deltas, and
ascending otherwise. */
- private fun <T : Entity<Long>> SizedIterable<T>.orderTaler(delta: Int):
List<T> {
- return if (delta < 0) {
- this.sortedByDescending { it.id }
- } else {
- this.sortedBy { it.id }
- }
- }
-
- /**
- * NOTE: those payto-builders default all to the x-taler-bank transport.
- * A mechanism to easily switch transport is needed, as production needs
- * 'iban'.
- */
- private fun buildPaytoUri(name: String, iban: String, bic: String): String
{
- return "payto://x-taler-bank/localhost/$iban"
- }
- private fun buildPaytoUri(iban: String, bic: String): String {
- return "payto://x-taler-bank/localhost/$iban"
- }
-
- /** Builds the comparison operator for history entries based on the sign
of 'delta' */
- private fun getComparisonOperator(delta: Int, start: Long, table:
IdTable<Long>): Op<Boolean> {
- return if (delta < 0) {
- Expression.build {
- table.id less start
- }
- } else {
- Expression.build {
- table.id greater start
- }
- }
- }
- /** Helper handling 'start' being optional and its dependence on 'delta'.
*/
- private fun handleStartArgument(start: String?, delta: Int): Long {
- return expectLong(start) ?: if (delta >= 0) {
- /**
- * Using -1 as the smallest value, as some DBMS might use 0 and
some
- * others might use 1 as the smallest row id.
- */
- -1
- } else {
- /**
- * NOTE: the database currently enforces there MAX_VALUE is always
- * strictly greater than any row's id in the database. In fact,
the
- * database throws exception whenever a new row is going to occupy
- * the MAX_VALUE with its id.
- */
- Long.MAX_VALUE
- }
- }
-
- /**
- * The Taler layer cannot rely on the ktor-internal
JSON-converter/responder,
- * because this one adds a "charset" extra information in the Content-Type
header
- * that makes the GNUnet JSON parser unhappy.
- *
- * The workaround is to explicitly convert the 'data class'-object into a
JSON
- * string (what this function does), and use the simpler respondText
method.
- */
- private fun customConverter(body: Any): String {
- return jacksonObjectMapper().writeValueAsString(body)
- }
-
- /**
- * This function indicates whether a payment in the raw table was already
reported
- * by some other EBICS message. It works for both incoming and outgoing
payments.
- * Basically, it tries to match all the relevant details with those from
the records
- * that are already stored in the local "taler" database.
- *
- * @param entry a new raw payment to be checked.
- * @return true if the payment was already "seen" by the Taler layer,
false otherwise.
- */
- private fun duplicatePayment(entry: RawBankTransactionEntity): Boolean {
- return false
- }
-
- /**
- * This function checks whether the bank didn't accept one exchange's
payment initiation.
- *
- * @param entry the raw entry to check
- * @return true if the payment failed, false if it was successful.
- */
- private fun paymentFailed(entry: RawBankTransactionEntity): Boolean {
- return false
- }
-
- /** Attach Taler endpoints to the main Web server */
-
- init {
- app.get("/taler") {
- call.respondText("Taler Gateway Hello\n", ContentType.Text.Plain,
HttpStatusCode.OK)
- return@get
- }
- app.post("/taler/transfer") {
- val exchangeId = transaction {
authenticateRequest(call.request.headers["Authorization"]).id.value }
- val transferRequest = call.receive<TalerTransferRequest>()
- val amountObj = parseAmount(transferRequest.amount)
- val creditorObj = parsePayto(transferRequest.credit_account)
- val opaque_row_id = transaction {
- val creditorData = parsePayto(transferRequest.credit_account)
- val exchangeBankAccount =
getBankAccountsFromNexusUserId(exchangeId).first()
- val nexusUser = extractNexusUser(exchangeId)
- /** Checking the UID has the desired characteristics */
- TalerRequestedPaymentEntity.find {
- TalerRequestedPayments.requestUId eq
transferRequest.request_uid
- }.forEach {
- if (
- (it.amount != transferRequest.amount) or
- (it.creditAccount !=
transferRequest.exchange_base_url) or
- (it.wtid != transferRequest.wtid)
- ) {
- throw NexusError(
- HttpStatusCode.Conflict,
- "This uid (${transferRequest.request_uid}) belongs
to a different payment already"
- )
- }
- }
- val pain001 = addPreparedPayment(
- Pain001Data(
- creditorIban = creditorData.iban,
- creditorBic = creditorData.bic,
- creditorName = creditorData.name,
- subject = transferRequest.wtid,
- sum = amountObj.amount,
- currency = amountObj.currency,
- debitorAccount = exchangeBankAccount.id.value
- ),
- nexusUser
- )
- val rawEbics = if (!isProduction()) {
- RawBankTransactionEntity.new {
- sourceFileName = "test"
- unstructuredRemittanceInformation =
transferRequest.wtid
- transactionType = "DBIT"
- currency = amountObj.currency
- this.amount = amountObj.amount.toPlainString()
- counterpartBic = creditorObj.bic
- counterpartIban = creditorObj.iban
- counterpartName = creditorObj.name
- bankAccount = exchangeBankAccount
- bookingDate = DateTime.now().millis
- this.nexusUser = nexusUser
- status = "BOOK"
- }
- } else null
-
- val row = TalerRequestedPaymentEntity.new {
- preparedPayment = pain001 // not really used/needed, just
here to silence warnings
- exchangeBaseUrl = transferRequest.exchange_base_url
- requestUId = transferRequest.request_uid
- amount = transferRequest.amount
- wtid = transferRequest.wtid
- creditAccount = transferRequest.credit_account
- rawConfirmed = rawEbics
- }
-
- row.id.value
- }
- call.respond(
- HttpStatusCode.OK,
- TextContent(
- customConverter(
- TalerTransferResponse(
- /**
- * Normally should point to the next round where
the background
- * routine will send new PAIN.001 data to the
bank; work in progress..
- */
- timestamp = GnunetTimestamp(DateTime.now().millis),
- row_id = opaque_row_id
- )
- ),
- ContentType.Application.Json
- )
- )
- return@post
- }
- /** Test-API that creates one new payment addressed to the exchange.
*/
- app.post("/taler/admin/add-incoming") {
- val exchangeId = transaction {
authenticateRequest(call.request.headers["Authorization"]).id.value }
- val addIncomingData = call.receive<TalerAdminAddIncoming>()
- val debtor = parsePayto(addIncomingData.debit_account)
- val amount = parseAmount(addIncomingData.amount)
- val (bookingDate, opaque_row_id) = transaction {
- val exchangeBankAccount =
getBankAccountsFromNexusUserId(exchangeId).first()
- val rawPayment = RawBankTransactionEntity.new {
- sourceFileName = "test"
- unstructuredRemittanceInformation =
addIncomingData.reserve_pub
- transactionType = "CRDT"
- currency = amount.currency
- this.amount = amount.amount.toPlainString()
- counterpartBic = debtor.bic
- counterpartName = debtor.name
- counterpartIban = debtor.iban
- bookingDate = DateTime.now().millis
- status = "BOOK"
- nexusUser = extractNexusUser(exchangeId)
- bankAccount = exchangeBankAccount
- }
- /** This payment is "valid by default" and will be returned
- * as soon as the exchange will ask for new payments. */
- val row = TalerIncomingPaymentEntity.new {
- payment = rawPayment
- valid = true
- }
- Pair(rawPayment.bookingDate, row.id.value)
- }
- call.respond(
- TextContent(
- customConverter(
- TalerAddIncomingResponse(
- timestamp = GnunetTimestamp(bookingDate/ 1000),
- row_id = opaque_row_id
- )
- ),
- ContentType.Application.Json
- )
- )
- return@post
- }
-
- /** This endpoint triggers the refunding of invalid payments.
'Refunding'
- * in this context means that nexus _prepares_ the payment instruction
and
- * places it into a further table. Eventually, another routine will
perform
- * all the prepared payments. */
-
app.post("/ebics/taler/{id}/accounts/{acctid}/refund-invalid-payments") {
- val userId = transaction {
authenticateRequest(call.request.headers["Authorization"]).id.value }
- val nexusUser = getNexusUser(userId)
- val callerBankAccount = expectNonNull(call.parameters["acctid"])
- transaction {
- val bankAccount = getBankAccount(
- userId,
- callerBankAccount
- )
- TalerIncomingPaymentEntity.find {
- TalerIncomingPayments.refunded eq false and
(TalerIncomingPayments.valid eq false)
- }.forEach {
- addPreparedPayment(
- Pain001Data(
- creditorName = it.payment.counterpartName,
- creditorIban = it.payment.counterpartIban,
- creditorBic = it.payment.counterpartBic,
- sum = calculateRefund(it.payment.amount),
- subject = "Taler refund",
- currency = it.payment.currency,
- debitorAccount = callerBankAccount
- ),
- nexusUser
- )
- it.refunded = true
- }
- }
- return@post
- }
-
- /** This endpoint triggers the examination of raw incoming payments
aimed
- * at separating the good payments (those that will lead to a new
reserve
- * being created), from the invalid payments (those with a invalid
subject
- * that will soon be refunded.) Recently, the examination of raw
OUTGOING
- * payment was added as well.
- */
- app.post("/ebics/taler/{id}/crunch-raw-transactions") {
- val id = ensureNonNull(call.parameters["id"])
- // first find highest ID value of already processed rows.
- transaction {
- val subscriberAccount =
getBankAccountsFromNexusUserId(id).first()
- /**
- * Search for fresh incoming payments in the raw table, and
making pointers
- * from the Taler incoming payments table to the found fresh
(and valid!) payments.
- */
- val latestIncomingPaymentId: Long =
TalerIncomingPaymentEntity.getLast()
- RawBankTransactionEntity.find {
- /** Those with exchange bank account involved */
- RawBankTransactionsTable.bankAccount eq
subscriberAccount.id.value and
- /** Those that are incoming */
- (RawBankTransactionsTable.transactionType eq
"CRDT") and
- /** Those that are booked */
- (RawBankTransactionsTable.status eq "BOOK") and
- /** Those that came later than the latest
processed payment */
-
(RawBankTransactionsTable.id.greater(latestIncomingPaymentId))
-
- }.forEach {
- if (duplicatePayment(it)) {
- logger.warn("Incomint payment already seen")
- throw NexusError(
- HttpStatusCode.InternalServerError,
- "Incoming payment already seen"
- )
- }
- if
(CryptoUtil.checkValidEddsaPublicKey(it.unstructuredRemittanceInformation)) {
- TalerIncomingPaymentEntity.new {
- payment = it
- valid = true
- }
- } else {
- TalerIncomingPaymentEntity.new {
- payment = it
- valid = false
- }
- }
- }
- /**
- * Search for fresh OUTGOING transactions acknowledged by the
bank. As well
- * searching only for BOOKed transactions, even though status
changes should
- * be really unexpected here.
- */
- val latestOutgoingPaymentId =
TalerRequestedPaymentEntity.getLast()
- RawBankTransactionEntity.find {
- /** Those that came after the last processed payment */
- RawBankTransactionsTable.id greater
latestOutgoingPaymentId and
- /** Those involving the exchange bank account */
- (RawBankTransactionsTable.bankAccount eq
subscriberAccount.id.value) and
- /** Those that are outgoing */
- (RawBankTransactionsTable.transactionType eq
"DBIT")
- }.forEach {
- if (paymentFailed(it)) {
- logger.error("Bank didn't accept one payment from the
exchange")
- throw NexusError(
- HttpStatusCode.InternalServerError,
- "Bank didn't accept one payment from the exchange"
- )
- }
- if (duplicatePayment(it)) {
- logger.warn("Incomint payment already seen")
- throw NexusError(
- HttpStatusCode.InternalServerError,
- "Outgoing payment already seen"
- )
- }
- var talerRequested = TalerRequestedPaymentEntity.find {
- TalerRequestedPayments.wtid eq
it.unstructuredRemittanceInformation
- }.firstOrNull() ?: throw NexusError(
- HttpStatusCode.InternalServerError,
- "Unrecognized fresh outgoing payment met (subject:
${it.unstructuredRemittanceInformation})."
- )
- talerRequested.rawConfirmed = it
- }
- }
-
- call.respondText (
- "New raw payments Taler-processed",
- ContentType.Text.Plain,
- HttpStatusCode.OK
- )
- return@post
- }
- /** Responds only with the payments that the EXCHANGE made. Typically
to
- * merchants but possibly to refund invalid incoming payments. A
payment is
- * counted only if was once confirmed by the bank.
- */
- app.get("/taler/history/outgoing") {
- /* sanitize URL arguments */
- val subscriberId = transaction {
authenticateRequest(call.request.headers["Authorization"]).id.value }
- val delta: Int = expectInt(call.expectUrlParameter("delta"))
- val start: Long =
handleStartArgument(call.request.queryParameters["start"], delta)
- val startCmpOp = getComparisonOperator(delta, start,
TalerRequestedPayments)
- /* retrieve database elements */
- val history = TalerOutgoingHistory()
- transaction {
- /** Retrieve all the outgoing payments from the _clean Taler
outgoing table_ */
- val subscriberBankAccount =
getBankAccountsFromNexusUserId(subscriberId).first()
- val reqPayments = TalerRequestedPaymentEntity.find {
- TalerRequestedPayments.rawConfirmed.isNotNull() and
startCmpOp
- }.orderTaler(delta)
- if (reqPayments.isNotEmpty()) {
- reqPayments.subList(0, min(abs(delta),
reqPayments.size)).forEach {
- history.outgoing_transactions.add(
- TalerOutgoingBankTransaction(
- row_id = it.id.value,
- amount = it.amount,
- wtid = it.wtid,
- date =
GnunetTimestamp(it.rawConfirmed?.bookingDate?.div(1000) ?: throw NexusError(
- HttpStatusCode.InternalServerError, "Null
value met after check, VERY strange.")),
- credit_account = it.creditAccount,
- debit_account =
buildPaytoUri(subscriberBankAccount.iban, subscriberBankAccount.bankCode),
- exchange_base_url =
"FIXME-to-request-along-subscriber-registration"
- )
- )
- }
- }
- }
- call.respond(
- HttpStatusCode.OK,
- TextContent(customConverter(history),
ContentType.Application.Json)
- )
- return@get
- }
- /** Responds only with the valid incoming payments */
- app.get("/taler/history/incoming") {
- val exchangeId = transaction {
authenticateRequest(call.request.headers["Authorization"]).id.value }
- val delta: Int = expectInt(call.expectUrlParameter("delta"))
- val start: Long =
handleStartArgument(call.request.queryParameters["start"], delta)
- val history = TalerIncomingHistory()
- val startCmpOp = getComparisonOperator(delta, start,
TalerIncomingPayments)
- transaction {
- /**
- * Below, the test harness creates the exchange's bank account
- * object based on the payto:// given as the funds receiver.
- *
- * This is needed because nexus takes this information from the
- * bank - normally - but tests are currently avoiding any
interaction
- * with banks or sandboxes.
- */
- if (!isProduction()) {
- val EXCHANGE_BANKACCOUNT_ID = "exchange-bankaccount-id"
- if (BankAccountEntity.findById(EXCHANGE_BANKACCOUNT_ID) ==
null) {
- val newBankAccount = BankAccountEntity.new(id =
EXCHANGE_BANKACCOUNT_ID) {
- accountHolder = "Test Exchange"
- iban = "42"
- bankCode = "localhost"
- }
- val nexusUser = extractNexusUser(exchangeId)
- BankAccountMapEntity.new {
- bankAccount = newBankAccount
- ebicsSubscriber = getEbicsTransport(exchangeId)
- this.nexusUser = nexusUser
- }
- }
- }
- val exchangeBankAccount =
getBankAccountsFromNexusUserId(exchangeId).first()
- val orderedPayments = TalerIncomingPaymentEntity.find {
- TalerIncomingPayments.valid eq true and startCmpOp
- }.orderTaler(delta)
- if (orderedPayments.isNotEmpty()) {
- orderedPayments.subList(0, min(abs(delta),
orderedPayments.size)).forEach {
- history.incoming_transactions.add(
- TalerIncomingBankTransaction(
- date = GnunetTimestamp(it.payment.bookingDate
/ 1000),
- row_id = it.id.value,
- amount =
"${it.payment.currency}:${it.payment.amount}",
- reserve_pub =
it.payment.unstructuredRemittanceInformation,
- credit_account = buildPaytoUri(
- it.payment.bankAccount.accountHolder,
- it.payment.bankAccount.iban,
- it.payment.bankAccount.bankCode
- ),
- debit_account = buildPaytoUri(
- it.payment.counterpartName,
- it.payment.counterpartIban,
- it.payment.counterpartBic
- )
- )
- )
- }
- }
- }
- call.respond(TextContent(customConverter(history),
ContentType.Application.Json))
- return@get
- }
- }
-}
\ No newline at end of file
+//class Taler(app: Route) {
+//
+// /** Payment initiating data structures: one endpoint
"$BASE_URL/transfer". */
+// private data class TalerTransferRequest(
+// val request_uid: String,
+// val amount: String,
+// val exchange_base_url: String,
+// val wtid: String,
+// val credit_account: String
+// )
+// private data class TalerTransferResponse(
+// // point in time when the nexus put the payment instruction into the
database.
+// val timestamp: GnunetTimestamp,
+// val row_id: Long
+// )
+//
+// /** History accounting data structures */
+// private data class TalerIncomingBankTransaction(
+// val row_id: Long,
+// val date: GnunetTimestamp, // timestamp
+// val amount: String,
+// val credit_account: String, // payto form,
+// val debit_account: String,
+// val reserve_pub: String
+// )
+// private data class TalerIncomingHistory(
+// var incoming_transactions: MutableList<TalerIncomingBankTransaction>
= mutableListOf()
+// )
+// private data class TalerOutgoingBankTransaction(
+// val row_id: Long,
+// val date: GnunetTimestamp, // timestamp
+// val amount: String,
+// val credit_account: String, // payto form,
+// val debit_account: String,
+// val wtid: String,
+// val exchange_base_url: String
+// )
+// private data class TalerOutgoingHistory(
+// var outgoing_transactions: MutableList<TalerOutgoingBankTransaction>
= mutableListOf()
+// )
+//
+// /** Test APIs' data structures. */
+// private data class TalerAdminAddIncoming(
+// val amount: String,
+// val reserve_pub: String,
+// /**
+// * This account is the one giving money to the exchange. It doesn't
+// * have to be 'created' as it might (and normally is) simply be a
payto://
+// * address pointing to a bank account hosted in a different financial
+// * institution.
+// */
+// val debit_account: String
+// )
+//
+// private data class GnunetTimestamp(
+// val t_ms: Long
+// )
+// private data class TalerAddIncomingResponse(
+// val timestamp: GnunetTimestamp,
+// val row_id: Long
+// )
+//
+// /**
+// * Helper data structures.
+// */
+// data class Payto(
+// val name: String = "NOTGIVEN",
+// val iban: String,
+// val bic: String = "NOTGIVEN"
+// )
+// /**
+// * Helper functions
+// */
+// fun parsePayto(paytoUri: String): Payto {
+// /**
+// * First try to parse a "iban"-type payto URI. If that fails,
+// * then assume a test is being run under the "x-taler-bank" type.
+// * If that one fails too, throw exception.
+// *
+// * Note: since the Nexus doesn't have the notion of "x-taler-bank",
+// * such URIs must yield a iban-compatible tuple of values.
Therefore,
+// * the plain bank account number maps to a "iban", and the <bank
hostname>
+// * maps to a "bic".
+// */
+//
+//
+// /**
+// * payto://iban/BIC?/IBAN?name=<name>
+// * payto://x-taler-bank/<bank hostname>/<plain account number>
+// */
+//
+// val ibanMatch =
Regex("payto://iban/([A-Z0-9]+/)?([A-Z0-9]+)\\?name=(\\w+)").find(paytoUri)
+// if (ibanMatch != null) {
+// val (bic, iban, name) = ibanMatch.destructured
+// return Payto(name, iban, bic.replace("/", ""))
+// }
+// val xTalerBankMatch =
Regex("payto://x-taler-bank/localhost/([0-9]+)").find(paytoUri)
+// if (xTalerBankMatch != null) {
+// val xTalerBankAcctNo = xTalerBankMatch.destructured.component1()
+// return Payto("Taler Exchange", xTalerBankAcctNo, "localhost")
+// }
+//
+// throw NexusError(HttpStatusCode.BadRequest, "invalid payto URI
($paytoUri)")
+// }
+//
+// /** Sort query results in descending order for negative deltas, and
ascending otherwise. */
+// private fun <T : Entity<Long>> SizedIterable<T>.orderTaler(delta: Int):
List<T> {
+// return if (delta < 0) {
+// this.sortedByDescending { it.id }
+// } else {
+// this.sortedBy { it.id }
+// }
+// }
+//
+// /**
+// * NOTE: those payto-builders default all to the x-taler-bank transport.
+// * A mechanism to easily switch transport is needed, as production needs
+// * 'iban'.
+// */
+// private fun buildPaytoUri(name: String, iban: String, bic: String):
String {
+// return "payto://x-taler-bank/localhost/$iban"
+// }
+// private fun buildPaytoUri(iban: String, bic: String): String {
+// return "payto://x-taler-bank/localhost/$iban"
+// }
+//
+// /** Builds the comparison operator for history entries based on the sign
of 'delta' */
+// private fun getComparisonOperator(delta: Int, start: Long, table:
IdTable<Long>): Op<Boolean> {
+// return if (delta < 0) {
+// Expression.build {
+// table.id less start
+// }
+// } else {
+// Expression.build {
+// table.id greater start
+// }
+// }
+// }
+// /** Helper handling 'start' being optional and its dependence on
'delta'. */
+// private fun handleStartArgument(start: String?, delta: Int): Long {
+// return expectLong(start) ?: if (delta >= 0) {
+// /**
+// * Using -1 as the smallest value, as some DBMS might use 0 and
some
+// * others might use 1 as the smallest row id.
+// */
+// -1
+// } else {
+// /**
+// * NOTE: the database currently enforces there MAX_VALUE is
always
+// * strictly greater than any row's id in the database. In fact,
the
+// * database throws exception whenever a new row is going to
occupy
+// * the MAX_VALUE with its id.
+// */
+// Long.MAX_VALUE
+// }
+// }
+//
+// /**
+// * The Taler layer cannot rely on the ktor-internal
JSON-converter/responder,
+// * because this one adds a "charset" extra information in the
Content-Type header
+// * that makes the GNUnet JSON parser unhappy.
+// *
+// * The workaround is to explicitly convert the 'data class'-object into
a JSON
+// * string (what this function does), and use the simpler respondText
method.
+// */
+// private fun customConverter(body: Any): String {
+// return jacksonObjectMapper().writeValueAsString(body)
+// }
+//
+// /**
+// * This function indicates whether a payment in the raw table was
already reported
+// * by some other EBICS message. It works for both incoming and outgoing
payments.
+// * Basically, it tries to match all the relevant details with those from
the records
+// * that are already stored in the local "taler" database.
+// *
+// * @param entry a new raw payment to be checked.
+// * @return true if the payment was already "seen" by the Taler layer,
false otherwise.
+// */
+// private fun duplicatePayment(entry: RawBankTransactionEntity): Boolean {
+// return false
+// }
+//
+// /**
+// * This function checks whether the bank didn't accept one exchange's
payment initiation.
+// *
+// * @param entry the raw entry to check
+// * @return true if the payment failed, false if it was successful.
+// */
+// private fun paymentFailed(entry: RawBankTransactionEntity): Boolean {
+// return false
+// }
+//
+// /** Attach Taler endpoints to the main Web server */
+//
+// init {
+// app.get("/taler") {
+// call.respondText("Taler Gateway Hello\n",
ContentType.Text.Plain, HttpStatusCode.OK)
+// return@get
+// }
+// app.post("/taler/transfer") {
+// val exchangeId = transaction {
authenticateRequest(call.request.headers["Authorization"]).id.value }
+// val transferRequest = call.receive<TalerTransferRequest>()
+// val amountObj = parseAmount(transferRequest.amount)
+// val creditorObj = parsePayto(transferRequest.credit_account)
+// val opaque_row_id = transaction {
+// val creditorData = parsePayto(transferRequest.credit_account)
+// val exchangeBankAccount =
getBankAccountsFromNexusUserId(exchangeId).first()
+// val nexusUser = extractNexusUser(exchangeId)
+// /** Checking the UID has the desired characteristics */
+// TalerRequestedPaymentEntity.find {
+// TalerRequestedPayments.requestUId eq
transferRequest.request_uid
+// }.forEach {
+// if (
+// (it.amount != transferRequest.amount) or
+// (it.creditAccount !=
transferRequest.exchange_base_url) or
+// (it.wtid != transferRequest.wtid)
+// ) {
+// throw NexusError(
+// HttpStatusCode.Conflict,
+// "This uid (${transferRequest.request_uid})
belongs to a different payment already"
+// )
+// }
+// }
+// val pain001 = addPreparedPayment(
+// Pain001Data(
+// creditorIban = creditorData.iban,
+// creditorBic = creditorData.bic,
+// creditorName = creditorData.name,
+// subject = transferRequest.wtid,
+// sum = amountObj.amount,
+// currency = amountObj.currency,
+// debitorAccount = exchangeBankAccount.id.value
+// ),
+// nexusUser
+// )
+// val rawEbics = if (!isProduction()) {
+// RawBankTransactionEntity.new {
+// sourceFileName = "test"
+// unstructuredRemittanceInformation =
transferRequest.wtid
+// transactionType = "DBIT"
+// currency = amountObj.currency
+// this.amount = amountObj.amount.toPlainString()
+// counterpartBic = creditorObj.bic
+// counterpartIban = creditorObj.iban
+// counterpartName = creditorObj.name
+// bankAccount = exchangeBankAccount
+// bookingDate = DateTime.now().millis
+// this.nexusUser = nexusUser
+// status = "BOOK"
+// }
+// } else null
+//
+// val row = TalerRequestedPaymentEntity.new {
+// preparedPayment = pain001 // not really used/needed,
just here to silence warnings
+// exchangeBaseUrl = transferRequest.exchange_base_url
+// requestUId = transferRequest.request_uid
+// amount = transferRequest.amount
+// wtid = transferRequest.wtid
+// creditAccount = transferRequest.credit_account
+// rawConfirmed = rawEbics
+// }
+//
+// row.id.value
+// }
+// call.respond(
+// HttpStatusCode.OK,
+// TextContent(
+// customConverter(
+// TalerTransferResponse(
+// /**
+// * Normally should point to the next round where
the background
+// * routine will send new PAIN.001 data to the
bank; work in progress..
+// */
+// timestamp =
GnunetTimestamp(DateTime.now().millis),
+// row_id = opaque_row_id
+// )
+// ),
+// ContentType.Application.Json
+// )
+// )
+// return@post
+// }
+// /** Test-API that creates one new payment addressed to the exchange.
*/
+// app.post("/taler/admin/add-incoming") {
+// val exchangeId = transaction {
authenticateRequest(call.request.headers["Authorization"]).id.value }
+// val addIncomingData = call.receive<TalerAdminAddIncoming>()
+// val debtor = parsePayto(addIncomingData.debit_account)
+// val amount = parseAmount(addIncomingData.amount)
+// val (bookingDate, opaque_row_id) = transaction {
+// val exchangeBankAccount =
getBankAccountsFromNexusUserId(exchangeId).first()
+// val rawPayment = RawBankTransactionEntity.new {
+// sourceFileName = "test"
+// unstructuredRemittanceInformation =
addIncomingData.reserve_pub
+// transactionType = "CRDT"
+// currency = amount.currency
+// this.amount = amount.amount.toPlainString()
+// counterpartBic = debtor.bic
+// counterpartName = debtor.name
+// counterpartIban = debtor.iban
+// bookingDate = DateTime.now().millis
+// status = "BOOK"
+// nexusUser = extractNexusUser(exchangeId)
+// bankAccount = exchangeBankAccount
+// }
+// /** This payment is "valid by default" and will be returned
+// * as soon as the exchange will ask for new payments. */
+// val row = TalerIncomingPaymentEntity.new {
+// payment = rawPayment
+// valid = true
+// }
+// Pair(rawPayment.bookingDate, row.id.value)
+// }
+// call.respond(
+// TextContent(
+// customConverter(
+// TalerAddIncomingResponse(
+// timestamp = GnunetTimestamp(bookingDate/ 1000),
+// row_id = opaque_row_id
+// )
+// ),
+// ContentType.Application.Json
+// )
+// )
+// return@post
+// }
+//
+// /** This endpoint triggers the refunding of invalid payments.
'Refunding'
+// * in this context means that nexus _prepares_ the payment
instruction and
+// * places it into a further table. Eventually, another routine will
perform
+// * all the prepared payments. */
+//
app.post("/ebics/taler/{id}/accounts/{acctid}/refund-invalid-payments") {
+// val userId = transaction {
authenticateRequest(call.request.headers["Authorization"]).id.value }
+// val nexusUser = getNexusUser(userId)
+// val callerBankAccount = expectNonNull(call.parameters["acctid"])
+// transaction {
+// val bankAccount = getBankAccount(
+// userId,
+// callerBankAccount
+// )
+// TalerIncomingPaymentEntity.find {
+// TalerIncomingPayments.refunded eq false and
(TalerIncomingPayments.valid eq false)
+// }.forEach {
+// addPreparedPayment(
+// Pain001Data(
+// creditorName = it.payment.counterpartName,
+// creditorIban = it.payment.counterpartIban,
+// creditorBic = it.payment.counterpartBic,
+// sum = calculateRefund(it.payment.amount),
+// subject = "Taler refund",
+// currency = it.payment.currency,
+// debitorAccount = callerBankAccount
+// ),
+// nexusUser
+// )
+// it.refunded = true
+// }
+// }
+// return@post
+// }
+//
+// /** This endpoint triggers the examination of raw incoming payments
aimed
+// * at separating the good payments (those that will lead to a new
reserve
+// * being created), from the invalid payments (those with a invalid
subject
+// * that will soon be refunded.) Recently, the examination of raw
OUTGOING
+// * payment was added as well.
+// */
+// app.post("/ebics/taler/{id}/crunch-raw-transactions") {
+// val id = ensureNonNull(call.parameters["id"])
+// // first find highest ID value of already processed rows.
+// transaction {
+// val subscriberAccount =
getBankAccountsFromNexusUserId(id).first()
+// /**
+// * Search for fresh incoming payments in the raw table, and
making pointers
+// * from the Taler incoming payments table to the found fresh
(and valid!) payments.
+// */
+// val latestIncomingPaymentId: Long =
TalerIncomingPaymentEntity.getLast()
+// RawBankTransactionEntity.find {
+// /** Those with exchange bank account involved */
+// RawBankTransactionsTable.bankAccount eq
subscriberAccount.id.value and
+// /** Those that are incoming */
+// (RawBankTransactionsTable.transactionType eq
"CRDT") and
+// /** Those that are booked */
+// (RawBankTransactionsTable.status eq "BOOK") and
+// /** Those that came later than the latest
processed payment */
+//
(RawBankTransactionsTable.id.greater(latestIncomingPaymentId))
+//
+// }.forEach {
+// if (duplicatePayment(it)) {
+// logger.warn("Incomint payment already seen")
+// throw NexusError(
+// HttpStatusCode.InternalServerError,
+// "Incoming payment already seen"
+// )
+// }
+// if
(CryptoUtil.checkValidEddsaPublicKey(it.unstructuredRemittanceInformation)) {
+// TalerIncomingPaymentEntity.new {
+// payment = it
+// valid = true
+// }
+// } else {
+// TalerIncomingPaymentEntity.new {
+// payment = it
+// valid = false
+// }
+// }
+// }
+// /**
+// * Search for fresh OUTGOING transactions acknowledged by
the bank. As well
+// * searching only for BOOKed transactions, even though
status changes should
+// * be really unexpected here.
+// */
+// val latestOutgoingPaymentId =
TalerRequestedPaymentEntity.getLast()
+// RawBankTransactionEntity.find {
+// /** Those that came after the last processed payment */
+// RawBankTransactionsTable.id greater
latestOutgoingPaymentId and
+// /** Those involving the exchange bank account */
+// (RawBankTransactionsTable.bankAccount eq
subscriberAccount.id.value) and
+// /** Those that are outgoing */
+// (RawBankTransactionsTable.transactionType eq
"DBIT")
+// }.forEach {
+// if (paymentFailed(it)) {
+// logger.error("Bank didn't accept one payment from
the exchange")
+// throw NexusError(
+// HttpStatusCode.InternalServerError,
+// "Bank didn't accept one payment from the
exchange"
+// )
+// }
+// if (duplicatePayment(it)) {
+// logger.warn("Incomint payment already seen")
+// throw NexusError(
+// HttpStatusCode.InternalServerError,
+// "Outgoing payment already seen"
+// )
+// }
+// var talerRequested = TalerRequestedPaymentEntity.find {
+// TalerRequestedPayments.wtid eq
it.unstructuredRemittanceInformation
+// }.firstOrNull() ?: throw NexusError(
+// HttpStatusCode.InternalServerError,
+// "Unrecognized fresh outgoing payment met (subject:
${it.unstructuredRemittanceInformation})."
+// )
+// talerRequested.rawConfirmed = it
+// }
+// }
+//
+// call.respondText (
+// "New raw payments Taler-processed",
+// ContentType.Text.Plain,
+// HttpStatusCode.OK
+// )
+// return@post
+// }
+// /** Responds only with the payments that the EXCHANGE made.
Typically to
+// * merchants but possibly to refund invalid incoming payments. A
payment is
+// * counted only if was once confirmed by the bank.
+// */
+// app.get("/taler/history/outgoing") {
+// /* sanitize URL arguments */
+// val subscriberId = transaction {
authenticateRequest(call.request.headers["Authorization"]).id.value }
+// val delta: Int = expectInt(call.expectUrlParameter("delta"))
+// val start: Long =
handleStartArgument(call.request.queryParameters["start"], delta)
+// val startCmpOp = getComparisonOperator(delta, start,
TalerRequestedPayments)
+// /* retrieve database elements */
+// val history = TalerOutgoingHistory()
+// transaction {
+// /** Retrieve all the outgoing payments from the _clean Taler
outgoing table_ */
+// val subscriberBankAccount =
getBankAccountsFromNexusUserId(subscriberId).first()
+// val reqPayments = TalerRequestedPaymentEntity.find {
+// TalerRequestedPayments.rawConfirmed.isNotNull() and
startCmpOp
+// }.orderTaler(delta)
+// if (reqPayments.isNotEmpty()) {
+// reqPayments.subList(0, min(abs(delta),
reqPayments.size)).forEach {
+// history.outgoing_transactions.add(
+// TalerOutgoingBankTransaction(
+// row_id = it.id.value,
+// amount = it.amount,
+// wtid = it.wtid,
+// date =
GnunetTimestamp(it.rawConfirmed?.bookingDate?.div(1000) ?: throw NexusError(
+// HttpStatusCode.InternalServerError,
"Null value met after check, VERY strange.")),
+// credit_account = it.creditAccount,
+// debit_account =
buildPaytoUri(subscriberBankAccount.iban, subscriberBankAccount.bankCode),
+// exchange_base_url =
"FIXME-to-request-along-subscriber-registration"
+// )
+// )
+// }
+// }
+// }
+// call.respond(
+// HttpStatusCode.OK,
+// TextContent(customConverter(history),
ContentType.Application.Json)
+// )
+// return@get
+// }
+// /** Responds only with the valid incoming payments */
+// app.get("/taler/history/incoming") {
+// val exchangeId = transaction {
authenticateRequest(call.request.headers["Authorization"]).id.value }
+// val delta: Int = expectInt(call.expectUrlParameter("delta"))
+// val start: Long =
handleStartArgument(call.request.queryParameters["start"], delta)
+// val history = TalerIncomingHistory()
+// val startCmpOp = getComparisonOperator(delta, start,
TalerIncomingPayments)
+// transaction {
+// /**
+// * Below, the test harness creates the exchange's bank
account
+// * object based on the payto:// given as the funds receiver.
+// *
+// * This is needed because nexus takes this information from
the
+// * bank - normally - but tests are currently avoiding any
interaction
+// * with banks or sandboxes.
+// */
+// if (!isProduction()) {
+// throw Error("currently not implemented")
+// val EXCHANGE_BANKACCOUNT_ID = "exchange-bankaccount-id"
+// if (BankAccountEntity.findById(EXCHANGE_BANKACCOUNT_ID)
== null) {
+// val newBankAccount = BankAccountEntity.new(id =
EXCHANGE_BANKACCOUNT_ID) {
+// accountHolder = "Test Exchange"
+// iban = "42"
+// bankCode = "localhost"
+// }
+// val nexusUser = extractNexusUser(exchangeId)
+// BankAccountMapEntity.new {
+// bankAccount = newBankAccount
+// ebicsSubscriber = getEbicsTransport(exchangeId)
+// this.nexusUser = nexusUser
+// }
+// }
+// }
+// val orderedPayments = TalerIncomingPaymentEntity.find {
+// TalerIncomingPayments.valid eq true and startCmpOp
+// }.orderTaler(delta)
+// if (orderedPayments.isNotEmpty()) {
+// orderedPayments.subList(0, min(abs(delta),
orderedPayments.size)).forEach {
+// history.incoming_transactions.add(
+// TalerIncomingBankTransaction(
+// date =
GnunetTimestamp(it.payment.bookingDate / 1000),
+// row_id = it.id.value,
+// amount =
"${it.payment.currency}:${it.payment.amount}",
+// reserve_pub =
it.payment.unstructuredRemittanceInformation,
+// credit_account = buildPaytoUri(
+// it.payment.bankAccount.accountHolder,
+// it.payment.bankAccount.iban,
+// it.payment.bankAccount.bankCode
+// ),
+// debit_account = buildPaytoUri(
+// it.payment.counterpartName,
+// it.payment.counterpartIban,
+// it.payment.counterpartBic
+// )
+// )
+// )
+// }
+// }
+// }
+// call.respond(TextContent(customConverter(history),
ContentType.Application.Json))
+// return@get
+// }
+// }
+//}
\ No newline at end of file
diff --git a/nexus/src/test/kotlin/authentication.kt
b/nexus/src/test/kotlin/authentication.kt
index 602f990..bcf3499 100644
--- a/nexus/src/test/kotlin/authentication.kt
+++ b/nexus/src/test/kotlin/authentication.kt
@@ -1,17 +1,12 @@
package tech.libeufin.nexus
-import org.jetbrains.exposed.sql.Database
-import org.jetbrains.exposed.sql.SchemaUtils
-import org.jetbrains.exposed.sql.transactions.transaction
import org.junit.Test
import junit.framework.TestCase.assertEquals
-import tech.libeufin.util.CryptoUtil
-import javax.sql.rowset.serial.SerialBlob
class AuthenticationTest {
@Test
fun basicAuthHeaderTest() {
- val pass = extractUserAndHashedPassword(
+ val pass = extractUserAndPassword(
"Basic dXNlcm5hbWU6cGFzc3dvcmQ="
).second
assertEquals("password", pass);
diff --git a/nexus/src/test/kotlin/taler.kt b/nexus/src/test/kotlin/taler.kt
index df49493..8cb59ad 100644
--- a/nexus/src/test/kotlin/taler.kt
+++ b/nexus/src/test/kotlin/taler.kt
@@ -10,26 +10,26 @@ import tech.libeufin.util.parseAmount
class TalerTest {
- @InternalAPI
- val taler = Taler(Route(null, RootRouteSelector("unused")))
-
- @InternalAPI
- @Test
- fun paytoParserTest() {
- val payto = taler.parsePayto("payto://iban/ABC/XYZ?name=foo")
- assert(payto.bic == "ABC" && payto.iban == "XYZ" && payto.name ==
"foo")
- val paytoNoBic = taler.parsePayto("payto://iban/XYZ?name=foo")
- assert(paytoNoBic.bic == "" && paytoNoBic.iban == "XYZ" &&
paytoNoBic.name == "foo")
- }
-
- @InternalAPI
- @Test
- fun amountParserTest() {
- val amount = parseAmount("EUR:1")
- assert(amount.currency == "EUR" && amount.amount.equals(BigDecimal(1)))
- val amount299 = parseAmount("EUR:2.99")
- assert(amount299.amount.compareTo(Amount("2.99")) == 0)
- val amount25 = parseAmount("EUR:2.5")
- assert(amount25.amount.compareTo(Amount("2.5")) == 0)
- }
+// @InternalAPI
+// val taler = Taler(Route(null, RootRouteSelector("unused")))
+//
+// @InternalAPI
+// @Test
+// fun paytoParserTest() {
+// val payto = taler.parsePayto("payto://iban/ABC/XYZ?name=foo")
+// assert(payto.bic == "ABC" && payto.iban == "XYZ" && payto.name ==
"foo")
+// val paytoNoBic = taler.parsePayto("payto://iban/XYZ?name=foo")
+// assert(paytoNoBic.bic == "" && paytoNoBic.iban == "XYZ" &&
paytoNoBic.name == "foo")
+// }
+//
+// @InternalAPI
+// @Test
+// fun amountParserTest() {
+// val amount = parseAmount("EUR:1")
+// assert(amount.currency == "EUR" &&
amount.amount.equals(BigDecimal(1)))
+// val amount299 = parseAmount("EUR:2.99")
+// assert(amount299.amount.compareTo(Amount("2.99")) == 0)
+// val amount25 = parseAmount("EUR:2.5")
+// assert(amount25.amount.compareTo(Amount("2.5")) == 0)
+// }
}
\ No newline at end of file
diff --git a/util/src/main/kotlin/Ebics.kt b/util/src/main/kotlin/Ebics.kt
index 690a122..589edbc 100644
--- a/util/src/main/kotlin/Ebics.kt
+++ b/util/src/main/kotlin/Ebics.kt
@@ -488,6 +488,14 @@ fun makeEbicsHEVRequest(subscriberDetails:
EbicsClientSubscriberDetails): String
return XMLUtil.convertDomToString(doc)
}
+fun makeEbicsHEVRequestRaw(hostID: String): String {
+ val req = HEVRequest().apply {
+ hostId = hostId
+ }
+ val doc = XMLUtil.convertJaxbToDocument(req)
+ return XMLUtil.convertDomToString(doc)
+}
+
fun parseEbicsHEVResponse(respStr: String): EbicsHevDetails {
val resp = try {
XMLUtil.convertStringToJaxb<HEVResponse>(respStr)
--
To stop receiving notification emails like this one, please contact
address@hidden.
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [libeufin] branch master updated: move towards new API,
gnunet <=