mirror of
https://github.com/OpenBankProject/OBP-API.git
synced 2026-02-06 15:47:01 +00:00
commit
f3177219c3
@ -1118,6 +1118,11 @@ default_auth_context_update_request_key=CUSTOMER_NUMBER
|
||||
# the alias prefix path for BerlinGroupV1.3 (OBP built-in is berlin-group/v1.3), the format must be xxx/yyy, eg: 0.6/v1
|
||||
#berlin_group_v1.3_alias.path=
|
||||
|
||||
# Show the path inside of Berlin Group error message
|
||||
#berlin_group_error_message_show_path = true
|
||||
|
||||
## Berlin Group Create Consent Frequency per Day Upper Limit
|
||||
#berlin_group_frequency_per_day_upper_limit = 4
|
||||
|
||||
# Support multiple brands on one instance. Note this needs checking on a clustered environment
|
||||
#brands_enabled=false
|
||||
|
||||
@ -4,7 +4,6 @@ import code.api.Constant
|
||||
import code.api.Constant._
|
||||
import code.api.UKOpenBanking.v2_0_0.JSONFactory_UKOpenBanking_200
|
||||
import code.api.UKOpenBanking.v2_0_0.JSONFactory_UKOpenBanking_200.{Account, AccountBalancesUKV200, AccountInner, AccountList, Accounts, BalanceJsonUKV200, BalanceUKOpenBankingJson, BankTransactionCodeJson, CreditLineJson, DataJsonUKV200, Links, MetaBisJson, MetaInnerJson, TransactionCodeJson, TransactionInnerJson, TransactionsInnerJson, TransactionsJsonUKV200}
|
||||
import code.api.berlin.group.v1.JSONFactory_BERLIN_GROUP_1.{AccountBalanceV1, AccountBalances, AmountOfMoneyV1, ClosingBookedBody, ExpectedBody, TransactionJsonV1, TransactionsJsonV1, ViewAccount}
|
||||
import code.api.dynamic.endpoint.helper.practise.PractiseEndpoint
|
||||
import code.api.util.APIUtil.{defaultJValue, _}
|
||||
import code.api.util.ApiRole._
|
||||
@ -12,8 +11,8 @@ import code.api.util.ExampleValue._
|
||||
import code.api.util.{ApiRole, ApiTrigger, ExampleValue}
|
||||
import code.api.v2_2_0.JSONFactory220.{AdapterImplementationJson, MessageDocJson, MessageDocsJson}
|
||||
import code.api.v3_0_0.JSONFactory300.createBranchJsonV300
|
||||
import code.api.v3_0_0.custom.JSONFactoryCustom300
|
||||
import code.api.v3_0_0._
|
||||
import code.api.v3_0_0.custom.JSONFactoryCustom300
|
||||
import code.api.v3_1_0._
|
||||
import code.api.v4_0_0._
|
||||
import code.api.v5_0_0._
|
||||
@ -27,9 +26,9 @@ import code.sandbox.SandboxData
|
||||
import com.github.dwickern.macros.NameOf.nameOf
|
||||
import com.openbankproject.commons.model
|
||||
import com.openbankproject.commons.model.PinResetReason.{FORGOT, GOOD_SECURITY_PRACTICE}
|
||||
import com.openbankproject.commons.model._
|
||||
import com.openbankproject.commons.model.enums.TransactionRequestTypes._
|
||||
import com.openbankproject.commons.model.enums.{AttributeCategory, CardAttributeType, ChallengeType}
|
||||
import com.openbankproject.commons.model._
|
||||
import com.openbankproject.commons.util.{ApiVersion, FieldNameApiVersions, ReflectUtils}
|
||||
import net.liftweb.json
|
||||
|
||||
@ -3477,10 +3476,6 @@ object SwaggerDefinitionsJSON {
|
||||
|
||||
val coreAccountsJsonV300 = CoreAccountsJsonV300(accounts = List(coreAccountJson))
|
||||
|
||||
val amountOfMoneyV1 = AmountOfMoneyV1(
|
||||
currency = "String",
|
||||
content = "String"
|
||||
)
|
||||
|
||||
val accountInnerJsonUKOpenBanking_v200 = AccountInner(
|
||||
SchemeName = "SortCodeAccountNumber",
|
||||
@ -3510,43 +3505,6 @@ object SwaggerDefinitionsJSON {
|
||||
Meta = metaUK
|
||||
)
|
||||
|
||||
val closingBookedBody = ClosingBookedBody(
|
||||
amount = amountOfMoneyV1,
|
||||
date = "2017-10-25"
|
||||
)
|
||||
|
||||
val expectedBody = ExpectedBody(
|
||||
amount = amountOfMoneyV1,
|
||||
lastActionDateTime = DateWithDayExampleObject
|
||||
)
|
||||
|
||||
val accountBalanceV1 = AccountBalanceV1(
|
||||
closingBooked = closingBookedBody,
|
||||
expected = expectedBody
|
||||
)
|
||||
|
||||
val accountBalances = AccountBalances(
|
||||
`balances` = List(accountBalanceV1)
|
||||
)
|
||||
|
||||
val transactionJsonV1 = TransactionJsonV1(
|
||||
transactionId = "String",
|
||||
creditorName = "String",
|
||||
creditorAccount = ibanJson,
|
||||
amount = amountOfMoneyV1,
|
||||
bookingDate = DateWithDayExampleObject,
|
||||
valueDate = DateWithDayExampleObject,
|
||||
remittanceInformationUnstructured = "String"
|
||||
)
|
||||
|
||||
val viewAccount = ViewAccount(viewAccount = "/v1/accounts/3dc3d5b3-7023-4848-9853- f5400a64e80f")
|
||||
|
||||
val transactionsJsonV1 = TransactionsJsonV1(
|
||||
transactions_booked = List(transactionJsonV1),
|
||||
transactions_pending = List(transactionJsonV1),
|
||||
_links = List(viewAccount)
|
||||
)
|
||||
|
||||
val accountIdJson = AccountIdJson(
|
||||
id = "5995d6a2-01b3-423c-a173-5481df49bdaf"
|
||||
)
|
||||
|
||||
@ -22,7 +22,6 @@ import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON.{NotSupportedYet, notSu
|
||||
import code.api.STET.v1_4.OBP_STET_1_4
|
||||
import code.api.UKOpenBanking.v2_0_0.OBP_UKOpenBanking_200
|
||||
import code.api.UKOpenBanking.v3_1_0.OBP_UKOpenBanking_310
|
||||
import code.api.berlin.group.v1.OBP_BERLIN_GROUP_1
|
||||
import code.api.berlin.group.v1_3.{OBP_BERLIN_GROUP_1_3, OBP_BERLIN_GROUP_1_3_Alias}
|
||||
import code.api.v1_4_0.JSONFactory1_4_0
|
||||
import com.openbankproject.commons.model.JsonFieldReName
|
||||
@ -294,7 +293,6 @@ object SwaggerJSONFactory extends MdcLoggable {
|
||||
"Creative Commons Attribution 3.0 Australia (CC BY 3.0 AU)"
|
||||
else if (apiVersion == OBP_BERLIN_GROUP_1_3.apiVersion
|
||||
|| apiVersion == OBP_BERLIN_GROUP_1_3_Alias.apiVersion
|
||||
|| apiVersion == OBP_BERLIN_GROUP_1.apiVersion
|
||||
) "Creative Commons Attribution-NoDerivatives 4.0 International (CC BY-ND)"
|
||||
else
|
||||
s"License: Unknown"
|
||||
|
||||
@ -1,186 +0,0 @@
|
||||
package code.api.berlin.group.v1
|
||||
|
||||
import code.api.APIFailureNewStyle
|
||||
import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON
|
||||
import code.api.berlin.group.v1.JSONFactory_BERLIN_GROUP_1.{Balances, CoreAccountJsonV1, CoreAccountsJsonV1, Transactions}
|
||||
import code.api.util.APIUtil.{defaultBankId, _}
|
||||
import code.api.util.{NewStyle}
|
||||
import code.api.util.ErrorMessages._
|
||||
import code.api.util.ApiTag._
|
||||
import code.api.util.NewStyle.HttpCode
|
||||
import code.bankconnectors.Connector
|
||||
import code.model._
|
||||
import code.util.Helper
|
||||
import code.views.Views
|
||||
import com.openbankproject.commons.model.{AccountId, BankId, BankIdAccountId, ViewId}
|
||||
import net.liftweb.common.Full
|
||||
import net.liftweb.http.rest.RestHelper
|
||||
|
||||
import scala.collection.immutable.Nil
|
||||
import scala.collection.mutable.ArrayBuffer
|
||||
import com.openbankproject.commons.ExecutionContext.Implicits.global
|
||||
import scala.concurrent.Future
|
||||
|
||||
object APIMethods_BERLIN_GROUP_1 extends RestHelper{
|
||||
val implementedInApiVersion = OBP_BERLIN_GROUP_1.apiVersion
|
||||
|
||||
val resourceDocs = ArrayBuffer[ResourceDoc]()
|
||||
val apiRelations = ArrayBuffer[ApiRelation]()
|
||||
val codeContext = CodeContext(resourceDocs, apiRelations)
|
||||
|
||||
|
||||
resourceDocs += ResourceDoc(
|
||||
getAccountList,
|
||||
implementedInApiVersion,
|
||||
"getAccountList",
|
||||
"GET",
|
||||
"/accounts",
|
||||
"Berlin Group: Read Account List",
|
||||
s"""
|
||||
|Reads a list of bank accounts, with balances where required.
|
||||
|It is assumed that a consent of the PSU to this access is already given and stored on the ASPSP system.
|
||||
|
|
||||
|${userAuthenticationMessage(true)}
|
||||
|
|
||||
|This endpoint is work in progress. Experimental!
|
||||
|""",
|
||||
EmptyBody,
|
||||
CoreAccountsJsonV1(List(CoreAccountJsonV1(
|
||||
id = "3dc3d5b3-7023-4848-9853-f5400a64e80f",
|
||||
iban = "DE2310010010123456789",
|
||||
currency = "EUR",
|
||||
accountType = "Girokonto",
|
||||
cashAccountType = "CurrentAccount",
|
||||
_links = List(
|
||||
Balances("/v1/accounts/3dc3d5b3-7023-4848-9853-f5400a64e80f/balances"),
|
||||
Transactions("/v1/accounts/3dc3d5b3-7023-4848-9853-f5400a64e80f/transactions")
|
||||
),
|
||||
name = "Main Account"
|
||||
))),
|
||||
List(UserNotLoggedIn,UnknownError),
|
||||
List(apiTagBerlinGroup, apiTagAccount, apiTagPrivateData, apiTagPsd2))
|
||||
|
||||
|
||||
apiRelations += ApiRelation(getAccountList, getAccountList, "self")
|
||||
|
||||
|
||||
|
||||
lazy val getAccountList : OBPEndpoint = {
|
||||
//get private accounts for one bank
|
||||
case "accounts" :: Nil JsonGet _ => {
|
||||
cc =>
|
||||
for {
|
||||
(Full(u), callContext) <- authenticatedAccess(cc)
|
||||
|
||||
_ <- Helper.booleanToFuture(failMsg= DefaultBankIdNotSet, cc=callContext) {defaultBankId != "DEFAULT_BANK_ID_NOT_SET"}
|
||||
|
||||
bankId = BankId(defaultBankId)
|
||||
|
||||
(_, callContext) <- NewStyle.function.getBank(bankId, callContext)
|
||||
|
||||
availablePrivateAccounts <- Views.views.vend.getPrivateBankAccountsFuture(u, bankId)
|
||||
|
||||
Full((coreAccounts,callContext1)) <- {Connector.connector.vend.getCoreBankAccounts(availablePrivateAccounts, callContext)}
|
||||
|
||||
} yield {
|
||||
(JSONFactory_BERLIN_GROUP_1.createTransactionListJSON(coreAccounts), callContext)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resourceDocs += ResourceDoc(
|
||||
getAccountBalances,
|
||||
implementedInApiVersion,
|
||||
"getAccountBalances",
|
||||
"GET",
|
||||
"/accounts/ACCOUNT_ID/balances",
|
||||
"Berlin Group Read Balance",
|
||||
s"""
|
||||
|Reads account data from a given account addressed by “account-id”.
|
||||
|
|
||||
|${userAuthenticationMessage(true)}
|
||||
|
|
||||
|This endpoint is work in progress. Experimental!
|
||||
|""",
|
||||
EmptyBody,
|
||||
SwaggerDefinitionsJSON.accountBalances,
|
||||
List(UserNotLoggedIn, ViewNotFound, UserNoPermissionAccessView, UnknownError),
|
||||
List(apiTagBerlinGroup, apiTagAccount, apiTagPrivateData, apiTagPsd2))
|
||||
|
||||
lazy val getAccountBalances : OBPEndpoint = {
|
||||
//get private accounts for all banks
|
||||
case "accounts" :: AccountId(accountId) :: "balances" :: Nil JsonGet _ => {
|
||||
cc =>
|
||||
for {
|
||||
(Full(u), callContext) <- authenticatedAccess(cc)
|
||||
_ <- Helper.booleanToFuture(failMsg= DefaultBankIdNotSet, cc=callContext) { defaultBankId != "DEFAULT_BANK_ID_NOT_SET" }
|
||||
(_, callContext) <- NewStyle.function.getBank(BankId(defaultBankId), callContext)
|
||||
(bankAccount, callContext) <- NewStyle.function.checkBankAccountExists(BankId(defaultBankId), accountId, callContext)
|
||||
view <- NewStyle.function.checkOwnerViewAccessAndReturnOwnerView(u, BankIdAccountId(bankAccount.bankId, bankAccount.accountId), callContext)
|
||||
(transactionRequests, callContext) <- Future { Connector.connector.vend.getTransactionRequests210(u, bankAccount, callContext)} map {
|
||||
x => fullBoxOrException(x ~> APIFailureNewStyle(InvalidConnectorResponseForGetTransactionRequests210, 400, callContext.map(_.toLight)))
|
||||
} map { unboxFull(_) }
|
||||
moderatedAccount <- Future {bankAccount.moderatedBankAccount(view, BankIdAccountId(bankAccount.bankId, accountId), Full(u), callContext)} map {
|
||||
x => fullBoxOrException(x ~> APIFailureNewStyle(UnknownError, 400, callContext.map(_.toLight)))
|
||||
} map { unboxFull(_) }
|
||||
} yield {
|
||||
(JSONFactory_BERLIN_GROUP_1.createAccountBalanceJSON(moderatedAccount, transactionRequests), HttpCode.`200`(callContext))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resourceDocs += ResourceDoc(
|
||||
getTransactionList,
|
||||
implementedInApiVersion,
|
||||
"getTransactionList",
|
||||
"GET",
|
||||
"/accounts/ACCOUNT_ID/transactions",
|
||||
"Berlin Group Read Account Transactions",
|
||||
s"""
|
||||
|Reads account data from a given account addressed by “account-id”.
|
||||
|${userAuthenticationMessage(true)}
|
||||
|
|
||||
|This endpoint is work in progress. Experimental!
|
||||
|""",
|
||||
EmptyBody,
|
||||
SwaggerDefinitionsJSON.transactionsJsonV1,
|
||||
List(UserNotLoggedIn,UnknownError),
|
||||
List(apiTagBerlinGroup, apiTagTransaction, apiTagPrivateData, apiTagPsd2))
|
||||
|
||||
lazy val getTransactionList : OBPEndpoint = {
|
||||
//get private accounts for all banks
|
||||
case "accounts" :: AccountId(accountId) :: "transactions" :: Nil JsonGet _ => {
|
||||
cc =>
|
||||
for {
|
||||
|
||||
(Full(u), callContext) <- authenticatedAccess(cc)
|
||||
|
||||
_ <- Helper.booleanToFuture(failMsg= DefaultBankIdNotSet, cc=callContext) {defaultBankId != "DEFAULT_BANK_ID_NOT_SET"}
|
||||
|
||||
bankId = BankId(defaultBankId)
|
||||
|
||||
(bank, callContext) <- NewStyle.function.getBank(bankId, callContext)
|
||||
|
||||
(bankAccount, callContext) <- NewStyle.function.checkBankAccountExists(bankId, accountId, callContext)
|
||||
|
||||
view <- NewStyle.function.checkOwnerViewAccessAndReturnOwnerView(u, BankIdAccountId(bankAccount.bankId, bankAccount.accountId), callContext)
|
||||
|
||||
params <- Future { createQueriesByHttpParams(callContext.get.requestHeaders)} map {
|
||||
x => fullBoxOrException(x ~> APIFailureNewStyle(UnknownError, 400, callContext.map(_.toLight)))
|
||||
} map { unboxFull(_) }
|
||||
|
||||
(transactionRequests, callContext) <- Future { Connector.connector.vend.getTransactionRequests210(u, bankAccount, callContext)} map {
|
||||
x => fullBoxOrException(x ~> APIFailureNewStyle(InvalidConnectorResponseForGetTransactionRequests210, 400, callContext.map(_.toLight)))
|
||||
} map { unboxFull(_) }
|
||||
|
||||
(transactions, callContext) <- Future { bankAccount.getModeratedTransactions(bank, Full(u), view, BankIdAccountId(bankId, accountId), callContext, params)} map {
|
||||
x => fullBoxOrException(x ~> APIFailureNewStyle(UnknownError, 400, callContext.map(_.toLight)))
|
||||
} map { unboxFull(_) }
|
||||
|
||||
} yield {
|
||||
(JSONFactory_BERLIN_GROUP_1.createTransactionsJson(transactions, transactionRequests), callContext)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,141 +0,0 @@
|
||||
package code.api.berlin.group.v1
|
||||
|
||||
import java.util.Date
|
||||
|
||||
import code.api.util.{APIUtil, CustomJsonFormats}
|
||||
import code.api.v2_1_0.IbanJson
|
||||
import code.model.{ModeratedBankAccount, ModeratedTransaction}
|
||||
import com.openbankproject.commons.model.{CoreAccount, TransactionRequest}
|
||||
|
||||
import scala.collection.immutable.List
|
||||
|
||||
object JSONFactory_BERLIN_GROUP_1 extends CustomJsonFormats {
|
||||
|
||||
/**
|
||||
* why links is class instead of trait?
|
||||
* because when do swagger file generation, there is no way to define a type of links, because all the implementations
|
||||
* have different structure. if it is abstract we have no way to define an doc example
|
||||
*/
|
||||
trait links
|
||||
case class Balances(balances: String) extends links
|
||||
case class Transactions(transactions: String) extends links
|
||||
case class ViewAccount(viewAccount: String) extends links
|
||||
case class CoreAccountJsonV1(
|
||||
id: String,
|
||||
iban: String,
|
||||
currency: String,
|
||||
accountType: String,
|
||||
cashAccountType: String,
|
||||
_links: List[links],
|
||||
name: String
|
||||
)
|
||||
|
||||
case class CoreAccountsJsonV1(`account-list`: List[CoreAccountJsonV1])
|
||||
|
||||
case class AmountOfMoneyV1(
|
||||
currency : String,
|
||||
content : String
|
||||
)
|
||||
case class ClosingBookedBody(
|
||||
amount : AmountOfMoneyV1,
|
||||
date: String //eg: “2017-10-25”, this is not a valid datetime (not java.util.Date)
|
||||
)
|
||||
case class ExpectedBody(
|
||||
amount : AmountOfMoneyV1,
|
||||
lastActionDateTime: Date
|
||||
)
|
||||
case class AccountBalanceV1(
|
||||
closingBooked: ClosingBookedBody,
|
||||
expected: ExpectedBody
|
||||
)
|
||||
case class AccountBalances(`balances`: List[AccountBalanceV1])
|
||||
|
||||
case class TransactionsJsonV1(
|
||||
transactions_booked: List[TransactionJsonV1],
|
||||
transactions_pending: List[TransactionJsonV1],
|
||||
_links: List[ViewAccount]
|
||||
)
|
||||
|
||||
case class TransactionJsonV1(
|
||||
transactionId: String,
|
||||
creditorName: String,
|
||||
creditorAccount: IbanJson,
|
||||
amount: AmountOfMoneyV1,
|
||||
bookingDate: Date,
|
||||
valueDate: Date,
|
||||
remittanceInformationUnstructured: String
|
||||
)
|
||||
|
||||
def createTransactionListJSON(coreAccounts: List[CoreAccount]): CoreAccountsJsonV1 = {
|
||||
CoreAccountsJsonV1(coreAccounts.map(
|
||||
x => CoreAccountJsonV1(
|
||||
id = x.id,
|
||||
iban = if (x.accountRoutings.headOption.isDefined && x.accountRoutings.head.scheme == "IBAN") x.accountRoutings.head.address else "",
|
||||
currency = "",
|
||||
accountType = "",
|
||||
cashAccountType = "",
|
||||
_links = Balances(s"/${OBP_BERLIN_GROUP_1.version}/accounts/${x.id}/balances") :: Transactions(s"/${OBP_BERLIN_GROUP_1.version}/accounts/${x.id}/transactions") :: Nil,
|
||||
name = x.label)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
def createAccountBalanceJSON(moderatedAccount: ModeratedBankAccount, transactionRequests: List[TransactionRequest]) = {
|
||||
// get the latest end_date of `COMPLETED` transactionRequests
|
||||
val latestCompletedEndDate = transactionRequests.sortBy(_.end_date).reverse.filter(_.status == "COMPLETED").map(_.end_date).headOption.getOrElse(null)
|
||||
|
||||
//get the latest end_date of !`COMPLETED` transactionRequests
|
||||
val latestUncompletedEndDate = transactionRequests.sortBy(_.end_date).reverse.filter(_.status != "COMPLETED").map(_.end_date).headOption.getOrElse(null)
|
||||
|
||||
// get the SUM of the amount of all !`COMPLETED` transactionRequests
|
||||
val sumOfAllUncompletedTransactionrequests = transactionRequests.filter(_.status != "COMPLETED").map(_.body.value.amount).map(BigDecimal(_)).sum
|
||||
// sum of the unCompletedTransactions and the account.balance is the current expectd amount:
|
||||
val sumOfAll = (BigDecimal(moderatedAccount.balance) + sumOfAllUncompletedTransactionrequests).toString()
|
||||
|
||||
AccountBalances(
|
||||
AccountBalanceV1(
|
||||
closingBooked = ClosingBookedBody(
|
||||
amount = AmountOfMoneyV1(currency = moderatedAccount.currency.getOrElse(""), content = moderatedAccount.balance),
|
||||
date = APIUtil.DateWithDayFormat.format(latestCompletedEndDate)
|
||||
),
|
||||
expected = ExpectedBody(
|
||||
amount = AmountOfMoneyV1(currency = moderatedAccount.currency.getOrElse(""),
|
||||
content = sumOfAll),
|
||||
lastActionDateTime = latestUncompletedEndDate)
|
||||
) :: Nil
|
||||
)
|
||||
}
|
||||
|
||||
def createTransactionJSON(transaction : ModeratedTransaction) : TransactionJsonV1 = {
|
||||
TransactionJsonV1(
|
||||
transactionId = transaction.id.value,
|
||||
creditorName = "",
|
||||
creditorAccount = IbanJson(APIUtil.stringOptionOrNull(transaction.bankAccount.get.iban)),
|
||||
amount = AmountOfMoneyV1(APIUtil.stringOptionOrNull(transaction.currency), transaction.amount.get.toString()),
|
||||
bookingDate = transaction.startDate.get,
|
||||
valueDate = transaction.finishDate.get,
|
||||
remittanceInformationUnstructured = APIUtil.stringOptionOrNull(transaction.description)
|
||||
)
|
||||
}
|
||||
|
||||
def createTransactionFromRequestJSON(transactionRequest : TransactionRequest) : TransactionJsonV1 = {
|
||||
TransactionJsonV1(
|
||||
transactionId = transactionRequest.id.value,
|
||||
creditorName = transactionRequest.name,
|
||||
creditorAccount = IbanJson(transactionRequest.from.account_id),
|
||||
amount = AmountOfMoneyV1(transactionRequest.charge.value.currency, transactionRequest.charge.value.amount),
|
||||
bookingDate = transactionRequest.start_date,
|
||||
valueDate = transactionRequest.end_date,
|
||||
remittanceInformationUnstructured = transactionRequest.body.description
|
||||
)
|
||||
}
|
||||
|
||||
def createTransactionsJson(transactions: List[ModeratedTransaction], transactionRequests: List[TransactionRequest]) : TransactionsJsonV1 = {
|
||||
TransactionsJsonV1(
|
||||
transactions_booked =transactions.map(createTransactionJSON),
|
||||
transactions_pending =transactionRequests.filter(_.status!="COMPLETED").map(createTransactionFromRequestJSON),
|
||||
_links = ViewAccount(s"/${OBP_BERLIN_GROUP_1.version}/accounts/${transactionRequests.head.from.account_id}/balances")::Nil
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,68 +0,0 @@
|
||||
/**
|
||||
Open Bank Project - API
|
||||
Copyright (C) 2011-2019, TESOBE GmbH.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Email: contact@tesobe.com
|
||||
TESOBE GmbH.
|
||||
Osloer Strasse 16/17
|
||||
Berlin 13359, Germany
|
||||
|
||||
This product includes software developed at
|
||||
TESOBE (http://www.tesobe.com/)
|
||||
|
||||
*/
|
||||
package code.api.berlin.group.v1
|
||||
|
||||
import code.api.OBPRestHelper
|
||||
import code.api.util.APIUtil.{OBPEndpoint, getAllowedEndpoints}
|
||||
import com.openbankproject.commons.util.{ApiVersionStatus, ScannedApiVersion}
|
||||
import code.api.util.ScannedApis
|
||||
import code.util.Helper.MdcLoggable
|
||||
import code.api.berlin.group.v1.APIMethods_BERLIN_GROUP_1._
|
||||
|
||||
import scala.collection.immutable.Nil
|
||||
|
||||
|
||||
|
||||
/*
|
||||
This file defines which endpoints from all the versions are available in v1
|
||||
*/
|
||||
|
||||
|
||||
object OBP_BERLIN_GROUP_1 extends OBPRestHelper with MdcLoggable with ScannedApis{
|
||||
|
||||
override val apiVersion = ScannedApiVersion("berlin-group", "BG", "v1")
|
||||
val versionStatus = ApiVersionStatus.DRAFT.toString
|
||||
|
||||
val allEndpoints =
|
||||
getAccountList ::
|
||||
getAccountBalances ::
|
||||
getAccountBalances ::
|
||||
getTransactionList ::
|
||||
Nil
|
||||
|
||||
override val allResourceDocs = resourceDocs
|
||||
|
||||
// Filter the possible endpoints by the disabled / enabled Props settings and add them together
|
||||
override val routes : List[OBPEndpoint] = getAllowedEndpoints(allEndpoints,resourceDocs)
|
||||
|
||||
|
||||
// Make them available for use!
|
||||
registerRoutes(routes, allResourceDocs, apiPrefix)
|
||||
|
||||
logger.info(s"version $apiVersion has been run! There are ${routes.length} routes.")
|
||||
|
||||
}
|
||||
@ -5,7 +5,7 @@ import code.api.APIFailureNewStyle
|
||||
import code.api.Constant.{SYSTEM_READ_ACCOUNTS_BERLIN_GROUP_VIEW_ID, SYSTEM_READ_BALANCES_BERLIN_GROUP_VIEW_ID, SYSTEM_READ_TRANSACTIONS_BERLIN_GROUP_VIEW_ID}
|
||||
import code.api.berlin.group.v1_3.JSONFactory_BERLIN_GROUP_1_3.{PostConsentResponseJson, _}
|
||||
import code.api.berlin.group.v1_3.model.{HrefType, LinksAll, ScaStatusResponse}
|
||||
import code.api.berlin.group.v1_3.{JSONFactory_BERLIN_GROUP_1_3, JvalueCaseClass, OBP_BERLIN_GROUP_1_3}
|
||||
import code.api.berlin.group.v1_3.{BgSpecValidation, JSONFactory_BERLIN_GROUP_1_3, JvalueCaseClass, OBP_BERLIN_GROUP_1_3}
|
||||
import code.api.berlin.group.v1_3.model._
|
||||
import code.api.util.APIUtil.{passesPsd2Aisp, _}
|
||||
import code.api.util.ApiTag._
|
||||
@ -106,6 +106,15 @@ This option is not supported for the Embedded SCA Approach.
|
||||
As a last option, an ASPSP might in addition accept a command with access rights
|
||||
* to see the list of available payment accounts or
|
||||
* to see the list of available payment accounts with balances.
|
||||
|
||||
frequencyPerDay:
|
||||
This field indicates the requested maximum frequency for an access without PSU involvement per day.
|
||||
For a one-off access, this attribute is set to "1".
|
||||
The frequency needs to be greater equal to one.
|
||||
If not otherwise agreed bilaterally between TPP and ASPSP, the frequency is less equal to 4.
|
||||
recurringIndicator:
|
||||
"true", if the consent is for recurring access to the account data.
|
||||
"false", if the consent is for one access to the account data.
|
||||
""",
|
||||
PostConsentJson(
|
||||
access = ConsentAccessJson(
|
||||
@ -125,7 +134,7 @@ As a last option, an ASPSP might in addition accept a command with access rights
|
||||
recurringIndicator = true,
|
||||
validUntil = "2020-12-31",
|
||||
frequencyPerDay = 4,
|
||||
combinedServiceIndicator = false
|
||||
combinedServiceIndicator = Some(false)
|
||||
),
|
||||
PostConsentResponseJson(
|
||||
consentId = "1234-wertiq-983",
|
||||
@ -150,9 +159,9 @@ As a last option, an ASPSP might in addition accept a command with access rights
|
||||
consentJson <- NewStyle.function.tryons(failMsg, 400, callContext) {
|
||||
json.extract[PostConsentJson]
|
||||
}
|
||||
|
||||
upperLimit = APIUtil.getPropsAsIntValue("berlin_group_frequency_per_day_upper_limit", 4)
|
||||
_ <- Helper.booleanToFuture(failMsg = FrequencyPerDayError, cc=callContext) {
|
||||
consentJson.frequencyPerDay > 0
|
||||
consentJson.frequencyPerDay > 0 && consentJson.frequencyPerDay <= upperLimit
|
||||
}
|
||||
|
||||
_ <- Helper.booleanToFuture(failMsg = FrequencyPerDayMustBeOneError, cc=callContext) {
|
||||
@ -160,9 +169,10 @@ As a last option, an ASPSP might in addition accept a command with access rights
|
||||
(consentJson.recurringIndicator == false && consentJson.frequencyPerDay == 1)
|
||||
}
|
||||
|
||||
failMsg = s"$InvalidDateFormat Current `validUntil` field is ${consentJson.validUntil}. Please use this format ${DateWithDayFormat.toPattern}!"
|
||||
validUntil <- NewStyle.function.tryons(failMsg, 400, callContext) {
|
||||
new SimpleDateFormat(DateWithDay).parse(consentJson.validUntil)
|
||||
failMsg = BgSpecValidation.getErrorMessage(consentJson.validUntil)
|
||||
validUntil = BgSpecValidation.getDate(consentJson.validUntil)
|
||||
_ <- Helper.booleanToFuture(failMsg, 400, callContext) {
|
||||
failMsg.isEmpty
|
||||
}
|
||||
|
||||
_ <- NewStyle.function.getBankAccountsByIban(consentJson.access.accounts.getOrElse(Nil).map(_.iban.getOrElse("")), callContext)
|
||||
@ -173,7 +183,7 @@ As a last option, an ASPSP might in addition accept a command with access rights
|
||||
recurringIndicator = consentJson.recurringIndicator,
|
||||
validUntil = validUntil,
|
||||
frequencyPerDay = consentJson.frequencyPerDay,
|
||||
combinedServiceIndicator = consentJson.combinedServiceIndicator,
|
||||
combinedServiceIndicator = consentJson.combinedServiceIndicator.getOrElse(false),
|
||||
apiStandard = Some(apiVersion.apiStandard),
|
||||
apiVersion = Some(apiVersion.apiShortVersion)
|
||||
)) map {
|
||||
|
||||
@ -0,0 +1,59 @@
|
||||
package code.api.berlin.group.v1_3
|
||||
|
||||
import code.api.util.APIUtil.DateWithDayFormat
|
||||
import code.api.util.ErrorMessages.InvalidDateFormat
|
||||
|
||||
import java.time.format.{DateTimeFormatter, DateTimeParseException}
|
||||
import java.time.{LocalDate, ZoneId}
|
||||
import java.util.Date
|
||||
|
||||
object BgSpecValidation {
|
||||
|
||||
val MaxValidDate: LocalDate = LocalDate.parse("9999-12-31")
|
||||
val DateFormat: DateTimeFormatter = DateTimeFormatter.ISO_LOCAL_DATE
|
||||
|
||||
def getErrorMessage(dateStr: String): String = {
|
||||
validateValidUntil(dateStr) match {
|
||||
case Right(validDate) => ""
|
||||
case Left(error) => error
|
||||
}
|
||||
}
|
||||
def getDate(dateStr: String): Date = {
|
||||
validateValidUntil(dateStr) match {
|
||||
case Right(validDate) =>
|
||||
Date.from(validDate.atStartOfDay(ZoneId.systemDefault).toInstant)
|
||||
case Left(error) =>
|
||||
null
|
||||
}
|
||||
}
|
||||
private def validateValidUntil(dateStr: String): Either[String, LocalDate] = {
|
||||
try {
|
||||
val date = LocalDate.parse(dateStr, DateFormat)
|
||||
val today = LocalDate.now()
|
||||
|
||||
if (date.isBefore(today)) {
|
||||
Left(s"$InvalidDateFormat Current `validUntil` field is ${dateStr}. The date must not be in the past!")
|
||||
} else if (date.isAfter(MaxValidDate)) {
|
||||
Left(s"$InvalidDateFormat Current `validUntil` field is ${dateStr}. The maximum allowed date is $MaxValidDate.!")
|
||||
} else {
|
||||
Right(date) // Valid date
|
||||
}
|
||||
} catch {
|
||||
case e: DateTimeParseException =>
|
||||
Left(s"$InvalidDateFormat Current `validUntil` field is ${dateStr}. Please use this format ${DateWithDayFormat.toPattern}!")
|
||||
}
|
||||
}
|
||||
|
||||
// Example usage
|
||||
def main(args: Array[String]): Unit = {
|
||||
val testDates = Seq("2025-05-10", "9999-12-31", "2015-01-01", "invalid-date", "2025-01-20T11:04:20Z")
|
||||
|
||||
testDates.foreach { date =>
|
||||
validateValidUntil(date) match {
|
||||
case Right(validDate) => println(s"Valid date: $validDate")
|
||||
case Left(error) => println(s"Error: $error")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,7 +22,7 @@ case class JvalueCaseClass(jvalueToCaseclass: JValue)
|
||||
|
||||
object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats {
|
||||
|
||||
case class ErrorMessageBG(category: String, code: Int, path: String, text: String)
|
||||
case class ErrorMessageBG(category: String, code: String, path: Option[String], text: String)
|
||||
case class ErrorMessagesBG(tppMessages: List[ErrorMessageBG])
|
||||
|
||||
case class PostSigningBasketJsonV13(
|
||||
@ -228,7 +228,7 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats {
|
||||
recurringIndicator: Boolean,
|
||||
validUntil: String,
|
||||
frequencyPerDay: Int,
|
||||
combinedServiceIndicator: Boolean
|
||||
combinedServiceIndicator: Option[Boolean]
|
||||
)
|
||||
case class ConsentLinksV13(
|
||||
startAuthorisation: Option[Href] = None,
|
||||
|
||||
@ -34,7 +34,6 @@ import code.api.OAuthHandshake._
|
||||
import code.api.UKOpenBanking.v2_0_0.OBP_UKOpenBanking_200
|
||||
import code.api.UKOpenBanking.v3_1_0.OBP_UKOpenBanking_310
|
||||
import code.api._
|
||||
import code.api.berlin.group.v1.OBP_BERLIN_GROUP_1
|
||||
import code.api.berlin.group.v1_3.JSONFactory_BERLIN_GROUP_1_3.{ErrorMessageBG, ErrorMessagesBG}
|
||||
import code.api.cache.Caching
|
||||
import code.api.dynamic.endpoint.OBPAPIDynamicEndpoint
|
||||
@ -728,7 +727,12 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
|
||||
def composeErrorMessage() = {
|
||||
val path = callContextLight.map(_.url).getOrElse("")
|
||||
if (path.contains("berlin-group")) {
|
||||
val errorMessagesBG = ErrorMessagesBG(tppMessages = List(ErrorMessageBG(category = "ERROR", code = code, path = callContextLight.map(_.url).getOrElse(""), text = message)))
|
||||
val path =
|
||||
if(APIUtil.getPropsAsBoolValue("berlin_group_error_message_show_path", defaultValue = true))
|
||||
callContextLight.map(_.url)
|
||||
else
|
||||
None
|
||||
val errorMessagesBG = ErrorMessagesBG(tppMessages = List(ErrorMessageBG(category = "ERROR", code = code.toString, path = path, text = message)))
|
||||
Extraction.decompose(errorMessagesBG)
|
||||
} else {
|
||||
Extraction.decompose(ErrorMessage(message = message, code = code))
|
||||
@ -4843,7 +4847,6 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
|
||||
++ OBP_UKOpenBanking_310.allResourceDocs
|
||||
++ code.api.Polish.v2_1_1_1.OBP_PAPI_2_1_1_1.allResourceDocs
|
||||
++ code.api.STET.v1_4.OBP_STET_1_4.allResourceDocs
|
||||
++ OBP_BERLIN_GROUP_1.allResourceDocs
|
||||
++ code.api.AUOpenBanking.v1_0_0.ApiCollector.allResourceDocs
|
||||
++ code.api.MxOF.CNBV9_1_0_0.allResourceDocs
|
||||
++ code.api.berlin.group.v1_3.OBP_BERLIN_GROUP_1_3.allResourceDocs
|
||||
|
||||
@ -809,7 +809,10 @@ object Consent extends MdcLoggable {
|
||||
if(views.isEmpty) {
|
||||
Empty
|
||||
} else {
|
||||
val updatedPayload = payloadToUpdate.map(i => i.copy(views = views)) // Update only the field "views"
|
||||
val updatedPayload = payloadToUpdate.map(i =>
|
||||
i.copy(views = views) // Update the field "views"
|
||||
.copy(access = Some(access)) // Update the field "access"
|
||||
)
|
||||
val jwtPayloadAsJson = compactRender(Extraction.decompose(updatedPayload))
|
||||
val jwtClaims: JWTClaimsSet = JWTClaimsSet.parse(jwtPayloadAsJson)
|
||||
Full(CertificateUtil.jwtWithHmacProtection(jwtClaims, consent.secret))
|
||||
|
||||
@ -208,7 +208,7 @@ object ErrorMessages {
|
||||
val ConsumerIsDisabled = "OBP-20058: Consumer is disabled."
|
||||
val CouldNotAssignAccountAccess = "OBP-20059: Could not assign account access. "
|
||||
val NoViewReadAccountsBerlinGroup = s"OBP-20060: User does not have access to the view:"
|
||||
val FrequencyPerDayError = "OBP-20062: Frequency per day must be greater than 0."
|
||||
val FrequencyPerDayError = s"OBP-20062: Frequency per day must be greater than 0 and less or equal to ${APIUtil.getPropsAsIntValue("berlin_group_frequency_per_day_upper_limit", 4)}"
|
||||
val FrequencyPerDayMustBeOneError = "OBP-20063: Frequency per day must be equal to 1 in case of one-off access."
|
||||
|
||||
val UserIsDeleted = "OBP-20064: The user is deleted!"
|
||||
|
||||
@ -1,9 +0,0 @@
|
||||
package code.api.berlin.group.v1
|
||||
|
||||
import code.setup.ServerSetupWithTestData
|
||||
|
||||
trait BerlinGroupV1ServerSetup extends ServerSetupWithTestData {
|
||||
|
||||
def BerlinGroup_V1Request = baseRequest / "berlin-group" / "v1"
|
||||
|
||||
}
|
||||
@ -1,53 +0,0 @@
|
||||
package code.api.berlin.group.v1
|
||||
|
||||
import code.api.berlin.group.v1.JSONFactory_BERLIN_GROUP_1.{AccountBalances, CoreAccountsJsonV1, TransactionsJsonV1}
|
||||
import code.api.util.APIUtil.OAuth._
|
||||
import code.setup.{APIResponse, DefaultUsers}
|
||||
import org.scalatest.Tag
|
||||
|
||||
class BerlinGroupV1Tests extends BerlinGroupV1ServerSetup with DefaultUsers {
|
||||
|
||||
object BerlinGroup extends Tag("berlinGroup")
|
||||
|
||||
feature("test the BG Read Account List")
|
||||
{
|
||||
scenario("Successful Case", BerlinGroup)
|
||||
{
|
||||
val requestGetAll = (BerlinGroup_V1Request / "accounts" ).GET <@(user1)
|
||||
val response: APIResponse = makeGetRequest(requestGetAll)
|
||||
|
||||
Then("We should get a 200 ")
|
||||
response.code should equal(200)
|
||||
// TODO because of the links is a trait, we can not extract automatically here.
|
||||
// logger.info(response.body)
|
||||
// val coreAccountsJsonV1 = response.body.extract[CoreAccountsJsonV1]
|
||||
}
|
||||
}
|
||||
|
||||
feature("test the BG Read Balance")
|
||||
{
|
||||
scenario("Successful Case", BerlinGroup)
|
||||
{
|
||||
val requestGetAll = (BerlinGroup_V1Request / "accounts"/ testAccountId1.value /"balances" ).GET <@(user1)
|
||||
val response = makeGetRequest(requestGetAll)
|
||||
|
||||
Then("We should get a 200 ")
|
||||
response.code should equal(200)
|
||||
val accountBalances = response.body.extract[AccountBalances]
|
||||
}
|
||||
}
|
||||
|
||||
feature("test the BG Read Account Transactions")
|
||||
{
|
||||
scenario("Successful Case", BerlinGroup)
|
||||
{
|
||||
val requestGetAll = (BerlinGroup_V1Request / "accounts"/ testAccountId1.value /"transactions" ).GET <@(user1)
|
||||
val response = makeGetRequest(requestGetAll)
|
||||
|
||||
Then("We should get a 200 ")
|
||||
response.code should equal(200)
|
||||
val transactionsJsonV1 = response.body.extract[TransactionsJsonV1]
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -17,6 +17,9 @@ import net.liftweb.json.Serialization.write
|
||||
import net.liftweb.mapper.By
|
||||
import org.scalatest.Tag
|
||||
|
||||
import java.time.LocalDate
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
class AccountInformationServiceAISApiTest extends BerlinGroupServerSetupV1_3 with DefaultUsers {
|
||||
|
||||
object getAccountList extends Tag(nameOf(APIMethods_AccountInformationServiceAISApi.getAccountList))
|
||||
@ -52,6 +55,10 @@ class AccountInformationServiceAISApiTest extends BerlinGroupServerSetupV1_3 wit
|
||||
object updateConsentsPsuDataUpdateSelectPsuAuthenticationMethod extends Tag(nameOf(APIMethods_AccountInformationServiceAISApi.updateConsentsPsuDataUpdateSelectPsuAuthenticationMethod))
|
||||
object updateConsentsPsuDataUpdateAuthorisationConfirmation extends Tag(nameOf(APIMethods_AccountInformationServiceAISApi.updateConsentsPsuDataUpdateAuthorisationConfirmation))
|
||||
|
||||
def getNextMonthDate(): String = {
|
||||
val nextMonthDate = LocalDate.now().plusMonths(1)
|
||||
nextMonthDate.format(DateTimeFormatter.ISO_LOCAL_DATE)
|
||||
}
|
||||
|
||||
feature(s"BG v1.3 - $getAccountList") {
|
||||
scenario("Not Authentication User, test failed ", BerlinGroupV1_3, getAccountList) {
|
||||
@ -245,9 +252,9 @@ class AccountInformationServiceAISApiTest extends BerlinGroupServerSetupV1_3 wit
|
||||
allPsd2 = None
|
||||
),
|
||||
recurringIndicator = true,
|
||||
validUntil = "2020-12-31",
|
||||
validUntil = getNextMonthDate(),
|
||||
frequencyPerDay = 4,
|
||||
combinedServiceIndicator = false
|
||||
combinedServiceIndicator = Some(false)
|
||||
)
|
||||
val requestPost = (V1_3_BG / "consents" ).POST <@ (user1)
|
||||
val response: APIResponse = makePostRequest(requestPost, write(postJsonBody))
|
||||
@ -281,9 +288,9 @@ class AccountInformationServiceAISApiTest extends BerlinGroupServerSetupV1_3 wit
|
||||
allPsd2 = None
|
||||
),
|
||||
recurringIndicator = true,
|
||||
validUntil = "2020-12-31",
|
||||
validUntil = getNextMonthDate(),
|
||||
frequencyPerDay = 4,
|
||||
combinedServiceIndicator = false
|
||||
combinedServiceIndicator = Some(false)
|
||||
)
|
||||
val requestPost = (V1_3_BG / "consents" ).POST <@ (user1)
|
||||
val response: APIResponse = makePostRequest(requestPost, write(postJsonBody))
|
||||
@ -326,9 +333,9 @@ class AccountInformationServiceAISApiTest extends BerlinGroupServerSetupV1_3 wit
|
||||
allPsd2 = None
|
||||
),
|
||||
recurringIndicator = true,
|
||||
validUntil = "2020-12-31",
|
||||
validUntil = getNextMonthDate(),
|
||||
frequencyPerDay = 4,
|
||||
combinedServiceIndicator = false
|
||||
combinedServiceIndicator = Some(false)
|
||||
)
|
||||
val requestPost = (V1_3_BG / "consents" ).POST <@ (user1)
|
||||
val response: APIResponse = makePostRequest(requestPost, write(postJsonBody))
|
||||
@ -374,9 +381,9 @@ class AccountInformationServiceAISApiTest extends BerlinGroupServerSetupV1_3 wit
|
||||
allPsd2 = None
|
||||
),
|
||||
recurringIndicator = true,
|
||||
validUntil = "2020-12-31",
|
||||
validUntil = getNextMonthDate(),
|
||||
frequencyPerDay = 4,
|
||||
combinedServiceIndicator = false
|
||||
combinedServiceIndicator = Some(false)
|
||||
)
|
||||
val requestPost = (V1_3_BG / "consents" ).POST <@ (user1)
|
||||
val response: APIResponse = makePostRequest(requestPost, write(postJsonBody))
|
||||
@ -432,9 +439,9 @@ class AccountInformationServiceAISApiTest extends BerlinGroupServerSetupV1_3 wit
|
||||
allPsd2 = None
|
||||
),
|
||||
recurringIndicator = true,
|
||||
validUntil = "2020-12-31",
|
||||
validUntil = getNextMonthDate(),
|
||||
frequencyPerDay = 4,
|
||||
combinedServiceIndicator = false
|
||||
combinedServiceIndicator = Some(false)
|
||||
)
|
||||
val requestPost = (V1_3_BG / "consents" ).POST <@ (user1)
|
||||
val response: APIResponse = makePostRequest(requestPost, write(postJsonBody))
|
||||
|
||||
Loading…
Reference in New Issue
Block a user