mirror of
https://github.com/OpenBankProject/OBP-API.git
synced 2026-02-06 11:06:49 +00:00
parent
a7f12d0a00
commit
a59d7fe783
@ -62,7 +62,7 @@ import code.model.dataAccess._
|
||||
import code.products.MappedProduct
|
||||
import code.transaction_types.MappedTransactionType
|
||||
import code.snippet.{OAuthAuthorisation, OAuthWorkedThanks}
|
||||
import code.transactionrequests.MappedTransactionRequest
|
||||
import code.transactionrequests.{MappedTransactionRequest210, MappedTransactionRequest}
|
||||
import code.usercustomerlinks.MappedUserCustomerLink
|
||||
import net.liftweb.common._
|
||||
import net.liftweb.http._
|
||||
@ -405,6 +405,7 @@ object ToSchemify {
|
||||
MappedBankAccount,
|
||||
MappedTransaction,
|
||||
MappedTransactionRequest,
|
||||
MappedTransactionRequest210,
|
||||
MappedTransactionImage,
|
||||
MappedMetric,
|
||||
MappedCustomer,
|
||||
|
||||
@ -3,7 +3,13 @@ package code.api.v2_1_0
|
||||
import java.text.SimpleDateFormat
|
||||
import code.api.util.ApiRole._
|
||||
import code.api.util.ErrorMessages
|
||||
import code.api.v2_1_0.JSONFactory210
|
||||
import code.api.v1_2_1.AmountOfMoneyJSON
|
||||
import code.api.v1_4_0.JSONFactory1_4_0.TransactionRequestAccountJSON
|
||||
import code.api.v2_0_0.JSONFactory200._
|
||||
import code.api.v2_0_0.{JSONFactory200, TransactionRequestBodyJSON}
|
||||
import code.api.v2_1_0.JSONFactory210._
|
||||
import code.bankconnectors.Connector
|
||||
import code.fx.fx
|
||||
import code.model._
|
||||
|
||||
import net.liftweb.http.Req
|
||||
@ -21,11 +27,13 @@ import code.api.APIFailure
|
||||
import code.api.util.APIUtil._
|
||||
import code.sandbox.{OBPDataImport, SandboxDataImport}
|
||||
import code.util.Helper
|
||||
import net.liftweb.common.Box
|
||||
import net.liftweb.common.{Empty, Full, Box}
|
||||
import net.liftweb.http.JsonResponse
|
||||
import net.liftweb.http.js.JE.JsRaw
|
||||
import net.liftweb.http.rest.RestHelper
|
||||
import net.liftweb.util.Helpers._
|
||||
import net.liftweb.json._
|
||||
import net.liftweb.json.Serialization.{read, write}
|
||||
|
||||
|
||||
trait APIMethods210 {
|
||||
@ -132,6 +140,198 @@ trait APIMethods210 {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
import net.liftweb.json.JsonAST._
|
||||
import net.liftweb.json.Extraction._
|
||||
import net.liftweb.json.Printer._
|
||||
val exchangeRates = pretty(render(decompose(fx.exchangeRates)))
|
||||
|
||||
resourceDocs += ResourceDoc(
|
||||
createTransactionRequest,
|
||||
apiVersion,
|
||||
"createTransactionRequest",
|
||||
"POST",
|
||||
"/banks/BANK_ID/accounts/ACCOUNT_ID/VIEW_ID/transaction-request-types/TRANSACTION_REQUEST_TYPE/transaction-requests",
|
||||
"Create Transaction Request.",
|
||||
s"""Initiate a Payment via a Transaction Request.
|
||||
|
|
||||
|This is the preferred method to create a payment and supersedes makePayment in 1.2.1.
|
||||
|
|
||||
|PSD2 Context: Third party access access to payments is a core tenent of PSD2.
|
||||
|
|
||||
|This call satisfies that requirement from several perspectives:
|
||||
|
|
||||
|1) A transaction can be initiated by a third party application.
|
||||
|
|
||||
|2) The customer is informed of the charge that will incurred.
|
||||
|
|
||||
|3) The call uses delegated authentication (OAuth)
|
||||
|
|
||||
|See [this python code](https://github.com/OpenBankProject/Hello-OBP-DirectLogin-Python/blob/master/hello_payments.py) for a complete example of this flow.
|
||||
|
|
||||
|In sandbox mode, if the amount is less than 100 (any currency), the transaction request will create a transaction without a challenge, else a challenge will need to be answered.
|
||||
|
|
||||
|You can transfer between different currency accounts. (new in 2.0.0). The currency in body must match the sending account.
|
||||
|
|
||||
|Currently TRANSACTION_REQUEST_TYPE must be set to SANDBOX_TAN
|
||||
|
|
||||
|The following static FX rates are available in sandbox mode:
|
||||
|
|
||||
|${exchangeRates}
|
||||
|
|
||||
|
|
||||
|The payer is set in the URL. Money comes out of the BANK_ID and ACCOUNT_ID specified in the URL
|
||||
|
|
||||
|The payee is set in the request body. Money goes into the BANK_ID and ACCOUNT_IDO specified in the request body.
|
||||
|
|
||||
|
|
||||
|${authenticationRequiredMessage(true)}
|
||||
|
|
||||
|""",
|
||||
Extraction.decompose(TransactionRequestBodyJSON (
|
||||
TransactionRequestAccountJSON("BANK_ID", "ACCOUNT_ID"),
|
||||
AmountOfMoneyJSON("EUR", "100.53"),
|
||||
"A description for the transaction to be created"
|
||||
)
|
||||
),
|
||||
emptyObjectJson,
|
||||
emptyObjectJson :: Nil,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
List(apiTagTransactionRequest))
|
||||
|
||||
lazy val createTransactionRequest: PartialFunction[Req, Box[User] => Box[JsonResponse]] = {
|
||||
case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: ViewId(viewId) :: "transaction-request-types" ::
|
||||
TransactionRequestType(transactionRequestType) :: "transaction-requests" :: Nil JsonPost json -> _ => {
|
||||
user =>
|
||||
if (Props.getBool("transactionRequests_enabled", false)) {
|
||||
for {
|
||||
/* TODO:
|
||||
* check if user has access using the view that is given (now it checks if user has access to owner view), will need some new permissions for transaction requests
|
||||
* test: functionality, error messages if user not given or invalid, if any other value is not existing
|
||||
*/
|
||||
u <- user ?~ ErrorMessages.UserNotLoggedIn
|
||||
|
||||
// Get Transaction Request Types from Props "transactionRequests_supported_types". Default is empty string
|
||||
validTransactionRequestTypes <- tryo{Props.get("transactionRequests_supported_types", "")}
|
||||
// Use a list instead of a string to avoid partial matches
|
||||
validTransactionRequestTypesList <- tryo{validTransactionRequestTypes.split(",")}
|
||||
isValidTransactionRequestType <- tryo(assert(transactionRequestType.value != "TRANSACTION_REQUEST_TYPE" && validTransactionRequestTypesList.contains(transactionRequestType.value))) ?~! s"${ErrorMessages.InvalidTransactionRequestType} : Invalid value is: '${transactionRequestType.value}' Valid values are: ${validTransactionRequestTypes}"
|
||||
|
||||
transDetailsJson <- transactionRequestType.value match {
|
||||
case "SANDBOX_TAN" => tryo {
|
||||
json.extract[TransactionRequestDetailsSandBoxTanJSON]
|
||||
} ?~ {
|
||||
ErrorMessages.InvalidJsonFormat
|
||||
}
|
||||
case "SEPA" => tryo {
|
||||
json.extract[TransactionRequestDetailsSEPAJSON]
|
||||
} ?~ {
|
||||
ErrorMessages.InvalidJsonFormat
|
||||
}
|
||||
}
|
||||
|
||||
transDetails <- transactionRequestType.value match {
|
||||
case "SANDBOX_TAN" => tryo{getTransactionRequestDetailsSandBoxTanFromJson(transDetailsJson.asInstanceOf[TransactionRequestDetailsSandBoxTanJSON])}
|
||||
case "SEPA" => tryo{getTransactionRequestDetailsSEPAFromJson(transDetailsJson.asInstanceOf[TransactionRequestDetailsSEPAJSON])}
|
||||
}
|
||||
|
||||
fromBank <- Bank(bankId) ?~! {ErrorMessages.BankNotFound}
|
||||
fromAccount <- BankAccount(bankId, accountId) ?~! {ErrorMessages.AccountNotFound}
|
||||
isOwnerOrHasEntitlement <- booleanToBox(u.ownerAccess(fromAccount) == true || hasEntitlement(fromAccount.bankId.value, u.userId, CanCreateAnyTransactionRequest) == true , ErrorMessages.InsufficientAuthorisationToCreateTransactionRequest)
|
||||
|
||||
// Prevent default value for transaction request type (at least).
|
||||
transferCurrencyEqual <- tryo(assert(transDetailsJson.value.currency == fromAccount.currency)) ?~! {"Transfer body currency and holder account currency must be the same."}
|
||||
|
||||
transDetailsSerialized <- tryo{
|
||||
implicit val formats = Serialization.formats(NoTypeHints)
|
||||
write(transDetailsJson)
|
||||
}
|
||||
|
||||
createdTransactionRequest <- transactionRequestType.value match {
|
||||
case "SANDBOX_TAN" => {
|
||||
for {
|
||||
toBankId <- Full(BankId(transDetailsJson.asInstanceOf[TransactionRequestDetailsSandBoxTanJSON].to.bank_id))
|
||||
toAccountId <- Full(AccountId(transDetailsJson.asInstanceOf[TransactionRequestDetailsSandBoxTanJSON].to.account_id))
|
||||
toAccount <- BankAccount(toBankId, toAccountId) ?~! {ErrorMessages.CounterpartyNotFound}
|
||||
createdTransactionRequest <- Connector.connector.vend.createTransactionRequestv210(u, fromAccount, Full(toAccount), transactionRequestType, transDetails, transDetailsSerialized)
|
||||
} yield createdTransactionRequest
|
||||
|
||||
}
|
||||
case "SEPA" => Connector.connector.vend.createTransactionRequestv210(u, fromAccount, Empty, transactionRequestType, transDetails, transDetailsSerialized)
|
||||
}
|
||||
} yield {
|
||||
// Explicitly format as v2.0.0 json
|
||||
val json = JSONFactory210.createTransactionRequestWithChargeJSON(createdTransactionRequest)
|
||||
createdJsonResponse(Extraction.decompose(json))
|
||||
}
|
||||
} else {
|
||||
Full(errorJsonResponse("Sorry, Transaction Requests are not enabled in this API instance."))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
resourceDocs += ResourceDoc(
|
||||
getTransactionRequests,
|
||||
apiVersion,
|
||||
"getTransactionRequests",
|
||||
"GET",
|
||||
"/banks/BANK_ID/accounts/ACCOUNT_ID/VIEW_ID/transaction-requests",
|
||||
"Get Transaction Requests." ,
|
||||
"""Returns transaction requests for account specified by ACCOUNT_ID at bank specified by BANK_ID.
|
||||
|
|
||||
|The VIEW_ID specified must be 'owner' and the user must have access to this view.
|
||||
|
|
||||
|Version 2.0.0 now returns charge information.
|
||||
|
|
||||
|Transaction Requests serve to initiate transactions that may or may not proceed. They contain information including:
|
||||
|
|
||||
|* Transaction Request Id
|
||||
|* Type
|
||||
|* Status (INITIATED, COMPLETED)
|
||||
|* Challenge (in order to confirm the request)
|
||||
|* From Bank / Account
|
||||
|* Details including Currency, Value, Description and other initiation information specific to each type. (Could potentialy include a list of future transactions.)
|
||||
|* Related Transactions
|
||||
|
|
||||
|PSD2 Context: PSD2 requires transparency of charges to the customer.
|
||||
|This endpoint provides the charge that would be applied if the Transaction Request proceeds - and a record of that charge there after.
|
||||
|The customer can proceed with the Transaction by answering the security challenge.
|
||||
|
|
||||
|
|
||||
""".stripMargin,
|
||||
emptyObjectJson,
|
||||
emptyObjectJson,
|
||||
emptyObjectJson :: Nil,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
List(apiTagTransactionRequest))
|
||||
|
||||
lazy val getTransactionRequests: PartialFunction[Req, Box[User] => Box[JsonResponse]] = {
|
||||
case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: ViewId(viewId) :: "transaction-requests" :: Nil JsonGet _ => {
|
||||
user =>
|
||||
if (Props.getBool("transactionRequests_enabled", false)) {
|
||||
for {
|
||||
u <- user ?~ ErrorMessages.UserNotLoggedIn
|
||||
fromBank <- Bank(bankId) ?~! {ErrorMessages.BankNotFound}
|
||||
fromAccount <- BankAccount(bankId, accountId) ?~! {ErrorMessages.AccountNotFound}
|
||||
view <- tryo(fromAccount.permittedViews(user).find(_ == viewId)) ?~ {"Current user does not have access to the view " + viewId}
|
||||
transactionRequests <- Connector.connector.vend.getTransactionRequests210(u, fromAccount)
|
||||
}
|
||||
yield {
|
||||
// Format the data as V2.0.0 json
|
||||
val json = JSONFactory210.createTransactionRequestJSONs(transactionRequests)
|
||||
successJsonResponse(Extraction.decompose(json))
|
||||
}
|
||||
} else {
|
||||
Full(errorJsonResponse("Sorry, Transaction Requests are not enabled in this API instance."))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -31,9 +31,49 @@ Berlin 13359, Germany
|
||||
*/
|
||||
package code.api.v2_1_0
|
||||
|
||||
import java.util.Date
|
||||
|
||||
import code.api.v1_2_1.AmountOfMoneyJSON
|
||||
import code.api.v1_4_0.JSONFactory1_4_0.{ChallengeJSON, TransactionRequestAccountJSON}
|
||||
import code.api.v2_0_0.{TransactionRequestWithChargeJSONs, TransactionRequestChargeJSON, TransactionRequestBodyJSON}
|
||||
import code.model.AmountOfMoney
|
||||
import code.transactionrequests.TransactionRequests._
|
||||
|
||||
case class TransactionRequestTypeJSON(transaction_request_type: String)
|
||||
case class TransactionRequestTypesJSON(transaction_request_types: List[TransactionRequestTypeJSON])
|
||||
|
||||
trait TransactionRequestDetailsJSON {
|
||||
val value : AmountOfMoneyJSON
|
||||
}
|
||||
|
||||
case class TransactionRequestDetailsSandBoxTanJSON(
|
||||
to: TransactionRequestAccountJSON,
|
||||
value : AmountOfMoneyJSON,
|
||||
description : String
|
||||
) extends TransactionRequestDetailsJSON
|
||||
|
||||
case class TransactionRequestDetailsSEPAJSON(
|
||||
value : AmountOfMoneyJSON,
|
||||
description : String
|
||||
) extends TransactionRequestDetailsJSON
|
||||
|
||||
case class TransactionRequestWithChargeJSON210(
|
||||
id: String,
|
||||
`type`: String,
|
||||
from: TransactionRequestAccountJSON,
|
||||
details: String,
|
||||
transaction_ids: String,
|
||||
status: String,
|
||||
start_date: Date,
|
||||
end_date: Date,
|
||||
challenge: ChallengeJSON,
|
||||
charge : TransactionRequestChargeJSON
|
||||
)
|
||||
|
||||
case class TransactionRequestWithChargeJSONs210(
|
||||
transaction_requests_with_charges : List[TransactionRequestWithChargeJSON210]
|
||||
)
|
||||
|
||||
object JSONFactory210{
|
||||
def createTransactionRequestTypeJSON(transactionRequestType : String ) : TransactionRequestTypeJSON = {
|
||||
new TransactionRequestTypeJSON(
|
||||
@ -44,4 +84,69 @@ object JSONFactory210{
|
||||
def createTransactionRequestTypeJSON(transactionRequestTypes : List[String]) : TransactionRequestTypesJSON = {
|
||||
TransactionRequestTypesJSON(transactionRequestTypes.map(createTransactionRequestTypeJSON))
|
||||
}
|
||||
|
||||
//transaction requests
|
||||
def getTransactionRequestDetailsSandBoxTanFromJson(details: TransactionRequestDetailsSandBoxTanJSON) : TransactionRequestDetailsSandBoxTan = {
|
||||
val toAcc = TransactionRequestAccount (
|
||||
bank_id = details.to.bank_id,
|
||||
account_id = details.to.account_id
|
||||
)
|
||||
val amount = AmountOfMoney (
|
||||
currency = details.value.currency,
|
||||
amount = details.value.amount
|
||||
)
|
||||
|
||||
TransactionRequestDetailsSandBoxTan (
|
||||
to = toAcc,
|
||||
value = amount,
|
||||
description = details.description
|
||||
)
|
||||
}
|
||||
|
||||
def getTransactionRequestDetailsSEPAFromJson(details: TransactionRequestDetailsSEPAJSON) : TransactionRequestDetailsSEPA = {
|
||||
val amount = AmountOfMoney (
|
||||
currency = details.value.currency,
|
||||
amount = details.value.amount
|
||||
)
|
||||
|
||||
TransactionRequestDetailsSEPA (
|
||||
value = amount,
|
||||
description = details.description
|
||||
)
|
||||
}
|
||||
|
||||
/** Creates v2.1.0 representation of a TransactionType
|
||||
*
|
||||
* @param tr An internal TransactionRequest instance
|
||||
* @return a v2.1.0 representation of a TransactionRequest
|
||||
*/
|
||||
|
||||
def createTransactionRequestWithChargeJSON(tr : TransactionRequest210) : TransactionRequestWithChargeJSON210 = {
|
||||
new TransactionRequestWithChargeJSON210(
|
||||
id = tr.id.value,
|
||||
`type` = tr.`type`,
|
||||
from = TransactionRequestAccountJSON (
|
||||
bank_id = tr.from.bank_id,
|
||||
account_id = tr.from.account_id),
|
||||
details = tr.details,
|
||||
transaction_ids = tr.transaction_ids,
|
||||
status = tr.status,
|
||||
start_date = tr.start_date,
|
||||
end_date = tr.end_date,
|
||||
// Some (mapped) data might not have the challenge. TODO Make this nicer
|
||||
challenge = {
|
||||
try {ChallengeJSON (id = tr.challenge.id, allowed_attempts = tr.challenge.allowed_attempts, challenge_type = tr.challenge.challenge_type)}
|
||||
// catch { case _ : Throwable => ChallengeJSON (id = "", allowed_attempts = 0, challenge_type = "")}
|
||||
catch { case _ : Throwable => null}
|
||||
},
|
||||
charge = TransactionRequestChargeJSON (summary = tr.charge.summary,
|
||||
value = AmountOfMoneyJSON(currency = tr.charge.value.currency,
|
||||
amount = tr.charge.value.amount)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
def createTransactionRequestJSONs(trs : List[TransactionRequest210]) : TransactionRequestWithChargeJSONs210 = {
|
||||
TransactionRequestWithChargeJSONs210(trs.map(createTransactionRequestWithChargeJSON))
|
||||
}
|
||||
}
|
||||
@ -143,9 +143,9 @@ object OBPAPI2_1_0 extends OBPRestHelper with APIMethods130 with APIMethods140 w
|
||||
Implementations2_0_0.allAccountsAtOneBank,
|
||||
Implementations2_0_0.privateAccountsAtOneBank,
|
||||
Implementations2_0_0.publicAccountsAtOneBank,
|
||||
Implementations2_0_0.createTransactionRequest,
|
||||
// Now in 2.1.0 Implementations2_0_0.createTransactionRequest,
|
||||
Implementations2_0_0.answerTransactionRequestChallenge,
|
||||
Implementations2_0_0.getTransactionRequests, // Now has charges information
|
||||
// Now in 2.1.0 Implementations2_0_0.getTransactionRequests, // Now has charges information
|
||||
// Updated in 2.0.0 (added sorting and better guards / error messages)
|
||||
Implementations2_0_0.accountById,
|
||||
Implementations2_0_0.getPermissionsForBankAccount,
|
||||
@ -182,7 +182,9 @@ object OBPAPI2_1_0 extends OBPRestHelper with APIMethods130 with APIMethods140 w
|
||||
Implementations2_0_0.getCustomers,
|
||||
// New in 2.1.0
|
||||
Implementations2_1_0.sandboxDataImport,
|
||||
Implementations2_1_0.getTransactionRequestTypesSupportedByBank
|
||||
Implementations2_1_0.getTransactionRequestTypesSupportedByBank,
|
||||
Implementations2_1_0.createTransactionRequest,
|
||||
Implementations2_1_0.getTransactionRequests
|
||||
)
|
||||
|
||||
routes.foreach(route => {
|
||||
|
||||
@ -10,7 +10,7 @@ import code.management.ImporterAPI.ImporterTransaction
|
||||
import code.model.{OtherBankAccount, Transaction, User, _}
|
||||
import code.tesobe.CashTransaction
|
||||
import code.transactionrequests.TransactionRequests
|
||||
import code.transactionrequests.TransactionRequests.{TransactionRequest, TransactionRequestBody, TransactionRequestChallenge, TransactionRequestCharge}
|
||||
import code.transactionrequests.TransactionRequests._
|
||||
import code.util.Helper._
|
||||
import net.liftweb.common.{Box, Empty, Failure, Full}
|
||||
import net.liftweb.util.Helpers._
|
||||
@ -169,6 +169,7 @@ trait Connector {
|
||||
} yield transactionId
|
||||
}
|
||||
|
||||
|
||||
protected def makePaymentImpl(fromAccount : AccountType, toAccount : AccountType, amt : BigDecimal, description : String) : Box[TransactionId]
|
||||
|
||||
|
||||
@ -302,6 +303,71 @@ trait Connector {
|
||||
result
|
||||
}
|
||||
|
||||
def createTransactionRequestv210(initiator : User, fromAccount : BankAccount, toAccount: Box[BankAccount], transactionRequestType: TransactionRequestType, details: TransactionRequestDetails, detailsPlain: String) : Box[TransactionRequest210] = {
|
||||
//set initial status
|
||||
//for sandbox / testing: depending on amount, we ask for challenge or not
|
||||
val status =
|
||||
if (transactionRequestType.value == TransactionRequests.CHALLENGE_SANDBOX_TAN && BigDecimal(details.value.amount) < 1000) {
|
||||
TransactionRequests.STATUS_COMPLETED
|
||||
} else {
|
||||
TransactionRequests.STATUS_INITIATED
|
||||
}
|
||||
|
||||
|
||||
// Always create a new Transaction Request
|
||||
var result = for {
|
||||
fromAccountType <- getBankAccount(fromAccount.bankId, fromAccount.accountId) ?~
|
||||
s"account ${fromAccount.accountId} not found at bank ${fromAccount.bankId}"
|
||||
isOwner <- booleanToBox(initiator.ownerAccess(fromAccount) == true || hasEntitlement(fromAccount.bankId.value, initiator.userId, CanCreateAnyTransactionRequest) == true , ErrorMessages.InsufficientAuthorisationToCreateTransactionRequest)
|
||||
|
||||
rawAmt <- tryo { BigDecimal(details.value.amount) } ?~! s"amount ${details.value.amount} not convertible to number"
|
||||
// isValidTransactionRequestType is checked at API layer. Maybe here too.
|
||||
isPositiveAmtToSend <- booleanToBox(rawAmt > BigDecimal("0"), s"Can't send a payment with a value of 0 or less. (${rawAmt})")
|
||||
|
||||
|
||||
// For now, arbitary charge value to demonstrate PSD2 charge transparency principle. Eventually this would come from Transaction Type? 10 decimal places of scaling so can add small percentage per transaction.
|
||||
chargeValue <- tryo {(BigDecimal(details.value.amount) * 0.0001).setScale(10, BigDecimal.RoundingMode.HALF_UP).toDouble} ?~! s"could not create charge for ${details.value.amount}"
|
||||
charge = TransactionRequestCharge("Total charges for completed transaction", AmountOfMoney(details.value.currency, chargeValue.toString()))
|
||||
|
||||
|
||||
|
||||
transactionRequest <- createTransactionRequestImpl210(TransactionRequestId(java.util.UUID.randomUUID().toString), transactionRequestType, fromAccount, detailsPlain, status, charge)
|
||||
} yield transactionRequest
|
||||
|
||||
//make sure we get something back
|
||||
result = Full(result.openOrThrowException("Exception: Couldn't create transactionRequest"))
|
||||
|
||||
// If no challenge necessary, create Transaction immediately and put in data store and object to return
|
||||
if (status == TransactionRequests.STATUS_COMPLETED) {
|
||||
val createdTransactionId = transactionRequestType.value match {
|
||||
case "SANDBOX_TAN" => Connector.connector.vend.makePaymentv200(initiator, BankAccountUID(fromAccount.bankId, fromAccount.accountId),
|
||||
BankAccountUID(toAccount.get.bankId, toAccount.get.accountId), BigDecimal(details.value.amount), details.asInstanceOf[TransactionRequestDetailsSandBoxTan].description)
|
||||
case "SEPA" => Empty
|
||||
}
|
||||
|
||||
//set challenge to null
|
||||
result = Full(result.get.copy(challenge = null))
|
||||
|
||||
//save transaction_id if we have one
|
||||
createdTransactionId match {
|
||||
case Full(ti) => {
|
||||
if (! createdTransactionId.isEmpty) {
|
||||
saveTransactionRequestTransaction(result.get.id, ti)
|
||||
result = Full(result.get.copy(transaction_ids = ti.value))
|
||||
}
|
||||
}
|
||||
case _ => None
|
||||
}
|
||||
} else {
|
||||
//if challenge necessary, create a new one
|
||||
var challenge = TransactionRequestChallenge(id = java.util.UUID.randomUUID().toString, allowed_attempts = 3, challenge_type = TransactionRequests.CHALLENGE_SANDBOX_TAN)
|
||||
saveTransactionRequestChallenge(result.get.id, challenge)
|
||||
result = Full(result.get.copy(challenge = challenge))
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -311,6 +377,10 @@ trait Connector {
|
||||
fromAccount : BankAccount, counterparty : BankAccount, body: TransactionRequestBody,
|
||||
status: String, charge: TransactionRequestCharge) : Box[TransactionRequest]
|
||||
|
||||
protected def createTransactionRequestImpl210(transactionRequestId: TransactionRequestId, transactionRequestType: TransactionRequestType,
|
||||
fromAccount : BankAccount, details: String,
|
||||
status: String, charge: TransactionRequestCharge) : Box[TransactionRequest210]
|
||||
|
||||
|
||||
def saveTransactionRequestTransaction(transactionRequestId: TransactionRequestId, transactionId: TransactionId) = {
|
||||
//put connector agnostic logic here if necessary
|
||||
@ -351,8 +421,33 @@ trait Connector {
|
||||
}
|
||||
}
|
||||
|
||||
def getTransactionRequests210(initiator : User, fromAccount : BankAccount) : Box[List[TransactionRequest210]] = {
|
||||
val transactionRequests =
|
||||
for {
|
||||
fromAccount <- getBankAccount(fromAccount.bankId, fromAccount.accountId) ?~
|
||||
s"account ${fromAccount.accountId} not found at bank ${fromAccount.bankId}"
|
||||
isOwner <- booleanToBox(initiator.ownerAccess(fromAccount), "user does not have access to owner view")
|
||||
transactionRequests <- getTransactionRequestsImpl210(fromAccount)
|
||||
} yield transactionRequests
|
||||
|
||||
//make sure we return null if no challenge was saved (instead of empty fields)
|
||||
if (!transactionRequests.isEmpty) {
|
||||
Full(
|
||||
transactionRequests.get.map(tr => if (tr.challenge.id == "") {
|
||||
tr.copy(challenge = null)
|
||||
} else {
|
||||
tr
|
||||
})
|
||||
)
|
||||
} else {
|
||||
transactionRequests
|
||||
}
|
||||
}
|
||||
|
||||
protected def getTransactionRequestsImpl(fromAccount : BankAccount) : Box[List[TransactionRequest]]
|
||||
|
||||
protected def getTransactionRequestsImpl210(fromAccount : BankAccount) : Box[List[TransactionRequest210]]
|
||||
|
||||
protected def getTransactionRequestImpl(transactionRequestId: TransactionRequestId) : Box[TransactionRequest]
|
||||
|
||||
def getTransactionRequestTypes(initiator : User, fromAccount : BankAccount) : Box[List[TransactionRequestType]] = {
|
||||
|
||||
@ -15,8 +15,8 @@ import code.model._
|
||||
import code.model.dataAccess._
|
||||
import code.sandbox.{CreateViewImpls, Saveable}
|
||||
import code.transaction.MappedTransaction
|
||||
import code.transactionrequests.MappedTransactionRequest
|
||||
import code.transactionrequests.TransactionRequests.{TransactionRequest, TransactionRequestBody, TransactionRequestChallenge, TransactionRequestCharge }
|
||||
import code.transactionrequests.{MappedTransactionRequest210, MappedTransactionRequest}
|
||||
import code.transactionrequests.TransactionRequests._
|
||||
import code.util.{Helper, TTLCache}
|
||||
import code.views.Views
|
||||
import net.liftweb.common._
|
||||
@ -488,6 +488,21 @@ object KafkaMappedConnector extends Connector with CreateViewImpls with Loggable
|
||||
Full(mappedTransactionRequest).flatMap(_.toTransactionRequest)
|
||||
}
|
||||
|
||||
override def createTransactionRequestImpl210(transactionRequestId: TransactionRequestId, transactionRequestType: TransactionRequestType,
|
||||
account : BankAccount, details: String,
|
||||
status: String, charge: TransactionRequestCharge) : Box[TransactionRequest210] = {
|
||||
val mappedTransactionRequest = MappedTransactionRequest210.create
|
||||
.mTransactionRequestId(transactionRequestId.value)
|
||||
.mType(transactionRequestType.value)
|
||||
.mFrom_BankId(account.bankId.value)
|
||||
.mFrom_AccountId(account.accountId.value)
|
||||
.mDetails(details)
|
||||
.mStatus(status)
|
||||
.mStartDate(now)
|
||||
.mEndDate(now).saveMe
|
||||
Full(mappedTransactionRequest).flatMap(_.toTransactionRequest210)
|
||||
}
|
||||
|
||||
override def saveTransactionRequestTransactionImpl(transactionRequestId: TransactionRequestId, transactionId: TransactionId): Box[Boolean] = {
|
||||
val mappedTransactionRequest = MappedTransactionRequest.find(By(MappedTransactionRequest.mTransactionRequestId, transactionRequestId.value))
|
||||
mappedTransactionRequest match {
|
||||
@ -524,6 +539,13 @@ object KafkaMappedConnector extends Connector with CreateViewImpls with Loggable
|
||||
Full(transactionRequests.flatMap(_.toTransactionRequest))
|
||||
}
|
||||
|
||||
override def getTransactionRequestsImpl210(fromAccount : BankAccount) : Box[List[TransactionRequest210]] = {
|
||||
val transactionRequests = MappedTransactionRequest210.findAll(By(MappedTransactionRequest210.mFrom_AccountId, fromAccount.accountId.value),
|
||||
By(MappedTransactionRequest210.mFrom_BankId, fromAccount.bankId.value))
|
||||
|
||||
Full(transactionRequests.flatMap(_.toTransactionRequest210))
|
||||
}
|
||||
|
||||
override def getTransactionRequestImpl(transactionRequestId: TransactionRequestId) : Box[TransactionRequest] = {
|
||||
val transactionRequest = MappedTransactionRequest.find(By(MappedTransactionRequest.mTransactionRequestId, transactionRequestId.value))
|
||||
transactionRequest.flatMap(_.toTransactionRequest)
|
||||
|
||||
@ -8,7 +8,7 @@ import code.metadata.counterparties.{Counterparties, Metadata, MongoCounterparti
|
||||
import code.model._
|
||||
import code.model.dataAccess._
|
||||
import code.tesobe.CashTransaction
|
||||
import code.transactionrequests.TransactionRequests.{TransactionRequest, TransactionRequestBody, TransactionRequestChallenge, TransactionRequestCharge}
|
||||
import code.transactionrequests.TransactionRequests._
|
||||
import code.util.Helper
|
||||
import com.mongodb.QueryBuilder
|
||||
import com.tesobe.model.UpdateBankAccount
|
||||
@ -314,9 +314,14 @@ private object LocalConnector extends Connector with Loggable {
|
||||
account : BankAccount, counterparty : BankAccount, body: TransactionRequestBody,
|
||||
status: String, charge: TransactionRequestCharge) : Box[TransactionRequest] = ???
|
||||
|
||||
override def createTransactionRequestImpl210(transactionRequestId: TransactionRequestId, transactionRequestType: TransactionRequestType,
|
||||
account : BankAccount, details: String,
|
||||
status: String, charge: TransactionRequestCharge) : Box[TransactionRequest210] = ???
|
||||
|
||||
override def saveTransactionRequestTransactionImpl(transactionRequestId: TransactionRequestId, transactionId: TransactionId) = ???
|
||||
override def saveTransactionRequestChallengeImpl(transactionRequestId: TransactionRequestId, challenge: TransactionRequestChallenge) = ???
|
||||
override def getTransactionRequestsImpl(fromAccount : BankAccount) : Box[List[TransactionRequest]] = ???
|
||||
override def getTransactionRequestsImpl210(fromAccount : BankAccount) : Box[List[TransactionRequest210]] = ???
|
||||
override def getTransactionRequestImpl(transactionRequestId: TransactionRequestId) : Box[TransactionRequest] = ???
|
||||
override def getTransactionRequestTypesImpl(fromAccount : BankAccount) : Box[List[TransactionRequestType]] = {
|
||||
//TODO: write logic / data access
|
||||
|
||||
@ -14,8 +14,8 @@ import code.model._
|
||||
import code.model.dataAccess._
|
||||
import code.tesobe.CashTransaction
|
||||
import code.transaction.MappedTransaction
|
||||
import code.transactionrequests.MappedTransactionRequest
|
||||
import code.transactionrequests.TransactionRequests.{TransactionRequest, TransactionRequestBody, TransactionRequestChallenge, TransactionRequestCharge}
|
||||
import code.transactionrequests.{MappedTransactionRequest210, MappedTransactionRequest}
|
||||
import code.transactionrequests.TransactionRequests._
|
||||
import code.util.Helper
|
||||
import com.tesobe.model.UpdateBankAccount
|
||||
import net.liftweb.common.{Box, Failure, Full, Loggable}
|
||||
@ -250,6 +250,25 @@ object LocalMappedConnector extends Connector with Loggable {
|
||||
Full(mappedTransactionRequest).flatMap(_.toTransactionRequest)
|
||||
}
|
||||
|
||||
override def createTransactionRequestImpl210(transactionRequestId: TransactionRequestId, transactionRequestType: TransactionRequestType,
|
||||
account : BankAccount, details: String,
|
||||
status: String, charge: TransactionRequestCharge) : Box[TransactionRequest210] = {
|
||||
val mappedTransactionRequest = MappedTransactionRequest210.create
|
||||
.mTransactionRequestId(transactionRequestId.value)
|
||||
.mType(transactionRequestType.value)
|
||||
.mFrom_BankId(account.bankId.value)
|
||||
.mFrom_AccountId(account.accountId.value)
|
||||
.mDetails(details)
|
||||
.mStatus(status)
|
||||
.mStartDate(now)
|
||||
.mEndDate(now)
|
||||
.mCharge_Summary(charge.summary)
|
||||
.mCharge_Amount(charge.value.amount)
|
||||
.mCharge_Currency(charge.value.currency)
|
||||
.saveMe
|
||||
Full(mappedTransactionRequest).flatMap(_.toTransactionRequest210)
|
||||
}
|
||||
|
||||
override def saveTransactionRequestTransactionImpl(transactionRequestId: TransactionRequestId, transactionId: TransactionId): Box[Boolean] = {
|
||||
val mappedTransactionRequest = MappedTransactionRequest.find(By(MappedTransactionRequest.mTransactionRequestId, transactionRequestId.value))
|
||||
mappedTransactionRequest match {
|
||||
@ -286,6 +305,13 @@ object LocalMappedConnector extends Connector with Loggable {
|
||||
Full(transactionRequests.flatMap(_.toTransactionRequest))
|
||||
}
|
||||
|
||||
override def getTransactionRequestsImpl210(fromAccount : BankAccount) : Box[List[TransactionRequest210]] = {
|
||||
val transactionRequests = MappedTransactionRequest210.findAll(By(MappedTransactionRequest210.mFrom_AccountId, fromAccount.accountId.value),
|
||||
By(MappedTransactionRequest210.mFrom_BankId, fromAccount.bankId.value))
|
||||
|
||||
Full(transactionRequests.flatMap(_.toTransactionRequest210))
|
||||
}
|
||||
|
||||
override def getTransactionRequestImpl(transactionRequestId: TransactionRequestId) : Box[TransactionRequest] = {
|
||||
val transactionRequest = MappedTransactionRequest.find(By(MappedTransactionRequest.mTransactionRequestId, transactionRequestId.value))
|
||||
transactionRequest.flatMap(_.toTransactionRequest)
|
||||
|
||||
@ -102,4 +102,68 @@ class MappedTransactionRequest extends LongKeyedMapper[MappedTransactionRequest]
|
||||
|
||||
object MappedTransactionRequest extends MappedTransactionRequest with LongKeyedMetaMapper[MappedTransactionRequest] {
|
||||
override def dbIndexes = UniqueIndex(mTransactionRequestId) :: super.dbIndexes
|
||||
}
|
||||
|
||||
class MappedTransactionRequest210 extends LongKeyedMapper[MappedTransactionRequest210] with IdPK with CreatedUpdated {
|
||||
|
||||
private val logger = Logger(classOf[MappedTransactionRequest210])
|
||||
|
||||
override def getSingleton = MappedTransactionRequest210
|
||||
|
||||
object mTransactionRequestId extends DefaultStringField(this)
|
||||
object mType extends DefaultStringField(this)
|
||||
object mFrom_BankId extends DefaultStringField(this)
|
||||
object mFrom_AccountId extends DefaultStringField(this)
|
||||
|
||||
//details fields
|
||||
object mDetails extends DefaultStringField(this)
|
||||
|
||||
object mTransactionIDs extends DefaultStringField(this)
|
||||
object mStatus extends DefaultStringField(this)
|
||||
object mStartDate extends MappedDate(this)
|
||||
object mEndDate extends MappedDate(this)
|
||||
object mChallenge_Id extends DefaultStringField(this)
|
||||
object mChallenge_AllowedAttempts extends MappedInt(this)
|
||||
object mChallenge_ChallengeType extends DefaultStringField(this)
|
||||
|
||||
object mCharge_Summary extends DefaultStringField(this)
|
||||
object mCharge_Amount extends DefaultStringField(this)
|
||||
object mCharge_Currency extends DefaultStringField(this)
|
||||
|
||||
def toTransactionRequest210 : Option[TransactionRequest210] = {
|
||||
val t_from = TransactionRequestAccount (
|
||||
bank_id = mFrom_BankId.get,
|
||||
account_id = mFrom_AccountId.get
|
||||
)
|
||||
|
||||
val t_challenge = TransactionRequestChallenge (
|
||||
id = mChallenge_Id,
|
||||
allowed_attempts = mChallenge_AllowedAttempts,
|
||||
challenge_type = mChallenge_ChallengeType
|
||||
)
|
||||
|
||||
val t_charge = TransactionRequestCharge (
|
||||
summary = mCharge_Summary,
|
||||
value = AmountOfMoney(currency = mCharge_Currency, amount = mCharge_Amount)
|
||||
)
|
||||
|
||||
Some(
|
||||
TransactionRequest210(
|
||||
id = TransactionRequestId(mTransactionRequestId.get),
|
||||
`type`= mType.get,
|
||||
from = t_from,
|
||||
details = mDetails.get,
|
||||
status = mStatus.get,
|
||||
transaction_ids = mTransactionIDs.get,
|
||||
start_date = mStartDate.get,
|
||||
end_date = mEndDate.get,
|
||||
challenge = t_challenge,
|
||||
charge = t_charge
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
object MappedTransactionRequest210 extends MappedTransactionRequest210 with LongKeyedMetaMapper[MappedTransactionRequest210] {
|
||||
override def dbIndexes = UniqueIndex(mTransactionRequestId) :: super.dbIndexes
|
||||
}
|
||||
@ -38,6 +38,19 @@ object TransactionRequests extends SimpleInjector {
|
||||
val charge: TransactionRequestCharge
|
||||
)
|
||||
|
||||
case class TransactionRequest210 (
|
||||
val id: TransactionRequestId,
|
||||
val `type` : String,
|
||||
val from: TransactionRequestAccount,
|
||||
val details: String,
|
||||
val transaction_ids: String,
|
||||
val status: String,
|
||||
val start_date: Date,
|
||||
val end_date: Date,
|
||||
val challenge: TransactionRequestChallenge,
|
||||
val charge: TransactionRequestCharge
|
||||
)
|
||||
|
||||
case class TransactionRequestChallenge (
|
||||
val id: String,
|
||||
val allowed_attempts : Int,
|
||||
@ -55,6 +68,21 @@ object TransactionRequests extends SimpleInjector {
|
||||
val description : String
|
||||
)
|
||||
|
||||
trait TransactionRequestDetails {
|
||||
val value: AmountOfMoney
|
||||
}
|
||||
|
||||
case class TransactionRequestDetailsSandBoxTan (
|
||||
val to: TransactionRequestAccount,
|
||||
val value : AmountOfMoney,
|
||||
val description : String
|
||||
) extends TransactionRequestDetails
|
||||
|
||||
case class TransactionRequestDetailsSEPA (
|
||||
val value : AmountOfMoney,
|
||||
val description : String
|
||||
) extends TransactionRequestDetails
|
||||
|
||||
val transactionRequestProvider = new Inject(buildOne _) {}
|
||||
|
||||
def buildOne: TransactionRequestProvider =
|
||||
|
||||
@ -8,7 +8,7 @@ import code.bankconnectors.{Connector, OBPQueryParam}
|
||||
import code.management.ImporterAPI.ImporterTransaction
|
||||
import code.model.{PhysicalCard, Consumer => OBPConsumer, Token => OBPToken, _}
|
||||
import code.tesobe.CashTransaction
|
||||
import code.transactionrequests.TransactionRequests.{TransactionRequest, TransactionRequestBody, TransactionRequestChallenge, TransactionRequestCharge}
|
||||
import code.transactionrequests.TransactionRequests._
|
||||
import net.liftweb.common.{Box, Empty, Failure, Loggable}
|
||||
|
||||
class PhysicalCardsTest extends ServerSetup with DefaultUsers {
|
||||
@ -95,11 +95,17 @@ class PhysicalCardsTest extends ServerSetup with DefaultUsers {
|
||||
status: String, charge: TransactionRequestCharge) : Box[TransactionRequest] = {
|
||||
Failure("not supported")
|
||||
}
|
||||
override def createTransactionRequestImpl210(transactionRequestId: TransactionRequestId, transactionRequestType: TransactionRequestType,
|
||||
account : BankAccount, details: String,
|
||||
status: String, charge: TransactionRequestCharge) : Box[TransactionRequest210] = {
|
||||
Failure("not supported")
|
||||
}
|
||||
override def saveTransactionRequestTransactionImpl(transactionRequestId: TransactionRequestId, transactionId: TransactionId) = ???
|
||||
override def saveTransactionRequestChallengeImpl(transactionRequestId: TransactionRequestId, challenge: TransactionRequestChallenge) = ???
|
||||
override def saveTransactionRequestStatusImpl(transactionRequestId: TransactionRequestId, status: String): Box[Boolean] = ???
|
||||
|
||||
override def getTransactionRequestsImpl(fromAccount : BankAccount) : Box[List[TransactionRequest]] = ???
|
||||
override def getTransactionRequestsImpl210(fromAccount : BankAccount) : Box[List[TransactionRequest210]] = ???
|
||||
override def getTransactionRequestImpl(transactionRequestId: TransactionRequestId) : Box[TransactionRequest] = ???
|
||||
override def getTransactionRequestTypesImpl(fromAccount : BankAccount) : Box[List[TransactionRequestType]] = {
|
||||
Failure("not supported")
|
||||
|
||||
Loading…
Reference in New Issue
Block a user