refactor/removed v140, v200 payments: createTransactionRequest, getTransactionRequests and answerTransactionRequestChallenge

This commit is contained in:
hongwei 2024-10-24 13:08:18 +02:00
parent 5c0107dc75
commit dce8cd0b62
12 changed files with 3 additions and 2731 deletions

View File

@ -696,7 +696,6 @@ object ErrorMessages {
val InvalidConnectorResponseForGetChargeLevel = "OBP-50207: Connector did not return the set of challenge level we requested."
val InvalidConnectorResponseForCreateTransactionRequestImpl210 = "OBP-50208: Connector did not return the set of transactions requests we requested."
val InvalidConnectorResponseForMakePayment = "OBP-50209: Connector did not return the set of transactions we requested."
val InvalidConnectorResponseForMakePaymentv200 = "OBP-50210: Connector did not return the set of transaction id we requested."
val InvalidConnectorResponseForGetCheckbookOrdersFuture = "OBP-50211: Connector did not return the set of check book."
val InvalidConnectorResponseForGetStatusOfCreditCardOrderFuture = "OBP-50212: Connector did not return the set of status of credit card."
val InvalidConnectorResponseForCreateTransactionAfterChallengev300 = "OBP-50213: The Connector did not return a valid response for payments."

View File

@ -473,199 +473,7 @@ trait APIMethods140 extends MdcLoggable with APIMethods130 with APIMethods121{
}
}
}
resourceDocs += ResourceDoc(
getTransactionRequests,
apiVersion,
"getTransactionRequests",
"GET",
"/banks/BANK_ID/accounts/ACCOUNT_ID/VIEW_ID/transaction-requests",
"Get all Transaction Requests",
"",
EmptyBody,
transactionRequestJson,
List(
UserNotLoggedIn,
BankNotFound,
AccountNotFound,
"Current user does not have access to the view",
"account not found at bank",
"user does not have access to owner view",
UnknownError),
List(apiTagTransactionRequest, apiTagPsd2, apiTagOldStyle))
lazy val getTransactionRequests: OBPEndpoint = {
case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: ViewId(viewId) :: "transaction-requests" :: Nil JsonGet _ => {
cc =>
if (APIUtil.getPropsAsBoolValue("transactionRequests_enabled", false)) {
for {
u <- cc.user ?~ ErrorMessages.UserNotLoggedIn
(bank, callContext ) <- BankX(bankId, Some(cc)) ?~! {ErrorMessages.BankNotFound}
fromAccount <- BankAccountX(bankId, accountId) ?~! {ErrorMessages.AccountNotFound}
view <- APIUtil.checkViewAccessAndReturnView(viewId, BankIdAccountId(bankId, accountId), Some(u), callContext)
_ <- Helper.booleanToBox(
view.canSeeTransactionRequests,
s"${ErrorMessages.ViewDoesNotPermitAccess} You need the `${StringHelpers.snakify(nameOf(ViewDefinition.canSeeTransactionRequests_)).dropRight(1)}` permission on the View(${viewId.value})"
)
transactionRequests <- Connector.connector.vend.getTransactionRequests(u, fromAccount, callContext)
oldTransactionRequest = transactionRequests.map(transforOldTransactionRequest(_).head)
}
yield {
val successJson = Extraction.decompose(oldTransactionRequest)
successJsonResponse(successJson)
}
} else {
Full(errorJsonResponse(TransactionRequestsNotEnabled))
}
}
}
case class TransactionIdJson(transaction_id : String)
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",
"""Initiate a Payment via a Transaction Request.
|
|This is the preferred method to create a payment and supersedes makePayment in 1.2.1.
|
|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 < 100 the transaction request will create a transaction without a challenge, else a challenge will need to be answered.
|If a challenge is created you must answer it using Answer Transaction Request Challenge before the Transaction is created.
|
|Please see later versions of this call in 2.0.0 or 2.1.0.
|""",
transactionRequestBodyJsonV140,
transactionRequestJson,
List(
UserNotLoggedIn,
InvalidJsonFormat,
BankNotFound,
AccountNotFound,
CounterpartyNotFound,
"Counterparty and holder accounts have differing currencies",
"Request currency and holder account currency can't be different.",
"Amount not convertible to number",
"account ${fromAccount.accountId} not found at bank ${fromAccount.bankId}",
"user does not have access to owner view",
"amount ${body.value.amount} not convertible to number",
"Cannot send payment to account with different currency",
"Can't send a payment with a value of 0 or less.",
TransactionRequestsNotEnabled,
UnknownError),
List(apiTagTransactionRequest, apiTagPsd2, apiTagOldStyle))
lazy val createTransactionRequest: OBPEndpoint = {
case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: ViewId(viewId) :: "transaction-request-types" ::
TransactionRequestType(transactionRequestType) :: "transaction-requests" :: Nil JsonPost json -> _ => {
cc =>
if (APIUtil.getPropsAsBoolValue("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 <- cc.user ?~ ErrorMessages.UserNotLoggedIn
transBodyJson <- tryo{json.extract[TransactionRequestBodyJsonV140]} ?~ {ErrorMessages.InvalidJsonFormat}
transBody <- tryo{getTransactionRequestBodyFromJson(transBodyJson)}
(bank, callContext ) <- BankX(bankId, Some(cc)) ?~! {ErrorMessages.BankNotFound}
fromAccount <- BankAccountX(bankId, accountId) ?~! {ErrorMessages.AccountNotFound}
_ <- APIUtil.checkAuthorisationToCreateTransactionRequest(viewId : ViewId, BankIdAccountId(bankId, accountId), u: User, callContext: Option[CallContext]) ?~! {
s"$InsufficientAuthorisationToCreateTransactionRequest " +
s"Current ViewId(${viewId.value})," +
s"current UserId(${u.userId})" +
s"current ConsumerId(${callContext.map (_.consumer.map (_.consumerId.get).getOrElse ("")).getOrElse ("")})"
}
toBankId <- tryo(BankId(transBodyJson.to.bank_id))
toAccountId <- tryo(AccountId(transBodyJson.to.account_id))
toAccount <- BankAccountX(toBankId, toAccountId) ?~! {ErrorMessages.CounterpartyNotFound}
_ <- tryo(assert(fromAccount.currency == toAccount.currency)) ?~! {"Counterparty and holder accounts have differing currencies."}
_ <- tryo(assert(transBodyJson.value.currency == fromAccount.currency)) ?~! {"Request currency and holder account currency can't be different."}
_ <- tryo {BigDecimal(transBodyJson.value.amount)} ?~! s"Amount ${transBodyJson.value.amount} not convertible to number"
createdTransactionRequest <- Connector.connector.vend.createTransactionRequest(u, fromAccount, toAccount, transactionRequestType, transBody, callContext)
oldTransactionRequest <- transforOldTransactionRequest(createdTransactionRequest)
} yield {
val json = Extraction.decompose(oldTransactionRequest)
createdJsonResponse(json)
}
} else {
Full(errorJsonResponse(TransactionRequestsNotEnabled))
}
}
}
resourceDocs += ResourceDoc(
answerTransactionRequestChallenge,
apiVersion,
"answerTransactionRequestChallenge",
"POST",
"/banks/BANK_ID/accounts/ACCOUNT_ID/VIEW_ID/transaction-request-types/TRANSACTION_REQUEST_TYPE/transaction-requests/TRANSACTION_REQUEST_ID/challenge",
"Answer Transaction Request Challenge.",
"""
|In Sandbox mode, any string that can be converted to a possitive integer will be accepted as an answer.
|
""".stripMargin,
challengeAnswerJSON,
transactionRequestJson,
List(
UserNotLoggedIn,
BankNotFound,
BankAccountNotFound,
InvalidJsonFormat,
"Current user does not have access to the view ",
"Couldn't create Transaction",
TransactionRequestsNotEnabled,
"Need a non-empty answer",
"Need a numeric TAN",
"Need a positive TAN",
"unknown challenge type",
"Sorry, you've used up your allowed attempts.",
"Error getting Transaction Request",
"Transaction Request not found",
"Couldn't create Transaction",
UnknownError),
List(apiTagTransactionRequest, apiTagPsd2, apiTagOldStyle))
lazy val answerTransactionRequestChallenge: OBPEndpoint = {
case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: ViewId(viewId) :: "transaction-request-types" ::
TransactionRequestType(transactionRequestType) :: "transaction-requests" :: TransactionRequestId(transReqId) :: "challenge" :: Nil JsonPost json -> _ => {
cc =>
if (APIUtil.getPropsAsBoolValue("transactionRequests_enabled", false)) {
for {
u <- cc.user ?~ ErrorMessages.UserNotLoggedIn
(bank, callContext ) <- BankX(bankId, Some(cc)) ?~! {ErrorMessages.BankNotFound}
fromAccount <- BankAccountX(bankId, accountId) ?~! BankAccountNotFound
_ <- APIUtil.checkAuthorisationToCreateTransactionRequest(viewId: ViewId, BankIdAccountId(bankId, accountId), u: User, callContext: Option[CallContext]) ?~! {
s"$InsufficientAuthorisationToCreateTransactionRequest " +
s"Current ViewId(${viewId.value})," +
s"current UserId(${u.userId})" +
s"current ConsumerId(${callContext.map(_.consumer.map(_.consumerId.get).getOrElse("")).getOrElse("")})"
}
answerJson <- tryo{json.extract[ChallengeAnswerJSON]} ?~ InvalidJsonFormat
//TODO check more things here
_ <- Connector.connector.vend.answerTransactionRequestChallenge(transReqId, answerJson.answer, callContext)
//create transaction and insert its id into the transaction request
transactionRequest <- Connector.connector.vend.createTransactionAfterChallenge(u, transReqId, callContext)
oldTransactionRequest <- transforOldTransactionRequest(transactionRequest)
} yield {
val successJson = Extraction.decompose(oldTransactionRequest)
successJsonResponse(successJson, 202)
}
} else {
Full(errorJsonResponse(TransactionRequestsNotEnabled))
}
}
}
resourceDocs += ResourceDoc(
addCustomer,
apiVersion,

View File

@ -103,10 +103,7 @@ object OBPAPI1_4_0 extends OBPRestHelper with APIMethods140 with MdcLoggable wit
Implementations1_4_0.getAtms,
Implementations1_4_0.getProducts,
Implementations1_4_0.getCrmEvents,
Implementations1_4_0.createTransactionRequest,
Implementations1_4_0.getTransactionRequests,
Implementations1_4_0.getTransactionRequestTypes,
Implementations1_4_0.answerTransactionRequestChallenge,
Implementations1_4_0.testResourceDoc
)

View File

@ -1296,257 +1296,6 @@ trait APIMethods200 {
import net.liftweb.json.JsonAST._
val exchangeRates = prettyRender(decompose(fx.fallbackExchangeRates))
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 the Transaction Request will be set to INITIALISED and a challenge will need to be answered.|
|If a challenge is created you must answer it using Answer Transaction Request Challenge before the Transaction is created.
|
|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)}
|
|""".stripMargin,
transactionRequestBodyJsonV200,
transactionRequestWithChargesJson,
List(
UserNotLoggedIn,
InvalidJsonFormat,
InvalidBankIdFormat,
InvalidAccountIdFormat,
BankNotFound,
AccountNotFound,
ViewNotFound,
UserNoPermissionAccessView,
InsufficientAuthorisationToCreateTransactionRequest,
CounterpartyNotFound,
InvalidTransactionRequestType,
InvalidTransactionRequestCurrency,
TransactionDisabled,
UnknownError
),
List(apiTagTransactionRequest, apiTagPsd2, apiTagOldStyle),
Some(List(canCreateAnyTransactionRequest)))
lazy val createTransactionRequest: OBPEndpoint = {
case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: ViewId(viewId) :: "transaction-request-types" ::
TransactionRequestType(transactionRequestType) :: "transaction-requests" :: Nil JsonPost json -> _ => {
cc =>
if (APIUtil.getPropsAsBoolValue("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 <- cc.user ?~! ErrorMessages.UserNotLoggedIn
transBodyJson <- tryo{json.extract[TransactionRequestBodyJsonV200]} ?~! InvalidJsonFormat
transBody <- tryo{getTransactionRequestBodyFromJson(transBodyJson)}
_ <- tryo(assert(isValidID(bankId.value)))?~! InvalidBankIdFormat
_ <- tryo(assert(isValidID(accountId.value)))?~! InvalidAccountIdFormat
(bank, callContext ) <- BankX(bankId, Some(cc)) ?~! BankNotFound
fromAccount <- BankAccountX(bankId, accountId) ?~! AccountNotFound
_ <- APIUtil.checkAuthorisationToCreateTransactionRequest(viewId: ViewId, BankIdAccountId(bankId, accountId), u: User, callContext: Option[CallContext]) ?~! {
s"$InsufficientAuthorisationToCreateTransactionRequest " +
s"Current ViewId(${viewId.value})," +
s"current UserId(${u.userId})" +
s"current ConsumerId(${callContext.map(_.consumer.map(_.consumerId.get).getOrElse("")).getOrElse("")})"
}
toBankId <- tryo(BankId(transBodyJson.to.bank_id))
toAccountId <- tryo(AccountId(transBodyJson.to.account_id))
toAccount <- BankAccountX(toBankId, toAccountId) ?~! {ErrorMessages.CounterpartyNotFound}
// Prevent default value for transaction request type (at least).
// Get Transaction Request Types from Props "transactionRequests_supported_types". Default is empty string
validTransactionRequestTypes <- tryo{APIUtil.getPropsValue("transactionRequests_supported_types", "")}
// Use a list instead of a string to avoid partial matches
validTransactionRequestTypesList <- tryo{validTransactionRequestTypes.split(",")}
_ <- tryo(assert(transactionRequestType.value != "TRANSACTION_REQUEST_TYPE" && validTransactionRequestTypesList.contains(transactionRequestType.value))) ?~! s"${InvalidTransactionRequestType} : Invalid value is: '${transactionRequestType.value}' Valid values are: ${validTransactionRequestTypes}"
_ <- tryo(assert(transBodyJson.value.currency == fromAccount.currency)) ?~! InvalidTransactionRequestCurrency
createdTransactionRequest <- Connector.connector.vend.createTransactionRequestv200(u, fromAccount, toAccount, transactionRequestType, transBody, callContext)
} yield {
// Explicitly format as v2.0.0 json
val json = JSONFactory200.createTransactionRequestWithChargeJSON(createdTransactionRequest)
createdJsonResponse(Extraction.decompose(json))
}
} else {
Full(errorJsonResponse(TransactionDisabled))
}
}
}
resourceDocs += ResourceDoc(
answerTransactionRequestChallenge,
apiVersion,
"answerTransactionRequestChallenge",
"POST",
"/banks/BANK_ID/accounts/ACCOUNT_ID/VIEW_ID/transaction-request-types/TRANSACTION_REQUEST_TYPE/transaction-requests/TRANSACTION_REQUEST_ID/challenge",
"Answer Transaction Request Challenge",
"""
|In Sandbox mode, any string that can be converted to a positive integer will be accepted as an answer.
|
""".stripMargin,
ChallengeAnswerJSON("89123812", "123345"),
transactionRequestWithChargeJson,
List(
UserNotLoggedIn,
InvalidAccountIdFormat,
InvalidBankIdFormat,
BankNotFound,
UserNoPermissionAccessView,
InvalidJsonFormat,
InvalidTransactionRequestId,
TransactionRequestTypeHasChanged,
InvalidTransactionRequestChallengeId,
TransactionRequestStatusNotInitiated,
TransactionDisabled,
UnknownError
),
List(apiTagTransactionRequest, apiTagPsd2, apiTagOldStyle))
lazy val answerTransactionRequestChallenge: OBPEndpoint = {
case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: ViewId(viewId) :: "transaction-request-types" ::
TransactionRequestType(transactionRequestType) :: "transaction-requests" :: TransactionRequestId(transReqId) :: "challenge" :: Nil JsonPost json -> _ => {
cc =>
if (APIUtil.getPropsAsBoolValue("transactionRequests_enabled", false)) {
for {
u <- cc.user ?~! ErrorMessages.UserNotLoggedIn
_ <- tryo(assert(isValidID(accountId.value)))?~! ErrorMessages.InvalidAccountIdFormat
_ <- tryo(assert(isValidID(bankId.value)))?~! ErrorMessages.InvalidBankIdFormat
(bank, callContext ) <- BankX(bankId, Some(cc)) ?~! BankNotFound
fromAccount <- BankAccountX(bankId, accountId) ?~! AccountNotFound
_ <- APIUtil.checkAuthorisationToCreateTransactionRequest(viewId: ViewId, BankIdAccountId(bankId, accountId), u: User, callContext: Option[CallContext]) ?~! {
s"$InsufficientAuthorisationToCreateTransactionRequest " +
s"Current ViewId(${viewId.value})," +
s"current UserId(${u.userId})" +
s"current ConsumerId(${callContext.map(_.consumer.map(_.consumerId.get).getOrElse("")).getOrElse("")})"
}
answerJson <- tryo{json.extract[ChallengeAnswerJSON]} ?~! InvalidJsonFormat
_ <- Connector.connector.vend.answerTransactionRequestChallenge(transReqId, answerJson.answer, callContext)
//check the transReqId validation.
(existingTransactionRequest, callContext) <- Connector.connector.vend.getTransactionRequestImpl(transReqId, callContext) ?~! s"${ErrorMessages.InvalidTransactionRequestId} : $transReqId"
//check the input transactionRequestType is same as when the user create the existingTransactionRequest
existingTransactionRequestType = existingTransactionRequest.`type`
_ <- booleanToBox(existingTransactionRequestType.equals(transactionRequestType.value),s"${ErrorMessages.TransactionRequestTypeHasChanged} It should be :'$existingTransactionRequestType' ")
//check the challenge id is same as when the user create the existingTransactionRequest
_ <- booleanToBox(existingTransactionRequest.challenge.id.equals(answerJson.id),{ErrorMessages.InvalidTransactionRequestChallengeId})
//check the challenge statue whether is initiated, only retreive INITIATED transaction requests.
_ <- booleanToBox(existingTransactionRequest.status.equals("INITIATED"),ErrorMessages.TransactionRequestStatusNotInitiated)
toBankId = BankId(existingTransactionRequest.body.to_sandbox_tan.get.bank_id)
toAccountId = AccountId(existingTransactionRequest.body.to_sandbox_tan.get.account_id)
toAccount <- BankAccountX(toBankId, toAccountId) ?~! s"$AccountNotFound,toBankId($toBankId) and toAccountId($toAccountId) is invalid ."
//create transaction and insert its id into the transaction request
transactionRequest <- Connector.connector.vend.createTransactionAfterChallengev200(fromAccount, toAccount, existingTransactionRequest, callContext)
} yield {
// Format explicitly as v2.0.0 json
val json = JSONFactory200.createTransactionRequestWithChargeJSON(transactionRequest)
//successJsonResponse(Extraction.decompose(json))
val successJson = Extraction.decompose(json)
successJsonResponse(successJson, 202)
}
} else {
Full(errorJsonResponse(TransactionDisabled))
}
}
}
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
|* Body including To Account, Currency, Value, Description and other initiation information. (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,
EmptyBody,
transactionRequestWithChargesJson,
List(UserNotLoggedIn, BankNotFound, AccountNotFound, UserNoPermissionAccessView, UnknownError),
List(apiTagTransactionRequest, apiTagPsd2, apiTagOldStyle))
lazy val getTransactionRequests: OBPEndpoint = {
case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: ViewId(viewId) :: "transaction-requests" :: Nil JsonGet _ => {
cc =>
if (APIUtil.getPropsAsBoolValue("transactionRequests_enabled", false)) {
for {
u <- cc.user ?~! UserNotLoggedIn
(bank, callContext ) <- BankX(bankId, Some(cc)) ?~! BankNotFound
fromAccount <- BankAccountX(bankId, accountId) ?~! AccountNotFound
view <-APIUtil.checkViewAccessAndReturnView(viewId, BankIdAccountId(fromAccount.bankId, fromAccount.accountId), Some(u), callContext)
_ <- Helper.booleanToBox(view.canSeeTransactionRequests,
s"${ErrorMessages.ViewDoesNotPermitAccess} You need the `${StringHelpers.snakify(nameOf(ViewDefinition.canSeeTransactionRequests_)).dropRight(1)}` permission on the View(${viewId.value} )")
transactionRequests <- Connector.connector.vend.getTransactionRequests(u, fromAccount, callContext)
}
yield {
// Format the data as V2.0.0 json
val json = JSONFactory200.createTransactionRequestJSONs(transactionRequests)
successJsonResponse(Extraction.decompose(json))
}
} else {
Full(errorJsonResponse(TransactionDisabled))
}
}
}
resourceDocs += ResourceDoc(
createUser,
apiVersion,

View File

@ -146,9 +146,6 @@ object OBPAPI2_0_0 extends OBPRestHelper with APIMethods130 with APIMethods140 w
Implementations2_0_0.corePrivateAccountsAtOneBank, // this is /my accounts
Implementations2_0_0.privateAccountsAtOneBank, // This was missing for a while from v2.0.0
Implementations2_0_0.publicAccountsAtOneBank,
Implementations2_0_0.createTransactionRequest,
Implementations2_0_0.answerTransactionRequestChallenge,
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,

View File

@ -671,37 +671,6 @@ trait Connector extends MdcLoggable {
callContext: Option[CallContext]
): OBPReturnType[Box[PhysicalCardTrait]] = Future{(Failure{setUnimplementedError(nameOf(updatePhysicalCard _))}, callContext)}
/**
* \
*
* @param initiator The user attempting to make the payment
* @param fromAccountUID The unique identifier of the account sending money
* @param toAccountUID The unique identifier of the account receiving money
* @param amt The amount of money to send ( > 0 )
* @return The id of the sender's new transaction,
*/
def makePayment(initiator : User, fromAccountUID : BankIdAccountId, toAccountUID : BankIdAccountId,
amt : BigDecimal, description : String, transactionRequestType: TransactionRequestType, callContext: Option[CallContext]) : Box[TransactionId] =
Failure(setUnimplementedError(nameOf(makePayment _)))
/**
* \
*
* @param fromAccount The unique identifier of the account sending money
* @param toAccount The unique identifier of the account receiving money
* @param amount The amount of money to send ( > 0 )
* @param transactionRequestType user input: SEPA, SANDBOX_TAN, FREE_FORM, COUNTERPARTY
* @return The id of the sender's new transaction,
*/
def makePaymentv200(fromAccount: BankAccount,
toAccount: BankAccount,
transactionRequestCommonBody: TransactionRequestCommonBodyJSON,
amount: BigDecimal,
description: String,
transactionRequestType: TransactionRequestType,
chargePolicy: String,
callContext: Option[CallContext]): Box[TransactionId] =
Failure(setUnimplementedError(nameOf(makePayment _)))
//Note: introduce v210 here, is for kafka connectors, use callContext and return Future.
def makePaymentv210(fromAccount: BankAccount,
@ -721,20 +690,6 @@ trait Connector extends MdcLoggable {
callContext: Option[CallContext]): OBPReturnType[Box[DoubleEntryTransaction]]= Future{(Failure(setUnimplementedError(nameOf(saveDoubleEntryBookTransaction _))), callContext)}
def getBalancingTransaction(transactionId: TransactionId,
callContext: Option[CallContext]): OBPReturnType[Box[DoubleEntryTransaction]]= Future{(Failure(setUnimplementedError(nameOf(getBalancingTransaction _))), callContext)}
/*
Transaction Requests
*/
// This is used for 1.4.0 See createTransactionRequestv200 for 2.0.0
def createTransactionRequest(initiator : User, fromAccount : BankAccount, toAccount: BankAccount, transactionRequestType: TransactionRequestType, body: TransactionRequestBody, callContext: Option[CallContext]) : Box[TransactionRequest] =
(Failure(setUnimplementedError(nameOf(createTransactionRequest _))), callContext)
def createTransactionRequestv200(initiator : User, fromAccount : BankAccount, toAccount: BankAccount, transactionRequestType: TransactionRequestType, body: TransactionRequestBody,
callContext: Option[CallContext]) : Box[TransactionRequest] = (Failure(setUnimplementedError(nameOf(createTransactionRequestv200 _))), callContext)
// Set initial status
def getStatus(challengeThresholdAmount: BigDecimal, transactionRequestCommonBodyAmount: BigDecimal, transactionRequestType: TransactionRequestType, callContext: Option[CallContext]): OBPReturnType[Box[TransactionRequestStatus.Value]] =
@ -838,18 +793,6 @@ trait Connector extends MdcLoggable {
def getTransactionRequestTypes(initiator : User, fromAccount : BankAccount, callContext: Option[CallContext]) : Box[List[TransactionRequestType]] =Failure(setUnimplementedError(nameOf(createChallengesC3 _)))
//Note: Now we use validateChallengeAnswer instead, new methods validate over kafka, and move the allowed_attempts guard into API level.
//It is only used for V140 and V200, has been deprecated from V210.
@deprecated
def answerTransactionRequestChallenge(transReqId: TransactionRequestId, answer: String, callContext: Option[CallContext]) :Box[Boolean] =
Failure(setUnimplementedError(nameOf(answerTransactionRequestChallenge _)))
def createTransactionAfterChallenge(initiator: User, transReqId: TransactionRequestId, callContext: Option[CallContext]) : Box[TransactionRequest] =
Failure(setUnimplementedError(nameOf(getBalancingTransaction _)))
def createTransactionAfterChallengev200(fromAccount: BankAccount, toAccount: BankAccount, transactionRequest: TransactionRequest, callContext: Option[CallContext]): Box[TransactionRequest] =
Failure(setUnimplementedError(nameOf(createTransactionAfterChallengev200 _)))
def createTransactionAfterChallengeV210(fromAccount: BankAccount, transactionRequest: TransactionRequest, callContext: Option[CallContext]) : OBPReturnType[Box[TransactionRequest]] =
Future{(Failure(setUnimplementedError(nameOf(createTransactionAfterChallengeV210 _))), callContext)}

View File

@ -382,10 +382,7 @@ object ConnectorBuilderUtil {
*/
val specialMethods = List(
"getPhysicalCards",
"makePayment",
"makePaymentv200",
"createTransactionRequest",
"createTransactionRequestv200",
"getStatus",
"getChargeValue",
"saveTransactionRequestTransaction",
@ -394,7 +391,6 @@ object ConnectorBuilderUtil {
"getTransactionRequestStatuses",
"getTransactionRequestTypes",
"createTransactionAfterChallenge",
"createTransactionAfterChallengev200",
"updateAccountLabel",
"getProduct",
"createOrUpdateBranch",

View File

@ -4196,219 +4196,11 @@ object LocalMappedConnector extends Connector with MdcLoggable {
val result: Box[(Transaction, Option[CallContext])] = getTransactionLegacy(bankId, accountId, transactionId, callContext)
Future(result.map(_._1), result.map(_._2).getOrElse(callContext))
}
//Payments api: just return Failure("not supported") from makePaymentImpl if you don't want to implement it
/**
* \
*
* @param initiator The user attempting to make the payment
* @param fromAccountUID The unique identifier of the account sending money
* @param toAccountUID The unique identifier of the account receiving money
* @param amt The amount of money to send ( > 0 )
* @return The id of the sender's new transaction,
*/
override def makePayment(initiator: User, fromAccountUID: BankIdAccountId, toAccountUID: BankIdAccountId,
amt: BigDecimal, description: String, transactionRequestType: TransactionRequestType,
callContext: Option[CallContext]): Box[TransactionId] = {
for {
(fromAccount, callContext) <- getBankAccountLegacy(fromAccountUID.bankId, fromAccountUID.accountId,callContext)?~
s"$BankAccountNotFound Account ${fromAccountUID.accountId} not found at bank ${fromAccountUID.bankId}"
(toAccount, callContext)<- getBankAccountLegacy(toAccountUID.bankId, toAccountUID.accountId, callContext) ?~
s"$BankAccountNotFound Account ${toAccountUID.accountId} not found at bank ${toAccountUID.bankId}"
sameCurrency <- booleanToBox(fromAccount.currency == toAccount.currency, {
s"$InvalidTransactionRequestCurrency, Cannot send payment to account with different currency (From ${fromAccount.currency} to ${toAccount.currency}"
})
isPositiveAmtToSend <- booleanToBox(amt > BigDecimal("0"), s"$NotPositiveAmount Can't send a payment with a value of 0 or less. ($amt)")
//TODO: verify the amount fits with the currency -> e.g. 12.543 EUR not allowed, 10.00 JPY not allowed, 12.53 EUR allowed
// Note for 'new MappedCounterparty()' in the following :
// We update the makePaymentImpl in V210, added the new parameter 'toCounterparty: CounterpartyTrait' for V210
// But in V200 or before, we do not used the new parameter toCounterparty. So just keep it empty.
transactionId <- LocalMappedConnectorInternal.makePaymentImpl(fromAccount,
toAccount,
transactionRequestCommonBody = null, //Note transactionRequestCommonBody started to use in V210
amt,
description,
transactionRequestType,
"",
callContext) //Note chargePolicy started to use in V210
} yield transactionId
}
/**
* \
*
* @param fromAccount The unique identifier of the account sending money
* @param toAccount The unique identifier of the account receiving money
* @param amount The amount of money to send ( > 0 )
* @param transactionRequestType user input: SEPA, SANDBOX_TAN, FREE_FORM, COUNTERPARTY
* @return The id of the sender's new transaction,
*/
override def makePaymentv200(fromAccount: BankAccount,
toAccount: BankAccount,
transactionRequestCommonBody: TransactionRequestCommonBodyJSON,
amount: BigDecimal,
description: String,
transactionRequestType: TransactionRequestType,
chargePolicy: String,
callContext: Option[CallContext]): Box[TransactionId] = {
for {
transactionId <- LocalMappedConnectorInternal.makePaymentImpl(fromAccount, toAccount, transactionRequestCommonBody, amount, description, transactionRequestType, chargePolicy, callContext) ?~! InvalidConnectorResponseForMakePayment
} yield transactionId
}
override def saveTransactionRequestChallenge(transactionRequestId: TransactionRequestId, challenge: TransactionRequestChallenge, callContext: Option[CallContext]): OBPReturnType[Box[Boolean]] ={
Future{(TransactionRequests.transactionRequestProvider.vend.saveTransactionRequestChallengeImpl(transactionRequestId, challenge), callContext)}
}
// This is used for 1.4.0 See createTransactionRequestv200 for 2.0.0
override def createTransactionRequest(initiator: User, fromAccount: BankAccount, toAccount: BankAccount, transactionRequestType: TransactionRequestType, body: TransactionRequestBody,
callContext: Option[CallContext]): Box[TransactionRequest] = {
//set initial status
//for sandbox / testing: depending on amount, we ask for challenge or not
val status =
if (transactionRequestType.value == TransactionRequestTypes.SANDBOX_TAN.toString && BigDecimal(body.value.amount) < 100) {
TransactionRequestStatus.COMPLETED
} else {
TransactionRequestStatus.INITIATED
}
//create a new transaction request
val request = for {
(fromAccountType,callContext) <- getBankAccountLegacy(fromAccount.bankId, fromAccount.accountId, callContext) ?~
s"account ${fromAccount.accountId} not found at bank ${fromAccount.bankId}"
(toAccountType, callContext) <- getBankAccountLegacy(toAccount.bankId, toAccount.accountId, callContext) ?~
s"account ${toAccount.accountId} not found at bank ${toAccount.bankId}"
rawAmt <- tryo {
BigDecimal(body.value.amount)
} ?~! s"amount ${body.value.amount} not convertible to number"
sameCurrency <- booleanToBox(fromAccount.currency == toAccount.currency, {
s"Cannot send payment to account with different currency (From ${fromAccount.currency} to ${toAccount.currency}"
})
isPositiveAmtToSend <- booleanToBox(rawAmt > BigDecimal("0"), s"Can't send a payment with a value of 0 or less. (${rawAmt})")
// Version 200 below has more support for charge
charge = TransactionRequestCharge("Charge for completed transaction", AmountOfMoney(body.value.currency, "0.00"))
transactionRequest <- TransactionRequests.transactionRequestProvider.vend.createTransactionRequestImpl(
TransactionRequestId(generateUUID()),
transactionRequestType,
fromAccount,
toAccount,
body,
status.toString,
charge
)
} yield transactionRequest
//make sure we get something back
var result = request.openOrThrowException("Exception: Couldn't create transactionRequest")
//if no challenge necessary, create transaction immediately and put in data store and object to return
if (status == TransactionRequestStatus.COMPLETED) {
val createdTransactionId = Connector.connector.vend.makePayment(initiator, BankIdAccountId(fromAccount.bankId, fromAccount.accountId),
BankIdAccountId(toAccount.bankId, toAccount.accountId), BigDecimal(body.value.amount), body.description, transactionRequestType,
callContext)
//set challenge to null
result = result.copy(challenge = null)
//save transaction_id if we have one
createdTransactionId match {
case Full(ti) => {
if (!createdTransactionId.isEmpty) {
saveTransactionRequestTransaction(result.id, ti, callContext)
result = result.copy(transaction_ids = ti.value)
}
}
case _ => None
}
} else {
//if challenge necessary, create a new one
val challenge = TransactionRequestChallenge(id = generateUUID(), allowed_attempts = 3, challenge_type = ChallengeType.OBP_TRANSACTION_REQUEST_CHALLENGE.toString)
saveTransactionRequestChallenge(result.id, challenge, callContext)
result = result.copy(challenge = challenge)
}
Full(result)
}
override def createTransactionRequestv200(initiator: User, fromAccount: BankAccount, toAccount: BankAccount, transactionRequestType: TransactionRequestType, body: TransactionRequestBody,
callContext: Option[CallContext]): Box[TransactionRequest] = {
//set initial status
//for sandbox / testing: depending on amount, we ask for challenge or not
val status =
if (transactionRequestType.value == TransactionRequestTypes.SANDBOX_TAN.toString && BigDecimal(body.value.amount) < 1000) {
TransactionRequestStatus.COMPLETED
} else {
TransactionRequestStatus.INITIATED
}
// Always create a new Transaction Request
val request = for {
(fromAccountType,callContext) <- getBankAccountLegacy(fromAccount.bankId, fromAccount.accountId, callContext) ?~ s"account ${fromAccount.accountId} not found at bank ${fromAccount.bankId}"
(toAccountType, callContext) <- getBankAccountLegacy(toAccount.bankId, toAccount.accountId, callContext) ?~ s"account ${toAccount.accountId} not found at bank ${toAccount.bankId}"
rawAmt <- tryo {
BigDecimal(body.value.amount)
} ?~! s"amount ${body.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(body.value.amount) * 0.0001).setScale(10, BigDecimal.RoundingMode.HALF_UP).toDouble
} ?~! s"could not create charge for ${body.value.amount}"
charge = TransactionRequestCharge("Total charges for completed transaction", AmountOfMoney(body.value.currency, chargeValue.toString()))
transactionRequest <- TransactionRequests.transactionRequestProvider.vend.createTransactionRequestImpl(
TransactionRequestId(generateUUID()),
transactionRequestType,
fromAccount,
toAccount,
body,
status.toString,
charge
)
} yield transactionRequest
//make sure we get something back
var result = request.openOrThrowException("Exception: Couldn't create transactionRequest")
// If no challenge necessary, create Transaction immediately and put in data store and object to return
if (status == TransactionRequestStatus.COMPLETED) {
// Note for 'new MappedCounterparty()' in the following :
// We update the makePaymentImpl in V210, added the new parameter 'toCounterparty: CounterpartyTrait' for V210
// But in V200 or before, we do not used the new parameter toCounterparty. So just keep it empty.
val createdTransactionId = Connector.connector.vend.makePaymentv200(fromAccount,
toAccount,
transactionRequestCommonBody = null, //Note chargePolicy only support in V210
BigDecimal(body.value.amount),
body.description,
transactionRequestType,
"",
callContext) //Note chargePolicy only support in V210
//set challenge to null
result = result.copy(challenge = null)
//save transaction_id if we have one
createdTransactionId match {
case Full(ti) => {
if (!createdTransactionId.isEmpty) {
saveTransactionRequestTransaction(result.id, ti, callContext)
result = result.copy(transaction_ids = ti.value)
}
}
case Failure(message, exception, chain) => return Failure(message, exception, chain)
case _ => None
}
} else {
//if challenge necessary, create a new one
val challenge = TransactionRequestChallenge(id = generateUUID(), allowed_attempts = 3, challenge_type = ChallengeType.OBP_TRANSACTION_REQUEST_CHALLENGE.toString)
saveTransactionRequestChallenge(result.id, challenge, callContext)
result = result.copy(challenge = challenge)
}
Full(result)
}
// Set initial status
override def getStatus(challengeThresholdAmount: BigDecimal, transactionRequestCommonBodyAmount: BigDecimal, transactionRequestType: TransactionRequestType, callContext: Option[CallContext]): OBPReturnType[Box[TransactionRequestStatus.Value]] = {
Future(Full(
@ -4882,73 +4674,6 @@ object LocalMappedConnector extends Connector with MdcLoggable {
Full(APIUtil.getPropsValue("transactionRequests_supported_types", "").split(",").map(x => TransactionRequestType(x)).toList)
}
//Note: Now we use validateChallengeAnswer instead, new methods validate over kafka, and move the allowed_attempts guard into API level.
//It is only used for V140 and V200, has been deprecated from V210.
@deprecated
override def answerTransactionRequestChallenge(transReqId: TransactionRequestId, answer: String, callContext: Option[CallContext]) : Box[Boolean]= {
val tr = getTransactionRequestImpl(transReqId, None) ?~! s"${ErrorMessages.InvalidTransactionRequestId} : $transReqId"
tr.map(_._1) match {
case Full(tr: TransactionRequest) =>
if (tr.challenge.allowed_attempts > 0) {
if (tr.challenge.challenge_type == ChallengeType.OBP_TRANSACTION_REQUEST_CHALLENGE.toString) {
//check if answer supplied is correct (i.e. for now, TAN -> some number and not empty)
for {
nonEmpty <- booleanToBox(answer.nonEmpty) ?~ "Need a non-empty answer"
answerToNumber <- tryo(BigInt(answer)) ?~! "Need a numeric TAN"
positive <- booleanToBox(answerToNumber > 0) ?~ "Need a positive TAN"
} yield true
//TODO: decrease allowed attempts value
}
//else if (tr.challenge.challenge_type == ...) {}
else {
Failure("unknown challenge type")
}
} else {
Failure("Sorry, you've used up your allowed attempts.")
}
case Failure(f, Empty, Empty) => Failure(f)
case _ => Failure("Error getting Transaction Request")
}
}
override def createTransactionAfterChallenge(initiator: User, transReqId: TransactionRequestId, callContext: Option[CallContext]): Box[TransactionRequest] = {
for {
(tr, callContext) <- getTransactionRequestImpl(transReqId, None) ?~! s"${ErrorMessages.InvalidTransactionRequestId} : $transReqId"
transId <- makePayment(initiator, BankIdAccountId(BankId(tr.from.bank_id), AccountId(tr.from.account_id)),
BankIdAccountId(BankId(tr.body.to_sandbox_tan.get.bank_id), AccountId(tr.body.to_sandbox_tan.get.account_id)), BigDecimal(tr.body.value.amount), tr.body.description, TransactionRequestType(tr.`type`),
callContext) ?~! InvalidConnectorResponseForMakePayment
_ = saveTransactionRequestTransaction(transReqId, transId, callContext)
_ = NewStyle.function.saveTransactionRequestStatusImpl(transReqId, TransactionRequestStatus.COMPLETED.toString, callContext)
//get transaction request again now with updated values
(tr, callContext) <- getTransactionRequestImpl(transReqId, None) ?~! s"${ErrorMessages.InvalidTransactionRequestId} : $transReqId"
} yield {
tr
}
}
override def createTransactionAfterChallengev200(fromAccount: BankAccount, toAccount: BankAccount, transactionRequest: TransactionRequest, callContext: Option[CallContext]): Box[TransactionRequest] = {
for {
transRequestId <- Full(transactionRequest.id)
transactionId <- makePaymentv200(
fromAccount,
toAccount,
transactionRequestCommonBody = null, //Note transactionRequestCommonBody started to use from V210
BigDecimal(transactionRequest.body.value.amount),
transactionRequest.body.description,
TransactionRequestType(transactionRequest.`type`),
"", //Note chargePolicy started to use from V210
callContext) ?~! InvalidConnectorResponseForMakePayment
_ = saveTransactionRequestTransaction(transRequestId, transactionId, None)
_ = NewStyle.function.saveTransactionRequestStatusImpl(transRequestId, TransactionRequestStatus.COMPLETED.toString, None)
transactionRequestUpdated <- Full(transactionRequest.copy(transaction_ids = transactionId.value, status = TransactionRequestStatus.COMPLETED.toString))
} yield {
transactionRequestUpdated
}
}
override def createTransactionAfterChallengeV210(fromAccount: BankAccount, transactionRequest: TransactionRequest, callContext: Option[CallContext]): OBPReturnType[Box[TransactionRequest]] = {
for {
body <- Future(transactionRequest.body)

View File

@ -578,28 +578,6 @@ object LocalMappedConnectorInternal extends MdcLoggable {
}
}
}
def makePaymentImpl(fromAccount: BankAccount,
toAccount: BankAccount,
transactionRequestCommonBody: TransactionRequestCommonBodyJSON,
amount: BigDecimal,
description: String,
transactionRequestType: TransactionRequestType,
chargePolicy: String,
callContext: Option[CallContext]): Box[TransactionId] = {
for {
//def exchangeRate --> do not return any exception, but it may return NONO there.
rate <- Full (fx.exchangeRate(fromAccount.currency, toAccount.currency, Some(fromAccount.bankId.value), callContext))
_ <- booleanToBox(rate.isDefined) ?~! s"$InvalidCurrency The requested currency conversion (${fromAccount.currency} to ${fromAccount.currency}) is not supported."
fromTransAmt = -amount //from fromAccount balance should decrease
toTransAmt = fx.convert(amount, rate)
sentTransactionId <- saveTransaction(fromAccount, toAccount, transactionRequestCommonBody, fromTransAmt, description, transactionRequestType, chargePolicy)
_sentTransactionId <- saveTransaction(toAccount, fromAccount, transactionRequestCommonBody, toTransAmt, description, transactionRequestType, chargePolicy)
} yield {
sentTransactionId
}
}
/**
* Saves a transaction with @amount, @toAccount and @transactionRequestType for @fromAccount and @toCounterparty. <br>

View File

@ -1,576 +0,0 @@
package code.api.v1_4_0
import code.api.Constant._
import code.api.util.APIUtil
import code.api.util.APIUtil.OAuth._
import com.openbankproject.commons.model.AmountOfMoneyJsonV121
import code.api.v1_4_0.JSONFactory1_4_0._
import code.bankconnectors.Connector
import code.setup.DefaultUsers
import com.openbankproject.commons.model.enums.TransactionRequestTypes._
import com.openbankproject.commons.model.enums.TransactionRequestTypes
import com.openbankproject.commons.model.enums.{ChallengeType, TransactionRequestStatus}
import net.liftweb.json.JsonAST.JString
import net.liftweb.json.Serialization.write
import org.scalatest.Tag
import code.api.util.ErrorMessages._
import code.model.BankAccountX
import com.openbankproject.commons.model.enums.TransactionRequestTypes.SANDBOX_TAN
import com.openbankproject.commons.model.{AccountId, BankAccount, TransactionRequestId}
import scala.collection.immutable.List
class TransactionRequestsTest extends V140ServerSetup with DefaultUsers {
object TransactionRequest extends Tag("transactionRequests")
feature("we can make transaction requests") {
val view = SYSTEM_OWNER_VIEW_ID
def transactionCount(accounts: BankAccount*) : Int = {
accounts.foldLeft(0)((accumulator, account) => {
//TODO: might be nice to avoid direct use of the connector, but if we use an api call we need to do
//it with the correct account owners, and be sure that we don't even run into pagination problems
accumulator + Connector.connector.vend.getTransactionsLegacy(account.bankId, account.accountId, None).map(_._1).openOrThrowException(attemptedToOpenAnEmptyBox).size
})
}
if (APIUtil.getPropsAsBoolValue("transactionRequests_enabled", false) == false) {
ignore("we create a transaction request without challenge", TransactionRequest) {}
} else {
scenario("we create a transaction request without challenge", TransactionRequest) {
val testBank = createBank("transactions-test-bank")
val bankId = testBank.bankId
val accountId1 = AccountId("__acc1")
val accountId2 = AccountId("__acc2")
createAccountRelevantResource(Some(resourceUser1), bankId, accountId1, "EUR")
createAccountRelevantResource(Some(resourceUser1), bankId, accountId2, "EUR")
def getFromAccount: BankAccount = {
BankAccountX(bankId, accountId1).getOrElse(fail("couldn't get from account"))
}
def getToAccount: BankAccount = {
BankAccountX(bankId, accountId2).getOrElse(fail("couldn't get to account"))
}
val fromAccount = getFromAccount
val toAccount = getToAccount
val totalTransactionsBefore = transactionCount(fromAccount, toAccount)
val beforeFromBalance = fromAccount.balance
val beforeToBalance = toAccount.balance
//Create a transaction (request)
//1. get possible challenge types for from account
//2. create transaction request to to-account with one of the possible challenges
//3. answer challenge
//4. have a new transaction
val transactionRequestId = TransactionRequestId("__trans1")
val toAccountJson = TransactionRequestAccountJsonV140(toAccount.bankId.value, toAccount.accountId.value)
val amt = BigDecimal("12.50")
val bodyValue = AmountOfMoneyJsonV121("EUR", amt.toString())
val transactionRequestBody = TransactionRequestBodyJsonV140(toAccountJson, bodyValue, "Test Transaction Request description", "")
//call createTransactionRequest
var request = (v1_4Request / "banks" / testBank.bankId.value / "accounts" / fromAccount.accountId.value /
SYSTEM_OWNER_VIEW_ID / "transaction-request-types" / SANDBOX_TAN.toString / "transaction-requests").POST <@(user1)
var response = makePostRequest(request, write(transactionRequestBody))
Then("we should get a 201 created code")
response.code should equal(201)
//created a transaction request, check some return values. As type is SANDBOX_TAN, we expect no challenge
val transId: String = (response.body \ "id" \ "value") match {
case JString(i) => i
case _ => ""
}
Then("We should have some new transaction id")
transId should not equal ("")
val status: String = (response.body \ "status") match {
case JString(i) => i
case _ => ""
}
status should equal (TransactionRequestStatus.COMPLETED.toString)
var challenge = (response.body \ "challenge").children
challenge.size should equal(0)
var transaction_id = (response.body \ "transaction_ids") match {
case JString(i) => i
case _ => ""
}
transaction_id should not equal("")
//call getTransactionRequests, check that we really created a transaction request
request = (v1_4Request / "banks" / testBank.bankId.value / "accounts" / fromAccount.accountId.value /
SYSTEM_OWNER_VIEW_ID / "transaction-requests").GET <@(user1)
response = makeGetRequest(request)
Then("we should get a 200 ok code")
response.code should equal(200)
val transactionRequests = response.body.children
transactionRequests.size should not equal(0)
//check transaction_ids again
transaction_id = (response.body \ "transaction_ids") match {
case JString(i) => i
case _ => ""
}
transaction_id should not equal("")
//make sure that we also get no challenges back from this url (after getting from db)
challenge = (response.body \ "challenge").children
challenge.size should equal(0)
//check that we created a new transaction (since no challenge)
request = (v1_4Request / "banks" / testBank.bankId.value / "accounts" / fromAccount.accountId.value /
SYSTEM_OWNER_VIEW_ID / "transactions").GET <@(user1)
response = makeGetRequest(request, List(("from_date", APIUtil.epochTimeString),("to_date", APIUtil.DefaultToDateString)))
Then("we should get a 200 ok code")
response.code should equal(200)
val transactions = response.body.children
transactions.size should equal(1)
//check that the description has been set
val description = (((response.body \ "transactions")(0) \ "details") \ "description") match {
case JString(i) => i
case _ => ""
}
description should not equal ("")
//check that the balances have been properly decreased/increased (since we handle that logic for sandbox accounts at least)
//(do it here even though the payments test does test makePayment already)
val fromAccountBalance = getFromAccount.balance
And("the from account should have a balance smaller by the amount specified to pay")
fromAccountBalance should equal((beforeFromBalance - amt))
/*
And("the newest transaction for the account receiving the payment should have the proper amount")
newestToAccountTransaction.details.value.amount should equal(amt.toString)
*/
And("the account receiving the payment should have a new balance plus the amount paid")
val toAccountBalance = getToAccount.balance
toAccountBalance should equal(beforeToBalance + amt)
And("there should now be 2 new transactions in the database (one for the sender, one for the receiver")
transactionCount(fromAccount, toAccount) should equal(totalTransactionsBefore + 2)
}
}
if (APIUtil.getPropsAsBoolValue("transactionRequests_enabled", false) == false) {
ignore("we create a transaction request with a challenge", TransactionRequest) {}
} else {
scenario("we create a transaction request with a challenge", TransactionRequest) {
//setup accounts
val testBank = createBank("transactions-test-bank")
val bankId = testBank.bankId
val accountId1 = AccountId("__acc1")
val accountId2 = AccountId("__acc2")
createAccountRelevantResource(Some(resourceUser1), bankId, accountId1, "EUR")
createAccountRelevantResource(Some(resourceUser1), bankId, accountId2, "EUR")
def getFromAccount: BankAccount = {
BankAccountX(bankId, accountId1).getOrElse(fail("couldn't get from account"))
}
def getToAccount: BankAccount = {
BankAccountX(bankId, accountId2).getOrElse(fail("couldn't get to account"))
}
val fromAccount = getFromAccount
val toAccount = getToAccount
val totalTransactionsBefore = transactionCount(fromAccount, toAccount)
val beforeFromBalance = fromAccount.balance
val beforeToBalance = toAccount.balance
val transactionRequestId = TransactionRequestId("__trans1")
val toAccountJson = TransactionRequestAccountJsonV140(toAccount.bankId.value, toAccount.accountId.value)
//1. TODO: get possible challenge types from account
//2. create transaction request to to-account with one of the possible challenges
//amount over 100 , so should trigger challenge request
val amt = BigDecimal("1250.00")
val bodyValue = AmountOfMoneyJsonV121("EUR", amt.toString())
val transactionRequestBody = TransactionRequestBodyJsonV140(toAccountJson, bodyValue, "Test Transaction Request description", ChallengeType.OBP_TRANSACTION_REQUEST_CHALLENGE.toString)
//call createTransactionRequest API method
var request = (v1_4Request / "banks" / testBank.bankId.value / "accounts" / fromAccount.accountId.value /
SYSTEM_OWNER_VIEW_ID / "transaction-request-types" / SANDBOX_TAN.toString / "transaction-requests").POST <@ (user1)
var response = makePostRequest(request, write(transactionRequestBody))
Then("we should get a 201 created code")
response.code should equal(201)
//ok, created a transaction request, check some return values. As type is SANDBOX_TAN but over 100, we expect a challenge
val transId: String = (response.body \ "id" \ "value") match {
case JString(i) => i
case _ => ""
}
transId should not equal ("")
var status: String = (response.body \ "status") match {
case JString(i) => i
case _ => ""
}
status should equal(TransactionRequestStatus.INITIATED.toString)
var transaction_id = (response.body \ "transaction_ids") match {
case JString(i) => i
case _ => ""
}
transaction_id should equal ("")
var challenge = (response.body \ "challenge").children
challenge.size should not equal(0)
val challenge_id = (response.body \ "challenge" \ "id") match {
case JString(s) => s
case _ => ""
}
challenge_id should not equal("")
//call getTransactionRequests, check that we really created a transaction request
request = (v1_4Request / "banks" / testBank.bankId.value / "accounts" / fromAccount.accountId.value /
SYSTEM_OWNER_VIEW_ID / "transaction-requests").GET <@ (user1)
response = makeGetRequest(request)
Then("we should get a 200 ok code")
response.code should equal(200)
var transactionRequests = response.body.children
transactionRequests.size should equal(1)
transaction_id = (response.body \ "transaction_ids") match {
case JString(i) => i
case _ => ""
}
transaction_id should equal ("")
challenge = (response.body \ "challenge").children
challenge.size should not equal(0)
//3. answer challenge and check if transaction is being created
//call answerTransactionRequestChallenge, give a false answer
var answerJson = ChallengeAnswerJSON(id = challenge_id, answer = "hello") //wrong answer, not a number
request = (v1_4Request / "banks" / testBank.bankId.value / "accounts" / fromAccount.accountId.value /
SYSTEM_OWNER_VIEW_ID / "transaction-request-types" / SANDBOX_TAN.toString / "transaction-requests" / transId / "challenge").POST <@ (user1)
response = makePostRequest(request, write(answerJson))
Then("we should get a 400 bad request code")
response.code should equal(400)
//TODO: check if allowed_attempts is decreased
//call answerTransactionRequestChallenge again, give a good answer
answerJson = ChallengeAnswerJSON(id = challenge_id, answer = "12345") //wrong answer, not a number
request = (v1_4Request / "banks" / testBank.bankId.value / "accounts" / fromAccount.accountId.value /
SYSTEM_OWNER_VIEW_ID / "transaction-request-types" / SANDBOX_TAN.toString / "transaction-requests" / transId / "challenge").POST <@ (user1)
response = makePostRequest(request, write(answerJson))
Then("we should get a 202 accepted code")
response.code should equal(202)
//check if returned data includes new transaction's id
status = (response.body \ "status") match {
case JString(i) => i
case _ => ""
}
status should equal(TransactionRequestStatus.COMPLETED.toString)
transaction_id = (response.body \ "transaction_ids") match {
case JString(i) => i
case _ => ""
}
transaction_id should not equal ("")
//call getTransactionRequests, check that we really created a transaction
request = (v1_4Request / "banks" / testBank.bankId.value / "accounts" / fromAccount.accountId.value /
SYSTEM_OWNER_VIEW_ID / "transaction-requests").GET <@ (user1)
response = makeGetRequest(request)
Then("we should get a 200 ok code")
response.code should equal(200)
transactionRequests = response.body.children
transactionRequests.size should equal(1)
transaction_id = (response.body \ "transaction_ids") match {
case JString(i) => i
case _ => ""
}
transaction_id should not equal ("")
challenge = (response.body \ "challenge").children
challenge.size should not equal(0)
//check that the balances have been properly decreased/increased (since we handle that logic for sandbox accounts at least)
//(do it here even though the payments test does test makePayment already)
val fromAccountBalance = getFromAccount.balance
And("the from account should have a balance smaller by the amount specified to pay")
fromAccountBalance should equal((beforeFromBalance - amt))
/*
And("the newest transaction for the account receiving the payment should have the proper amount")
newestToAccountTransaction.details.value.amount should equal(amt.toString)
*/
And("the account receiving the payment should have a new balance plus the amount paid")
val toAccountBalance = getToAccount.balance
toAccountBalance should equal(beforeToBalance + amt)
And("there should now be 2 new transactions in the database (one for the sender, one for the receiver")
transactionCount(fromAccount, toAccount) should equal(totalTransactionsBefore + 2)
}
}
/* TODO Make this tests functional
scenario("we can't make a payment without access to the owner view", Payments) {
val testBank = createPaymentTestBank()
val bankId = testBank.bankId
val accountId1 = AccountId("__acc1")
val accountId2 = AccountId("__acc2")
createAccountAndOwnerView(Some(authuser1), bankId, accountId1, "EUR")
createAccountAndOwnerView(Some(authuser1), bankId, accountId2, "EUR")
def getFromAccount : BankAccount = {
BankAccount(bankId, accountId1).getOrElse(fail("couldn't get from account"))
}
def getToAccount : BankAccount = {
BankAccount(bankId, accountId2).getOrElse(fail("couldn't get to account"))
}
val fromAccount = getFromAccount
val toAccount = getToAccount
val totalTransactionsBefore = transactionCount(fromAccount, toAccount)
val beforeFromBalance = fromAccount.balance
val beforeToBalance = toAccount.balance
val amt = BigDecimal("12.33")
val payJson = MakePaymentJson(toAccount.bankId.value, toAccount.accountId.value, amt.toString)
val postResult = postTransaction(fromAccount.bankId.value, fromAccount.accountId.value, view, payJson, user2)
Then("we should get a 400")
postResult.code should equal(400)
And("the number of transactions for each account should remain unchanged")
totalTransactionsBefore should equal(transactionCount(fromAccount, toAccount))
And("the balances of each account should remain unchanged")
beforeFromBalance should equal(getFromAccount.balance)
beforeToBalance should equal(getToAccount.balance)
}
scenario("we can't make a payment without an oauth user", Payments) {
val testBank = createPaymentTestBank()
val bankId = testBank.bankId
val accountId1 = AccountId("__acc1")
val accountId2 = AccountId("__acc2")
createAccountAndOwnerView(Some(authuser1), bankId, accountId1, "EUR")
createAccountAndOwnerView(Some(authuser1), bankId, accountId2, "EUR")
def getFromAccount : BankAccount = {
BankAccount(bankId, accountId1).getOrElse(fail("couldn't get from account"))
}
def getToAccount : BankAccount = {
BankAccount(bankId, accountId2).getOrElse(fail("couldn't get to account"))
}
val fromAccount = getFromAccount
val toAccount = getToAccount
val totalTransactionsBefore = transactionCount(fromAccount, toAccount)
val beforeFromBalance = fromAccount.balance
val beforeToBalance = toAccount.balance
val amt = BigDecimal("12.33")
val payJson = MakePaymentJson(toAccount.bankId.value, toAccount.accountId.value, amt.toString)
val postResult = postTransaction(fromAccount.bankId.value, fromAccount.accountId.value, view, payJson, None)
Then("we should get a 400")
postResult.code should equal(400)
And("the number of transactions for each account should remain unchanged")
totalTransactionsBefore should equal(transactionCount(fromAccount, toAccount))
And("the balances of each account should remain unchanged")
beforeFromBalance should equal(getFromAccount.balance)
beforeToBalance should equal(getToAccount.balance)
}
scenario("we can't make a payment of zero units of currency", Payments) {
When("we try to make a payment with amount = 0")
val testBank = createPaymentTestBank()
val bankId = testBank.bankId
val accountId1 = AccountId("__acc1")
val accountId2 = AccountId("__acc2")
createAccountAndOwnerView(Some(authuser1), bankId, accountId1, "EUR")
createAccountAndOwnerView(Some(authuser1), bankId, accountId2, "EUR")
def getFromAccount : BankAccount = {
BankAccount(bankId, accountId1).getOrElse(fail("couldn't get from account"))
}
def getToAccount : BankAccount = {
BankAccount(bankId, accountId2).getOrElse(fail("couldn't get to account"))
}
val fromAccount = getFromAccount
val toAccount = getToAccount
val totalTransactionsBefore = transactionCount(fromAccount, toAccount)
val beforeFromBalance = fromAccount.balance
val beforeToBalance = toAccount.balance
val amt = BigDecimal("0")
val payJson = MakePaymentJson(toAccount.bankId.value, toAccount.accountId.value, amt.toString)
val postResult = postTransaction(fromAccount.bankId.value, fromAccount.accountId.value, view, payJson, user1)
Then("we should get a 400")
postResult.code should equal(400)
And("the number of transactions for each account should remain unchanged")
totalTransactionsBefore should equal(transactionCount(fromAccount, toAccount))
And("the balances of each account should remain unchanged")
beforeFromBalance should equal(getFromAccount.balance)
beforeToBalance should equal(getToAccount.balance)
}
scenario("we can't make a payment with a negative amount of money", Payments) {
val testBank = createPaymentTestBank()
val bankId = testBank.bankId
val accountId1 = AccountId("__acc1")
val accountId2 = AccountId("__acc2")
val acc1 = createAccountAndOwnerView(Some(authuser1), bankId, accountId1, "EUR")
val acc2 = createAccountAndOwnerView(Some(authuser1), bankId, accountId2, "EUR")
When("we try to make a payment with amount < 0")
def getFromAccount : BankAccount = {
BankAccount(bankId, accountId1).getOrElse(fail("couldn't get from account"))
}
def getToAccount : BankAccount = {
BankAccount(bankId, accountId2).getOrElse(fail("couldn't get to account"))
}
val fromAccount = getFromAccount
val toAccount = getToAccount
val totalTransactionsBefore = transactionCount(fromAccount, toAccount)
val beforeFromBalance = fromAccount.balance
val beforeToBalance = toAccount.balance
val amt = BigDecimal("-20.30")
val payJson = MakePaymentJson(toAccount.bankId.value, toAccount.accountId.value, amt.toString)
val postResult = postTransaction(fromAccount.bankId.value, fromAccount.accountId.value, view, payJson, user1)
Then("we should get a 400")
postResult.code should equal(400)
And("the number of transactions for each account should remain unchanged")
totalTransactionsBefore should equal(transactionCount(fromAccount, toAccount))
And("the balances of each account should remain unchanged")
beforeFromBalance should equal(getFromAccount.balance)
beforeToBalance should equal(getToAccount.balance)
}
scenario("we can't make a payment to an account that doesn't exist", Payments) {
val testBank = createPaymentTestBank()
val bankId = testBank.bankId
val accountId1 = AccountId("__acc1")
val acc1 = createAccountAndOwnerView(Some(authuser1), bankId, accountId1, "EUR")
When("we try to make a payment to an account that doesn't exist")
def getFromAccount : BankAccount = {
BankAccount(bankId, accountId1).getOrElse(fail("couldn't get from account"))
}
val fromAccount = getFromAccount
val totalTransactionsBefore = transactionCount(fromAccount)
val beforeFromBalance = fromAccount.balance
val amt = BigDecimal("17.30")
val payJson = MakePaymentJson(bankId.value, "ACCOUNTTHATDOESNOTEXIST232321321", amt.toString)
val postResult = postTransaction(fromAccount.bankId.value, fromAccount.accountId.value, view, payJson, user1)
Then("we should get a 400")
postResult.code should equal(400)
And("the number of transactions for the sender's account should remain unchanged")
totalTransactionsBefore should equal(transactionCount(fromAccount))
And("the balance of the sender's account should remain unchanged")
beforeFromBalance should equal(getFromAccount.balance)
}
scenario("we can't make a payment between accounts with different currencies", Payments) {
When("we try to make a payment to an account that has a different currency")
val testBank = createPaymentTestBank()
val bankId = testBank.bankId
val accountId1 = AccountId("__acc1")
val accountId2 = AccountId("__acc2")
createAccountAndOwnerView(Some(authuser1), bankId, accountId1, "EUR")
createAccountAndOwnerView(Some(authuser1), bankId, accountId2, "GBP")
def getFromAccount : BankAccount = {
BankAccount(bankId, accountId1).getOrElse(fail("couldn't get from account"))
}
def getToAccount : BankAccount = {
BankAccount(bankId, accountId2).getOrElse(fail("couldn't get to account"))
}
val fromAccount = getFromAccount
val toAccount = getToAccount
val totalTransactionsBefore = transactionCount(fromAccount, toAccount)
val beforeFromBalance = fromAccount.balance
val beforeToBalance = toAccount.balance
val amt = BigDecimal("4.95")
val payJson = MakePaymentJson(toAccount.bankId.value, toAccount.accountId.value, amt.toString)
val postResult = postTransaction(fromAccount.bankId.value, fromAccount.accountId.value, view, payJson, user1)
Then("we should get a 400")
postResult.code should equal(400)
And("the number of transactions for each account should remain unchanged")
totalTransactionsBefore should equal(transactionCount(fromAccount, toAccount))
And("the balances of each account should remain unchanged")
beforeFromBalance should equal(getFromAccount.balance)
beforeToBalance should equal(getToAccount.balance)
} */
}
}

View File

@ -1054,26 +1054,13 @@ case class InBoundGetPhysicalCardsForBankLegacy(status: Status, data: List[Physi
override val inboundAdapterCallContext: InboundAdapterCallContext = InboundAdapterCallContext()
}
case class OutBoundMakePayment(outboundAdapterCallContext: OutboundAdapterCallContext,initiator: User, fromAccountUID: BankIdAccountId, toAccountUID: BankIdAccountId, amt: BigDecimal, description: String, transactionRequestType: TransactionRequestType) extends TopicTrait
case class InBoundMakePayment(status: Status, data: TransactionId) extends InBoundTrait[TransactionId] {
override val inboundAdapterCallContext: InboundAdapterCallContext = InboundAdapterCallContext()
}
case class OutBoundMakePaymentv200(fromAccount: BankAccount, toAccount: BankAccount, transactionRequestCommonBody: TransactionRequestCommonBodyJSON, amount: BigDecimal, description: String, transactionRequestType: TransactionRequestType, chargePolicy: String) extends TopicTrait
case class InBoundMakePaymentv200(status: Status, data: TransactionId) extends InBoundTrait[TransactionId] {
override val inboundAdapterCallContext: InboundAdapterCallContext = InboundAdapterCallContext()
}
case class OutBoundCreateTransactionRequest(outboundAdapterCallContext: OutboundAdapterCallContext,initiator: User, fromAccount: BankAccount, toAccount: BankAccount, transactionRequestType: TransactionRequestType, body: TransactionRequestBody) extends TopicTrait
case class InBoundCreateTransactionRequest(status: Status, data: TransactionRequest) extends InBoundTrait[TransactionRequest] {
override val inboundAdapterCallContext: InboundAdapterCallContext = InboundAdapterCallContext()
}
case class OutBoundCreateTransactionRequestv200(outboundAdapterCallContext: OutboundAdapterCallContext,initiator: User, fromAccount: BankAccount, toAccount: BankAccount, transactionRequestType: TransactionRequestType, body: TransactionRequestBody) extends TopicTrait
case class InBoundCreateTransactionRequestv200(status: Status, data: TransactionRequest) extends InBoundTrait[TransactionRequest] {
override val inboundAdapterCallContext: InboundAdapterCallContext = InboundAdapterCallContext()
}
case class OutBoundGetStatus(challengeThresholdAmount: BigDecimal, transactionRequestCommonBodyAmount: BigDecimal, transactionRequestType: TransactionRequestType) extends TopicTrait
case class InBoundGetStatus(status: Status, statusValue: String) extends InBoundTrait[TransactionRequestStatus.Value] {
@ -1164,11 +1151,6 @@ case class InBoundCreateTransactionAfterChallenge(status: Status, data: Transact
override val inboundAdapterCallContext: InboundAdapterCallContext = InboundAdapterCallContext()
}
case class OutBoundCreateTransactionAfterChallengev200(fromAccount: BankAccount, toAccount: BankAccount, transactionRequest: TransactionRequest) extends TopicTrait
case class InBoundCreateTransactionAfterChallengev200(status: Status, data: TransactionRequest) extends InBoundTrait[TransactionRequest] {
override val inboundAdapterCallContext: InboundAdapterCallContext = InboundAdapterCallContext()
}
case class OutBoundAddBankAccount(outboundAdapterCallContext: OutboundAdapterCallContext, bankId: BankId, accountType: String, accountLabel: String, currency: String, initialBalance: BigDecimal, accountHolderName: String, branchId: String, accountRoutings: List[AccountRouting]) extends TopicTrait
case class InBoundAddBankAccount(inboundAdapterCallContext: InboundAdapterCallContext, status: Status, data: BankAccountCommons) extends InBoundTrait[BankAccountCommons]