Closes issue #124 + issue #132

This commit is contained in:
sorinmanole 2016-08-15 12:24:33 +02:00
parent a7f12d0a00
commit a59d7fe783
11 changed files with 567 additions and 13 deletions

View File

@ -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,

View File

@ -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."))
}
}
}
}
}

View File

@ -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))
}
}

View File

@ -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 => {

View File

@ -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]] = {

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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
}

View File

@ -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 =

View File

@ -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")