[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[libeufin] 02/05: DB definitions for payments ordered by the exchange.
From: |
gnunet |
Subject: |
[libeufin] 02/05: DB definitions for payments ordered by the exchange. |
Date: |
Fri, 10 Apr 2020 00:17:29 +0200 |
This is an automated email from the git hooks/post-receive script.
marcello pushed a commit to branch master
in repository libeufin.
commit 95994e9af317eb115a64341e179c76772afc3055
Author: Marcello Stanisci <address@hidden>
AuthorDate: Thu Apr 9 18:39:48 2020 +0200
DB definitions for payments ordered by the exchange.
---
nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt | 52 +++++++++++++++++---
nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt | 8 +--
nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt | 57 +++++++++++++---------
3 files changed, 83 insertions(+), 34 deletions(-)
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt
b/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt
index df84540..1311dca 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt
@@ -10,6 +10,33 @@ 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.
+ */
+object TalerRequestedPayments: LongIdTable() {
+ val payment = 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) {
+ var payment by Pain001Entity referencedOn TalerRequestedPayments.payment
+ 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")
@@ -17,10 +44,15 @@ object TalerIncomingPayments: LongIdTable() {
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,7 +61,7 @@ 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 refunded by TalerIncomingPayments.refunded
}
@@ -59,8 +91,8 @@ object EbicsRawBankTransactionsTable : LongIdTable() {
val bookingDate = text("bookingDate")
}
-class EbicsRawBankTransactionEntry(id: EntityID<Long>) : LongEntity(id) {
- companion object :
LongEntityClass<EbicsRawBankTransactionEntry>(EbicsRawBankTransactionsTable)
+class EbicsRawBankTransactionEntity(id: EntityID<Long>) : LongEntity(id) {
+ companion object :
LongEntityClass<EbicsRawBankTransactionEntity>(EbicsRawBankTransactionsTable)
var sourceType by EbicsRawBankTransactionsTable.sourceType // C52 or C53
or C54?
var sourceFileName by EbicsRawBankTransactionsTable.sourceFileName
var unstructuredRemittanceInformation by
EbicsRawBankTransactionsTable.unstructuredRemittanceInformation
@@ -76,6 +108,12 @@ class EbicsRawBankTransactionEntry(id: EntityID<Long>) :
LongEntity(id) {
var nexusSubscriber by EbicsSubscriberEntity referencedOn
EbicsRawBankTransactionsTable.nexusSubscriber
}
+/**
+ * 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")
@@ -93,7 +131,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)
}
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
index 652cc3c..3a97db5 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
@@ -314,7 +314,7 @@ fun createPain001document(pain001Entity: Pain001Entity):
String {
/**
* Insert one row in the database, and leaves it marked as non-submitted.
*/
-fun createPain001entry(entry: Pain001Data, debtorAccountId: String) {
+fun createPain001entity(entry: Pain001Data, debtorAccountId: String) {
val randomId = Random().nextLong()
transaction {
Pain001Entity.new {
@@ -483,7 +483,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
@@ -630,7 +630,7 @@ fun main() {
var ret = ""
transaction {
val subscriber: EbicsSubscriberEntity =
getSubscriberEntityFromId(id)
- EbicsRawBankTransactionEntry.find {
+ EbicsRawBankTransactionEntity.find {
(EbicsRawBankTransactionsTable.nexusSubscriber eq
subscriber.id.value) and
(EbicsRawBankTransactionsTable.sourceType eq
"C53")
}.forEach {
@@ -668,7 +668,7 @@ fun main() {
val fileName = it.first
val camt53doc =
XMLUtil.parseStringIntoDom(it.second)
transaction {
- EbicsRawBankTransactionEntry.new {
+ EbicsRawBankTransactionEntity.new {
sourceType = "C53"
sourceFileName = fileName
unstructuredRemittanceInformation =
camt53doc.pickString("//*[local-name()='Ntry']//*[local-name()='Amt']/@Ccy")
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt
b/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt
index 5347c78..d44c027 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt
@@ -17,7 +17,6 @@ import org.joda.time.format.DateTimeFormat
import tech.libeufin.util.Amount
import tech.libeufin.util.CryptoUtil
import tech.libeufin.util.toZonedString
-import java.util.*
import kotlin.math.abs
class Taler(app: Route) {
@@ -79,7 +78,9 @@ class Taler(app: Route) {
val row_id: Long
)
- /** Helper data structures. */
+ /**
+ * Helper data structures.
+ */
data class Payto(
val name: String,
val iban: String,
@@ -90,7 +91,9 @@ class Taler(app: Route) {
val amount: Amount
)
- /** Helper functions */
+ /**
+ * Helper functions
+ */
fun parsePayto(paytoUri: String): Payto {
// payto://iban/BIC?/IBAN?name=<name>
@@ -106,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 }
@@ -120,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 {
@@ -135,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) {
/**
@@ -158,16 +157,15 @@ class Taler(app: Route) {
/** attaches Taler endpoints to the main Web server */
init {
+ /** 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>()
val debtor = parsePayto(addIncomingData.debit_account)
val amount = parseAmount(addIncomingData.amount)
-
- /** Decompose amount and payto fields. */
val (bookingDate, opaque_row_id) = transaction {
val exchangeBankAccount =
getBankAccountsInfoFromId(exchangeId).first()
- val rawPayment = EbicsRawBankTransactionEntry.new {
+ val rawPayment = EbicsRawBankTransactionEntity.new {
sourceFileName = "test"
sourceType = "C53"
unstructuredRemittanceInformation =
addIncomingData.reserve_pub
@@ -183,7 +181,7 @@ class Taler(app: Route) {
}
/** This payment is "valid by default" and will be returned
* as soon as the exchange will ask for new payments. */
- val row = TalerIncomingPaymentEntry.new {
+ val row = TalerIncomingPaymentEntity.new {
payment = rawPayment
}
Pair(rawPayment.bookingDate, row.id.value)
@@ -194,6 +192,11 @@ class Taler(app: Route) {
))
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"])
@@ -204,10 +207,10 @@ class Taler(app: Route) {
"Such subscriber (${subscriber.id}) can't drive such
account (${acctid.id})"
)
}
- TalerIncomingPaymentEntry.find {
+ TalerIncomingPaymentEntity.find {
TalerIncomingPayments.refunded eq false and
(TalerIncomingPayments.valid eq false)
}.forEach {
- createPain001entry(
+ createPain001entity(
Pain001Data(
creditorName = it.payment.debitorName,
creditorIban = it.payment.debitorIban,
@@ -222,6 +225,11 @@ class Taler(app: Route) {
}
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.) */
app.post("/ebics/taler/{id}/digest-incoming-transactions") {
val id = expectId(call.parameters["id"])
// first find highest ID value of already processed rows.
@@ -235,22 +243,22 @@ class Taler(app: Route) {
* 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_.
*/
- 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
}
@@ -264,6 +272,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"])
@@ -275,7 +285,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(
@@ -297,6 +307,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"))
@@ -305,7 +316,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(
--
To stop receiving notification emails like this one, please contact
address@hidden.
- [libeufin] branch master updated (107a5bc -> f54438f), gnunet, 2020/04/09
- [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 <=
- [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