[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[libeufin] branch master updated (107a5bc -> f54438f)
From: |
gnunet |
Subject: |
[libeufin] branch master updated (107a5bc -> f54438f) |
Date: |
Fri, 10 Apr 2020 00:17:27 +0200 |
This is an automated email from the git hooks/post-receive script.
marcello pushed a change to branch master
in repository libeufin.
from 107a5bc testing payto/amount parsers
new b37353d Completing admin add-incoming.
new 95994e9 DB definitions for payments ordered by the exchange.
new 0169677 Create main/rough logic for Taler-started transfers.
new 96950a3 Store bank-provided IDs, and transaction statuses.
new f54438f propagate last change
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:
cli/python/libeufin-cli | 4 +-
nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt | 82 +++++++---
nexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt | 1 +
nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt | 29 ++--
nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt | 165 +++++++++++++++++----
util/src/main/kotlin/XMLUtil.kt | 9 ++
6 files changed, 229 insertions(+), 61 deletions(-)
diff --git a/cli/python/libeufin-cli b/cli/python/libeufin-cli
index 10e8b32..221d0d5 100755
--- a/cli/python/libeufin-cli
+++ b/cli/python/libeufin-cli
@@ -465,8 +465,8 @@ def refund(ctx, account_id, bank_account_id,
nexus_base_url):
@click.argument(
"nexus-base-url"
)
-def digest_transactions(obj, account_id, nexus_base_url):
- url = urljoin(nexus_base_url,
"/ebics/subscribers/{}/digest-incoming-transactions".format(account_id))
+def crunch_transactions(obj, account_id, nexus_base_url):
+ url = urljoin(nexus_base_url,
"/ebics/subscribers/{}/crunch-incoming-transactions".format(account_id))
resp = post(url, json=dict())
print(resp.content.decode("utf-8"))
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt
b/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt
index c0f8ec9..26c20cf 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt
@@ -10,17 +10,52 @@ import java.sql.Connection
const val ID_MAX_LENGTH = 50
+/**
+ * This table holds the values that exchange gave to issue a payment,
+ * plus a reference to the prepared pain.001 version of. Note that
+ * whether a pain.001 document was sent or not to the bank is indicated
+ * in the PAIN-table.
+ */
+object TalerRequestedPayments: LongIdTable() {
+ val preparedPayment = TalerIncomingPayments.reference("payment",
Pain001Table)
+ val requestUId = text("request_uid")
+ val amount = text("amount")
+ val exchangeBaseUrl = text("exchange_base_url")
+ val wtid = text("wtid")
+ val creditAccount = text("credit_account")
+}
+
+class TalerRequestedPaymentEntity(id: EntityID<Long>) : LongEntity(id) {
+ companion object :
LongEntityClass<TalerRequestedPaymentEntity>(TalerIncomingPayments)
+ var preparedPayment by Pain001Entity referencedOn
TalerRequestedPayments.preparedPayment
+ var requestUId by TalerRequestedPayments.requestUId
+ var amount by TalerRequestedPayments.amount
+ var exchangeBaseUrl by TalerRequestedPayments.exchangeBaseUrl
+ var wtid by TalerRequestedPayments.wtid
+ var creditAccount by TalerRequestedPayments.creditAccount
+}
+
+/**
+ * This table "augments" the information given in the raw payments table, with
Taler-related
+ * ones. It tells if a payment is valid and/or it was refunded already. And
moreover, it is
+ * the table whose ("clean") IDs the exchange will base its history requests
on.
+ */
object TalerIncomingPayments: LongIdTable() {
val payment = reference("payment", EbicsRawBankTransactionsTable)
val valid = bool("valid")
// avoid refunding twice!
- val processed = bool("refunded").default(false)
+ val refunded = bool("refunded").default(false)
}
-class TalerIncomingPaymentEntry(id: EntityID<Long>) : LongEntity(id) {
- companion object :
LongEntityClass<TalerIncomingPaymentEntry>(TalerIncomingPayments) {
- override fun new(init: TalerIncomingPaymentEntry.() -> Unit):
TalerIncomingPaymentEntry {
+class TalerIncomingPaymentEntity(id: EntityID<Long>) : LongEntity(id) {
+ companion object :
LongEntityClass<TalerIncomingPaymentEntity>(TalerIncomingPayments) {
+ override fun new(init: TalerIncomingPaymentEntity.() -> Unit):
TalerIncomingPaymentEntity {
val newRow = super.new(init)
+ /**
+ * In case the exchange asks for all the values strictly lesser
than MAX_VALUE,
+ * it would lose the row whose id == MAX_VALUE. So the check
below makes this
+ * situation impossible by disallowing MAX_VALUE as a id value.
+ */
if (newRow.id.value == Long.MAX_VALUE) {
throw NexusError(
HttpStatusCode.InsufficientStorage, "Cannot store rows
anymore"
@@ -29,21 +64,21 @@ class TalerIncomingPaymentEntry(id: EntityID<Long>) :
LongEntity(id) {
return newRow
}
}
- var payment by EbicsRawBankTransactionEntry referencedOn
TalerIncomingPayments.payment
+ var payment by EbicsRawBankTransactionEntity referencedOn
TalerIncomingPayments.payment
var valid by TalerIncomingPayments.valid
- var processed by TalerIncomingPayments.processed
+ var refunded by TalerIncomingPayments.refunded
}
+/**
+ * This table _assumes_ that all the entries have a BOOK status. The
+ * current code however does only enforces this trusting the C53 response,
+ * but never actually checking the appropriate "Sts" field.
+ */
object EbicsRawBankTransactionsTable : LongIdTable() {
val nexusSubscriber = reference("subscriber", EbicsSubscribersTable)
- // How did we learn about this transaction? C52 / C53 / C54
- val sourceType = text("sourceType")
- // Name of the ZIP entry
- val sourceFileName = text("sourceFileName")
- // "Subject" of the SEPA transaction
+ val sourceFileName = text("sourceFileName") /* ZIP entry's name */
val unstructuredRemittanceInformation =
text("unstructuredRemittanceInformation")
- // Debit or credit
- val transactionType = text("transactionType")
+ val transactionType = text("transactionType") /* DBIT or CRDT */
val currency = text("currency")
val amount = text("amount")
val creditorIban = text("creditorIban")
@@ -52,11 +87,13 @@ object EbicsRawBankTransactionsTable : LongIdTable() {
val debitorName = text("debitorName")
val counterpartBic = text("counterpartBic")
val bookingDate = text("bookingDate")
+ val status = text("status") // BOOK, ..
+ val servicerCode = text("servicerCode").nullable() /* "internal" code
given by the bank */
+ val proprietaryCode = text("proprietaryCode") /* code given by the DK */
}
-class EbicsRawBankTransactionEntry(id: EntityID<Long>) : LongEntity(id) {
- companion object :
LongEntityClass<EbicsRawBankTransactionEntry>(EbicsRawBankTransactionsTable)
- var sourceType by EbicsRawBankTransactionsTable.sourceType // C52 or C53
or C54?
+class EbicsRawBankTransactionEntity(id: EntityID<Long>) : LongEntity(id) {
+ companion object :
LongEntityClass<EbicsRawBankTransactionEntity>(EbicsRawBankTransactionsTable)
var sourceFileName by EbicsRawBankTransactionsTable.sourceFileName
var unstructuredRemittanceInformation by
EbicsRawBankTransactionsTable.unstructuredRemittanceInformation
var transactionType by EbicsRawBankTransactionsTable.transactionType
@@ -69,13 +106,23 @@ class EbicsRawBankTransactionEntry(id: EntityID<Long>) :
LongEntity(id) {
var counterpartBic by EbicsRawBankTransactionsTable.counterpartBic
var bookingDate by EbicsRawBankTransactionsTable.bookingDate
var nexusSubscriber by EbicsSubscriberEntity referencedOn
EbicsRawBankTransactionsTable.nexusSubscriber
+ var status by EbicsRawBankTransactionsTable.status
+ var servicerCode by EbicsRawBankTransactionsTable.servicerCode
+ var proprietaryCode by EbicsRawBankTransactionsTable.proprietaryCode
}
+/**
+ * NOTE: every column in this table corresponds to a particular
+ * value described in the pain.001 official documentation; therefore
+ * this table is not really suitable to hold custom data (like Taler-related,
+ * for example)
+ */
object Pain001Table : IntIdTableWithAmount() {
val msgId = long("msgId").uniqueIndex().autoIncrement()
val paymentId = long("paymentId")
val fileDate = long("fileDate")
val sum = amount("sum")
+ val currency = varchar("currency", length = 3).default("EUR")
val debtorAccount = text("debtorAccount")
val endToEndId = long("EndToEndId")
val subject = text("subject")
@@ -88,7 +135,7 @@ object Pain001Table : IntIdTableWithAmount() {
/* Indicates whether the bank didn't perform the payment: note that
* this state can be reached when the payment gets listed in a CRZ
- * response OR when the payment doesn's show up in a C52/C53 response
+ * response OR when the payment doesn't show up in a C52/C53 response
*/
val invalid = bool("invalid").default(false)
}
@@ -99,6 +146,7 @@ class Pain001Entity(id: EntityID<Int>) : IntEntity(id) {
var paymentId by Pain001Table.paymentId
var date by Pain001Table.fileDate
var sum by Pain001Table.sum
+ var currency by Pain001Table.currency
var debtorAccount by Pain001Table.debtorAccount
var endToEndId by Pain001Table.endToEndId
var subject by Pain001Table.subject
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt
b/nexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt
index a4774a9..7d7eb28 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt
@@ -91,6 +91,7 @@ data class Pain001Data(
val creditorBic: String,
val creditorName: String,
val sum: Amount,
+ val currency: String = "EUR",
val subject: String
)
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
index ae80bef..9d334ec 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
@@ -142,7 +142,8 @@ fun getSubscriberDetailsFromBankAccount(bankAccountId:
String): EbicsClientSubsc
/**
* Given a subscriber id, returns the _list_ of bank accounts associated to it.
* @param id the subscriber id
- * @return the query set containing the subscriber's bank accounts
+ * @return the query set containing the subscriber's bank accounts. The result
+ * is guaranteed to be non empty.
*/
fun getBankAccountsInfoFromId(id: String):
SizedIterable<EbicsAccountInfoEntity> {
val list = transaction {
@@ -287,7 +288,7 @@ fun createPain001document(pain001Entity: Pain001Entity):
String {
}
}
element("Amt/InstdAmt") {
- attribute("Ccy", "EUR")
+ attribute("Ccy", pain001Entity.currency)
text(pain001Entity.sum.toString())
}
element("CdtrAgt/FinInstnId/BIC") {
@@ -312,10 +313,14 @@ fun createPain001document(pain001Entity: Pain001Entity):
String {
/**
* 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
+ * account of the subscriber that is creating the pain entity. In this case,
+ * it will be the account whose money will pay the wire transfer being defined
+ * by this pain document.
*/
-fun createPain001entry(entry: Pain001Data, debtorAccountId: String) {
+fun createPain001entity(entry: Pain001Data, debtorAccountId: String):
Pain001Entity {
val randomId = Random().nextLong()
- transaction {
+ return transaction {
Pain001Entity.new {
subject = entry.subject
sum = entry.sum
@@ -482,7 +487,7 @@ fun main() {
accountInfo.id.value
}
val pain001data = call.receive<Pain001Data>()
- createPain001entry(pain001data, acctid)
+ createPain001entity(pain001data, acctid)
call.respondText(
"Payment instructions persisted in DB",
ContentType.Text.Plain, HttpStatusCode.OK
@@ -629,9 +634,8 @@ fun main() {
var ret = ""
transaction {
val subscriber: EbicsSubscriberEntity =
getSubscriberEntityFromId(id)
- EbicsRawBankTransactionEntry.find {
- (EbicsRawBankTransactionsTable.nexusSubscriber eq
subscriber.id.value) and
- (EbicsRawBankTransactionsTable.sourceType eq
"C53")
+ EbicsRawBankTransactionEntity.find {
+ EbicsRawBankTransactionsTable.nexusSubscriber eq
subscriber.id.value
}.forEach {
ret += "###\nDebitor: ${it.debitorIban}\nCreditor:
${it.creditorIban}\nAmount: ${it.currency}:${it.amount}\nDate:
${it.bookingDate}\n"
}
@@ -653,8 +657,7 @@ fun main() {
val paramsJson = call.receive<EbicsStandardOrderParamsJson>()
val orderParams = paramsJson.toOrderParams()
val subscriberData = getSubscriberDetailsFromId(id)
- val response = doEbicsDownloadTransaction(client,
subscriberData, "C53", orderParams)
- when (response) {
+ when (val response = doEbicsDownloadTransaction(client,
subscriberData, "C53", orderParams)) {
is EbicsDownloadSuccessResult -> {
/**
* The current code is _heavily_ dependent on the way
GLS returns
@@ -667,13 +670,15 @@ fun main() {
val fileName = it.first
val camt53doc =
XMLUtil.parseStringIntoDom(it.second)
transaction {
- EbicsRawBankTransactionEntry.new {
- sourceType = "C53"
+ EbicsRawBankTransactionEntity.new {
sourceFileName = fileName
unstructuredRemittanceInformation =
camt53doc.pickString("//*[local-name()='Ntry']//*[local-name()='Amt']/@Ccy")
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']")
+ servicerCode =
camt53doc.pickStringNullable("//*[local-name()='Ntry']//*[local-name()='AcctSvcrRef']")
+ proprietaryCode =
camt53doc.pickString("//*[local-name()='Ntry']//*[local-name()='BkTxCd']/*[local-name()='Prtry']/*[local-name()='Cd']")
bookingDate =
camt53doc.pickString("//*[local-name()='BookgDt']//*[local-name()='Dt']")
nexusSubscriber =
getSubscriberEntityFromId(id)
creditorName =
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt
b/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt
index 2d9f338..ab448ec 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt
@@ -16,7 +16,7 @@ import org.joda.time.DateTime
import org.joda.time.format.DateTimeFormat
import tech.libeufin.util.Amount
import tech.libeufin.util.CryptoUtil
-import java.util.*
+import tech.libeufin.util.toZonedString
import kotlin.math.abs
class Taler(app: Route) {
@@ -64,6 +64,12 @@ class Taler(app: Route) {
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
)
@@ -72,18 +78,22 @@ class Taler(app: Route) {
val row_id: Long
)
- /** Helper data structures. */
+ /**
+ * Helper data structures.
+ */
data class Payto(
- val name: String,
+ val name: String = "NOTGIVEN",
val iban: String,
- val bic: String // empty string in case no BIC was given
+ val bic: String = "NOTGIVEN"
)
data class AmountWithCurrency(
val currency: String,
val amount: Amount
)
- /** Helper functions */
+ /**
+ * Helper functions
+ */
fun parsePayto(paytoUri: String): Payto {
// payto://iban/BIC?/IBAN?name=<name>
@@ -99,7 +109,7 @@ class Taler(app: Route) {
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) {
this.sortedByDescending { it.id }
@@ -113,10 +123,8 @@ class Taler(app: Route) {
private fun parseDate(date: String): DateTime {
return DateTime.parse(date, DateTimeFormat.forPattern("YYYY-MM-DD"))
}
- /**
- * Builds the comparison operator for history entries based on the
- * sign of 'delta'
- */
+
+ /** Builds the comparison operator for history entries based on the sign
of 'delta' */
private fun getComparisonOperator(delta: Int, start: Long): Op<Boolean> {
return if (delta < 0) {
Expression.build {
@@ -128,9 +136,7 @@ class Taler(app: Route) {
}
}
}
- /**
- * Helper handling 'start' being optional and its dependence on 'delta'.
- */
+ /** Helper handling 'start' being optional and its dependence on 'delta'.
*/
private fun handleStartArgument(start: String?, delta: Int): Long {
return expectLong(start) ?: if (delta >= 0) {
/**
@@ -151,14 +157,105 @@ class Taler(app: Route) {
/** attaches Taler endpoints to the main Web server */
init {
+ app.post("/taler/transfer") {
+ val exchangeId =
authenticateRequest(call.request.headers["Authorization"])
+ val transferRequest = call.receive<TalerTransferRequest>()
+
+ /**
+ * FIXME: check the UID before putting new data into the database.
+ */
+ val opaque_row_id = transaction {
+ val creditorData = parsePayto(transferRequest.credit_account)
+ val exchangeBankAccount = getBankAccountsInfoFromId(exchangeId)
+ val pain001 = createPain001entity(
+ Pain001Data(
+ creditorIban = creditorData.iban,
+ creditorBic = creditorData.bic,
+ creditorName = creditorData.name,
+ subject = transferRequest.wtid,
+ sum = parseAmount(transferRequest.amount).amount
+ ),
+ exchangeBankAccount.first().id.value
+ )
+ 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}) belong
to a different payment altrady"
+ )
+ }
+ }
+ val row = TalerRequestedPaymentEntity.new {
+ preparedPayment = pain001
+ exchangeBaseUrl = transferRequest.exchange_base_url
+ requestUId = transferRequest.request_uid
+ amount = transferRequest.amount
+ wtid = transferRequest.wtid
+ creditAccount = transferRequest.credit_account
+ }
+ row.id.value
+ }
+ call.respond(
+ HttpStatusCode.OK,
+ TalerTransferResponse(
+ /**
+ * Normally should point to the next round where the
background
+ * routing will sent new PAIN.001 data to the bank; work
in progress..
+ */
+ timestamp = DateTime.now().millis / 1000,
+ row_id = opaque_row_id
+ )
+ )
+ return@post
+ }
+ /** Test-API that creates one new payment addressed to the exchange.
*/
app.post("/taler/admin/add-incoming") {
+ val exchangeId =
authenticateRequest(call.request.headers["Authorization"])
val addIncomingData = call.receive<TalerAdminAddIncoming>()
- /** Decompose amount and payto fields. */
-
-
- call.respond(HttpStatusCode.OK, NexusErrorJson("Not implemented"))
+ val debtor = parsePayto(addIncomingData.debit_account)
+ val amount = parseAmount(addIncomingData.amount)
+ val (bookingDate, opaque_row_id) = transaction {
+ val exchangeBankAccount =
getBankAccountsInfoFromId(exchangeId).first()
+ val rawPayment = EbicsRawBankTransactionEntity.new {
+ sourceFileName = "test"
+ unstructuredRemittanceInformation =
addIncomingData.reserve_pub
+ transactionType = "CRDT"
+ currency = amount.currency
+ this.amount = amount.amount.toPlainString()
+ creditorIban = exchangeBankAccount.iban
+ creditorName = "Exchange's company name"
+ debitorIban = debtor.iban
+ debitorName = debtor.name
+ counterpartBic = debtor.bic
+ bookingDate = DateTime.now().toZonedString()
+ status = "BOOK"
+ servicerCode = "test-0"
+ proprietaryCode = "test-0"
+ }
+ /** 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
+ }
+ Pair(rawPayment.bookingDate, row.id.value)
+ }
+ call.respond(HttpStatusCode.OK, TalerAddIncomingResponse(
+ timestamp = parseDate(bookingDate).millis / 1000,
+ row_id = opaque_row_id
+ ))
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") {
transaction {
val subscriber = expectIdTransaction(call.parameters["id"])
@@ -169,10 +266,10 @@ class Taler(app: Route) {
"Such subscriber (${subscriber.id}) can't drive such
account (${acctid.id})"
)
}
- TalerIncomingPaymentEntry.find {
- TalerIncomingPayments.processed eq false and
(TalerIncomingPayments.valid eq false)
+ TalerIncomingPaymentEntity.find {
+ TalerIncomingPayments.refunded eq false and
(TalerIncomingPayments.valid eq false)
}.forEach {
- createPain001entry(
+ createPain001entity(
Pain001Data(
creditorName = it.payment.debitorName,
creditorIban = it.payment.debitorIban,
@@ -182,12 +279,17 @@ class Taler(app: Route) {
),
acctid.id.value
)
- it.processed = true
+ it.refunded = true
}
}
return@post
}
- app.post("/ebics/taler/{id}/digest-incoming-transactions") {
+
+ /** 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.) */
+ app.post("/ebics/taler/{id}/crunch-incoming-transactions") {
val id = expectId(call.parameters["id"])
// first find highest ID value of already processed rows.
transaction {
@@ -197,25 +299,25 @@ class Taler(app: Route) {
* information.
*
* This latestId value points at the latest id in the _raw
transactions table_
- * that was last processed. On the other hand, the "row_id"
value that the exchange
- * will get along each history element will be the id in the
_digested entries table_.
+ * that was last processed here. Note, the "row_id" value
that the exchange
+ * will get along each history element will be the id in the
_crunched entries table_.
*/
- val latestId: Long =
TalerIncomingPaymentEntry.all().sortedByDescending {
+ val latestId: Long =
TalerIncomingPaymentEntity.all().sortedByDescending {
it.payment.id
}.firstOrNull()?.payment?.id?.value ?: -1
val subscriberAccount = getBankAccountsInfoFromId(id).first()
/* search for fresh transactions having the exchange IBAN in
the creditor field. */
- EbicsRawBankTransactionEntry.find {
+ EbicsRawBankTransactionEntity.find {
EbicsRawBankTransactionsTable.creditorIban eq
subscriberAccount.iban and
(EbicsRawBankTransactionsTable.id.greater(latestId))
}.forEach {
if
(CryptoUtil.checkValidEddsaPublicKey(it.unstructuredRemittanceInformation)) {
- TalerIncomingPaymentEntry.new {
+ TalerIncomingPaymentEntity.new {
payment = it
valid = true
}
} else {
- TalerIncomingPaymentEntry.new {
+ TalerIncomingPaymentEntity.new {
payment = it
valid = false
}
@@ -229,6 +331,8 @@ class Taler(app: Route) {
)
return@post
}
+ /** Responds only with the payments that the EXCHANGE made. Typically
to
+ * merchants but possibly to refund invalid incoming payments. */
app.get("/taler/history/outgoing") {
/* sanitize URL arguments */
val subscriberId =
authenticateRequest(call.request.headers["Authorization"])
@@ -240,7 +344,7 @@ class Taler(app: Route) {
transaction {
/** Retrieve all the outgoing payments from the _raw
transactions table_ */
val subscriberBankAccount =
getBankAccountsInfoFromId(subscriberId)
- EbicsRawBankTransactionEntry.find {
+ EbicsRawBankTransactionEntity.find {
EbicsRawBankTransactionsTable.debitorIban eq
subscriberBankAccount.first().iban and startCmpOp
}.orderTaler(delta).subList(0, abs(delta)).forEach {
history.outgoing_transactions.add(
@@ -262,6 +366,7 @@ class Taler(app: Route) {
)
return@get
}
+ /** Responds only with the valid incoming payments */
app.get("/taler/history/incoming") {
val subscriberId =
authenticateRequest(call.request.headers["Authorization"])
val delta: Int = expectInt(call.expectUrlParameter("delta"))
@@ -270,7 +375,7 @@ class Taler(app: Route) {
val startCmpOp = getComparisonOperator(delta, start)
transaction {
val subscriberBankAccount =
getBankAccountsInfoFromId(subscriberId)
- TalerIncomingPaymentEntry.find {
+ TalerIncomingPaymentEntity.find {
TalerIncomingPayments.valid eq true and startCmpOp
}.orderTaler(delta).subList(0, abs(delta)).forEach {
history.incoming_transactions.add(
diff --git a/util/src/main/kotlin/XMLUtil.kt b/util/src/main/kotlin/XMLUtil.kt
index 2889a80..eb1f6f7 100644
--- a/util/src/main/kotlin/XMLUtil.kt
+++ b/util/src/main/kotlin/XMLUtil.kt
@@ -427,3 +427,12 @@ class XMLUtil private constructor() {
fun Document.pickString(xpath: String): String {
return XMLUtil.getStringFromXpath(this, xpath)
}
+
+fun Document.pickStringNullable(xpath: String): String? {
+ return try {
+ XMLUtil.getStringFromXpath(this, xpath)
+ } catch (e: UtilError) {
+ logger.info("Tolerating missing value: $xpath")
+ null
+ }
+}
\ No newline at end of file
--
To stop receiving notification emails like this one, please contact
address@hidden.
- [libeufin] branch master updated (107a5bc -> f54438f),
gnunet <=
- [libeufin] 03/05: Create main/rough logic for Taler-started transfers., gnunet, 2020/04/09
- [libeufin] 02/05: DB definitions for payments ordered by the exchange., gnunet, 2020/04/09
- [libeufin] 01/05: Completing admin add-incoming., gnunet, 2020/04/09
- [libeufin] 04/05: Store bank-provided IDs, and transaction statuses., gnunet, 2020/04/09
- [libeufin] 05/05: propagate last change, gnunet, 2020/04/09