[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[libeufin] branch master updated (f73bdad -> 7033a39)
From: |
gnunet |
Subject: |
[libeufin] branch master updated (f73bdad -> 7033a39) |
Date: |
Sun, 10 May 2020 01:15:16 +0200 |
This is an automated email from the git hooks/post-receive script.
ms pushed a change to branch master
in repository libeufin.
from f73bdad POST preapred-payments/submit
new e752f65 POST ../prepared-payments
new ce65389 POST ../collected-transactions
new 4b42c65 Organize JSON types.
new d0a36f7 API migration
new 7033a39 POST ../collected-transactions
The 5 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
nexus/build.gradle | 2 +-
nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt | 64 ++++----
.../src/main/kotlin/tech/libeufin/nexus/Helpers.kt | 105 +++++++------
nexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt | 166 +++++++++++++--------
nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt | 156 ++++++++++++++++---
.../kotlin/tech/libeufin/nexus/MainDeprecated.kt | 14 +-
nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt | 25 +---
nexus/src/test/kotlin/PainGeneration.kt | 6 +-
util/src/main/kotlin/strings.kt | 13 ++
9 files changed, 358 insertions(+), 193 deletions(-)
diff --git a/nexus/build.gradle b/nexus/build.gradle
index ff58a78..b2a264a 100644
--- a/nexus/build.gradle
+++ b/nexus/build.gradle
@@ -16,7 +16,7 @@ plugins {
sourceSets {
main.kotlin.srcDirs = ["src/main/kotlin"]
- main.kotlin.excludes = ["**/MainDeprecated.kt"]
+ main.kotlin.excludes = ["**/MainDeprecated.kt", "**/taler.kt"]
}
task installToPrefix(type: Copy) {
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt
b/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt
index ba5f7f8..957e3ca 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt
@@ -5,8 +5,6 @@ import org.jetbrains.exposed.dao.*
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.TransactionManager
import org.jetbrains.exposed.sql.transactions.transaction
-import tech.libeufin.nexus.BankAccountsTable.entityId
-import tech.libeufin.nexus.BankAccountsTable.primaryKey
import tech.libeufin.util.amount
import java.sql.Connection
@@ -19,7 +17,7 @@ const val ID_MAX_LENGTH = 50
* in the PAIN-table.
*/
object TalerRequestedPayments: LongIdTable() {
- val preparedPayment = reference("payment", Pain001Table)
+ val preparedPayment = reference("payment", PreparedPaymentsTable)
val requestUId = text("request_uid")
val amount = text("amount")
val exchangeBaseUrl = text("exchange_base_url")
@@ -34,7 +32,7 @@ object TalerRequestedPayments: LongIdTable() {
class TalerRequestedPaymentEntity(id: EntityID<Long>) : LongEntity(id) {
companion object :
LongEntityClass<TalerRequestedPaymentEntity>(TalerRequestedPayments)
- var preparedPayment by Pain001Entity referencedOn
TalerRequestedPayments.preparedPayment
+ var preparedPayment by PreparedPaymentEntity referencedOn
TalerRequestedPayments.preparedPayment
var requestUId by TalerRequestedPayments.requestUId
var amount by TalerRequestedPayments.amount
var exchangeBaseUrl by TalerRequestedPayments.exchangeBaseUrl
@@ -91,13 +89,12 @@ object RawBankTransactionsTable : LongIdTable() {
val transactionType = text("transactionType") /* DBIT or CRDT */
val currency = text("currency")
val amount = text("amount")
- val creditorIban = text("creditorIban")
- val creditorName = text("creditorBic")
- val debitorIban = text("debitorIban")
- val debitorName = text("debitorName")
+ val counterpartIban = text("counterpartIban")
val counterpartBic = text("counterpartBic")
+ val counterpartName = text("counterpartName")
val bookingDate = long("bookingDate")
val status = text("status") // BOOK or other.
+ val bankAccount = reference("bankAccount", BankAccountsTable)
}
class RawBankTransactionEntity(id: EntityID<Long>) : LongEntity(id) {
@@ -107,22 +104,23 @@ class RawBankTransactionEntity(id: EntityID<Long>) :
LongEntity(id) {
var transactionType by RawBankTransactionsTable.transactionType
var currency by RawBankTransactionsTable.currency
var amount by RawBankTransactionsTable.amount
- var debitorIban by RawBankTransactionsTable.debitorIban
- var debitorName by RawBankTransactionsTable.debitorName
- var creditorName by RawBankTransactionsTable.creditorName
- var creditorIban by RawBankTransactionsTable.creditorIban
+ var counterpartIban by RawBankTransactionsTable.counterpartIban
var counterpartBic by RawBankTransactionsTable.counterpartBic
+ var counterpartName by RawBankTransactionsTable.counterpartName
var bookingDate by RawBankTransactionsTable.bookingDate
var nexusUser by NexusUserEntity referencedOn
RawBankTransactionsTable.nexusUser
var status by RawBankTransactionsTable.status
+ var bankAccount by BankAccountEntity referencedOn
RawBankTransactionsTable.bankAccount
}
/**
* Represent a prepare payment.
*/
-object Pain001Table : IdTable<String>() {
+object PreparedPaymentsTable : IdTable<String>() {
+ /** the UUID representing this payment in the system */
override val id = BankAccountsTable.varchar("id",
ID_MAX_LENGTH).entityId().primaryKey()
val paymentId = long("paymentId")
- val fileDate = long("fileDate")
+ val preparationDate = long("preparationDate")
+ val submissionDate = long("submissionDate").nullable()
val sum = amount("sum")
val currency = varchar("currency", length = 3).default("EUR")
val endToEndId = long("EndToEndId")
@@ -139,25 +137,27 @@ object Pain001Table : 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 Pain001Entity(id: EntityID<String>) : Entity<String>(id) {
- companion object : EntityClass<String, Pain001Entity>(Pain001Table)
- var paymentId by Pain001Table.paymentId
- var date by Pain001Table.fileDate
- var sum by Pain001Table.sum
- var currency by Pain001Table.currency
- var debitorIban by Pain001Table.debitorIban
- var debitorBic by Pain001Table.debitorBic
- var debitorName by Pain001Table.debitorName
- var endToEndId by Pain001Table.endToEndId
- var subject by Pain001Table.subject
- var creditorIban by Pain001Table.creditorIban
- var creditorBic by Pain001Table.creditorBic
- var creditorName by Pain001Table.creditorName
- var submitted by Pain001Table.submitted
- var invalid by Pain001Table.invalid
- var nexusUser by NexusUserEntity referencedOn Pain001Table.nexusUser
+class PreparedPaymentEntity(id: EntityID<String>) : Entity<String>(id) {
+ companion object : EntityClass<String,
PreparedPaymentEntity>(PreparedPaymentsTable)
+ var paymentId by PreparedPaymentsTable.paymentId
+ var preparationDate by PreparedPaymentsTable.preparationDate
+ var submissionDate by PreparedPaymentsTable.submissionDate
+ var sum by PreparedPaymentsTable.sum
+ var currency by PreparedPaymentsTable.currency
+ var debitorIban by PreparedPaymentsTable.debitorIban
+ var debitorBic by PreparedPaymentsTable.debitorBic
+ var debitorName by PreparedPaymentsTable.debitorName
+ var endToEndId by PreparedPaymentsTable.endToEndId
+ var subject by PreparedPaymentsTable.subject
+ var creditorIban by PreparedPaymentsTable.creditorIban
+ var creditorBic by PreparedPaymentsTable.creditorBic
+ var creditorName by PreparedPaymentsTable.creditorName
+ var submitted by PreparedPaymentsTable.submitted
+ var invalid by PreparedPaymentsTable.invalid
+ var nexusUser by NexusUserEntity referencedOn
PreparedPaymentsTable.nexusUser
}
/**
@@ -236,7 +236,7 @@ fun dbCreateTables() {
transaction {
addLogger(StdOutSqlLogger)
SchemaUtils.create(
- Pain001Table,
+ PreparedPaymentsTable,
EbicsSubscribersTable,
BankAccountsTable,
RawBankTransactionsTable,
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt
b/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt
index 638d2fc..2b5eb66 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt
@@ -1,11 +1,9 @@
package tech.libeufin.nexus
-import io.ktor.application.ApplicationCall
import io.ktor.http.HttpStatusCode
import org.jetbrains.exposed.sql.and
import org.jetbrains.exposed.sql.transactions.transaction
import org.joda.time.DateTime
-import org.joda.time.format.DateTimeFormat
import tech.libeufin.util.Amount
import tech.libeufin.util.CryptoUtil
import tech.libeufin.util.EbicsClientSubscriberDetails
@@ -62,23 +60,18 @@ fun extractFirstBic(bankCodes:
List<EbicsTypes.AbstractBankCode>?): String? {
}
/**
- * Given a nexus user id, returns the _list_ of bank accounts associated to it.
- *
- * @param id the subscriber id
- * @return the bank account associated with this user. Can/should be adapted
to
- * return multiple bank accounts.
+ * Retrieve bank account details, only if user owns it.
*/
-fun getBankAccountFromNexusUserId(id: String): BankAccountEntity {
- logger.debug("Looking up bank account of user '$id'")
- val map = transaction {
- BankAccountMapEntity.find {
- BankAccountMapsTable.nexusUser eq id
- }
- }.firstOrNull() ?: throw NexusError(
+fun getBankAccount(userId: String, accountId: String): BankAccountEntity {
+ return transaction {
+ val bankAccountMap = BankAccountMapEntity.find {
+ BankAccountMapsTable.nexusUser eq userId
+ }.firstOrNull() ?: throw NexusError(
HttpStatusCode.NotFound,
- "Such user '$id' does not have any bank account associated"
- )
- return map.bankAccount
+ "Bank account '$accountId' not found"
+ )
+ bankAccountMap.bankAccount
+ }
}
/**
@@ -156,7 +149,7 @@ fun getSubscriberDetailsFromNexusUserId(id: String):
EbicsClientSubscriberDetail
* Create a PAIN.001 XML document according to the input data.
* Needs to be called within a transaction block.
*/
-fun createPain001document(pain001Entity: Pain001Entity): String {
+fun createPain001document(paymentData: PreparedPaymentEntity): String {
/**
* Every PAIN.001 document contains at least three IDs:
*
@@ -172,8 +165,8 @@ fun createPain001document(pain001Entity: Pain001Entity):
String {
*/
val debitorBankAccountLabel = transaction {
val debitorBankAcount = BankAccountEntity.find {
- BankAccountsTable.iban eq pain001Entity.debitorIban and
- (BankAccountsTable.bankCode eq pain001Entity.debitorBic)
+ BankAccountsTable.iban eq paymentData.debitorIban and
+ (BankAccountsTable.bankCode eq paymentData.debitorBic)
}.firstOrNull() ?: throw NexusError(
HttpStatusCode.NotFound,
"Please download bank accounts details first (HTD)"
@@ -189,11 +182,11 @@ fun createPain001document(pain001Entity: Pain001Entity):
String {
element("CstmrCdtTrfInitn") {
element("GrpHdr") {
element("MsgId") {
- text(pain001Entity.id.value.toString())
+ text(paymentData.id.value.toString())
}
element("CreDtTm") {
val dateMillis = transaction {
- pain001Entity.date
+ paymentData.preparationDate
}
val dateFormatter =
DateTimeFormatter.ISO_OFFSET_DATE_TIME
val instant = Instant.ofEpochSecond(dateMillis / 1000)
@@ -204,7 +197,7 @@ fun createPain001document(pain001Entity: Pain001Entity):
String {
text("1")
}
element("CtrlSum") {
- text(pain001Entity.sum.toString())
+ text(paymentData.sum.toString())
}
element("InitgPty/Nm") {
text(debitorBankAccountLabel)
@@ -212,7 +205,7 @@ fun createPain001document(pain001Entity: Pain001Entity):
String {
}
element("PmtInf") {
element("PmtInfId") {
- text(pain001Entity.id.value.toString())
+ text(paymentData.id.value.toString())
}
element("PmtMtd") {
text("TRF")
@@ -224,14 +217,14 @@ fun createPain001document(pain001Entity: Pain001Entity):
String {
text("1")
}
element("CtrlSum") {
- text(pain001Entity.sum.toString())
+ text(paymentData.sum.toString())
}
element("PmtTpInf/SvcLvl/Cd") {
text("SEPA")
}
element("ReqdExctnDt") {
val dateMillis = transaction {
- pain001Entity.date
+ paymentData.preparationDate
}
text(DateTime(dateMillis).toString("Y-MM-dd"))
}
@@ -239,10 +232,10 @@ fun createPain001document(pain001Entity: Pain001Entity):
String {
text(debitorBankAccountLabel)
}
element("DbtrAcct/Id/IBAN") {
- text(pain001Entity.debitorIban)
+ text(paymentData.debitorIban)
}
element("DbtrAgt/FinInstnId/BIC") {
- text(pain001Entity.debitorBic)
+ text(paymentData.debitorBic)
}
element("ChrgBr") {
text("SLEV")
@@ -255,20 +248,20 @@ fun createPain001document(pain001Entity: Pain001Entity):
String {
}
}
element("Amt/InstdAmt") {
- attribute("Ccy", pain001Entity.currency)
- text(pain001Entity.sum.toString())
+ attribute("Ccy", paymentData.currency)
+ text(paymentData.sum.toString())
}
element("CdtrAgt/FinInstnId/BIC") {
- text(pain001Entity.creditorBic)
+ text(paymentData.creditorBic)
}
element("Cdtr/Nm") {
- text(pain001Entity.creditorName)
+ text(paymentData.creditorName)
}
element("CdtrAcct/Id/IBAN") {
- text(pain001Entity.creditorIban)
+ text(paymentData.creditorIban)
}
element("RmtInf/Ustrd") {
- text(pain001Entity.subject)
+ text(paymentData.subject)
}
}
}
@@ -278,6 +271,19 @@ fun createPain001document(pain001Entity: Pain001Entity):
String {
return s
}
+/**
+ * Retrieve prepared payment from database, raising exception
+ * if not found.
+ */
+fun getPreparedPayment(uuid: String): PreparedPaymentEntity {
+ return transaction {
+ PreparedPaymentEntity.findById(uuid)
+ } ?: throw NexusError(
+ HttpStatusCode.NotFound,
+ "Payment '$uuid' not found"
+ )
+}
+
/**
* Insert one row in the database, and leaves it marked as non-submitted.
* @param debtorAccountId the mnemonic id assigned by the bank to one bank
@@ -285,19 +291,20 @@ fun createPain001document(pain001Entity: Pain001Entity):
String {
* it will be the account whose money will pay the wire transfer being defined
* by this pain document.
*/
-fun createPain001entity(entry: Pain001Data, nexusUser: NexusUserEntity):
Pain001Entity {
+fun addPreparedPayment(paymentData: Pain001Data, nexusUser: NexusUserEntity):
PreparedPaymentEntity {
val randomId = Random().nextLong()
return transaction {
- Pain001Entity.new(randomId.toString()) {
- subject = entry.subject
- sum = entry.sum
- debitorIban = entry.debitorIban
- debitorBic = entry.debitorBic
- debitorName = entry.debitorName
- creditorName = entry.creditorName
- creditorBic = entry.creditorBic
- creditorIban = entry.creditorIban
- date = DateTime.now().millis
+ val debitorAccount = getBankAccount(nexusUser.id.value,
paymentData.debitorAccount)
+ PreparedPaymentEntity.new(randomId.toString()) {
+ subject = paymentData.subject
+ sum = paymentData.sum
+ debitorIban = debitorAccount.iban
+ debitorBic = debitorAccount.bankCode
+ debitorName = debitorAccount.accountHolder
+ creditorName = paymentData.creditorName
+ creditorBic = paymentData.creditorBic
+ creditorIban = paymentData.creditorIban
+ preparationDate = DateTime.now().millis
paymentId = randomId
endToEndId = randomId
this.nexusUser = nexusUser
@@ -322,14 +329,6 @@ fun extractNexusUser(param: String?): NexusUserEntity {
}
}
-/* Needs a transaction{} block to be called */
-fun expectAcctidTransaction(param: String?): BankAccountEntity {
- if (param == null) {
- throw NexusError(HttpStatusCode.BadRequest, "Null Acctid given")
- }
- return BankAccountEntity.findById(param) ?: throw
NexusError(HttpStatusCode.NotFound, "Account: $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
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt
b/nexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt
index c6d6f01..ded37a1 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt
@@ -12,9 +12,7 @@ data class NexusErrorJson(
val message: String
)
-data class EbicsStandardOrderParamsJson(
- val dateRange: EbicsDateRangeJson?
-) {
+data class EbicsStandardOrderParamsJson(val dateRange: EbicsDateRangeJson?) {
fun toOrderParams(): EbicsOrderParams {
var dateRange: EbicsDateRange? = if (this.dateRange != null) {
EbicsDateRange(
@@ -29,9 +27,7 @@ data class EbicsStandardOrderParamsJson(
}
data class EbicsDateRangeJson(
- /**
- * ISO 8601 calendar dates: YEAR-MONTH(01-12)-DAY(1-31)
- */
+ /** ISO 8601 calendar dates: YEAR-MONTH(01-12)-DAY(1-31) */
val start: String?,
val end: String?
)
@@ -76,89 +72,139 @@ data class EbicsErrorJson(
val error: EbicsErrorDetailJson
)
-data class BankAccount(
- var holder: String,
- var iban: String,
- var bic: String,
- var account: String
+/** Instructs the nexus to CREATE a new Ebics subscriber.
+ * Note that the nexus user to which the subscriber must be
+ * associated is extracted from other HTTP details.
+ *
+ * This same structure can be user to SHOW one Ebics subscriber
+ * existing at the nexus.
+ */
+data class EbicsSubscriber(
+ val ebicsURL: String,
+ val hostID: String,
+ val partnerID: String,
+ val userID: String,
+ val systemID: String? = null
)
-data class BankAccounts(
- var accounts: MutableList<BankAccount> = mutableListOf()
+data class RawPayments(
+ var payments: MutableList<RawPayment> = mutableListOf()
)
-/** THE NEXUS USER */
+/*************************************************
+ * API types (used as requests/responses types) *
+ *************************************************/
-/** SHOWS details about one user */
-data class NexusUser(
- val userID: String,
- val transports: MutableList<Any> = mutableListOf()
+/** Response type of "GET /prepared-payments/{uuid}" */
+data class PaymentStatus(
+ val uuid: String,
+ val submitted: Boolean,
+ val creditorIban: String,
+ val creditorBic: String,
+ val creditorName: String,
+ val amount: String,
+ val subject: String,
+ val submissionDate: String,
+ val preparationDate: String
+)
+
+/** Response type of "GET /collected-transactions" */
+data class Transaction(
+ val account: String,
+ val counterpartIban: String,
+ val counterpartBic: String,
+ val counterpartName: String,
+ val amount: String,
+ val subject: String,
+ val date: String
)
-/** is "UserResponse" in the API spec */
+data class Transactions(
+ val transactions: MutableList<Transaction> = mutableListOf()
+)
+
+/** Request type of "POST /prepared-payments/submit" */
+data class SubmitPayment(
+ val uuid: String,
+ val transport: String?
+)
+
+/** Request type of "POST /collected-transactions" */
+data class CollectedTransaction(
+ val transport: String?,
+ val start: String?,
+ val end: String?
+)
+
+/** Request type of "POST /prepared-payments" */
+data class PreparedPaymentRequest(
+ val iban: String,
+ val bic: String,
+ val name: String,
+ val amount: String,
+ val subject: String
+)
+
+/** Response type of "POST /prepared-payments" */
+data class PreparedPaymentResponse(
+ val uuid: String
+)
+
+/** Response type of "GET /user" */
data class UserResponse(
val username: String,
val superuser: Boolean
)
-/** Instructs the nexus to CREATE a new user */
+/** Request type of "POST /users" */
data class User(
val username: String,
val password: String
)
-/** Collection of all the nexus users existing in the system */
-data class Users(
- val users: MutableList<NexusUser> = mutableListOf()
+/** Response (list's element) type of "GET /bank-accounts" */
+data class BankAccount(
+ var holder: String,
+ var iban: String,
+ var bic: String,
+ var account: String
)
-/************************************/
-
-/** TRANSPORT TYPES */
-
-/** Instructs the nexus to CREATE a new Ebics subscriber.
- * Note that the nexus user to which the subscriber must be
- * associated is extracted from other HTTP details.
- *
- * This same structure can be user to SHOW one Ebics subscriber
- * existing at the nexus.
- */
-data class EbicsSubscriber(
- val ebicsURL: String,
- val hostID: String,
- val partnerID: String,
- val userID: String,
- val systemID: String? = null
+/** Response type of "GET /bank-accounts" */
+data class BankAccounts(
+ var accounts: MutableList<BankAccount> = mutableListOf()
)
-/** Type representing the "test" transport. Test transport
- * does not cooperate with the bank/sandbox in order to obtain
- * data about one user. All the data is just mocked internally
- * at the NEXUS.
- */
-class TestSubscriber()
-
-/** PAYMENT INSTRUCTIONS TYPES */
+/**********************************************************************
+ * Convenience types (ONLY used to gather data together in one place) *
+ **********************************************************************/
-/** This structure is used to INSTRUCT the nexus to prepare such payment. */
data class Pain001Data(
val creditorIban: String,
val creditorBic: String,
val creditorName: String,
- val debitorIban: String,
- val debitorBic: String,
- val debitorName: String?,
+ val debitorAccount: String,
val sum: Amount,
- val currency: String = "EUR",
+ val currency: String,
val subject: String
)
-data class RawPayments(
- var payments: MutableList<RawPayment> = mutableListOf()
-)
-data class SubmitPayment(
- val uuid: String,
- val transport: String?
-)
\ No newline at end of file
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
index e0d61f2..4a6daf7 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
@@ -50,12 +50,8 @@ import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.slf4j.event.Level
import tech.libeufin.util.*
-import tech.libeufin.util.ebics_h004.HTDResponseOrderData
import java.text.DateFormat
-import java.text.SimpleDateFormat
-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()
@@ -187,27 +183,18 @@ fun main() {
post("/bank-accounts/{accountid}/prepared-payments/submit") {
val userId =
authenticateRequest(call.request.headers["Authorization"])
val body = call.receive<SubmitPayment>()
-
- // 1 find payment.
- val preparedPayment = transaction {
- Pain001Entity.findById(body.uuid)
- } ?: throw NexusError(
- HttpStatusCode.NotFound,
- "Could not find prepared payment: ${body.uuid}"
+ val preparedPayment = getPreparedPayment(body.uuid)
+ if (preparedPayment.nexusUser.id.value != userId) throw
NexusError(
+ HttpStatusCode.Forbidden,
+ "No rights over such payment"
)
-
- // 2 check if was submitted yet
if (preparedPayment.submitted) {
throw NexusError(
HttpStatusCode.PreconditionFailed,
"Payment ${body.uuid} was submitted already"
)
}
-
- // 3 submit
val pain001document = createPain001document(preparedPayment)
-
- // 4 check if the user has a instance in such bank transport.
when (body.transport) {
"ebics" -> {
val subscriberDetails =
getSubscriberDetailsFromNexusUserId(userId)
@@ -221,7 +208,7 @@ fun main() {
)
/** mark payment as 'submitted' */
transaction {
- val payment = Pain001Entity.findById(body.uuid) ?:
throw NexusError(
+ val payment =
PreparedPaymentEntity.findById(body.uuid) ?: throw NexusError(
HttpStatusCode.InternalServerError,
"Severe internal error: could not find payment
in DB after having submitted it to the bank"
)
@@ -232,6 +219,7 @@ fun main() {
ContentType.Text.Plain,
HttpStatusCode.OK
)
+ preparedPayment.submissionDate = DateTime.now().millis
}
else -> throw NexusError(
HttpStatusCode.NotImplemented,
@@ -244,24 +232,153 @@ fun main() {
* Shows information about one particular prepared payment.
*/
get("/bank-accounts/{accountid}/prepared-payments/{uuid}") {
+ val userId =
authenticateRequest(call.request.headers["Authorization"])
+ val preparedPayment =
getPreparedPayment(expectId(call.parameters["uuid"]))
+ if (preparedPayment.nexusUser.id.value != userId) throw
NexusError(
+ HttpStatusCode.Forbidden,
+ "No rights over such payment"
+ )
+ 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()
+ )
+ )
return@get
}
/**
* Adds a new prepared payment.
*/
post("/bank-accounts/{accountid}/prepared-payments") {
+ val userId =
authenticateRequest(call.request.headers["Authorization"])
+ val bankAccount = getBankAccount(userId,
expectId(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)
+ )
+ call.respond(
+ HttpStatusCode.OK,
+ PreparedPaymentResponse(uuid = paymentEntity.id.value)
+ )
return@post
}
/**
* Downloads new transactions from the bank.
*/
post("/bank-accounts/{accountid}/collected-transactions") {
+ val userId =
authenticateRequest(call.request.headers["Authorization"])
+ val body = call.receive<CollectedTransaction>()
+ when (body.transport) {
+ "ebics" -> {
+ val orderParams = EbicsStandardOrderParamsJson(
+ EbicsDateRangeJson(
+ body.start,
+ body.end
+ )
+ ).toOrderParams()
+ val subscriberData =
getSubscriberDetailsFromNexusUserId(userId)
+ when (val response =
doEbicsDownloadTransaction(client, subscriberData, "C53", orderParams)) {
+ is EbicsDownloadSuccessResult -> {
+ /**
+ * The current code is _heavily_ dependent on
the way GLS returns
+ * data. For example, GLS makes one ZIP entry
for each "Ntry" element
+ * (a bank transfer), but per the
specifications one bank can choose to
+ * return all the "Ntry" elements into one
single ZIP entry, or even unzipped
+ * at all.
+ */
+ response.orderData.unzipWithLambda {
+ logger.debug("C53 entry: ${it.second}")
+ val fileName = it.first
+ val camt53doc =
XMLUtil.parseStringIntoDom(it.second)
+ transaction {
+ RawBankTransactionEntity.new {
+ bankAccount =
getBankAccountFromIban(camt53doc.pickString("//*[local-name()='Stmt']/Acct/Id/IBAN"))
+ sourceFileName = fileName
+ unstructuredRemittanceInformation
= camt53doc.pickString("//*[local-name()='Ntry']//*[local-name()='Ustrd']")
+ transactionType =
camt53doc.pickString("//*[local-name()='Ntry']//*[local-name()='CdtDbtInd']")
+ currency =
camt53doc.pickString("//*[local-name()='Ntry']//*[local-name()='Amt']/@Ccy")
+ amount =
camt53doc.pickString("//*[local-name()='Ntry']//*[local-name()='Amt']")
+ status =
camt53doc.pickString("//*[local-name()='Ntry']//*[local-name()='Sts']")
+ bookingDate =
parseDashedDate(camt53doc.pickString("//*[local-name()='BookgDt']//*[local-name()='Dt']")).millis
+ nexusUser =
extractNexusUser(userId)
+ counterpartIban =
camt53doc.pickString("//*[local-name()='${if (this.transactionType == "DBIT")
"CdtrAcct" else "DbtrAcct"}']//*[local-name()='IBAN']")
+ counterpartName =
camt53doc.pickString("//*[local-name()='RltdPties']//*[local-name()='${if
(this.transactionType == "DBIT") "Cdtr" else "Dbtr"}']//*[local-name()='Nm']")
+ counterpartBic =
camt53doc.pickString("//*[local-name()='RltdAgts']//*[local-name()='BIC']")
+ }
+ }
+ }
+ call.respondText(
+ "C53 data persisted into the database
(WIP).",
+ ContentType.Text.Plain,
+ HttpStatusCode.OK
+ )
+ }
+ is EbicsDownloadBankErrorResult -> {
+ call.respond(
+ HttpStatusCode.BadGateway,
+ EbicsErrorJson(
+ EbicsErrorDetailJson(
+ "bankError",
+ response.returnCode.errorCode
+ )
+ )
+ )
+ }
+ }
+ }
+ else -> throw NexusError(
+ HttpStatusCode.NotImplemented,
+ "Bank transport ${body.transport} is not implemented"
+ )
+ }
return@post
}
/**
- * Queries list of transactions ALREADY downloaded from the bank.
+ * Asks list of transactions ALREADY downloaded from the bank.
*/
get("/bank-accounts/{accountid}/collected-transactions") {
+ val userId =
authenticateRequest(call.request.headers["Authorization"])
+ val start = call.request.queryParameters["start"]
+ val end = call.request.queryParameters["end"]
+ val ret = Transactions()
+ transaction {
+ RawBankTransactionEntity.find {
+ RawBankTransactionsTable.nexusUser eq userId and
+ RawBankTransactionsTable.bookingDate.between(
+ parseDashedDate(start ?: "1970-01-01"),
+ parseDashedDate(end ?:
DateTime.now().toDashedDate())
+ )
+ }.forEach {
+ ret.transactions.add(
+ Transaction(
+ account = it.bankAccount.id.value,
+ counterpartBic = it.counterpartBic,
+ counterpartIban = it.counterpartIban,
+ counterpartName = it.counterpartName,
+ date = DateTime(it.bookingDate).toDashedDate(),
+ subject = it.unstructuredRemittanceInformation,
+ amount = "${it.currency}:${it.amount}"
+ )
+ )
+ }
+ }
return@get
}
/**
@@ -284,6 +401,7 @@ fun main() {
post("/bank-transports/{transportName}/sync{MSG}") {
return@post
}
+
/**
* Hello endpoint.
*/
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/MainDeprecated.kt
b/nexus/src/main/kotlin/tech/libeufin/nexus/MainDeprecated.kt
index 547f0b2..f589d76 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/MainDeprecated.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/MainDeprecated.kt
@@ -46,8 +46,6 @@ import kotlinx.io.core.ExperimentalIoApi
import org.jetbrains.exposed.sql.and
import org.jetbrains.exposed.sql.transactions.transaction
import org.joda.time.DateTime
-import org.slf4j.Logger
-import org.slf4j.LoggerFactory
import org.slf4j.event.Level
import tech.libeufin.util.*
import tech.libeufin.util.ebics_h004.HTDResponseOrderData
@@ -258,8 +256,8 @@ fun main() {
BankAccountMapsTable.nexusUser eq nexusUser.id
}
bankAccountsMap.forEach {
- Pain001Entity.find {
- Pain001Table.debitorIban eq it.bankAccount.iban
+ PreparedPaymentEntity.find {
+ PreparedPaymentsTable.debitorIban eq
it.bankAccount.iban
}.forEach {
ret.payments.add(
RawPayment(
@@ -287,7 +285,7 @@ fun main() {
)
}
}
- createPain001entity(pain001data, nexusUser)
+ addPreparedPayment(pain001data, nexusUser)
call.respondText(
"Payment instructions persisted in DB",
ContentType.Text.Plain, HttpStatusCode.OK
@@ -560,8 +558,8 @@ fun main() {
post("/ebics/execute-payments") {
val (paymentRowId, painDoc, subscriber) = transaction {
- val entity = Pain001Entity.find {
- (Pain001Table.submitted eq false) and
(Pain001Table.invalid eq false)
+ val entity = PreparedPaymentEntity.find {
+ (PreparedPaymentsTable.submitted eq false) and
(PreparedPaymentsTable.invalid eq false)
}.firstOrNull() ?: throw
NexusError(HttpStatusCode.Accepted, reason = "No ready payments found")
Triple(entity.id, createPain001document(entity),
entity.nexusUser.ebicsSubscriber)
}
@@ -581,7 +579,7 @@ fun main() {
)
/* flow here == no errors occurred */
transaction {
- val payment = Pain001Entity.findById(paymentRowId) ?:
throw NexusError(
+ val payment = PreparedPaymentEntity.findById(paymentRowId)
?: throw NexusError(
HttpStatusCode.InternalServerError,
"Severe internal error: could not find payment in DB
after having submitted it to the bank"
)
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt
b/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt
index bb16050..8a97107 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt
@@ -90,11 +90,6 @@ class Taler(app: Route) {
val iban: String,
val bic: String = "NOTGIVEN"
)
- data class AmountWithCurrency(
- val currency: String,
- val amount: Amount
- )
-
/**
* Helper functions
*/
@@ -130,12 +125,6 @@ class Taler(app: Route) {
throw NexusError(HttpStatusCode.BadRequest, "invalid payto URI
($paytoUri)")
}
- fun parseAmount(amount: String): AmountWithCurrency {
- val match = Regex("([A-Z]+):([0-9]+(\\.[0-9]+)?)").find(amount) ?:
throw
- NexusError(HttpStatusCode.BadRequest, "invalid payto URI
($amount)")
- val (currency, number) = match.destructured
- return AmountWithCurrency(currency, Amount(number))
- }
/** 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) {
@@ -254,13 +243,14 @@ class Taler(app: Route) {
)
}
}
- val pain001 = createPain001entity(
+ val pain001 = addPreparedPayment(
Pain001Data(
creditorIban = creditorData.iban,
creditorBic = creditorData.bic,
creditorName = creditorData.name,
subject = transferRequest.wtid,
- sum = parseAmount(transferRequest.amount).amount,
+ sum = amountObj.amount,
+ currency = amountObj.currency,
debitorName = exchangeBankAccount.accountHolder,
debitorBic = exchangeBankAccount.bankCode,
debitorIban = exchangeBankAccount.iban
@@ -273,7 +263,7 @@ class Taler(app: Route) {
unstructuredRemittanceInformation =
transferRequest.wtid
transactionType = "DBIT"
currency = amountObj.currency
- amount = amountObj.amount.toPlainString()
+ this.amount = amountObj.amount.toPlainString()
debitorName = "Exchange Company"
debitorIban = exchangeBankAccount.iban
creditorName = creditorObj.name
@@ -330,7 +320,7 @@ class Taler(app: Route) {
currency = amount.currency
this.amount = amount.amount.toPlainString()
creditorIban = exchangeBankAccount.iban
- creditorName = exchangeBankAccount.accountHolder ?:
"Exchange default name for tests"
+ creditorName = exchangeBankAccount.accountHolder
debitorIban = debtor.iban
debitorName = debtor.name
counterpartBic = debtor.bic
@@ -378,7 +368,7 @@ class Taler(app: Route) {
TalerIncomingPaymentEntity.find {
TalerIncomingPayments.refunded eq false and
(TalerIncomingPayments.valid eq false)
}.forEach {
- createPain001entity(
+ addPreparedPayment(
Pain001Data(
creditorName = it.payment.debitorName,
creditorIban = it.payment.debitorIban,
@@ -387,7 +377,8 @@ class Taler(app: Route) {
subject = "Taler refund",
debitorIban = requesterBankAccount.iban,
debitorBic = requesterBankAccount.bankCode,
- debitorName = requesterBankAccount.accountHolder
+ debitorName = requesterBankAccount.accountHolder,
+ currency = it.payment.currency
),
nexusUser
)
diff --git a/nexus/src/test/kotlin/PainGeneration.kt
b/nexus/src/test/kotlin/PainGeneration.kt
index 4eef6d8..e257c50 100644
--- a/nexus/src/test/kotlin/PainGeneration.kt
+++ b/nexus/src/test/kotlin/PainGeneration.kt
@@ -14,14 +14,14 @@ class PainTest {
Database.connect("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1", driver =
"org.h2.Driver")
transaction {
SchemaUtils.create(BankAccountsTable)
- SchemaUtils.create(Pain001Table)
+ SchemaUtils.create(PreparedPaymentsTable)
SchemaUtils.create(NexusUsersTable)
BankAccountEntity.new(id = "acctid") {
accountHolder = "Account Holder"
iban = "DEBIT IBAN"
bankCode = "DEBIT BIC"
}
- Pain001Entity.new {
+ PreparedPaymentEntity.new {
sum = Amount(1)
debitorIban = "DEBIT IBAN"
debitorBic = "DEBIT BIC"
@@ -42,7 +42,7 @@ class PainTest {
@Test
fun testPain001document() {
transaction {
- val s = createPain001document(Pain001Entity.all().first())
+ val s = createPain001document(PreparedPaymentEntity.all().first())
println(s)
}
}
diff --git a/util/src/main/kotlin/strings.kt b/util/src/main/kotlin/strings.kt
index 68cfff3..f8b0c21 100644
--- a/util/src/main/kotlin/strings.kt
+++ b/util/src/main/kotlin/strings.kt
@@ -1,5 +1,6 @@
package tech.libeufin.util
+import io.ktor.http.HttpStatusCode
import java.math.BigInteger
import java.util.*
@@ -69,4 +70,16 @@ fun chunkString(input: String): String {
ret.append(input[i])
}
return ret.toString().toUpperCase()
+}
+
+data class AmountWithCurrency(
+ val currency: String,
+ val amount: Amount
+)
+
+fun parseAmount(amount: String): AmountWithCurrency {
+ val match = Regex("([A-Z]+):([0-9]+(\\.[0-9]+)?)").find(amount) ?: throw
+ UtilError(HttpStatusCode.BadRequest, "invalid payto URI ($amount)")
+ val (currency, number) = match.destructured
+ return AmountWithCurrency(currency, Amount(number))
}
\ No newline at end of file
--
To stop receiving notification emails like this one, please contact
address@hidden.
- [libeufin] branch master updated (f73bdad -> 7033a39),
gnunet <=
- [libeufin] 02/05: POST ../collected-transactions, gnunet, 2020/05/09
- [libeufin] 03/05: Organize JSON types., gnunet, 2020/05/09
- [libeufin] 05/05: POST ../collected-transactions, gnunet, 2020/05/09
- [libeufin] 04/05: API migration, gnunet, 2020/05/09
- [libeufin] 01/05: POST ../prepared-payments, gnunet, 2020/05/09