TalerMessage.kt (16513B)
1 /* 2 * This file is part of LibEuFin. 3 * Copyright (C) 2024, 2025, 2026 Taler Systems S.A. 4 * 5 * LibEuFin is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU Affero General Public License as 7 * published by the Free Software Foundation; either version 3, or 8 * (at your option) any later version. 9 * 10 * LibEuFin is distributed in the hope that it will be useful, but 11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General 13 * Public License for more details. 14 * 15 * You should have received a copy of the GNU Affero General Public 16 * License along with LibEuFin; see the file COPYING. If not, see 17 * <http://www.gnu.org/licenses/> 18 */ 19 20 package tech.libeufin.common 21 22 import java.nio.ByteBuffer 23 import java.nio.ByteOrder 24 import java.security.MessageDigest 25 import io.github.smiley4.schemakenerator.core.annotations.Description 26 import kotlinx.serialization.SerialName 27 import kotlinx.serialization.Serializable 28 import tech.libeufin.common.crypto.CryptoUtil.hashStringNbo 29 import tech.libeufin.common.crypto.NBO 30 31 enum class IncomingType { 32 reserve, 33 kyc, 34 map 35 } 36 37 @Description("State of a wire transfer") 38 enum class TransferStatusState { 39 pending, 40 transient_failure, 41 permanent_failure, 42 success 43 } 44 45 /** Response GET /taler-wire-gateway/config */ 46 @Serializable 47 @Description("Wire gateway configuration response") 48 data class WireGatewayConfig( 49 @Description("Currency supported by the gateway") 50 val currency: String, 51 @Description("Whether account check is supported") 52 val support_account_check: Boolean 53 ) { 54 @Description("API name identifier") 55 val name: String = "taler-wire-gateway" 56 @Description("API version string") 57 val version: String = WIRE_GATEWAY_API_VERSION 58 } 59 60 /** Request POST /taler-wire-gateway/transfer */ 61 @Serializable 62 @Description("Wire transfer request") 63 data class TransferRequest( 64 @Description("Unique identifier for this request") 65 val request_uid: HashCode, 66 @Description("Amount to transfer") 67 val amount: TalerAmount, 68 @Description("Base URL of the exchange") 69 val exchange_base_url: BaseURL, 70 @Description("Wire transfer identifier") 71 val wtid: ShortHashCode, 72 @Description("Payto URI of the credit account") 73 val credit_account: Payto, 74 @Description("Optional transfer metadata") 75 val metadata: String? = null, 76 ) { 77 init { 78 if (metadata != null && !METADATA_REGEX.matches(metadata)) 79 throw badRequest("metadata '$metadata' is malformed, must match [a-zA-Z0-9-.+:]{1,40}") 80 } 81 82 companion object { 83 private val METADATA_REGEX = Regex("^[a-zA-Z0-9-.:]{1,40}$") 84 } 85 } 86 87 /** Response POST /taler-wire-gateway/transfer */ 88 @Serializable 89 @Description("Wire transfer response") 90 data class TransferResponse( 91 @Description("Timestamp of the transfer") 92 val timestamp: TalerTimestamp, 93 @Description("Database row identifier") 94 val row_id: Long 95 ) 96 97 /** Request GET /taler-wire-gateway/transfers */ 98 @Serializable 99 @Description("List of wire transfers") 100 data class TransferList( 101 @Description("List of transfer statuses") 102 val transfers: List<TransferListStatus>, 103 @Description("Payto URI of the debit account") 104 val debit_account: String 105 ) 106 107 @Serializable 108 @Description("Transfer status in a list response") 109 data class TransferListStatus( 110 @Description("Database row identifier") 111 val row_id: Long, 112 @Description("Current transfer status") 113 val status: TransferStatusState, 114 @Description("Transfer amount") 115 val amount: TalerAmount, 116 @Description("Payto URI of the credit account") 117 val credit_account: String, 118 @Description("Timestamp of the transfer") 119 val timestamp: TalerTimestamp 120 ) 121 122 /** Request GET /taler-wire-gateway/transfers/{ROW_iD} */ 123 @Serializable 124 @Description("Detailed status of a single transfer") 125 data class TransferStatus( 126 @Description("Current transfer status") 127 val status: TransferStatusState, 128 @Description("Optional status message") 129 val status_msg: String? = null, 130 @Description("Transfer amount") 131 val amount: TalerAmount, 132 @Description("URL of the originating exchange") 133 val origin_exchange_url: String, 134 @Description("Optional transfer metadata") 135 val metadata: String? = null, 136 @Description("Wire transfer identifier") 137 val wtid: ShortHashCode, 138 @Description("Payto URI of the credit account") 139 val credit_account: String, 140 @Description("Timestamp of the transfer") 141 val timestamp: TalerTimestamp 142 ) 143 144 /** Request POST /taler-wire-gateway/admin/add-incoming */ 145 @Serializable 146 @Description("Request to add an incoming transaction") 147 data class AddIncomingRequest( 148 @Description("Amount of the incoming transaction") 149 val amount: TalerAmount, 150 @Description("Reserve public key") 151 val reserve_pub: EddsaPublicKey, 152 @Description("Payto URI of the debit account") 153 val debit_account: Payto 154 ) 155 156 /** Response POST /taler-wire-gateway/admin/add-incoming */ 157 @Serializable 158 @Description("Response to adding an incoming transaction") 159 data class AddIncomingResponse( 160 @Description("Timestamp of the transaction") 161 val timestamp: TalerTimestamp, 162 @Description("Database row identifier") 163 val row_id: Long 164 ) 165 166 /** Request POST /taler-wire-gateway/admin/add-kycauth */ 167 @Serializable 168 @Description("Request to add a KYC auth transaction") 169 data class AddKycauthRequest( 170 @Description("Amount of the KYC auth transaction") 171 val amount: TalerAmount, 172 @Description("Account public key for KYC") 173 val account_pub: EddsaPublicKey, 174 @Description("Payto URI of the debit account") 175 val debit_account: Payto 176 ) 177 178 /** Request POST /taler-wire-gateway/admin/add-mapped */ 179 @Serializable 180 data class AddMappedRequest( 181 val amount: TalerAmount, 182 val authorization_pub: EddsaPublicKey, 183 val debit_account: Payto 184 ) 185 186 /** Response GET /taler-wire-gateway/history/incoming */ 187 @Serializable 188 @Description("History of incoming transactions") 189 data class IncomingHistory( 190 @Description("List of incoming transactions") 191 val incoming_transactions: List<IncomingBankTransaction>, 192 @Description("Payto URI of the credit account") 193 val credit_account: String 194 ) 195 196 /** Inner response GET /taler-wire-gateway/history/incoming */ 197 @Serializable 198 @Description("Incoming bank transaction details") 199 sealed interface IncomingBankTransaction { 200 val row_id: Long 201 val date: TalerTimestamp 202 val amount: TalerAmount 203 val debit_account: String 204 val credit_fee: TalerAmount? 205 } 206 207 @Serializable 208 @SerialName("KYCAUTH") 209 @Description("Incoming KYC authentication transaction") 210 data class IncomingKycAuthTransaction( 211 @Description("Database row identifier") 212 override val row_id: Long, 213 @Description("Timestamp of the transaction") 214 override val date: TalerTimestamp, 215 @Description("Transaction amount") 216 override val amount: TalerAmount, 217 @Description("Optional credit fee") 218 override val credit_fee: TalerAmount? = null, 219 @Description("Payto URI of the debit account") 220 override val debit_account: String, 221 @Description("Account public key for KYC") 222 val account_pub: EddsaPublicKey, 223 @Description("Optional authorization public key") 224 val authorization_pub: EddsaPublicKey? = null, 225 @Description("Optional authorization signature") 226 val authorization_sig: EddsaSignature? = null, 227 ) : IncomingBankTransaction 228 229 @Serializable 230 @SerialName("RESERVE") 231 @Description("Incoming reserve transaction") 232 data class IncomingReserveTransaction( 233 @Description("Database row identifier") 234 override val row_id: Long, 235 @Description("Timestamp of the transaction") 236 override val date: TalerTimestamp, 237 @Description("Transaction amount") 238 override val amount: TalerAmount, 239 @Description("Optional credit fee") 240 override val credit_fee: TalerAmount? = null, 241 @Description("Payto URI of the debit account") 242 override val debit_account: String, 243 @Description("Reserve public key") 244 val reserve_pub: EddsaPublicKey, 245 @Description("Optional authorization public key") 246 val authorization_pub: EddsaPublicKey? = null, 247 @Description("Optional authorization signature") 248 val authorization_sig: EddsaSignature? = null, 249 ) : IncomingBankTransaction 250 251 @Serializable 252 @SerialName("WAD") 253 @Description("Incoming WAD transaction") 254 data class IncomingWadTransaction( 255 @Description("Database row identifier") 256 override val row_id: Long, 257 @Description("Timestamp of the transaction") 258 override val date: TalerTimestamp, 259 @Description("Transaction amount") 260 override val amount: TalerAmount, 261 @Description("Optional credit fee") 262 override val credit_fee: TalerAmount? = null, 263 @Description("Payto URI of the debit account") 264 override val debit_account: String, 265 @Description("URL of the originating exchange") 266 val origin_exchange_url: String, 267 @Description("WAD identifier") 268 val wad_id: String // TODO 24 bytes Base32 269 ) : IncomingBankTransaction 270 271 /** Response GET /taler-wire-gateway/history/outgoing */ 272 @Serializable 273 @Description("History of outgoing transactions") 274 data class OutgoingHistory( 275 @Description("List of outgoing transactions") 276 val outgoing_transactions: List<OutgoingTransaction>, 277 @Description("Payto URI of the debit account") 278 val debit_account: String 279 ) 280 281 /** Inner response GET /taler-wire-gateway/history/outgoing */ 282 @Serializable 283 @Description("Single outgoing transaction details") 284 data class OutgoingTransaction( 285 @Description("Database row identifier") 286 val row_id: Long, // DB row ID of the payment. 287 @Description("Timestamp of the transaction") 288 val date: TalerTimestamp, 289 @Description("Transaction amount") 290 val amount: TalerAmount, 291 @Description("Payto URI of the credit account") 292 val credit_account: String, 293 @Description("Wire transfer identifier") 294 val wtid: ShortHashCode, 295 @Description("Base URL of the exchange") 296 val exchange_base_url: String, 297 @Description("Optional transfer metadata") 298 val metadata: String? = null, 299 @Description("Optional debit fee") 300 val debit_fee: TalerAmount? = null 301 ) 302 303 /** Response GET /taler-wire-gateway/account/check */ 304 @Serializable 305 @Description("Account information response") 306 class AccountInfo() 307 308 /** Response GET /taler-prepared-transfer/config */ 309 @Serializable 310 @Description("Prepared transfer configuration") 311 data class PreparedTransferConfig( 312 @Description("Currency supported") 313 val currency: String, 314 @Description("List of supported subject formats") 315 val supported_formats: List<SubjectFormat> 316 ) { 317 @Description("API name identifier") 318 val name: String = "taler-prepared-transfer" 319 @Description("API version string") 320 val version: String = WIRE_TRANSFER_API_VERSION 321 } 322 323 324 /** Inner response GET /taler-prepared-transfer/registration */ 325 @Serializable 326 @Description("Transfer subject information") 327 sealed interface TransferSubject { 328 @Serializable 329 @SerialName("SIMPLE") 330 @Description("Simple text transfer subject") 331 data class Simple( 332 @Description("Plain text transfer subject") 333 val subject: String, 334 @Description("Credit amount for the transfer") 335 val credit_amount: TalerAmount 336 ) : TransferSubject 337 338 @Serializable 339 @SerialName("URI") 340 @Description("URI-based transfer subject") 341 data class Uri( 342 @Description("Taler URI for the transfer") 343 val uri: String, 344 @Description("Credit amount for the transfer") 345 val credit_amount: TalerAmount 346 ) : TransferSubject 347 348 @Serializable 349 @SerialName("CH_QR_BILL") 350 @Description("Swiss QR bill transfer subject") 351 data class QrBill( 352 @Description("QR reference number for the bill") 353 val qr_reference_number: String, 354 @Description("Credit amount for the transfer") 355 val credit_amount: TalerAmount, 356 ) : TransferSubject 357 } 358 359 @Serializable 360 @Description("Supported transfer subject format") 361 enum class SubjectFormat { 362 SIMPLE, 363 URI, 364 CH_QR_BILL 365 } 366 367 @Serializable 368 @Description("Public key algorithm") 369 enum class PublicKeyAlg { 370 EdDSA 371 } 372 373 @Serializable 374 @Description("Type of wire transfer") 375 enum class TransferType { 376 reserve, 377 kyc 378 } 379 380 @Serializable 381 @Description("Request to generate a transfer subject") 382 data class SubjectRequest( 383 @Description("Payto URI of the credit account") 384 val credit_account: Payto, 385 @Description("Type of transfer") 386 val type: TransferType, 387 @Description("Whether subject is recurrent") 388 val recurrent: Boolean, 389 @Description("Credit amount for the transfer") 390 val credit_amount: TalerAmount, 391 @Description("Public key algorithm") 392 val alg: PublicKeyAlg, 393 @Description("Account public key") 394 val account_pub: EddsaPublicKey, 395 @Description("Authorization public key") 396 val authorization_pub: EddsaPublicKey, 397 @Description("Authorization signature") 398 val authorization_sig: EddsaSignature, 399 ) : NBO { 400 /* Network bytes */ 401 override fun nbo(): ByteArray = ByteBuffer.allocate(104).apply { 402 order(ByteOrder.BIG_ENDIAN) 403 putInt(capacity()) 404 putInt(1224) 405 put(hashStringNbo(credit_account.toString())) 406 put(credit_amount.nbo()) 407 putInt(when (type) { 408 TransferType.reserve -> 1 409 TransferType.kyc -> 2 410 }) 411 putShort(if (recurrent) 2 else 1) 412 putShort(when (alg) { 413 PublicKeyAlg.EdDSA -> 1 414 }) 415 put(account_pub.raw) 416 }.array() 417 418 fun sign(priv: ByteArray): SubjectRequest = 419 this.copy(authorization_sig = this.signNbo(priv)) 420 421 fun verify(): Boolean = 422 this.verifyNbo(this.authorization_sig, this.authorization_pub) 423 } 424 425 @Serializable 426 @Description("Result of subject generation") 427 data class SubjectResult( 428 @Description("List of generated transfer subjects") 429 val subjects: List<TransferSubject>, 430 @Description("Expiration timestamp of the subjects") 431 val expiration: TalerTimestamp 432 ) 433 434 @Serializable 435 @Description("Request to unregister a subject") 436 data class Unregistration( 437 @Description("Timestamp of the unregistration") 438 val timestamp: TalerTimestamp, 439 @Description("Authorization public key") 440 val authorization_pub: EddsaPublicKey, 441 @Description("Authorization signature") 442 val authorization_sig: EddsaSignature 443 ) : NBO { 444 /* Network bytes */ 445 override fun nbo(): ByteArray = ByteBuffer.allocate(16).apply { 446 order(ByteOrder.BIG_ENDIAN) 447 putInt(capacity()) 448 putInt(1225) 449 putLong(timestamp.instant.epochSecond * 1000 * 1000) 450 }.array() 451 452 fun sign(priv: ByteArray): Unregistration = 453 this.copy(authorization_sig = this.signNbo(priv)) 454 455 fun verify(): Boolean = 456 this.verifyNbo(this.authorization_sig, this.authorization_pub) 457 } 458 459 /** Response GET /taler-revenue/config */ 460 @Serializable 461 @Description("Revenue API configuration response") 462 data class RevenueConfig( 463 @Description("Currency supported by the API") 464 val currency: String 465 ) { 466 @Description("API name identifier") 467 val name: String = "taler-revenue" 468 @Description("API version string") 469 val version: String = REVENUE_API_VERSION 470 } 471 472 /** Request GET /taler-revenue/history */ 473 @Serializable 474 @Description("History of revenue incoming transactions") 475 data class RevenueIncomingHistory( 476 @Description("List of incoming revenue transactions") 477 val incoming_transactions: List<RevenueIncomingBankTransaction>, 478 @Description("Payto URI of the credit account") 479 val credit_account: String 480 ) 481 482 /** Inner request GET /taler-revenue/history */ 483 @Serializable 484 @Description("Single revenue incoming bank transaction") 485 data class RevenueIncomingBankTransaction( 486 @Description("Database row identifier") 487 val row_id: Long, 488 @Description("Timestamp of the transaction") 489 val date: TalerTimestamp, 490 @Description("Transaction amount") 491 val amount: TalerAmount, 492 @Description("Optional credit fee") 493 val credit_fee: TalerAmount? = null, 494 @Description("Payto URI of the debit account") 495 val debit_account: String, 496 @Description("Transaction subject line") 497 val subject: String 498 ) 499 500 /** Response GET /taler-observability/config */ 501 @Serializable 502 @Description("Observability API configuration response") 503 class TalerObservabilityConfig() { 504 @Description("API name identifier") 505 val name: String = "taler-observability" 506 @Description("API version string") 507 val version: String = OBSERVABILITY_API_VERSION 508 }