diff --git a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocsAPIMethods.scala b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocsAPIMethods.scala index 1889cbc618..0ea4ccc903 100644 --- a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocsAPIMethods.scala +++ b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocsAPIMethods.scala @@ -123,6 +123,7 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth val resourceDocs = requestedApiVersion match { case ApiVersion.v7_0_0 => code.api.v7_0_0.Http4s700.allResourceDocs // Use aggregated docs for v7.0.0 + case ConstantsBG.`berlinGroupVersion1` => code.api.berlin.group.v1_3.Http4sBGv13.resourceDocs case ConstantsBG.`berlinGroupVersion2` => code.api.berlin.group.v2.Http4sBGv2.resourceDocs case ApiVersion.v6_0_0 => OBPAPI6_0_0.allResourceDocs case ApiVersion.v5_1_0 => OBPAPI5_1_0.allResourceDocs @@ -146,6 +147,7 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth val versionRoutes = requestedApiVersion match { case ApiVersion.v7_0_0 => Nil + case ConstantsBG.`berlinGroupVersion1` => Nil case ConstantsBG.`berlinGroupVersion2` => Nil case ApiVersion.v6_0_0 => OBPAPI6_0_0.routes case ApiVersion.v5_1_0 => OBPAPI5_1_0.routes @@ -175,6 +177,7 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth // Only return the resource docs that have available routes val activeResourceDocs = requestedApiVersion match { case ApiVersion.v7_0_0 => resourceDocs + case ConstantsBG.`berlinGroupVersion1` => resourceDocs // fully on http4s — no Lift route filter case ConstantsBG.`berlinGroupVersion2` => resourceDocs case ApiVersion.v1_2_1 => resourceDocs case ApiVersion.v6_0_0 => resourceDocs // fully on http4s — no Lift route filter diff --git a/obp-api/src/main/scala/code/api/berlin/group/v1_3/AccountInformationServiceAISApi.scala b/obp-api/src/main/scala/code/api/berlin/group/v1_3/AccountInformationServiceAISApi.scala index bee7519aa4..24157a0db4 100644 --- a/obp-api/src/main/scala/code/api/berlin/group/v1_3/AccountInformationServiceAISApi.scala +++ b/obp-api/src/main/scala/code/api/berlin/group/v1_3/AccountInformationServiceAISApi.scala @@ -1,1533 +1,1533 @@ -package code.api.builder.AccountInformationServiceAISApi - -import scala.language.implicitConversions -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.ConstantsBG -import code.api.berlin.group.v1_3.JSONFactory_BERLIN_GROUP_1_3.{PostConsentResponseJson, _} -import code.api.berlin.group.v1_3.model._ -import code.api.berlin.group.v1_3.{BgSpecValidation, JSONFactory_BERLIN_GROUP_1_3, JvalueCaseClass} -import code.api.util.APIUtil.{passesPsd2Aisp, _} -import code.api.util.ApiTag._ -import code.api.util.ErrorMessages._ -import code.api.util.NewStyle.HttpCode -import code.api.util.NewStyle.function.extractQueryParams -import code.api.util._ -import code.api.util.newstyle.ViewNewStyle -import code.consent.{ConsentStatus, Consents} -import code.context.{ConsentAuthContextProvider, UserAuthContextProvider} -import code.model -import code.model._ -import code.util.Helper -import code.views.Views -import com.github.dwickern.macros.NameOf.nameOf -import com.openbankproject.commons.ExecutionContext.Implicits.global -import com.openbankproject.commons.model._ -import com.openbankproject.commons.model.enums.{ChallengeType, StrongCustomerAuthenticationStatus, SuppliedAnswerType} -import net.liftweb -import net.liftweb.common.{Empty, Full} -import net.liftweb.http.js.JE.JsRaw -import net.liftweb.http.provider.HTTPParam -import net.liftweb.http.rest.RestHelper -import net.liftweb.json -import net.liftweb.json._ - -import scala.collection.mutable.ArrayBuffer -import scala.concurrent.Future - -object APIMethods_AccountInformationServiceAISApi extends RestHelper { - val apiVersion = ConstantsBG.berlinGroupVersion1 - val resourceDocs = ArrayBuffer[ResourceDoc]() - val apiRelations = ArrayBuffer[ApiRelation]() - protected implicit def JvalueToSuper(what: JValue): JvalueCaseClass = JvalueCaseClass(what) - - val endpoints = - createConsent :: - deleteConsent :: - getAccountList :: - getBalances :: - getCardAccounts :: - getCardAccountBalances :: - getCardAccountTransactionList :: - getConsentAuthorisation :: - getConsentInformation :: - getConsentScaStatus :: - getConsentStatus :: - getTransactionDetails :: - getTransactionList :: - getAccountDetails :: - readCardAccount :: - startConsentAuthorisationTransactionAuthorisation :: - startConsentAuthorisationUpdatePsuAuthentication :: - startConsentAuthorisationSelectPsuAuthenticationMethod :: - updateConsentsPsuDataTransactionAuthorisation :: - updateConsentsPsuDataUpdatePsuAuthentication :: - updateConsentsPsuDataUpdateAuthorisationConfirmation :: - updateConsentsPsuDataUpdateSelectPsuAuthenticationMethod :: - Nil - lazy val newStyleEndpoints: List[(String, String)] = resourceDocs.map { - rd => (rd.partialFunctionName, rd.implementedInApiVersion.toString()) - }.toList - - - private def checkAccountAccess(viewId: ViewId, u: User, account: BankAccount, callContext: Option[CallContext]) = { - Future { - Helper.booleanToBox(u.hasViewAccess(BankIdAccountId(account.bankId, account.accountId), viewId, callContext)) - } map { - unboxFullOrFail(_, callContext, s"$NoViewReadAccountsBerlinGroup ${viewId.value} userId : ${u.userId}. account : ${account.accountId}", 403) - } - } - - resourceDocs += ResourceDoc( - createConsent, - apiVersion, - nameOf(createConsent), - "POST", - "/consents", - "Create consent", - s"""${mockedDataText(false)} -This method create a consent resource, defining access rights to dedicated accounts of -a given PSU-ID. These accounts are addressed explicitly in the method as -parameters as a core function. - -**Side Effects** -When this Consent Request is a request where the "recurringIndicator" equals "true", -and if it exists already a former consent for recurring access on account information -for the addressed PSU, then the former consent automatically expires as soon as the new -consent request is authorised by the PSU. - -Optional Extension: -As an option, an ASPSP might optionally accept a specific access right on the access on all psd2 related services for all available accounts. - -As another option an ASPSP might optionally also accept a command, where only access rights are inserted without mentioning the addressed account. -The relation to accounts is then handled afterwards between PSU and ASPSP. -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( - accounts = Option(List( ConsentAccessAccountsJson( - iban = Some(ExampleValue.ibanExample.value), - bban = None, - pan = None, - maskedPan = None, - msisdn = None, - currency = None, - ))), - balances = None, - transactions = None, - availableAccounts = None, - allPsd2 = None - ), - recurringIndicator = true, - validUntil = "2020-12-31", - frequencyPerDay = 4, - combinedServiceIndicator = Some(false) - ), - PostConsentResponseJson( - consentId = "1234-wertiq-983", - consentStatus = "received", - _links = ConsentLinksV13(Some(Href("/v1.3/consents/1234-wertiq-983/authorisations"))) - ), - List(AuthenticatedUserIsRequired, UnknownError), - ApiTag("Account Information Service (AIS)") :: apiTagBerlinGroupM :: Nil - ) - - lazy val createConsent : OBPEndpoint = { - case "consents" :: Nil JsonPost json -> _ => { - cc => - for { - (_, callContext) <- applicationAccess(cc) - _ <- passesPsd2Aisp(callContext) - createdByUser: Option[User] <- callContext.map(_.user).getOrElse(Empty) match { - case Full(user) => Future(Some(user)) - case _ => Future(None) - } - failMsg = s"$InvalidJsonFormat The Json body should be the $PostConsentJson " - consentJson <- NewStyle.function.tryons(failMsg, 400, callContext) { - json.extract[PostConsentJson] - } - - _ <- if (consentJson.access.availableAccounts.isDefined) { - for { - _ <- Helper.booleanToFuture(failMsg = BerlinGroupConsentAccessAvailableAccounts, cc = callContext) { - consentJson.access.availableAccounts.contains("allAccounts") - } - _ <- Helper.booleanToFuture(failMsg = BerlinGroupConsentAccessRecurringIndicator, cc = callContext) { - !consentJson.recurringIndicator - } - _ <- Helper.booleanToFuture(failMsg = BerlinGroupConsentAccessFrequencyPerDay, cc = callContext) { - consentJson.frequencyPerDay == 1 - } - } yield Full(()) - } else { - Helper.booleanToFuture( - failMsg = BerlinGroupConsentAccessIsEmpty, cc = callContext) { - consentJson.access.accounts.isDefined || - consentJson.access.balances.isDefined || - consentJson.access.transactions.isDefined - } - } - - upperLimit = APIUtil.getPropsAsIntValue("berlin_group_frequency_per_day_upper_limit", 4) - _ <- Helper.booleanToFuture(failMsg = FrequencyPerDayError, cc=callContext) { - consentJson.frequencyPerDay > 0 && consentJson.frequencyPerDay <= upperLimit - } - - _ <- Helper.booleanToFuture(failMsg = FrequencyPerDayMustBeOneError, cc=callContext) { - consentJson.recurringIndicator || - !consentJson.recurringIndicator && consentJson.frequencyPerDay == 1 - } - - 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) - - createdConsent <- Future(Consents.consentProvider.vend.createBerlinGroupConsent( - createdByUser, - callContext.flatMap(_.consumer), - recurringIndicator = consentJson.recurringIndicator, - validUntil = validUntil, - frequencyPerDay = consentJson.frequencyPerDay, - combinedServiceIndicator = consentJson.combinedServiceIndicator.getOrElse(false), - apiStandard = Some(apiVersion.apiStandard), - apiVersion = Some(apiVersion.apiShortVersion) - )) map { - i => connectorEmptyResponse(i, callContext) - } - consentJWT <- - Consent.createBerlinGroupConsentJWT( - createdByUser, - consentJson, - createdConsent.secret, - createdConsent.consentId, - callContext.flatMap(_.consumer).map(_.consumerId.get), - Some(validUntil), - callContext - ) map { - i => connectorEmptyResponse(i, callContext) - } - _ <- Future(Consents.consentProvider.vend.setJsonWebToken(createdConsent.consentId, consentJWT)) map { - i => connectorEmptyResponse(i, callContext) - } - -/* _ <- Future(Authorisations.authorisationProvider.vend.createAuthorization( - "", - createdConsent.consentId, - AuthenticationType.SMS_OTP.toString, - "", - ScaStatus.received.toString, - "12345" // TODO Implement SMS sending - )) map { - unboxFullOrFail(_, callContext, s"$UnknownError ") - }*/ - } yield { - (createPostConsentResponseJson(createdConsent), HttpCode.`201`(callContext)) - } - } - } - - resourceDocs += ResourceDoc( - deleteConsent, - apiVersion, - nameOf(deleteConsent), - "DELETE", - "/consents/CONSENTID", - "Delete Consent", - s"""${mockedDataText(false)} - The TPP can delete an account information consent object if needed.""", - EmptyBody, - EmptyBody, - List(AuthenticatedUserIsRequired, UnknownError), - ApiTag("Account Information Service (AIS)") :: apiTagBerlinGroupM :: Nil - ) - - lazy val deleteConsent : OBPEndpoint = { - case "consents" :: consentId :: Nil JsonDelete _ => { - cc => - for { - (_, callContext) <- applicationAccess(cc) - _ <- passesPsd2Aisp(callContext) - consent <- Future(Consents.consentProvider.vend.getConsentByConsentId(consentId)) map { - unboxFullOrFail(_, callContext, ConsentNotFound, 403) - } - consumerIdFromConsent = consent.mConsumerId.get - consumerIdFromCurrentCall = callContext.map(_.consumer.map(_.consumerId.get).getOrElse("None")).getOrElse("None") - _ <- Helper.booleanToFuture(failMsg = ConsentNotFound, failCode = 403, cc = cc.callContext) { - consumerIdFromConsent == consumerIdFromCurrentCall - } - _ <- Future(Consents.consentProvider.vend.revokeBerlinGroupConsent(consentId)) map { - i => connectorEmptyResponse(i, callContext) - } - } yield { - (JsRaw(""), HttpCode.`204`(callContext)) - } - } - } - - resourceDocs += ResourceDoc( - getAccountList, - apiVersion, - nameOf(getAccountList), - "GET", - "/accounts", - "Read Account List", - s"""${mockedDataText(false)} -Read the identifiers of the available payment account together with -booking balance information, depending on the consent granted. - -It is assumed that a consent of the PSU to this access is already given and stored on the ASPSP system. -The addressed list of accounts depends then on the PSU ID and the stored consent addressed by consentId, -respectively the OAuth2 access token. - -Returns all identifiers of the accounts, to which an account access has been granted to through -the /consents endpoint by the PSU. -In addition, relevant information about the accounts and hyperlinks to corresponding account -information resources are provided if a related consent has been already granted. - -Remark: Note that the /consents endpoint optionally offers to grant an access on all available -payment accounts of a PSU. -In this case, this endpoint will deliver the information about all available payment accounts -of the PSU at this ASPSP. -""", - EmptyBody, - json.parse("""{ - | "accounts": [ - | { - | "resourceId": "3dc3d5b3-7023-4848-9853-f5400a64e80f", - | "iban": "DE2310010010123456789", - | "currency": "EUR", - | "product": "Girokonto", - | "cashAccountType": "CACC", - | "name": "Main Account", - | "_links": { - | "balances": { - | "href": "/v1/accounts/3dc3d5b3-7023-4848-9853-f5400a64e80f/balances" - | } - | } - | }, - | { - | "resourceId": "3dc3d5b3-7023-4848-9853-f5400a64e81g", - | "iban": "DE2310010010123456788", - | "currency": "USD", - | "product": "Fremdwährungskonto", - | "cashAccountType": "CACC", - | "name": "US Dollar Account", - | "_links": { - | "balances": { - | "href": "/v1/accounts/3dc3d5b3-7023-4848-9853-f5400a64e81g/balances" - | } - | } - | } - | ] - |}""".stripMargin), - List(AuthenticatedUserIsRequired, UnknownError), - ApiTag("Account Information Service (AIS)") :: apiTagBerlinGroupM :: Nil - ) - - lazy val getAccountList : OBPEndpoint = { - case "accounts" :: Nil JsonGet _ => { - cc => - for { - (Full(u), callContext) <- authenticatedAccess(cc) - withBalanceParam <- NewStyle.function.tryons(s"$InvalidUrlParameters withBalance parameter can only take two values: TRUE or FALSE!", 400, callContext) { - - val withBalance = APIUtil.getHttpRequestUrlParam(cc.url, "withBalance") - - if(withBalance.isEmpty)Some(false) else Some(withBalance.toBoolean) - } - _ <- passesPsd2Aisp(callContext) - (availablePrivateAccounts, callContext) <- NewStyle.function.getAccountListOfBerlinGroup(u, callContext) - (canReadBalancesAccounts, callContext) <- NewStyle.function.getAccountCanReadBalancesOfBerlinGroup(u, callContext) - (canReadTransactionsAccounts, callContext) <- NewStyle.function.getAccountCanReadTransactionsOfBerlinGroup(u, callContext) - (accounts, callContext) <- NewStyle.function.getBankAccounts(availablePrivateAccounts, callContext) - bankAccountsFiltered = accounts.filter(bankAccount => - bankAccount.attributes.toList.flatten.find(attribute => - attribute.name.equals("CashAccountTypeCode")&& - attribute.`type`.equals("STRING")&& - attribute.value.equalsIgnoreCase("card") - ).isEmpty) - - (balances, callContext) <- code.api.util.newstyle.BankAccountBalanceNewStyle.getBankAccountsBalances( - bankAccountsFiltered.map(_.accountId), - callContext - ) - - } yield { - (JSONFactory_BERLIN_GROUP_1_3.createAccountListJson( - bankAccountsFiltered, - canReadBalancesAccounts, - canReadTransactionsAccounts, - u, - withBalanceParam, - balances - ), callContext) - } - } - } - - resourceDocs += ResourceDoc( - getBalances, - apiVersion, - nameOf(getBalances), - "GET", - "/accounts/ACCOUNT_ID/balances", - "Read Balance", - s"""${mockedDataText(false)} -Reads account data from a given account addressed by "account-id". - -**Remark:** This account-id can be a tokenised identification due to data protection reason since the path -information might be logged on intermediary servers within the ASPSP sphere. -This account-id then can be retrieved by the "GET Account List" call. - -The account-id is constant at least throughout the lifecycle of a given consent. -""", - EmptyBody, - json.parse("""{ - "account":{ - "iban":"DE91 1000 0000 0123 4567 89" - }, - "balances":[{ - "balanceAmount":{ - "currency":"EUR", - "amount":"50.89" - }, - "balanceType":"AC", - "lastChangeDateTime":"yyyy-MM-dd'T'HH:mm:ss.SSSZ", - "lastCommittedTransaction":"String", - "referenceDate":"2018-03-08" - }] -} -"""), - List(AuthenticatedUserIsRequired, UnknownError), - ApiTag("Account Information Service (AIS)") :: apiTagBerlinGroupM :: Nil - ) - - lazy val getBalances : OBPEndpoint = { - case "accounts" :: AccountId(accountId):: "balances" :: Nil JsonGet _ => { - cc => - for { - (Full(u), callContext) <- authenticatedAccess(cc) - _ <- passesPsd2Aisp(callContext) - (account: BankAccount, callContext) <- NewStyle.function.getBankAccountByAccountId(accountId, callContext) - _ <- checkAccountAccess(ViewId(SYSTEM_READ_BALANCES_BERLIN_GROUP_VIEW_ID), u, account, callContext) - (accountBalances, callContext) <- code.api.util.newstyle.BankAccountBalanceNewStyle.getBankAccountBalances( - accountId, - callContext - ) - - } yield { - (JSONFactory_BERLIN_GROUP_1_3.createAccountBalanceJSON(account, accountBalances), HttpCode.`200`(callContext)) - } - } - } - - resourceDocs += ResourceDoc( - getCardAccounts, - apiVersion, - nameOf(getCardAccounts), - "GET", - "/card-accounts", - "Reads a list of card accounts", - s"""${mockedDataText(false)} -Reads a list of card accounts with additional information, e.g. balance information. -It is assumed that a consent of the PSU to this access is already given and stored on the ASPSP system. -The addressed list of card accounts depends then on the PSU ID and the stored consent addressed by consentId, -respectively the OAuth2 access token. -""", - EmptyBody, - json.parse("""{ - "cardAccounts": [ - { - "resourceId": "3d9a81b3-a47d-4130-8765-a9c0ff861b99", - "maskedPan": "525412******3241", - "currency": "EUR", - "name": "Main", - "product": "Basic Credit", - "status": "enabled", - "creditLimit": { - "currency": "EUR", - "amount": 15000 - }, - "_links": { - "balances": { - "href": "/v1/card-accounts/3d9a81b3-a47d-4130-8765-a9c0ff861b99/balances" - } - } - } - ] -}"""), - List(AuthenticatedUserIsRequired, UnknownError), - ApiTag("Account Information Service (AIS)") :: apiTagMockedData :: Nil - ) - - lazy val getCardAccounts : OBPEndpoint = { - case "card-accounts" :: Nil JsonGet _ => { - cc => - for { - (Full(u), callContext) <- authenticatedAccess(cc) - _ <- passesPsd2Aisp(callContext) - availablePrivateAccounts <- Views.views.vend.getPrivateBankAccountsFuture(u) - //The card contains the account object, it mean the card account. - (_, callContext) <- NewStyle.function.getPhysicalCardsForUser(u, callContext) - (accounts, callContext) <- NewStyle.function.getBankAccounts(availablePrivateAccounts, callContext) - (canReadBalancesAccounts, callContext) <- NewStyle.function.getAccountCanReadBalancesOfBerlinGroup(u, callContext) - (canReadTransactionsAccounts, callContext) <- NewStyle.function.getAccountCanReadTransactionsOfBerlinGroup(u, callContext) - //also see `getAccountList` endpoint - bankAccountsFiltered = accounts.filter(bankAccount => - bankAccount.attributes.toList.flatten.find(attribute=> - attribute.name.equals("CashAccountTypeCode")&& - attribute.`type`.equals("STRING")&& - attribute.value.equalsIgnoreCase("card") - ).isDefined) - } yield { - (JSONFactory_BERLIN_GROUP_1_3.createCardAccountListJson( - bankAccountsFiltered, - canReadBalancesAccounts, - canReadTransactionsAccounts, - u - ), - callContext - ) - } - } - } - - - resourceDocs += ResourceDoc( - getCardAccountBalances, - apiVersion, - nameOf(getCardAccountBalances), - "GET", - "/card-accounts/ACCOUNT_ID/balances", - "Read card account balances", - s"""${mockedDataText(false)} -Reads balance data from a given card account addressed by -"account-id". - -Remark: This account-id can be a tokenised identification due -to data protection reason since the path information might be -logged on intermediary servers within the ASPSP sphere. -This account-id then can be retrieved by the -"GET Card Account List" call -""", - EmptyBody, - json.parse("""{ - "cardAccount":{ - "iban":"DE91 1000 0000 0123 4567 89" - }, - "balances":[{ - "balanceAmount":{ - "currency":"EUR", - "amount":"50.89" - }, - "balanceType":"AC", - "lastChangeDateTime":"yyyy-MM-dd'T'HH:mm:ss.SSSZ", - "lastCommittedTransaction":"String", - "referenceDate":"2018-03-08" - }] -}"""), - List(AuthenticatedUserIsRequired, UnknownError), - ApiTag("Account Information Service (AIS)") :: Nil - ) - - lazy val getCardAccountBalances : OBPEndpoint = { - case "card-accounts" :: AccountId(accountId) :: "balances" :: Nil JsonGet _ => { - cc => - for { - (Full(u), callContext) <- authenticatedAccess(cc) - _ <- passesPsd2Aisp(callContext) - (account: BankAccount, callContext) <- NewStyle.function.getBankAccountByAccountId(accountId, callContext) - _ <- checkAccountAccess(ViewId(SYSTEM_READ_BALANCES_BERLIN_GROUP_VIEW_ID), u, account, callContext) - (accountBalances, callContext) <- code.api.util.newstyle.BankAccountBalanceNewStyle.getBankAccountBalances( - accountId, - callContext - ) - } yield { - (JSONFactory_BERLIN_GROUP_1_3.createCardAccountBalanceJSON(account, accountBalances), HttpCode.`200`(callContext)) - } - } - } - - resourceDocs += ResourceDoc( - getCardAccountTransactionList, - apiVersion, - nameOf(getCardAccountTransactionList), - "GET", - "/card-accounts/ACCOUNT_ID/transactions", - "Read transaction list of a card account", - s"""${mockedDataText(false)} -Reads account data from a given card account addressed by "account-id". -""", - EmptyBody, - json.parse("""{ - "cardAccount": { - "maskedPan": "525412******3241" - }, - "transactions": { - "booked": [ - { - "cardTransactionId": "201710020036959", - "transactionAmount": { - "currency": "EUR", - "amount": "256.67" - }, - "transactionDate": "2017-10-25", - "bookingDate": "2017-10-26", - "originalAmount": { - "currency": "SEK", - "amount": "2499" - }, - "cardAcceptorAddress": { - "city": "STOCKHOLM", - "country": "SE" - }, - "maskedPan": "525412******3241", - "proprietaryBankTransactionCode": "PURCHASE", - "invoiced": false, - "transactionDetails": "WIFIMARKET.SE" - }, - { - "cardTransactionId": "201710020091863", - "transactionAmount": { - "currency": "EUR", - "amount": "10.72" - }, - "transactionDate": "2017-10-25", - "bookingDate": "2017-10-26", - "originalAmount": { - "currency": "SEK", - "amount": "99" - }, - "cardAcceptorAddress": { - "city": "STOCKHOLM", - "country": "SE" - }, - "maskedPan": "525412******8999", - "proprietaryBankTransactionCode": "PURCHASE", - "invoiced": false, - "transactionDetails": "ICA SUPERMARKET SKOGHA" - } - ], - "_links": { - "cardAccount": { - "href": "/v1.3/card-accounts/3d9a81b3-a47d-4130-8765-a9c0ff861b99" - } - } - } - }"""), - List(AuthenticatedUserIsRequired, UnknownError), - ApiTag("Account Information Service (AIS)") :: apiTagBerlinGroupM ::Nil - ) - - lazy val getCardAccountTransactionList : OBPEndpoint = { - case "card-accounts" :: AccountId(accountId):: "transactions" :: Nil JsonGet _ => { - cc => - for { - (Full(u), callContext) <- authenticatedAccess(cc) - _ <- passesPsd2Aisp(callContext) - (bankAccount: BankAccount, callContext) <- NewStyle.function.getBankAccountByAccountId(accountId, callContext) - (bank, callContext) <- NewStyle.function.getBank(bankAccount.bankId, callContext) - viewId = ViewId(SYSTEM_READ_TRANSACTIONS_BERLIN_GROUP_VIEW_ID) - bankIdAccountId = BankIdAccountId(bankAccount.bankId, bankAccount.accountId) - view <- ViewNewStyle.checkAccountAccessAndGetView(viewId, bankIdAccountId, Full(u), 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))) +//package code.api.builder.AccountInformationServiceAISApi +// +//import scala.language.implicitConversions +//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.ConstantsBG +//import code.api.berlin.group.v1_3.JSONFactory_BERLIN_GROUP_1_3.{PostConsentResponseJson, _} +//import code.api.berlin.group.v1_3.model._ +//import code.api.berlin.group.v1_3.{BgSpecValidation, JSONFactory_BERLIN_GROUP_1_3, JvalueCaseClass} +//import code.api.util.APIUtil.{passesPsd2Aisp, _} +//import code.api.util.ApiTag._ +//import code.api.util.ErrorMessages._ +//import code.api.util.NewStyle.HttpCode +//import code.api.util.NewStyle.function.extractQueryParams +//import code.api.util._ +//import code.api.util.newstyle.ViewNewStyle +//import code.consent.{ConsentStatus, Consents} +//import code.context.{ConsentAuthContextProvider, UserAuthContextProvider} +//import code.model +//import code.model._ +//import code.util.Helper +//import code.views.Views +//import com.github.dwickern.macros.NameOf.nameOf +//import com.openbankproject.commons.ExecutionContext.Implicits.global +//import com.openbankproject.commons.model._ +//import com.openbankproject.commons.model.enums.{ChallengeType, StrongCustomerAuthenticationStatus, SuppliedAnswerType} +//import net.liftweb +//import net.liftweb.common.{Empty, Full} +//import net.liftweb.http.js.JE.JsRaw +//import net.liftweb.http.provider.HTTPParam +//import net.liftweb.http.rest.RestHelper +//import net.liftweb.json +//import net.liftweb.json._ +// +//import scala.collection.mutable.ArrayBuffer +//import scala.concurrent.Future +// +//object APIMethods_AccountInformationServiceAISApi extends RestHelper { +// val apiVersion = ConstantsBG.berlinGroupVersion1 +// val resourceDocs = ArrayBuffer[ResourceDoc]() +// val apiRelations = ArrayBuffer[ApiRelation]() +// protected implicit def JvalueToSuper(what: JValue): JvalueCaseClass = JvalueCaseClass(what) +// +// val endpoints = +// createConsent :: +// deleteConsent :: +// getAccountList :: +// getBalances :: +// getCardAccounts :: +// getCardAccountBalances :: +// getCardAccountTransactionList :: +// getConsentAuthorisation :: +// getConsentInformation :: +// getConsentScaStatus :: +// getConsentStatus :: +// getTransactionDetails :: +// getTransactionList :: +// getAccountDetails :: +// readCardAccount :: +// startConsentAuthorisationTransactionAuthorisation :: +// startConsentAuthorisationUpdatePsuAuthentication :: +// startConsentAuthorisationSelectPsuAuthenticationMethod :: +// updateConsentsPsuDataTransactionAuthorisation :: +// updateConsentsPsuDataUpdatePsuAuthentication :: +// updateConsentsPsuDataUpdateAuthorisationConfirmation :: +// updateConsentsPsuDataUpdateSelectPsuAuthenticationMethod :: +// Nil +// lazy val newStyleEndpoints: List[(String, String)] = resourceDocs.map { +// rd => (rd.partialFunctionName, rd.implementedInApiVersion.toString()) +// }.toList +// +// +// private def checkAccountAccess(viewId: ViewId, u: User, account: BankAccount, callContext: Option[CallContext]) = { +// Future { +// Helper.booleanToBox(u.hasViewAccess(BankIdAccountId(account.bankId, account.accountId), viewId, callContext)) +// } map { +// unboxFullOrFail(_, callContext, s"$NoViewReadAccountsBerlinGroup ${viewId.value} userId : ${u.userId}. account : ${account.accountId}", 403) +// } +// } +// +// resourceDocs += ResourceDoc( +// createConsent, +// apiVersion, +// nameOf(createConsent), +// "POST", +// "/consents", +// "Create consent", +// s"""${mockedDataText(false)} +//This method create a consent resource, defining access rights to dedicated accounts of +//a given PSU-ID. These accounts are addressed explicitly in the method as +//parameters as a core function. +// +//**Side Effects** +//When this Consent Request is a request where the "recurringIndicator" equals "true", +//and if it exists already a former consent for recurring access on account information +//for the addressed PSU, then the former consent automatically expires as soon as the new +//consent request is authorised by the PSU. +// +//Optional Extension: +//As an option, an ASPSP might optionally accept a specific access right on the access on all psd2 related services for all available accounts. +// +//As another option an ASPSP might optionally also accept a command, where only access rights are inserted without mentioning the addressed account. +//The relation to accounts is then handled afterwards between PSU and ASPSP. +//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( +// accounts = Option(List( ConsentAccessAccountsJson( +// iban = Some(ExampleValue.ibanExample.value), +// bban = None, +// pan = None, +// maskedPan = None, +// msisdn = None, +// currency = None, +// ))), +// balances = None, +// transactions = None, +// availableAccounts = None, +// allPsd2 = None +// ), +// recurringIndicator = true, +// validUntil = "2020-12-31", +// frequencyPerDay = 4, +// combinedServiceIndicator = Some(false) +// ), +// PostConsentResponseJson( +// consentId = "1234-wertiq-983", +// consentStatus = "received", +// _links = ConsentLinksV13(Some(Href("/v1.3/consents/1234-wertiq-983/authorisations"))) +// ), +// List(AuthenticatedUserIsRequired, UnknownError), +// ApiTag("Account Information Service (AIS)") :: apiTagBerlinGroupM :: Nil +// ) +// +// lazy val createConsent : OBPEndpoint = { +// case "consents" :: Nil JsonPost json -> _ => { +// cc => +// for { +// (_, callContext) <- applicationAccess(cc) +// _ <- passesPsd2Aisp(callContext) +// createdByUser: Option[User] <- callContext.map(_.user).getOrElse(Empty) match { +// case Full(user) => Future(Some(user)) +// case _ => Future(None) +// } +// failMsg = s"$InvalidJsonFormat The Json body should be the $PostConsentJson " +// consentJson <- NewStyle.function.tryons(failMsg, 400, callContext) { +// json.extract[PostConsentJson] +// } +// +// _ <- if (consentJson.access.availableAccounts.isDefined) { +// for { +// _ <- Helper.booleanToFuture(failMsg = BerlinGroupConsentAccessAvailableAccounts, cc = callContext) { +// consentJson.access.availableAccounts.contains("allAccounts") +// } +// _ <- Helper.booleanToFuture(failMsg = BerlinGroupConsentAccessRecurringIndicator, cc = callContext) { +// !consentJson.recurringIndicator +// } +// _ <- Helper.booleanToFuture(failMsg = BerlinGroupConsentAccessFrequencyPerDay, cc = callContext) { +// consentJson.frequencyPerDay == 1 +// } +// } yield Full(()) +// } else { +// Helper.booleanToFuture( +// failMsg = BerlinGroupConsentAccessIsEmpty, cc = callContext) { +// consentJson.access.accounts.isDefined || +// consentJson.access.balances.isDefined || +// consentJson.access.transactions.isDefined +// } +// } +// +// upperLimit = APIUtil.getPropsAsIntValue("berlin_group_frequency_per_day_upper_limit", 4) +// _ <- Helper.booleanToFuture(failMsg = FrequencyPerDayError, cc=callContext) { +// consentJson.frequencyPerDay > 0 && consentJson.frequencyPerDay <= upperLimit +// } +// +// _ <- Helper.booleanToFuture(failMsg = FrequencyPerDayMustBeOneError, cc=callContext) { +// consentJson.recurringIndicator || +// !consentJson.recurringIndicator && consentJson.frequencyPerDay == 1 +// } +// +// 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) +// +// createdConsent <- Future(Consents.consentProvider.vend.createBerlinGroupConsent( +// createdByUser, +// callContext.flatMap(_.consumer), +// recurringIndicator = consentJson.recurringIndicator, +// validUntil = validUntil, +// frequencyPerDay = consentJson.frequencyPerDay, +// combinedServiceIndicator = consentJson.combinedServiceIndicator.getOrElse(false), +// apiStandard = Some(apiVersion.apiStandard), +// apiVersion = Some(apiVersion.apiShortVersion) +// )) map { +// i => connectorEmptyResponse(i, callContext) +// } +// consentJWT <- +// Consent.createBerlinGroupConsentJWT( +// createdByUser, +// consentJson, +// createdConsent.secret, +// createdConsent.consentId, +// callContext.flatMap(_.consumer).map(_.consumerId.get), +// Some(validUntil), +// callContext +// ) map { +// i => connectorEmptyResponse(i, callContext) +// } +// _ <- Future(Consents.consentProvider.vend.setJsonWebToken(createdConsent.consentId, consentJWT)) map { +// i => connectorEmptyResponse(i, callContext) +// } +// +///* _ <- Future(Authorisations.authorisationProvider.vend.createAuthorization( +// "", +// createdConsent.consentId, +// AuthenticationType.SMS_OTP.toString, +// "", +// ScaStatus.received.toString, +// "12345" // TODO Implement SMS sending +// )) map { +// unboxFullOrFail(_, callContext, s"$UnknownError ") +// }*/ +// } yield { +// (createPostConsentResponseJson(createdConsent), HttpCode.`201`(callContext)) +// } +// } +// } +// +// resourceDocs += ResourceDoc( +// deleteConsent, +// apiVersion, +// nameOf(deleteConsent), +// "DELETE", +// "/consents/CONSENTID", +// "Delete Consent", +// s"""${mockedDataText(false)} +// The TPP can delete an account information consent object if needed.""", +// EmptyBody, +// EmptyBody, +// List(AuthenticatedUserIsRequired, UnknownError), +// ApiTag("Account Information Service (AIS)") :: apiTagBerlinGroupM :: Nil +// ) +// +// lazy val deleteConsent : OBPEndpoint = { +// case "consents" :: consentId :: Nil JsonDelete _ => { +// cc => +// for { +// (_, callContext) <- applicationAccess(cc) +// _ <- passesPsd2Aisp(callContext) +// consent <- Future(Consents.consentProvider.vend.getConsentByConsentId(consentId)) map { +// unboxFullOrFail(_, callContext, ConsentNotFound, 403) +// } +// consumerIdFromConsent = consent.mConsumerId.get +// consumerIdFromCurrentCall = callContext.map(_.consumer.map(_.consumerId.get).getOrElse("None")).getOrElse("None") +// _ <- Helper.booleanToFuture(failMsg = ConsentNotFound, failCode = 403, cc = cc.callContext) { +// consumerIdFromConsent == consumerIdFromCurrentCall +// } +// _ <- Future(Consents.consentProvider.vend.revokeBerlinGroupConsent(consentId)) map { +// i => connectorEmptyResponse(i, callContext) +// } +// } yield { +// (JsRaw(""), HttpCode.`204`(callContext)) +// } +// } +// } +// +// resourceDocs += ResourceDoc( +// getAccountList, +// apiVersion, +// nameOf(getAccountList), +// "GET", +// "/accounts", +// "Read Account List", +// s"""${mockedDataText(false)} +//Read the identifiers of the available payment account together with +//booking balance information, depending on the consent granted. +// +//It is assumed that a consent of the PSU to this access is already given and stored on the ASPSP system. +//The addressed list of accounts depends then on the PSU ID and the stored consent addressed by consentId, +//respectively the OAuth2 access token. +// +//Returns all identifiers of the accounts, to which an account access has been granted to through +//the /consents endpoint by the PSU. +//In addition, relevant information about the accounts and hyperlinks to corresponding account +//information resources are provided if a related consent has been already granted. +// +//Remark: Note that the /consents endpoint optionally offers to grant an access on all available +//payment accounts of a PSU. +//In this case, this endpoint will deliver the information about all available payment accounts +//of the PSU at this ASPSP. +//""", +// EmptyBody, +// json.parse("""{ +// | "accounts": [ +// | { +// | "resourceId": "3dc3d5b3-7023-4848-9853-f5400a64e80f", +// | "iban": "DE2310010010123456789", +// | "currency": "EUR", +// | "product": "Girokonto", +// | "cashAccountType": "CACC", +// | "name": "Main Account", +// | "_links": { +// | "balances": { +// | "href": "/v1/accounts/3dc3d5b3-7023-4848-9853-f5400a64e80f/balances" +// | } +// | } +// | }, +// | { +// | "resourceId": "3dc3d5b3-7023-4848-9853-f5400a64e81g", +// | "iban": "DE2310010010123456788", +// | "currency": "USD", +// | "product": "Fremdwährungskonto", +// | "cashAccountType": "CACC", +// | "name": "US Dollar Account", +// | "_links": { +// | "balances": { +// | "href": "/v1/accounts/3dc3d5b3-7023-4848-9853-f5400a64e81g/balances" +// | } +// | } +// | } +// | ] +// |}""".stripMargin), +// List(AuthenticatedUserIsRequired, UnknownError), +// ApiTag("Account Information Service (AIS)") :: apiTagBerlinGroupM :: Nil +// ) +// +// lazy val getAccountList : OBPEndpoint = { +// case "accounts" :: Nil JsonGet _ => { +// cc => +// for { +// (Full(u), callContext) <- authenticatedAccess(cc) +// withBalanceParam <- NewStyle.function.tryons(s"$InvalidUrlParameters withBalance parameter can only take two values: TRUE or FALSE!", 400, callContext) { +// +// val withBalance = APIUtil.getHttpRequestUrlParam(cc.url, "withBalance") +// +// if(withBalance.isEmpty)Some(false) else Some(withBalance.toBoolean) +// } +// _ <- passesPsd2Aisp(callContext) +// (availablePrivateAccounts, callContext) <- NewStyle.function.getAccountListOfBerlinGroup(u, callContext) +// (canReadBalancesAccounts, callContext) <- NewStyle.function.getAccountCanReadBalancesOfBerlinGroup(u, callContext) +// (canReadTransactionsAccounts, callContext) <- NewStyle.function.getAccountCanReadTransactionsOfBerlinGroup(u, callContext) +// (accounts, callContext) <- NewStyle.function.getBankAccounts(availablePrivateAccounts, callContext) +// bankAccountsFiltered = accounts.filter(bankAccount => +// bankAccount.attributes.toList.flatten.find(attribute => +// attribute.name.equals("CashAccountTypeCode")&& +// attribute.`type`.equals("STRING")&& +// attribute.value.equalsIgnoreCase("card") +// ).isEmpty) +// +// (balances, callContext) <- code.api.util.newstyle.BankAccountBalanceNewStyle.getBankAccountsBalances( +// bankAccountsFiltered.map(_.accountId), +// callContext +// ) +// +// } yield { +// (JSONFactory_BERLIN_GROUP_1_3.createAccountListJson( +// bankAccountsFiltered, +// canReadBalancesAccounts, +// canReadTransactionsAccounts, +// u, +// withBalanceParam, +// balances +// ), callContext) +// } +// } +// } +// +// resourceDocs += ResourceDoc( +// getBalances, +// apiVersion, +// nameOf(getBalances), +// "GET", +// "/accounts/ACCOUNT_ID/balances", +// "Read Balance", +// s"""${mockedDataText(false)} +//Reads account data from a given account addressed by "account-id". +// +//**Remark:** This account-id can be a tokenised identification due to data protection reason since the path +//information might be logged on intermediary servers within the ASPSP sphere. +//This account-id then can be retrieved by the "GET Account List" call. +// +//The account-id is constant at least throughout the lifecycle of a given consent. +//""", +// EmptyBody, +// json.parse("""{ +// "account":{ +// "iban":"DE91 1000 0000 0123 4567 89" +// }, +// "balances":[{ +// "balanceAmount":{ +// "currency":"EUR", +// "amount":"50.89" +// }, +// "balanceType":"AC", +// "lastChangeDateTime":"yyyy-MM-dd'T'HH:mm:ss.SSSZ", +// "lastCommittedTransaction":"String", +// "referenceDate":"2018-03-08" +// }] +//} +//"""), +// List(AuthenticatedUserIsRequired, UnknownError), +// ApiTag("Account Information Service (AIS)") :: apiTagBerlinGroupM :: Nil +// ) +// +// lazy val getBalances : OBPEndpoint = { +// case "accounts" :: AccountId(accountId):: "balances" :: Nil JsonGet _ => { +// cc => +// for { +// (Full(u), callContext) <- authenticatedAccess(cc) +// _ <- passesPsd2Aisp(callContext) +// (account: BankAccount, callContext) <- NewStyle.function.getBankAccountByAccountId(accountId, callContext) +// _ <- checkAccountAccess(ViewId(SYSTEM_READ_BALANCES_BERLIN_GROUP_VIEW_ID), u, account, callContext) +// (accountBalances, callContext) <- code.api.util.newstyle.BankAccountBalanceNewStyle.getBankAccountBalances( +// accountId, +// callContext +// ) +// +// } yield { +// (JSONFactory_BERLIN_GROUP_1_3.createAccountBalanceJSON(account, accountBalances), HttpCode.`200`(callContext)) +// } +// } +// } +// +// resourceDocs += ResourceDoc( +// getCardAccounts, +// apiVersion, +// nameOf(getCardAccounts), +// "GET", +// "/card-accounts", +// "Reads a list of card accounts", +// s"""${mockedDataText(false)} +//Reads a list of card accounts with additional information, e.g. balance information. +//It is assumed that a consent of the PSU to this access is already given and stored on the ASPSP system. +//The addressed list of card accounts depends then on the PSU ID and the stored consent addressed by consentId, +//respectively the OAuth2 access token. +//""", +// EmptyBody, +// json.parse("""{ +// "cardAccounts": [ +// { +// "resourceId": "3d9a81b3-a47d-4130-8765-a9c0ff861b99", +// "maskedPan": "525412******3241", +// "currency": "EUR", +// "name": "Main", +// "product": "Basic Credit", +// "status": "enabled", +// "creditLimit": { +// "currency": "EUR", +// "amount": 15000 +// }, +// "_links": { +// "balances": { +// "href": "/v1/card-accounts/3d9a81b3-a47d-4130-8765-a9c0ff861b99/balances" +// } +// } +// } +// ] +//}"""), +// List(AuthenticatedUserIsRequired, UnknownError), +// ApiTag("Account Information Service (AIS)") :: apiTagMockedData :: Nil +// ) +// +// lazy val getCardAccounts : OBPEndpoint = { +// case "card-accounts" :: Nil JsonGet _ => { +// cc => +// for { +// (Full(u), callContext) <- authenticatedAccess(cc) +// _ <- passesPsd2Aisp(callContext) +// availablePrivateAccounts <- Views.views.vend.getPrivateBankAccountsFuture(u) +// //The card contains the account object, it mean the card account. +// (_, callContext) <- NewStyle.function.getPhysicalCardsForUser(u, callContext) +// (accounts, callContext) <- NewStyle.function.getBankAccounts(availablePrivateAccounts, callContext) +// (canReadBalancesAccounts, callContext) <- NewStyle.function.getAccountCanReadBalancesOfBerlinGroup(u, callContext) +// (canReadTransactionsAccounts, callContext) <- NewStyle.function.getAccountCanReadTransactionsOfBerlinGroup(u, callContext) +// //also see `getAccountList` endpoint +// bankAccountsFiltered = accounts.filter(bankAccount => +// bankAccount.attributes.toList.flatten.find(attribute=> +// attribute.name.equals("CashAccountTypeCode")&& +// attribute.`type`.equals("STRING")&& +// attribute.value.equalsIgnoreCase("card") +// ).isDefined) +// } yield { +// (JSONFactory_BERLIN_GROUP_1_3.createCardAccountListJson( +// bankAccountsFiltered, +// canReadBalancesAccounts, +// canReadTransactionsAccounts, +// u +// ), +// callContext +// ) +// } +// } +// } +// +// +// resourceDocs += ResourceDoc( +// getCardAccountBalances, +// apiVersion, +// nameOf(getCardAccountBalances), +// "GET", +// "/card-accounts/ACCOUNT_ID/balances", +// "Read card account balances", +// s"""${mockedDataText(false)} +//Reads balance data from a given card account addressed by +//"account-id". +// +//Remark: This account-id can be a tokenised identification due +//to data protection reason since the path information might be +//logged on intermediary servers within the ASPSP sphere. +//This account-id then can be retrieved by the +//"GET Card Account List" call +//""", +// EmptyBody, +// json.parse("""{ +// "cardAccount":{ +// "iban":"DE91 1000 0000 0123 4567 89" +// }, +// "balances":[{ +// "balanceAmount":{ +// "currency":"EUR", +// "amount":"50.89" +// }, +// "balanceType":"AC", +// "lastChangeDateTime":"yyyy-MM-dd'T'HH:mm:ss.SSSZ", +// "lastCommittedTransaction":"String", +// "referenceDate":"2018-03-08" +// }] +//}"""), +// List(AuthenticatedUserIsRequired, UnknownError), +// ApiTag("Account Information Service (AIS)") :: Nil +// ) +// +// lazy val getCardAccountBalances : OBPEndpoint = { +// case "card-accounts" :: AccountId(accountId) :: "balances" :: Nil JsonGet _ => { +// cc => +// for { +// (Full(u), callContext) <- authenticatedAccess(cc) +// _ <- passesPsd2Aisp(callContext) +// (account: BankAccount, callContext) <- NewStyle.function.getBankAccountByAccountId(accountId, callContext) +// _ <- checkAccountAccess(ViewId(SYSTEM_READ_BALANCES_BERLIN_GROUP_VIEW_ID), u, account, callContext) +// (accountBalances, callContext) <- code.api.util.newstyle.BankAccountBalanceNewStyle.getBankAccountBalances( +// accountId, +// callContext +// ) +// } yield { +// (JSONFactory_BERLIN_GROUP_1_3.createCardAccountBalanceJSON(account, accountBalances), HttpCode.`200`(callContext)) +// } +// } +// } +// +// resourceDocs += ResourceDoc( +// getCardAccountTransactionList, +// apiVersion, +// nameOf(getCardAccountTransactionList), +// "GET", +// "/card-accounts/ACCOUNT_ID/transactions", +// "Read transaction list of a card account", +// s"""${mockedDataText(false)} +//Reads account data from a given card account addressed by "account-id". +//""", +// EmptyBody, +// json.parse("""{ +// "cardAccount": { +// "maskedPan": "525412******3241" +// }, +// "transactions": { +// "booked": [ +// { +// "cardTransactionId": "201710020036959", +// "transactionAmount": { +// "currency": "EUR", +// "amount": "256.67" +// }, +// "transactionDate": "2017-10-25", +// "bookingDate": "2017-10-26", +// "originalAmount": { +// "currency": "SEK", +// "amount": "2499" +// }, +// "cardAcceptorAddress": { +// "city": "STOCKHOLM", +// "country": "SE" +// }, +// "maskedPan": "525412******3241", +// "proprietaryBankTransactionCode": "PURCHASE", +// "invoiced": false, +// "transactionDetails": "WIFIMARKET.SE" +// }, +// { +// "cardTransactionId": "201710020091863", +// "transactionAmount": { +// "currency": "EUR", +// "amount": "10.72" +// }, +// "transactionDate": "2017-10-25", +// "bookingDate": "2017-10-26", +// "originalAmount": { +// "currency": "SEK", +// "amount": "99" +// }, +// "cardAcceptorAddress": { +// "city": "STOCKHOLM", +// "country": "SE" +// }, +// "maskedPan": "525412******8999", +// "proprietaryBankTransactionCode": "PURCHASE", +// "invoiced": false, +// "transactionDetails": "ICA SUPERMARKET SKOGHA" +// } +// ], +// "_links": { +// "cardAccount": { +// "href": "/v1.3/card-accounts/3d9a81b3-a47d-4130-8765-a9c0ff861b99" +// } +// } +// } +// }"""), +// List(AuthenticatedUserIsRequired, UnknownError), +// ApiTag("Account Information Service (AIS)") :: apiTagBerlinGroupM ::Nil +// ) +// +// lazy val getCardAccountTransactionList : OBPEndpoint = { +// case "card-accounts" :: AccountId(accountId):: "transactions" :: Nil JsonGet _ => { +// cc => +// for { +// (Full(u), callContext) <- authenticatedAccess(cc) +// _ <- passesPsd2Aisp(callContext) +// (bankAccount: BankAccount, callContext) <- NewStyle.function.getBankAccountByAccountId(accountId, callContext) +// (bank, callContext) <- NewStyle.function.getBank(bankAccount.bankId, callContext) +// viewId = ViewId(SYSTEM_READ_TRANSACTIONS_BERLIN_GROUP_VIEW_ID) +// bankIdAccountId = BankIdAccountId(bankAccount.bankId, bankAccount.accountId) +// view <- ViewNewStyle.checkAccountAccessAndGetView(viewId, bankIdAccountId, Full(u), callContext) +// params <- Future { createQueriesByHttpParams(callContext.get.requestHeaders)} map { +// x => fullBoxOrException(x ~> APIFailureNewStyle(UnknownError, 400, callContext.map(_.toLight))) // } map { unboxFull(_) } - (transactions, callContext) <- model.toBankAccountExtended(bankAccount).getModeratedTransactionsFuture(bank, Full(u), view, callContext, params) map { - x => fullBoxOrException(x ~> APIFailureNewStyle(UnknownError, 400, callContext.map(_.toLight))) - } map { unboxFull(_) } - } yield { - (JSONFactory_BERLIN_GROUP_1_3.createCardTransactionsJson(bankAccount, transactions), callContext) - } - } - } - - resourceDocs += ResourceDoc( - getConsentAuthorisation, - apiVersion, - nameOf(getConsentAuthorisation), - "GET", - "/consents/CONSENTID/authorisations", - "Get Consent Authorisation Sub-Resources Request", - s"""${mockedDataText(false)} -Return a list of all authorisation subresources IDs which have been created. - -This function returns an array of hyperlinks to all generated authorisation sub-resources. -""", - EmptyBody, - json.parse("""{ - "authorisationIds" : "faa3657e-13f0-4feb-a6c3-34bf21a9ae8e" -}"""), - List(AuthenticatedUserIsRequired, UnknownError), - ApiTag("Account Information Service (AIS)") :: apiTagBerlinGroupM :: Nil - ) - - lazy val getConsentAuthorisation : OBPEndpoint = { - case "consents" :: consentId :: "authorisations" :: Nil JsonGet _ => { - cc => - for { - (_, callContext) <- authenticatedAccess(cc) - _ <- passesPsd2Aisp(callContext) - (challenges, callContext) <- NewStyle.function.getChallengesByConsentId(consentId, callContext) - } yield { - (JSONFactory_BERLIN_GROUP_1_3.AuthorisationJsonV13(challenges.map(_.challengeId)), HttpCode.`200`(callContext)) - } - } - } - - resourceDocs += ResourceDoc( - getConsentInformation, - apiVersion, - nameOf(getConsentInformation), - "GET", - "/consents/CONSENTID", - "Get Consent Request", - s"""${mockedDataText(false)} -Returns the content of an account information consent object. -This is returning the data for the TPP especially in cases, -where the consent was directly managed between ASPSP and PSU e.g. in a re-direct SCA Approach. -""", - EmptyBody, - json.parse("""{ - "access": { - "accounts": [ - { - "bban": "BARC12345612345678", - "maskedPan": "123456xxxxxx1234", - "iban": "FR7612345987650123456789014", - "currency": "EUR", - "msisdn": "+49 170 1234567", - "pan": "5409050000000000" - }, - { - "bban": "BARC12345612345678", - "maskedPan": "123456xxxxxx1234", - "iban": "FR7612345987650123456789014", - "currency": "EUR", - "msisdn": "+49 170 1234567", - "pan": "5409050000000000" - } - ] - }, - "recurringIndicator": false, - "validUntil": "2020-12-31", - "frequencyPerDay": 4, - "combinedServiceIndicator": false, - "lastActionDate": "2019-06-30", - "consentStatus": "received" - }"""), - List(AuthenticatedUserIsRequired, ConsentNotFound, UnknownError), - ApiTag("Account Information Service (AIS)") :: apiTagBerlinGroupM :: Nil - ) - - lazy val getConsentInformation : OBPEndpoint = { - case "consents" :: consentId :: Nil JsonGet _ => { - cc => - for { - (_, callContext) <- applicationAccess(cc) - _ <- passesPsd2Aisp(callContext) - consent <- Future(Consents.consentProvider.vend.getConsentByConsentId(consentId)) map { - unboxFullOrFail(_, callContext, s"$ConsentNotFound ($consentId)") - } - consumerIdFromConsent = consent.mConsumerId.get - consumerIdFromCurrentCall = callContext.map(_.consumer.map(_.consumerId.get).getOrElse("None")).getOrElse("None") - _ <- Helper.booleanToFuture(failMsg = ConsentNotFound, failCode = 403, cc = cc.callContext) { - consumerIdFromConsent == consumerIdFromCurrentCall - } - } yield { - (createGetConsentResponseJson(consent), HttpCode.`200`(callContext)) - } - } - } - - - resourceDocs += ResourceDoc( - getConsentScaStatus, - apiVersion, - nameOf(getConsentScaStatus), - "GET", - "/consents/CONSENTID/authorisations/AUTHORISATIONID", - "Read the SCA status of the consent authorisation", - s"""${mockedDataText(false)} -This method returns the SCA status of a consent initiation's authorisation sub-resource. -""", - EmptyBody, - json.parse("""{ - "scaStatus" : "started" -}"""), - List(AuthenticatedUserIsRequired, UnknownError), - ApiTag("Account Information Service (AIS)") :: apiTagBerlinGroupM :: Nil - ) - - lazy val getConsentScaStatus : OBPEndpoint = { - case "consents" :: consentId:: "authorisations" :: authorisationId :: Nil JsonGet _ => { - cc => - for { - (_, callContext) <- authenticatedAccess(cc) - _ <- passesPsd2Aisp(callContext) - _ <- Future(Consents.consentProvider.vend.getConsentByConsentId(consentId)) map { - unboxFullOrFail(_, callContext, s"$ConsentNotFound ($consentId)", 403) - } - (challenges, callContext) <- NewStyle.function.getChallengesByConsentId(consentId, callContext) - } yield { - val challengeStatus = challenges.filter(_.challengeId == authorisationId) - .flatMap(_.scaStatus).headOption.map(_.toString).getOrElse("None") - (JSONFactory_BERLIN_GROUP_1_3.ScaStatusJsonV13(challengeStatus), HttpCode.`200`(callContext)) - } - } - } - - resourceDocs += ResourceDoc( - getConsentStatus, - apiVersion, - nameOf(getConsentStatus), - "GET", - "/consents/CONSENTID/status", - "Consent status request", - s"""${mockedDataText(false)} - Read the status of an account information consent resource.""", - EmptyBody, - json.parse("""{ - "consentStatus": "received" - }"""), - List(AuthenticatedUserIsRequired, UnknownError), - ApiTag("Account Information Service (AIS)") :: apiTagBerlinGroupM :: Nil - ) - - lazy val getConsentStatus : OBPEndpoint = { - case "consents" :: consentId:: "status" :: Nil JsonGet _ => { - cc => - for { - (_, callContext) <- applicationAccess(cc) - _ <- passesPsd2Aisp(callContext) - consent <- Future(Consents.consentProvider.vend.getConsentByConsentId(consentId)) map { - unboxFullOrFail(_, callContext, ConsentNotFound, 403) - } - } yield { - val status = consent.status - (JSONFactory_BERLIN_GROUP_1_3.ConsentStatusJsonV13(status), HttpCode.`200`(callContext)) - } - - } - } - - resourceDocs += ResourceDoc( - getTransactionDetails, - apiVersion, - nameOf(getTransactionDetails), - "GET", - "/accounts/ACCOUNT_ID/transactions/TRANSACTIONID", - "Read Transaction Details", - s"""${mockedDataText(false)} -Reads transaction details from a given transaction addressed by "transactionId" on a given account addressed -by "account-id". This call is only available on transactions as reported in a JSON format. - -**Remark:** Please note that the PATH might be already given in detail by the corresponding entry of the response -of the "Read Transaction List" call within the _links subfield. - - """, - EmptyBody, - json.parse("""{ - "description": "Example for transaction details", - "value": { - "transactionsDetails": { - "transactionId": "1234567", - "creditorName": "John Miles", - "creditorAccount": { - "iban": "DE67100100101306118605" - }, - "mandateId": "Mandate-2018-04-20-1234-Identification of Mandates, e.g. a SEPA Mandate ID.", - "transactionAmount": { - "currency": "EUR", - "amount": "-256.67" - }, - "bookingDate": "2017-10-25", - "valueDate": "2017-10-26", - "remittanceInformationUnstructured": "Example 1", - "bankTransactionCode": "PMNT-RCVD-ESDD" - } - } -}"""), - List(AuthenticatedUserIsRequired, UnknownError), - ApiTag("Account Information Service (AIS)") :: Nil - ) - - lazy val getTransactionDetails : OBPEndpoint = { - case "accounts" :: accountId :: "transactions" :: transactionId :: Nil JsonGet _ => { - cc => - for { - (Full(user), callContext) <- authenticatedAccess(cc) - (account: BankAccount, callContext) <- NewStyle.function.getBankAccountByAccountId(AccountId(accountId), callContext) - viewId = ViewId(SYSTEM_READ_TRANSACTIONS_BERLIN_GROUP_VIEW_ID) - bankIdAccountId = BankIdAccountId(account.bankId, account.accountId) - view <- ViewNewStyle.checkAccountAccessAndGetView(viewId, bankIdAccountId, Full(user), callContext) - (moderatedTransaction, callContext) <- account.moderatedTransactionFuture(TransactionId(transactionId), view, Some(user), callContext) map { - unboxFullOrFail(_, callContext, GetTransactionsException) - } - } yield { - (JSONFactory_BERLIN_GROUP_1_3.createTransactionJson(account, moderatedTransaction), callContext) - } - } - } - - resourceDocs += ResourceDoc( - getTransactionList, - apiVersion, - nameOf(getTransactionList), - "GET", - "/accounts/ACCOUNT_ID/transactions", - "Read transaction list of an account", - s"""${mockedDataText(false)} -Read transaction reports or transaction lists of a given account addressed by "account-id", -depending on the steering parameter "bookingStatus" together with balances. -For a given account, additional parameters are e.g. the attributes "dateFrom" and "dateTo". -The ASPSP might add balance information, if transaction lists without balances are not supported. """, - EmptyBody, - json.parse("""{ - "account": { - "iban": "DE2310010010123456788" - }, - "transactions": { - "booked": [ - { - "transactionId": "1234567", - "creditorName": "John Miles", - "creditorAccount": { - "iban": "DE67100100101306118605" - }, - "transactionAmount": { - "currency": "EUR", - "amount": "256.67" - }, - "bookingDate": "2017-10-25", - "valueDate": "2017-10-26", - "remittanceInformationUnstructured": "Example 1" - }, - { - "transactionId": "1234568", - "debtorName": "Paul Simpson", - "debtorAccount": { - "iban": "NL76RABO0359400371" - }, - "transactionAmount": { - "currency": "EUR", - "amount": "343.01" - }, - "bookingDate": "2017-10-25", - "valueDate": "2017-10-26", - "remittanceInformationUnstructured": "Example 2" - } - ], - "pending": [ - { - "transactionId": "1234569", - "creditorName": "Claude Renault", - "creditorAccount": { - "iban": "FR7612345987650123456789014" - }, - "transactionAmount": { - "currency": "EUR", - "amount": "-100.03" - }, - "valueDate": "2017-10-26", - "remittanceInformationUnstructured": "Example 3" - } - ], - "_links": { - "account": { - "href": "/v1.3/accounts/3dc3d5b3-7023-4848-9853-f5400a64e80f" - } - } - } - }"""), - List(AuthenticatedUserIsRequired, UnknownError), - ApiTag("Account Information Service (AIS)") :: apiTagBerlinGroupM :: Nil - ) - - lazy val getTransactionList : OBPEndpoint = { - case "accounts" :: AccountId(accountId):: "transactions" :: Nil JsonGet _ => { - cc => - for { - (Full(u), callContext) <- authenticatedAccess(cc) - _ <- passesPsd2Aisp(callContext) - (bankAccount: BankAccount, callContext) <- NewStyle.function.getBankAccountByAccountId(accountId, callContext) - (bank, callContext) <- NewStyle.function.getBank(bankAccount.bankId, callContext) - viewId = ViewId(SYSTEM_READ_TRANSACTIONS_BERLIN_GROUP_VIEW_ID) - bankIdAccountId = BankIdAccountId(bankAccount.bankId, bankAccount.accountId) - view <- ViewNewStyle.checkAccountAccessAndGetView(viewId, bankIdAccountId, Full(u), callContext) - params <- Future { createQueriesByHttpParams(callContext.get.requestHeaders)} map { - x => fullBoxOrException(x ~> APIFailureNewStyle(UnknownError, 400, callContext.map(_.toLight))) - } map { unboxFull(_) } - bookingStatus = APIUtil.getHttpRequestUrlParam(cc.url, "bookingStatus") - _ <- Helper.booleanToFuture(s"$InvalidUrlParameters bookingStatus parameter must take two one of those values : booked, pending or both!", 400, callContext) { - bookingStatus match { - case "booked" | "pending" | "both" => true - case _ => false - } - } - (transactions, callContext) <-bankAccount.getModeratedTransactionsFuture(bank, Full(u), view, callContext, params) map { - x => fullBoxOrException(x ~> APIFailureNewStyle(UnknownError, 400, callContext.map(_.toLight))) - } map { unboxFull(_) } - } yield { - (JSONFactory_BERLIN_GROUP_1_3.createTransactionsJson(bankAccount, transactions, bookingStatus), callContext) - } - } - } - - resourceDocs += ResourceDoc( - getAccountDetails, - apiVersion, - nameOf(getAccountDetails), - "GET", - "/accounts/ACCOUNT_ID", - "Read Account Details", - s"""${mockedDataText(false)} -Reads details about an account, 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. -The addressed details of this account depends then on the stored consent addressed by consentId, -respectively the OAuth2 access token. **NOTE:** The account-id can represent a multicurrency account. -In this case the currency code is set to "XXX". Give detailed information about the addressed account. -Give detailed information about the addressed account together with balance information - - """, - EmptyBody, - json.parse("""{ - "account": { - "resourceId": "3dc3d5b3-7023-4848-9853-f5400a64e80f", - "iban": "FR7612345987650123456789014", - "currency": "EUR", - "product": "Girokonto", - "cashAccountType": "CACC", - "name": "Main Account", - "_links": { - "balances": { - "href": "/v1/accounts/3dc3d5b3-7023-4848-9853-f5400a64e80f/balances" - }, - "transactions": { - "href": "/v1/accounts/3dc3d5b3-7023-4848-9853-f5400a64e80f/transactions" - } - } - } -}"""), - List(AuthenticatedUserIsRequired, UnknownError), - ApiTag("Account Information Service (AIS)") :: apiTagBerlinGroupM :: Nil - ) - - lazy val getAccountDetails : OBPEndpoint = { - case "accounts" :: accountId :: Nil JsonGet _ => { - cc => - for { - (Full(u), callContext) <- authenticatedAccess(cc) - withBalanceParam <- NewStyle.function.tryons(s"$InvalidUrlParameters withBalance parameter can only take two values: TRUE or FALSE!", 400, callContext) { - val withBalance = APIUtil.getHttpRequestUrlParam(cc.url, "withBalance") - if (withBalance.isEmpty) Some(false) else Some(withBalance.toBoolean) - } - _ <- passesPsd2Aisp(callContext) - (account: BankAccount, callContext) <- NewStyle.function.getBankAccountByAccountId(AccountId(accountId), callContext) - (canReadBalancesAccounts, callContext) <- NewStyle.function.getAccountCanReadBalancesOfBerlinGroup(u, callContext) - (canReadTransactionsAccounts, callContext) <- NewStyle.function.getAccountCanReadTransactionsOfBerlinGroup(u, callContext) - _ <- checkAccountAccess(ViewId(SYSTEM_READ_ACCOUNTS_BERLIN_GROUP_VIEW_ID), u, account, callContext) - (accountBalances, callContext) <- code.api.util.newstyle.BankAccountBalanceNewStyle.getBankAccountBalances( - AccountId(accountId), - callContext - ) - } yield { - ( - JSONFactory_BERLIN_GROUP_1_3.createAccountDetailsJson( - account, - canReadBalancesAccounts, - canReadTransactionsAccounts, - withBalanceParam, - accountBalances, - u - ), - callContext - ) - } - } - } - - resourceDocs += ResourceDoc( - readCardAccount, - apiVersion, - nameOf(readCardAccount), - "GET", - "/card-accounts/ACCOUNT_ID", - "Reads details about a card account", - s"""${mockedDataText(false)} -Reads details about a card account. -It is assumed that a consent of the PSU to this access is already given and stored on the ASPSP system. -The addressed details of this account depends then on the stored consent addressed by consentId, -respectively the OAuth2 access token. -""", - EmptyBody, - json.parse("""{ - | "cardAccount": { - | "resourceId": "3d9a81b3-a47d-4130-8765-a9c0ff861b99", - | "maskedPan": "525412******3241", - | "currency": "EUR", - | "name": "Main", - | "product": "Basic Credit", - | "status": "enabled", - | "creditLimit": { - | "currency": "EUR", - | "amount": "15000" - | }, - | "_links": { - | "balances": { - | "href": "/v1/card-accounts/3d9a81b3-a47d-4130-8765-a9c0ff861b99/balances" - | }, - | "transactions": { - | "href": "/v1/card-accounts/3d9a81b3-a47d-4130-8765-a9c0ff861b99/transactions" - | } - | } - | } - |}""".stripMargin), - List(AuthenticatedUserIsRequired, UnknownError), - ApiTag("Account Information Service (AIS)") :: Nil - ) - - lazy val readCardAccount : OBPEndpoint = { - case "card-accounts" :: accountId :: Nil JsonGet _ => { - cc => - for { - (Full(u), callContext) <- authenticatedAccess(cc) - _ <- passesPsd2Aisp(callContext) - (account: BankAccount, callContext) <- NewStyle.function.getBankAccountByAccountId(AccountId(accountId), callContext) - (canReadBalancesAccounts, callContext) <- NewStyle.function.getAccountCanReadBalancesOfBerlinGroup(u, callContext) - (canReadTransactionsAccounts, callContext) <- NewStyle.function.getAccountCanReadTransactionsOfBerlinGroup(u, callContext) - _ <- checkAccountAccess(ViewId(SYSTEM_READ_ACCOUNTS_BERLIN_GROUP_VIEW_ID), u, account, callContext) - withBalanceParam <- NewStyle.function.tryons(s"$InvalidUrlParameters withBalance parameter can only take two values: TRUE or FALSE!", 400, callContext) { - val withBalance = APIUtil.getHttpRequestUrlParam(cc.url, "withBalance") - if (withBalance.isEmpty) Some(false) else Some(withBalance.toBoolean) - } - (accountBalances, callContext) <- code.api.util.newstyle.BankAccountBalanceNewStyle.getBankAccountBalances( - AccountId(accountId), - callContext - ) - } yield { - (JSONFactory_BERLIN_GROUP_1_3.createCardAccountDetailsJson( - account, - canReadBalancesAccounts, - canReadTransactionsAccounts, - withBalanceParam, - accountBalances, - u - ), callContext) - } - } - } - - def generalStartConsentAuthorisationSummary(isMockedData:Boolean) = - s"""${mockedDataText(false)} -Create an authorisation sub-resource and start the authorisation process of a consent. -The message might in addition transmit authentication and authorisation related data. -his method is iterated n times for a n times SCA authorisation in a corporate context, -each creating an own authorisation sub-endpoint for the corresponding PSU authorising the consent. -The ASPSP might make the usage of this access method unnecessary, since the related authorisation -resource will be automatically created by the ASPSP after the submission of the consent data with the -first POST consents call. The start authorisation process is a process which is needed for creating -a new authorisation or cancellation sub-resource. - -This applies in the following scenarios: * The ASPSP has indicated with an 'startAuthorisation' hyperlink -in the preceding Payment Initiation Response that an explicit start of the authorisation process is needed by the TPP. -The 'startAuthorisation' hyperlink can transport more information about data which needs to be uploaded by using -the extended forms. -* 'startAuthorisationWithPsuIdentfication', -* 'startAuthorisationWithPsuAuthentication' -* 'startAuthorisationWithEncryptedPsuAuthentication' -* 'startAuthorisationWithAuthentciationMethodSelection' -* The related payment initiation cannot yet be executed since a multilevel SCA is mandated. -* The ASPSP has indicated with an 'startAuthorisation' hyperlink in the preceding Payment Cancellation -Response that an explicit start of the authorisation process is needed by the TPP. - -The 'startAuthorisation' hyperlink can transport more information about data which needs to be uploaded by -using the extended forms as indicated above. -* The related payment cancellation request cannot be applied yet since a multilevel SCA is mandate for executing the cancellation. -* The signing basket needs to be authorised yet. - -""" - - resourceDocs += ResourceDoc( - startConsentAuthorisationTransactionAuthorisation, - apiVersion, - nameOf(startConsentAuthorisationTransactionAuthorisation), - "POST", - "/consents/CONSENTID/authorisations", - "Start the authorisation process for a consent(transactionAuthorisation)", - generalStartConsentAuthorisationSummary(false), - json.parse("""{"scaAuthenticationData":""}"""), - json.parse("""{ - "scaStatus": "received", - "psuMessage": "Please use your BankApp for transaction Authorisation.", - "authorisationId": "123auth456.", - "_links": - { - "scaStatus": {"href":"/v1.3/consents/qwer3456tzui7890/authorisations/123auth456"} - } - }"""), - List(AuthenticatedUserIsRequired, UnknownError), - ApiTag("Account Information Service (AIS)") :: apiTagBerlinGroupM :: Nil - ) - - lazy val startConsentAuthorisationTransactionAuthorisation : OBPEndpoint = { - case "consents" :: consentId :: "authorisations" :: Nil JsonPost json -> _ if checkTransactionAuthorisation(json)=> { - cc => - for { - (Full(u), callContext) <- authenticatedAccess(cc) - _ <- passesPsd2Aisp(callContext) - consent <- Future(Consents.consentProvider.vend.getConsentByConsentId(consentId)) map { - unboxFullOrFail(_, callContext, ConsentNotFound, 403) - } - (challenges, callContext) <- NewStyle.function.createChallengesC2( - List(u.userId), - ChallengeType.BERLIN_GROUP_CONSENT_CHALLENGE, - None, - getSuggestedDefaultScaMethod(), - Some(StrongCustomerAuthenticationStatus.received), - Some(consentId), - None, - callContext - ) - //NOTE: in OBP it support multiple challenges, but in Berlin Group it has only one challenge. The following guard is to make sure it return the 1st challenge properly. - challenge <- NewStyle.function.tryons(InvalidConnectorResponseForCreateChallenge,400, callContext) { - challenges.head - } - } yield { - (createStartConsentAuthorisationJson(consent, challenge), HttpCode.`201`(callContext)) - } - } - } - - resourceDocs += ResourceDoc( - startConsentAuthorisationUpdatePsuAuthentication, - apiVersion, - nameOf(startConsentAuthorisationUpdatePsuAuthentication), - "POST", - "/consents/CONSENTID/authorisations", - "Start the authorisation process for a consent(updatePsuAuthentication)", - generalStartConsentAuthorisationSummary(true), - json.parse("""{ - "psuData": { - "password": "start12" - } - }"""), - json.parse("""{ - "scaStatus": "received", - "psuMessage": "Please use your BankApp for transaction Authorisation.", - "authorisationId": "123auth456.", - "_links": - { - "scaStatus": {"href":"/v1.3/consents/qwer3456tzui7890/authorisations/123auth456"} - } - }"""), - List(AuthenticatedUserIsRequired, UnknownError), - ApiTag("Account Information Service (AIS)") :: apiTagBerlinGroupM :: Nil - ) - - lazy val startConsentAuthorisationUpdatePsuAuthentication : OBPEndpoint = { - case "consents" :: consentId :: "authorisations" :: Nil JsonPost json -> _ if checkUpdatePsuAuthentication(json)=> { - cc => - for { - (Full(u), callContext) <- authenticatedAccess(cc) - } yield { - (liftweb.json.parse( - """{ - "scaStatus": "received", - "psuMessage": "Please use your BankApp for transaction Authorisation.", - "authorisationId": "123auth456.", - "_links": - { - "scaStatus": {"href":"/v1.3/consents/qwer3456tzui7890/authorisations/123auth456"} - } - }"""),HttpCode.`201`(callContext)) - } - } - } - - resourceDocs += ResourceDoc( - startConsentAuthorisationSelectPsuAuthenticationMethod, - apiVersion, - nameOf(startConsentAuthorisationSelectPsuAuthenticationMethod), - "POST", - "/consents/CONSENTID/authorisations", - "Start the authorisation process for a consent(selectPsuAuthenticationMethod)", - generalStartConsentAuthorisationSummary(true), - json.parse("""{"authenticationMethodId":""}"""), - json.parse("""{ - "scaStatus": "received", - "psuMessage": "Please use your BankApp for transaction Authorisation.", - "authorisationId": "123auth456.", - "_links": - { - "scaStatus": {"href":"/v1.3/consents/qwer3456tzui7890/authorisations/123auth456"} - } - }"""), - List(AuthenticatedUserIsRequired, UnknownError), - ApiTag("Account Information Service (AIS)") :: apiTagBerlinGroupM :: Nil - ) - - lazy val startConsentAuthorisationSelectPsuAuthenticationMethod : OBPEndpoint = { - case "consents" :: consentId :: "authorisations" :: Nil JsonPost json -> _ if checkSelectPsuAuthenticationMethod(json)=> { - cc => - for { - (Full(u), callContext) <- authenticatedAccess(cc) - } yield { - (liftweb.json.parse( - """{ - "scaStatus": "received", - "psuMessage": "Please use your BankApp for transaction Authorisation.", - "authorisationId": "123auth456.", - "_links": - { - "scaStatus": {"href":"/v1.3/consents/qwer3456tzui7890/authorisations/123auth456"} - } - }"""),HttpCode.`201`(callContext)) - } - } - } - - def generalUpdateConsentsPsuDataSummary(isMockedData: Boolean) = - s"""${mockedDataText(false)} -This method update PSU data on the consents resource if needed. It may authorise a consent within the Embedded -SCA Approach where needed. Independently from the SCA Approach it supports -e.g. the selection of the authentication method and a non-SCA PSU authentication. -This methods updates PSU data on the cancellation authorisation resource if needed. -There are several possible Update PSU Data requests in the context of a consent request if needed, -which depends on the SCA approach: * Redirect SCA Approach: A specific Update PSU Data Request is applicable -for -* the selection of authentication methods, before choosing the actual SCA approach. -* Decoupled SCA Approach: A specific Update PSU Data Request is only applicable for -* adding the PSU Identification, if not provided yet in the Payment Initiation Request or the Account Information Consent Request, -or if no OAuth2 access token is used, or -* the selection of authentication methods. -* Embedded SCA Approach: The Update PSU Data Request might be used -* to add credentials as a first factor authentication data of the PSU and -* to select the authentication method and -* transaction authorisation. -The SCA Approach might depend on the chosen SCA method. For that reason, -the following possible Update PSU Data request can apply to all SCA approaches: -* Select an SCA method in case of several SCA methods are available for the customer. There are the following request types on this access path: -* Update PSU Identification * Update PSU Authentication -* Select PSU Autorization Method WARNING: This method need a reduced header, therefore many optional elements are not present. -Maybe in a later version the access path will change. -* Transaction Authorisation WARNING: This method need a reduced header, therefore many optional elements are not present. -Maybe in a later version the access path will change. - - """ - - resourceDocs += ResourceDoc( - updateConsentsPsuDataTransactionAuthorisation, - apiVersion, - nameOf(updateConsentsPsuDataTransactionAuthorisation), - "PUT", - "/consents/CONSENTID/authorisations/AUTHORISATIONID", - "Update PSU Data for consents (transactionAuthorisation)", - generalUpdateConsentsPsuDataSummary(false), - json.parse("""{"scaAuthenticationData":"123"}"""), - ScaStatusResponse( - scaStatus = "received", - _links = Some(LinksAll(scaStatus = Some(HrefType(Some(s"/v1.3/consents/1234-wertiq-983/authorisations"))))) - ), - List(AuthenticatedUserIsRequired, UnknownError), - ApiTag("Account Information Service (AIS)") :: apiTagBerlinGroupM :: Nil - ) - - lazy val updateConsentsPsuDataTransactionAuthorisation : OBPEndpoint = { - case "consents" :: consentId :: "authorisations" :: authorisationId :: Nil JsonPut jsonPut -> _ if checkTransactionAuthorisation(jsonPut) => { - cc => - for { - (Full(u), callContext) <- authenticatedAccess(cc) - _ <- passesPsd2Aisp(callContext) - _ <- Future(Consents.consentProvider.vend.getConsentByConsentId(consentId)) map { - unboxFullOrFail(_, callContext, ConsentNotFound, 403) - } - failMsg = s"$InvalidJsonFormat The Json body should be the $TransactionAuthorisation " - updateJson <- NewStyle.function.tryons(failMsg, 400, callContext) { - jsonPut.extract[TransactionAuthorisation] - } - (_, callContext) <- NewStyle.function.getChallenge(authorisationId, callContext) - (challenge, callContext) <- NewStyle.function.validateChallengeAnswerC4( - ChallengeType.BERLIN_GROUP_CONSENT_CHALLENGE, - None, - Some(consentId), - authorisationId, - updateJson.scaAuthenticationData, - SuppliedAnswerType.PLAIN_TEXT_VALUE, - callContext - ) - consent <- challenge.scaStatus match { - case Some(status) if status == StrongCustomerAuthenticationStatus.finalised => // finalised - Future(Consents.consentProvider.vend.updateConsentStatus(consentId, ConsentStatus.valid)) - case Some(status) if status == StrongCustomerAuthenticationStatus.failed => // failed - Future(Consents.consentProvider.vend.updateConsentStatus(consentId, ConsentStatus.rejected)) - case _ => // all other cases - Future(Consents.consentProvider.vend.getConsentByConsentId(consentId)) - } - _ <- NewStyle.function.tryons(ConsentUpdateStatusError, 400, callContext) { - consent.toList.size == 1 - } - _ <- Future { - val authContexts = UserAuthContextProvider.userAuthContextProvider.vend.getUserAuthContextsBox(u.userId) - .map(_.map(i => BasicUserAuthContext(i.key, i.value))) - ConsentAuthContextProvider.consentAuthContextProvider.vend.createOrUpdateConsentAuthContexts(consentId, authContexts.getOrElse(Nil)) - } map { - unboxFullOrFail(_, callContext, ConsentUserAuthContextCannotBeAdded) - } - _ <- Future(Consents.consentProvider.vend.updateConsentUser(consentId, u)) map { - unboxFullOrFail(_, callContext, ConsentUserCannotBeAdded) - } - } yield { - (createPutConsentResponseJson(consent.toList.head), HttpCode.`200`(callContext)) - } - } - } - - resourceDocs += ResourceDoc( - updateConsentsPsuDataUpdatePsuAuthentication, - apiVersion, - nameOf(updateConsentsPsuDataUpdatePsuAuthentication), - "PUT", - "/consents/CONSENTID/authorisations/AUTHORISATIONID", - "Update PSU Data for consents (updatePsuAuthentication)", - generalUpdateConsentsPsuDataSummary(true), - json.parse("""{"psuData": {"password": "start12"}}""".stripMargin), - json.parse("""{ - | "scaStatus": "psuAuthenticated", - | "_links": { - | "authoriseTransaction": {"href": "/psd2/v1/payments/1234-wertiq-983/authorisations/123auth456"} - | } - | }""".stripMargin), - List(AuthenticatedUserIsRequired, UnknownError), - ApiTag("Account Information Service (AIS)") :: apiTagBerlinGroupM :: Nil - ) - - lazy val updateConsentsPsuDataUpdatePsuAuthentication : OBPEndpoint = { - case "consents" :: consentId :: "authorisations" :: authorisationId :: Nil JsonPut jsonPut -> _ if checkUpdatePsuAuthentication(jsonPut) => { - cc => - for { - (Full(u), callContext) <- authenticatedAccess(cc) - - } yield { - (liftweb.json.parse( - """{ - | "scaStatus": "psuAuthenticated", - | "_links": { - | "authoriseTransaction": {"href": "/psd2/v1/payments/1234-wertiq-983/authorisations/123auth456"} - | } - |}""".stripMargin), HttpCode.`200`(callContext)) - } - } - } - - resourceDocs += ResourceDoc( - updateConsentsPsuDataUpdateSelectPsuAuthenticationMethod, - apiVersion, - nameOf(updateConsentsPsuDataUpdateSelectPsuAuthenticationMethod), - "PUT", - "/consents/CONSENTID/authorisations/AUTHORISATIONID", - "Update PSU Data for consents (selectPsuAuthenticationMethod)", - generalUpdateConsentsPsuDataSummary(true), - json.parse("""{ - | "authenticationMethodId": "myAuthenticationID" - |}""".stripMargin), - json.parse("""{ - | "scaStatus": "scaMethodSelected", - | "chosenScaMethod": { - | "authenticationType": "SMS_OTP", - | "authenticationMethodId": "myAuthenticationID"}, - | "challengeData": { - | "otpMaxLength": 6, - | "otpFormat": "integer"}, - | "_links": { - | "authoriseTransaction": {"href": "/psd2/v1/payments/1234-wertiq-983/authorisations/123auth456"} - | } - | }""".stripMargin), - List(AuthenticatedUserIsRequired, UnknownError), - ApiTag("Account Information Service (AIS)") :: apiTagBerlinGroupM :: Nil - ) - - lazy val updateConsentsPsuDataUpdateSelectPsuAuthenticationMethod : OBPEndpoint = { - case "consents" :: consentId :: "authorisations" :: authorisationId :: Nil JsonPut jsonPut -> _ if checkSelectPsuAuthenticationMethod(jsonPut) => { - cc => - for { - (Full(u), callContext) <- authenticatedAccess(cc) - - } yield { - (liftweb.json.parse( - """{ - | "scaStatus": "scaMethodSelected", - | "chosenScaMethod": { - | "authenticationType": "SMS_OTP", - | "authenticationMethodId": "myAuthenticationID"}, - | "challengeData": { - | "otpMaxLength": 6, - | "otpFormat": "integer"}, - | "_links": { - | "authoriseTransaction": {"href": "/psd2/v1/payments/1234-wertiq-983/authorisations/123auth456"} - | } - |}""".stripMargin), HttpCode.`200`(callContext)) - } - } - } - - resourceDocs += ResourceDoc( - updateConsentsPsuDataUpdateAuthorisationConfirmation, - apiVersion, - nameOf(updateConsentsPsuDataUpdateAuthorisationConfirmation), - "PUT", - "/consents/CONSENTID/authorisations/AUTHORISATIONID", - "Update PSU Data for consents (authorisationConfirmation)", - generalUpdateConsentsPsuDataSummary(true), - json.parse("""{"confirmationCode":"confirmationCode"}"""), - json.parse("""{ - | "scaStatus": "finalised", - | "_links":{ - | "status": {"href":"/v1/payments/sepa-credit-transfers/qwer3456tzui7890/status"} - | } - | }""".stripMargin), - List(AuthenticatedUserIsRequired, UnknownError), - ApiTag("Account Information Service (AIS)") :: apiTagBerlinGroupM :: Nil - ) - - lazy val updateConsentsPsuDataUpdateAuthorisationConfirmation : OBPEndpoint = { - case "consents" :: consentId :: "authorisations" :: authorisationId :: Nil JsonPut jsonPut -> _ if checkAuthorisationConfirmation(jsonPut) => { - cc => - for { - (Full(u), callContext) <- authenticatedAccess(cc) - } yield { - (json.parse( - """{ - | "scaStatus": "finalised", - | "_links":{ - | "status": {"href":"/v1/payments/sepa-credit-transfers/qwer3456tzui7890/status"} - | } - |}""".stripMargin), - HttpCode.`200`(callContext)) - } - } - } - -} - - - +//// (transactionRequests, callContext) <- Future { Connector.connector.vend.getTransactionRequests210(u, bankAccount, callContext)} map { +//// x => fullBoxOrException(x ~> APIFailureNewStyle(InvalidConnectorResponseForGetTransactionRequests210, 400, callContext.map(_.toLight))) +//// } map { unboxFull(_) } +// (transactions, callContext) <- model.toBankAccountExtended(bankAccount).getModeratedTransactionsFuture(bank, Full(u), view, callContext, params) map { +// x => fullBoxOrException(x ~> APIFailureNewStyle(UnknownError, 400, callContext.map(_.toLight))) +// } map { unboxFull(_) } +// } yield { +// (JSONFactory_BERLIN_GROUP_1_3.createCardTransactionsJson(bankAccount, transactions), callContext) +// } +// } +// } +// +// resourceDocs += ResourceDoc( +// getConsentAuthorisation, +// apiVersion, +// nameOf(getConsentAuthorisation), +// "GET", +// "/consents/CONSENTID/authorisations", +// "Get Consent Authorisation Sub-Resources Request", +// s"""${mockedDataText(false)} +//Return a list of all authorisation subresources IDs which have been created. +// +//This function returns an array of hyperlinks to all generated authorisation sub-resources. +//""", +// EmptyBody, +// json.parse("""{ +// "authorisationIds" : "faa3657e-13f0-4feb-a6c3-34bf21a9ae8e" +//}"""), +// List(AuthenticatedUserIsRequired, UnknownError), +// ApiTag("Account Information Service (AIS)") :: apiTagBerlinGroupM :: Nil +// ) +// +// lazy val getConsentAuthorisation : OBPEndpoint = { +// case "consents" :: consentId :: "authorisations" :: Nil JsonGet _ => { +// cc => +// for { +// (_, callContext) <- authenticatedAccess(cc) +// _ <- passesPsd2Aisp(callContext) +// (challenges, callContext) <- NewStyle.function.getChallengesByConsentId(consentId, callContext) +// } yield { +// (JSONFactory_BERLIN_GROUP_1_3.AuthorisationJsonV13(challenges.map(_.challengeId)), HttpCode.`200`(callContext)) +// } +// } +// } +// +// resourceDocs += ResourceDoc( +// getConsentInformation, +// apiVersion, +// nameOf(getConsentInformation), +// "GET", +// "/consents/CONSENTID", +// "Get Consent Request", +// s"""${mockedDataText(false)} +//Returns the content of an account information consent object. +//This is returning the data for the TPP especially in cases, +//where the consent was directly managed between ASPSP and PSU e.g. in a re-direct SCA Approach. +//""", +// EmptyBody, +// json.parse("""{ +// "access": { +// "accounts": [ +// { +// "bban": "BARC12345612345678", +// "maskedPan": "123456xxxxxx1234", +// "iban": "FR7612345987650123456789014", +// "currency": "EUR", +// "msisdn": "+49 170 1234567", +// "pan": "5409050000000000" +// }, +// { +// "bban": "BARC12345612345678", +// "maskedPan": "123456xxxxxx1234", +// "iban": "FR7612345987650123456789014", +// "currency": "EUR", +// "msisdn": "+49 170 1234567", +// "pan": "5409050000000000" +// } +// ] +// }, +// "recurringIndicator": false, +// "validUntil": "2020-12-31", +// "frequencyPerDay": 4, +// "combinedServiceIndicator": false, +// "lastActionDate": "2019-06-30", +// "consentStatus": "received" +// }"""), +// List(AuthenticatedUserIsRequired, ConsentNotFound, UnknownError), +// ApiTag("Account Information Service (AIS)") :: apiTagBerlinGroupM :: Nil +// ) +// +// lazy val getConsentInformation : OBPEndpoint = { +// case "consents" :: consentId :: Nil JsonGet _ => { +// cc => +// for { +// (_, callContext) <- applicationAccess(cc) +// _ <- passesPsd2Aisp(callContext) +// consent <- Future(Consents.consentProvider.vend.getConsentByConsentId(consentId)) map { +// unboxFullOrFail(_, callContext, s"$ConsentNotFound ($consentId)") +// } +// consumerIdFromConsent = consent.mConsumerId.get +// consumerIdFromCurrentCall = callContext.map(_.consumer.map(_.consumerId.get).getOrElse("None")).getOrElse("None") +// _ <- Helper.booleanToFuture(failMsg = ConsentNotFound, failCode = 403, cc = cc.callContext) { +// consumerIdFromConsent == consumerIdFromCurrentCall +// } +// } yield { +// (createGetConsentResponseJson(consent), HttpCode.`200`(callContext)) +// } +// } +// } +// +// +// resourceDocs += ResourceDoc( +// getConsentScaStatus, +// apiVersion, +// nameOf(getConsentScaStatus), +// "GET", +// "/consents/CONSENTID/authorisations/AUTHORISATIONID", +// "Read the SCA status of the consent authorisation", +// s"""${mockedDataText(false)} +//This method returns the SCA status of a consent initiation's authorisation sub-resource. +//""", +// EmptyBody, +// json.parse("""{ +// "scaStatus" : "started" +//}"""), +// List(AuthenticatedUserIsRequired, UnknownError), +// ApiTag("Account Information Service (AIS)") :: apiTagBerlinGroupM :: Nil +// ) +// +// lazy val getConsentScaStatus : OBPEndpoint = { +// case "consents" :: consentId:: "authorisations" :: authorisationId :: Nil JsonGet _ => { +// cc => +// for { +// (_, callContext) <- authenticatedAccess(cc) +// _ <- passesPsd2Aisp(callContext) +// _ <- Future(Consents.consentProvider.vend.getConsentByConsentId(consentId)) map { +// unboxFullOrFail(_, callContext, s"$ConsentNotFound ($consentId)", 403) +// } +// (challenges, callContext) <- NewStyle.function.getChallengesByConsentId(consentId, callContext) +// } yield { +// val challengeStatus = challenges.filter(_.challengeId == authorisationId) +// .flatMap(_.scaStatus).headOption.map(_.toString).getOrElse("None") +// (JSONFactory_BERLIN_GROUP_1_3.ScaStatusJsonV13(challengeStatus), HttpCode.`200`(callContext)) +// } +// } +// } +// +// resourceDocs += ResourceDoc( +// getConsentStatus, +// apiVersion, +// nameOf(getConsentStatus), +// "GET", +// "/consents/CONSENTID/status", +// "Consent status request", +// s"""${mockedDataText(false)} +// Read the status of an account information consent resource.""", +// EmptyBody, +// json.parse("""{ +// "consentStatus": "received" +// }"""), +// List(AuthenticatedUserIsRequired, UnknownError), +// ApiTag("Account Information Service (AIS)") :: apiTagBerlinGroupM :: Nil +// ) +// +// lazy val getConsentStatus : OBPEndpoint = { +// case "consents" :: consentId:: "status" :: Nil JsonGet _ => { +// cc => +// for { +// (_, callContext) <- applicationAccess(cc) +// _ <- passesPsd2Aisp(callContext) +// consent <- Future(Consents.consentProvider.vend.getConsentByConsentId(consentId)) map { +// unboxFullOrFail(_, callContext, ConsentNotFound, 403) +// } +// } yield { +// val status = consent.status +// (JSONFactory_BERLIN_GROUP_1_3.ConsentStatusJsonV13(status), HttpCode.`200`(callContext)) +// } +// +// } +// } +// +// resourceDocs += ResourceDoc( +// getTransactionDetails, +// apiVersion, +// nameOf(getTransactionDetails), +// "GET", +// "/accounts/ACCOUNT_ID/transactions/TRANSACTIONID", +// "Read Transaction Details", +// s"""${mockedDataText(false)} +//Reads transaction details from a given transaction addressed by "transactionId" on a given account addressed +//by "account-id". This call is only available on transactions as reported in a JSON format. +// +//**Remark:** Please note that the PATH might be already given in detail by the corresponding entry of the response +//of the "Read Transaction List" call within the _links subfield. +// +// """, +// EmptyBody, +// json.parse("""{ +// "description": "Example for transaction details", +// "value": { +// "transactionsDetails": { +// "transactionId": "1234567", +// "creditorName": "John Miles", +// "creditorAccount": { +// "iban": "DE67100100101306118605" +// }, +// "mandateId": "Mandate-2018-04-20-1234-Identification of Mandates, e.g. a SEPA Mandate ID.", +// "transactionAmount": { +// "currency": "EUR", +// "amount": "-256.67" +// }, +// "bookingDate": "2017-10-25", +// "valueDate": "2017-10-26", +// "remittanceInformationUnstructured": "Example 1", +// "bankTransactionCode": "PMNT-RCVD-ESDD" +// } +// } +//}"""), +// List(AuthenticatedUserIsRequired, UnknownError), +// ApiTag("Account Information Service (AIS)") :: Nil +// ) +// +// lazy val getTransactionDetails : OBPEndpoint = { +// case "accounts" :: accountId :: "transactions" :: transactionId :: Nil JsonGet _ => { +// cc => +// for { +// (Full(user), callContext) <- authenticatedAccess(cc) +// (account: BankAccount, callContext) <- NewStyle.function.getBankAccountByAccountId(AccountId(accountId), callContext) +// viewId = ViewId(SYSTEM_READ_TRANSACTIONS_BERLIN_GROUP_VIEW_ID) +// bankIdAccountId = BankIdAccountId(account.bankId, account.accountId) +// view <- ViewNewStyle.checkAccountAccessAndGetView(viewId, bankIdAccountId, Full(user), callContext) +// (moderatedTransaction, callContext) <- account.moderatedTransactionFuture(TransactionId(transactionId), view, Some(user), callContext) map { +// unboxFullOrFail(_, callContext, GetTransactionsException) +// } +// } yield { +// (JSONFactory_BERLIN_GROUP_1_3.createTransactionJson(account, moderatedTransaction), callContext) +// } +// } +// } +// +// resourceDocs += ResourceDoc( +// getTransactionList, +// apiVersion, +// nameOf(getTransactionList), +// "GET", +// "/accounts/ACCOUNT_ID/transactions", +// "Read transaction list of an account", +// s"""${mockedDataText(false)} +//Read transaction reports or transaction lists of a given account addressed by "account-id", +//depending on the steering parameter "bookingStatus" together with balances. +//For a given account, additional parameters are e.g. the attributes "dateFrom" and "dateTo". +//The ASPSP might add balance information, if transaction lists without balances are not supported. """, +// EmptyBody, +// json.parse("""{ +// "account": { +// "iban": "DE2310010010123456788" +// }, +// "transactions": { +// "booked": [ +// { +// "transactionId": "1234567", +// "creditorName": "John Miles", +// "creditorAccount": { +// "iban": "DE67100100101306118605" +// }, +// "transactionAmount": { +// "currency": "EUR", +// "amount": "256.67" +// }, +// "bookingDate": "2017-10-25", +// "valueDate": "2017-10-26", +// "remittanceInformationUnstructured": "Example 1" +// }, +// { +// "transactionId": "1234568", +// "debtorName": "Paul Simpson", +// "debtorAccount": { +// "iban": "NL76RABO0359400371" +// }, +// "transactionAmount": { +// "currency": "EUR", +// "amount": "343.01" +// }, +// "bookingDate": "2017-10-25", +// "valueDate": "2017-10-26", +// "remittanceInformationUnstructured": "Example 2" +// } +// ], +// "pending": [ +// { +// "transactionId": "1234569", +// "creditorName": "Claude Renault", +// "creditorAccount": { +// "iban": "FR7612345987650123456789014" +// }, +// "transactionAmount": { +// "currency": "EUR", +// "amount": "-100.03" +// }, +// "valueDate": "2017-10-26", +// "remittanceInformationUnstructured": "Example 3" +// } +// ], +// "_links": { +// "account": { +// "href": "/v1.3/accounts/3dc3d5b3-7023-4848-9853-f5400a64e80f" +// } +// } +// } +// }"""), +// List(AuthenticatedUserIsRequired, UnknownError), +// ApiTag("Account Information Service (AIS)") :: apiTagBerlinGroupM :: Nil +// ) +// +// lazy val getTransactionList : OBPEndpoint = { +// case "accounts" :: AccountId(accountId):: "transactions" :: Nil JsonGet _ => { +// cc => +// for { +// (Full(u), callContext) <- authenticatedAccess(cc) +// _ <- passesPsd2Aisp(callContext) +// (bankAccount: BankAccount, callContext) <- NewStyle.function.getBankAccountByAccountId(accountId, callContext) +// (bank, callContext) <- NewStyle.function.getBank(bankAccount.bankId, callContext) +// viewId = ViewId(SYSTEM_READ_TRANSACTIONS_BERLIN_GROUP_VIEW_ID) +// bankIdAccountId = BankIdAccountId(bankAccount.bankId, bankAccount.accountId) +// view <- ViewNewStyle.checkAccountAccessAndGetView(viewId, bankIdAccountId, Full(u), callContext) +// params <- Future { createQueriesByHttpParams(callContext.get.requestHeaders)} map { +// x => fullBoxOrException(x ~> APIFailureNewStyle(UnknownError, 400, callContext.map(_.toLight))) +// } map { unboxFull(_) } +// bookingStatus = APIUtil.getHttpRequestUrlParam(cc.url, "bookingStatus") +// _ <- Helper.booleanToFuture(s"$InvalidUrlParameters bookingStatus parameter must take two one of those values : booked, pending or both!", 400, callContext) { +// bookingStatus match { +// case "booked" | "pending" | "both" => true +// case _ => false +// } +// } +// (transactions, callContext) <-bankAccount.getModeratedTransactionsFuture(bank, Full(u), view, callContext, params) map { +// x => fullBoxOrException(x ~> APIFailureNewStyle(UnknownError, 400, callContext.map(_.toLight))) +// } map { unboxFull(_) } +// } yield { +// (JSONFactory_BERLIN_GROUP_1_3.createTransactionsJson(bankAccount, transactions, bookingStatus), callContext) +// } +// } +// } +// +// resourceDocs += ResourceDoc( +// getAccountDetails, +// apiVersion, +// nameOf(getAccountDetails), +// "GET", +// "/accounts/ACCOUNT_ID", +// "Read Account Details", +// s"""${mockedDataText(false)} +//Reads details about an account, 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. +//The addressed details of this account depends then on the stored consent addressed by consentId, +//respectively the OAuth2 access token. **NOTE:** The account-id can represent a multicurrency account. +//In this case the currency code is set to "XXX". Give detailed information about the addressed account. +//Give detailed information about the addressed account together with balance information +// +// """, +// EmptyBody, +// json.parse("""{ +// "account": { +// "resourceId": "3dc3d5b3-7023-4848-9853-f5400a64e80f", +// "iban": "FR7612345987650123456789014", +// "currency": "EUR", +// "product": "Girokonto", +// "cashAccountType": "CACC", +// "name": "Main Account", +// "_links": { +// "balances": { +// "href": "/v1/accounts/3dc3d5b3-7023-4848-9853-f5400a64e80f/balances" +// }, +// "transactions": { +// "href": "/v1/accounts/3dc3d5b3-7023-4848-9853-f5400a64e80f/transactions" +// } +// } +// } +//}"""), +// List(AuthenticatedUserIsRequired, UnknownError), +// ApiTag("Account Information Service (AIS)") :: apiTagBerlinGroupM :: Nil +// ) +// +// lazy val getAccountDetails : OBPEndpoint = { +// case "accounts" :: accountId :: Nil JsonGet _ => { +// cc => +// for { +// (Full(u), callContext) <- authenticatedAccess(cc) +// withBalanceParam <- NewStyle.function.tryons(s"$InvalidUrlParameters withBalance parameter can only take two values: TRUE or FALSE!", 400, callContext) { +// val withBalance = APIUtil.getHttpRequestUrlParam(cc.url, "withBalance") +// if (withBalance.isEmpty) Some(false) else Some(withBalance.toBoolean) +// } +// _ <- passesPsd2Aisp(callContext) +// (account: BankAccount, callContext) <- NewStyle.function.getBankAccountByAccountId(AccountId(accountId), callContext) +// (canReadBalancesAccounts, callContext) <- NewStyle.function.getAccountCanReadBalancesOfBerlinGroup(u, callContext) +// (canReadTransactionsAccounts, callContext) <- NewStyle.function.getAccountCanReadTransactionsOfBerlinGroup(u, callContext) +// _ <- checkAccountAccess(ViewId(SYSTEM_READ_ACCOUNTS_BERLIN_GROUP_VIEW_ID), u, account, callContext) +// (accountBalances, callContext) <- code.api.util.newstyle.BankAccountBalanceNewStyle.getBankAccountBalances( +// AccountId(accountId), +// callContext +// ) +// } yield { +// ( +// JSONFactory_BERLIN_GROUP_1_3.createAccountDetailsJson( +// account, +// canReadBalancesAccounts, +// canReadTransactionsAccounts, +// withBalanceParam, +// accountBalances, +// u +// ), +// callContext +// ) +// } +// } +// } +// +// resourceDocs += ResourceDoc( +// readCardAccount, +// apiVersion, +// nameOf(readCardAccount), +// "GET", +// "/card-accounts/ACCOUNT_ID", +// "Reads details about a card account", +// s"""${mockedDataText(false)} +//Reads details about a card account. +//It is assumed that a consent of the PSU to this access is already given and stored on the ASPSP system. +//The addressed details of this account depends then on the stored consent addressed by consentId, +//respectively the OAuth2 access token. +//""", +// EmptyBody, +// json.parse("""{ +// | "cardAccount": { +// | "resourceId": "3d9a81b3-a47d-4130-8765-a9c0ff861b99", +// | "maskedPan": "525412******3241", +// | "currency": "EUR", +// | "name": "Main", +// | "product": "Basic Credit", +// | "status": "enabled", +// | "creditLimit": { +// | "currency": "EUR", +// | "amount": "15000" +// | }, +// | "_links": { +// | "balances": { +// | "href": "/v1/card-accounts/3d9a81b3-a47d-4130-8765-a9c0ff861b99/balances" +// | }, +// | "transactions": { +// | "href": "/v1/card-accounts/3d9a81b3-a47d-4130-8765-a9c0ff861b99/transactions" +// | } +// | } +// | } +// |}""".stripMargin), +// List(AuthenticatedUserIsRequired, UnknownError), +// ApiTag("Account Information Service (AIS)") :: Nil +// ) +// +// lazy val readCardAccount : OBPEndpoint = { +// case "card-accounts" :: accountId :: Nil JsonGet _ => { +// cc => +// for { +// (Full(u), callContext) <- authenticatedAccess(cc) +// _ <- passesPsd2Aisp(callContext) +// (account: BankAccount, callContext) <- NewStyle.function.getBankAccountByAccountId(AccountId(accountId), callContext) +// (canReadBalancesAccounts, callContext) <- NewStyle.function.getAccountCanReadBalancesOfBerlinGroup(u, callContext) +// (canReadTransactionsAccounts, callContext) <- NewStyle.function.getAccountCanReadTransactionsOfBerlinGroup(u, callContext) +// _ <- checkAccountAccess(ViewId(SYSTEM_READ_ACCOUNTS_BERLIN_GROUP_VIEW_ID), u, account, callContext) +// withBalanceParam <- NewStyle.function.tryons(s"$InvalidUrlParameters withBalance parameter can only take two values: TRUE or FALSE!", 400, callContext) { +// val withBalance = APIUtil.getHttpRequestUrlParam(cc.url, "withBalance") +// if (withBalance.isEmpty) Some(false) else Some(withBalance.toBoolean) +// } +// (accountBalances, callContext) <- code.api.util.newstyle.BankAccountBalanceNewStyle.getBankAccountBalances( +// AccountId(accountId), +// callContext +// ) +// } yield { +// (JSONFactory_BERLIN_GROUP_1_3.createCardAccountDetailsJson( +// account, +// canReadBalancesAccounts, +// canReadTransactionsAccounts, +// withBalanceParam, +// accountBalances, +// u +// ), callContext) +// } +// } +// } +// +// def generalStartConsentAuthorisationSummary(isMockedData:Boolean) = +// s"""${mockedDataText(false)} +//Create an authorisation sub-resource and start the authorisation process of a consent. +//The message might in addition transmit authentication and authorisation related data. +//his method is iterated n times for a n times SCA authorisation in a corporate context, +//each creating an own authorisation sub-endpoint for the corresponding PSU authorising the consent. +//The ASPSP might make the usage of this access method unnecessary, since the related authorisation +//resource will be automatically created by the ASPSP after the submission of the consent data with the +//first POST consents call. The start authorisation process is a process which is needed for creating +//a new authorisation or cancellation sub-resource. +// +//This applies in the following scenarios: * The ASPSP has indicated with an 'startAuthorisation' hyperlink +//in the preceding Payment Initiation Response that an explicit start of the authorisation process is needed by the TPP. +//The 'startAuthorisation' hyperlink can transport more information about data which needs to be uploaded by using +//the extended forms. +//* 'startAuthorisationWithPsuIdentfication', +//* 'startAuthorisationWithPsuAuthentication' +//* 'startAuthorisationWithEncryptedPsuAuthentication' +//* 'startAuthorisationWithAuthentciationMethodSelection' +//* The related payment initiation cannot yet be executed since a multilevel SCA is mandated. +//* The ASPSP has indicated with an 'startAuthorisation' hyperlink in the preceding Payment Cancellation +//Response that an explicit start of the authorisation process is needed by the TPP. +// +//The 'startAuthorisation' hyperlink can transport more information about data which needs to be uploaded by +//using the extended forms as indicated above. +//* The related payment cancellation request cannot be applied yet since a multilevel SCA is mandate for executing the cancellation. +//* The signing basket needs to be authorised yet. +// +//""" +// +// resourceDocs += ResourceDoc( +// startConsentAuthorisationTransactionAuthorisation, +// apiVersion, +// nameOf(startConsentAuthorisationTransactionAuthorisation), +// "POST", +// "/consents/CONSENTID/authorisations", +// "Start the authorisation process for a consent(transactionAuthorisation)", +// generalStartConsentAuthorisationSummary(false), +// json.parse("""{"scaAuthenticationData":""}"""), +// json.parse("""{ +// "scaStatus": "received", +// "psuMessage": "Please use your BankApp for transaction Authorisation.", +// "authorisationId": "123auth456.", +// "_links": +// { +// "scaStatus": {"href":"/v1.3/consents/qwer3456tzui7890/authorisations/123auth456"} +// } +// }"""), +// List(AuthenticatedUserIsRequired, UnknownError), +// ApiTag("Account Information Service (AIS)") :: apiTagBerlinGroupM :: Nil +// ) +// +// lazy val startConsentAuthorisationTransactionAuthorisation : OBPEndpoint = { +// case "consents" :: consentId :: "authorisations" :: Nil JsonPost json -> _ if checkTransactionAuthorisation(json)=> { +// cc => +// for { +// (Full(u), callContext) <- authenticatedAccess(cc) +// _ <- passesPsd2Aisp(callContext) +// consent <- Future(Consents.consentProvider.vend.getConsentByConsentId(consentId)) map { +// unboxFullOrFail(_, callContext, ConsentNotFound, 403) +// } +// (challenges, callContext) <- NewStyle.function.createChallengesC2( +// List(u.userId), +// ChallengeType.BERLIN_GROUP_CONSENT_CHALLENGE, +// None, +// getSuggestedDefaultScaMethod(), +// Some(StrongCustomerAuthenticationStatus.received), +// Some(consentId), +// None, +// callContext +// ) +// //NOTE: in OBP it support multiple challenges, but in Berlin Group it has only one challenge. The following guard is to make sure it return the 1st challenge properly. +// challenge <- NewStyle.function.tryons(InvalidConnectorResponseForCreateChallenge,400, callContext) { +// challenges.head +// } +// } yield { +// (createStartConsentAuthorisationJson(consent, challenge), HttpCode.`201`(callContext)) +// } +// } +// } +// +// resourceDocs += ResourceDoc( +// startConsentAuthorisationUpdatePsuAuthentication, +// apiVersion, +// nameOf(startConsentAuthorisationUpdatePsuAuthentication), +// "POST", +// "/consents/CONSENTID/authorisations", +// "Start the authorisation process for a consent(updatePsuAuthentication)", +// generalStartConsentAuthorisationSummary(true), +// json.parse("""{ +// "psuData": { +// "password": "start12" +// } +// }"""), +// json.parse("""{ +// "scaStatus": "received", +// "psuMessage": "Please use your BankApp for transaction Authorisation.", +// "authorisationId": "123auth456.", +// "_links": +// { +// "scaStatus": {"href":"/v1.3/consents/qwer3456tzui7890/authorisations/123auth456"} +// } +// }"""), +// List(AuthenticatedUserIsRequired, UnknownError), +// ApiTag("Account Information Service (AIS)") :: apiTagBerlinGroupM :: Nil +// ) +// +// lazy val startConsentAuthorisationUpdatePsuAuthentication : OBPEndpoint = { +// case "consents" :: consentId :: "authorisations" :: Nil JsonPost json -> _ if checkUpdatePsuAuthentication(json)=> { +// cc => +// for { +// (Full(u), callContext) <- authenticatedAccess(cc) +// } yield { +// (liftweb.json.parse( +// """{ +// "scaStatus": "received", +// "psuMessage": "Please use your BankApp for transaction Authorisation.", +// "authorisationId": "123auth456.", +// "_links": +// { +// "scaStatus": {"href":"/v1.3/consents/qwer3456tzui7890/authorisations/123auth456"} +// } +// }"""),HttpCode.`201`(callContext)) +// } +// } +// } +// +// resourceDocs += ResourceDoc( +// startConsentAuthorisationSelectPsuAuthenticationMethod, +// apiVersion, +// nameOf(startConsentAuthorisationSelectPsuAuthenticationMethod), +// "POST", +// "/consents/CONSENTID/authorisations", +// "Start the authorisation process for a consent(selectPsuAuthenticationMethod)", +// generalStartConsentAuthorisationSummary(true), +// json.parse("""{"authenticationMethodId":""}"""), +// json.parse("""{ +// "scaStatus": "received", +// "psuMessage": "Please use your BankApp for transaction Authorisation.", +// "authorisationId": "123auth456.", +// "_links": +// { +// "scaStatus": {"href":"/v1.3/consents/qwer3456tzui7890/authorisations/123auth456"} +// } +// }"""), +// List(AuthenticatedUserIsRequired, UnknownError), +// ApiTag("Account Information Service (AIS)") :: apiTagBerlinGroupM :: Nil +// ) +// +// lazy val startConsentAuthorisationSelectPsuAuthenticationMethod : OBPEndpoint = { +// case "consents" :: consentId :: "authorisations" :: Nil JsonPost json -> _ if checkSelectPsuAuthenticationMethod(json)=> { +// cc => +// for { +// (Full(u), callContext) <- authenticatedAccess(cc) +// } yield { +// (liftweb.json.parse( +// """{ +// "scaStatus": "received", +// "psuMessage": "Please use your BankApp for transaction Authorisation.", +// "authorisationId": "123auth456.", +// "_links": +// { +// "scaStatus": {"href":"/v1.3/consents/qwer3456tzui7890/authorisations/123auth456"} +// } +// }"""),HttpCode.`201`(callContext)) +// } +// } +// } +// +// def generalUpdateConsentsPsuDataSummary(isMockedData: Boolean) = +// s"""${mockedDataText(false)} +//This method update PSU data on the consents resource if needed. It may authorise a consent within the Embedded +//SCA Approach where needed. Independently from the SCA Approach it supports +//e.g. the selection of the authentication method and a non-SCA PSU authentication. +//This methods updates PSU data on the cancellation authorisation resource if needed. +//There are several possible Update PSU Data requests in the context of a consent request if needed, +//which depends on the SCA approach: * Redirect SCA Approach: A specific Update PSU Data Request is applicable +//for +//* the selection of authentication methods, before choosing the actual SCA approach. +//* Decoupled SCA Approach: A specific Update PSU Data Request is only applicable for +//* adding the PSU Identification, if not provided yet in the Payment Initiation Request or the Account Information Consent Request, +//or if no OAuth2 access token is used, or +//* the selection of authentication methods. +//* Embedded SCA Approach: The Update PSU Data Request might be used +//* to add credentials as a first factor authentication data of the PSU and +//* to select the authentication method and +//* transaction authorisation. +//The SCA Approach might depend on the chosen SCA method. For that reason, +//the following possible Update PSU Data request can apply to all SCA approaches: +//* Select an SCA method in case of several SCA methods are available for the customer. There are the following request types on this access path: +//* Update PSU Identification * Update PSU Authentication +//* Select PSU Autorization Method WARNING: This method need a reduced header, therefore many optional elements are not present. +//Maybe in a later version the access path will change. +//* Transaction Authorisation WARNING: This method need a reduced header, therefore many optional elements are not present. +//Maybe in a later version the access path will change. +// +// """ +// +// resourceDocs += ResourceDoc( +// updateConsentsPsuDataTransactionAuthorisation, +// apiVersion, +// nameOf(updateConsentsPsuDataTransactionAuthorisation), +// "PUT", +// "/consents/CONSENTID/authorisations/AUTHORISATIONID", +// "Update PSU Data for consents (transactionAuthorisation)", +// generalUpdateConsentsPsuDataSummary(false), +// json.parse("""{"scaAuthenticationData":"123"}"""), +// ScaStatusResponse( +// scaStatus = "received", +// _links = Some(LinksAll(scaStatus = Some(HrefType(Some(s"/v1.3/consents/1234-wertiq-983/authorisations"))))) +// ), +// List(AuthenticatedUserIsRequired, UnknownError), +// ApiTag("Account Information Service (AIS)") :: apiTagBerlinGroupM :: Nil +// ) +// +// lazy val updateConsentsPsuDataTransactionAuthorisation : OBPEndpoint = { +// case "consents" :: consentId :: "authorisations" :: authorisationId :: Nil JsonPut jsonPut -> _ if checkTransactionAuthorisation(jsonPut) => { +// cc => +// for { +// (Full(u), callContext) <- authenticatedAccess(cc) +// _ <- passesPsd2Aisp(callContext) +// _ <- Future(Consents.consentProvider.vend.getConsentByConsentId(consentId)) map { +// unboxFullOrFail(_, callContext, ConsentNotFound, 403) +// } +// failMsg = s"$InvalidJsonFormat The Json body should be the $TransactionAuthorisation " +// updateJson <- NewStyle.function.tryons(failMsg, 400, callContext) { +// jsonPut.extract[TransactionAuthorisation] +// } +// (_, callContext) <- NewStyle.function.getChallenge(authorisationId, callContext) +// (challenge, callContext) <- NewStyle.function.validateChallengeAnswerC4( +// ChallengeType.BERLIN_GROUP_CONSENT_CHALLENGE, +// None, +// Some(consentId), +// authorisationId, +// updateJson.scaAuthenticationData, +// SuppliedAnswerType.PLAIN_TEXT_VALUE, +// callContext +// ) +// consent <- challenge.scaStatus match { +// case Some(status) if status == StrongCustomerAuthenticationStatus.finalised => // finalised +// Future(Consents.consentProvider.vend.updateConsentStatus(consentId, ConsentStatus.valid)) +// case Some(status) if status == StrongCustomerAuthenticationStatus.failed => // failed +// Future(Consents.consentProvider.vend.updateConsentStatus(consentId, ConsentStatus.rejected)) +// case _ => // all other cases +// Future(Consents.consentProvider.vend.getConsentByConsentId(consentId)) +// } +// _ <- NewStyle.function.tryons(ConsentUpdateStatusError, 400, callContext) { +// consent.toList.size == 1 +// } +// _ <- Future { +// val authContexts = UserAuthContextProvider.userAuthContextProvider.vend.getUserAuthContextsBox(u.userId) +// .map(_.map(i => BasicUserAuthContext(i.key, i.value))) +// ConsentAuthContextProvider.consentAuthContextProvider.vend.createOrUpdateConsentAuthContexts(consentId, authContexts.getOrElse(Nil)) +// } map { +// unboxFullOrFail(_, callContext, ConsentUserAuthContextCannotBeAdded) +// } +// _ <- Future(Consents.consentProvider.vend.updateConsentUser(consentId, u)) map { +// unboxFullOrFail(_, callContext, ConsentUserCannotBeAdded) +// } +// } yield { +// (createPutConsentResponseJson(consent.toList.head), HttpCode.`200`(callContext)) +// } +// } +// } +// +// resourceDocs += ResourceDoc( +// updateConsentsPsuDataUpdatePsuAuthentication, +// apiVersion, +// nameOf(updateConsentsPsuDataUpdatePsuAuthentication), +// "PUT", +// "/consents/CONSENTID/authorisations/AUTHORISATIONID", +// "Update PSU Data for consents (updatePsuAuthentication)", +// generalUpdateConsentsPsuDataSummary(true), +// json.parse("""{"psuData": {"password": "start12"}}""".stripMargin), +// json.parse("""{ +// | "scaStatus": "psuAuthenticated", +// | "_links": { +// | "authoriseTransaction": {"href": "/psd2/v1/payments/1234-wertiq-983/authorisations/123auth456"} +// | } +// | }""".stripMargin), +// List(AuthenticatedUserIsRequired, UnknownError), +// ApiTag("Account Information Service (AIS)") :: apiTagBerlinGroupM :: Nil +// ) +// +// lazy val updateConsentsPsuDataUpdatePsuAuthentication : OBPEndpoint = { +// case "consents" :: consentId :: "authorisations" :: authorisationId :: Nil JsonPut jsonPut -> _ if checkUpdatePsuAuthentication(jsonPut) => { +// cc => +// for { +// (Full(u), callContext) <- authenticatedAccess(cc) +// +// } yield { +// (liftweb.json.parse( +// """{ +// | "scaStatus": "psuAuthenticated", +// | "_links": { +// | "authoriseTransaction": {"href": "/psd2/v1/payments/1234-wertiq-983/authorisations/123auth456"} +// | } +// |}""".stripMargin), HttpCode.`200`(callContext)) +// } +// } +// } +// +// resourceDocs += ResourceDoc( +// updateConsentsPsuDataUpdateSelectPsuAuthenticationMethod, +// apiVersion, +// nameOf(updateConsentsPsuDataUpdateSelectPsuAuthenticationMethod), +// "PUT", +// "/consents/CONSENTID/authorisations/AUTHORISATIONID", +// "Update PSU Data for consents (selectPsuAuthenticationMethod)", +// generalUpdateConsentsPsuDataSummary(true), +// json.parse("""{ +// | "authenticationMethodId": "myAuthenticationID" +// |}""".stripMargin), +// json.parse("""{ +// | "scaStatus": "scaMethodSelected", +// | "chosenScaMethod": { +// | "authenticationType": "SMS_OTP", +// | "authenticationMethodId": "myAuthenticationID"}, +// | "challengeData": { +// | "otpMaxLength": 6, +// | "otpFormat": "integer"}, +// | "_links": { +// | "authoriseTransaction": {"href": "/psd2/v1/payments/1234-wertiq-983/authorisations/123auth456"} +// | } +// | }""".stripMargin), +// List(AuthenticatedUserIsRequired, UnknownError), +// ApiTag("Account Information Service (AIS)") :: apiTagBerlinGroupM :: Nil +// ) +// +// lazy val updateConsentsPsuDataUpdateSelectPsuAuthenticationMethod : OBPEndpoint = { +// case "consents" :: consentId :: "authorisations" :: authorisationId :: Nil JsonPut jsonPut -> _ if checkSelectPsuAuthenticationMethod(jsonPut) => { +// cc => +// for { +// (Full(u), callContext) <- authenticatedAccess(cc) +// +// } yield { +// (liftweb.json.parse( +// """{ +// | "scaStatus": "scaMethodSelected", +// | "chosenScaMethod": { +// | "authenticationType": "SMS_OTP", +// | "authenticationMethodId": "myAuthenticationID"}, +// | "challengeData": { +// | "otpMaxLength": 6, +// | "otpFormat": "integer"}, +// | "_links": { +// | "authoriseTransaction": {"href": "/psd2/v1/payments/1234-wertiq-983/authorisations/123auth456"} +// | } +// |}""".stripMargin), HttpCode.`200`(callContext)) +// } +// } +// } +// +// resourceDocs += ResourceDoc( +// updateConsentsPsuDataUpdateAuthorisationConfirmation, +// apiVersion, +// nameOf(updateConsentsPsuDataUpdateAuthorisationConfirmation), +// "PUT", +// "/consents/CONSENTID/authorisations/AUTHORISATIONID", +// "Update PSU Data for consents (authorisationConfirmation)", +// generalUpdateConsentsPsuDataSummary(true), +// json.parse("""{"confirmationCode":"confirmationCode"}"""), +// json.parse("""{ +// | "scaStatus": "finalised", +// | "_links":{ +// | "status": {"href":"/v1/payments/sepa-credit-transfers/qwer3456tzui7890/status"} +// | } +// | }""".stripMargin), +// List(AuthenticatedUserIsRequired, UnknownError), +// ApiTag("Account Information Service (AIS)") :: apiTagBerlinGroupM :: Nil +// ) +// +// lazy val updateConsentsPsuDataUpdateAuthorisationConfirmation : OBPEndpoint = { +// case "consents" :: consentId :: "authorisations" :: authorisationId :: Nil JsonPut jsonPut -> _ if checkAuthorisationConfirmation(jsonPut) => { +// cc => +// for { +// (Full(u), callContext) <- authenticatedAccess(cc) +// } yield { +// (json.parse( +// """{ +// | "scaStatus": "finalised", +// | "_links":{ +// | "status": {"href":"/v1/payments/sepa-credit-transfers/qwer3456tzui7890/status"} +// | } +// |}""".stripMargin), +// HttpCode.`200`(callContext)) +// } +// } +// } +// +//} +// +// +// diff --git a/obp-api/src/main/scala/code/api/berlin/group/v1_3/CommonServicesApi.scala b/obp-api/src/main/scala/code/api/berlin/group/v1_3/CommonServicesApi.scala index 6a40112e93..cb483306c4 100644 --- a/obp-api/src/main/scala/code/api/berlin/group/v1_3/CommonServicesApi.scala +++ b/obp-api/src/main/scala/code/api/berlin/group/v1_3/CommonServicesApi.scala @@ -1,96 +1,96 @@ -package code.api.builder.CommonServicesApi - -import scala.language.implicitConversions -import code.api.berlin.group.ConstantsBG -import code.api.berlin.group.v1_3.{JvalueCaseClass, OBP_BERLIN_GROUP_1_3} -import code.api.builder.AccountInformationServiceAISApi.APIMethods_AccountInformationServiceAISApi -import code.api.builder.PaymentInitiationServicePISApi.APIMethods_PaymentInitiationServicePISApi -import code.api.builder.SigningBasketsApi.APIMethods_SigningBasketsApi -import code.api.util.APIUtil._ -import com.openbankproject.commons.util.ApiVersion -import net.liftweb.http.rest.RestHelper -import net.liftweb.json._ - -import scala.collection.immutable.Nil -import scala.collection.mutable.ArrayBuffer - -//TODO maybe we can remove this common services, it just show other apis in this tag. no new ones. -object APIMethods_CommonServicesApi extends RestHelper { - val apiVersion = ConstantsBG.berlinGroupVersion1 - val resourceDocs = ArrayBuffer[ResourceDoc]() - val apiRelations = ArrayBuffer[ApiRelation]() - val codeContext = CodeContext(resourceDocs, apiRelations) - protected implicit def JvalueToSuper(what: JValue): JvalueCaseClass = JvalueCaseClass(what) - - - val endpoints = APIMethods_SigningBasketsApi.deleteSigningBasket :: - APIMethods_SigningBasketsApi.getSigningBasketAuthorisation :: - APIMethods_SigningBasketsApi.getSigningBasketScaStatus :: - APIMethods_SigningBasketsApi.startSigningBasketAuthorisation :: - APIMethods_SigningBasketsApi.getSigningBasketStatus :: - APIMethods_SigningBasketsApi.updateSigningBasketPsuData :: - APIMethods_PaymentInitiationServicePISApi.getPaymentCancellationScaStatus :: - APIMethods_PaymentInitiationServicePISApi.getPaymentInitiationAuthorisation :: - APIMethods_PaymentInitiationServicePISApi.getPaymentInitiationScaStatus :: - APIMethods_PaymentInitiationServicePISApi.startPaymentAuthorisationUpdatePsuAuthentication :: - APIMethods_PaymentInitiationServicePISApi.startPaymentAuthorisationTransactionAuthorisation :: - APIMethods_PaymentInitiationServicePISApi.startPaymentAuthorisationSelectPsuAuthenticationMethod :: - APIMethods_PaymentInitiationServicePISApi.startPaymentInitiationCancellationAuthorisationTransactionAuthorisation :: - APIMethods_PaymentInitiationServicePISApi.startPaymentInitiationCancellationAuthorisationUpdatePsuAuthentication :: - APIMethods_PaymentInitiationServicePISApi.startPaymentInitiationCancellationAuthorisationSelectPsuAuthenticationMethod :: - APIMethods_PaymentInitiationServicePISApi.updatePaymentCancellationPsuDataUpdatePsuAuthentication :: - APIMethods_PaymentInitiationServicePISApi.updatePaymentCancellationPsuDataTransactionAuthorisation :: - APIMethods_PaymentInitiationServicePISApi.updatePaymentCancellationPsuDataSelectPsuAuthenticationMethod :: - APIMethods_PaymentInitiationServicePISApi.updatePaymentCancellationPsuDataAuthorisationConfirmation :: - APIMethods_PaymentInitiationServicePISApi.updatePaymentPsuDataTransactionAuthorisation :: - APIMethods_PaymentInitiationServicePISApi.updatePaymentPsuDataUpdatePsuAuthentication :: - APIMethods_PaymentInitiationServicePISApi.updatePaymentPsuDataSelectPsuAuthenticationMethod :: - APIMethods_PaymentInitiationServicePISApi.updatePaymentPsuDataAuthorisationConfirmation :: - APIMethods_AccountInformationServiceAISApi.startConsentAuthorisationTransactionAuthorisation :: - APIMethods_AccountInformationServiceAISApi.startConsentAuthorisationUpdatePsuAuthentication :: - APIMethods_AccountInformationServiceAISApi.startConsentAuthorisationSelectPsuAuthenticationMethod :: - APIMethods_AccountInformationServiceAISApi.updateConsentsPsuDataTransactionAuthorisation :: - APIMethods_AccountInformationServiceAISApi.updateConsentsPsuDataUpdatePsuAuthentication :: - APIMethods_AccountInformationServiceAISApi.updateConsentsPsuDataUpdateAuthorisationConfirmation :: - APIMethods_AccountInformationServiceAISApi.updateConsentsPsuDataUpdateSelectPsuAuthenticationMethod :: - APIMethods_AccountInformationServiceAISApi.getConsentScaStatus :: Nil - - - resourceDocs += APIMethods_SigningBasketsApi.resourceDocs.filter(_.partialFunction == APIMethods_SigningBasketsApi.deleteSigningBasket).head - resourceDocs += APIMethods_SigningBasketsApi.resourceDocs.filter(_.partialFunction == APIMethods_SigningBasketsApi.getSigningBasketAuthorisation).head - resourceDocs += APIMethods_SigningBasketsApi.resourceDocs.filter(_.partialFunction == APIMethods_SigningBasketsApi.getSigningBasketScaStatus).head - resourceDocs += APIMethods_SigningBasketsApi.resourceDocs.filter(_.partialFunction == APIMethods_SigningBasketsApi.startSigningBasketAuthorisation).head - resourceDocs += APIMethods_SigningBasketsApi.resourceDocs.filter(_.partialFunction == APIMethods_SigningBasketsApi.getSigningBasketStatus).head - resourceDocs += APIMethods_SigningBasketsApi.resourceDocs.filter(_.partialFunction == APIMethods_SigningBasketsApi.updateSigningBasketPsuData).head - - resourceDocs += APIMethods_PaymentInitiationServicePISApi.resourceDocs.filter(_.partialFunction == APIMethods_PaymentInitiationServicePISApi.getPaymentCancellationScaStatus).head - resourceDocs += APIMethods_PaymentInitiationServicePISApi.resourceDocs.filter(_.partialFunction == APIMethods_PaymentInitiationServicePISApi.getPaymentInitiationAuthorisation).head - resourceDocs += APIMethods_PaymentInitiationServicePISApi.resourceDocs.filter(_.partialFunction == APIMethods_PaymentInitiationServicePISApi.getPaymentInitiationScaStatus).head - resourceDocs += APIMethods_PaymentInitiationServicePISApi.resourceDocs.filter(_.partialFunction == APIMethods_PaymentInitiationServicePISApi.startPaymentAuthorisationUpdatePsuAuthentication).head - resourceDocs += APIMethods_PaymentInitiationServicePISApi.resourceDocs.filter(_.partialFunction == APIMethods_PaymentInitiationServicePISApi.startPaymentAuthorisationSelectPsuAuthenticationMethod).head - resourceDocs += APIMethods_PaymentInitiationServicePISApi.resourceDocs.filter(_.partialFunction == APIMethods_PaymentInitiationServicePISApi.startPaymentAuthorisationTransactionAuthorisation).head - resourceDocs += APIMethods_PaymentInitiationServicePISApi.resourceDocs.filter(_.partialFunction == APIMethods_PaymentInitiationServicePISApi.startPaymentInitiationCancellationAuthorisationTransactionAuthorisation).head - resourceDocs += APIMethods_PaymentInitiationServicePISApi.resourceDocs.filter(_.partialFunction == APIMethods_PaymentInitiationServicePISApi.startPaymentInitiationCancellationAuthorisationUpdatePsuAuthentication).head - resourceDocs += APIMethods_PaymentInitiationServicePISApi.resourceDocs.filter(_.partialFunction == APIMethods_PaymentInitiationServicePISApi.startPaymentInitiationCancellationAuthorisationSelectPsuAuthenticationMethod).head - resourceDocs += APIMethods_PaymentInitiationServicePISApi.resourceDocs.filter(_.partialFunction == APIMethods_PaymentInitiationServicePISApi.updatePaymentCancellationPsuDataUpdatePsuAuthentication).head - resourceDocs += APIMethods_PaymentInitiationServicePISApi.resourceDocs.filter(_.partialFunction == APIMethods_PaymentInitiationServicePISApi.updatePaymentCancellationPsuDataTransactionAuthorisation).head - resourceDocs += APIMethods_PaymentInitiationServicePISApi.resourceDocs.filter(_.partialFunction == APIMethods_PaymentInitiationServicePISApi.updatePaymentCancellationPsuDataAuthorisationConfirmation).head - resourceDocs += APIMethods_PaymentInitiationServicePISApi.resourceDocs.filter(_.partialFunction == APIMethods_PaymentInitiationServicePISApi.updatePaymentCancellationPsuDataSelectPsuAuthenticationMethod).head - resourceDocs += APIMethods_PaymentInitiationServicePISApi.resourceDocs.filter(_.partialFunction == APIMethods_PaymentInitiationServicePISApi.updatePaymentPsuDataTransactionAuthorisation).head - resourceDocs += APIMethods_PaymentInitiationServicePISApi.resourceDocs.filter(_.partialFunction == APIMethods_PaymentInitiationServicePISApi.updatePaymentPsuDataAuthorisationConfirmation).head - resourceDocs += APIMethods_PaymentInitiationServicePISApi.resourceDocs.filter(_.partialFunction == APIMethods_PaymentInitiationServicePISApi.updatePaymentPsuDataSelectPsuAuthenticationMethod).head - resourceDocs += APIMethods_PaymentInitiationServicePISApi.resourceDocs.filter(_.partialFunction == APIMethods_PaymentInitiationServicePISApi.updatePaymentPsuDataAuthorisationConfirmation).head - - resourceDocs += APIMethods_AccountInformationServiceAISApi.resourceDocs.filter(_.partialFunction == APIMethods_AccountInformationServiceAISApi.startConsentAuthorisationTransactionAuthorisation).head - resourceDocs += APIMethods_AccountInformationServiceAISApi.resourceDocs.filter(_.partialFunction == APIMethods_AccountInformationServiceAISApi.startConsentAuthorisationUpdatePsuAuthentication).head - resourceDocs += APIMethods_AccountInformationServiceAISApi.resourceDocs.filter(_.partialFunction == APIMethods_AccountInformationServiceAISApi.startConsentAuthorisationSelectPsuAuthenticationMethod).head - resourceDocs += APIMethods_AccountInformationServiceAISApi.resourceDocs.filter(_.partialFunction == APIMethods_AccountInformationServiceAISApi.updateConsentsPsuDataTransactionAuthorisation).head - resourceDocs += APIMethods_AccountInformationServiceAISApi.resourceDocs.filter(_.partialFunction == APIMethods_AccountInformationServiceAISApi.updateConsentsPsuDataUpdatePsuAuthentication).head - resourceDocs += APIMethods_AccountInformationServiceAISApi.resourceDocs.filter(_.partialFunction == APIMethods_AccountInformationServiceAISApi.updateConsentsPsuDataUpdateAuthorisationConfirmation).head - resourceDocs += APIMethods_AccountInformationServiceAISApi.resourceDocs.filter(_.partialFunction == APIMethods_AccountInformationServiceAISApi.updateConsentsPsuDataUpdateSelectPsuAuthenticationMethod).head - resourceDocs += APIMethods_AccountInformationServiceAISApi.resourceDocs.filter(_.partialFunction == APIMethods_AccountInformationServiceAISApi.getConsentScaStatus).head - -} - - - +//package code.api.builder.CommonServicesApi +// +//import scala.language.implicitConversions +//import code.api.berlin.group.ConstantsBG +//import code.api.berlin.group.v1_3.{JvalueCaseClass, OBP_BERLIN_GROUP_1_3} +//import code.api.builder.AccountInformationServiceAISApi.APIMethods_AccountInformationServiceAISApi +//import code.api.builder.PaymentInitiationServicePISApi.APIMethods_PaymentInitiationServicePISApi +//import code.api.builder.SigningBasketsApi.APIMethods_SigningBasketsApi +//import code.api.util.APIUtil._ +//import com.openbankproject.commons.util.ApiVersion +//import net.liftweb.http.rest.RestHelper +//import net.liftweb.json._ +// +//import scala.collection.immutable.Nil +//import scala.collection.mutable.ArrayBuffer +// +////TODO maybe we can remove this common services, it just show other apis in this tag. no new ones. +//object APIMethods_CommonServicesApi extends RestHelper { +// val apiVersion = ConstantsBG.berlinGroupVersion1 +// val resourceDocs = ArrayBuffer[ResourceDoc]() +// val apiRelations = ArrayBuffer[ApiRelation]() +// val codeContext = CodeContext(resourceDocs, apiRelations) +// protected implicit def JvalueToSuper(what: JValue): JvalueCaseClass = JvalueCaseClass(what) +// +// +// val endpoints = APIMethods_SigningBasketsApi.deleteSigningBasket :: +// APIMethods_SigningBasketsApi.getSigningBasketAuthorisation :: +// APIMethods_SigningBasketsApi.getSigningBasketScaStatus :: +// APIMethods_SigningBasketsApi.startSigningBasketAuthorisation :: +// APIMethods_SigningBasketsApi.getSigningBasketStatus :: +// APIMethods_SigningBasketsApi.updateSigningBasketPsuData :: +// APIMethods_PaymentInitiationServicePISApi.getPaymentCancellationScaStatus :: +// APIMethods_PaymentInitiationServicePISApi.getPaymentInitiationAuthorisation :: +// APIMethods_PaymentInitiationServicePISApi.getPaymentInitiationScaStatus :: +// APIMethods_PaymentInitiationServicePISApi.startPaymentAuthorisationUpdatePsuAuthentication :: +// APIMethods_PaymentInitiationServicePISApi.startPaymentAuthorisationTransactionAuthorisation :: +// APIMethods_PaymentInitiationServicePISApi.startPaymentAuthorisationSelectPsuAuthenticationMethod :: +// APIMethods_PaymentInitiationServicePISApi.startPaymentInitiationCancellationAuthorisationTransactionAuthorisation :: +// APIMethods_PaymentInitiationServicePISApi.startPaymentInitiationCancellationAuthorisationUpdatePsuAuthentication :: +// APIMethods_PaymentInitiationServicePISApi.startPaymentInitiationCancellationAuthorisationSelectPsuAuthenticationMethod :: +// APIMethods_PaymentInitiationServicePISApi.updatePaymentCancellationPsuDataUpdatePsuAuthentication :: +// APIMethods_PaymentInitiationServicePISApi.updatePaymentCancellationPsuDataTransactionAuthorisation :: +// APIMethods_PaymentInitiationServicePISApi.updatePaymentCancellationPsuDataSelectPsuAuthenticationMethod :: +// APIMethods_PaymentInitiationServicePISApi.updatePaymentCancellationPsuDataAuthorisationConfirmation :: +// APIMethods_PaymentInitiationServicePISApi.updatePaymentPsuDataTransactionAuthorisation :: +// APIMethods_PaymentInitiationServicePISApi.updatePaymentPsuDataUpdatePsuAuthentication :: +// APIMethods_PaymentInitiationServicePISApi.updatePaymentPsuDataSelectPsuAuthenticationMethod :: +// APIMethods_PaymentInitiationServicePISApi.updatePaymentPsuDataAuthorisationConfirmation :: +// APIMethods_AccountInformationServiceAISApi.startConsentAuthorisationTransactionAuthorisation :: +// APIMethods_AccountInformationServiceAISApi.startConsentAuthorisationUpdatePsuAuthentication :: +// APIMethods_AccountInformationServiceAISApi.startConsentAuthorisationSelectPsuAuthenticationMethod :: +// APIMethods_AccountInformationServiceAISApi.updateConsentsPsuDataTransactionAuthorisation :: +// APIMethods_AccountInformationServiceAISApi.updateConsentsPsuDataUpdatePsuAuthentication :: +// APIMethods_AccountInformationServiceAISApi.updateConsentsPsuDataUpdateAuthorisationConfirmation :: +// APIMethods_AccountInformationServiceAISApi.updateConsentsPsuDataUpdateSelectPsuAuthenticationMethod :: +// APIMethods_AccountInformationServiceAISApi.getConsentScaStatus :: Nil +// +// +// resourceDocs += APIMethods_SigningBasketsApi.resourceDocs.filter(_.partialFunction == APIMethods_SigningBasketsApi.deleteSigningBasket).head +// resourceDocs += APIMethods_SigningBasketsApi.resourceDocs.filter(_.partialFunction == APIMethods_SigningBasketsApi.getSigningBasketAuthorisation).head +// resourceDocs += APIMethods_SigningBasketsApi.resourceDocs.filter(_.partialFunction == APIMethods_SigningBasketsApi.getSigningBasketScaStatus).head +// resourceDocs += APIMethods_SigningBasketsApi.resourceDocs.filter(_.partialFunction == APIMethods_SigningBasketsApi.startSigningBasketAuthorisation).head +// resourceDocs += APIMethods_SigningBasketsApi.resourceDocs.filter(_.partialFunction == APIMethods_SigningBasketsApi.getSigningBasketStatus).head +// resourceDocs += APIMethods_SigningBasketsApi.resourceDocs.filter(_.partialFunction == APIMethods_SigningBasketsApi.updateSigningBasketPsuData).head +// +// resourceDocs += APIMethods_PaymentInitiationServicePISApi.resourceDocs.filter(_.partialFunction == APIMethods_PaymentInitiationServicePISApi.getPaymentCancellationScaStatus).head +// resourceDocs += APIMethods_PaymentInitiationServicePISApi.resourceDocs.filter(_.partialFunction == APIMethods_PaymentInitiationServicePISApi.getPaymentInitiationAuthorisation).head +// resourceDocs += APIMethods_PaymentInitiationServicePISApi.resourceDocs.filter(_.partialFunction == APIMethods_PaymentInitiationServicePISApi.getPaymentInitiationScaStatus).head +// resourceDocs += APIMethods_PaymentInitiationServicePISApi.resourceDocs.filter(_.partialFunction == APIMethods_PaymentInitiationServicePISApi.startPaymentAuthorisationUpdatePsuAuthentication).head +// resourceDocs += APIMethods_PaymentInitiationServicePISApi.resourceDocs.filter(_.partialFunction == APIMethods_PaymentInitiationServicePISApi.startPaymentAuthorisationSelectPsuAuthenticationMethod).head +// resourceDocs += APIMethods_PaymentInitiationServicePISApi.resourceDocs.filter(_.partialFunction == APIMethods_PaymentInitiationServicePISApi.startPaymentAuthorisationTransactionAuthorisation).head +// resourceDocs += APIMethods_PaymentInitiationServicePISApi.resourceDocs.filter(_.partialFunction == APIMethods_PaymentInitiationServicePISApi.startPaymentInitiationCancellationAuthorisationTransactionAuthorisation).head +// resourceDocs += APIMethods_PaymentInitiationServicePISApi.resourceDocs.filter(_.partialFunction == APIMethods_PaymentInitiationServicePISApi.startPaymentInitiationCancellationAuthorisationUpdatePsuAuthentication).head +// resourceDocs += APIMethods_PaymentInitiationServicePISApi.resourceDocs.filter(_.partialFunction == APIMethods_PaymentInitiationServicePISApi.startPaymentInitiationCancellationAuthorisationSelectPsuAuthenticationMethod).head +// resourceDocs += APIMethods_PaymentInitiationServicePISApi.resourceDocs.filter(_.partialFunction == APIMethods_PaymentInitiationServicePISApi.updatePaymentCancellationPsuDataUpdatePsuAuthentication).head +// resourceDocs += APIMethods_PaymentInitiationServicePISApi.resourceDocs.filter(_.partialFunction == APIMethods_PaymentInitiationServicePISApi.updatePaymentCancellationPsuDataTransactionAuthorisation).head +// resourceDocs += APIMethods_PaymentInitiationServicePISApi.resourceDocs.filter(_.partialFunction == APIMethods_PaymentInitiationServicePISApi.updatePaymentCancellationPsuDataAuthorisationConfirmation).head +// resourceDocs += APIMethods_PaymentInitiationServicePISApi.resourceDocs.filter(_.partialFunction == APIMethods_PaymentInitiationServicePISApi.updatePaymentCancellationPsuDataSelectPsuAuthenticationMethod).head +// resourceDocs += APIMethods_PaymentInitiationServicePISApi.resourceDocs.filter(_.partialFunction == APIMethods_PaymentInitiationServicePISApi.updatePaymentPsuDataTransactionAuthorisation).head +// resourceDocs += APIMethods_PaymentInitiationServicePISApi.resourceDocs.filter(_.partialFunction == APIMethods_PaymentInitiationServicePISApi.updatePaymentPsuDataAuthorisationConfirmation).head +// resourceDocs += APIMethods_PaymentInitiationServicePISApi.resourceDocs.filter(_.partialFunction == APIMethods_PaymentInitiationServicePISApi.updatePaymentPsuDataSelectPsuAuthenticationMethod).head +// resourceDocs += APIMethods_PaymentInitiationServicePISApi.resourceDocs.filter(_.partialFunction == APIMethods_PaymentInitiationServicePISApi.updatePaymentPsuDataAuthorisationConfirmation).head +// +// resourceDocs += APIMethods_AccountInformationServiceAISApi.resourceDocs.filter(_.partialFunction == APIMethods_AccountInformationServiceAISApi.startConsentAuthorisationTransactionAuthorisation).head +// resourceDocs += APIMethods_AccountInformationServiceAISApi.resourceDocs.filter(_.partialFunction == APIMethods_AccountInformationServiceAISApi.startConsentAuthorisationUpdatePsuAuthentication).head +// resourceDocs += APIMethods_AccountInformationServiceAISApi.resourceDocs.filter(_.partialFunction == APIMethods_AccountInformationServiceAISApi.startConsentAuthorisationSelectPsuAuthenticationMethod).head +// resourceDocs += APIMethods_AccountInformationServiceAISApi.resourceDocs.filter(_.partialFunction == APIMethods_AccountInformationServiceAISApi.updateConsentsPsuDataTransactionAuthorisation).head +// resourceDocs += APIMethods_AccountInformationServiceAISApi.resourceDocs.filter(_.partialFunction == APIMethods_AccountInformationServiceAISApi.updateConsentsPsuDataUpdatePsuAuthentication).head +// resourceDocs += APIMethods_AccountInformationServiceAISApi.resourceDocs.filter(_.partialFunction == APIMethods_AccountInformationServiceAISApi.updateConsentsPsuDataUpdateAuthorisationConfirmation).head +// resourceDocs += APIMethods_AccountInformationServiceAISApi.resourceDocs.filter(_.partialFunction == APIMethods_AccountInformationServiceAISApi.updateConsentsPsuDataUpdateSelectPsuAuthenticationMethod).head +// resourceDocs += APIMethods_AccountInformationServiceAISApi.resourceDocs.filter(_.partialFunction == APIMethods_AccountInformationServiceAISApi.getConsentScaStatus).head +// +//} +// +// +// diff --git a/obp-api/src/main/scala/code/api/berlin/group/v1_3/ConfirmationOfFundsServicePIISApi.scala b/obp-api/src/main/scala/code/api/berlin/group/v1_3/ConfirmationOfFundsServicePIISApi.scala index 6a2bf7f41d..b649ba3c3a 100644 --- a/obp-api/src/main/scala/code/api/berlin/group/v1_3/ConfirmationOfFundsServicePIISApi.scala +++ b/obp-api/src/main/scala/code/api/berlin/group/v1_3/ConfirmationOfFundsServicePIISApi.scala @@ -1,114 +1,114 @@ -package code.api.builder.ConfirmationOfFundsServicePIISApi - -import scala.language.implicitConversions -import code.api.berlin.group.ConstantsBG -import code.api.berlin.group.v1_3.JSONFactory_BERLIN_GROUP_1_3._ -import code.api.berlin.group.v1_3.{JvalueCaseClass, OBP_BERLIN_GROUP_1_3} -import code.api.util.APIUtil._ -import code.api.util.ApiTag._ -import code.api.util.ErrorMessages._ -import code.api.util.{ApiTag, NewStyle} -import code.fx.fx -import code.util.Helper -import com.github.dwickern.macros.NameOf.nameOf -import com.openbankproject.commons.ExecutionContext.Implicits.global -import com.openbankproject.commons.util.ApiVersion -import net.liftweb.common.Full -import net.liftweb.http.rest.RestHelper -import net.liftweb.json -import net.liftweb.json._ - -import scala.collection.immutable.Nil -import scala.collection.mutable.ArrayBuffer - -object APIMethods_ConfirmationOfFundsServicePIISApi extends RestHelper { - val apiVersion = ConstantsBG.berlinGroupVersion1 - val resourceDocs = ArrayBuffer[ResourceDoc]() - val apiRelations = ArrayBuffer[ApiRelation]() - protected implicit def JvalueToSuper(what: JValue): JvalueCaseClass = JvalueCaseClass(what) - - val endpoints = - checkAvailabilityOfFunds :: - Nil - - - resourceDocs += ResourceDoc( - checkAvailabilityOfFunds, - apiVersion, - nameOf(checkAvailabilityOfFunds), - "POST", - "/funds-confirmations", - "Confirmation of Funds Request", - s""" ${mockedDataText(false)} -Creates a confirmation of funds request at the ASPSP. Checks whether a specific amount is available at point -of time of the request on an account linked to a given tuple card issuer(TPP)/card number, or addressed by -IBAN and TPP respectively. If the related extended services are used a conditional Consent-ID is contained -in the header. This field is contained but commented out in this specification. """, - json.parse( - """{ - "instructedAmount" : { - "amount" : "123", - "currency" : "EUR" - }, - "account" : { - "iban" : "GR12 1234 5123 4511 3981 4475 477", - } - }"""), - json.parse( - """{ - "fundsAvailable" : true - }"""), - List(AuthenticatedUserIsRequired, UnknownError), - ApiTag("Confirmation of Funds Service (PIIS)") :: apiTagBerlinGroupM :: Nil - ) - - lazy val checkAvailabilityOfFunds : OBPEndpoint = { - case "funds-confirmations" :: Nil JsonPost json -> _ => { - cc => - for { - (Full(u), callContext) <- authenticatedAccess(cc) - _ <- passesPsd2Aisp(callContext) - checkAvailabilityOfFundsJson <- NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the $CheckAvailabilityOfFundsJson ", 400, callContext) { - json.extract[CheckAvailabilityOfFundsJson] - } - - requestAccountAmount <- NewStyle.function.tryons(s"$InvalidNumber Current input is ${checkAvailabilityOfFundsJson.instructedAmount.amount} ", 400, callContext) { - BigDecimal(checkAvailabilityOfFundsJson.instructedAmount.amount) - } - - requestAccountCurrency = checkAvailabilityOfFundsJson.instructedAmount.currency - - _ <- Helper.booleanToFuture(s"${InvalidISOCurrencyCode} Current input is: '${ requestAccountCurrency}'", cc=callContext) { - isValidCurrencyISOCode(requestAccountCurrency) - } - - requestAccountIban = checkAvailabilityOfFundsJson.account.iban - (bankAccount, callContext) <- NewStyle.function.getBankAccountByIban(requestAccountIban, callContext) - currentAccountCurrency = bankAccount.currency - currentAccountBalance = bankAccount.balance - - - //From change from requestAccount Currency to currentBankAccount Currency - rate = fx.exchangeRate(requestAccountCurrency, currentAccountCurrency, Some(bankAccount.bankId.value), callContext) - - _ <- Helper.booleanToFuture(s"$InvalidCurrency The requested currency conversion (${requestAccountCurrency} to ${currentAccountCurrency}) is not supported.", cc=callContext) { - rate.isDefined - } - - requestChangedCurrencyAmount = fx.convert(requestAccountAmount, rate) - - fundsAvailable = (currentAccountBalance >= requestChangedCurrencyAmount) - - } yield { - (net.liftweb.json.parse(s"""{ - "fundsAvailable" : $fundsAvailable - }"""), - callContext) - } - } - } - -} - - - +//package code.api.builder.ConfirmationOfFundsServicePIISApi +// +//import scala.language.implicitConversions +//import code.api.berlin.group.ConstantsBG +//import code.api.berlin.group.v1_3.JSONFactory_BERLIN_GROUP_1_3._ +//import code.api.berlin.group.v1_3.{JvalueCaseClass, OBP_BERLIN_GROUP_1_3} +//import code.api.util.APIUtil._ +//import code.api.util.ApiTag._ +//import code.api.util.ErrorMessages._ +//import code.api.util.{ApiTag, NewStyle} +//import code.fx.fx +//import code.util.Helper +//import com.github.dwickern.macros.NameOf.nameOf +//import com.openbankproject.commons.ExecutionContext.Implicits.global +//import com.openbankproject.commons.util.ApiVersion +//import net.liftweb.common.Full +//import net.liftweb.http.rest.RestHelper +//import net.liftweb.json +//import net.liftweb.json._ +// +//import scala.collection.immutable.Nil +//import scala.collection.mutable.ArrayBuffer +// +//object APIMethods_ConfirmationOfFundsServicePIISApi extends RestHelper { +// val apiVersion = ConstantsBG.berlinGroupVersion1 +// val resourceDocs = ArrayBuffer[ResourceDoc]() +// val apiRelations = ArrayBuffer[ApiRelation]() +// protected implicit def JvalueToSuper(what: JValue): JvalueCaseClass = JvalueCaseClass(what) +// +// val endpoints = +// checkAvailabilityOfFunds :: +// Nil +// +// +// resourceDocs += ResourceDoc( +// checkAvailabilityOfFunds, +// apiVersion, +// nameOf(checkAvailabilityOfFunds), +// "POST", +// "/funds-confirmations", +// "Confirmation of Funds Request", +// s""" ${mockedDataText(false)} +//Creates a confirmation of funds request at the ASPSP. Checks whether a specific amount is available at point +//of time of the request on an account linked to a given tuple card issuer(TPP)/card number, or addressed by +//IBAN and TPP respectively. If the related extended services are used a conditional Consent-ID is contained +//in the header. This field is contained but commented out in this specification. """, +// json.parse( +// """{ +// "instructedAmount" : { +// "amount" : "123", +// "currency" : "EUR" +// }, +// "account" : { +// "iban" : "GR12 1234 5123 4511 3981 4475 477", +// } +// }"""), +// json.parse( +// """{ +// "fundsAvailable" : true +// }"""), +// List(AuthenticatedUserIsRequired, UnknownError), +// ApiTag("Confirmation of Funds Service (PIIS)") :: apiTagBerlinGroupM :: Nil +// ) +// +// lazy val checkAvailabilityOfFunds : OBPEndpoint = { +// case "funds-confirmations" :: Nil JsonPost json -> _ => { +// cc => +// for { +// (Full(u), callContext) <- authenticatedAccess(cc) +// _ <- passesPsd2Aisp(callContext) +// checkAvailabilityOfFundsJson <- NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the $CheckAvailabilityOfFundsJson ", 400, callContext) { +// json.extract[CheckAvailabilityOfFundsJson] +// } +// +// requestAccountAmount <- NewStyle.function.tryons(s"$InvalidNumber Current input is ${checkAvailabilityOfFundsJson.instructedAmount.amount} ", 400, callContext) { +// BigDecimal(checkAvailabilityOfFundsJson.instructedAmount.amount) +// } +// +// requestAccountCurrency = checkAvailabilityOfFundsJson.instructedAmount.currency +// +// _ <- Helper.booleanToFuture(s"${InvalidISOCurrencyCode} Current input is: '${ requestAccountCurrency}'", cc=callContext) { +// isValidCurrencyISOCode(requestAccountCurrency) +// } +// +// requestAccountIban = checkAvailabilityOfFundsJson.account.iban +// (bankAccount, callContext) <- NewStyle.function.getBankAccountByIban(requestAccountIban, callContext) +// currentAccountCurrency = bankAccount.currency +// currentAccountBalance = bankAccount.balance +// +// +// //From change from requestAccount Currency to currentBankAccount Currency +// rate = fx.exchangeRate(requestAccountCurrency, currentAccountCurrency, Some(bankAccount.bankId.value), callContext) +// +// _ <- Helper.booleanToFuture(s"$InvalidCurrency The requested currency conversion (${requestAccountCurrency} to ${currentAccountCurrency}) is not supported.", cc=callContext) { +// rate.isDefined +// } +// +// requestChangedCurrencyAmount = fx.convert(requestAccountAmount, rate) +// +// fundsAvailable = (currentAccountBalance >= requestChangedCurrencyAmount) +// +// } yield { +// (net.liftweb.json.parse(s"""{ +// "fundsAvailable" : $fundsAvailable +// }"""), +// callContext) +// } +// } +// } +// +//} +// +// +// diff --git a/obp-api/src/main/scala/code/api/berlin/group/v1_3/Http4sBGv13.scala b/obp-api/src/main/scala/code/api/berlin/group/v1_3/Http4sBGv13.scala new file mode 100644 index 0000000000..a2d83e2737 --- /dev/null +++ b/obp-api/src/main/scala/code/api/berlin/group/v1_3/Http4sBGv13.scala @@ -0,0 +1,39 @@ +package code.api.berlin.group.v1_3 + +import cats.data.{Kleisli, OptionT} +import cats.effect._ +import code.api.berlin.group.ConstantsBG +import code.api.util.APIUtil.ResourceDoc +import code.api.util.http4s.ResourceDocMiddleware +import code.util.Helper.MdcLoggable +import org.http4s._ + +import scala.collection.mutable.ArrayBuffer + +/** + * Native http4s aggregator for Berlin Group v1.3, replacing the Lift + * `OBP_BERLIN_GROUP_1_3` statelessDispatch registration. Mirrors `Http4sBGv2`. + * + * Groups: AIS / PIS / SigningBaskets / PIIS. Added incrementally. + */ +object Http4sBGv13 extends MdcLoggable { + + type HttpF[A] = OptionT[IO, A] + + val implementedInApiVersion = ConstantsBG.berlinGroupVersion1 + + val resourceDocs: ArrayBuffer[ResourceDoc] = + Http4sBGv13AIS.resourceDocs ++ + Http4sBGv13PIS.resourceDocs ++ + Http4sBGv13PIIS.resourceDocs ++ + Http4sBGv13SigningBaskets.resourceDocs + + val allRoutes: HttpRoutes[IO] = Kleisli[HttpF, Request[IO], Response[IO]] { req => + Http4sBGv13AIS.routes(req) + .orElse(Http4sBGv13PIS.routes(req)) + .orElse(Http4sBGv13PIIS.routes(req)) + .orElse(Http4sBGv13SigningBaskets.routes(req)) + } + + val wrappedRoutes: HttpRoutes[IO] = ResourceDocMiddleware.apply(resourceDocs)(allRoutes) +} diff --git a/obp-api/src/main/scala/code/api/berlin/group/v1_3/Http4sBGv13AIS.scala b/obp-api/src/main/scala/code/api/berlin/group/v1_3/Http4sBGv13AIS.scala new file mode 100644 index 0000000000..f536dcad48 --- /dev/null +++ b/obp-api/src/main/scala/code/api/berlin/group/v1_3/Http4sBGv13AIS.scala @@ -0,0 +1,1356 @@ +package code.api.berlin.group.v1_3 + +import cats.data.{Kleisli, OptionT} +import cats.effect._ +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.ConstantsBG +import code.api.berlin.group.v1_3.JSONFactory_BERLIN_GROUP_1_3._ +import code.api.berlin.group.v1_3.model._ +import code.api.berlin.group.v1_3.{BgSpecValidation, JSONFactory_BERLIN_GROUP_1_3, JvalueCaseClass} +import code.api.util.APIUtil.{EmptyBody, ResourceDoc, UserOrApplication, connectorEmptyResponse, createQueriesByHttpParams, fullBoxOrException, getHttpRequestUrlParam, getSuggestedDefaultScaMethod, mockedDataText, passesPsd2Aisp, unboxFull, unboxFullOrFail} +import code.api.util.CallContext +import code.api.util.ApiTag._ +import code.api.util.CustomJsonFormats +import code.api.util.ErrorMessages._ +import code.api.util.{ApiTag, NewStyle} +import code.api.util.newstyle.ViewNewStyle +import code.api.util.http4s.Http4sRequestAttributes.{EndpointHelpers, RequestOps} +import code.consent.{ConsentStatus, Consents} +import code.context.{ConsentAuthContextProvider, UserAuthContextProvider} +import code.model +import code.model._ +import code.util.Helper +import code.util.Helper.{MdcLoggable, booleanToFuture} +import code.views.Views +import com.github.dwickern.macros.NameOf.nameOf +import com.openbankproject.commons.ExecutionContext.Implicits.global +import com.openbankproject.commons.model._ +import com.openbankproject.commons.model.enums.{ChallengeType, StrongCustomerAuthenticationStatus, SuppliedAnswerType} +import net.liftweb +import net.liftweb.common.{Empty, Full} +import net.liftweb.json +import net.liftweb.json.Formats +import org.http4s._ +import org.http4s.dsl.io._ + +import scala.collection.mutable.ArrayBuffer +import scala.concurrent.Future +import scala.language.implicitConversions + +object Http4sBGv13AIS extends MdcLoggable { + + type HttpF[A] = OptionT[IO, A] + + implicit val formats: Formats = CustomJsonFormats.formats + + protected implicit def JvalueToSuper(what: net.liftweb.json.JValue): JvalueCaseClass = JvalueCaseClass(what) + + val implementedInApiVersion = ConstantsBG.berlinGroupVersion1 + val resourceDocs = ArrayBuffer[ResourceDoc]() + + val bgV13Prefix = Root / ConstantsBG.berlinGroupVersion1.urlPrefix / ConstantsBG.berlinGroupVersion1.apiShortVersion + + private def checkAccountAccess(viewId: ViewId, u: User, account: BankAccount, callContext: Option[code.api.util.CallContext]) = { + Future { + Helper.booleanToBox(u.hasViewAccess(BankIdAccountId(account.bankId, account.accountId), viewId, callContext)) + } map { + unboxFullOrFail(_, callContext, s"$NoViewReadAccountsBerlinGroup ${viewId.value} userId : ${u.userId}. account : ${account.accountId}", 403) + } + } + + // ── POST /consents ────────────────────────────────────────────────────── + lazy val createConsent: HttpRoutes[IO] = HttpRoutes.of[IO] { + case req @ POST -> `bgV13Prefix` / "consents" => + EndpointHelpers.executeFutureCreated(req) { + val cc = req.callContext + val callContext = Some(cc) + val createdByUser: Option[User] = cc.user match { + case Full(user) => Some(user) + case _ => None + } + for { + _ <- passesPsd2Aisp(callContext) + failMsg = s"$InvalidJsonFormat The Json body should be the $PostConsentJson " + consentJson <- NewStyle.function.tryons(failMsg, 400, callContext) { + json.parse(cc.httpBody.getOrElse("")).extract[PostConsentJson] + } + _ <- if (consentJson.access.availableAccounts.isDefined) { + for { + _ <- booleanToFuture(failMsg = BerlinGroupConsentAccessAvailableAccounts, cc = callContext) { + consentJson.access.availableAccounts.contains("allAccounts") + } + _ <- booleanToFuture(failMsg = BerlinGroupConsentAccessRecurringIndicator, cc = callContext) { + !consentJson.recurringIndicator + } + _ <- booleanToFuture(failMsg = BerlinGroupConsentAccessFrequencyPerDay, cc = callContext) { + consentJson.frequencyPerDay == 1 + } + } yield Full(()) + } else { + booleanToFuture(failMsg = BerlinGroupConsentAccessIsEmpty, cc = callContext) { + consentJson.access.accounts.isDefined || + consentJson.access.balances.isDefined || + consentJson.access.transactions.isDefined + } + } + upperLimit = code.api.util.APIUtil.getPropsAsIntValue("berlin_group_frequency_per_day_upper_limit", 4) + _ <- booleanToFuture(failMsg = FrequencyPerDayError, cc = callContext) { + consentJson.frequencyPerDay > 0 && consentJson.frequencyPerDay <= upperLimit + } + _ <- booleanToFuture(failMsg = FrequencyPerDayMustBeOneError, cc = callContext) { + consentJson.recurringIndicator || + !consentJson.recurringIndicator && consentJson.frequencyPerDay == 1 + } + failMsg2 = BgSpecValidation.getErrorMessage(consentJson.validUntil) + validUntil = BgSpecValidation.getDate(consentJson.validUntil) + _ <- booleanToFuture(failMsg2, 400, callContext) { + failMsg2.isEmpty + } + _ <- NewStyle.function.getBankAccountsByIban(consentJson.access.accounts.getOrElse(Nil).map(_.iban.getOrElse("")), callContext) + createdConsent <- Future(Consents.consentProvider.vend.createBerlinGroupConsent( + createdByUser, + callContext.flatMap(_.consumer), + recurringIndicator = consentJson.recurringIndicator, + validUntil = validUntil, + frequencyPerDay = consentJson.frequencyPerDay, + combinedServiceIndicator = consentJson.combinedServiceIndicator.getOrElse(false), + apiStandard = Some(implementedInApiVersion.apiStandard), + apiVersion = Some(implementedInApiVersion.apiShortVersion) + )) map { + i => connectorEmptyResponse(i, callContext) + } + consentJWT <- _root_.code.api.util.Consent.createBerlinGroupConsentJWT( + createdByUser, + consentJson, + createdConsent.secret, + createdConsent.consentId, + callContext.flatMap(_.consumer).map(_.consumerId.get), + Some(validUntil), + callContext + ) map { + i => connectorEmptyResponse(i, callContext) + } + _ <- Future(Consents.consentProvider.vend.setJsonWebToken(createdConsent.consentId, consentJWT)) map { + i => connectorEmptyResponse(i, callContext) + } + } yield { + createPostConsentResponseJson(createdConsent) + } + } + } + + // ── DELETE /consents/CONSENTID ────────────────────────────────────────── + lazy val deleteConsent: HttpRoutes[IO] = HttpRoutes.of[IO] { + case req @ DELETE -> `bgV13Prefix` / "consents" / consentId => + EndpointHelpers.executeDelete(req) { cc => + val callContext = Some(cc) + for { + _ <- passesPsd2Aisp(callContext) + consent <- Future(Consents.consentProvider.vend.getConsentByConsentId(consentId)) map { + unboxFullOrFail(_, callContext, ConsentNotFound, 403) + } + consumerIdFromConsent = consent.mConsumerId.get + consumerIdFromCurrentCall = callContext.map(_.consumer.map(_.consumerId.get).getOrElse("None")).getOrElse("None") + _ <- booleanToFuture(failMsg = ConsentNotFound, failCode = 403, cc = callContext) { + consumerIdFromConsent == consumerIdFromCurrentCall + } + _ <- Future(Consents.consentProvider.vend.revokeBerlinGroupConsent(consentId)) map { + i => connectorEmptyResponse(i, callContext) + } + } yield () + } + } + + // ── GET /accounts ─────────────────────────────────────────────────────── + lazy val getAccountList: HttpRoutes[IO] = HttpRoutes.of[IO] { + case req @ GET -> `bgV13Prefix` / "accounts" => + EndpointHelpers.executeAndRespond(req) { cc => + val callContext = Some(cc) + val u = cc.user.openOrThrowException(AuthenticatedUserIsRequired) + for { + withBalanceParam <- NewStyle.function.tryons(s"$InvalidUrlParameters withBalance parameter can only take two values: TRUE or FALSE!", 400, callContext) { + val withBalance = getHttpRequestUrlParam(cc.url, "withBalance") + if (withBalance.isEmpty) Some(false) else Some(withBalance.toBoolean) + } + _ <- passesPsd2Aisp(callContext) + (availablePrivateAccounts, callContext) <- NewStyle.function.getAccountListOfBerlinGroup(u, callContext) + (canReadBalancesAccounts, callContext) <- NewStyle.function.getAccountCanReadBalancesOfBerlinGroup(u, callContext) + (canReadTransactionsAccounts, callContext) <- NewStyle.function.getAccountCanReadTransactionsOfBerlinGroup(u, callContext) + (accounts, callContext) <- NewStyle.function.getBankAccounts(availablePrivateAccounts, callContext) + bankAccountsFiltered = accounts.filter(bankAccount => + bankAccount.attributes.toList.flatten.find(attribute => + attribute.name.equals("CashAccountTypeCode") && + attribute.`type`.equals("STRING") && + attribute.value.equalsIgnoreCase("card") + ).isEmpty) + (balances, callContext) <- code.api.util.newstyle.BankAccountBalanceNewStyle.getBankAccountsBalances( + bankAccountsFiltered.map(_.accountId), + callContext + ) + } yield { + JSONFactory_BERLIN_GROUP_1_3.createAccountListJson( + bankAccountsFiltered, + canReadBalancesAccounts, + canReadTransactionsAccounts, + u, + withBalanceParam, + balances + ) + } + } + } + + // ── GET /accounts/ACCOUNT_ID/balances ─────────────────────────────────── + lazy val getBalances: HttpRoutes[IO] = HttpRoutes.of[IO] { + case req @ GET -> `bgV13Prefix` / "accounts" / accountId / "balances" => + EndpointHelpers.executeAndRespond(req) { cc => + val callContext = Some(cc) + val u = cc.user.openOrThrowException(AuthenticatedUserIsRequired) + for { + _ <- passesPsd2Aisp(callContext) + (account: BankAccount, callContext) <- NewStyle.function.getBankAccountByAccountId(AccountId(accountId), callContext) + _ <- checkAccountAccess(ViewId(SYSTEM_READ_BALANCES_BERLIN_GROUP_VIEW_ID), u, account, callContext) + (accountBalances, callContext) <- code.api.util.newstyle.BankAccountBalanceNewStyle.getBankAccountBalances( + AccountId(accountId), + callContext + ) + } yield { + JSONFactory_BERLIN_GROUP_1_3.createAccountBalanceJSON(account, accountBalances) + } + } + } + + // ── GET /card-accounts ────────────────────────────────────────────────── + lazy val getCardAccounts: HttpRoutes[IO] = HttpRoutes.of[IO] { + case req @ GET -> `bgV13Prefix` / "card-accounts" => + EndpointHelpers.executeAndRespond(req) { cc => + val callContext = Some(cc) + val u = cc.user.openOrThrowException(AuthenticatedUserIsRequired) + for { + _ <- passesPsd2Aisp(callContext) + availablePrivateAccounts <- Views.views.vend.getPrivateBankAccountsFuture(u) + (_, callContext) <- NewStyle.function.getPhysicalCardsForUser(u, callContext) + (accounts, callContext) <- NewStyle.function.getBankAccounts(availablePrivateAccounts, callContext) + (canReadBalancesAccounts, callContext) <- NewStyle.function.getAccountCanReadBalancesOfBerlinGroup(u, callContext) + (canReadTransactionsAccounts, callContext) <- NewStyle.function.getAccountCanReadTransactionsOfBerlinGroup(u, callContext) + bankAccountsFiltered = accounts.filter(bankAccount => + bankAccount.attributes.toList.flatten.find(attribute => + attribute.name.equals("CashAccountTypeCode") && + attribute.`type`.equals("STRING") && + attribute.value.equalsIgnoreCase("card") + ).isDefined) + } yield { + JSONFactory_BERLIN_GROUP_1_3.createCardAccountListJson( + bankAccountsFiltered, + canReadBalancesAccounts, + canReadTransactionsAccounts, + u + ) + } + } + } + + // ── GET /card-accounts/ACCOUNT_ID/balances ────────────────────────────── + lazy val getCardAccountBalances: HttpRoutes[IO] = HttpRoutes.of[IO] { + case req @ GET -> `bgV13Prefix` / "card-accounts" / accountId / "balances" => + EndpointHelpers.executeAndRespond(req) { cc => + val callContext = Some(cc) + val u = cc.user.openOrThrowException(AuthenticatedUserIsRequired) + for { + _ <- passesPsd2Aisp(callContext) + (account: BankAccount, callContext) <- NewStyle.function.getBankAccountByAccountId(AccountId(accountId), callContext) + _ <- checkAccountAccess(ViewId(SYSTEM_READ_BALANCES_BERLIN_GROUP_VIEW_ID), u, account, callContext) + (accountBalances, callContext) <- code.api.util.newstyle.BankAccountBalanceNewStyle.getBankAccountBalances( + AccountId(accountId), + callContext + ) + } yield { + JSONFactory_BERLIN_GROUP_1_3.createCardAccountBalanceJSON(account, accountBalances) + } + } + } + + // ── GET /card-accounts/ACCOUNT_ID/transactions ────────────────────────── + lazy val getCardAccountTransactionList: HttpRoutes[IO] = HttpRoutes.of[IO] { + case req @ GET -> `bgV13Prefix` / "card-accounts" / accountId / "transactions" => + EndpointHelpers.executeAndRespond(req) { cc => + val callContext = Some(cc) + val u = cc.user.openOrThrowException(AuthenticatedUserIsRequired) + for { + _ <- passesPsd2Aisp(callContext) + (bankAccount: BankAccount, callContext) <- NewStyle.function.getBankAccountByAccountId(AccountId(accountId), callContext) + (bank, callContext) <- NewStyle.function.getBank(bankAccount.bankId, callContext) + viewId = ViewId(SYSTEM_READ_TRANSACTIONS_BERLIN_GROUP_VIEW_ID) + bankIdAccountId = BankIdAccountId(bankAccount.bankId, bankAccount.accountId) + view <- ViewNewStyle.checkAccountAccessAndGetView(viewId, bankIdAccountId, Full(u), callContext) + params <- Future { createQueriesByHttpParams(callContext.get.requestHeaders)} map { + x => fullBoxOrException(x ~> APIFailureNewStyle(UnknownError, 400, callContext.map(_.toLight))) + } map { unboxFull(_) } + (transactions, callContext) <- code.model.toBankAccountExtended(bankAccount).getModeratedTransactionsFuture(bank, Full(u), view, callContext, params) map { + x => fullBoxOrException(x ~> APIFailureNewStyle(UnknownError, 400, callContext.map(_.toLight))) + } map { unboxFull(_) } + } yield { + JSONFactory_BERLIN_GROUP_1_3.createCardTransactionsJson(bankAccount, transactions) + } + } + } + + // ── GET /consents/CONSENTID/authorisations ────────────────────────────── + lazy val getConsentAuthorisation: HttpRoutes[IO] = HttpRoutes.of[IO] { + case req @ GET -> `bgV13Prefix` / "consents" / consentId / "authorisations" => + EndpointHelpers.executeAndRespond(req) { cc => + val callContext = Some(cc) + for { + _ <- passesPsd2Aisp(callContext) + (challenges, callContext) <- NewStyle.function.getChallengesByConsentId(consentId, callContext) + } yield { + JSONFactory_BERLIN_GROUP_1_3.AuthorisationJsonV13(challenges.map(_.challengeId)) + } + } + } + + // ── GET /consents/CONSENTID ───────────────────────────────────────────── + lazy val getConsentInformation: HttpRoutes[IO] = HttpRoutes.of[IO] { + case req @ GET -> `bgV13Prefix` / "consents" / consentId => + EndpointHelpers.executeAndRespond(req) { cc => + val callContext = Some(cc) + for { + _ <- passesPsd2Aisp(callContext) + consent <- Future(Consents.consentProvider.vend.getConsentByConsentId(consentId)) map { + unboxFullOrFail(_, callContext, s"$ConsentNotFound ($consentId)") + } + consumerIdFromConsent = consent.mConsumerId.get + consumerIdFromCurrentCall = callContext.map(_.consumer.map(_.consumerId.get).getOrElse("None")).getOrElse("None") + _ <- booleanToFuture(failMsg = ConsentNotFound, failCode = 403, cc = callContext) { + consumerIdFromConsent == consumerIdFromCurrentCall + } + } yield { + createGetConsentResponseJson(consent) + } + } + } + + // ── GET /consents/CONSENTID/authorisations/AUTHORISATIONID ───────────── + lazy val getConsentScaStatus: HttpRoutes[IO] = HttpRoutes.of[IO] { + case req @ GET -> `bgV13Prefix` / "consents" / consentId / "authorisations" / authorisationId => + EndpointHelpers.executeAndRespond(req) { cc => + val callContext = Some(cc) + for { + _ <- passesPsd2Aisp(callContext) + _ <- Future(Consents.consentProvider.vend.getConsentByConsentId(consentId)) map { + unboxFullOrFail(_, callContext, s"$ConsentNotFound ($consentId)", 403) + } + (challenges, callContext) <- NewStyle.function.getChallengesByConsentId(consentId, callContext) + } yield { + val challengeStatus = challenges.filter(_.challengeId == authorisationId) + .flatMap(_.scaStatus).headOption.map(_.toString).getOrElse("None") + JSONFactory_BERLIN_GROUP_1_3.ScaStatusJsonV13(challengeStatus) + } + } + } + + // ── GET /consents/CONSENTID/status ────────────────────────────────────── + lazy val getConsentStatus: HttpRoutes[IO] = HttpRoutes.of[IO] { + case req @ GET -> `bgV13Prefix` / "consents" / consentId / "status" => + EndpointHelpers.executeAndRespond(req) { cc => + val callContext = Some(cc) + for { + _ <- passesPsd2Aisp(callContext) + consent <- Future(Consents.consentProvider.vend.getConsentByConsentId(consentId)) map { + unboxFullOrFail(_, callContext, ConsentNotFound, 403) + } + } yield { + JSONFactory_BERLIN_GROUP_1_3.ConsentStatusJsonV13(consent.status) + } + } + } + + // ── GET /accounts/ACCOUNT_ID/transactions/TRANSACTIONID ───────────────── + lazy val getTransactionDetails: HttpRoutes[IO] = HttpRoutes.of[IO] { + case req @ GET -> `bgV13Prefix` / "accounts" / accountId / "transactions" / transactionId => + EndpointHelpers.executeAndRespond(req) { cc => + val callContext = Some(cc) + val user = cc.user.openOrThrowException(AuthenticatedUserIsRequired) + for { + (account: BankAccount, callContext) <- NewStyle.function.getBankAccountByAccountId(AccountId(accountId), callContext) + viewId = ViewId(SYSTEM_READ_TRANSACTIONS_BERLIN_GROUP_VIEW_ID) + bankIdAccountId = BankIdAccountId(account.bankId, account.accountId) + view <- ViewNewStyle.checkAccountAccessAndGetView(viewId, bankIdAccountId, Full(user), callContext) + (moderatedTransaction, callContext) <- account.moderatedTransactionFuture(TransactionId(transactionId), view, Some(user), callContext) map { + unboxFullOrFail(_, callContext, GetTransactionsException) + } + } yield { + JSONFactory_BERLIN_GROUP_1_3.createTransactionJson(account, moderatedTransaction) + } + } + } + + // ── GET /accounts/ACCOUNT_ID/transactions ─────────────────────────────── + lazy val getTransactionList: HttpRoutes[IO] = HttpRoutes.of[IO] { + case req @ GET -> `bgV13Prefix` / "accounts" / accountId / "transactions" => + EndpointHelpers.executeAndRespond(req) { cc => + val callContext = Some(cc) + val u = cc.user.openOrThrowException(AuthenticatedUserIsRequired) + for { + _ <- passesPsd2Aisp(callContext) + (bankAccount: BankAccount, callContext) <- NewStyle.function.getBankAccountByAccountId(AccountId(accountId), callContext) + (bank, callContext) <- NewStyle.function.getBank(bankAccount.bankId, callContext) + viewId = ViewId(SYSTEM_READ_TRANSACTIONS_BERLIN_GROUP_VIEW_ID) + bankIdAccountId = BankIdAccountId(bankAccount.bankId, bankAccount.accountId) + view <- ViewNewStyle.checkAccountAccessAndGetView(viewId, bankIdAccountId, Full(u), callContext) + params <- Future { createQueriesByHttpParams(callContext.get.requestHeaders)} map { + x => fullBoxOrException(x ~> APIFailureNewStyle(UnknownError, 400, callContext.map(_.toLight))) + } map { unboxFull(_) } + bookingStatus = getHttpRequestUrlParam(cc.url, "bookingStatus") + _ <- booleanToFuture(s"$InvalidUrlParameters bookingStatus parameter must take two one of those values : booked, pending or both!", 400, callContext) { + bookingStatus match { + case "booked" | "pending" | "both" => true + case _ => false + } + } + (transactions, callContext) <- bankAccount.getModeratedTransactionsFuture(bank, Full(u), view, callContext, params) map { + x => fullBoxOrException(x ~> APIFailureNewStyle(UnknownError, 400, callContext.map(_.toLight))) + } map { unboxFull(_) } + } yield { + JSONFactory_BERLIN_GROUP_1_3.createTransactionsJson(bankAccount, transactions, bookingStatus) + } + } + } + + // ── GET /accounts/ACCOUNT_ID ──────────────────────────────────────────── + lazy val getAccountDetails: HttpRoutes[IO] = HttpRoutes.of[IO] { + case req @ GET -> `bgV13Prefix` / "accounts" / accountId => + EndpointHelpers.executeAndRespond(req) { cc => + val callContext = Some(cc) + val u = cc.user.openOrThrowException(AuthenticatedUserIsRequired) + for { + withBalanceParam <- NewStyle.function.tryons(s"$InvalidUrlParameters withBalance parameter can only take two values: TRUE or FALSE!", 400, callContext) { + val withBalance = getHttpRequestUrlParam(cc.url, "withBalance") + if (withBalance.isEmpty) Some(false) else Some(withBalance.toBoolean) + } + _ <- passesPsd2Aisp(callContext) + (account: BankAccount, callContext) <- NewStyle.function.getBankAccountByAccountId(AccountId(accountId), callContext) + (canReadBalancesAccounts, callContext) <- NewStyle.function.getAccountCanReadBalancesOfBerlinGroup(u, callContext) + (canReadTransactionsAccounts, callContext) <- NewStyle.function.getAccountCanReadTransactionsOfBerlinGroup(u, callContext) + _ <- checkAccountAccess(ViewId(SYSTEM_READ_ACCOUNTS_BERLIN_GROUP_VIEW_ID), u, account, callContext) + (accountBalances, callContext) <- code.api.util.newstyle.BankAccountBalanceNewStyle.getBankAccountBalances( + AccountId(accountId), + callContext + ) + } yield { + JSONFactory_BERLIN_GROUP_1_3.createAccountDetailsJson( + account, + canReadBalancesAccounts, + canReadTransactionsAccounts, + withBalanceParam, + accountBalances, + u + ) + } + } + } + + // ── GET /card-accounts/ACCOUNT_ID ─────────────────────────────────────── + lazy val readCardAccount: HttpRoutes[IO] = HttpRoutes.of[IO] { + case req @ GET -> `bgV13Prefix` / "card-accounts" / accountId => + EndpointHelpers.executeAndRespond(req) { cc => + val callContext = Some(cc) + val u = cc.user.openOrThrowException(AuthenticatedUserIsRequired) + for { + _ <- passesPsd2Aisp(callContext) + (account: BankAccount, callContext) <- NewStyle.function.getBankAccountByAccountId(AccountId(accountId), callContext) + (canReadBalancesAccounts, callContext) <- NewStyle.function.getAccountCanReadBalancesOfBerlinGroup(u, callContext) + (canReadTransactionsAccounts, callContext) <- NewStyle.function.getAccountCanReadTransactionsOfBerlinGroup(u, callContext) + _ <- checkAccountAccess(ViewId(SYSTEM_READ_ACCOUNTS_BERLIN_GROUP_VIEW_ID), u, account, callContext) + withBalanceParam <- NewStyle.function.tryons(s"$InvalidUrlParameters withBalance parameter can only take two values: TRUE or FALSE!", 400, callContext) { + val withBalance = getHttpRequestUrlParam(cc.url, "withBalance") + if (withBalance.isEmpty) Some(false) else Some(withBalance.toBoolean) + } + (accountBalances, callContext) <- code.api.util.newstyle.BankAccountBalanceNewStyle.getBankAccountBalances( + AccountId(accountId), + callContext + ) + } yield { + JSONFactory_BERLIN_GROUP_1_3.createCardAccountDetailsJson( + account, + canReadBalancesAccounts, + canReadTransactionsAccounts, + withBalanceParam, + accountBalances, + u + ) + } + } + } + + // ── POST /consents/CONSENTID/authorisations (3 body-guard variants) ───── + lazy val startConsentAuthorisationAll: HttpRoutes[IO] = HttpRoutes.of[IO] { + case req @ POST -> `bgV13Prefix` / "consents" / consentId / "authorisations" => + EndpointHelpers.executeFutureCreated(req) { + val cc = req.callContext + val callContext = Some(cc) + val u = cc.user.openOrThrowException(AuthenticatedUserIsRequired) + val parsedJson = scala.util.Try(json.parse(cc.httpBody.getOrElse(""))).getOrElse(json.JNothing) + if (checkTransactionAuthorisation(parsedJson)) { + for { + _ <- passesPsd2Aisp(callContext) + consent <- Future(Consents.consentProvider.vend.getConsentByConsentId(consentId)) map { + unboxFullOrFail(_, callContext, ConsentNotFound, 403) + } + (challenges, callContext) <- NewStyle.function.createChallengesC2( + List(u.userId), + ChallengeType.BERLIN_GROUP_CONSENT_CHALLENGE, + None, + getSuggestedDefaultScaMethod(), + Some(StrongCustomerAuthenticationStatus.received), + Some(consentId), + None, + callContext + ) + challenge <- NewStyle.function.tryons(InvalidConnectorResponseForCreateChallenge, 400, callContext) { + challenges.head + } + } yield { + createStartConsentAuthorisationJson(consent, challenge) + } + } else { + // mocked for updatePsuAuthentication and selectPsuAuthenticationMethod variants + Future.successful(liftweb.json.parse( + """{ + "scaStatus": "received", + "psuMessage": "Please use your BankApp for transaction Authorisation.", + "authorisationId": "123auth456.", + "_links": + { + "scaStatus": {"href":"/v1.3/consents/qwer3456tzui7890/authorisations/123auth456"} + } + }""")) + } + } + } + + // ── PUT /consents/CONSENTID/authorisations/AUTHORISATIONID (4 variants) ─ + lazy val updateConsentsPsuDataAll: HttpRoutes[IO] = HttpRoutes.of[IO] { + case req @ PUT -> `bgV13Prefix` / "consents" / consentId / "authorisations" / authorisationId => + EndpointHelpers.executeAndRespond(req) { cc => + val callContext = Some(cc) + val u = cc.user.openOrThrowException(AuthenticatedUserIsRequired) + val parsedJson = scala.util.Try(json.parse(cc.httpBody.getOrElse(""))).getOrElse(json.JNothing) + if (checkTransactionAuthorisation(parsedJson)) { + for { + _ <- passesPsd2Aisp(callContext) + _ <- Future(Consents.consentProvider.vend.getConsentByConsentId(consentId)) map { + unboxFullOrFail(_, callContext, ConsentNotFound, 403) + } + failMsg = s"$InvalidJsonFormat The Json body should be the $TransactionAuthorisation " + updateJson <- NewStyle.function.tryons(failMsg, 400, callContext) { + parsedJson.extract[TransactionAuthorisation] + } + (_, callContext) <- NewStyle.function.getChallenge(authorisationId, callContext) + (challenge, callContext) <- NewStyle.function.validateChallengeAnswerC4( + ChallengeType.BERLIN_GROUP_CONSENT_CHALLENGE, + None, + Some(consentId), + authorisationId, + updateJson.scaAuthenticationData, + SuppliedAnswerType.PLAIN_TEXT_VALUE, + callContext + ) + consent <- challenge.scaStatus match { + case Some(status) if status == StrongCustomerAuthenticationStatus.finalised => + Future(Consents.consentProvider.vend.updateConsentStatus(consentId, ConsentStatus.valid)) + case Some(status) if status == StrongCustomerAuthenticationStatus.failed => + Future(Consents.consentProvider.vend.updateConsentStatus(consentId, ConsentStatus.rejected)) + case _ => + Future(Consents.consentProvider.vend.getConsentByConsentId(consentId)) + } + _ <- NewStyle.function.tryons(ConsentUpdateStatusError, 400, callContext) { + consent.toList.size == 1 + } + _ <- Future { + val authContexts = UserAuthContextProvider.userAuthContextProvider.vend.getUserAuthContextsBox(u.userId) + .map(_.map(i => BasicUserAuthContext(i.key, i.value))) + ConsentAuthContextProvider.consentAuthContextProvider.vend.createOrUpdateConsentAuthContexts(consentId, authContexts.getOrElse(Nil)) + } map { + unboxFullOrFail(_, callContext, ConsentUserAuthContextCannotBeAdded) + } + _ <- Future(Consents.consentProvider.vend.updateConsentUser(consentId, u)) map { + unboxFullOrFail(_, callContext, ConsentUserCannotBeAdded) + } + } yield { + createPutConsentResponseJson(consent.toList.head) + } + } else if (checkUpdatePsuAuthentication(parsedJson)) { + Future.successful(liftweb.json.parse( + """{ + | "scaStatus": "psuAuthenticated", + | "_links": { + | "authoriseTransaction": {"href": "/psd2/v1/payments/1234-wertiq-983/authorisations/123auth456"} + | } + |}""".stripMargin)) + } else if (checkSelectPsuAuthenticationMethod(parsedJson)) { + Future.successful(liftweb.json.parse( + """{ + | "scaStatus": "scaMethodSelected", + | "chosenScaMethod": { + | "authenticationType": "SMS_OTP", + | "authenticationMethodId": "myAuthenticationID"}, + | "challengeData": { + | "otpMaxLength": 6, + | "otpFormat": "integer"}, + | "_links": { + | "authoriseTransaction": {"href": "/psd2/v1/payments/1234-wertiq-983/authorisations/123auth456"} + | } + |}""".stripMargin)) + } else { + // authorisationConfirmation variant + Future.successful(liftweb.json.parse( + """{ + | "scaStatus": "finalised", + | "_links":{ + | "status": {"href":"/v1/payments/sepa-credit-transfers/qwer3456tzui7890/status"} + | } + |}""".stripMargin)) + } + } + } + + private def initConsentResourceDocs(): Unit = { + + resourceDocs += ResourceDoc( + null, + implementedInApiVersion, + nameOf(createConsent), + "POST", + "/consents", + "Create consent", + s"""${mockedDataText(false)} +This method create a consent resource, defining access rights to dedicated accounts of +a given PSU-ID. These accounts are addressed explicitly in the method as +parameters as a core function. + +**Side Effects** +When this Consent Request is a request where the "recurringIndicator" equals "true", +and if it exists already a former consent for recurring access on account information +for the addressed PSU, then the former consent automatically expires as soon as the new +consent request is authorised by the PSU. + +Optional Extension: +As an option, an ASPSP might optionally accept a specific access right on the access on all psd2 related services for all available accounts. + +As another option an ASPSP might optionally also accept a command, where only access rights are inserted without mentioning the addressed account. +The relation to accounts is then handled afterwards between PSU and ASPSP. +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( + accounts = Option(List(ConsentAccessAccountsJson( + iban = Some(code.api.util.ExampleValue.ibanExample.value), + bban = None, + pan = None, + maskedPan = None, + msisdn = None, + currency = None, + ))), + balances = None, + transactions = None, + availableAccounts = None, + allPsd2 = None + ), + recurringIndicator = true, + validUntil = "2020-12-31", + frequencyPerDay = 4, + combinedServiceIndicator = Some(false) + ), + PostConsentResponseJson( + consentId = "1234-wertiq-983", + consentStatus = "received", + _links = ConsentLinksV13(Some(Href("/v1.3/consents/1234-wertiq-983/authorisations"))) + ), + List(AuthenticatedUserIsRequired, UnknownError), + ApiTag("Account Information Service (AIS)") :: apiTagBerlinGroupM :: Nil, + authMode = UserOrApplication, + http4sPartialFunction = Some(createConsent) + ) + + resourceDocs += ResourceDoc( + null, + implementedInApiVersion, + nameOf(deleteConsent), + "DELETE", + "/consents/CONSENTID", + "Delete Consent", + s"""${mockedDataText(false)} + The TPP can delete an account information consent object if needed.""", + EmptyBody, + EmptyBody, + List(AuthenticatedUserIsRequired, UnknownError), + ApiTag("Account Information Service (AIS)") :: apiTagBerlinGroupM :: Nil, + authMode = UserOrApplication, + http4sPartialFunction = Some(deleteConsent) + ) + + resourceDocs += ResourceDoc( + null, + implementedInApiVersion, + nameOf(getConsentInformation), + "GET", + "/consents/CONSENTID", + "Get Consent Request", + s"""${mockedDataText(false)} +Returns the content of an account information consent object. +This is returning the data for the TPP especially in cases, +where the consent was directly managed between ASPSP and PSU e.g. in a re-direct SCA Approach. +""", + EmptyBody, + json.parse("""{ + "access": { + "accounts": [ + { + "bban": "BARC12345612345678", + "maskedPan": "123456xxxxxx1234", + "iban": "FR7612345987650123456789014", + "currency": "EUR", + "msisdn": "+49 170 1234567", + "pan": "5409050000000000" + } + ] + }, + "recurringIndicator": false, + "validUntil": "2020-12-31", + "frequencyPerDay": 4, + "combinedServiceIndicator": false, + "lastActionDate": "2019-06-30", + "consentStatus": "received" + }"""), + List(AuthenticatedUserIsRequired, ConsentNotFound, UnknownError), + ApiTag("Account Information Service (AIS)") :: apiTagBerlinGroupM :: Nil, + authMode = UserOrApplication, + http4sPartialFunction = Some(getConsentInformation) + ) + + resourceDocs += ResourceDoc( + null, + implementedInApiVersion, + nameOf(getConsentStatus), + "GET", + "/consents/CONSENTID/status", + "Consent status request", + s"""${mockedDataText(false)} + Read the status of an account information consent resource.""", + EmptyBody, + json.parse("""{ + "consentStatus": "received" + }"""), + List(AuthenticatedUserIsRequired, UnknownError), + ApiTag("Account Information Service (AIS)") :: apiTagBerlinGroupM :: Nil, + authMode = UserOrApplication, + http4sPartialFunction = Some(getConsentStatus) + ) + + val generalStartConsentAuthorisationSummary = + s"""${mockedDataText(false)} +Create an authorisation sub-resource and start the authorisation process of a consent. +The message might in addition transmit authentication and authorisation related data. +his method is iterated n times for a n times SCA authorisation in a corporate context, +each creating an own authorisation sub-endpoint for the corresponding PSU authorising the consent. +The ASPSP might make the usage of this access method unnecessary, since the related authorisation +resource will be automatically created by the ASPSP after the submission of the consent data with the +first POST consents call. The start authorisation process is a process which is needed for creating +a new authorisation or cancellation sub-resource. + +This applies in the following scenarios: * The ASPSP has indicated with an 'startAuthorisation' hyperlink +in the preceding Payment Initiation Response that an explicit start of the authorisation process is needed by the TPP. +The 'startAuthorisation' hyperlink can transport more information about data which needs to be uploaded by using +the extended forms. +* 'startAuthorisationWithPsuIdentfication', +* 'startAuthorisationWithPsuAuthentication' +* 'startAuthorisationWithEncryptedPsuAuthentication' +* 'startAuthorisationWithAuthentciationMethodSelection' +* The related payment initiation cannot yet be executed since a multilevel SCA is mandated. +* The ASPSP has indicated with an 'startAuthorisation' hyperlink in the preceding Payment Cancellation +Response that an explicit start of the authorisation process is needed by the TPP. + +The 'startAuthorisation' hyperlink can transport more information about data which needs to be uploaded by +using the extended forms as indicated above. +* The related payment cancellation request cannot be applied yet since a multilevel SCA is mandate for executing the cancellation. +* The signing basket needs to be authorised yet. + +""" + + val startConsentAuthorisationResponse = json.parse("""{ + "scaStatus": "received", + "psuMessage": "Please use your BankApp for transaction Authorisation.", + "authorisationId": "123auth456.", + "_links": + { + "scaStatus": {"href":"/v1.3/consents/qwer3456tzui7890/authorisations/123auth456"} + } + }""") + + resourceDocs += ResourceDoc( + null, + implementedInApiVersion, + "startConsentAuthorisationTransactionAuthorisation", + "POST", + "/consents/CONSENTID/authorisations", + "Start the authorisation process for a consent(transactionAuthorisation)", + generalStartConsentAuthorisationSummary, + json.parse("""{"scaAuthenticationData":""}"""), + startConsentAuthorisationResponse, + List(AuthenticatedUserIsRequired, UnknownError), + ApiTag("Account Information Service (AIS)") :: apiTagBerlinGroupM :: Nil, + http4sPartialFunction = Some(startConsentAuthorisationAll) + ) + + resourceDocs += ResourceDoc( + null, + implementedInApiVersion, + "startConsentAuthorisationUpdatePsuAuthentication", + "POST", + "/consents/CONSENTID/authorisations", + "Start the authorisation process for a consent(updatePsuAuthentication)", + generalStartConsentAuthorisationSummary, + json.parse("""{"psuData": {"password": "start12"}}"""), + startConsentAuthorisationResponse, + List(AuthenticatedUserIsRequired, UnknownError), + ApiTag("Account Information Service (AIS)") :: apiTagBerlinGroupM :: Nil, + http4sPartialFunction = Some(startConsentAuthorisationAll) + ) + + resourceDocs += ResourceDoc( + null, + implementedInApiVersion, + "startConsentAuthorisationSelectPsuAuthenticationMethod", + "POST", + "/consents/CONSENTID/authorisations", + "Start the authorisation process for a consent(selectPsuAuthenticationMethod)", + generalStartConsentAuthorisationSummary, + json.parse("""{"authenticationMethodId":""}"""), + startConsentAuthorisationResponse, + List(AuthenticatedUserIsRequired, UnknownError), + ApiTag("Account Information Service (AIS)") :: apiTagBerlinGroupM :: Nil, + http4sPartialFunction = Some(startConsentAuthorisationAll) + ) + + val generalUpdateConsentsPsuDataSummary = + s"""${mockedDataText(false)} +This method update PSU data on the consents resource if needed. It may authorise a consent within the Embedded +SCA Approach where needed. Independently from the SCA Approach it supports +e.g. the selection of the authentication method and a non-SCA PSU authentication. +This methods updates PSU data on the cancellation authorisation resource if needed. +There are several possible Update PSU Data requests in the context of a consent request if needed, +which depends on the SCA approach: * Redirect SCA Approach: A specific Update PSU Data Request is applicable +for +* the selection of authentication methods, before choosing the actual SCA approach. +* Decoupled SCA Approach: A specific Update PSU Data Request is only applicable for +* adding the PSU Identification, if not provided yet in the Payment Initiation Request or the Account Information Consent Request, +or if no OAuth2 access token is used, or +* the selection of authentication methods. +* Embedded SCA Approach: The Update PSU Data Request might be used +* to add credentials as a first factor authentication data of the PSU and +* to select the authentication method and +* transaction authorisation. +The SCA Approach might depend on the chosen SCA method. For that reason, +the following possible Update PSU Data request can apply to all SCA approaches: +* Select an SCA method in case of several SCA methods are available for the customer. There are the following request types on this access path: +* Update PSU Identification * Update PSU Authentication +* Select PSU Autorization Method WARNING: This method need a reduced header, therefore many optional elements are not present. +Maybe in a later version the access path will change. +* Transaction Authorisation WARNING: This method need a reduced header, therefore many optional elements are not present. +Maybe in a later version the access path will change. + + """ + + resourceDocs += ResourceDoc( + null, + implementedInApiVersion, + "updateConsentsPsuDataTransactionAuthorisation", + "PUT", + "/consents/CONSENTID/authorisations/AUTHORISATIONID", + "Update PSU Data for consents (transactionAuthorisation)", + generalUpdateConsentsPsuDataSummary, + json.parse("""{"scaAuthenticationData":"123"}"""), + ScaStatusResponse( + scaStatus = "received", + _links = Some(LinksAll(scaStatus = Some(HrefType(Some(s"/v1.3/consents/1234-wertiq-983/authorisations"))))) + ), + List(AuthenticatedUserIsRequired, UnknownError), + ApiTag("Account Information Service (AIS)") :: apiTagBerlinGroupM :: Nil, + http4sPartialFunction = Some(updateConsentsPsuDataAll) + ) + + resourceDocs += ResourceDoc( + null, + implementedInApiVersion, + "updateConsentsPsuDataUpdatePsuAuthentication", + "PUT", + "/consents/CONSENTID/authorisations/AUTHORISATIONID", + "Update PSU Data for consents (updatePsuAuthentication)", + generalUpdateConsentsPsuDataSummary, + json.parse("""{"psuData": {"password": "start12"}}""".stripMargin), + json.parse("""{ + | "scaStatus": "psuAuthenticated", + | "_links": { + | "authoriseTransaction": {"href": "/psd2/v1/payments/1234-wertiq-983/authorisations/123auth456"} + | } + | }""".stripMargin), + List(AuthenticatedUserIsRequired, UnknownError), + ApiTag("Account Information Service (AIS)") :: apiTagBerlinGroupM :: Nil, + http4sPartialFunction = Some(updateConsentsPsuDataAll) + ) + + resourceDocs += ResourceDoc( + null, + implementedInApiVersion, + "updateConsentsPsuDataUpdateSelectPsuAuthenticationMethod", + "PUT", + "/consents/CONSENTID/authorisations/AUTHORISATIONID", + "Update PSU Data for consents (selectPsuAuthenticationMethod)", + generalUpdateConsentsPsuDataSummary, + json.parse("""{ + | "authenticationMethodId": "myAuthenticationID" + |}""".stripMargin), + json.parse("""{ + | "scaStatus": "scaMethodSelected", + | "chosenScaMethod": { + | "authenticationType": "SMS_OTP", + | "authenticationMethodId": "myAuthenticationID"}, + | "challengeData": { + | "otpMaxLength": 6, + | "otpFormat": "integer"}, + | "_links": { + | "authoriseTransaction": {"href": "/psd2/v1/payments/1234-wertiq-983/authorisations/123auth456"} + | } + | }""".stripMargin), + List(AuthenticatedUserIsRequired, UnknownError), + ApiTag("Account Information Service (AIS)") :: apiTagBerlinGroupM :: Nil, + http4sPartialFunction = Some(updateConsentsPsuDataAll) + ) + + resourceDocs += ResourceDoc( + null, + implementedInApiVersion, + "updateConsentsPsuDataUpdateAuthorisationConfirmation", + "PUT", + "/consents/CONSENTID/authorisations/AUTHORISATIONID", + "Update PSU Data for consents (authorisationConfirmation)", + generalUpdateConsentsPsuDataSummary, + json.parse("""{"confirmationCode":"confirmationCode"}"""), + json.parse("""{ + | "scaStatus": "finalised", + | "_links":{ + | "status": {"href":"/v1/payments/sepa-credit-transfers/qwer3456tzui7890/status"} + | } + | }""".stripMargin), + List(AuthenticatedUserIsRequired, UnknownError), + ApiTag("Account Information Service (AIS)") :: apiTagBerlinGroupM :: Nil, + http4sPartialFunction = Some(updateConsentsPsuDataAll) + ) + } + + private def initAccountResourceDocs(): Unit = { + + resourceDocs += ResourceDoc( + null, + implementedInApiVersion, + nameOf(getAccountList), + "GET", + "/accounts", + "Read Account List", + s"""${mockedDataText(false)} +Read the identifiers of the available payment account together with +booking balance information, depending on the consent granted. + +It is assumed that a consent of the PSU to this access is already given and stored on the ASPSP system. +The addressed list of accounts depends then on the PSU ID and the stored consent addressed by consentId, +respectively the OAuth2 access token. + +Returns all identifiers of the accounts, to which an account access has been granted to through +the /consents endpoint by the PSU. +In addition, relevant information about the accounts and hyperlinks to corresponding account +information resources are provided if a related consent has been already granted. + +Remark: Note that the /consents endpoint optionally offers to grant an access on all available +payment accounts of a PSU. +In this case, this endpoint will deliver the information about all available payment accounts +of the PSU at this ASPSP. +""", + EmptyBody, + json.parse("""{ + | "accounts": [ + | { + | "resourceId": "3dc3d5b3-7023-4848-9853-f5400a64e80f", + | "iban": "DE2310010010123456789", + | "currency": "EUR", + | "product": "Girokonto", + | "cashAccountType": "CACC", + | "name": "Main Account", + | "_links": { + | "balances": { + | "href": "/v1/accounts/3dc3d5b3-7023-4848-9853-f5400a64e80f/balances" + | } + | } + | } + | ] + |}""".stripMargin), + List(AuthenticatedUserIsRequired, UnknownError), + ApiTag("Account Information Service (AIS)") :: apiTagBerlinGroupM :: Nil, + http4sPartialFunction = Some(getAccountList) + ) + + resourceDocs += ResourceDoc( + null, + implementedInApiVersion, + nameOf(getBalances), + "GET", + "/accounts/ACCOUNT_ID/balances", + "Read Balance", + s"""${mockedDataText(false)} +Reads account data from a given account addressed by "account-id". + +**Remark:** This account-id can be a tokenised identification due to data protection reason since the path +information might be logged on intermediary servers within the ASPSP sphere. +This account-id then can be retrieved by the "GET Account List" call. + +The account-id is constant at least throughout the lifecycle of a given consent. +""", + EmptyBody, + json.parse("""{ + "account":{ + "iban":"DE91 1000 0000 0123 4567 89" + }, + "balances":[{ + "balanceAmount":{ + "currency":"EUR", + "amount":"50.89" + }, + "balanceType":"AC", + "lastChangeDateTime":"yyyy-MM-dd'T'HH:mm:ss.SSSZ", + "lastCommittedTransaction":"String", + "referenceDate":"2018-03-08" + }] +} +"""), + List(AuthenticatedUserIsRequired, UnknownError), + ApiTag("Account Information Service (AIS)") :: apiTagBerlinGroupM :: Nil, + http4sPartialFunction = Some(getBalances) + ) + + resourceDocs += ResourceDoc( + null, + implementedInApiVersion, + nameOf(getCardAccounts), + "GET", + "/card-accounts", + "Reads a list of card accounts", + s"""${mockedDataText(false)} +Reads a list of card accounts with additional information, e.g. balance information. +It is assumed that a consent of the PSU to this access is already given and stored on the ASPSP system. +The addressed list of card accounts depends then on the PSU ID and the stored consent addressed by consentId, +respectively the OAuth2 access token. +""", + EmptyBody, + json.parse("""{ + "cardAccounts": [ + { + "resourceId": "3d9a81b3-a47d-4130-8765-a9c0ff861b99", + "maskedPan": "525412******3241", + "currency": "EUR", + "name": "Main", + "product": "Basic Credit", + "status": "enabled", + "creditLimit": { + "currency": "EUR", + "amount": 15000 + }, + "_links": { + "balances": { + "href": "/v1/card-accounts/3d9a81b3-a47d-4130-8765-a9c0ff861b99/balances" + } + } + } + ] +}"""), + List(AuthenticatedUserIsRequired, UnknownError), + ApiTag("Account Information Service (AIS)") :: apiTagMockedData :: Nil, + http4sPartialFunction = Some(getCardAccounts) + ) + + resourceDocs += ResourceDoc( + null, + implementedInApiVersion, + nameOf(getCardAccountBalances), + "GET", + "/card-accounts/ACCOUNT_ID/balances", + "Read card account balances", + s"""${mockedDataText(false)} +Reads balance data from a given card account addressed by +"account-id". + +Remark: This account-id can be a tokenised identification due +to data protection reason since the path information might be +logged on intermediary servers within the ASPSP sphere. +This account-id then can be retrieved by the +"GET Card Account List" call +""", + EmptyBody, + json.parse("""{ + "cardAccount":{ + "iban":"DE91 1000 0000 0123 4567 89" + }, + "balances":[{ + "balanceAmount":{ + "currency":"EUR", + "amount":"50.89" + }, + "balanceType":"AC", + "lastChangeDateTime":"yyyy-MM-dd'T'HH:mm:ss.SSSZ", + "lastCommittedTransaction":"String", + "referenceDate":"2018-03-08" + }] +}"""), + List(AuthenticatedUserIsRequired, UnknownError), + ApiTag("Account Information Service (AIS)") :: Nil, + http4sPartialFunction = Some(getCardAccountBalances) + ) + + resourceDocs += ResourceDoc( + null, + implementedInApiVersion, + nameOf(getCardAccountTransactionList), + "GET", + "/card-accounts/ACCOUNT_ID/transactions", + "Read transaction list of a card account", + s"""${mockedDataText(false)} +Reads account data from a given card account addressed by "account-id". +""", + EmptyBody, + json.parse("""{ + "cardAccount": { + "maskedPan": "525412******3241" + }, + "transactions": { + "booked": [], + "_links": { + "cardAccount": { + "href": "/v1.3/card-accounts/3d9a81b3-a47d-4130-8765-a9c0ff861b99" + } + } + } + }"""), + List(AuthenticatedUserIsRequired, UnknownError), + ApiTag("Account Information Service (AIS)") :: apiTagBerlinGroupM :: Nil, + http4sPartialFunction = Some(getCardAccountTransactionList) + ) + + resourceDocs += ResourceDoc( + null, + implementedInApiVersion, + nameOf(getConsentAuthorisation), + "GET", + "/consents/CONSENTID/authorisations", + "Get Consent Authorisation Sub-Resources Request", + s"""${mockedDataText(false)} +Return a list of all authorisation subresources IDs which have been created. + +This function returns an array of hyperlinks to all generated authorisation sub-resources. +""", + EmptyBody, + json.parse("""{ + "authorisationIds" : "faa3657e-13f0-4feb-a6c3-34bf21a9ae8e" +}"""), + List(AuthenticatedUserIsRequired, UnknownError), + ApiTag("Account Information Service (AIS)") :: apiTagBerlinGroupM :: Nil, + http4sPartialFunction = Some(getConsentAuthorisation) + ) + + resourceDocs += ResourceDoc( + null, + implementedInApiVersion, + nameOf(getConsentScaStatus), + "GET", + "/consents/CONSENTID/authorisations/AUTHORISATIONID", + "Read the SCA status of the consent authorisation", + s"""${mockedDataText(false)} +This method returns the SCA status of a consent initiation's authorisation sub-resource. +""", + EmptyBody, + json.parse("""{ + "scaStatus" : "started" +}"""), + List(AuthenticatedUserIsRequired, UnknownError), + ApiTag("Account Information Service (AIS)") :: apiTagBerlinGroupM :: Nil, + http4sPartialFunction = Some(getConsentScaStatus) + ) + + resourceDocs += ResourceDoc( + null, + implementedInApiVersion, + nameOf(getTransactionDetails), + "GET", + "/accounts/ACCOUNT_ID/transactions/TRANSACTIONID", + "Read Transaction Details", + s"""${mockedDataText(false)} +Reads transaction details from a given transaction addressed by "transactionId" on a given account addressed +by "account-id". This call is only available on transactions as reported in a JSON format. + +**Remark:** Please note that the PATH might be already given in detail by the corresponding entry of the response +of the "Read Transaction List" call within the _links subfield. + + """, + EmptyBody, + json.parse("""{ + "description": "Example for transaction details", + "value": { + "transactionsDetails": { + "transactionId": "1234567", + "creditorName": "John Miles", + "transactionAmount": { + "currency": "EUR", + "amount": "-256.67" + }, + "bookingDate": "2017-10-25", + "valueDate": "2017-10-26" + } + } +}"""), + List(AuthenticatedUserIsRequired, UnknownError), + ApiTag("Account Information Service (AIS)") :: Nil, + http4sPartialFunction = Some(getTransactionDetails) + ) + + resourceDocs += ResourceDoc( + null, + implementedInApiVersion, + nameOf(getTransactionList), + "GET", + "/accounts/ACCOUNT_ID/transactions", + "Read transaction list of an account", + s"""${mockedDataText(false)} +Read transaction reports or transaction lists of a given account addressed by "account-id", +depending on the steering parameter "bookingStatus" together with balances. +For a given account, additional parameters are e.g. the attributes "dateFrom" and "dateTo". +The ASPSP might add balance information, if transaction lists without balances are not supported. """, + EmptyBody, + json.parse("""{ + "account": { + "iban": "DE2310010010123456788" + }, + "transactions": { + "booked": [], + "_links": { + "account": { + "href": "/v1.3/accounts/3dc3d5b3-7023-4848-9853-f5400a64e80f" + } + } + } + }"""), + List(AuthenticatedUserIsRequired, UnknownError), + ApiTag("Account Information Service (AIS)") :: apiTagBerlinGroupM :: Nil, + http4sPartialFunction = Some(getTransactionList) + ) + + resourceDocs += ResourceDoc( + null, + implementedInApiVersion, + nameOf(getAccountDetails), + "GET", + "/accounts/ACCOUNT_ID", + "Read Account Details", + s"""${mockedDataText(false)} +Reads details about an account, 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. +The addressed details of this account depends then on the stored consent addressed by consentId, +respectively the OAuth2 access token. **NOTE:** The account-id can represent a multicurrency account. +In this case the currency code is set to "XXX". Give detailed information about the addressed account. +Give detailed information about the addressed account together with balance information + + """, + EmptyBody, + json.parse("""{ + "account": { + "resourceId": "3dc3d5b3-7023-4848-9853-f5400a64e80f", + "iban": "FR7612345987650123456789014", + "currency": "EUR", + "product": "Girokonto", + "cashAccountType": "CACC", + "name": "Main Account", + "_links": { + "balances": { + "href": "/v1/accounts/3dc3d5b3-7023-4848-9853-f5400a64e80f/balances" + } + } + } +}"""), + List(AuthenticatedUserIsRequired, UnknownError), + ApiTag("Account Information Service (AIS)") :: apiTagBerlinGroupM :: Nil, + http4sPartialFunction = Some(getAccountDetails) + ) + + resourceDocs += ResourceDoc( + null, + implementedInApiVersion, + nameOf(readCardAccount), + "GET", + "/card-accounts/ACCOUNT_ID", + "Reads details about a card account", + s"""${mockedDataText(false)} +Reads details about a card account. +It is assumed that a consent of the PSU to this access is already given and stored on the ASPSP system. +The addressed details of this account depends then on the stored consent addressed by consentId, +respectively the OAuth2 access token. +""", + EmptyBody, + json.parse("""{ + | "cardAccount": { + | "resourceId": "3d9a81b3-a47d-4130-8765-a9c0ff861b99", + | "maskedPan": "525412******3241", + | "currency": "EUR", + | "name": "Main", + | "product": "Basic Credit", + | "status": "enabled" + | } + |}""".stripMargin), + List(AuthenticatedUserIsRequired, UnknownError), + ApiTag("Account Information Service (AIS)") :: Nil, + http4sPartialFunction = Some(readCardAccount) + ) + } + + initConsentResourceDocs() + initAccountResourceDocs() + + val routes: HttpRoutes[IO] = Kleisli[HttpF, Request[IO], Response[IO]] { req => + createConsent(req) + .orElse(deleteConsent(req)) + .orElse(getAccountList(req)) + .orElse(getBalances(req)) + .orElse(getCardAccounts(req)) + .orElse(getCardAccountBalances(req)) + .orElse(getCardAccountTransactionList(req)) + .orElse(getConsentAuthorisation(req)) + .orElse(getConsentInformation(req)) + .orElse(getConsentScaStatus(req)) + .orElse(getConsentStatus(req)) + .orElse(getTransactionDetails(req)) + .orElse(getTransactionList(req)) + .orElse(getAccountDetails(req)) + .orElse(readCardAccount(req)) + .orElse(startConsentAuthorisationAll(req)) + .orElse(updateConsentsPsuDataAll(req)) + } +} diff --git a/obp-api/src/main/scala/code/api/berlin/group/v1_3/Http4sBGv13Alias.scala b/obp-api/src/main/scala/code/api/berlin/group/v1_3/Http4sBGv13Alias.scala new file mode 100644 index 0000000000..029e5a8eda --- /dev/null +++ b/obp-api/src/main/scala/code/api/berlin/group/v1_3/Http4sBGv13Alias.scala @@ -0,0 +1,85 @@ +package code.api.berlin.group.v1_3 + +import cats.data.OptionT +import cats.effect._ +import code.api.berlin.group.ConstantsBG +import code.api.util.APIUtil.{ResourceDoc, berlinGroupV13AliasPath} +import code.util.Helper.MdcLoggable +import com.openbankproject.commons.util.ScannedApiVersion +import org.http4s._ + +import scala.collection.mutable.ArrayBuffer + +/** + * http4s alias bridge for Berlin Group v1.3. + * + * When the `berlin_group_v1_3_alias_path` prop is set (e.g. "my-bank/v1.3"), + * requests arriving at the alias prefix are path-rewritten to the canonical + * `/berlin-group/v1.3/...` prefix and delegated to `Http4sBGv13.wrappedRoutes`. + * This mirrors the behaviour of the Lift `OBP_BERLIN_GROUP_1_3_Alias` aggregator. + * + * When the prop is absent/empty, `wrappedRoutes` is `HttpRoutes.empty` (no-op). + * + * ResourceDocs are the same as the canonical BG v1.3 docs, re-stamped with the + * alias `implementedInApiVersion` so they appear under the alias version in the + * resource-docs endpoint. + */ +object Http4sBGv13Alias extends MdcLoggable { + + /** The alias ScannedApiVersion, matching OBP_BERLIN_GROUP_1_3_Alias.apiVersion. */ + val aliasVersion: ScannedApiVersion = + if (berlinGroupV13AliasPath.nonEmpty) + ScannedApiVersion(berlinGroupV13AliasPath.head, berlinGroupV13AliasPath.head, berlinGroupV13AliasPath.last) + else + ConstantsBG.berlinGroupVersion1 // inactive; value unused + + /** + * ResourceDocs for the alias: the canonical BG v1.3 docs with + * `implementedInApiVersion` overridden to the alias version. + * Empty when the alias is not configured. + */ + val resourceDocs: ArrayBuffer[ResourceDoc] = + if (berlinGroupV13AliasPath.nonEmpty) + Http4sBGv13.resourceDocs.map(doc => + doc.copy(implementedInApiVersion = + aliasVersion.copy(apiStandard = doc.implementedInApiVersion.apiStandard))) + else + ArrayBuffer.empty[ResourceDoc] + + // e.g. "/berlin-group/v1.3" + private val canonicalPrefixStr: String = + s"/${ConstantsBG.berlinGroupVersion1.urlPrefix}/${ConstantsBG.berlinGroupVersion1.apiShortVersion}" + + // e.g. "/my-bank-group/v1.3" (empty string when alias not configured) + private val aliasPrefixStr: String = + if (berlinGroupV13AliasPath.nonEmpty) "/" + berlinGroupV13AliasPath.mkString("/") + else "" + + /** + * Path-rewriting bridge routes. + * + * For each request whose path starts with the alias prefix: + * 1. Strip the alias prefix. + * 2. Prepend the canonical BG v1.3 prefix. + * 3. Delegate the rewritten request to `Http4sBGv13.wrappedRoutes`. + * + * Falls through (`OptionT.none`) for paths that do not start with the alias + * prefix, and is `HttpRoutes.empty` when the alias is not configured. + */ + val wrappedRoutes: HttpRoutes[IO] = + if (berlinGroupV13AliasPath.nonEmpty) { + HttpRoutes[IO] { req => + val pathStr = req.uri.path.renderString + if (pathStr.startsWith(aliasPrefixStr)) { + val remainder = pathStr.substring(aliasPrefixStr.length) // "" or "/..." + val rewrittenPath = Uri.Path.unsafeFromString(canonicalPrefixStr + remainder) + val rewrittenReq = req.withUri(req.uri.copy(path = rewrittenPath)) + Http4sBGv13.wrappedRoutes.run(rewrittenReq) + } else { + OptionT.none + } + } + } else { + HttpRoutes.empty[IO] + } +} diff --git a/obp-api/src/main/scala/code/api/berlin/group/v1_3/Http4sBGv13PIIS.scala b/obp-api/src/main/scala/code/api/berlin/group/v1_3/Http4sBGv13PIIS.scala new file mode 100644 index 0000000000..3af72af80c --- /dev/null +++ b/obp-api/src/main/scala/code/api/berlin/group/v1_3/Http4sBGv13PIIS.scala @@ -0,0 +1,122 @@ +package code.api.berlin.group.v1_3 + +import cats.data.{Kleisli, OptionT} +import cats.effect._ +import code.api.berlin.group.ConstantsBG +import code.api.berlin.group.v1_3.JSONFactory_BERLIN_GROUP_1_3._ +import code.api.util.APIUtil._ +import code.api.util.ApiTag._ +import code.api.util.ErrorMessages._ +import code.api.util.CustomJsonFormats +import code.api.util.{ApiTag, NewStyle} +import code.api.util.http4s.Http4sRequestAttributes.{EndpointHelpers, RequestOps} +import code.fx.fx +import code.util.Helper +import code.util.Helper.MdcLoggable +import com.github.dwickern.macros.NameOf.nameOf +import com.openbankproject.commons.ExecutionContext.Implicits.global +import net.liftweb.json +import net.liftweb.json.Formats +import org.http4s._ +import org.http4s.dsl.io._ + +import scala.collection.mutable.ArrayBuffer +import scala.language.implicitConversions + +object Http4sBGv13PIIS extends MdcLoggable { + + type HttpF[A] = OptionT[IO, A] + + implicit val formats: Formats = CustomJsonFormats.formats + + // ResourceDoc example bodies are written as `json.parse(...)` (JValue); ResourceDoc requires + // scala.Product, so wrap via the same implicit the Lift builder used (JvalueCaseClass is a Product). + protected implicit def JvalueToSuper(what: net.liftweb.json.JValue): JvalueCaseClass = JvalueCaseClass(what) + + val implementedInApiVersion = ConstantsBG.berlinGroupVersion1 + val resourceDocs = ArrayBuffer[ResourceDoc]() + + val bgV13Prefix = Root / ConstantsBG.berlinGroupVersion1.urlPrefix / ConstantsBG.berlinGroupVersion1.apiShortVersion + + // ── POST /funds-confirmations ───────────────────────────────────── + // Lift source: code.api.builder.ConfirmationOfFundsServicePIISApi.checkAvailabilityOfFunds + // Auth: authenticatedAccess → authMode UserOnly (default); user resolved by ResourceDocMiddleware. + val checkAvailabilityOfFunds: HttpRoutes[IO] = HttpRoutes.of[IO] { + case req @ POST -> `bgV13Prefix` / "funds-confirmations" => + EndpointHelpers.executeAndRespond(req) { cc => + val callContext = Some(cc) + for { + _ <- passesPsd2Aisp(callContext) + checkAvailabilityOfFundsJson <- NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the $CheckAvailabilityOfFundsJson ", 400, callContext) { + json.parse(cc.httpBody.getOrElse("")).extract[CheckAvailabilityOfFundsJson] + } + + requestAccountAmount <- NewStyle.function.tryons(s"$InvalidNumber Current input is ${checkAvailabilityOfFundsJson.instructedAmount.amount} ", 400, callContext) { + BigDecimal(checkAvailabilityOfFundsJson.instructedAmount.amount) + } + + requestAccountCurrency = checkAvailabilityOfFundsJson.instructedAmount.currency + + _ <- Helper.booleanToFuture(s"${InvalidISOCurrencyCode} Current input is: '${requestAccountCurrency}'", cc = callContext) { + isValidCurrencyISOCode(requestAccountCurrency) + } + + requestAccountIban = checkAvailabilityOfFundsJson.account.iban + (bankAccount, callContext) <- NewStyle.function.getBankAccountByIban(requestAccountIban, callContext) + currentAccountCurrency = bankAccount.currency + currentAccountBalance = bankAccount.balance + + // From change from requestAccount Currency to currentBankAccount Currency + rate = fx.exchangeRate(requestAccountCurrency, currentAccountCurrency, Some(bankAccount.bankId.value), callContext) + + _ <- Helper.booleanToFuture(s"$InvalidCurrency The requested currency conversion (${requestAccountCurrency} to ${currentAccountCurrency}) is not supported.", cc = callContext) { + rate.isDefined + } + + requestChangedCurrencyAmount = fx.convert(requestAccountAmount, rate) + + fundsAvailable = (currentAccountBalance >= requestChangedCurrencyAmount) + + } yield { + net.liftweb.json.parse(s"""{ + "fundsAvailable" : $fundsAvailable + }""") + } + } + } + + resourceDocs += ResourceDoc( + null, + implementedInApiVersion, + nameOf(checkAvailabilityOfFunds), + "POST", + "/funds-confirmations", + "Confirmation of Funds Request", + s""" ${mockedDataText(false)} +Creates a confirmation of funds request at the ASPSP. Checks whether a specific amount is available at point +of time of the request on an account linked to a given tuple card issuer(TPP)/card number, or addressed by +IBAN and TPP respectively. If the related extended services are used a conditional Consent-ID is contained +in the header. This field is contained but commented out in this specification. """, + json.parse( + """{ + "instructedAmount" : { + "amount" : "123", + "currency" : "EUR" + }, + "account" : { + "iban" : "GR12 1234 5123 4511 3981 4475 477", + } + }"""), + json.parse( + """{ + "fundsAvailable" : true + }"""), + List(AuthenticatedUserIsRequired, UnknownError), + ApiTag("Confirmation of Funds Service (PIIS)") :: apiTagBerlinGroupM :: Nil, + http4sPartialFunction = Some(checkAvailabilityOfFunds) + ) + + val routes: HttpRoutes[IO] = Kleisli[HttpF, Request[IO], Response[IO]] { req => + checkAvailabilityOfFunds(req) + } +} diff --git a/obp-api/src/main/scala/code/api/berlin/group/v1_3/Http4sBGv13PIS.scala b/obp-api/src/main/scala/code/api/berlin/group/v1_3/Http4sBGv13PIS.scala new file mode 100644 index 0000000000..43993fa87d --- /dev/null +++ b/obp-api/src/main/scala/code/api/berlin/group/v1_3/Http4sBGv13PIS.scala @@ -0,0 +1,1257 @@ +package code.api.berlin.group.v1_3 + +import cats.data.{Kleisli, OptionT} +import cats.effect._ +import code.api.berlin.group.ConstantsBG +import code.api.berlin.group.v1_3.JSONFactory_BERLIN_GROUP_1_3._ +import code.api.berlin.group.v1_3.model.TransactionStatus.mapTransactionStatus +import code.api.berlin.group.v1_3.model._ +import code.api.util.APIUtil.{EmptyBody, ResourceDoc, UserOrApplication, getScaMethodAtInstance, getServerUrl, isValidCurrencyISOCode, mockedDataText, passesPsd2Pisp} +import code.api.util.ApiTag._ +import code.api.util.ErrorMessages._ +import code.api.util.CustomJsonFormats +import code.api.util.{ApiTag, CallContext, NewStyle} +import code.api.util.http4s.Http4sRequestAttributes.{EndpointHelpers, RequestOps} +import code.api.util.http4s.{ErrorResponseConverter, RequestScopeConnection} +import code.fx.fx +import code.util.Helper +import code.util.Helper.{MdcLoggable, booleanToFuture} +import com.github.dwickern.macros.NameOf.nameOf +import com.openbankproject.commons.ExecutionContext.Implicits.global +import com.openbankproject.commons.model._ +import com.openbankproject.commons.model.enums.TransactionRequestStatus._ +import com.openbankproject.commons.model.enums.TransactionRequestTypes._ +import com.openbankproject.commons.model.enums.{ChallengeType, PaymentServiceTypes, StrongCustomerAuthenticationStatus, SuppliedAnswerType, TransactionRequestStatus, TransactionRequestTypes} +import net.liftweb.common.Box.tryo +import net.liftweb.common.Full +import net.liftweb.json +import net.liftweb.json.Formats +import net.liftweb.json.JsonAST.prettyRender +import net.liftweb.json.{Extraction => LiftExtraction} +import org.http4s._ +import org.http4s.dsl.io._ + +import scala.collection.mutable.ArrayBuffer +import scala.concurrent.Future +import scala.language.implicitConversions + +/** + * Native http4s aggregator for Berlin Group v1.3 – Payment Initiation Service (PIS). + * Ports all 24 PIS endpoints from code.api.builder.PaymentInitiationServicePISApi. + * Route handlers declared as lazy val (avoids 64KB limit for large objects). + */ +object Http4sBGv13PIS extends MdcLoggable { + + type HttpF[A] = OptionT[IO, A] + + implicit val formats: Formats = CustomJsonFormats.formats + + protected implicit def JvalueToSuper(what: net.liftweb.json.JValue): JvalueCaseClass = JvalueCaseClass(what) + + val implementedInApiVersion = ConstantsBG.berlinGroupVersion1 + val resourceDocs = ArrayBuffer[ResourceDoc]() + + val bgV13Prefix: Path = + Root / ConstantsBG.berlinGroupVersion1.urlPrefix / ConstantsBG.berlinGroupVersion1.apiShortVersion + + // ── private helpers (ported from APIMethods_PaymentInitiationServicePISApi) ─ + + private def checkPaymentServerTypeError(paymentService: String) = { + s"${InvalidTransactionRequestType.replaceAll("TRANSACTION_REQUEST_TYPE", "PAYMENT_SERVICE in the URL.")}: '${paymentService}'.It should be `payments` or `periodic-payments` for now, will support `bulk-payments` soon" + } + + private def checkPaymentProductError(paymentProduct: String) = + s"${InvalidTransactionRequestType.replaceAll("TRANSACTION_REQUEST_TYPE", "PAYMENT_PRODUCT in the URL.")}: '${paymentProduct}'.It should be `sepa-credit-transfers`for now, will support (instant-sepa-credit-transfers, target-2-payments, cross-border-credit-transfers) soon." + + private def checkPaymentServiceType(paymentService: String) = tryo { + PaymentServiceTypes.withName(paymentService.replaceAll("-", "_")) + }.isDefined + + /** + * Shared business logic for all three initiate-payment variants (payments / periodic-payments / + * bulk-payments). Mirrors `initiatePaymentImplementation` from the Lift builder; auth is handled + * by middleware (authMode = UserOrApplication), so no inline applicationAccess call. + */ + private def initiatePaymentImpl( + paymentService: String, + paymentProduct: String, + callContext: Option[CallContext] + ): Future[net.liftweb.json.JValue] = { + val u = callContext.flatMap(_.user.toOption) + val rawBody = callContext.flatMap(_.httpBody).getOrElse("") + val bodyJson = scala.util.Try(json.parse(rawBody)).getOrElse(json.JNothing) + for { + _ <- passesPsd2Pisp(callContext) + paymentServiceType <- NewStyle.function.tryons(checkPaymentServerTypeError(paymentService), 404, callContext) { + PaymentServiceTypes.withName(paymentService.replaceAll("-", "_")) + } + transactionRequestType <- NewStyle.function.tryons(checkPaymentProductError(paymentProduct), 404, callContext) { + TransactionRequestTypes.withName(paymentProduct.replaceAll("-", "_").toUpperCase) + } + sepaCreditTransfersBerlinGroupV13 <- if (paymentServiceType.equals(PaymentServiceTypes.payments)) { + NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the $SepaCreditTransfersBerlinGroupV13 ", 400, callContext) { + bodyJson.extract[SepaCreditTransfersBerlinGroupV13] + } + } else if (paymentServiceType.equals(PaymentServiceTypes.periodic_payments)) { + NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the $PeriodicSepaCreditTransfersBerlinGroupV13 ", 400, callContext) { + bodyJson.extract[PeriodicSepaCreditTransfersBerlinGroupV13] + } + } else { + Future { throw new RuntimeException(checkPaymentServerTypeError(paymentServiceType.toString)) } + } + isValidAmountNumber <- NewStyle.function.tryons(s"$InvalidNumber Current input is ${sepaCreditTransfersBerlinGroupV13.instructedAmount.amount} ", 400, callContext) { + BigDecimal(sepaCreditTransfersBerlinGroupV13.instructedAmount.amount) + } + _ <- Helper.booleanToFuture(s"${NotPositiveAmount} Current input is: '${isValidAmountNumber}'", cc = callContext) { + isValidAmountNumber > BigDecimal("0") + } + _ <- Helper.booleanToFuture(s"${InvalidISOCurrencyCode} Current input is: '${sepaCreditTransfersBerlinGroupV13.instructedAmount.currency}'", cc = callContext) { + isValidCurrencyISOCode(sepaCreditTransfersBerlinGroupV13.instructedAmount.currency) + } + _ <- NewStyle.function.isEnabledTransactionRequests(callContext) + (createdTransactionRequest, _) <- transactionRequestType match { + case TransactionRequestTypes.SEPA_CREDIT_TRANSFERS => + NewStyle.function.createTransactionRequestBGV1( + initiator = u, + paymentServiceType, + transactionRequestType, + transactionRequestBody = sepaCreditTransfersBerlinGroupV13, + callContext + ) + } + } yield { + LiftExtraction.decompose(JSONFactory_BERLIN_GROUP_1_3.createTransactionRequestJson(createdTransactionRequest)) + } + } + + // ── DELETE /{paymentService}/{paymentProduct}/{paymentId} ────────────────────────────────────────────────────── + // Variable response: 202 (SCA required) with CancelPaymentResponseJson body, or 204 (direct cancel) with no body. + // Custom IO handler to produce truly-empty 204 (NoContent) — executeFutureWithStatus would always add a body. + lazy val cancelPayment: HttpRoutes[IO] = HttpRoutes.of[IO] { + case req @ DELETE -> `bgV13Prefix` / paymentService / paymentProduct / paymentId => + implicit val cc: CallContext = req.callContext + val callContext = Some(cc) + RequestScopeConnection.fromFuture { + val u = cc.user.openOrThrowException(AuthenticatedUserIsRequired) + for { + _ <- passesPsd2Pisp(callContext) + _ <- NewStyle.function.tryons(checkPaymentServerTypeError(paymentService), 404, callContext) { + PaymentServiceTypes.withName(paymentService.replaceAll("-", "_")) + } + transactionRequestTypes <- NewStyle.function.tryons(checkPaymentProductError(paymentProduct), 404, callContext) { + TransactionRequestTypes.withName(paymentProduct.replaceAll("-", "_").toUpperCase) + } + (transactionRequest, _) <- NewStyle.function.getTransactionRequestImpl(TransactionRequestId(paymentId), callContext) + transactionRequestBody <- NewStyle.function.tryons(s"${UnknownError} No data for Payment Body ", 400, callContext) { + transactionRequest.body.to_sepa_credit_transfers.get + } + fromAccountIban = transactionRequestBody.debtorAccount.iban + toAccountIban = transactionRequestBody.creditorAccount.iban + (_, _) <- NewStyle.function.getBankAccountByIban(fromAccountIban, callContext) + (ibanChecker, _) <- NewStyle.function.validateAndCheckIbanNumber(toAccountIban, callContext) + _ <- Helper.booleanToFuture(invalidIban, cc = callContext) { ibanChecker.isValid == true } + (_, _) <- NewStyle.function.getToBankAccountByIban(toAccountIban, callContext) + currentStatus = transactionRequest.status.toUpperCase() + mappedStatus = mapTransactionStatus(currentStatus) + (canBeCancelled, _, startSca) <- transactionRequestTypes match { + case TransactionRequestTypes.SEPA_CREDIT_TRANSFERS => + currentStatus match { + case TransactionStatus.RCVD.code | "INITIATED" => + NewStyle.function.saveTransactionRequestStatusImpl(transactionRequest.id, CANCELLED.toString, callContext) + Future.successful((true, callContext, Some(false))) + case TransactionStatus.ACCP.code | "COMPLETED" => + NewStyle.function.cancelPaymentV400(TransactionId(transactionRequest.transaction_ids), callContext) map { x => + x._1 match { + case CancelPayment(true, Some(startSca)) if startSca => + NewStyle.function.saveTransactionRequestStatusImpl(transactionRequest.id, CANCELLATION_PENDING.toString, callContext) + (true, x._2, Some(startSca)) + case CancelPayment(true, Some(startSca)) if !startSca => + NewStyle.function.saveTransactionRequestStatusImpl(transactionRequest.id, CANCELLED.toString, callContext) + (true, x._2, Some(startSca)) + case CancelPayment(false, _) => + (false, x._2, Some(false)) + } + } + case TransactionStatus.PDNG.code | "PENDING" => + NewStyle.function.cancelPaymentV400(TransactionId(transactionRequest.transaction_ids), callContext) map { x => + x._1 match { + case CancelPayment(true, Some(startSca)) if startSca => + NewStyle.function.saveTransactionRequestStatusImpl(transactionRequest.id, CANCELLATION_PENDING.toString, callContext) + (true, x._2, Some(startSca)) + case CancelPayment(true, Some(startSca)) if !startSca => + NewStyle.function.saveTransactionRequestStatusImpl(transactionRequest.id, CANCELLED.toString, callContext) + (true, x._2, Some(startSca)) + case CancelPayment(false, _) => + (false, x._2, Some(false)) + } + } + case TransactionStatus.CANC.code | "CANCELLED" => + Future.successful((true, callContext, Some(false))) + case _ => + Future.successful((false, callContext, Some(false))) + } + } + _ <- Helper.booleanToFuture( + failMsg = s"$TransactionRequestCannotBeCancelled Payment status: $mappedStatus. Only payments in RCVD, ACCP, PDNG, or CANC status can be cancelled.", + cc = callContext + ) { canBeCancelled == true } + (updatedTransactionRequest, _) <- NewStyle.function.getTransactionRequestImpl(TransactionRequestId(paymentId), callContext) + } yield { + startSca.getOrElse(false) match { + case true => Some(createCancellationTransactionRequestJson(updatedTransactionRequest)) + case false => None + } + } + }.attempt.flatMap { + case Right(Some(cancelJson)) => + Accepted(prettyRender(LiftExtraction.decompose(cancelJson))) + case Right(None) => + NoContent() + case Left(err) => + ErrorResponseConverter.toHttp4sResponse(err, cc) + } + } + + // ── GET /{paymentService}/{paymentProduct}/{paymentId}/cancellation-authorisations/{cancellationId} ─── + lazy val getPaymentCancellationScaStatus: HttpRoutes[IO] = HttpRoutes.of[IO] { + case req @ GET -> `bgV13Prefix` / paymentService / paymentProduct / paymentId / "cancellation-authorisations" / cancellationId => + EndpointHelpers.executeAndRespond(req) { cc => + val callContext = Some(cc) + for { + _ <- passesPsd2Pisp(callContext) + _ <- NewStyle.function.tryons(checkPaymentServerTypeError(paymentService), 404, callContext) { + PaymentServiceTypes.withName(paymentService.replaceAll("-", "_")) + } + _ <- NewStyle.function.tryons(checkPaymentProductError(paymentProduct), 404, callContext) { + TransactionRequestTypes.withName(paymentProduct.replaceAll("-", "_").toUpperCase) + } + (_, _) <- NewStyle.function.getTransactionRequestImpl(TransactionRequestId(paymentId), callContext) + (challenge, _) <- NewStyle.function.getChallenge(cancellationId, callContext) + } yield { + JSONFactory_BERLIN_GROUP_1_3.ScaStatusJsonV13(challenge.scaStatus.map(_.toString).getOrElse("None")) + } + } + } + + // ── GET /{paymentService}/{paymentProduct}/{paymentId} (with checkPaymentServiceType guard in Lift) ── + lazy val getPaymentInformation: HttpRoutes[IO] = HttpRoutes.of[IO] { + case req @ GET -> `bgV13Prefix` / paymentService / paymentProduct / paymentId if checkPaymentServiceType(paymentService) => + EndpointHelpers.executeAndRespond(req) { cc => + val callContext = Some(cc) + for { + _ <- passesPsd2Pisp(callContext) + _ <- NewStyle.function.tryons(checkPaymentServerTypeError(paymentService), 404, callContext) { + PaymentServiceTypes.withName(paymentService.replaceAll("-", "_")) + } + _ <- NewStyle.function.tryons(checkPaymentProductError(paymentProduct), 404, callContext) { + TransactionRequestTypes.withName(paymentProduct.replaceAll("-", "_").toUpperCase) + } + (transactionRequest, _) <- NewStyle.function.getTransactionRequestImpl(TransactionRequestId(paymentId), callContext) + transactionRequestBody <- NewStyle.function.tryons(s"${UnknownError} No data for Payment Body ", 400, callContext) { + transactionRequest.body.to_sepa_credit_transfers.get + } + } yield { + transactionRequestBody + } + } + } + + // ── GET /{paymentService}/{paymentProduct}/{paymentId}/authorisations ───────────────────────────── + lazy val getPaymentInitiationAuthorisation: HttpRoutes[IO] = HttpRoutes.of[IO] { + case req @ GET -> `bgV13Prefix` / paymentService / paymentProduct / paymentId / "authorisations" => + EndpointHelpers.executeAndRespond(req) { cc => + val callContext = Some(cc) + for { + _ <- passesPsd2Pisp(callContext) + _ <- NewStyle.function.tryons(checkPaymentServerTypeError(paymentService), 404, callContext) { + PaymentServiceTypes.withName(paymentService.replaceAll("-", "_")) + } + _ <- NewStyle.function.tryons(checkPaymentProductError(paymentProduct), 404, callContext) { + TransactionRequestTypes.withName(paymentProduct.replaceAll("-", "_").toUpperCase) + } + (_, _) <- NewStyle.function.getTransactionRequestImpl(TransactionRequestId(paymentId), callContext) + (challenges, _) <- NewStyle.function.getChallengesByTransactionRequestId(paymentId, callContext) + } yield { + JSONFactory_BERLIN_GROUP_1_3.createStartPaymentAuthorisationsJson(challenges) + } + } + } + + // ── GET /{paymentService}/{paymentProduct}/{paymentId}/cancellation-authorisations ────────────────── + lazy val getPaymentInitiationCancellationAuthorisationInformation: HttpRoutes[IO] = HttpRoutes.of[IO] { + case req @ GET -> `bgV13Prefix` / paymentService / paymentProduct / paymentId / "cancellation-authorisations" => + EndpointHelpers.executeAndRespond(req) { cc => + val callContext = Some(cc) + for { + _ <- passesPsd2Pisp(callContext) + _ <- NewStyle.function.tryons(checkPaymentServerTypeError(paymentService), 404, callContext) { + PaymentServiceTypes.withName(paymentService.replaceAll("-", "_")) + } + _ <- NewStyle.function.tryons(checkPaymentProductError(paymentProduct), 404, callContext) { + TransactionRequestTypes.withName(paymentProduct.replaceAll("-", "_").toUpperCase) + } + (challenges, _) <- NewStyle.function.getChallengesByTransactionRequestId(paymentId, callContext) + } yield { + JSONFactory_BERLIN_GROUP_1_3.CancellationJsonV13(challenges.map(_.challengeId)) + } + } + } + + // ── GET /{paymentService}/{paymentProduct}/{paymentId}/authorisations/{authorisationId} ──────────── + lazy val getPaymentInitiationScaStatus: HttpRoutes[IO] = HttpRoutes.of[IO] { + case req @ GET -> `bgV13Prefix` / paymentService / paymentProduct / paymentId / "authorisations" / authorisationId => + EndpointHelpers.executeAndRespond(req) { cc => + val callContext = Some(cc) + for { + _ <- passesPsd2Pisp(callContext) + _ <- NewStyle.function.tryons(checkPaymentServerTypeError(paymentService), 404, callContext) { + PaymentServiceTypes.withName(paymentService.replaceAll("-", "_")) + } + _ <- NewStyle.function.tryons(checkPaymentProductError(paymentProduct), 404, callContext) { + TransactionRequestTypes.withName(paymentProduct.replaceAll("-", "_").toUpperCase) + } + (_, _) <- NewStyle.function.getTransactionRequestImpl(TransactionRequestId(paymentId), callContext) + (challenge, _) <- NewStyle.function.getChallenge(authorisationId, callContext) + } yield { + json.parse(s"""{"scaStatus" : "${challenge.scaStatus.getOrElse("None")}"}""") + } + } + } + + // ── GET /{paymentService}/{paymentProduct}/{paymentId}/status ───────────────────────────────────── + lazy val getPaymentInitiationStatus: HttpRoutes[IO] = HttpRoutes.of[IO] { + case req @ GET -> `bgV13Prefix` / paymentService / paymentProduct / paymentId / "status" => + EndpointHelpers.executeAndRespond(req) { cc => + val callContext = Some(cc) + import net.liftweb.json.JsonDSL._ + for { + _ <- passesPsd2Pisp(callContext) + _ <- NewStyle.function.tryons(checkPaymentServerTypeError(paymentService), 404, callContext) { + PaymentServiceTypes.withName(paymentService.replaceAll("-", "_")) + } + _ <- NewStyle.function.tryons(checkPaymentProductError(paymentProduct), 404, callContext) { + TransactionRequestTypes.withName(paymentProduct.replaceAll("-", "_").toUpperCase) + } + (transactionRequest, _) <- NewStyle.function.getTransactionRequestImpl(TransactionRequestId(paymentId), callContext) + transactionRequestStatus = mapTransactionStatus(transactionRequest.status) + transactionRequestAmount <- NewStyle.function.tryons(s"${InvalidNumber} transaction request amount cannot convert to a Decimal", 400, callContext) { + BigDecimal(transactionRequest.body.to_sepa_credit_transfers.get.instructedAmount.amount) + } + transactionRequestCurrency <- NewStyle.function.tryons(s"${InvalidCurrency} can not get currency from this paymentId(${paymentId})", 400, callContext) { + transactionRequest.body.to_sepa_credit_transfers.get.instructedAmount.currency + } + transactionRequestFromAccount = transactionRequest.from + (fromAccount, _) <- NewStyle.function.checkBankAccountExists( + BankId(transactionRequestFromAccount.bank_id), + AccountId(transactionRequestFromAccount.account_id), + callContext + ) + fromAccountBalance = fromAccount.balance + fromAccountCurrency = fromAccount.currency + rate = fx.exchangeRate(transactionRequestCurrency, fromAccountCurrency, None, callContext) + _ <- Helper.booleanToFuture(s"$InvalidCurrency The requested currency conversion (${transactionRequestCurrency} to ${fromAccountCurrency}) is not supported.", cc = callContext) { + rate.isDefined + } + requestChangedCurrencyAmount = fx.convert(transactionRequestAmount, rate) + fundsAvailable = (fromAccountBalance >= requestChangedCurrencyAmount) + transactionRequestStatusCheckedFunds = if (fundsAvailable) transactionRequestStatus else TransactionStatus.RCVD.code + } yield { + ("transactionStatus" -> transactionRequestStatusCheckedFunds) ~ + ("fundsAvailable" -> fundsAvailable) + } + } + } + + // ── POST /payments/{paymentProduct} ────────────────────────────────────────────────────────────── + // Auth: applicationAccess in Lift → authMode = UserOrApplication in ResourceDoc + lazy val initiatePayments: HttpRoutes[IO] = HttpRoutes.of[IO] { + case req @ POST -> `bgV13Prefix` / "payments" / paymentProduct => + EndpointHelpers.executeFutureCreated(req) { + initiatePaymentImpl("payments", paymentProduct, Some(req.callContext)) + } + } + + // ── POST /periodic-payments/{paymentProduct} ────────────────────────────────────────────────────── + lazy val initiatePeriodicPayments: HttpRoutes[IO] = HttpRoutes.of[IO] { + case req @ POST -> `bgV13Prefix` / "periodic-payments" / paymentProduct => + EndpointHelpers.executeFutureCreated(req) { + initiatePaymentImpl("periodic-payments", paymentProduct, Some(req.callContext)) + } + } + + // ── POST /bulk-payments/{paymentProduct} ────────────────────────────────────────────────────────── + lazy val initiateBulkPayments: HttpRoutes[IO] = HttpRoutes.of[IO] { + case req @ POST -> `bgV13Prefix` / "bulk-payments" / paymentProduct => + EndpointHelpers.executeFutureCreated(req) { + initiatePaymentImpl("bulk-payments", paymentProduct, Some(req.callContext)) + } + } + + // ── POST /{paymentService}/{paymentProduct}/{paymentId}/authorisations (3 body-guard variants) ─── + // + // Dispatches on the request body: + // scaAuthenticationData → startPaymentAuthorisationTransactionAuthorisation (real SCA logic) + // psuData → startPaymentAuthorisationUpdatePsuAuthentication (mocked) + // authenticationMethodId → startPaymentAuthorisationSelectPsuAuthenticationMethod (mocked) + lazy val startPaymentAuthorisationAll: HttpRoutes[IO] = HttpRoutes.of[IO] { + case req @ POST -> `bgV13Prefix` / paymentService / paymentProduct / paymentId / "authorisations" => + EndpointHelpers.executeFutureCreated(req) { + val cc = req.callContext + val callContext = Some(cc) + val u = cc.user.openOrThrowException(AuthenticatedUserIsRequired) + val parsedJson = scala.util.Try(json.parse(cc.httpBody.getOrElse(""))).getOrElse(json.JNothing) + if (checkTransactionAuthorisation(parsedJson)) { + for { + _ <- passesPsd2Pisp(callContext) + _ <- NewStyle.function.tryons(checkPaymentServerTypeError(paymentService), 404, callContext) { + PaymentServiceTypes.withName(paymentService.replaceAll("-", "_")) + } + _ <- NewStyle.function.tryons(checkPaymentProductError(paymentProduct), 404, callContext) { + TransactionRequestTypes.withName(paymentProduct.replaceAll("-", "_").toUpperCase) + } + (_, _) <- NewStyle.function.getTransactionRequestImpl(TransactionRequestId(paymentId), callContext) + (challenges, _) <- NewStyle.function.createChallengesC2( + List(u.userId), + ChallengeType.BERLIN_GROUP_PAYMENT_CHALLENGE, + Some(paymentId), + getScaMethodAtInstance(SEPA_CREDIT_TRANSFERS.toString).toOption, + Some(StrongCustomerAuthenticationStatus.received), + None, + None, + callContext + ) + challenge <- NewStyle.function.tryons(InvalidConnectorResponseForCreateChallenge, 400, callContext) { + challenges.head + } + } yield { + JSONFactory_BERLIN_GROUP_1_3.createStartPaymentAuthorisationJson(challenge) + } + } else { + // Mocked response for updatePsuAuthentication and selectPsuAuthenticationMethod variants + Future.successful(json.parse( + """{ + "challengeData": { + "scaStatus": "received", + "authorisationId": "88695566-6642-46d5-9985-0d824624f507", + "psuMessage": "Please check your SMS at a mobile device.", + "_links": { + "scaStatus": "/v1.3/payments/sepa-credit-transfers/88695566-6642-46d5-9985-0d824624f507" + } + } + }""")) + } + } + } + + // ── POST /{paymentService}/{paymentProduct}/{paymentId}/cancellation-authorisations (3 variants) ─ + lazy val startPaymentInitiationCancellationAuthorisationAll: HttpRoutes[IO] = HttpRoutes.of[IO] { + case req @ POST -> `bgV13Prefix` / paymentService / paymentProduct / paymentId / "cancellation-authorisations" => + EndpointHelpers.executeFutureCreated(req) { + val cc = req.callContext + val callContext = Some(cc) + val u = cc.user.openOrThrowException(AuthenticatedUserIsRequired) + val parsedJson = scala.util.Try(json.parse(cc.httpBody.getOrElse(""))).getOrElse(json.JNothing) + if (checkTransactionAuthorisation(parsedJson)) { + for { + _ <- passesPsd2Pisp(callContext) + _ <- NewStyle.function.tryons(checkPaymentServerTypeError(paymentService), 404, callContext) { + PaymentServiceTypes.withName(paymentService.replaceAll("-", "_")) + } + _ <- NewStyle.function.tryons(checkPaymentProductError(paymentProduct), 404, callContext) { + TransactionRequestTypes.withName(paymentProduct.replaceAll("-", "_").toUpperCase) + } + (transactionRequest, _) <- NewStyle.function.getTransactionRequestImpl(TransactionRequestId(paymentId), callContext) + _ <- Helper.booleanToFuture(failMsg = CannotStartTheAuthorisationProcessForTheCancellation, cc = callContext) { + transactionRequest.status == TransactionRequestStatus.CANCELLATION_PENDING.toString + } + (challenges, _) <- NewStyle.function.createChallengesC2( + List(u.userId), + ChallengeType.BERLIN_GROUP_PAYMENT_CHALLENGE, + Some(paymentId), + getScaMethodAtInstance(SEPA_CREDIT_TRANSFERS.toString).toOption, + Some(StrongCustomerAuthenticationStatus.received), + None, + None, + callContext + ) + challenge <- NewStyle.function.tryons(InvalidConnectorResponseForCreateChallenge, 400, callContext) { + challenges.head + } + } yield { + JSONFactory_BERLIN_GROUP_1_3.createStartPaymentInitiationCancellationAuthorisation( + challenge, paymentService, paymentProduct, paymentId + ) + } + } else { + // Mocked for updatePsuAuthentication and selectPsuAuthenticationMethod variants + Future.successful(json.parse( + """{ + "scaStatus": "received", + "authorisationId": "123auth456", + "psuMessage": "Please use your BankApp for transaction Authorisation.", + "_links": { + "scaStatus": { + "href": "/v1.3/payments/qwer3456tzui7890/authorisations/123auth456" + } + } + }""")) + } + } + } + + // ── PUT /{paymentService}/{paymentProduct}/{paymentId}/cancellation-authorisations/{authorisationId} (4 variants) ─ + lazy val updatePaymentCancellationPsuDataAll: HttpRoutes[IO] = HttpRoutes.of[IO] { + case req @ PUT -> `bgV13Prefix` / paymentService / paymentProduct / paymentId / "cancellation-authorisations" / authorisationId => + EndpointHelpers.executeAndRespond(req) { cc => + val callContext = Some(cc) + val parsedJson = scala.util.Try(json.parse(cc.httpBody.getOrElse(""))).getOrElse(json.JNothing) + if (checkTransactionAuthorisation(parsedJson)) { + for { + _ <- passesPsd2Pisp(callContext) + failMsg = s"$InvalidJsonFormat The Json body should be the $UpdatePaymentPsuDataJson " + transactionAuthorisation <- NewStyle.function.tryons(failMsg, 400, callContext) { + parsedJson.extract[TransactionAuthorisation] + } + _ <- NewStyle.function.tryons(checkPaymentServerTypeError(paymentService), 404, callContext) { + PaymentServiceTypes.withName(paymentService.replaceAll("-", "_")) + } + _ <- NewStyle.function.tryons(checkPaymentProductError(paymentProduct), 404, callContext) { + TransactionRequestTypes.withName(paymentProduct.replaceAll("-", "_").toUpperCase) + } + transactionRequestId = TransactionRequestId(paymentId) + (existingTransactionRequest, _) <- NewStyle.function.getTransactionRequestImpl(transactionRequestId, callContext) + _ <- Helper.booleanToFuture(failMsg = CannotUpdatePSUDataCancellation, cc = callContext) { + existingTransactionRequest.status == TransactionRequestStatus.INITIATED.toString || + existingTransactionRequest.status == TransactionRequestStatus.CANCELLATION_PENDING.toString || + existingTransactionRequest.status == TransactionRequestStatus.COMPLETED.toString + } + (_, _) <- NewStyle.function.getTransactionRequestImpl(TransactionRequestId(paymentId), callContext) + (challenge, _) <- NewStyle.function.validateChallengeAnswerC4( + ChallengeType.BERLIN_GROUP_PAYMENT_CHALLENGE, + Some(paymentId), + None, + authorisationId, + transactionAuthorisation.scaAuthenticationData, + SuppliedAnswerType.PLAIN_TEXT_VALUE, + callContext + ) + (fromAccount, _) <- NewStyle.function.checkBankAccountExists( + BankId(existingTransactionRequest.from.bank_id), + AccountId(existingTransactionRequest.from.account_id), + callContext + ) + _ <- challenge.scaStatus match { + case Some(status) if status == StrongCustomerAuthenticationStatus.finalised => + NewStyle.function.saveTransactionRequestStatusImpl(existingTransactionRequest.id, CANCELLED.toString, callContext) + case Some(status) if status == StrongCustomerAuthenticationStatus.failed => + NewStyle.function.saveTransactionRequestStatusImpl(existingTransactionRequest.id, REJECTED.toString, callContext) + case _ => + Future(Full(true)) + } + } yield { + JSONFactory_BERLIN_GROUP_1_3.createStartPaymentCancellationAuthorisationJson( + challenge, paymentService, paymentProduct, paymentId + ) + } + } else if (checkUpdatePsuAuthentication(parsedJson)) { + Future.successful(json.parse( + """{ + "scaStatus": "psuAuthenticated", + "_links": { + "authoriseTransaction": {"href": "/psd2/v1.3/payments/1234-wertiq-983/authorisations/123auth456"} + } + }""")) + } else if (checkSelectPsuAuthenticationMethod(parsedJson)) { + Future.successful(json.parse( + """{ + "scaStatus": "scaMethodSelected", + "chosenScaMethod": { + "authenticationType": "SMS_OTP", + "authenticationMethodId": "myAuthenticationID"}, + "challengeData": { + "otpMaxLength": 6, + "otpFormat": "integer"}, + "_links": { + "authoriseTransaction": {"href": "/psd2/v1.3/payments/1234-wertiq-983/authorisations/123auth456"} + } + }""")) + } else { + // authorisationConfirmation variant + Future.successful(json.parse( + """{ + "scaStatus": "finalised", + "_links":{ + "status": {"href":"/v1.3/payments/sepa-credit-transfers/qwer3456tzui7890/status"} + } + }""")) + } + } + } + + // ── PUT /{paymentService}/{paymentProduct}/{paymentId}/authorisations/{authorisationId} (4 variants) ─ + lazy val updatePaymentPsuDataAll: HttpRoutes[IO] = HttpRoutes.of[IO] { + case req @ PUT -> `bgV13Prefix` / paymentService / paymentProduct / paymentId / "authorisations" / authorisationId => + EndpointHelpers.executeAndRespond(req) { cc => + val callContext = Some(cc) + val parsedJson = scala.util.Try(json.parse(cc.httpBody.getOrElse(""))).getOrElse(json.JNothing) + if (checkTransactionAuthorisation(parsedJson)) { + val u = cc.user.openOrThrowException(AuthenticatedUserIsRequired) + for { + _ <- passesPsd2Pisp(callContext) + failMsg = s"$InvalidJsonFormat The Json body should be the $TransactionAuthorisation " + transactionAuthorisationJson <- NewStyle.function.tryons(failMsg, 400, callContext) { + parsedJson.extract[TransactionAuthorisation] + } + _ <- NewStyle.function.tryons(checkPaymentServerTypeError(paymentService), 404, callContext) { + PaymentServiceTypes.withName(paymentService.replaceAll("-", "_")) + } + _ <- NewStyle.function.tryons(checkPaymentProductError(paymentProduct), 404, callContext) { + TransactionRequestTypes.withName(paymentProduct.replaceAll("-", "_").toUpperCase) + } + transactionRequestId = TransactionRequestId(paymentId) + (existingTransactionRequest, _) <- NewStyle.function.getTransactionRequestImpl(transactionRequestId, callContext) + _ <- Helper.booleanToFuture(failMsg = CannotUpdatePSUData, cc = callContext) { + existingTransactionRequest.status == TransactionStatus.RCVD.code + } + (_, _) <- NewStyle.function.getChallenge(authorisationId, callContext) + (challenge, _) <- NewStyle.function.validateChallengeAnswerC4( + ChallengeType.BERLIN_GROUP_PAYMENT_CHALLENGE, + Some(paymentId), + None, + authorisationId, + transactionAuthorisationJson.scaAuthenticationData, + SuppliedAnswerType.PLAIN_TEXT_VALUE, + callContext + ) + (fromAccount, _) <- NewStyle.function.checkBankAccountExists( + BankId(existingTransactionRequest.from.bank_id), + AccountId(existingTransactionRequest.from.account_id), + callContext + ) + _ <- challenge.scaStatus match { + case Some(status) if status == StrongCustomerAuthenticationStatus.finalised => + NewStyle.function.createTransactionAfterChallengeV210(fromAccount, existingTransactionRequest, callContext) map { _ => + NewStyle.function.saveTransactionRequestStatusImpl(existingTransactionRequest.id, COMPLETED.toString, callContext) + } + case Some(status) if status == StrongCustomerAuthenticationStatus.failed => + NewStyle.function.saveTransactionRequestStatusImpl(existingTransactionRequest.id, REJECTED.toString, callContext) + case _ => + Future(Full(true)) + } + } yield { + JSONFactory_BERLIN_GROUP_1_3.createUpdatePaymentPsuDataTransactionAuthorisationJson(challenge) + } + } else if (checkUpdatePsuAuthentication(parsedJson)) { + Future.successful(json.parse( + """{ + "scaStatus": "finalised", + "_links": { + "scaStatus": {"href":"/v1.3/payments/sepa-credit-transfers/88695566-6642-46d5-9985-0d824624f507"} + } + }""")) + } else if (checkSelectPsuAuthenticationMethod(parsedJson)) { + Future.successful(json.parse( + """{ + "scaStatus": "scaMethodSelected", + "chosenScaMethod": { + "authenticationType": "SMS_OTP", + "authenticationMethodId": "myAuthenticationID"}, + "challengeData": { + "otpMaxLength": 6, + "otpFormat": "integer"}, + "_links": { + "authoriseTransaction": {"href": "/psd2/v1.3/payments/1234-wertiq-983/authorisations/123auth456"} + } + }""")) + } else { + // authorisationConfirmation variant + Future.successful(json.parse( + """{ + "scaStatus": "finalised", + "_links":{ + "status": {"href":"/v1.3/payments/sepa-credit-transfers/qwer3456tzui7890/status"} + } + }""")) + } + } + } + + // ── ResourceDocs ─────────────────────────────────────────────────────────────────────────────────── + + private val generalPaymentSummaryText: String = + s""" This method is used to initiate a payment at the ASPSP. + + ## Variants of Payment Initiation Requests + + This method to initiate a payment initiation at the ASPSP can be sent with either a JSON body or an pain.001 body depending on the payment product in the path. + + There are the following **payment products**: + + - Payment products with payment information in *JSON* format: + - ***sepa-credit-transfers*** + - ***instant-sepa-credit-transfers*** + - ***target-2-payments*** + - ***cross-border-credit-transfers*** + - Payment products with payment information in *pain.001* XML format: + - ***pain.001-sepa-credit-transfers*** + - ***pain.001-instant-sepa-credit-transfers*** + - ***pain.001-target-2-payments*** + - ***pain.001-cross-border-credit-transfers*** + + - Furthermore the request body depends on the **payment-service** + - ***payments***: A single payment initiation request. + - ***bulk-payments***: A collection of several payment iniatiation requests. + In case of a *pain.001* message there are more than one payments contained in the *pain.001 message. + In case of a *JSON* there are several JSON payment blocks contained in a joining list. + - ***periodic-payments***: + Create a standing order initiation resource for recurrent i.e. periodic payments addressable under {paymentId} + with all data relevant for the corresponding payment product and the execution of the standing order contained in a JSON body. + + This is the first step in the API to initiate the related recurring/periodic payment. + + Additional Instructions: + + for PAYMENT_SERVICE use payments + + for PAYMENT_PRODUCT use sepa-credit-transfers + """ + + private val generalStartPaymentAuthorisationSummary: String = + s"""${mockedDataText(true)} +Create an authorisation sub-resource and start the authorisation process. +The message might in addition transmit authentication and authorisation related data. + +This method is iterated n times for a n times SCA authorisation in a +corporate context, each creating an own authorisation sub-endpoint for +the corresponding PSU authorising the transaction. + +The ASPSP might make the usage of this access method unnecessary in case +of only one SCA process needed, since the related authorisation resource +might be automatically created by the ASPSP after the submission of the +payment data with the first POST payments/{payment-product} call. + +The start authorisation process is a process which is needed for creating a new authorisation +or cancellation sub-resource. +""" + + private val startPaymentAuthorisationResponse = json.parse("""{ + "challengeData": { + "scaStatus": "received", + "authorisationId": "88695566-6642-46d5-9985-0d824624f507", + "psuMessage": "Please check your SMS at a mobile device.", + "_links": { + "scaStatus": "/v1.3/payments/sepa-credit-transfers/88695566-6642-46d5-9985-0d824624f507" + } + } + }""") + + private val generalStartPaymentInitiationCancellationAuthorisationSummary: String = + s"""${mockedDataText(true)} +Creates an authorisation sub-resource and start the authorisation process of the cancellation of the addressed payment. +The message might in addition transmit authentication and authorisation related data. +""" + + private val startPaymentInitiationCancellationAuthorisationResponse = json.parse("""{ + "scaStatus": "received", + "authorisationId": "123auth456", + "psuMessage": "Please use your BankApp for transaction Authorisation.", + "_links": { + "scaStatus": { + "href": "/v1.3/payments/qwer3456tzui7890/authorisations/123auth456" + } + } + }""") + + private val generalUpdatePaymentCancellationPsuDataSummary: String = + s"""${mockedDataText(true)} +This method updates PSU data on the cancellation authorisation resource if needed. +It may authorise a cancellation of the payment within the Embedded SCA Approach where needed. +""" + + private val generalUpdatePaymentPsuDataSummary: String = + s"""${mockedDataText(false)} +This methods updates PSU data on the authorisation resource if needed. +It may authorise a payment within the Embedded SCA Approach where needed. + + NOTE: For this endpoint, for sandbox mode, the `scaAuthenticationData` is fixed value: 123. To make the process work. + Normally the app use will get SMS/EMAIL to get the value for this process. +""" + + private def initCancelAndGetResourceDocs(): Unit = { + resourceDocs += ResourceDoc( + null, implementedInApiVersion, nameOf(cancelPayment), + "DELETE", "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENTID", + "Payment Cancellation Request", + s"""${mockedDataText(false)} +This method initiates the cancellation of a payment. Depending on the payment-service, the payment-product +and the ASPSP's implementation, this TPP call might be sufficient to cancel a payment. If an authorisation +of the payment cancellation is mandated by the ASPSP, a corresponding hyperlink will be contained in the +response message. Cancels the addressed payment with resource identification paymentId if applicable to the +payment-service, payment-product and received in product related timelines (e.g. before end of business day +for scheduled payments of the last business day before the scheduled execution day). The response to this +DELETE command will tell the TPP whether the * access method was rejected * access method was successful, +or * access method is generally applicable, but further authorisation processes are needed. +""", + EmptyBody, + CancelPaymentResponseJson( + "ACTC", + _links = CancelPaymentResponseLinks( + self = LinkHrefJson(s"/v1.3/payments/sepa-credit-transfers/1234-wertiq-983"), + status = LinkHrefJson(s"/v1.3/payments/sepa-credit-transfers/1234-wertiq-983/status"), + startAuthorisation = LinkHrefJson(s"/v1.3/payments/sepa-credit-transfers/cancellation-authorisations/1234-wertiq-983/status") + ) + ), + List(AuthenticatedUserIsRequired, UnknownError), + ApiTag("Payment Initiation Service (PIS)") :: Nil, + http4sPartialFunction = Some(cancelPayment) + ) + + resourceDocs += ResourceDoc( + null, implementedInApiVersion, nameOf(getPaymentCancellationScaStatus), + "GET", "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENTID/cancellation-authorisations/CANCELLATIONID", + "Read the SCA status of the payment cancellation's authorisation.", + s"""${mockedDataText(false)} +This method returns the SCA status of a payment initiation's authorisation sub-resource. +""", + EmptyBody, + json.parse("""{"scaStatus" : "psuAuthenticated"}"""), + List(AuthenticatedUserIsRequired, UnknownError), + ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil, + http4sPartialFunction = Some(getPaymentCancellationScaStatus) + ) + + resourceDocs += ResourceDoc( + null, implementedInApiVersion, nameOf(getPaymentInformation), + "GET", "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENTID", + "Get Payment Information", + s"""${mockedDataText(false)} +Returns the content of a payment object""", + EmptyBody, + json.parse("""{ + "debtorAccount":{ + "iban":"GR12 1234 5123 4511 3981 4475 477" + }, + "instructedAmount":{ + "currency":"EUR", + "amount":"1234" + }, + "creditorAccount":{ + "iban":"GR12 1234 5123 4514 4575 3645 077" + }, + "creditorName":"70charname" + }"""), + List(AuthenticatedUserIsRequired, UnknownError), + ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil, + http4sPartialFunction = Some(getPaymentInformation) + ) + + resourceDocs += ResourceDoc( + null, implementedInApiVersion, nameOf(getPaymentInitiationAuthorisation), + "GET", "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENTID/authorisations", + "Get Payment Initiation Authorisation Sub-Resources Request", + s"""${mockedDataText(false)} +Read a list of all authorisation subresources IDs which have been created. + +This function returns an array of hyperlinks to all generated authorisation sub-resources. +""", + EmptyBody, + json.parse("""[{ + "scaStatus": "received", + "authorisationId": "940948c7-1c86-4d88-977e-e739bf2c1492", + "psuMessage": "Please check your SMS at a mobile device.", + "_links": {"scaStatus": "/v1.3/payments/sepa-credit-transfers/940948c7-1c86-4d88-977e-e739bf2c1492"} + }]"""), + List(AuthenticatedUserIsRequired, UnknownError), + ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil, + http4sPartialFunction = Some(getPaymentInitiationAuthorisation) + ) + + resourceDocs += ResourceDoc( + null, implementedInApiVersion, nameOf(getPaymentInitiationCancellationAuthorisationInformation), + "GET", "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENTID/cancellation-authorisations", + "Get Cancellation Authorisation Sub-Resources Request", + s"""${mockedDataText(false)} +Retrieve a list of all created cancellation authorisation sub-resources. +""", + EmptyBody, + json.parse("""{"cancellationIds" : ["faa3657e-13f0-4feb-a6c3-34bf21a9ae8e"]}"""), + List(AuthenticatedUserIsRequired, UnknownError), + ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil, + http4sPartialFunction = Some(getPaymentInitiationCancellationAuthorisationInformation) + ) + + resourceDocs += ResourceDoc( + null, implementedInApiVersion, nameOf(getPaymentInitiationScaStatus), + "GET", "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENT_ID/authorisations/AUTHORISATION_ID", + "Read the SCA Status of the payment authorisation", + s"""${mockedDataText(false)} +This method returns the SCA status of a payment initiation's authorisation sub-resource. +""", + EmptyBody, + json.parse("""{"scaStatus" : "psuAuthenticated"}"""), + List(AuthenticatedUserIsRequired, UnknownError), + ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil, + http4sPartialFunction = Some(getPaymentInitiationScaStatus) + ) + + resourceDocs += ResourceDoc( + null, implementedInApiVersion, nameOf(getPaymentInitiationStatus), + "GET", "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENT_ID/status", + "Payment initiation status request", + s"""${mockedDataText(false)} +Check the transaction status of a payment initiation.""", + EmptyBody, + json.parse(s"""{"transactionStatus": "${TransactionStatus.ACCP.code}"}"""), + List(AuthenticatedUserIsRequired, UnknownError), + ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil, + http4sPartialFunction = Some(getPaymentInitiationStatus) + ) + } + + private def initInitiatePaymentResourceDocs(): Unit = { + val initiatePaymentRequestBody = json.parse(s"""{ + "debtorAccount": {"iban": "DE123456987480123"}, + "instructedAmount": {"currency": "EUR", "amount": "100"}, + "creditorAccount": {"iban": "UK12 1234 5123 4517 2948 6166 077"}, + "creditorName": "70charname" + }""") + val initiatePaymentResponseBody = json.parse(s"""{ + "transactionStatus": "${TransactionStatus.RCVD.code}", + "paymentId": "1234-wertiq-983", + "_links": { + "scaRedirect": {"href": "$getServerUrl/otp?flow=payment&paymentService=payments&paymentProduct=sepa_credit_transfers&paymentId=b0472c21-6cea-4ee0-b036-3e253adb3b0b"}, + "self": {"href": "/v1.3/payments/sepa-credit-transfers/1234-wertiq-983"}, + "status": {"href": "/v1.3/payments/1234-wertiq-983/status"}, + "scaStatus": {"href": "/v1.3/payments/1234-wertiq-983/authorisations/123auth456"} + } + }""") + + resourceDocs += ResourceDoc( + null, implementedInApiVersion, nameOf(initiatePayments), + "POST", "/payments/PAYMENT_PRODUCT", + "Payment initiation request(payments)", + s"""${mockedDataText(false)} +$generalPaymentSummaryText""", + initiatePaymentRequestBody, + initiatePaymentResponseBody, + List(AuthenticatedUserIsRequired, UnknownError), + ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil, + authMode = UserOrApplication, + http4sPartialFunction = Some(initiatePayments) + ) + + resourceDocs += ResourceDoc( + null, implementedInApiVersion, nameOf(initiatePeriodicPayments), + "POST", "/periodic-payments/PAYMENT_PRODUCT", + "Payment initiation request(periodic-payments)", + s"""${mockedDataText(false)} +$generalPaymentSummaryText""", + json.parse(s"""{ + "instructedAmount": {"currency": "EUR", "amount": "123"}, + "debtorAccount": {"iban": "DE40100100103307118608"}, + "creditorName": "Merchant123", + "creditorAccount": {"iban": "DE23100120020123456789"}, + "remittanceInformationUnstructured": "Ref Number Abonnement", + "startDate": "2018-03-01", + "executionRule": "preceding", + "frequency": "Monthly", + "dayOfExecution": "01" + }"""), + json.parse(s"""{ + "transactionStatus": "${TransactionStatus.RCVD.code}", + "paymentId": "1234-wertiq-983", + "_links": { + "scaRedirect": {"href": "$getServerUrl/otp?flow=payment&paymentService=payments&paymentProduct=sepa_credit_transfers&paymentId=b0472c21-6cea-4ee0-b036-3e253adb3b0b"}, + "self": {"href": "/v1.3/periodic-payments/instant-sepa-credit-transfer/1234-wertiq-983"}, + "status": {"href": "/v1.3/periodic-payments/1234-wertiq-983/status"}, + "scaStatus": {"href": "/v1.3/periodic-payments/1234-wertiq-983/authorisations/123auth456"} + } + }"""), + List(AuthenticatedUserIsRequired, UnknownError), + ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil, + authMode = UserOrApplication, + http4sPartialFunction = Some(initiatePeriodicPayments) + ) + + resourceDocs += ResourceDoc( + null, implementedInApiVersion, nameOf(initiateBulkPayments), + "POST", "/bulk-payments/PAYMENT_PRODUCT", + "Payment initiation request(bulk-payments)", + s"""${mockedDataText(true)} +$generalPaymentSummaryText""", + json.parse(s"""{ + "batchBookingPreferred": "true", + "debtorAccount": {"iban": "DE40100100103307118608"}, + "paymentInformationId": "my-bulk-identification-1234", + "requestedExecutionDate": "2018-08-01", + "payments": [ + {"instructedAmount": {"currency": "EUR", "amount": "123.50"}, "creditorName": "Merchant123", + "creditorAccount": {"iban": "DE02100100109307118603"}, + "remittanceInformationUnstructured": "Ref Number Merchant 1"}, + {"instructedAmount": {"currency": "EUR", "amount": "34.10"}, "creditorName": "Merchant456", + "creditorAccount": {"iban": "FR7612345987650123456789014"}, + "remittanceInformationUnstructured": "Ref Number Merchant 2"} + ] + }"""), + json.parse(s"""{ + "transactionStatus": "${TransactionStatus.RCVD.code}", + "paymentId": "1234-wertiq-983", + "_links": { + "scaRedirect": {"href": "$getServerUrl/otp?flow=payment&paymentService=payments&paymentProduct=sepa_credit_transfers&paymentId=b0472c21-6cea-4ee0-b036-3e253adb3b0b"}, + "self": {"href": "/v1.3/bulk-payments/sepa-credit-transfers/1234-wertiq-983"}, + "status": {"href": "/v1.3/bulk-payments/1234-wertiq-983/status"}, + "scaStatus": {"href": "/v1.3/bulk-payments/1234-wertiq-983/authorisations/123auth456"} + } + }"""), + List(AuthenticatedUserIsRequired, UnknownError), + ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil, + authMode = UserOrApplication, + http4sPartialFunction = Some(initiateBulkPayments) + ) + } + + private def initStartAuthorisationResourceDocs(): Unit = { + // POST /PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENT_ID/authorisations — 3 body variants + resourceDocs += ResourceDoc( + null, implementedInApiVersion, + "startPaymentAuthorisationUpdatePsuAuthentication", + "POST", "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENT_ID/authorisations", + "Start the authorisation process for a payment initiation (updatePsuAuthentication)", + generalStartPaymentAuthorisationSummary, + json.parse("""{"psuData": {"password": "start12"}}"""), + startPaymentAuthorisationResponse, + List(AuthenticatedUserIsRequired, UnknownError), + ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil, + http4sPartialFunction = Some(startPaymentAuthorisationAll) + ) + resourceDocs += ResourceDoc( + null, implementedInApiVersion, + "startPaymentAuthorisationSelectPsuAuthenticationMethod", + "POST", "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENT_ID/authorisations", + "Start the authorisation process for a payment initiation (selectPsuAuthenticationMethod)", + generalStartPaymentAuthorisationSummary, + json.parse("""{"authenticationMethodId":""}"""), + startPaymentAuthorisationResponse, + List(AuthenticatedUserIsRequired, UnknownError), + ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil, + http4sPartialFunction = Some(startPaymentAuthorisationAll) + ) + resourceDocs += ResourceDoc( + null, implementedInApiVersion, + "startPaymentAuthorisationTransactionAuthorisation", + "POST", "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENT_ID/authorisations", + "Start the authorisation process for a payment initiation (transactionAuthorisation)", + s"""${mockedDataText(false)} +Create an authorisation sub-resource and start the authorisation process. +The message might in addition transmit authentication and authorisation related data. +""", + json.parse("""{"scaAuthenticationData":"123"}"""), + startPaymentAuthorisationResponse, + List(AuthenticatedUserIsRequired, UnknownError), + ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil, + http4sPartialFunction = Some(startPaymentAuthorisationAll) + ) + + // POST /PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENT_ID/cancellation-authorisations — 3 body variants + resourceDocs += ResourceDoc( + null, implementedInApiVersion, + "startPaymentInitiationCancellationAuthorisationTransactionAuthorisation", + "POST", "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENT_ID/cancellation-authorisations", + "Start the authorisation process for the cancellation of the addressed payment (transactionAuthorisation)", + s"""${mockedDataText(false)} +Creates an authorisation sub-resource and start the authorisation process of the cancellation of the addressed payment. +""", + json.parse("""{"scaAuthenticationData":""}"""), + json.parse("""{ + "scaStatus": "received", + "authorisationId": "123auth456", + "psuMessage": "Please use your BankApp for transaction Authorisation.", + "_links": {"scaStatus": {"href": "/v1.3/payments/qwer3456tzui7890/authorisations/123auth456"}} + }"""), + List(AuthenticatedUserIsRequired, UnknownError), + ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil, + http4sPartialFunction = Some(startPaymentInitiationCancellationAuthorisationAll) + ) + resourceDocs += ResourceDoc( + null, implementedInApiVersion, + "startPaymentInitiationCancellationAuthorisationUpdatePsuAuthentication", + "POST", "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENT_ID/cancellation-authorisations", + "Start the authorisation process for the cancellation of the addressed payment (updatePsuAuthentication)", + generalStartPaymentInitiationCancellationAuthorisationSummary, + json.parse("""{"psuData": {"password": "start12"}}"""), + startPaymentInitiationCancellationAuthorisationResponse, + List(AuthenticatedUserIsRequired, UnknownError), + ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil, + http4sPartialFunction = Some(startPaymentInitiationCancellationAuthorisationAll) + ) + resourceDocs += ResourceDoc( + null, implementedInApiVersion, + "startPaymentInitiationCancellationAuthorisationSelectPsuAuthenticationMethod", + "POST", "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENT_ID/cancellation-authorisations", + "Start the authorisation process for the cancellation of the addressed payment (selectPsuAuthenticationMethod)", + generalStartPaymentInitiationCancellationAuthorisationSummary, + json.parse("""{"authenticationMethodId":""}"""), + startPaymentInitiationCancellationAuthorisationResponse, + List(AuthenticatedUserIsRequired, UnknownError), + ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil, + http4sPartialFunction = Some(startPaymentInitiationCancellationAuthorisationAll) + ) + } + + private def initUpdatePsuDataResourceDocs(): Unit = { + // PUT /PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENT_ID/cancellation-authorisations/AUTHORISATION_ID — 4 variants + resourceDocs += ResourceDoc( + null, implementedInApiVersion, + "updatePaymentCancellationPsuDataTransactionAuthorisation", + "PUT", "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENT_ID/cancellation-authorisations/AUTHORISATION_ID", + "Update PSU Data for payment initiation cancellation (transactionAuthorisation)", + s"""${mockedDataText(false)} +This method updates PSU data on the cancellation authorisation resource if needed. +""", + json.parse("""{"scaAuthenticationData":"123"}"""), + json.parse("""{ + "scaStatus":"finalised", + "psuMessage":"Please check your SMS at a mobile device.", + "_links":{"scaStatus":"/v1.3/payments/sepa-credit-transfers/PAYMENT_ID/4f4a8b7f-9968-4183-92ab-ca512b396bfc"} + }"""), + List(AuthenticatedUserIsRequired, UnknownError), + ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil, + http4sPartialFunction = Some(updatePaymentCancellationPsuDataAll) + ) + resourceDocs += ResourceDoc( + null, implementedInApiVersion, + "updatePaymentCancellationPsuDataUpdatePsuAuthentication", + "PUT", "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENT_ID/cancellation-authorisations/AUTHORISATION_ID", + "Update PSU Data for payment initiation cancellation (updatePsuAuthentication)", + generalUpdatePaymentCancellationPsuDataSummary, + json.parse("""{"psuData":{"password":"start12"}}"""), + json.parse("""{ + "scaStatus": "psuAuthenticated", + "_links": {"authoriseTransaction": {"href": "/psd2/v1.3/payments/1234-wertiq-983/authorisations/123auth456"}} + }"""), + List(AuthenticatedUserIsRequired, UnknownError), + ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil, + http4sPartialFunction = Some(updatePaymentCancellationPsuDataAll) + ) + resourceDocs += ResourceDoc( + null, implementedInApiVersion, + "updatePaymentCancellationPsuDataSelectPsuAuthenticationMethod", + "PUT", "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENT_ID/cancellation-authorisations/AUTHORISATION_ID", + "Update PSU Data for payment initiation cancellation (selectPsuAuthenticationMethod)", + generalUpdatePaymentCancellationPsuDataSummary, + json.parse("""{"authenticationMethodId":""}"""), + json.parse("""{ + "scaStatus": "scaMethodSelected", + "chosenScaMethod": {"authenticationType": "SMS_OTP", "authenticationMethodId": "myAuthenticationID"}, + "challengeData": {"otpMaxLength": 6, "otpFormat": "integer"}, + "_links": {"authoriseTransaction": {"href": "/psd2/v1.3/payments/1234-wertiq-983/authorisations/123auth456"}} + }"""), + List(AuthenticatedUserIsRequired, UnknownError), + ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil, + http4sPartialFunction = Some(updatePaymentCancellationPsuDataAll) + ) + resourceDocs += ResourceDoc( + null, implementedInApiVersion, + "updatePaymentCancellationPsuDataAuthorisationConfirmation", + "PUT", "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENT_ID/cancellation-authorisations/AUTHORISATION_ID", + "Update PSU Data for payment initiation cancellation (authorisationConfirmation)", + generalUpdatePaymentCancellationPsuDataSummary, + json.parse("""{"confirmationCode":"confirmationCode"}"""), + json.parse("""{ + "scaStatus": "finalised", + "_links":{"status": {"href":"/v1.3/payments/sepa-credit-transfers/qwer3456tzui7890/status"}} + }"""), + List(AuthenticatedUserIsRequired, UnknownError), + ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil, + http4sPartialFunction = Some(updatePaymentCancellationPsuDataAll) + ) + + // PUT /PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENT_ID/authorisations/AUTHORISATION_ID — 4 variants + resourceDocs += ResourceDoc( + null, implementedInApiVersion, + "updatePaymentPsuDataTransactionAuthorisation", + "PUT", "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENT_ID/authorisations/AUTHORISATION_ID", + "Update PSU data for payment initiation (transactionAuthorisation)", + generalUpdatePaymentPsuDataSummary, + json.parse("""{"scaAuthenticationData":"123"}"""), + json.parse("""{ + "scaStatus": "finalised", + "psuMessage": "Please check your SMS at a mobile device.", + "_links": {"scaStatus": {"href":"/v1.3/payments/sepa-credit-transfers/88695566-6642-46d5-9985-0d824624f507"}} + }"""), + List(AuthenticatedUserIsRequired, UnknownError), + ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil, + http4sPartialFunction = Some(updatePaymentPsuDataAll) + ) + resourceDocs += ResourceDoc( + null, implementedInApiVersion, + "updatePaymentPsuDataUpdatePsuAuthentication", + "PUT", "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENT_ID/authorisations/AUTHORISATION_ID", + "Update PSU data for payment initiation (updatePsuAuthentication)", + generalUpdatePaymentPsuDataSummary, + json.parse("""{"psuData": {"password": "start12"}}"""), + json.parse("""{ + "scaStatus": "finalised", + "_links": {"scaStatus": {"href":"/v1.3/payments/sepa-credit-transfers/88695566-6642-46d5-9985-0d824624f507"}} + }"""), + List(AuthenticatedUserIsRequired, UnknownError), + ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil, + http4sPartialFunction = Some(updatePaymentPsuDataAll) + ) + resourceDocs += ResourceDoc( + null, implementedInApiVersion, + "updatePaymentPsuDataSelectPsuAuthenticationMethod", + "PUT", "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENT_ID/authorisations/AUTHORISATION_ID", + "Update PSU data for payment initiation (selectPsuAuthenticationMethod)", + generalUpdatePaymentPsuDataSummary, + json.parse("""{"authenticationMethodId":""}"""), + json.parse("""{ + "scaStatus": "scaMethodSelected", + "chosenScaMethod": {"authenticationType": "SMS_OTP", "authenticationMethodId": "myAuthenticationID"}, + "challengeData": {"otpMaxLength": 6, "otpFormat": "integer"}, + "_links": {"authoriseTransaction": {"href": "/psd2/v1.3/payments/1234-wertiq-983/authorisations/123auth456"}} + }"""), + List(AuthenticatedUserIsRequired, UnknownError), + ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil, + http4sPartialFunction = Some(updatePaymentPsuDataAll) + ) + resourceDocs += ResourceDoc( + null, implementedInApiVersion, + "updatePaymentPsuDataAuthorisationConfirmation", + "PUT", "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENT_ID/authorisations/AUTHORISATION_ID", + "Update PSU data for payment initiation (authorisationConfirmation)", + generalUpdatePaymentPsuDataSummary, + json.parse("""{"confirmationCode":"confirmationCode"}"""), + json.parse("""{ + "scaStatus": "finalised", + "_links":{"status": {"href":"/v1.3/payments/sepa-credit-transfers/qwer3456tzui7890/status"}} + }"""), + List(AuthenticatedUserIsRequired, UnknownError), + ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil, + http4sPartialFunction = Some(updatePaymentPsuDataAll) + ) + } + + // Initialise all ResourceDocs at object-construction time + initCancelAndGetResourceDocs() + initInitiatePaymentResourceDocs() + initStartAuthorisationResourceDocs() + initUpdatePsuDataResourceDocs() + + val routes: HttpRoutes[IO] = Kleisli[HttpF, Request[IO], Response[IO]] { req => + cancelPayment(req) + .orElse(getPaymentCancellationScaStatus(req)) + .orElse(getPaymentInformation(req)) + .orElse(getPaymentInitiationAuthorisation(req)) + .orElse(getPaymentInitiationCancellationAuthorisationInformation(req)) + .orElse(getPaymentInitiationScaStatus(req)) + .orElse(getPaymentInitiationStatus(req)) + .orElse(initiatePayments(req)) + .orElse(initiatePeriodicPayments(req)) + .orElse(initiateBulkPayments(req)) + .orElse(startPaymentAuthorisationAll(req)) + .orElse(startPaymentInitiationCancellationAuthorisationAll(req)) + .orElse(updatePaymentCancellationPsuDataAll(req)) + .orElse(updatePaymentPsuDataAll(req)) + } +} diff --git a/obp-api/src/main/scala/code/api/berlin/group/v1_3/Http4sBGv13SigningBaskets.scala b/obp-api/src/main/scala/code/api/berlin/group/v1_3/Http4sBGv13SigningBaskets.scala new file mode 100644 index 0000000000..3a42a2e592 --- /dev/null +++ b/obp-api/src/main/scala/code/api/berlin/group/v1_3/Http4sBGv13SigningBaskets.scala @@ -0,0 +1,536 @@ +package code.api.berlin.group.v1_3 + +import cats.data.{Kleisli, OptionT} +import cats.effect._ +import code.api.berlin.group.ConstantsBG +import code.api.berlin.group.v1_3.JSONFactory_BERLIN_GROUP_1_3._ +import code.api.util.APIUtil.{EmptyBody, ResourceDoc, connectorEmptyResponse, getSuggestedDefaultScaMethod, mockedDataText, passesPsd2Pisp, unboxFullOrFail} +import code.api.util.ApiTag._ +import code.api.util.ErrorMessages._ +import code.api.util.CustomJsonFormats +import code.api.util.{ApiTag, NewStyle} +import code.api.util.http4s.Http4sRequestAttributes.{EndpointHelpers, RequestOps} +import code.api.util.newstyle.SigningBasketNewStyle +import code.bankconnectors.Connector +import code.signingbaskets.SigningBasketX +import code.util.Helper.{MdcLoggable, booleanToFuture} +import com.github.dwickern.macros.NameOf.nameOf +import com.openbankproject.commons.ExecutionContext.Implicits.global +import com.openbankproject.commons.model.enums.TransactionRequestStatus.{COMPLETED, REJECTED} +import com.openbankproject.commons.model.enums.{ChallengeType, StrongCustomerAuthenticationStatus, SuppliedAnswerType} +import com.openbankproject.commons.model.{ChallengeTrait, TransactionRequestId} +import net.liftweb.common.Empty +import net.liftweb.json +import net.liftweb.json.Formats +import org.http4s._ +import org.http4s.dsl.io._ + +import scala.collection.mutable.ArrayBuffer +import scala.concurrent.Future +import scala.language.implicitConversions + +object Http4sBGv13SigningBaskets extends MdcLoggable { + + type HttpF[A] = OptionT[IO, A] + + implicit val formats: Formats = CustomJsonFormats.formats + + protected implicit def JvalueToSuper(what: net.liftweb.json.JValue): JvalueCaseClass = JvalueCaseClass(what) + + val implementedInApiVersion = ConstantsBG.berlinGroupVersion1 + val resourceDocs = ArrayBuffer[ResourceDoc]() + + val bgV13Prefix = Root / ConstantsBG.berlinGroupVersion1.urlPrefix / ConstantsBG.berlinGroupVersion1.apiShortVersion + + // ── POST /signing-baskets ────────────────────────────────────────────── + val createSigningBasket: HttpRoutes[IO] = HttpRoutes.of[IO] { + case req @ POST -> `bgV13Prefix` / "signing-baskets" => + EndpointHelpers.executeFutureCreated(req) { + val cc = req.callContext + val callContext = Some(cc) + for { + _ <- passesPsd2Pisp(callContext) + failMsg = s"$InvalidJsonFormat The Json body should be the $PostSigningBasketJsonV13 " + postJson <- NewStyle.function.tryons(failMsg, 400, callContext) { + json.parse(cc.httpBody.getOrElse("")).extract[PostSigningBasketJsonV13] + } + _ <- booleanToFuture(failMsg, cc = callContext) { + !(postJson.paymentIds.isEmpty && postJson.consentIds.isEmpty) + } + signingBasket <- Future { + SigningBasketX.signingBasketProvider.vend.createSigningBasket( + postJson.paymentIds, + postJson.consentIds, + ) + }.map(connectorEmptyResponse(_, callContext)) + } yield { + createSigningBasketResponseJson(signingBasket) + } + } + } + + resourceDocs += ResourceDoc( + null, + implementedInApiVersion, + nameOf(createSigningBasket), + "POST", + "/signing-baskets", + "Create a signing basket resource", + s"""${mockedDataText(false)} +Create a signing basket resource for authorising several transactions with one SCA method. +The resource identifications of these transactions are contained in the payload of this access method +""", + PostSigningBasketJsonV13(paymentIds = Some(List("123qwert456789", "12345qwert7899")), None), + json.parse("""{ + "basketId" : "1234-basket-567", + "challengeData" : { + "otpMaxLength" : 0, + "additionalInformation" : "additionalInformation", + "image" : "image", + "imageLink" : "http://example.com/aeiou", + "otpFormat" : "characters", + "data" : [ "data", "data" ] + }, + "scaMethods" : "", + "tppMessages" : [ { + "path" : "path", + "code" : { }, + "text" : { }, + "category" : { } + } ], + "_links" : { + "scaStatus" : "/v1.3/payments/sepa-credit-transfers/1234-wertiq-983", + "startAuthorisation" : "/v1.3/payments/sepa-credit-transfers/1234-wertiq-983", + "status" : "/v1.3/payments/sepa-credit-transfers/1234-wertiq-983" + }, + "chosenScaMethod" : "", + "transactionStatus" : "ACCP", + "psuMessage" : { } +}"""), + List(AuthenticatedUserIsRequired, UnknownError), + apiTagSigningBaskets :: Nil, + http4sPartialFunction = Some(createSigningBasket) + ) + + // ── DELETE /signing-baskets/BASKETID ────────────────────────────────── + val deleteSigningBasket: HttpRoutes[IO] = HttpRoutes.of[IO] { + case req @ DELETE -> `bgV13Prefix` / "signing-baskets" / basketid => + EndpointHelpers.executeDelete(req) { cc => + val callContext = Some(cc) + for { + _ <- passesPsd2Pisp(callContext) + _ <- Future { + SigningBasketX.signingBasketProvider.vend.deleteSigningBasket(basketid) + }.map(connectorEmptyResponse(_, callContext)) + } yield () + } + } + + resourceDocs += ResourceDoc( + null, + implementedInApiVersion, + nameOf(deleteSigningBasket), + "DELETE", + "/signing-baskets/BASKETID", + "Delete the signing basket", + s"""${mockedDataText(false)} +Delete the signing basket structure as long as no (partial) authorisation has yet been applied. +The undlerying transactions are not affected by this deletion. + +Remark: The signing basket as such is not deletable after a first (partial) authorisation has been applied. +Nevertheless, single transactions might be cancelled on an individual basis on the XS2A interface. +""", + EmptyBody, + EmptyBody, + List(AuthenticatedUserIsRequired, UnknownError), + apiTagSigningBaskets :: Nil, + http4sPartialFunction = Some(deleteSigningBasket) + ) + + // ── GET /signing-baskets/BASKETID ───────────────────────────────────── + val getSigningBasket: HttpRoutes[IO] = HttpRoutes.of[IO] { + case req @ GET -> `bgV13Prefix` / "signing-baskets" / basketid => + EndpointHelpers.executeAndRespond(req) { cc => + val callContext = Some(cc) + for { + _ <- passesPsd2Pisp(callContext) + basket <- Future { + SigningBasketX.signingBasketProvider.vend.getSigningBasketByBasketId(basketid) + }.map(connectorEmptyResponse(_, callContext)) + } yield { + getSigningBasketResponseJson(basket) + } + } + } + + resourceDocs += ResourceDoc( + null, + implementedInApiVersion, + nameOf(getSigningBasket), + "GET", + "/signing-baskets/BASKETID", + "Returns the content of an signing basket object.", + s"""${mockedDataText(false)} +Returns the content of an signing basket object.""", + EmptyBody, + json.parse("""{ + "transactionStatus" : "ACCP", + "payments" : "", + "consents" : "" +}"""), + List(AuthenticatedUserIsRequired, UnknownError), + apiTagSigningBaskets :: Nil, + http4sPartialFunction = Some(getSigningBasket) + ) + + // ── GET /signing-baskets/BASKETID/authorisations ────────────────────── + val getSigningBasketAuthorisation: HttpRoutes[IO] = HttpRoutes.of[IO] { + case req @ GET -> `bgV13Prefix` / "signing-baskets" / basketid / "authorisations" => + EndpointHelpers.executeAndRespond(req) { cc => + val callContext = Some(cc) + for { + _ <- passesPsd2Pisp(callContext) + (challenges, _) <- NewStyle.function.getChallengesByBasketId(basketid, callContext) + } yield { + JSONFactory_BERLIN_GROUP_1_3.AuthorisationJsonV13(challenges.map(_.challengeId)) + } + } + } + + resourceDocs += ResourceDoc( + null, + implementedInApiVersion, + nameOf(getSigningBasketAuthorisation), + "GET", + "/signing-baskets/BASKETID/authorisations", + "Get Signing Basket Authorisation Sub-Resources Request", + s"""${mockedDataText(false)} +Read a list of all authorisation subresources IDs which have been created. + +This function returns an array of hyperlinks to all generated authorisation sub-resources. +""", + EmptyBody, + json.parse("""{ + "authorisationIds" : "" +}"""), + List(AuthenticatedUserIsRequired, UnknownError), + apiTagSigningBaskets :: Nil, + http4sPartialFunction = Some(getSigningBasketAuthorisation) + ) + + // ── GET /signing-baskets/BASKETID/authorisations/AUTHORISATIONID ─────── + val getSigningBasketScaStatus: HttpRoutes[IO] = HttpRoutes.of[IO] { + case req @ GET -> `bgV13Prefix` / "signing-baskets" / basketId / "authorisations" / authorisationId => + EndpointHelpers.executeAndRespond(req) { cc => + val callContext = Some(cc) + for { + _ <- passesPsd2Pisp(callContext) + _ <- Future(SigningBasketX.signingBasketProvider.vend.getSigningBasketByBasketId(basketId)) + .map(unboxFullOrFail(_, callContext, s"$ConsentNotFound ($basketId)", 403)) + (challenges, _) <- NewStyle.function.getChallengesByBasketId(basketId, callContext) + } yield { + val challengeStatus = challenges.filter(_.challengeId == authorisationId) + .flatMap(_.scaStatus).headOption.map(_.toString).getOrElse("None") + JSONFactory_BERLIN_GROUP_1_3.ScaStatusJsonV13(challengeStatus) + } + } + } + + resourceDocs += ResourceDoc( + null, + implementedInApiVersion, + nameOf(getSigningBasketScaStatus), + "GET", + "/signing-baskets/BASKETID/authorisations/AUTHORISATIONID", + "Read the SCA status of the signing basket authorisation", + s"""${mockedDataText(false)} +This method returns the SCA status of a signing basket's authorisation sub-resource. +""", + EmptyBody, + json.parse("""{ + "scaStatus" : "psuAuthenticated" +}"""), + List(AuthenticatedUserIsRequired, UnknownError), + apiTagSigningBaskets :: Nil, + http4sPartialFunction = Some(getSigningBasketScaStatus) + ) + + // ── GET /signing-baskets/BASKETID/status ────────────────────────────── + val getSigningBasketStatus: HttpRoutes[IO] = HttpRoutes.of[IO] { + case req @ GET -> `bgV13Prefix` / "signing-baskets" / basketid / "status" => + EndpointHelpers.executeAndRespond(req) { cc => + val callContext = Some(cc) + for { + _ <- passesPsd2Pisp(callContext) + basket <- Future { + SigningBasketX.signingBasketProvider.vend.getSigningBasketByBasketId(basketid) + }.map(connectorEmptyResponse(_, callContext)) + } yield { + getSigningBasketStatusResponseJson(basket) + } + } + } + + resourceDocs += ResourceDoc( + null, + implementedInApiVersion, + nameOf(getSigningBasketStatus), + "GET", + "/signing-baskets/BASKETID/status", + "Read the status of the signing basket", + s"""${mockedDataText(false)} +Returns the status of a signing basket object. +""", + EmptyBody, + json.parse("""{ + "transactionStatus" : "RCVD" +}"""), + List(AuthenticatedUserIsRequired, UnknownError), + apiTagSigningBaskets :: Nil, + http4sPartialFunction = Some(getSigningBasketStatus) + ) + + // ── POST /signing-baskets/BASKETID/authorisations ───────────────────── + val startSigningBasketAuthorisation: HttpRoutes[IO] = HttpRoutes.of[IO] { + case req @ POST -> `bgV13Prefix` / "signing-baskets" / basketId / "authorisations" => + EndpointHelpers.executeFutureCreated(req) { + val cc = req.callContext + val callContext = Some(cc) + for { + _ <- passesPsd2Pisp(callContext) + (challenges, _) <- NewStyle.function.createChallengesC3( + List(cc.user.map(_.userId).openOr("")), + ChallengeType.BERLIN_GROUP_SIGNING_BASKETS_CHALLENGE, + None, + getSuggestedDefaultScaMethod(), + Some(StrongCustomerAuthenticationStatus.received), + None, + Some(basketId), + None, + callContext + ) + challenge <- NewStyle.function.tryons(InvalidConnectorResponseForCreateChallenge, 400, callContext) { + challenges.head + } + } yield { + createStartSigningBasketAuthorisationJson(basketId, challenge) + } + } + } + + resourceDocs += ResourceDoc( + null, + implementedInApiVersion, + nameOf(startSigningBasketAuthorisation), + "POST", + "/signing-baskets/BASKETID/authorisations", + "Start the authorisation process for a signing basket", + s"""${mockedDataText(false)} +Create an authorisation sub-resource and start the authorisation process of a signing basket. +The message might in addition transmit authentication and authorisation related data. + +This method is iterated n times for a n times SCA authorisation in a +corporate context, each creating an own authorisation sub-endpoint for +the corresponding PSU authorising the signing-baskets. + +The ASPSP might make the usage of this access method unnecessary in case +of only one SCA process needed, since the related authorisation resource +might be automatically created by the ASPSP after the submission of the +payment data with the first POST signing basket call. + +The start authorisation process is a process which is needed for creating a new authorisation +or cancellation sub-resource. + +This applies in the following scenarios: + + * The ASPSP has indicated with an 'startAuthorisation' hyperlink in the preceeding Payment + Initiation Response that an explicit start of the authorisation process is needed by the TPP. + The 'startAuthorisation' hyperlink can transport more information about data which needs to be + uploaded by using the extended forms. + * 'startAuthorisationWithPsuIdentfication', + * 'startAuthorisationWithPsuAuthentication' #TODO + * 'startAuthorisationWithAuthentciationMethodSelection' + * The related payment initiation cannot yet be executed since a multilevel SCA is mandated. + * The ASPSP has indicated with an 'startAuthorisation' hyperlink in the preceeding + Payment Cancellation Response that an explicit start of the authorisation process is needed by the TPP. + The 'startAuthorisation' hyperlink can transport more information about data which needs to be uploaded + by using the extended forms as indicated above. + * The related payment cancellation request cannot be applied yet since a multilevel SCA is mandate for + executing the cancellation. + * The signing basket needs to be authorised yet. +""", + EmptyBody, + json.parse("""{ + "challengeData" : { + "otpMaxLength" : 0, + "additionalInformation" : "additionalInformation", + "image" : "image", + "imageLink" : "http://example.com/aeiou", + "otpFormat" : "characters", + "data" : "data" + }, + "scaMethods" : "", + "scaStatus" : "psuAuthenticated", + "_links" : { + "scaStatus" : "/v1.3/payments/sepa-credit-transfers/1234-wertiq-983", + "startAuthorisationWithEncryptedPsuAuthentication" : "/v1.3/payments/sepa-credit-transfers/1234-wertiq-983", + "scaRedirect" : "/v1.3/payments/sepa-credit-transfers/1234-wertiq-983", + "selectAuthenticationMethod" : "/v1.3/payments/sepa-credit-transfers/1234-wertiq-983", + "startAuthorisationWithPsuAuthentication" : "/v1.3/payments/sepa-credit-transfers/1234-wertiq-983", + "authoriseTransaction" : "/v1.3/payments/sepa-credit-transfers/1234-wertiq-983", + "scaOAuth" : "/v1.3/payments/sepa-credit-transfers/1234-wertiq-983", + "updatePsuIdentification" : "/v1.3/payments/sepa-credit-transfers/1234-wertiq-983" + }, + "chosenScaMethod" : "", + "psuMessage" : { } +}"""), + List(AuthenticatedUserIsRequired, UnknownError), + apiTagSigningBaskets :: Nil, + http4sPartialFunction = Some(startSigningBasketAuthorisation) + ) + + // ── PUT /signing-baskets/BASKETID/authorisations/AUTHORISATIONID ─────── + val updateSigningBasketPsuData: HttpRoutes[IO] = HttpRoutes.of[IO] { + case req @ PUT -> `bgV13Prefix` / "signing-baskets" / basketId / "authorisations" / authorisationId => + EndpointHelpers.executeAndRespond(req) { cc => + val callContext = Some(cc) + for { + _ <- passesPsd2Pisp(callContext) + failMsg = s"$InvalidJsonFormat The Json body should be the $UpdatePaymentPsuDataJson " + updateBasketPsuDataJson <- NewStyle.function.tryons(failMsg, 400, callContext) { + json.parse(cc.httpBody.getOrElse("")).extract[UpdatePaymentPsuDataJson] + } + _ <- SigningBasketNewStyle.checkSigningBasketPayments(basketId, callContext) + (boxedChallenge, _) <- NewStyle.function.validateChallengeAnswerC5( + ChallengeType.BERLIN_GROUP_SIGNING_BASKETS_CHALLENGE, + None, + None, + Some(basketId), + authorisationId, + updateBasketPsuDataJson.scaAuthenticationData, + SuppliedAnswerType.PLAIN_TEXT_VALUE, + callContext + ) + (challenge, updatedCC) <- NewStyle.function.getChallenge(authorisationId, callContext) + _ <- challenge.scaStatus match { + case Some(status) if status.toString == StrongCustomerAuthenticationStatus.finalised.toString => + Future { + val basket = SigningBasketX.signingBasketProvider.vend.getSigningBasketByBasketId(basketId) + val existAll = + basket.flatMap(_.payments.map(_.forall(i => Connector.connector.vend.getTransactionRequestImpl(TransactionRequestId(i), updatedCC).isDefined))) + val alreadyCompleted: List[String] = + basket.flatMap(_.payments).getOrElse(Nil).filter { i => + Connector.connector.vend.getTransactionRequestImpl(TransactionRequestId(i), updatedCC) + .exists(_._1.status == COMPLETED.toString) + } + if (alreadyCompleted.nonEmpty) { + unboxFullOrFail(Empty, updatedCC, s"$InvalidConnectorResponse Some of paymentIds [${alreadyCompleted.mkString(",")}] are already completed") + } else if (existAll.getOrElse(false)) { + basket.map { i => + i.payments.map(_.map { i => + NewStyle.function.saveTransactionRequestStatusImpl(TransactionRequestId(i), COMPLETED.toString, updatedCC) + Connector.connector.vend.getTransactionRequestImpl(TransactionRequestId(i), updatedCC).map { t => + Connector.connector.vend.makePaymentV400(t._1, None, updatedCC) + } + }) + } + SigningBasketX.signingBasketProvider.vend.saveSigningBasketStatus(basketId, ConstantsBG.SigningBasketsStatus.ACTC.toString) + unboxFullOrFail(boxedChallenge, updatedCC, s"$InvalidConnectorResponse validateChallengeAnswerC5") + } else { + val paymentIds = basket.flatMap(_.payments).getOrElse(Nil).mkString(",") + unboxFullOrFail(Empty, updatedCC, s"$InvalidConnectorResponse Some of paymentIds [${paymentIds}] are invalid") + } + } + case Some(status) if status.toString == StrongCustomerAuthenticationStatus.failed.toString => + Future { + val basket = SigningBasketX.signingBasketProvider.vend.getSigningBasketByBasketId(basketId) + basket.map { i => + i.payments.map(_.map { i => + NewStyle.function.saveTransactionRequestStatusImpl(TransactionRequestId(i), REJECTED.toString, updatedCC) + }) + } + unboxFullOrFail(boxedChallenge, updatedCC, s"$InvalidConnectorResponse validateChallengeAnswerC5") + } + case _ => + Future(unboxFullOrFail(Empty, updatedCC, s"$InvalidConnectorResponse getChallenge")) + } + } yield { + JSONFactory_BERLIN_GROUP_1_3.createStartPaymentAuthorisationJson(challenge) + } + } + } + + resourceDocs += ResourceDoc( + null, + implementedInApiVersion, + nameOf(updateSigningBasketPsuData), + "PUT", + "/signing-baskets/BASKETID/authorisations/AUTHORISATIONID", + "Update PSU Data for signing basket", + s"""${mockedDataText(false)} +This method update PSU data on the signing basket resource if needed. +It may authorise a igning basket within the Embedded SCA Approach where needed. + +Independently from the SCA Approach it supports e.g. the selection of +the authentication method and a non-SCA PSU authentication. + +This methods updates PSU data on the cancellation authorisation resource if needed. + +There are several possible Update PSU Data requests in the context of a consent request if needed, +which depends on the SCA approach: + +* Redirect SCA Approach: + A specific Update PSU Data Request is applicable for + * the selection of authentication methods, before choosing the actual SCA approach. +* Decoupled SCA Approach: + A specific Update PSU Data Request is only applicable for + * adding the PSU Identification, if not provided yet in the Payment Initiation Request or the Account Information Consent Request, or if no OAuth2 access token is used, or + * the selection of authentication methods. +* Embedded SCA Approach: + The Update PSU Data Request might be used + * to add credentials as a first factor authentication data of the PSU and + * to select the authentication method and + * transaction authorisation. + +The SCA Approach might depend on the chosen SCA method. +For that reason, the following possible Update PSU Data request can apply to all SCA approaches: + +* Select an SCA method in case of several SCA methods are available for the customer. + +There are the following request types on this access path: + * Update PSU Identification + * Update PSU Authentication + * Select PSU Autorization Method + WARNING: This method need a reduced header, + therefore many optional elements are not present. + Maybe in a later version the access path will change. + * Transaction Authorisation + WARNING: This method need a reduced header, + therefore many optional elements are not present. + Maybe in a later version the access path will change. +""", + json.parse("""{"scaAuthenticationData":"123"}"""), + json.parse("""{ + "scaStatus":"finalised", + "authorisationId":"4f4a8b7f-9968-4183-92ab-ca512b396bfc", + "psuMessage":"Please check your SMS at a mobile device.", + "_links":{ + "scaStatus":"/v1.3/payments/sepa-credit-transfers/PAYMENT_ID/4f4a8b7f-9968-4183-92ab-ca512b396bfc" + } + }"""), + List(AuthenticatedUserIsRequired, UnknownError), + apiTagSigningBaskets :: Nil, + http4sPartialFunction = Some(updateSigningBasketPsuData) + ) + + val routes: HttpRoutes[IO] = Kleisli[HttpF, Request[IO], Response[IO]] { req => + createSigningBasket(req) + .orElse(deleteSigningBasket(req)) + .orElse(getSigningBasket(req)) + .orElse(getSigningBasketAuthorisation(req)) + .orElse(getSigningBasketScaStatus(req)) + .orElse(getSigningBasketStatus(req)) + .orElse(startSigningBasketAuthorisation(req)) + .orElse(updateSigningBasketPsuData(req)) + } +} diff --git a/obp-api/src/main/scala/code/api/berlin/group/v1_3/OBP_BERLIN_GROUP_1_3.scala b/obp-api/src/main/scala/code/api/berlin/group/v1_3/OBP_BERLIN_GROUP_1_3.scala index 0fea84db81..9aa60b48c0 100644 --- a/obp-api/src/main/scala/code/api/berlin/group/v1_3/OBP_BERLIN_GROUP_1_3.scala +++ b/obp-api/src/main/scala/code/api/berlin/group/v1_3/OBP_BERLIN_GROUP_1_3.scala @@ -33,48 +33,53 @@ package code.api.berlin.group.v1_3 import code.api.OBPRestHelper import code.api.berlin.group.ConstantsBG -import code.api.builder.AccountInformationServiceAISApi.APIMethods_AccountInformationServiceAISApi -import code.api.builder.CommonServicesApi.APIMethods_CommonServicesApi -import code.api.builder.ConfirmationOfFundsServicePIISApi.APIMethods_ConfirmationOfFundsServicePIISApi -import code.api.builder.PaymentInitiationServicePISApi.APIMethods_PaymentInitiationServicePISApi -import code.api.builder.SigningBasketsApi.APIMethods_SigningBasketsApi -import code.api.util.APIUtil.{OBPEndpoint, ResourceDoc, getAllowedEndpoints} +import code.api.util.APIUtil.{OBPEndpoint, ResourceDoc} import code.api.util.ScannedApis import code.util.Helper.MdcLoggable -import com.openbankproject.commons.util.{ApiVersion, ApiVersionStatus, ScannedApiVersion} +import com.openbankproject.commons.util.{ApiVersionStatus, ScannedApiVersion} import scala.collection.mutable.ArrayBuffer - - - /* -This file defines which endpoints from all the versions are available in v1 + * All BG v1.3 endpoints have been migrated to their respective + * Http4sBGv13* objects (AIS/PIS/PIIS/SigningBaskets — 55 endpoints). + * + * This aggregator is retained for ScannedApis registration (class-path scanning) + * and so that external callers that access OBP_BERLIN_GROUP_1_3.apiVersion / + * .allResourceDocs continue to compile. + * Routes are served by Http4sBGv13.wrappedRoutes in Http4sApp (ahead of the + * Lift bridge). */ object OBP_BERLIN_GROUP_1_3 extends OBPRestHelper with MdcLoggable with ScannedApis { - override val apiVersion = ConstantsBG.berlinGroupVersion1 - val versionStatus = ApiVersionStatus.DRAFT.toString - - val endpoints = - APIMethods_AccountInformationServiceAISApi.endpoints ++ - APIMethods_ConfirmationOfFundsServicePIISApi.endpoints ++ - APIMethods_PaymentInitiationServicePISApi.endpoints ++ - APIMethods_SigningBasketsApi.endpoints ++ - APIMethods_CommonServicesApi.endpoints + override val apiVersion: ScannedApiVersion = ConstantsBG.berlinGroupVersion1 + val versionStatus: String = ApiVersionStatus.DRAFT.toString - override val allResourceDocs: ArrayBuffer[ResourceDoc] = - APIMethods_AccountInformationServiceAISApi.resourceDocs ++ - APIMethods_ConfirmationOfFundsServicePIISApi.resourceDocs ++ - APIMethods_PaymentInitiationServicePISApi.resourceDocs ++ - APIMethods_SigningBasketsApi.resourceDocs ++ - APIMethods_CommonServicesApi.resourceDocs + override val allResourceDocs: ArrayBuffer[ResourceDoc] = Http4sBGv13.resourceDocs - // Filter the possible endpoints by the disabled / enabled Props settings and add them together - override val routes : List[OBPEndpoint] = getAllowedEndpoints(endpoints, allResourceDocs) - - // Make them available for use! - registerRoutes(routes, allResourceDocs, apiPrefix) - - logger.info(s"version $version has been run! There are ${routes.length} routes.") + override val routes: List[OBPEndpoint] = Nil } + +// ─── Original Lift aggregator (commented out) ──────────────────────────────── +// The 5 builder *Api.scala files (AccountInformationServiceAISApi, +// PaymentInitiationServicePISApi, ConfirmationOfFundsServicePIISApi, +// SigningBasketsApi, CommonServicesApi) have been commented out. +// Their endpoints are now served by the corresponding Http4sBGv13* objects. +// +// val endpoints = +// APIMethods_AccountInformationServiceAISApi.endpoints ++ +// APIMethods_ConfirmationOfFundsServicePIISApi.endpoints ++ +// APIMethods_PaymentInitiationServicePISApi.endpoints ++ +// APIMethods_SigningBasketsApi.endpoints ++ +// APIMethods_CommonServicesApi.endpoints +// +// override val allResourceDocs: ArrayBuffer[ResourceDoc] = +// APIMethods_AccountInformationServiceAISApi.resourceDocs ++ +// APIMethods_ConfirmationOfFundsServicePIISApi.resourceDocs ++ +// APIMethods_PaymentInitiationServicePISApi.resourceDocs ++ +// APIMethods_SigningBasketsApi.resourceDocs ++ +// APIMethods_CommonServicesApi.resourceDocs +// +// override val routes : List[OBPEndpoint] = getAllowedEndpoints(endpoints, allResourceDocs) +// registerRoutes(routes, allResourceDocs, apiPrefix) +// logger.info(s"version $version has been run! There are ${routes.length} routes.") diff --git a/obp-api/src/main/scala/code/api/berlin/group/v1_3/OBP_BERLIN_GROUP_1_3_Alias.scala b/obp-api/src/main/scala/code/api/berlin/group/v1_3/OBP_BERLIN_GROUP_1_3_Alias.scala index 35eaab593a..7f23087689 100644 --- a/obp-api/src/main/scala/code/api/berlin/group/v1_3/OBP_BERLIN_GROUP_1_3_Alias.scala +++ b/obp-api/src/main/scala/code/api/berlin/group/v1_3/OBP_BERLIN_GROUP_1_3_Alias.scala @@ -32,33 +32,43 @@ package code.api.berlin.group.v1_3 import code.api.OBPRestHelper -import code.api.util.APIUtil.{OBPEndpoint, ResourceDoc, berlinGroupV13AliasPath, getAllowedEndpoints} +import code.api.util.APIUtil.{OBPEndpoint, ResourceDoc, berlinGroupV13AliasPath} import code.api.util.ScannedApis import code.util.Helper.MdcLoggable import com.openbankproject.commons.util.{ApiVersionStatus, ScannedApiVersion} + import scala.collection.mutable.ArrayBuffer /* -This file defines which endpoints from all the versions are available in v1 + * All BG v1.3 alias endpoints are now served natively by Http4sBGv13Alias.wrappedRoutes. + * + * This aggregator is retained for ScannedApis registration (class-path scanning). + * Routes are served by Http4sBGv13Alias.wrappedRoutes in Http4sApp. */ object OBP_BERLIN_GROUP_1_3_Alias extends OBPRestHelper with MdcLoggable with ScannedApis { - override val apiVersion = ScannedApiVersion(berlinGroupV13AliasPath.head, berlinGroupV13AliasPath.head, berlinGroupV13AliasPath.last) - val versionStatus = ApiVersionStatus.DRAFT.toString - override val allResourceDocs: ArrayBuffer[ResourceDoc] = if(berlinGroupV13AliasPath.nonEmpty){ - OBP_BERLIN_GROUP_1_3.allResourceDocs.map(resourceDoc => resourceDoc.copy( - implementedInApiVersion = apiVersion.copy(apiStandard = resourceDoc.implementedInApiVersion.apiStandard), - )) - } else ArrayBuffer.empty[ResourceDoc] + override val apiVersion: ScannedApiVersion = + ScannedApiVersion(berlinGroupV13AliasPath.head, berlinGroupV13AliasPath.head, berlinGroupV13AliasPath.last) + + val versionStatus: String = ApiVersionStatus.DRAFT.toString - // Filter the possible endpoints by the disabled / enabled Props settings and add them together - override val routes: List[OBPEndpoint] = if(berlinGroupV13AliasPath.nonEmpty){ - getAllowedEndpoints(OBP_BERLIN_GROUP_1_3.endpoints, allResourceDocs) - } else List.empty[OBPEndpoint] + override val allResourceDocs: ArrayBuffer[ResourceDoc] = Http4sBGv13Alias.resourceDocs - // Make them available for use! - if(berlinGroupV13AliasPath.nonEmpty){ - registerRoutes(routes, allResourceDocs, apiPrefix) - logger.info(s"version $apiVersion has been run! There are ${routes.length} routes.") - } + override val routes: List[OBPEndpoint] = Nil } + +// ─── Original Lift aggregator (commented out) ──────────────────────────────── +// override val allResourceDocs: ArrayBuffer[ResourceDoc] = if(berlinGroupV13AliasPath.nonEmpty){ +// OBP_BERLIN_GROUP_1_3.allResourceDocs.map(resourceDoc => resourceDoc.copy( +// implementedInApiVersion = apiVersion.copy(apiStandard = resourceDoc.implementedInApiVersion.apiStandard), +// )) +// } else ArrayBuffer.empty[ResourceDoc] +// +// override val routes: List[OBPEndpoint] = if(berlinGroupV13AliasPath.nonEmpty){ +// getAllowedEndpoints(OBP_BERLIN_GROUP_1_3.endpoints, allResourceDocs) +// } else List.empty[OBPEndpoint] +// +// if(berlinGroupV13AliasPath.nonEmpty){ +// registerRoutes(routes, allResourceDocs, apiPrefix) +// logger.info(s"version $apiVersion has been run! There are ${routes.length} routes.") +// } diff --git a/obp-api/src/main/scala/code/api/berlin/group/v1_3/PaymentInitiationServicePISApi.scala b/obp-api/src/main/scala/code/api/berlin/group/v1_3/PaymentInitiationServicePISApi.scala index f968518725..6364c32440 100644 --- a/obp-api/src/main/scala/code/api/berlin/group/v1_3/PaymentInitiationServicePISApi.scala +++ b/obp-api/src/main/scala/code/api/berlin/group/v1_3/PaymentInitiationServicePISApi.scala @@ -1,1632 +1,1632 @@ -package code.api.builder.PaymentInitiationServicePISApi - -import scala.language.implicitConversions -import code.api.berlin.group.ConstantsBG -import code.api.berlin.group.v1_3.JSONFactory_BERLIN_GROUP_1_3.{CancelPaymentResponseJson, CancelPaymentResponseLinks, LinkHrefJson, UpdatePaymentPsuDataJson, checkAuthorisationConfirmation, checkSelectPsuAuthenticationMethod, checkTransactionAuthorisation, checkUpdatePsuAuthentication, createCancellationTransactionRequestJson} -import code.api.berlin.group.v1_3.model.TransactionStatus.mapTransactionStatus -import code.api.berlin.group.v1_3.model._ -import code.api.berlin.group.v1_3.{JSONFactory_BERLIN_GROUP_1_3, JvalueCaseClass} -import code.api.util.APIUtil._ -import code.api.util.ApiTag._ -import code.api.util.ErrorMessages._ -import code.api.util.NewStyle.HttpCode -import code.api.util.{ApiTag, CallContext, NewStyle} -import code.fx.fx -import code.util.Helper -import com.github.dwickern.macros.NameOf.nameOf -import com.openbankproject.commons.ExecutionContext.Implicits.global -import com.openbankproject.commons.model._ -import com.openbankproject.commons.model.enums.TransactionRequestStatus._ -import com.openbankproject.commons.model.enums.TransactionRequestTypes._ -import com.openbankproject.commons.model.enums.{TransactionRequestStatus, _} -import net.liftweb -import net.liftweb.common.Box.tryo -import net.liftweb.common.Full -import net.liftweb.http.js.JE.JsRaw -import net.liftweb.http.rest.RestHelper -import net.liftweb.json -import net.liftweb.json._ - -import scala.collection.mutable.ArrayBuffer -import scala.concurrent.Future - -object APIMethods_PaymentInitiationServicePISApi extends RestHelper { - val apiVersion = ConstantsBG.berlinGroupVersion1 - val resourceDocs = ArrayBuffer[ResourceDoc]() - val apiRelations = ArrayBuffer[ApiRelation]() - protected implicit def JvalueToSuper(what: JValue): JvalueCaseClass = JvalueCaseClass(what) - - def checkPaymentServerTypeError(paymentService: String) = { - val ccc = "" - s"${InvalidTransactionRequestType.replaceAll("TRANSACTION_REQUEST_TYPE", "PAYMENT_SERVICE in the URL.")}: '${paymentService}'.It should be `payments` or `periodic-payments` for now, will support `bulk-payments` soon" - } - def checkPaymentProductError(paymentProduct: String) = s"${InvalidTransactionRequestType.replaceAll("TRANSACTION_REQUEST_TYPE", "PAYMENT_PRODUCT in the URL.")}: '${paymentProduct}'.It should be `sepa-credit-transfers`for now, will support (instant-sepa-credit-transfers, target-2-payments, cross-border-credit-transfers) soon." - - def checkPaymentServiceType(paymentService: String) = tryo { - PaymentServiceTypes.withName(paymentService.replaceAll("-","_")) - }.isDefined - - val endpoints = - cancelPayment :: - getPaymentCancellationScaStatus :: - getPaymentInformation :: - getPaymentInitiationAuthorisation :: - getPaymentInitiationCancellationAuthorisationInformation :: - getPaymentInitiationScaStatus :: - getPaymentInitiationStatus :: - initiatePayments :: - initiateBulkPayments :: - initiatePeriodicPayments :: - startPaymentAuthorisationUpdatePsuAuthentication :: - startPaymentAuthorisationTransactionAuthorisation :: - startPaymentAuthorisationSelectPsuAuthenticationMethod :: - startPaymentInitiationCancellationAuthorisationTransactionAuthorisation :: - startPaymentInitiationCancellationAuthorisationUpdatePsuAuthentication :: - startPaymentInitiationCancellationAuthorisationSelectPsuAuthenticationMethod :: - updatePaymentCancellationPsuDataUpdatePsuAuthentication :: - updatePaymentCancellationPsuDataTransactionAuthorisation :: - updatePaymentCancellationPsuDataSelectPsuAuthenticationMethod :: - updatePaymentCancellationPsuDataAuthorisationConfirmation :: - updatePaymentPsuDataTransactionAuthorisation :: - updatePaymentPsuDataAuthorisationConfirmation :: - updatePaymentPsuDataSelectPsuAuthenticationMethod :: - updatePaymentPsuDataAuthorisationConfirmation :: - Nil - - - resourceDocs += ResourceDoc( - cancelPayment, - apiVersion, - nameOf(cancelPayment), - "DELETE", - "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENTID", - "Payment Cancellation Request", - s"""${mockedDataText(false)} -This method initiates the cancellation of a payment. Depending on the payment-service, the payment-product -and the ASPSP's implementation, this TPP call might be sufficient to cancel a payment. If an authorisation -of the payment cancellation is mandated by the ASPSP, a corresponding hyperlink will be contained in the -response message. Cancels the addressed payment with resource identification paymentId if applicable to the -payment-service, payment-product and received in product related timelines (e.g. before end of business day -for scheduled payments of the last business day before the scheduled execution day). The response to this -DELETE command will tell the TPP whether the * access method was rejected * access method was successful, -or * access method is generally applicable, but further authorisation processes are needed. -""", - EmptyBody, - CancelPaymentResponseJson( - "ACTC", - _links = CancelPaymentResponseLinks( - self = LinkHrefJson(s"/v1.3/payments/sepa-credit-transfers/1234-wertiq-983"), - status = LinkHrefJson(s"/v1.3/payments/sepa-credit-transfers/1234-wertiq-983/status"), - startAuthorisation = LinkHrefJson(s"/v1.3/payments/sepa-credit-transfers/cancellation-authorisations/1234-wertiq-983/status") - ) - ), - List(AuthenticatedUserIsRequired, UnknownError), - ApiTag("Payment Initiation Service (PIS)") :: Nil - ) - - lazy val cancelPayment : OBPEndpoint = { - case paymentService :: paymentProduct :: paymentId :: Nil JsonDelete _ => { - cc => - for { - (Full(u), callContext) <- authenticatedAccess(cc) - _ <- passesPsd2Pisp(callContext) - _ <- NewStyle.function.tryons(checkPaymentServerTypeError(paymentService),404, callContext) { - PaymentServiceTypes.withName(paymentService.replaceAll("-","_")) - } - transactionRequestTypes <- NewStyle.function.tryons(checkPaymentProductError(paymentProduct),404, callContext) { - TransactionRequestTypes.withName(paymentProduct.replaceAll("-","_").toUpperCase) - } - (transactionRequest, callContext) <- NewStyle.function.getTransactionRequestImpl(TransactionRequestId(paymentId), callContext) - - transactionRequestBody <- NewStyle.function.tryons(s"${UnknownError} No data for Payment Body ",400, callContext) { - transactionRequest.body.to_sepa_credit_transfers.get - } - fromAccountIban = transactionRequestBody.debtorAccount.iban - toAccountIban = transactionRequestBody.creditorAccount.iban - (_, callContext) <- NewStyle.function.getBankAccountByIban(fromAccountIban, callContext) - (ibanChecker, callContext) <- NewStyle.function.validateAndCheckIbanNumber(toAccountIban, callContext) - _ <- Helper.booleanToFuture(invalidIban, cc=callContext) { ibanChecker.isValid == true } - (_, callContext) <- NewStyle.function.getToBankAccountByIban(toAccountIban, callContext) - // Check payment status and determine if cancellation is allowed - currentStatus = transactionRequest.status.toUpperCase() - mappedStatus = mapTransactionStatus(currentStatus) - (canBeCancelled, _, startSca) <- transactionRequestTypes match { - case TransactionRequestTypes.SEPA_CREDIT_TRANSFERS => { - currentStatus match { - case TransactionStatus.RCVD.code | "INITIATED" => - // INITIATED status (maps to RCVD externally) - direct cancellation - NewStyle.function.saveTransactionRequestStatusImpl(transactionRequest.id, CANCELLED.toString, callContext) - Future(true, callContext, Some(false)) - case TransactionStatus.ACCP.code | "COMPLETED" => - // ACCP status - may require SCA for cancellation - NewStyle.function.cancelPaymentV400(TransactionId(transactionRequest.transaction_ids), callContext) map { - x => x._1 match { - case CancelPayment(true, Some(startSca)) if startSca == true => - NewStyle.function.saveTransactionRequestStatusImpl(transactionRequest.id, CANCELLATION_PENDING.toString, callContext) - (true, x._2, Some(startSca)) - case CancelPayment(true, Some(startSca)) if startSca == false => - NewStyle.function.saveTransactionRequestStatusImpl(transactionRequest.id, CANCELLED.toString, callContext) - (true, x._2, Some(startSca)) - case CancelPayment(false, _) => - (false, x._2, Some(false)) - } - } - case TransactionStatus.PDNG.code | "PENDING" => - // PENDING status (maps to PDNG externally) - may require SCA - NewStyle.function.cancelPaymentV400(TransactionId(transactionRequest.transaction_ids), callContext) map { - x => x._1 match { - case CancelPayment(true, Some(startSca)) if startSca == true => - NewStyle.function.saveTransactionRequestStatusImpl(transactionRequest.id, CANCELLATION_PENDING.toString, callContext) - (true, x._2, Some(startSca)) - case CancelPayment(true, Some(startSca)) if startSca == false => - NewStyle.function.saveTransactionRequestStatusImpl(transactionRequest.id, CANCELLED.toString, callContext) - (true, x._2, Some(startSca)) - case CancelPayment(false, _) => - (false, x._2, Some(false)) - } - } - case TransactionStatus.CANC.code | "CANCELLED" => - // Already cancelled - return success - Future(true, callContext, Some(false)) - case _ => - // Other statuses cannot be cancelled - Future(false, callContext, Some(false)) - } - } - } - _ <- Helper.booleanToFuture( - failMsg = s"$TransactionRequestCannotBeCancelled Payment status: $mappedStatus. Only payments in RCVD, ACCP, PDNG, or CANC status can be cancelled.", - cc = callContext - ) { canBeCancelled == true } - (updatedTransactionRequest, callContext) <- NewStyle.function.getTransactionRequestImpl(TransactionRequestId(paymentId), callContext) - } yield { - startSca.getOrElse(false) match { - case true => (createCancellationTransactionRequestJson(updatedTransactionRequest), HttpCode.`202`(callContext)) - case false => (JsRaw(""), HttpCode.`204`(callContext)) - } - } - } - } - - resourceDocs += ResourceDoc( - getPaymentCancellationScaStatus, - apiVersion, - nameOf(getPaymentCancellationScaStatus), - "GET", - "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENTID/cancellation-authorisations/CANCELLATIONID", - "Read the SCA status of the payment cancellation's authorisation.", - s"""${mockedDataText(false)} -This method returns the SCA status of a payment initiation's authorisation sub-resource. -""", - EmptyBody, - json.parse("""{ - "scaStatus" : "psuAuthenticated" -}"""), - List(AuthenticatedUserIsRequired, UnknownError), - ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil - ) - - lazy val getPaymentCancellationScaStatus : OBPEndpoint = { - case paymentService :: paymentProduct :: paymentId:: "cancellation-authorisations" :: cancellationId :: Nil JsonGet _ => { - cc => - for { - (_, callContext) <- authenticatedAccess(cc) - _ <- passesPsd2Pisp(callContext) - _ <- NewStyle.function.tryons(checkPaymentServerTypeError(paymentService),404, callContext) { - PaymentServiceTypes.withName(paymentService.replaceAll("-","_")) - } - _ <- NewStyle.function.tryons(checkPaymentProductError(paymentProduct),404, callContext) { - TransactionRequestTypes.withName(paymentProduct.replaceAll("-","_").toUpperCase) - } - (_, callContext) <- NewStyle.function.getTransactionRequestImpl(TransactionRequestId(paymentId), callContext) - (challenge, callContext) <- NewStyle.function.getChallenge(cancellationId, callContext) - } yield { - (JSONFactory_BERLIN_GROUP_1_3.ScaStatusJsonV13(challenge.scaStatus.map(_.toString).getOrElse("None")), HttpCode.`200`(callContext)) - } - } - } - - resourceDocs += ResourceDoc( - getPaymentInformation, - apiVersion, - nameOf(getPaymentInformation), - "GET", - "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENTID", - "Get Payment Information", - s"""${mockedDataText(false)} -Returns the content of a payment object""", - EmptyBody, - json.parse("""{ - "debtorAccount":{ - "iban":"GR12 1234 5123 4511 3981 4475 477" - }, - "instructedAmount":{ - "currency":"EUR", - "amount":"1234" - }, - "creditorAccount":{ - "iban":"GR12 1234 5123 4514 4575 3645 077" - }, - "creditorName":"70charname" - }"""), - List(AuthenticatedUserIsRequired, UnknownError), - ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM ::Nil - ) - - lazy val getPaymentInformation : OBPEndpoint = { - case paymentService :: paymentProduct :: paymentId :: Nil JsonGet _ if checkPaymentServiceType(paymentService) => { - cc => - for { - (Full(u), callContext) <- authenticatedAccess(cc) - _ <- passesPsd2Pisp(callContext) - _ <- NewStyle.function.tryons(checkPaymentServerTypeError(paymentService),404, callContext) { - PaymentServiceTypes.withName(paymentService.replaceAll("-","_")) - } - transactionRequestTypes <- NewStyle.function.tryons(checkPaymentProductError(paymentProduct),404, callContext) { - TransactionRequestTypes.withName(paymentProduct.replaceAll("-","_").toUpperCase) - } - (transactionRequest, callContext) <- NewStyle.function.getTransactionRequestImpl(TransactionRequestId(paymentId), callContext) - - transactionRequestBody <- NewStyle.function.tryons(s"${UnknownError} No data for Payment Body ",400, callContext) { - transactionRequest.body.to_sepa_credit_transfers.get - } - - } yield { - (transactionRequestBody, callContext) - } - } - } - - resourceDocs += ResourceDoc( - getPaymentInitiationAuthorisation, - apiVersion, - nameOf(getPaymentInitiationAuthorisation), - "GET", - "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENTID/authorisations", - "Get Payment Initiation Authorisation Sub-Resources Request", - s"""${mockedDataText(false)} -Read a list of all authorisation subresources IDs which have been created. - -This function returns an array of hyperlinks to all generated authorisation sub-resources. -""", - EmptyBody, - json.parse("""[ - { - "scaStatus": "received", - "authorisationId": "940948c7-1c86-4d88-977e-e739bf2c1492", - "psuMessage": "Please check your SMS at a mobile device.", - "_links": { - "scaStatus": "/v1.3/payments/sepa-credit-transfers/940948c7-1c86-4d88-977e-e739bf2c1492" - } - }, - { - "scaStatus": "received", - "authorisationId": "0ae75eee-deba-41d6-8116-1a4d6e05dd83", - "psuMessage": "Please check your SMS at a mobile device.", - "_links": { - "scaStatus": "/v1.3/payments/sepa-credit-transfers/0ae75eee-deba-41d6-8116-1a4d6e05dd83" - } - } - ]"""), - List(AuthenticatedUserIsRequired, UnknownError), - ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil - ) - - lazy val getPaymentInitiationAuthorisation : OBPEndpoint = { - case paymentService :: paymentProduct :: paymentId :: "authorisations" :: Nil JsonGet _ => { - cc => - for { - (_, callContext) <- authenticatedAccess(cc) - _ <- passesPsd2Pisp(callContext) - _ <- NewStyle.function.tryons(checkPaymentServerTypeError(paymentService),404, callContext) { - PaymentServiceTypes.withName(paymentService.replaceAll("-","_")) - } - _ <- NewStyle.function.tryons(checkPaymentProductError(paymentProduct),404, callContext) { - TransactionRequestTypes.withName(paymentProduct.replaceAll("-","_").toUpperCase) - } - (_, callContext) <- NewStyle.function.getTransactionRequestImpl(TransactionRequestId(paymentId), callContext) - (challenges, callContext) <- NewStyle.function.getChallengesByTransactionRequestId(paymentId, callContext) - } yield { - (JSONFactory_BERLIN_GROUP_1_3.createStartPaymentAuthorisationsJson(challenges), callContext) - } - } - } - - resourceDocs += ResourceDoc( - getPaymentInitiationCancellationAuthorisationInformation, - apiVersion, - nameOf(getPaymentInitiationCancellationAuthorisationInformation), - "GET", - "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENTID/cancellation-authorisations", - "Get Cancellation Authorisation Sub-Resources Request", - s"""${mockedDataText(false)} -Retrieve a list of all created cancellation authorisation sub-resources. -""", - EmptyBody, - json.parse("""{ - "cancellationIds" : ["faa3657e-13f0-4feb-a6c3-34bf21a9ae8e]" -}"""), - List(AuthenticatedUserIsRequired, UnknownError), - ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil - ) - - lazy val getPaymentInitiationCancellationAuthorisationInformation : OBPEndpoint = { - case paymentService :: paymentProduct :: paymentId :: "cancellation-authorisations" :: Nil JsonGet _ => { - cc => - for { - (_, callContext) <- authenticatedAccess(cc) - _ <- passesPsd2Pisp(callContext) - _ <- NewStyle.function.tryons(checkPaymentServerTypeError(paymentService),404, callContext) { - PaymentServiceTypes.withName(paymentService.replaceAll("-","_")) - } - _ <- NewStyle.function.tryons(checkPaymentProductError(paymentProduct),404, callContext) { - TransactionRequestTypes.withName(paymentProduct.replaceAll("-","_").toUpperCase) - } - (challenges, callContext) <- NewStyle.function.getChallengesByTransactionRequestId(paymentId, callContext) - } yield { - (JSONFactory_BERLIN_GROUP_1_3.CancellationJsonV13(challenges.map(_.challengeId)), callContext) - } - } - } - - resourceDocs += ResourceDoc( - getPaymentInitiationScaStatus, - apiVersion, - nameOf(getPaymentInitiationScaStatus), - "GET", - "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENT_ID/authorisations/AUTHORISATION_ID", - "Read the SCA Status of the payment authorisation", - s"""${mockedDataText(false)} -This method returns the SCA status of a payment initiation's authorisation sub-resource. -""", - EmptyBody, - json.parse("""{ - "scaStatus" : "psuAuthenticated" -}"""), - List(AuthenticatedUserIsRequired, UnknownError), - ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil - ) - - lazy val getPaymentInitiationScaStatus : OBPEndpoint = { - case paymentService :: paymentProduct :: paymentId:: "authorisations" :: authorisationid :: Nil JsonGet _ => { - cc => - for { - (Full(u), callContext) <- authenticatedAccess(cc) - _ <- passesPsd2Pisp(callContext) - _ <- NewStyle.function.tryons(checkPaymentServerTypeError(paymentService),404, callContext) { - PaymentServiceTypes.withName(paymentService.replaceAll("-","_")) - } - _ <- NewStyle.function.tryons(checkPaymentProductError(paymentProduct),404, callContext) { - TransactionRequestTypes.withName(paymentProduct.replaceAll("-","_").toUpperCase) - } - (_, callContext) <- NewStyle.function.getTransactionRequestImpl(TransactionRequestId(paymentId), callContext) - (challenge, callContext) <- NewStyle.function.getChallenge(authorisationid, callContext) - - } yield { - (json.parse( - s"""{"scaStatus" : "${challenge.scaStatus.getOrElse("None")}"}"""), callContext) - } - } - } - - resourceDocs += ResourceDoc( - getPaymentInitiationStatus, - apiVersion, - nameOf(getPaymentInitiationStatus), - "GET", - "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENT_ID/status", - "Payment initiation status request", - s"""${mockedDataText(false)} -Check the transaction status of a payment initiation.""", - EmptyBody, - json.parse(s"""{ - "transactionStatus": "${TransactionStatus.ACCP.code}" - }"""), - List(AuthenticatedUserIsRequired, UnknownError), - ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil - ) - - lazy val getPaymentInitiationStatus : OBPEndpoint = { - case paymentService :: paymentProduct :: paymentId:: "status" :: Nil JsonGet _ => { - cc => - for { - (Full(u), callContext) <- authenticatedAccess(cc) - _ <- passesPsd2Pisp(callContext) - _ <- NewStyle.function.tryons(checkPaymentServerTypeError(paymentService),404, callContext) { - PaymentServiceTypes.withName(paymentService.replaceAll("-","_")) - } - _ <- NewStyle.function.tryons(checkPaymentProductError(paymentProduct),404, callContext) { - TransactionRequestTypes.withName(paymentProduct.replaceAll("-","_").toUpperCase) - } - (transactionRequest, callContext) <- NewStyle.function.getTransactionRequestImpl(TransactionRequestId(paymentId), callContext) - - transactionRequestStatus = mapTransactionStatus(transactionRequest.status) - - transactionRequestAmount <- NewStyle.function.tryons(s"${InvalidNumber} transaction request amount cannot convert to a Decimal",400, callContext) { - BigDecimal(transactionRequest.body.to_sepa_credit_transfers.get.instructedAmount.amount) - } - transactionRequestCurrency <- NewStyle.function.tryons(s"${InvalidCurrency} can not get currency from this paymentId(${paymentId})",400, callContext) { - transactionRequest.body.to_sepa_credit_transfers.get.instructedAmount.currency - } - - - transactionRequestFromAccount = transactionRequest.from - (fromAccount, callContext) <- NewStyle.function.checkBankAccountExists(BankId(transactionRequestFromAccount.bank_id), AccountId(transactionRequestFromAccount.account_id), callContext) - fromAccountBalance = fromAccount.balance - fromAccountCurrency = fromAccount.currency - fundsAvalible = fromAccountBalance >= transactionRequestAmount - - - //From change from requestAccount Currency to currentBankAccount Currency - rate = fx.exchangeRate(transactionRequestCurrency, fromAccountCurrency, None, callContext) - _ <- Helper.booleanToFuture(s"$InvalidCurrency The requested currency conversion (${transactionRequestCurrency} to ${fromAccountCurrency}) is not supported.", cc=callContext) { - rate.isDefined - } - - requestChangedCurrencyAmount = fx.convert(transactionRequestAmount, rate) - - fundsAvailable = (fromAccountBalance >= requestChangedCurrencyAmount) - - transactionRequestStatusChekedFunds = if(fundsAvailable) transactionRequestStatus else TransactionStatus.RCVD.code - - } yield { - (json.parse(s"""{ - "transactionStatus": "$transactionRequestStatusChekedFunds" - "fundsAvailable": $fundsAvailable - }""" - ), callContext) - } - } - } - - - val additionalInstructions : String = - """ - |Additional Instructions: - | - |for PAYMENT_SERVICE use payments - | - |for PAYMENT_PRODUCT use sepa-credit-transfers - | - """.stripMargin - - - def generalPaymentSummary (isMockedData :Boolean) = - s"""${mockedDataText(isMockedData)} - This method is used to initiate a payment at the ASPSP. - - ## Variants of Payment Initiation Requests - - This method to initiate a payment initiation at the ASPSP can be sent with either a JSON body or an pain.001 body depending on the payment product in the path. - - There are the following **payment products**: - - - Payment products with payment information in *JSON* format: - - ***sepa-credit-transfers*** - - ***instant-sepa-credit-transfers*** - - ***target-2-payments*** - - ***cross-border-credit-transfers*** - - Payment products with payment information in *pain.001* XML format: - - ***pain.001-sepa-credit-transfers*** - - ***pain.001-instant-sepa-credit-transfers*** - - ***pain.001-target-2-payments*** - - ***pain.001-cross-border-credit-transfers*** - - - Furthermore the request body depends on the **payment-service** - - ***payments***: A single payment initiation request. - - ***bulk-payments***: A collection of several payment iniatiation requests. - In case of a *pain.001* message there are more than one payments contained in the *pain.001 message. - In case of a *JSON* there are several JSON payment blocks contained in a joining list. - - ***periodic-payments***: - Create a standing order initiation resource for recurrent i.e. periodic payments addressable under {paymentId} - with all data relevant for the corresponding payment product and the execution of the standing order contained in a JSON body. - - This is the first step in the API to initiate the related recurring/periodic payment. - - ## Single and mulitilevel SCA Processes - - The Payment Initiation Requests are independent from the need of one ore multilevel - SCA processing, i.e. independent from the number of authorisations needed for the execution of payments. - - But the response messages are specific to either one SCA processing or multilevel SCA processing. - - For payment initiation with multilevel SCA, this specification requires an explicit start of the authorisation, - i.e. links directly associated with SCA processing like 'scaRedirect' or 'scaOAuth' cannot be contained in the - response message of a Payment Initation Request for a payment, where multiple authorisations are needed. - Also if any data is needed for the next action, like selecting an SCA method is not supported in the response, - since all starts of the multiple authorisations are fully equal. - In these cases, first an authorisation sub-resource has to be generated following the 'startAuthorisation' link. - - - $additionalInstructions - - """ - def initiatePaymentImplementation(paymentService: String, paymentProduct: String, json: liftweb.json.JValue, cc: CallContext) = { - for { - (u, callContext) <- applicationAccess(cc) - _ <- passesPsd2Pisp(callContext) - - paymentServiceType <- NewStyle.function.tryons(checkPaymentServerTypeError(paymentService), 404, callContext) { - PaymentServiceTypes.withName(paymentService.replaceAll("-", "_")) - } - - //Berlin Group PaymentProduct is OBP transaction request type - transactionRequestType <- NewStyle.function.tryons(checkPaymentProductError(paymentProduct), 404, callContext) { - TransactionRequestTypes.withName(paymentProduct.replaceAll("-", "_").toUpperCase) - } - - sepaCreditTransfersBerlinGroupV13 <- if(paymentServiceType.equals(PaymentServiceTypes.payments)){ - NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the $SepaCreditTransfersBerlinGroupV13 ", 400, callContext) { - json.extract[SepaCreditTransfersBerlinGroupV13] - } - } else if(paymentServiceType.equals(PaymentServiceTypes.periodic_payments)){ - NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the $PeriodicSepaCreditTransfersBerlinGroupV13 ", 400, callContext) { - json.extract[PeriodicSepaCreditTransfersBerlinGroupV13] - } - }else{ - Future{throw new RuntimeException(checkPaymentServerTypeError(paymentServiceType.toString))} - } - isValidAmountNumber <- NewStyle.function.tryons(s"$InvalidNumber Current input is ${sepaCreditTransfersBerlinGroupV13.instructedAmount.amount} ", 400, callContext) { - BigDecimal(sepaCreditTransfersBerlinGroupV13.instructedAmount.amount) - } - - _ <- Helper.booleanToFuture(s"${NotPositiveAmount} Current input is: '${isValidAmountNumber}'", cc = callContext) { - isValidAmountNumber > BigDecimal("0") - } - - // Prevent default value for transaction request type (at least). - _ <- Helper.booleanToFuture(s"${InvalidISOCurrencyCode} Current input is: '${sepaCreditTransfersBerlinGroupV13.instructedAmount.currency}'", cc = callContext) { - isValidCurrencyISOCode(sepaCreditTransfersBerlinGroupV13.instructedAmount.currency) - } - - _ <- NewStyle.function.isEnabledTransactionRequests(callContext) - - - (createdTransactionRequest, callContext) <- transactionRequestType match { - case TransactionRequestTypes.SEPA_CREDIT_TRANSFERS => { - for { - (createdTransactionRequest, callContext) <- NewStyle.function.createTransactionRequestBGV1( - initiator = u, - paymentServiceType, - transactionRequestType, - transactionRequestBody = sepaCreditTransfersBerlinGroupV13, - callContext - ) - } yield (createdTransactionRequest, callContext) - } - } - } yield { - (JSONFactory_BERLIN_GROUP_1_3.createTransactionRequestJson(createdTransactionRequest), HttpCode.`201`(callContext)) - } - } - - - resourceDocs += ResourceDoc( - initiatePayments, - apiVersion, - nameOf(initiatePayments), - "POST", - "/payments/PAYMENT_PRODUCT", - "Payment initiation request(payments)", - generalPaymentSummary(false), - json.parse(s"""{ - "debtorAccount": { - "iban": "DE123456987480123" - }, - "instructedAmount": { - "currency": "EUR", - "amount": "100" - }, - "creditorAccount": { - "iban": "UK12 1234 5123 4517 2948 6166 077" - }, - "creditorName": "70charname" - }"""), - json.parse(s"""{ - "transactionStatus": "${TransactionStatus.RCVD.code}", - "paymentId": "1234-wertiq-983", - "_links": - { - "scaRedirect": {"href": "$getServerUrl/otp?flow=payment&paymentService=payments&paymentProduct=sepa_credit_transfers&paymentId=b0472c21-6cea-4ee0-b036-3e253adb3b0b"}, - "self": {"href": "/v1.3/payments/sepa-credit-transfers/1234-wertiq-983"}, - "status": {"href": "/v1.3/payments/1234-wertiq-983/status"}, - "scaStatus": {"href": "/v1.3/payments/1234-wertiq-983/authorisations/123auth456"} - } - }"""), - List(AuthenticatedUserIsRequired, UnknownError), - ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil - ) - - lazy val initiatePayments : OBPEndpoint = { - case "payments" :: paymentProduct :: Nil JsonPost json -> _ => { - cc => - initiatePaymentImplementation("payments", paymentProduct, json, cc) - } - } - - - resourceDocs += ResourceDoc( - initiatePeriodicPayments, - apiVersion, - nameOf(initiatePeriodicPayments), - "POST", - "/periodic-payments/PAYMENT_PRODUCT", - "Payment initiation request(periodic-payments)", - generalPaymentSummary(false), - json.parse(s"""{ - "instructedAmount": { - "currency": "EUR", - "amount": "123" - }, - "debtorAccount": { - "iban": "DE40100100103307118608" - }, - "creditorName": "Merchant123", - "creditorAccount": { - "iban": "DE23100120020123456789" - }, - "remittanceInformationUnstructured": "Ref Number Abonnement", - "startDate": "2018-03-01", - "executionRule": "preceding", - "frequency": "Monthly", - "dayOfExecution": "01" - }"""), - json.parse(s"""{ - "transactionStatus": "${TransactionStatus.RCVD.code}", - "paymentId": "1234-wertiq-983", - "_links": - { - "scaRedirect": {"href": "$getServerUrl/otp?flow=payment&paymentService=payments&paymentProduct=sepa_credit_transfers&paymentId=b0472c21-6cea-4ee0-b036-3e253adb3b0b"}, - "self": {"href": "/v1.3/periodic-payments/instant-sepa-credit-transfer/1234-wertiq-983"}, - "status": {"href": "/v1.3/periodic-payments/1234-wertiq-983/status"}, - "scaStatus": {"href": "/v1.3/periodic-payments/1234-wertiq-983/authorisations/123auth456"} - } - }"""), - List(AuthenticatedUserIsRequired, UnknownError), - ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil - ) - - lazy val initiatePeriodicPayments : OBPEndpoint = { - case "periodic-payments" :: paymentProduct :: Nil JsonPost json -> _ => { - cc => - initiatePaymentImplementation("periodic-payments", paymentProduct, json, cc) - } - } - - resourceDocs += ResourceDoc( - initiateBulkPayments, - apiVersion, - nameOf(initiateBulkPayments), - "POST", - "/bulk-payments/PAYMENT_PRODUCT", - "Payment initiation request(bulk-payments)", - generalPaymentSummary(true), - json.parse(s"""{ - "batchBookingPreferred": "true", - "debtorAccount": { - "iban": "DE40100100103307118608" - }, - "paymentInformationId": "my-bulk-identification-1234", - "requestedExecutionDate": "2018-08-01", - "payments": [ - { - "instructedAmount": { - "currency": "EUR", - "amount": "123.50" - }, - "creditorName": "Merchant123", - "creditorAccount": { - "iban": "DE02100100109307118603" - }, - "remittanceInformationUnstructured": "Ref Number Merchant 1" - }, - { - "instructedAmount": { - "currency": "EUR", - "amount": "34.10" - }, - "creditorName": "Merchant456", - "creditorAccount": { - "iban": "FR7612345987650123456789014" - }, - "remittanceInformationUnstructured": "Ref Number Merchant 2" - } - ] - }"""), - json.parse(s"""{ - "transactionStatus": "${TransactionStatus.RCVD.code}", - "paymentId": "1234-wertiq-983", - "_links": - { - "scaRedirect": {"href": "$getServerUrl/otp?flow=payment&paymentService=payments&paymentProduct=sepa_credit_transfers&paymentId=b0472c21-6cea-4ee0-b036-3e253adb3b0b"}, - "self": {"href": "/v1.3/bulk-payments/sepa-credit-transfers/1234-wertiq-983"}, - "status": {"href": "/v1.3/bulk-payments/1234-wertiq-983/status"}, - "scaStatus": {"href": "/v1.3/bulk-payments/1234-wertiq-983/authorisations/123auth456"} - } - }"""), - List(AuthenticatedUserIsRequired, UnknownError), - ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil - ) - - lazy val initiateBulkPayments : OBPEndpoint = { - case "bulk-payments" :: paymentProduct :: Nil JsonPost json -> _ => { - cc => - initiatePaymentImplementation("bulk-payments", paymentProduct, json, cc) - } - } - - def generalStartPaymentAuthorisationSummary(isMockedDate: Boolean) = s"""${mockedDataText(isMockedDate)} -Create an authorisation sub-resource and start the authorisation process. -The message might in addition transmit authentication and authorisation related data. - -This method is iterated n times for a n times SCA authorisation in a -corporate context, each creating an own authorisation sub-endpoint for -the corresponding PSU authorising the transaction. - -The ASPSP might make the usage of this access method unnecessary in case -of only one SCA process needed, since the related authorisation resource -might be automatically created by the ASPSP after the submission of the -payment data with the first POST payments/{payment-product} call. - -The start authorisation process is a process which is needed for creating a new authorisation -or cancellation sub-resource. - -This applies in the following scenarios: - - * The ASPSP has indicated with an 'startAuthorisation' hyperlink in the preceeding Payment - Initiation Response that an explicit start of the authorisation process is needed by the TPP. - The 'startAuthorisation' hyperlink can transport more information about data which needs to be - uploaded by using the extended forms. - * 'startAuthorisationWithPsuIdentfication', - * 'startAuthorisationWithPsuAuthentication' #TODO - * 'startAuthorisationWithAuthentciationMethodSelection' - * The related payment initiation cannot yet be executed since a multilevel SCA is mandated. - * The ASPSP has indicated with an 'startAuthorisation' hyperlink in the preceeding - Payment Cancellation Response that an explicit start of the authorisation process is needed by the TPP. - The 'startAuthorisation' hyperlink can transport more information about data which needs to be uploaded - by using the extended forms as indicated above. - * The related payment cancellation request cannot be applied yet since a multilevel SCA is mandate for - executing the cancellation. - * The signing basket needs to be authorised yet. -""" - - resourceDocs += ResourceDoc( - startPaymentAuthorisationUpdatePsuAuthentication, - apiVersion, - nameOf(startPaymentAuthorisationUpdatePsuAuthentication), - "POST", - "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENT_ID/authorisations", - "Start the authorisation process for a payment initiation (updatePsuAuthentication)", - generalStartPaymentAuthorisationSummary(true), - json.parse( - """{ - | "scaStatus": "finalised", - | "_links":{ - | "status": {"href":"/v1/payments/sepa-credit-transfers/qwer3456tzui7890/status"} - | } - | }""".stripMargin), - json.parse("""{ - "challengeData": { - "scaStatus": "received", - "authorisationId": "88695566-6642-46d5-9985-0d824624f507", - "psuMessage": "Please check your SMS at a mobile device.", - "_links": { - "scaStatus": "/v1.3/payments/sepa-credit-transfers/88695566-6642-46d5-9985-0d824624f507" - } - } - }"""), - List(AuthenticatedUserIsRequired, UnknownError), - ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil - ) - - - lazy val startPaymentAuthorisationUpdatePsuAuthentication : OBPEndpoint = { - case paymentService :: paymentProduct :: paymentId :: "authorisations" :: Nil JsonPost json -> _ if checkUpdatePsuAuthentication(json) => { - cc => - for { - (_, callContext) <- authenticatedAccess(cc) - } yield { - (liftweb.json.parse("""{ - "challengeData": { - "scaStatus": "received", - "authorisationId": "88695566-6642-46d5-9985-0d824624f507", - "psuMessage": "Please check your SMS at a mobile device.", - "_links": { - "scaStatus": "/v1.3/payments/sepa-credit-transfers/88695566-6642-46d5-9985-0d824624f507" - } - } - }"""), HttpCode.`201`(callContext)) - } - } - } - - resourceDocs += ResourceDoc( - startPaymentAuthorisationSelectPsuAuthenticationMethod, - apiVersion, - nameOf(startPaymentAuthorisationSelectPsuAuthenticationMethod), - "POST", - "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENT_ID/authorisations", - "Start the authorisation process for a payment initiation (selectPsuAuthenticationMethod)", - generalStartPaymentAuthorisationSummary(true), - json.parse("""{"authenticationMethodId":""}"""), - json.parse("""{ - "challengeData": { - "scaStatus": "received", - "authorisationId": "88695566-6642-46d5-9985-0d824624f507", - "psuMessage": "Please check your SMS at a mobile device.", - "_links": { - "scaStatus": "/v1.3/payments/sepa-credit-transfers/88695566-6642-46d5-9985-0d824624f507" - } - } - }"""), - List(AuthenticatedUserIsRequired, UnknownError), - ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil - ) - - lazy val startPaymentAuthorisationSelectPsuAuthenticationMethod : OBPEndpoint = { - case paymentService :: paymentProduct :: paymentId :: "authorisations" :: Nil JsonPost json -> _ if checkSelectPsuAuthenticationMethod(json) => { - cc => - for { - (Full(u), callContext) <- authenticatedAccess(cc) - } yield { - (liftweb.json.parse( - """{ - "challengeData": { - "scaStatus": "received", - "authorisationId": "88695566-6642-46d5-9985-0d824624f507", - "psuMessage": "Please check your SMS at a mobile device.", - "_links": { - "scaStatus": "/v1.3/payments/sepa-credit-transfers/88695566-6642-46d5-9985-0d824624f507" - } - } - }"""), HttpCode.`201`(callContext)) - } - } - } - - resourceDocs += ResourceDoc( - startPaymentAuthorisationTransactionAuthorisation, - apiVersion, - nameOf(startPaymentAuthorisationTransactionAuthorisation), - "POST", - "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENT_ID/authorisations", - "Start the authorisation process for a payment initiation (transactionAuthorisation)", - generalStartPaymentAuthorisationSummary(false), - json.parse("""{"scaAuthenticationData":"123"}"""), - json.parse("""{ - "challengeData": { - "scaStatus": "received", - "authorisationId": "88695566-6642-46d5-9985-0d824624f507", - "psuMessage": "Please check your SMS at a mobile device.", - "_links": { - "scaStatus": "/v1.3/payments/sepa-credit-transfers/88695566-6642-46d5-9985-0d824624f507" - } - } - }"""), - List(AuthenticatedUserIsRequired, UnknownError), - ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil - ) - - - lazy val startPaymentAuthorisationTransactionAuthorisation : OBPEndpoint = { - case paymentService :: paymentProduct :: paymentId :: "authorisations" :: Nil JsonPost json -> _ if checkTransactionAuthorisation(json) => { - cc => - for { - (Full(u), callContext) <- authenticatedAccess(cc) - _ <- passesPsd2Pisp(callContext) - _ <- NewStyle.function.tryons(checkPaymentServerTypeError(paymentService), 404, callContext) { - PaymentServiceTypes.withName(paymentService.replaceAll("-", "_")) - } - _ <- NewStyle.function.tryons(checkPaymentProductError(paymentProduct), 404, callContext) { - TransactionRequestTypes.withName(paymentProduct.replaceAll("-", "_").toUpperCase) - } - (_, callContext) <- NewStyle.function.getTransactionRequestImpl(TransactionRequestId(paymentId), callContext) - - (challenges, callContext) <- NewStyle.function.createChallengesC2( - List(u.userId), - ChallengeType.BERLIN_GROUP_PAYMENT_CHALLENGE, - Some(paymentId), - getScaMethodAtInstance(SEPA_CREDIT_TRANSFERS.toString).toOption, - Some(StrongCustomerAuthenticationStatus.received), - None, - None, - callContext - ) - //NOTE: in OBP it support multiple challenges, but in Berlin Group it has only one challenge. The following guard is to make sure it returns the 1st challenge properly. - challenge <- NewStyle.function.tryons(InvalidConnectorResponseForCreateChallenge, 400, callContext) { - challenges.head - } - } yield { - (JSONFactory_BERLIN_GROUP_1_3.createStartPaymentAuthorisationJson(challenge), HttpCode.`201`(callContext)) - } - } - } - - def generalStartPaymentInitiationCancellationAuthorisationSummary (isMockedDate:Boolean) = - s"""${mockedDataText(isMockedDate)} -Creates an authorisation sub-resource and start the authorisation process of the cancellation of the addressed payment. -The message might in addition transmit authentication and authorisation related data. - -This method is iterated n times for a n times SCA authorisation in a -corporate context, each creating an own authorisation sub-endpoint for -the corresponding PSU authorising the cancellation-authorisation. - -The ASPSP might make the usage of this access method unnecessary in case -of only one SCA process needed, since the related authorisation resource -might be automatically created by the ASPSP after the submission of the -payment data with the first POST payments/{payment-product} call. - -The start authorisation process is a process which is needed for creating a new authorisation -or cancellation sub-resource. - -This applies in the following scenarios: - -* The ASPSP has indicated with an 'startAuthorisation' hyperlink in the preceeding Payment - Initiation Response that an explicit start of the authorisation process is needed by the TPP. - The 'startAuthorisation' hyperlink can transport more information about data which needs to be - uploaded by using the extended forms. - * 'startAuthorisationWithPsuIdentfication', - * 'startAuthorisationWithPsuAuthentication' #TODO - * 'startAuthorisationWithAuthentciationMethodSelection' -* The related payment initiation cannot yet be executed since a multilevel SCA is mandated. -* The ASPSP has indicated with an 'startAuthorisation' hyperlink in the preceeding - Payment Cancellation Response that an explicit start of the authorisation process is needed by the TPP. - The 'startAuthorisation' hyperlink can transport more information about data which needs to be uploaded - by using the extended forms as indicated above. -* The related payment cancellation request cannot be applied yet since a multilevel SCA is mandate for - executing the cancellation. -* The signing basket needs to be authorised yet. -""" - - resourceDocs += ResourceDoc( - startPaymentInitiationCancellationAuthorisationTransactionAuthorisation, - apiVersion, - nameOf(startPaymentInitiationCancellationAuthorisationTransactionAuthorisation), - "POST", - "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENT_ID/cancellation-authorisations", - "Start the authorisation process for the cancellation of the addressed payment (transactionAuthorisation)", - generalStartPaymentInitiationCancellationAuthorisationSummary(false), - json.parse("""{"scaAuthenticationData":""}"""), - json.parse("""{ - "scaStatus": "received", - "authorisationId": "123auth456", - "psuMessage": "Please use your BankApp for transaction Authorisation.", - "_links": { - "scaStatus": { - "href": "/v1.3/payments/qwer3456tzui7890/authorisations/123auth456" - } - } - }"""), - List(AuthenticatedUserIsRequired, UnknownError), - ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil - ) - - lazy val startPaymentInitiationCancellationAuthorisationTransactionAuthorisation : OBPEndpoint = { - case paymentService :: paymentProduct :: paymentId:: "cancellation-authorisations" :: Nil JsonPost json -> _ if checkTransactionAuthorisation(json)=> { - cc => - for { - (Full(u), callContext) <- authenticatedAccess(cc) - _ <- passesPsd2Pisp(callContext) - _ <- NewStyle.function.tryons(checkPaymentServerTypeError(paymentService),404, callContext) { - PaymentServiceTypes.withName(paymentService.replaceAll("-","_")) - } - _ <- NewStyle.function.tryons(checkPaymentProductError(paymentProduct),404, callContext) { - TransactionRequestTypes.withName(paymentProduct.replaceAll("-","_").toUpperCase) - } - (transactionRequest, callContext) <- NewStyle.function.getTransactionRequestImpl(TransactionRequestId(paymentId), callContext) - _ <- Helper.booleanToFuture(failMsg= CannotStartTheAuthorisationProcessForTheCancellation, cc=callContext) { - transactionRequest.status == TransactionRequestStatus.CANCELLATION_PENDING.toString - } - (challenges, callContext) <- NewStyle.function.createChallengesC2( - List(u.userId), - ChallengeType.BERLIN_GROUP_PAYMENT_CHALLENGE, - Some(paymentId), - getScaMethodAtInstance(SEPA_CREDIT_TRANSFERS.toString).toOption, - Some(StrongCustomerAuthenticationStatus.received), - None, - None, - callContext - ) - //NOTE: in OBP it support multiple challenges, but in Berlin Group it has only one challenge. The following guard is to make sure it return the 1st challenge properly. - challenge <- NewStyle.function.tryons(InvalidConnectorResponseForCreateChallenge,400, callContext) { - challenges.head - } - } yield { - (JSONFactory_BERLIN_GROUP_1_3.createStartPaymentInitiationCancellationAuthorisation( - challenge, - paymentService, - paymentProduct, - paymentId - ), HttpCode.`201`(callContext)) - } - } - } - - resourceDocs += ResourceDoc( - startPaymentInitiationCancellationAuthorisationUpdatePsuAuthentication, - apiVersion, - nameOf(startPaymentInitiationCancellationAuthorisationUpdatePsuAuthentication), - "POST", - "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENT_ID/cancellation-authorisations", - "Start the authorisation process for the cancellation of the addressed payment (updatePsuAuthentication)", - generalStartPaymentInitiationCancellationAuthorisationSummary(true), - json.parse("""{ - "psuData": { - "password": "start12" - } - }"""), - json.parse("""{ - "scaStatus": "received", - "authorisationId": "123auth456", - "psuMessage": "Please use your BankApp for transaction Authorisation.", - "_links": { - "scaStatus": { - "href": "/v1.3/payments/qwer3456tzui7890/authorisations/123auth456" - } - } - }"""), - List(AuthenticatedUserIsRequired, UnknownError), - ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil - ) - - lazy val startPaymentInitiationCancellationAuthorisationUpdatePsuAuthentication : OBPEndpoint = { - case paymentService :: paymentProduct :: paymentId:: "cancellation-authorisations" :: Nil JsonPost json -> _ if checkUpdatePsuAuthentication(json)=> { - cc => - for { - (Full(u), callContext) <- authenticatedAccess(cc) - } yield { - (liftweb.json.parse( - """{ - "scaStatus": "received", - "authorisationId": "123auth456", - "psuMessage": "Please use your BankApp for transaction Authorisation.", - "_links": { - "scaStatus": { - "href": "/v1.3/payments/qwer3456tzui7890/authorisations/123auth456" - } - } - }"""), HttpCode.`201`(callContext)) - } - } - } - - resourceDocs += ResourceDoc( - startPaymentInitiationCancellationAuthorisationSelectPsuAuthenticationMethod, - apiVersion, - nameOf(startPaymentInitiationCancellationAuthorisationSelectPsuAuthenticationMethod), - "POST", - "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENT_ID/cancellation-authorisations", - "Start the authorisation process for the cancellation of the addressed payment (selectPsuAuthenticationMethod)", - generalStartPaymentInitiationCancellationAuthorisationSummary(true), - json.parse("""{"authenticationMethodId":""}"""), - json.parse("""{ - "scaStatus": "received", - "authorisationId": "123auth456", - "psuMessage": "Please use your BankApp for transaction Authorisation.", - "_links": { - "scaStatus": { - "href": "/v1.3/payments/qwer3456tzui7890/authorisations/123auth456" - } - } - }"""), - List(AuthenticatedUserIsRequired, UnknownError), - ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil - ) - - lazy val startPaymentInitiationCancellationAuthorisationSelectPsuAuthenticationMethod : OBPEndpoint = { - case paymentService :: paymentProduct :: paymentId:: "cancellation-authorisations" :: Nil JsonPost json -> _ if checkSelectPsuAuthenticationMethod(json)=> { - cc => - for { - (Full(u), callContext) <- authenticatedAccess(cc) - } yield { - (liftweb.json.parse( - """{ - "scaStatus": "received", - "authorisationId": "123auth456", - "psuMessage": "Please use your BankApp for transaction Authorisation.", - "_links": { - "scaStatus": { - "href": "/v1.3/payments/qwer3456tzui7890/authorisations/123auth456" - } - } - }"""), HttpCode.`201`(callContext)) - } - } - } - - def generalUpdatePaymentCancellationPsuDataSummary (isMockedData: Boolean)= - s"""${mockedDataText(isMockedData)} -This method updates PSU data on the cancellation authorisation resource if needed. -It may authorise a cancellation of the payment within the Embedded SCA Approach where needed. - -Independently from the SCA Approach it supports e.g. the selection of -the authentication method and a non-SCA PSU authentication. - -This methods updates PSU data on the cancellation authorisation resource if needed. - -There are several possible Update PSU Data requests in the context of a cancellation authorisation within the payment initiation services needed, -which depends on the SCA approach: - -* Redirect SCA Approach: -A specific Update PSU Data Request is applicable for - * the selection of authentication methods, before choosing the actual SCA approach. -* Decoupled SCA Approach: -A specific Update PSU Data Request is only applicable for -* adding the PSU Identification, if not provided yet in the Payment Initiation Request or the Account Information Consent Request, or if no OAuth2 access token is used, or -* the selection of authentication methods. -* Embedded SCA Approach: -The Update PSU Data Request might be used -* to add credentials as a first factor authentication data of the PSU and -* to select the authentication method and -* transaction authorisation. - -The SCA Approach might depend on the chosen SCA method. -For that reason, the following possible Update PSU Data request can apply to all SCA approaches: - -* Select an SCA method in case of several SCA methods are available for the customer. - -There are the following request types on this access path: -* Update PSU Identification -* Update PSU Authentication -* Select PSU Autorization Method - WARNING: This method need a reduced header, - therefore many optional elements are not present. - Maybe in a later version the access path will change. -* Transaction Authorisation - WARNING: This method need a reduced header, - therefore many optional elements are not present. - Maybe in a later version the access path will change. -""" - - resourceDocs += ResourceDoc( - updatePaymentCancellationPsuDataTransactionAuthorisation, - apiVersion, - nameOf(updatePaymentCancellationPsuDataTransactionAuthorisation), - "PUT", - "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENT_ID/cancellation-authorisations/AUTHORISATION_ID", - "Update PSU Data for payment initiation cancellation (transactionAuthorisation)", - generalUpdatePaymentCancellationPsuDataSummary(false), - json.parse("""{"scaAuthenticationData":"123"}"""), - json.parse("""{ - "scaStatus":"finalised", - "psuMessage":"Please check your SMS at a mobile device.", - "_links":{ - "scaStatus":"/v1.3/payments/sepa-credit-transfers/PAYMENT_ID/4f4a8b7f-9968-4183-92ab-ca512b396bfc" - } - }"""), - List(AuthenticatedUserIsRequired, UnknownError), - ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil - ) - - lazy val updatePaymentCancellationPsuDataTransactionAuthorisation : OBPEndpoint = { - case paymentService :: paymentProduct :: paymentId:: "cancellation-authorisations" :: authorisationId :: Nil JsonPut json -> _ if checkTransactionAuthorisation(json) => { - cc => - for { - (_, callContext) <- authenticatedAccess(cc) - _ <- passesPsd2Pisp(callContext) - failMsg = s"$InvalidJsonFormat The Json body should be the $UpdatePaymentPsuDataJson " - transactionAuthorisation <- NewStyle.function.tryons(failMsg, 400, callContext) { - json.extract[TransactionAuthorisation] - } - - _ <- NewStyle.function.tryons(checkPaymentServerTypeError(paymentService),404, callContext) { - PaymentServiceTypes.withName(paymentService.replaceAll("-","_")) - } - _ <- NewStyle.function.tryons(checkPaymentProductError(paymentProduct),404, callContext) { - TransactionRequestTypes.withName(paymentProduct.replaceAll("-","_").toUpperCase) - } - //Map obp transaction request id with BerlinGroup PaymentId - transactionRequestId = TransactionRequestId(paymentId) - (existingTransactionRequest, callContext) <- NewStyle.function.getTransactionRequestImpl(transactionRequestId, callContext) - _ <- Helper.booleanToFuture(failMsg= CannotUpdatePSUDataCancellation, cc=callContext) { - existingTransactionRequest.status == TransactionRequestStatus.INITIATED.toString || - existingTransactionRequest.status == TransactionRequestStatus.CANCELLATION_PENDING.toString || - existingTransactionRequest.status == TransactionRequestStatus.COMPLETED.toString - } - (_, callContext) <- NewStyle.function.getTransactionRequestImpl(TransactionRequestId(paymentId), callContext) - (challenge, callContext) <- NewStyle.function.validateChallengeAnswerC4( - ChallengeType.BERLIN_GROUP_PAYMENT_CHALLENGE, - Some(paymentId), - None, - authorisationId, - transactionAuthorisation.scaAuthenticationData, - SuppliedAnswerType.PLAIN_TEXT_VALUE, - callContext - ) - - (fromAccount, callContext) <- NewStyle.function.checkBankAccountExists( - BankId(existingTransactionRequest.from.bank_id), - AccountId(existingTransactionRequest.from.account_id), - callContext - ) - _ <- challenge.scaStatus match { - case Some(status) if status == StrongCustomerAuthenticationStatus.finalised => // finalised - NewStyle.function.saveTransactionRequestStatusImpl(existingTransactionRequest.id, CANCELLED.toString, callContext) - case Some(status) if status == StrongCustomerAuthenticationStatus.failed => // failed - NewStyle.function.saveTransactionRequestStatusImpl(existingTransactionRequest.id, REJECTED.toString, callContext) - case _ => // all other cases - Future(Full(true)) - } - } yield { - (JSONFactory_BERLIN_GROUP_1_3.createStartPaymentCancellationAuthorisationJson( - challenge, - paymentService, - paymentProduct, - paymentId - ), callContext) - } - } - } - - resourceDocs += ResourceDoc( - updatePaymentCancellationPsuDataUpdatePsuAuthentication, - apiVersion, - nameOf(updatePaymentCancellationPsuDataUpdatePsuAuthentication), - "PUT", - "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENT_ID/cancellation-authorisations/AUTHORISATION_ID", - "Update PSU Data for payment initiation cancellation (updatePsuAuthentication)", - generalUpdatePaymentCancellationPsuDataSummary(true), - json.parse("""{ "psuData":{"password":"start12" }}"""), - json.parse("""{ - "scaStatus": "psuAuthenticated", - "_links": { - "authoriseTransaction": {"href": "/psd2/v1.3/payments/1234-wertiq-983/authorisations/123auth456"} - } - }"""), - List(AuthenticatedUserIsRequired, UnknownError), - ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil - ) - - lazy val updatePaymentCancellationPsuDataUpdatePsuAuthentication : OBPEndpoint = { - case paymentService :: paymentProduct :: paymentId:: "cancellation-authorisations" :: authorisationId :: Nil JsonPut json -> _ if checkUpdatePsuAuthentication(json)=> { - cc => - for { - (_, callContext) <- authenticatedAccess(cc) - } yield { - (net.liftweb.json.parse( - """{ - "scaStatus": "psuAuthenticated", - "_links": { - "authoriseTransaction": {"href": "/psd2/v1.3/payments/1234-wertiq-983/authorisations/123auth456"} - } - }"""), callContext) - } - } - } - - resourceDocs += ResourceDoc( - updatePaymentCancellationPsuDataSelectPsuAuthenticationMethod, - apiVersion, - nameOf(updatePaymentCancellationPsuDataSelectPsuAuthenticationMethod), - "PUT", - "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENT_ID/cancellation-authorisations/AUTHORISATION_ID", - "Update PSU Data for payment initiation cancellation (selectPsuAuthenticationMethod)", - generalUpdatePaymentCancellationPsuDataSummary(true), - json.parse("""{"authenticationMethodId":""}"""), - json.parse("""{ - "scaStatus": "scaMethodSelected", - "chosenScaMethod": { - "authenticationType": "SMS_OTP", - "authenticationMethodId": "myAuthenticationID"}, - "challengeData": { - "otpMaxLength": 6, - "otpFormat": "integer"}, - "_links": { - "authoriseTransaction": {"href": "/psd2/v1.3/payments/1234-wertiq-983/authorisations/123auth456"} - } - }"""), - List(AuthenticatedUserIsRequired, UnknownError), - ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil - ) - - lazy val updatePaymentCancellationPsuDataSelectPsuAuthenticationMethod : OBPEndpoint = { - case paymentService :: paymentProduct :: paymentId:: "cancellation-authorisations" :: authorisationId :: Nil JsonPut json -> _ if checkSelectPsuAuthenticationMethod(json)=> { - cc => - for { - (_, callContext) <- authenticatedAccess(cc) - } yield { - (net.liftweb.json.parse( - """{ - "scaStatus": "scaMethodSelected", - "chosenScaMethod": { - "authenticationType": "SMS_OTP", - "authenticationMethodId": "myAuthenticationID"}, - "challengeData": { - "otpMaxLength": 6, - "otpFormat": "integer"}, - "_links": { - "authoriseTransaction": {"href": "/psd2/v1.3/payments/1234-wertiq-983/authorisations/123auth456"} - } - }"""), callContext) - } - } - } - - resourceDocs += ResourceDoc( - updatePaymentCancellationPsuDataAuthorisationConfirmation, - apiVersion, - nameOf(updatePaymentCancellationPsuDataAuthorisationConfirmation), - "PUT", - "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENT_ID/cancellation-authorisations/AUTHORISATION_ID", - "Update PSU Data for payment initiation cancellation (authorisationConfirmation)", - generalUpdatePaymentCancellationPsuDataSummary(true), - json.parse("""{"confirmationCode":"confirmationCode"}"""), - json.parse("""{ - "scaStatus": "finalised", - "_links":{ - "status": {"href":"/v1.3/payments/sepa-credit-transfers/qwer3456tzui7890/status"} - } - }"""), - List(AuthenticatedUserIsRequired, UnknownError), - ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil - ) - - lazy val updatePaymentCancellationPsuDataAuthorisationConfirmation : OBPEndpoint = { - case paymentService :: paymentProduct :: paymentId:: "cancellation-authorisations" :: authorisationId :: Nil JsonPut json -> _ if checkAuthorisationConfirmation(json)=> { - cc => - for { - (_, callContext) <- authenticatedAccess(cc) - } yield { - (net.liftweb.json.parse( - """{ - "scaStatus": "finalised", - "_links":{ - "status": {"href":"/v1.3/payments/sepa-credit-transfers/qwer3456tzui7890/status"} - } - }"""), callContext) - } - } - } - - - def generalUpdatePaymentPsuDataSumarry(isMockedData: Boolean) = - s"""${mockedDataText(isMockedData)} -This methods updates PSU data on the authorisation resource if needed. -It may authorise a payment within the Embedded SCA Approach where needed. - -Independently from the SCA Approach it supports e.g. the selection of -the authentication method and a non-SCA PSU authentication. - -There are several possible Update PSU Data requests in the context of payment initiation services needed, -which depends on the SCA approach: - -* Redirect SCA Approach: -A specific Update PSU Data Request is applicable for - * the selection of authentication methods, before choosing the actual SCA approach. -* Decoupled SCA Approach: -A specific Update PSU Data Request is only applicable for -* adding the PSU Identification, if not provided yet in the Payment Initiation Request or the Account Information Consent Request, or if no OAuth2 access token is used, or -* the selection of authentication methods. -* Embedded SCA Approach: -The Update PSU Data Request might be used -* to add credentials as a first factor authentication data of the PSU and -* to select the authentication method and -* transaction authorisation. - -The SCA Approach might depend on the chosen SCA method. -For that reason, the following possible Update PSU Data request can apply to all SCA approaches: - -* Select an SCA method in case of several SCA methods are available for the customer. - -There are the following request types on this access path: -* Update PSU Identification -* Update PSU Authentication -* Select PSU Autorization Method - WARNING: This method need a reduced header, - therefore many optional elements are not present. - Maybe in a later version the access path will change. -* Transaction Authorisation - WARNING: This method need a reduced header, - therefore many optional elements are not present. - Maybe in a later version the access path will change. - - NOTE: For this endpoint, for sandbox mode, the `scaAuthenticationData` is fixed value: 123. To make the process work. - Normally the app use will get SMS/EMAIL to get the value for this process. - -""" - - resourceDocs += ResourceDoc( - updatePaymentPsuDataTransactionAuthorisation, - apiVersion, - nameOf(updatePaymentPsuDataTransactionAuthorisation), - "PUT", - "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENT_ID/authorisations/AUTHORISATION_ID", - "Update PSU data for payment initiation (transactionAuthorisation)", - generalUpdatePaymentPsuDataSumarry(false), - json.parse("""{"scaAuthenticationData":"123"}"""), - json.parse("""{ - "scaStatus": "finalised", - "psuMessage": "Please check your SMS at a mobile device.", - "_links": { - "scaStatus": {"href":"/v1.3/payments/sepa-credit-transfers/88695566-6642-46d5-9985-0d824624f507"} - } - }"""), - List(AuthenticatedUserIsRequired, UnknownError), - ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil - ) - - lazy val updatePaymentPsuDataTransactionAuthorisation : OBPEndpoint = { - case paymentService :: paymentProduct :: paymentId:: "authorisations" :: authorisationId :: Nil JsonPut json -> _ if checkTransactionAuthorisation(json) => { - cc => - for { - (Full(u), callContext) <- authenticatedAccess(cc) - _ <- passesPsd2Pisp(callContext) - failMsg = s"$InvalidJsonFormat The Json body should be the $TransactionAuthorisation " - transactionAuthorisationJson <- NewStyle.function.tryons(failMsg, 400, callContext) { - json.extract[TransactionAuthorisation] - } - - _ <- NewStyle.function.tryons(checkPaymentServerTypeError(paymentService),404, callContext) { - PaymentServiceTypes.withName(paymentService.replaceAll("-","_")) - } - _ <- NewStyle.function.tryons(checkPaymentProductError(paymentProduct),404, callContext) { - TransactionRequestTypes.withName(paymentProduct.replaceAll("-","_").toUpperCase) - } - //Map obp transaction request id with BerlinGroup PaymentId - transactionRequestId = TransactionRequestId(paymentId) - (existingTransactionRequest, callContext) <- NewStyle.function.getTransactionRequestImpl(transactionRequestId, callContext) - _ <- Helper.booleanToFuture(failMsg= CannotUpdatePSUData, cc=callContext) { - existingTransactionRequest.status == TransactionStatus.RCVD.code - } - (_, callContext) <- NewStyle.function.getChallenge(authorisationId, callContext) - (challenge, callContext) <- NewStyle.function.validateChallengeAnswerC4( - ChallengeType.BERLIN_GROUP_PAYMENT_CHALLENGE, - Some(paymentId), - None, - authorisationId, - transactionAuthorisationJson.scaAuthenticationData, - SuppliedAnswerType.PLAIN_TEXT_VALUE, - callContext - ) - - (fromAccount, callContext) <- NewStyle.function.checkBankAccountExists( - BankId(existingTransactionRequest.from.bank_id), - AccountId(existingTransactionRequest.from.account_id), - callContext - ) - _ <- challenge.scaStatus match { - case Some(status) if status == StrongCustomerAuthenticationStatus.finalised => // finalised - NewStyle.function.createTransactionAfterChallengeV210(fromAccount, existingTransactionRequest, callContext) map { - response => - NewStyle.function.saveTransactionRequestStatusImpl(existingTransactionRequest.id, COMPLETED.toString, callContext) - } - case Some(status) if status == StrongCustomerAuthenticationStatus.failed => // failed - NewStyle.function.saveTransactionRequestStatusImpl(existingTransactionRequest.id, REJECTED.toString, callContext) - case _ => // started and all other cases - Future(Full(true)) - } - } yield { - (JSONFactory_BERLIN_GROUP_1_3.createUpdatePaymentPsuDataTransactionAuthorisationJson(challenge), callContext) - } - } - } - - resourceDocs += ResourceDoc( - updatePaymentPsuDataUpdatePsuAuthentication, - apiVersion, - nameOf(updatePaymentPsuDataUpdatePsuAuthentication), - "PUT", - "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENT_ID/authorisations/AUTHORISATION_ID", - "Update PSU data for payment initiation (updatePsuAuthentication)", - generalUpdatePaymentPsuDataSumarry(true), - json.parse("""{"psuData": {"password": "start12"}}""".stripMargin), - json.parse("""{ - "scaStatus": "finalised", - "_links": { - "scaStatus": {"href":"/v1.3/payments/sepa-credit-transfers/88695566-6642-46d5-9985-0d824624f507"} - } - }"""), - List(AuthenticatedUserIsRequired, UnknownError), - ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil - ) - - lazy val updatePaymentPsuDataUpdatePsuAuthentication : OBPEndpoint = { - case paymentService :: paymentProduct :: paymentId:: "authorisations" :: authorisationid :: Nil JsonPut json -> _ if checkUpdatePsuAuthentication(json) => { - cc => - for { - (Full(u), callContext) <- authenticatedAccess(cc) - - } yield { - (liftweb.json.parse( - """{ - "scaStatus": "finalised", - "_links": { - "scaStatus": {"href":"/v1.3/payments/sepa-credit-transfers/88695566-6642-46d5-9985-0d824624f507"} - } - }"""), callContext) - } - } - } - - resourceDocs += ResourceDoc( - updatePaymentPsuDataSelectPsuAuthenticationMethod, - apiVersion, - nameOf(updatePaymentPsuDataSelectPsuAuthenticationMethod), - "PUT", - "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENT_ID/authorisations/AUTHORISATION_ID", - "Update PSU data for payment initiation (selectPsuAuthenticationMethod)", - generalUpdatePaymentPsuDataSumarry(true), - json.parse("""{"authenticationMethodId":""}"""), - json.parse( - """{ - "scaStatus": "scaMethodSelected", - "chosenScaMethod": { - "authenticationType": "SMS_OTP", - "authenticationMethodId": "myAuthenticationID"}, - "challengeData": { - "otpMaxLength": 6, - "otpFormat": "integer"}, - "_links": { - "authoriseTransaction": {"href": "/psd2/v1.3/payments/1234-wertiq-983/authorisations/123auth456"} - } - }"""), - List(AuthenticatedUserIsRequired, UnknownError), - ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil - ) - - lazy val updatePaymentPsuDataSelectPsuAuthenticationMethod : OBPEndpoint = { - case paymentService :: paymentProduct :: paymentId:: "authorisations" :: authorisationid :: Nil JsonPut json -> _ if checkSelectPsuAuthenticationMethod(json) => { - cc => - for { - (Full(u), callContext) <- authenticatedAccess(cc) - - } yield { - (liftweb.json.parse( - """{ - "scaStatus": "scaMethodSelected", - "chosenScaMethod": { - "authenticationType": "SMS_OTP", - "authenticationMethodId": "myAuthenticationID"}, - "challengeData": { - "otpMaxLength": 6, - "otpFormat": "integer"}, - "_links": { - "authoriseTransaction": {"href": "/psd2/v1.3/payments/1234-wertiq-983/authorisations/123auth456"} - } - }"""), callContext) - } - } - } - - resourceDocs += ResourceDoc( - updatePaymentPsuDataAuthorisationConfirmation, - apiVersion, - nameOf(updatePaymentPsuDataAuthorisationConfirmation), - "PUT", - "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENT_ID/authorisations/AUTHORISATION_ID", - "Update PSU data for payment initiation (authorisationConfirmation)", - generalUpdatePaymentPsuDataSumarry(true), - json.parse("""{"confirmationCode":"confirmationCode"}"""), - json.parse( - """{ - "scaStatus": "finalised", - "_links":{ - "status": {"href":"/v1.3/payments/sepa-credit-transfers/qwer3456tzui7890/status"} - } - }"""), - List(AuthenticatedUserIsRequired, UnknownError), - ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil - ) - - lazy val updatePaymentPsuDataAuthorisationConfirmation : OBPEndpoint = { - case paymentService :: paymentProduct :: paymentId:: "authorisations" :: authorisationid :: Nil JsonPut json -> _ if checkAuthorisationConfirmation(json) => { - cc => - for { - (Full(u), callContext) <- authenticatedAccess(cc) - - } yield { - (liftweb.json.parse( - """{ - "scaStatus": "finalised", - "_links":{ - "status": {"href":"/v1.3/payments/sepa-credit-transfers/qwer3456tzui7890/status"} - } - }"""), callContext) - } - } - } - -} - - - +//package code.api.builder.PaymentInitiationServicePISApi +// +//import scala.language.implicitConversions +//import code.api.berlin.group.ConstantsBG +//import code.api.berlin.group.v1_3.JSONFactory_BERLIN_GROUP_1_3.{CancelPaymentResponseJson, CancelPaymentResponseLinks, LinkHrefJson, UpdatePaymentPsuDataJson, checkAuthorisationConfirmation, checkSelectPsuAuthenticationMethod, checkTransactionAuthorisation, checkUpdatePsuAuthentication, createCancellationTransactionRequestJson} +//import code.api.berlin.group.v1_3.model.TransactionStatus.mapTransactionStatus +//import code.api.berlin.group.v1_3.model._ +//import code.api.berlin.group.v1_3.{JSONFactory_BERLIN_GROUP_1_3, JvalueCaseClass} +//import code.api.util.APIUtil._ +//import code.api.util.ApiTag._ +//import code.api.util.ErrorMessages._ +//import code.api.util.NewStyle.HttpCode +//import code.api.util.{ApiTag, CallContext, NewStyle} +//import code.fx.fx +//import code.util.Helper +//import com.github.dwickern.macros.NameOf.nameOf +//import com.openbankproject.commons.ExecutionContext.Implicits.global +//import com.openbankproject.commons.model._ +//import com.openbankproject.commons.model.enums.TransactionRequestStatus._ +//import com.openbankproject.commons.model.enums.TransactionRequestTypes._ +//import com.openbankproject.commons.model.enums.{TransactionRequestStatus, _} +//import net.liftweb +//import net.liftweb.common.Box.tryo +//import net.liftweb.common.Full +//import net.liftweb.http.js.JE.JsRaw +//import net.liftweb.http.rest.RestHelper +//import net.liftweb.json +//import net.liftweb.json._ +// +//import scala.collection.mutable.ArrayBuffer +//import scala.concurrent.Future +// +//object APIMethods_PaymentInitiationServicePISApi extends RestHelper { +// val apiVersion = ConstantsBG.berlinGroupVersion1 +// val resourceDocs = ArrayBuffer[ResourceDoc]() +// val apiRelations = ArrayBuffer[ApiRelation]() +// protected implicit def JvalueToSuper(what: JValue): JvalueCaseClass = JvalueCaseClass(what) +// +// def checkPaymentServerTypeError(paymentService: String) = { +// val ccc = "" +// s"${InvalidTransactionRequestType.replaceAll("TRANSACTION_REQUEST_TYPE", "PAYMENT_SERVICE in the URL.")}: '${paymentService}'.It should be `payments` or `periodic-payments` for now, will support `bulk-payments` soon" +// } +// def checkPaymentProductError(paymentProduct: String) = s"${InvalidTransactionRequestType.replaceAll("TRANSACTION_REQUEST_TYPE", "PAYMENT_PRODUCT in the URL.")}: '${paymentProduct}'.It should be `sepa-credit-transfers`for now, will support (instant-sepa-credit-transfers, target-2-payments, cross-border-credit-transfers) soon." +// +// def checkPaymentServiceType(paymentService: String) = tryo { +// PaymentServiceTypes.withName(paymentService.replaceAll("-","_")) +// }.isDefined +// +// val endpoints = +// cancelPayment :: +// getPaymentCancellationScaStatus :: +// getPaymentInformation :: +// getPaymentInitiationAuthorisation :: +// getPaymentInitiationCancellationAuthorisationInformation :: +// getPaymentInitiationScaStatus :: +// getPaymentInitiationStatus :: +// initiatePayments :: +// initiateBulkPayments :: +// initiatePeriodicPayments :: +// startPaymentAuthorisationUpdatePsuAuthentication :: +// startPaymentAuthorisationTransactionAuthorisation :: +// startPaymentAuthorisationSelectPsuAuthenticationMethod :: +// startPaymentInitiationCancellationAuthorisationTransactionAuthorisation :: +// startPaymentInitiationCancellationAuthorisationUpdatePsuAuthentication :: +// startPaymentInitiationCancellationAuthorisationSelectPsuAuthenticationMethod :: +// updatePaymentCancellationPsuDataUpdatePsuAuthentication :: +// updatePaymentCancellationPsuDataTransactionAuthorisation :: +// updatePaymentCancellationPsuDataSelectPsuAuthenticationMethod :: +// updatePaymentCancellationPsuDataAuthorisationConfirmation :: +// updatePaymentPsuDataTransactionAuthorisation :: +// updatePaymentPsuDataAuthorisationConfirmation :: +// updatePaymentPsuDataSelectPsuAuthenticationMethod :: +// updatePaymentPsuDataAuthorisationConfirmation :: +// Nil +// +// +// resourceDocs += ResourceDoc( +// cancelPayment, +// apiVersion, +// nameOf(cancelPayment), +// "DELETE", +// "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENTID", +// "Payment Cancellation Request", +// s"""${mockedDataText(false)} +//This method initiates the cancellation of a payment. Depending on the payment-service, the payment-product +//and the ASPSP's implementation, this TPP call might be sufficient to cancel a payment. If an authorisation +//of the payment cancellation is mandated by the ASPSP, a corresponding hyperlink will be contained in the +//response message. Cancels the addressed payment with resource identification paymentId if applicable to the +//payment-service, payment-product and received in product related timelines (e.g. before end of business day +//for scheduled payments of the last business day before the scheduled execution day). The response to this +//DELETE command will tell the TPP whether the * access method was rejected * access method was successful, +//or * access method is generally applicable, but further authorisation processes are needed. +//""", +// EmptyBody, +// CancelPaymentResponseJson( +// "ACTC", +// _links = CancelPaymentResponseLinks( +// self = LinkHrefJson(s"/v1.3/payments/sepa-credit-transfers/1234-wertiq-983"), +// status = LinkHrefJson(s"/v1.3/payments/sepa-credit-transfers/1234-wertiq-983/status"), +// startAuthorisation = LinkHrefJson(s"/v1.3/payments/sepa-credit-transfers/cancellation-authorisations/1234-wertiq-983/status") +// ) +// ), +// List(AuthenticatedUserIsRequired, UnknownError), +// ApiTag("Payment Initiation Service (PIS)") :: Nil +// ) +// +// lazy val cancelPayment : OBPEndpoint = { +// case paymentService :: paymentProduct :: paymentId :: Nil JsonDelete _ => { +// cc => +// for { +// (Full(u), callContext) <- authenticatedAccess(cc) +// _ <- passesPsd2Pisp(callContext) +// _ <- NewStyle.function.tryons(checkPaymentServerTypeError(paymentService),404, callContext) { +// PaymentServiceTypes.withName(paymentService.replaceAll("-","_")) +// } +// transactionRequestTypes <- NewStyle.function.tryons(checkPaymentProductError(paymentProduct),404, callContext) { +// TransactionRequestTypes.withName(paymentProduct.replaceAll("-","_").toUpperCase) +// } +// (transactionRequest, callContext) <- NewStyle.function.getTransactionRequestImpl(TransactionRequestId(paymentId), callContext) +// +// transactionRequestBody <- NewStyle.function.tryons(s"${UnknownError} No data for Payment Body ",400, callContext) { +// transactionRequest.body.to_sepa_credit_transfers.get +// } +// fromAccountIban = transactionRequestBody.debtorAccount.iban +// toAccountIban = transactionRequestBody.creditorAccount.iban +// (_, callContext) <- NewStyle.function.getBankAccountByIban(fromAccountIban, callContext) +// (ibanChecker, callContext) <- NewStyle.function.validateAndCheckIbanNumber(toAccountIban, callContext) +// _ <- Helper.booleanToFuture(invalidIban, cc=callContext) { ibanChecker.isValid == true } +// (_, callContext) <- NewStyle.function.getToBankAccountByIban(toAccountIban, callContext) +// // Check payment status and determine if cancellation is allowed +// currentStatus = transactionRequest.status.toUpperCase() +// mappedStatus = mapTransactionStatus(currentStatus) +// (canBeCancelled, _, startSca) <- transactionRequestTypes match { +// case TransactionRequestTypes.SEPA_CREDIT_TRANSFERS => { +// currentStatus match { +// case TransactionStatus.RCVD.code | "INITIATED" => +// // INITIATED status (maps to RCVD externally) - direct cancellation +// NewStyle.function.saveTransactionRequestStatusImpl(transactionRequest.id, CANCELLED.toString, callContext) +// Future(true, callContext, Some(false)) +// case TransactionStatus.ACCP.code | "COMPLETED" => +// // ACCP status - may require SCA for cancellation +// NewStyle.function.cancelPaymentV400(TransactionId(transactionRequest.transaction_ids), callContext) map { +// x => x._1 match { +// case CancelPayment(true, Some(startSca)) if startSca == true => +// NewStyle.function.saveTransactionRequestStatusImpl(transactionRequest.id, CANCELLATION_PENDING.toString, callContext) +// (true, x._2, Some(startSca)) +// case CancelPayment(true, Some(startSca)) if startSca == false => +// NewStyle.function.saveTransactionRequestStatusImpl(transactionRequest.id, CANCELLED.toString, callContext) +// (true, x._2, Some(startSca)) +// case CancelPayment(false, _) => +// (false, x._2, Some(false)) +// } +// } +// case TransactionStatus.PDNG.code | "PENDING" => +// // PENDING status (maps to PDNG externally) - may require SCA +// NewStyle.function.cancelPaymentV400(TransactionId(transactionRequest.transaction_ids), callContext) map { +// x => x._1 match { +// case CancelPayment(true, Some(startSca)) if startSca == true => +// NewStyle.function.saveTransactionRequestStatusImpl(transactionRequest.id, CANCELLATION_PENDING.toString, callContext) +// (true, x._2, Some(startSca)) +// case CancelPayment(true, Some(startSca)) if startSca == false => +// NewStyle.function.saveTransactionRequestStatusImpl(transactionRequest.id, CANCELLED.toString, callContext) +// (true, x._2, Some(startSca)) +// case CancelPayment(false, _) => +// (false, x._2, Some(false)) +// } +// } +// case TransactionStatus.CANC.code | "CANCELLED" => +// // Already cancelled - return success +// Future(true, callContext, Some(false)) +// case _ => +// // Other statuses cannot be cancelled +// Future(false, callContext, Some(false)) +// } +// } +// } +// _ <- Helper.booleanToFuture( +// failMsg = s"$TransactionRequestCannotBeCancelled Payment status: $mappedStatus. Only payments in RCVD, ACCP, PDNG, or CANC status can be cancelled.", +// cc = callContext +// ) { canBeCancelled == true } +// (updatedTransactionRequest, callContext) <- NewStyle.function.getTransactionRequestImpl(TransactionRequestId(paymentId), callContext) +// } yield { +// startSca.getOrElse(false) match { +// case true => (createCancellationTransactionRequestJson(updatedTransactionRequest), HttpCode.`202`(callContext)) +// case false => (JsRaw(""), HttpCode.`204`(callContext)) +// } +// } +// } +// } +// +// resourceDocs += ResourceDoc( +// getPaymentCancellationScaStatus, +// apiVersion, +// nameOf(getPaymentCancellationScaStatus), +// "GET", +// "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENTID/cancellation-authorisations/CANCELLATIONID", +// "Read the SCA status of the payment cancellation's authorisation.", +// s"""${mockedDataText(false)} +//This method returns the SCA status of a payment initiation's authorisation sub-resource. +//""", +// EmptyBody, +// json.parse("""{ +// "scaStatus" : "psuAuthenticated" +//}"""), +// List(AuthenticatedUserIsRequired, UnknownError), +// ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil +// ) +// +// lazy val getPaymentCancellationScaStatus : OBPEndpoint = { +// case paymentService :: paymentProduct :: paymentId:: "cancellation-authorisations" :: cancellationId :: Nil JsonGet _ => { +// cc => +// for { +// (_, callContext) <- authenticatedAccess(cc) +// _ <- passesPsd2Pisp(callContext) +// _ <- NewStyle.function.tryons(checkPaymentServerTypeError(paymentService),404, callContext) { +// PaymentServiceTypes.withName(paymentService.replaceAll("-","_")) +// } +// _ <- NewStyle.function.tryons(checkPaymentProductError(paymentProduct),404, callContext) { +// TransactionRequestTypes.withName(paymentProduct.replaceAll("-","_").toUpperCase) +// } +// (_, callContext) <- NewStyle.function.getTransactionRequestImpl(TransactionRequestId(paymentId), callContext) +// (challenge, callContext) <- NewStyle.function.getChallenge(cancellationId, callContext) +// } yield { +// (JSONFactory_BERLIN_GROUP_1_3.ScaStatusJsonV13(challenge.scaStatus.map(_.toString).getOrElse("None")), HttpCode.`200`(callContext)) +// } +// } +// } +// +// resourceDocs += ResourceDoc( +// getPaymentInformation, +// apiVersion, +// nameOf(getPaymentInformation), +// "GET", +// "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENTID", +// "Get Payment Information", +// s"""${mockedDataText(false)} +//Returns the content of a payment object""", +// EmptyBody, +// json.parse("""{ +// "debtorAccount":{ +// "iban":"GR12 1234 5123 4511 3981 4475 477" +// }, +// "instructedAmount":{ +// "currency":"EUR", +// "amount":"1234" +// }, +// "creditorAccount":{ +// "iban":"GR12 1234 5123 4514 4575 3645 077" +// }, +// "creditorName":"70charname" +// }"""), +// List(AuthenticatedUserIsRequired, UnknownError), +// ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM ::Nil +// ) +// +// lazy val getPaymentInformation : OBPEndpoint = { +// case paymentService :: paymentProduct :: paymentId :: Nil JsonGet _ if checkPaymentServiceType(paymentService) => { +// cc => +// for { +// (Full(u), callContext) <- authenticatedAccess(cc) +// _ <- passesPsd2Pisp(callContext) +// _ <- NewStyle.function.tryons(checkPaymentServerTypeError(paymentService),404, callContext) { +// PaymentServiceTypes.withName(paymentService.replaceAll("-","_")) +// } +// transactionRequestTypes <- NewStyle.function.tryons(checkPaymentProductError(paymentProduct),404, callContext) { +// TransactionRequestTypes.withName(paymentProduct.replaceAll("-","_").toUpperCase) +// } +// (transactionRequest, callContext) <- NewStyle.function.getTransactionRequestImpl(TransactionRequestId(paymentId), callContext) +// +// transactionRequestBody <- NewStyle.function.tryons(s"${UnknownError} No data for Payment Body ",400, callContext) { +// transactionRequest.body.to_sepa_credit_transfers.get +// } +// +// } yield { +// (transactionRequestBody, callContext) +// } +// } +// } +// +// resourceDocs += ResourceDoc( +// getPaymentInitiationAuthorisation, +// apiVersion, +// nameOf(getPaymentInitiationAuthorisation), +// "GET", +// "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENTID/authorisations", +// "Get Payment Initiation Authorisation Sub-Resources Request", +// s"""${mockedDataText(false)} +//Read a list of all authorisation subresources IDs which have been created. +// +//This function returns an array of hyperlinks to all generated authorisation sub-resources. +//""", +// EmptyBody, +// json.parse("""[ +// { +// "scaStatus": "received", +// "authorisationId": "940948c7-1c86-4d88-977e-e739bf2c1492", +// "psuMessage": "Please check your SMS at a mobile device.", +// "_links": { +// "scaStatus": "/v1.3/payments/sepa-credit-transfers/940948c7-1c86-4d88-977e-e739bf2c1492" +// } +// }, +// { +// "scaStatus": "received", +// "authorisationId": "0ae75eee-deba-41d6-8116-1a4d6e05dd83", +// "psuMessage": "Please check your SMS at a mobile device.", +// "_links": { +// "scaStatus": "/v1.3/payments/sepa-credit-transfers/0ae75eee-deba-41d6-8116-1a4d6e05dd83" +// } +// } +// ]"""), +// List(AuthenticatedUserIsRequired, UnknownError), +// ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil +// ) +// +// lazy val getPaymentInitiationAuthorisation : OBPEndpoint = { +// case paymentService :: paymentProduct :: paymentId :: "authorisations" :: Nil JsonGet _ => { +// cc => +// for { +// (_, callContext) <- authenticatedAccess(cc) +// _ <- passesPsd2Pisp(callContext) +// _ <- NewStyle.function.tryons(checkPaymentServerTypeError(paymentService),404, callContext) { +// PaymentServiceTypes.withName(paymentService.replaceAll("-","_")) +// } +// _ <- NewStyle.function.tryons(checkPaymentProductError(paymentProduct),404, callContext) { +// TransactionRequestTypes.withName(paymentProduct.replaceAll("-","_").toUpperCase) +// } +// (_, callContext) <- NewStyle.function.getTransactionRequestImpl(TransactionRequestId(paymentId), callContext) +// (challenges, callContext) <- NewStyle.function.getChallengesByTransactionRequestId(paymentId, callContext) +// } yield { +// (JSONFactory_BERLIN_GROUP_1_3.createStartPaymentAuthorisationsJson(challenges), callContext) +// } +// } +// } +// +// resourceDocs += ResourceDoc( +// getPaymentInitiationCancellationAuthorisationInformation, +// apiVersion, +// nameOf(getPaymentInitiationCancellationAuthorisationInformation), +// "GET", +// "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENTID/cancellation-authorisations", +// "Get Cancellation Authorisation Sub-Resources Request", +// s"""${mockedDataText(false)} +//Retrieve a list of all created cancellation authorisation sub-resources. +//""", +// EmptyBody, +// json.parse("""{ +// "cancellationIds" : ["faa3657e-13f0-4feb-a6c3-34bf21a9ae8e]" +//}"""), +// List(AuthenticatedUserIsRequired, UnknownError), +// ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil +// ) +// +// lazy val getPaymentInitiationCancellationAuthorisationInformation : OBPEndpoint = { +// case paymentService :: paymentProduct :: paymentId :: "cancellation-authorisations" :: Nil JsonGet _ => { +// cc => +// for { +// (_, callContext) <- authenticatedAccess(cc) +// _ <- passesPsd2Pisp(callContext) +// _ <- NewStyle.function.tryons(checkPaymentServerTypeError(paymentService),404, callContext) { +// PaymentServiceTypes.withName(paymentService.replaceAll("-","_")) +// } +// _ <- NewStyle.function.tryons(checkPaymentProductError(paymentProduct),404, callContext) { +// TransactionRequestTypes.withName(paymentProduct.replaceAll("-","_").toUpperCase) +// } +// (challenges, callContext) <- NewStyle.function.getChallengesByTransactionRequestId(paymentId, callContext) +// } yield { +// (JSONFactory_BERLIN_GROUP_1_3.CancellationJsonV13(challenges.map(_.challengeId)), callContext) +// } +// } +// } +// +// resourceDocs += ResourceDoc( +// getPaymentInitiationScaStatus, +// apiVersion, +// nameOf(getPaymentInitiationScaStatus), +// "GET", +// "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENT_ID/authorisations/AUTHORISATION_ID", +// "Read the SCA Status of the payment authorisation", +// s"""${mockedDataText(false)} +//This method returns the SCA status of a payment initiation's authorisation sub-resource. +//""", +// EmptyBody, +// json.parse("""{ +// "scaStatus" : "psuAuthenticated" +//}"""), +// List(AuthenticatedUserIsRequired, UnknownError), +// ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil +// ) +// +// lazy val getPaymentInitiationScaStatus : OBPEndpoint = { +// case paymentService :: paymentProduct :: paymentId:: "authorisations" :: authorisationid :: Nil JsonGet _ => { +// cc => +// for { +// (Full(u), callContext) <- authenticatedAccess(cc) +// _ <- passesPsd2Pisp(callContext) +// _ <- NewStyle.function.tryons(checkPaymentServerTypeError(paymentService),404, callContext) { +// PaymentServiceTypes.withName(paymentService.replaceAll("-","_")) +// } +// _ <- NewStyle.function.tryons(checkPaymentProductError(paymentProduct),404, callContext) { +// TransactionRequestTypes.withName(paymentProduct.replaceAll("-","_").toUpperCase) +// } +// (_, callContext) <- NewStyle.function.getTransactionRequestImpl(TransactionRequestId(paymentId), callContext) +// (challenge, callContext) <- NewStyle.function.getChallenge(authorisationid, callContext) +// +// } yield { +// (json.parse( +// s"""{"scaStatus" : "${challenge.scaStatus.getOrElse("None")}"}"""), callContext) +// } +// } +// } +// +// resourceDocs += ResourceDoc( +// getPaymentInitiationStatus, +// apiVersion, +// nameOf(getPaymentInitiationStatus), +// "GET", +// "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENT_ID/status", +// "Payment initiation status request", +// s"""${mockedDataText(false)} +//Check the transaction status of a payment initiation.""", +// EmptyBody, +// json.parse(s"""{ +// "transactionStatus": "${TransactionStatus.ACCP.code}" +// }"""), +// List(AuthenticatedUserIsRequired, UnknownError), +// ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil +// ) +// +// lazy val getPaymentInitiationStatus : OBPEndpoint = { +// case paymentService :: paymentProduct :: paymentId:: "status" :: Nil JsonGet _ => { +// cc => +// for { +// (Full(u), callContext) <- authenticatedAccess(cc) +// _ <- passesPsd2Pisp(callContext) +// _ <- NewStyle.function.tryons(checkPaymentServerTypeError(paymentService),404, callContext) { +// PaymentServiceTypes.withName(paymentService.replaceAll("-","_")) +// } +// _ <- NewStyle.function.tryons(checkPaymentProductError(paymentProduct),404, callContext) { +// TransactionRequestTypes.withName(paymentProduct.replaceAll("-","_").toUpperCase) +// } +// (transactionRequest, callContext) <- NewStyle.function.getTransactionRequestImpl(TransactionRequestId(paymentId), callContext) +// +// transactionRequestStatus = mapTransactionStatus(transactionRequest.status) +// +// transactionRequestAmount <- NewStyle.function.tryons(s"${InvalidNumber} transaction request amount cannot convert to a Decimal",400, callContext) { +// BigDecimal(transactionRequest.body.to_sepa_credit_transfers.get.instructedAmount.amount) +// } +// transactionRequestCurrency <- NewStyle.function.tryons(s"${InvalidCurrency} can not get currency from this paymentId(${paymentId})",400, callContext) { +// transactionRequest.body.to_sepa_credit_transfers.get.instructedAmount.currency +// } +// +// +// transactionRequestFromAccount = transactionRequest.from +// (fromAccount, callContext) <- NewStyle.function.checkBankAccountExists(BankId(transactionRequestFromAccount.bank_id), AccountId(transactionRequestFromAccount.account_id), callContext) +// fromAccountBalance = fromAccount.balance +// fromAccountCurrency = fromAccount.currency +// fundsAvalible = fromAccountBalance >= transactionRequestAmount +// +// +// //From change from requestAccount Currency to currentBankAccount Currency +// rate = fx.exchangeRate(transactionRequestCurrency, fromAccountCurrency, None, callContext) +// _ <- Helper.booleanToFuture(s"$InvalidCurrency The requested currency conversion (${transactionRequestCurrency} to ${fromAccountCurrency}) is not supported.", cc=callContext) { +// rate.isDefined +// } +// +// requestChangedCurrencyAmount = fx.convert(transactionRequestAmount, rate) +// +// fundsAvailable = (fromAccountBalance >= requestChangedCurrencyAmount) +// +// transactionRequestStatusChekedFunds = if(fundsAvailable) transactionRequestStatus else TransactionStatus.RCVD.code +// +// } yield { +// (json.parse(s"""{ +// "transactionStatus": "$transactionRequestStatusChekedFunds" +// "fundsAvailable": $fundsAvailable +// }""" +// ), callContext) +// } +// } +// } +// +// +// val additionalInstructions : String = +// """ +// |Additional Instructions: +// | +// |for PAYMENT_SERVICE use payments +// | +// |for PAYMENT_PRODUCT use sepa-credit-transfers +// | +// """.stripMargin +// +// +// def generalPaymentSummary (isMockedData :Boolean) = +// s"""${mockedDataText(isMockedData)} +// This method is used to initiate a payment at the ASPSP. +// +// ## Variants of Payment Initiation Requests +// +// This method to initiate a payment initiation at the ASPSP can be sent with either a JSON body or an pain.001 body depending on the payment product in the path. +// +// There are the following **payment products**: +// +// - Payment products with payment information in *JSON* format: +// - ***sepa-credit-transfers*** +// - ***instant-sepa-credit-transfers*** +// - ***target-2-payments*** +// - ***cross-border-credit-transfers*** +// - Payment products with payment information in *pain.001* XML format: +// - ***pain.001-sepa-credit-transfers*** +// - ***pain.001-instant-sepa-credit-transfers*** +// - ***pain.001-target-2-payments*** +// - ***pain.001-cross-border-credit-transfers*** +// +// - Furthermore the request body depends on the **payment-service** +// - ***payments***: A single payment initiation request. +// - ***bulk-payments***: A collection of several payment iniatiation requests. +// In case of a *pain.001* message there are more than one payments contained in the *pain.001 message. +// In case of a *JSON* there are several JSON payment blocks contained in a joining list. +// - ***periodic-payments***: +// Create a standing order initiation resource for recurrent i.e. periodic payments addressable under {paymentId} +// with all data relevant for the corresponding payment product and the execution of the standing order contained in a JSON body. +// +// This is the first step in the API to initiate the related recurring/periodic payment. +// +// ## Single and mulitilevel SCA Processes +// +// The Payment Initiation Requests are independent from the need of one ore multilevel +// SCA processing, i.e. independent from the number of authorisations needed for the execution of payments. +// +// But the response messages are specific to either one SCA processing or multilevel SCA processing. +// +// For payment initiation with multilevel SCA, this specification requires an explicit start of the authorisation, +// i.e. links directly associated with SCA processing like 'scaRedirect' or 'scaOAuth' cannot be contained in the +// response message of a Payment Initation Request for a payment, where multiple authorisations are needed. +// Also if any data is needed for the next action, like selecting an SCA method is not supported in the response, +// since all starts of the multiple authorisations are fully equal. +// In these cases, first an authorisation sub-resource has to be generated following the 'startAuthorisation' link. +// +// +// $additionalInstructions +// +// """ +// def initiatePaymentImplementation(paymentService: String, paymentProduct: String, json: liftweb.json.JValue, cc: CallContext) = { +// for { +// (u, callContext) <- applicationAccess(cc) +// _ <- passesPsd2Pisp(callContext) +// +// paymentServiceType <- NewStyle.function.tryons(checkPaymentServerTypeError(paymentService), 404, callContext) { +// PaymentServiceTypes.withName(paymentService.replaceAll("-", "_")) +// } +// +// //Berlin Group PaymentProduct is OBP transaction request type +// transactionRequestType <- NewStyle.function.tryons(checkPaymentProductError(paymentProduct), 404, callContext) { +// TransactionRequestTypes.withName(paymentProduct.replaceAll("-", "_").toUpperCase) +// } +// +// sepaCreditTransfersBerlinGroupV13 <- if(paymentServiceType.equals(PaymentServiceTypes.payments)){ +// NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the $SepaCreditTransfersBerlinGroupV13 ", 400, callContext) { +// json.extract[SepaCreditTransfersBerlinGroupV13] +// } +// } else if(paymentServiceType.equals(PaymentServiceTypes.periodic_payments)){ +// NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the $PeriodicSepaCreditTransfersBerlinGroupV13 ", 400, callContext) { +// json.extract[PeriodicSepaCreditTransfersBerlinGroupV13] +// } +// }else{ +// Future{throw new RuntimeException(checkPaymentServerTypeError(paymentServiceType.toString))} +// } +// isValidAmountNumber <- NewStyle.function.tryons(s"$InvalidNumber Current input is ${sepaCreditTransfersBerlinGroupV13.instructedAmount.amount} ", 400, callContext) { +// BigDecimal(sepaCreditTransfersBerlinGroupV13.instructedAmount.amount) +// } +// +// _ <- Helper.booleanToFuture(s"${NotPositiveAmount} Current input is: '${isValidAmountNumber}'", cc = callContext) { +// isValidAmountNumber > BigDecimal("0") +// } +// +// // Prevent default value for transaction request type (at least). +// _ <- Helper.booleanToFuture(s"${InvalidISOCurrencyCode} Current input is: '${sepaCreditTransfersBerlinGroupV13.instructedAmount.currency}'", cc = callContext) { +// isValidCurrencyISOCode(sepaCreditTransfersBerlinGroupV13.instructedAmount.currency) +// } +// +// _ <- NewStyle.function.isEnabledTransactionRequests(callContext) +// +// +// (createdTransactionRequest, callContext) <- transactionRequestType match { +// case TransactionRequestTypes.SEPA_CREDIT_TRANSFERS => { +// for { +// (createdTransactionRequest, callContext) <- NewStyle.function.createTransactionRequestBGV1( +// initiator = u, +// paymentServiceType, +// transactionRequestType, +// transactionRequestBody = sepaCreditTransfersBerlinGroupV13, +// callContext +// ) +// } yield (createdTransactionRequest, callContext) +// } +// } +// } yield { +// (JSONFactory_BERLIN_GROUP_1_3.createTransactionRequestJson(createdTransactionRequest), HttpCode.`201`(callContext)) +// } +// } +// +// +// resourceDocs += ResourceDoc( +// initiatePayments, +// apiVersion, +// nameOf(initiatePayments), +// "POST", +// "/payments/PAYMENT_PRODUCT", +// "Payment initiation request(payments)", +// generalPaymentSummary(false), +// json.parse(s"""{ +// "debtorAccount": { +// "iban": "DE123456987480123" +// }, +// "instructedAmount": { +// "currency": "EUR", +// "amount": "100" +// }, +// "creditorAccount": { +// "iban": "UK12 1234 5123 4517 2948 6166 077" +// }, +// "creditorName": "70charname" +// }"""), +// json.parse(s"""{ +// "transactionStatus": "${TransactionStatus.RCVD.code}", +// "paymentId": "1234-wertiq-983", +// "_links": +// { +// "scaRedirect": {"href": "$getServerUrl/otp?flow=payment&paymentService=payments&paymentProduct=sepa_credit_transfers&paymentId=b0472c21-6cea-4ee0-b036-3e253adb3b0b"}, +// "self": {"href": "/v1.3/payments/sepa-credit-transfers/1234-wertiq-983"}, +// "status": {"href": "/v1.3/payments/1234-wertiq-983/status"}, +// "scaStatus": {"href": "/v1.3/payments/1234-wertiq-983/authorisations/123auth456"} +// } +// }"""), +// List(AuthenticatedUserIsRequired, UnknownError), +// ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil +// ) +// +// lazy val initiatePayments : OBPEndpoint = { +// case "payments" :: paymentProduct :: Nil JsonPost json -> _ => { +// cc => +// initiatePaymentImplementation("payments", paymentProduct, json, cc) +// } +// } +// +// +// resourceDocs += ResourceDoc( +// initiatePeriodicPayments, +// apiVersion, +// nameOf(initiatePeriodicPayments), +// "POST", +// "/periodic-payments/PAYMENT_PRODUCT", +// "Payment initiation request(periodic-payments)", +// generalPaymentSummary(false), +// json.parse(s"""{ +// "instructedAmount": { +// "currency": "EUR", +// "amount": "123" +// }, +// "debtorAccount": { +// "iban": "DE40100100103307118608" +// }, +// "creditorName": "Merchant123", +// "creditorAccount": { +// "iban": "DE23100120020123456789" +// }, +// "remittanceInformationUnstructured": "Ref Number Abonnement", +// "startDate": "2018-03-01", +// "executionRule": "preceding", +// "frequency": "Monthly", +// "dayOfExecution": "01" +// }"""), +// json.parse(s"""{ +// "transactionStatus": "${TransactionStatus.RCVD.code}", +// "paymentId": "1234-wertiq-983", +// "_links": +// { +// "scaRedirect": {"href": "$getServerUrl/otp?flow=payment&paymentService=payments&paymentProduct=sepa_credit_transfers&paymentId=b0472c21-6cea-4ee0-b036-3e253adb3b0b"}, +// "self": {"href": "/v1.3/periodic-payments/instant-sepa-credit-transfer/1234-wertiq-983"}, +// "status": {"href": "/v1.3/periodic-payments/1234-wertiq-983/status"}, +// "scaStatus": {"href": "/v1.3/periodic-payments/1234-wertiq-983/authorisations/123auth456"} +// } +// }"""), +// List(AuthenticatedUserIsRequired, UnknownError), +// ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil +// ) +// +// lazy val initiatePeriodicPayments : OBPEndpoint = { +// case "periodic-payments" :: paymentProduct :: Nil JsonPost json -> _ => { +// cc => +// initiatePaymentImplementation("periodic-payments", paymentProduct, json, cc) +// } +// } +// +// resourceDocs += ResourceDoc( +// initiateBulkPayments, +// apiVersion, +// nameOf(initiateBulkPayments), +// "POST", +// "/bulk-payments/PAYMENT_PRODUCT", +// "Payment initiation request(bulk-payments)", +// generalPaymentSummary(true), +// json.parse(s"""{ +// "batchBookingPreferred": "true", +// "debtorAccount": { +// "iban": "DE40100100103307118608" +// }, +// "paymentInformationId": "my-bulk-identification-1234", +// "requestedExecutionDate": "2018-08-01", +// "payments": [ +// { +// "instructedAmount": { +// "currency": "EUR", +// "amount": "123.50" +// }, +// "creditorName": "Merchant123", +// "creditorAccount": { +// "iban": "DE02100100109307118603" +// }, +// "remittanceInformationUnstructured": "Ref Number Merchant 1" +// }, +// { +// "instructedAmount": { +// "currency": "EUR", +// "amount": "34.10" +// }, +// "creditorName": "Merchant456", +// "creditorAccount": { +// "iban": "FR7612345987650123456789014" +// }, +// "remittanceInformationUnstructured": "Ref Number Merchant 2" +// } +// ] +// }"""), +// json.parse(s"""{ +// "transactionStatus": "${TransactionStatus.RCVD.code}", +// "paymentId": "1234-wertiq-983", +// "_links": +// { +// "scaRedirect": {"href": "$getServerUrl/otp?flow=payment&paymentService=payments&paymentProduct=sepa_credit_transfers&paymentId=b0472c21-6cea-4ee0-b036-3e253adb3b0b"}, +// "self": {"href": "/v1.3/bulk-payments/sepa-credit-transfers/1234-wertiq-983"}, +// "status": {"href": "/v1.3/bulk-payments/1234-wertiq-983/status"}, +// "scaStatus": {"href": "/v1.3/bulk-payments/1234-wertiq-983/authorisations/123auth456"} +// } +// }"""), +// List(AuthenticatedUserIsRequired, UnknownError), +// ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil +// ) +// +// lazy val initiateBulkPayments : OBPEndpoint = { +// case "bulk-payments" :: paymentProduct :: Nil JsonPost json -> _ => { +// cc => +// initiatePaymentImplementation("bulk-payments", paymentProduct, json, cc) +// } +// } +// +// def generalStartPaymentAuthorisationSummary(isMockedDate: Boolean) = s"""${mockedDataText(isMockedDate)} +//Create an authorisation sub-resource and start the authorisation process. +//The message might in addition transmit authentication and authorisation related data. +// +//This method is iterated n times for a n times SCA authorisation in a +//corporate context, each creating an own authorisation sub-endpoint for +//the corresponding PSU authorising the transaction. +// +//The ASPSP might make the usage of this access method unnecessary in case +//of only one SCA process needed, since the related authorisation resource +//might be automatically created by the ASPSP after the submission of the +//payment data with the first POST payments/{payment-product} call. +// +//The start authorisation process is a process which is needed for creating a new authorisation +//or cancellation sub-resource. +// +//This applies in the following scenarios: +// +// * The ASPSP has indicated with an 'startAuthorisation' hyperlink in the preceeding Payment +// Initiation Response that an explicit start of the authorisation process is needed by the TPP. +// The 'startAuthorisation' hyperlink can transport more information about data which needs to be +// uploaded by using the extended forms. +// * 'startAuthorisationWithPsuIdentfication', +// * 'startAuthorisationWithPsuAuthentication' #TODO +// * 'startAuthorisationWithAuthentciationMethodSelection' +// * The related payment initiation cannot yet be executed since a multilevel SCA is mandated. +// * The ASPSP has indicated with an 'startAuthorisation' hyperlink in the preceeding +// Payment Cancellation Response that an explicit start of the authorisation process is needed by the TPP. +// The 'startAuthorisation' hyperlink can transport more information about data which needs to be uploaded +// by using the extended forms as indicated above. +// * The related payment cancellation request cannot be applied yet since a multilevel SCA is mandate for +// executing the cancellation. +// * The signing basket needs to be authorised yet. +//""" +// +// resourceDocs += ResourceDoc( +// startPaymentAuthorisationUpdatePsuAuthentication, +// apiVersion, +// nameOf(startPaymentAuthorisationUpdatePsuAuthentication), +// "POST", +// "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENT_ID/authorisations", +// "Start the authorisation process for a payment initiation (updatePsuAuthentication)", +// generalStartPaymentAuthorisationSummary(true), +// json.parse( +// """{ +// | "scaStatus": "finalised", +// | "_links":{ +// | "status": {"href":"/v1/payments/sepa-credit-transfers/qwer3456tzui7890/status"} +// | } +// | }""".stripMargin), +// json.parse("""{ +// "challengeData": { +// "scaStatus": "received", +// "authorisationId": "88695566-6642-46d5-9985-0d824624f507", +// "psuMessage": "Please check your SMS at a mobile device.", +// "_links": { +// "scaStatus": "/v1.3/payments/sepa-credit-transfers/88695566-6642-46d5-9985-0d824624f507" +// } +// } +// }"""), +// List(AuthenticatedUserIsRequired, UnknownError), +// ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil +// ) +// +// +// lazy val startPaymentAuthorisationUpdatePsuAuthentication : OBPEndpoint = { +// case paymentService :: paymentProduct :: paymentId :: "authorisations" :: Nil JsonPost json -> _ if checkUpdatePsuAuthentication(json) => { +// cc => +// for { +// (_, callContext) <- authenticatedAccess(cc) +// } yield { +// (liftweb.json.parse("""{ +// "challengeData": { +// "scaStatus": "received", +// "authorisationId": "88695566-6642-46d5-9985-0d824624f507", +// "psuMessage": "Please check your SMS at a mobile device.", +// "_links": { +// "scaStatus": "/v1.3/payments/sepa-credit-transfers/88695566-6642-46d5-9985-0d824624f507" +// } +// } +// }"""), HttpCode.`201`(callContext)) +// } +// } +// } +// +// resourceDocs += ResourceDoc( +// startPaymentAuthorisationSelectPsuAuthenticationMethod, +// apiVersion, +// nameOf(startPaymentAuthorisationSelectPsuAuthenticationMethod), +// "POST", +// "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENT_ID/authorisations", +// "Start the authorisation process for a payment initiation (selectPsuAuthenticationMethod)", +// generalStartPaymentAuthorisationSummary(true), +// json.parse("""{"authenticationMethodId":""}"""), +// json.parse("""{ +// "challengeData": { +// "scaStatus": "received", +// "authorisationId": "88695566-6642-46d5-9985-0d824624f507", +// "psuMessage": "Please check your SMS at a mobile device.", +// "_links": { +// "scaStatus": "/v1.3/payments/sepa-credit-transfers/88695566-6642-46d5-9985-0d824624f507" +// } +// } +// }"""), +// List(AuthenticatedUserIsRequired, UnknownError), +// ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil +// ) +// +// lazy val startPaymentAuthorisationSelectPsuAuthenticationMethod : OBPEndpoint = { +// case paymentService :: paymentProduct :: paymentId :: "authorisations" :: Nil JsonPost json -> _ if checkSelectPsuAuthenticationMethod(json) => { +// cc => +// for { +// (Full(u), callContext) <- authenticatedAccess(cc) +// } yield { +// (liftweb.json.parse( +// """{ +// "challengeData": { +// "scaStatus": "received", +// "authorisationId": "88695566-6642-46d5-9985-0d824624f507", +// "psuMessage": "Please check your SMS at a mobile device.", +// "_links": { +// "scaStatus": "/v1.3/payments/sepa-credit-transfers/88695566-6642-46d5-9985-0d824624f507" +// } +// } +// }"""), HttpCode.`201`(callContext)) +// } +// } +// } +// +// resourceDocs += ResourceDoc( +// startPaymentAuthorisationTransactionAuthorisation, +// apiVersion, +// nameOf(startPaymentAuthorisationTransactionAuthorisation), +// "POST", +// "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENT_ID/authorisations", +// "Start the authorisation process for a payment initiation (transactionAuthorisation)", +// generalStartPaymentAuthorisationSummary(false), +// json.parse("""{"scaAuthenticationData":"123"}"""), +// json.parse("""{ +// "challengeData": { +// "scaStatus": "received", +// "authorisationId": "88695566-6642-46d5-9985-0d824624f507", +// "psuMessage": "Please check your SMS at a mobile device.", +// "_links": { +// "scaStatus": "/v1.3/payments/sepa-credit-transfers/88695566-6642-46d5-9985-0d824624f507" +// } +// } +// }"""), +// List(AuthenticatedUserIsRequired, UnknownError), +// ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil +// ) +// +// +// lazy val startPaymentAuthorisationTransactionAuthorisation : OBPEndpoint = { +// case paymentService :: paymentProduct :: paymentId :: "authorisations" :: Nil JsonPost json -> _ if checkTransactionAuthorisation(json) => { +// cc => +// for { +// (Full(u), callContext) <- authenticatedAccess(cc) +// _ <- passesPsd2Pisp(callContext) +// _ <- NewStyle.function.tryons(checkPaymentServerTypeError(paymentService), 404, callContext) { +// PaymentServiceTypes.withName(paymentService.replaceAll("-", "_")) +// } +// _ <- NewStyle.function.tryons(checkPaymentProductError(paymentProduct), 404, callContext) { +// TransactionRequestTypes.withName(paymentProduct.replaceAll("-", "_").toUpperCase) +// } +// (_, callContext) <- NewStyle.function.getTransactionRequestImpl(TransactionRequestId(paymentId), callContext) +// +// (challenges, callContext) <- NewStyle.function.createChallengesC2( +// List(u.userId), +// ChallengeType.BERLIN_GROUP_PAYMENT_CHALLENGE, +// Some(paymentId), +// getScaMethodAtInstance(SEPA_CREDIT_TRANSFERS.toString).toOption, +// Some(StrongCustomerAuthenticationStatus.received), +// None, +// None, +// callContext +// ) +// //NOTE: in OBP it support multiple challenges, but in Berlin Group it has only one challenge. The following guard is to make sure it returns the 1st challenge properly. +// challenge <- NewStyle.function.tryons(InvalidConnectorResponseForCreateChallenge, 400, callContext) { +// challenges.head +// } +// } yield { +// (JSONFactory_BERLIN_GROUP_1_3.createStartPaymentAuthorisationJson(challenge), HttpCode.`201`(callContext)) +// } +// } +// } +// +// def generalStartPaymentInitiationCancellationAuthorisationSummary (isMockedDate:Boolean) = +// s"""${mockedDataText(isMockedDate)} +//Creates an authorisation sub-resource and start the authorisation process of the cancellation of the addressed payment. +//The message might in addition transmit authentication and authorisation related data. +// +//This method is iterated n times for a n times SCA authorisation in a +//corporate context, each creating an own authorisation sub-endpoint for +//the corresponding PSU authorising the cancellation-authorisation. +// +//The ASPSP might make the usage of this access method unnecessary in case +//of only one SCA process needed, since the related authorisation resource +//might be automatically created by the ASPSP after the submission of the +//payment data with the first POST payments/{payment-product} call. +// +//The start authorisation process is a process which is needed for creating a new authorisation +//or cancellation sub-resource. +// +//This applies in the following scenarios: +// +//* The ASPSP has indicated with an 'startAuthorisation' hyperlink in the preceeding Payment +// Initiation Response that an explicit start of the authorisation process is needed by the TPP. +// The 'startAuthorisation' hyperlink can transport more information about data which needs to be +// uploaded by using the extended forms. +// * 'startAuthorisationWithPsuIdentfication', +// * 'startAuthorisationWithPsuAuthentication' #TODO +// * 'startAuthorisationWithAuthentciationMethodSelection' +//* The related payment initiation cannot yet be executed since a multilevel SCA is mandated. +//* The ASPSP has indicated with an 'startAuthorisation' hyperlink in the preceeding +// Payment Cancellation Response that an explicit start of the authorisation process is needed by the TPP. +// The 'startAuthorisation' hyperlink can transport more information about data which needs to be uploaded +// by using the extended forms as indicated above. +//* The related payment cancellation request cannot be applied yet since a multilevel SCA is mandate for +// executing the cancellation. +//* The signing basket needs to be authorised yet. +//""" +// +// resourceDocs += ResourceDoc( +// startPaymentInitiationCancellationAuthorisationTransactionAuthorisation, +// apiVersion, +// nameOf(startPaymentInitiationCancellationAuthorisationTransactionAuthorisation), +// "POST", +// "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENT_ID/cancellation-authorisations", +// "Start the authorisation process for the cancellation of the addressed payment (transactionAuthorisation)", +// generalStartPaymentInitiationCancellationAuthorisationSummary(false), +// json.parse("""{"scaAuthenticationData":""}"""), +// json.parse("""{ +// "scaStatus": "received", +// "authorisationId": "123auth456", +// "psuMessage": "Please use your BankApp for transaction Authorisation.", +// "_links": { +// "scaStatus": { +// "href": "/v1.3/payments/qwer3456tzui7890/authorisations/123auth456" +// } +// } +// }"""), +// List(AuthenticatedUserIsRequired, UnknownError), +// ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil +// ) +// +// lazy val startPaymentInitiationCancellationAuthorisationTransactionAuthorisation : OBPEndpoint = { +// case paymentService :: paymentProduct :: paymentId:: "cancellation-authorisations" :: Nil JsonPost json -> _ if checkTransactionAuthorisation(json)=> { +// cc => +// for { +// (Full(u), callContext) <- authenticatedAccess(cc) +// _ <- passesPsd2Pisp(callContext) +// _ <- NewStyle.function.tryons(checkPaymentServerTypeError(paymentService),404, callContext) { +// PaymentServiceTypes.withName(paymentService.replaceAll("-","_")) +// } +// _ <- NewStyle.function.tryons(checkPaymentProductError(paymentProduct),404, callContext) { +// TransactionRequestTypes.withName(paymentProduct.replaceAll("-","_").toUpperCase) +// } +// (transactionRequest, callContext) <- NewStyle.function.getTransactionRequestImpl(TransactionRequestId(paymentId), callContext) +// _ <- Helper.booleanToFuture(failMsg= CannotStartTheAuthorisationProcessForTheCancellation, cc=callContext) { +// transactionRequest.status == TransactionRequestStatus.CANCELLATION_PENDING.toString +// } +// (challenges, callContext) <- NewStyle.function.createChallengesC2( +// List(u.userId), +// ChallengeType.BERLIN_GROUP_PAYMENT_CHALLENGE, +// Some(paymentId), +// getScaMethodAtInstance(SEPA_CREDIT_TRANSFERS.toString).toOption, +// Some(StrongCustomerAuthenticationStatus.received), +// None, +// None, +// callContext +// ) +// //NOTE: in OBP it support multiple challenges, but in Berlin Group it has only one challenge. The following guard is to make sure it return the 1st challenge properly. +// challenge <- NewStyle.function.tryons(InvalidConnectorResponseForCreateChallenge,400, callContext) { +// challenges.head +// } +// } yield { +// (JSONFactory_BERLIN_GROUP_1_3.createStartPaymentInitiationCancellationAuthorisation( +// challenge, +// paymentService, +// paymentProduct, +// paymentId +// ), HttpCode.`201`(callContext)) +// } +// } +// } +// +// resourceDocs += ResourceDoc( +// startPaymentInitiationCancellationAuthorisationUpdatePsuAuthentication, +// apiVersion, +// nameOf(startPaymentInitiationCancellationAuthorisationUpdatePsuAuthentication), +// "POST", +// "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENT_ID/cancellation-authorisations", +// "Start the authorisation process for the cancellation of the addressed payment (updatePsuAuthentication)", +// generalStartPaymentInitiationCancellationAuthorisationSummary(true), +// json.parse("""{ +// "psuData": { +// "password": "start12" +// } +// }"""), +// json.parse("""{ +// "scaStatus": "received", +// "authorisationId": "123auth456", +// "psuMessage": "Please use your BankApp for transaction Authorisation.", +// "_links": { +// "scaStatus": { +// "href": "/v1.3/payments/qwer3456tzui7890/authorisations/123auth456" +// } +// } +// }"""), +// List(AuthenticatedUserIsRequired, UnknownError), +// ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil +// ) +// +// lazy val startPaymentInitiationCancellationAuthorisationUpdatePsuAuthentication : OBPEndpoint = { +// case paymentService :: paymentProduct :: paymentId:: "cancellation-authorisations" :: Nil JsonPost json -> _ if checkUpdatePsuAuthentication(json)=> { +// cc => +// for { +// (Full(u), callContext) <- authenticatedAccess(cc) +// } yield { +// (liftweb.json.parse( +// """{ +// "scaStatus": "received", +// "authorisationId": "123auth456", +// "psuMessage": "Please use your BankApp for transaction Authorisation.", +// "_links": { +// "scaStatus": { +// "href": "/v1.3/payments/qwer3456tzui7890/authorisations/123auth456" +// } +// } +// }"""), HttpCode.`201`(callContext)) +// } +// } +// } +// +// resourceDocs += ResourceDoc( +// startPaymentInitiationCancellationAuthorisationSelectPsuAuthenticationMethod, +// apiVersion, +// nameOf(startPaymentInitiationCancellationAuthorisationSelectPsuAuthenticationMethod), +// "POST", +// "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENT_ID/cancellation-authorisations", +// "Start the authorisation process for the cancellation of the addressed payment (selectPsuAuthenticationMethod)", +// generalStartPaymentInitiationCancellationAuthorisationSummary(true), +// json.parse("""{"authenticationMethodId":""}"""), +// json.parse("""{ +// "scaStatus": "received", +// "authorisationId": "123auth456", +// "psuMessage": "Please use your BankApp for transaction Authorisation.", +// "_links": { +// "scaStatus": { +// "href": "/v1.3/payments/qwer3456tzui7890/authorisations/123auth456" +// } +// } +// }"""), +// List(AuthenticatedUserIsRequired, UnknownError), +// ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil +// ) +// +// lazy val startPaymentInitiationCancellationAuthorisationSelectPsuAuthenticationMethod : OBPEndpoint = { +// case paymentService :: paymentProduct :: paymentId:: "cancellation-authorisations" :: Nil JsonPost json -> _ if checkSelectPsuAuthenticationMethod(json)=> { +// cc => +// for { +// (Full(u), callContext) <- authenticatedAccess(cc) +// } yield { +// (liftweb.json.parse( +// """{ +// "scaStatus": "received", +// "authorisationId": "123auth456", +// "psuMessage": "Please use your BankApp for transaction Authorisation.", +// "_links": { +// "scaStatus": { +// "href": "/v1.3/payments/qwer3456tzui7890/authorisations/123auth456" +// } +// } +// }"""), HttpCode.`201`(callContext)) +// } +// } +// } +// +// def generalUpdatePaymentCancellationPsuDataSummary (isMockedData: Boolean)= +// s"""${mockedDataText(isMockedData)} +//This method updates PSU data on the cancellation authorisation resource if needed. +//It may authorise a cancellation of the payment within the Embedded SCA Approach where needed. +// +//Independently from the SCA Approach it supports e.g. the selection of +//the authentication method and a non-SCA PSU authentication. +// +//This methods updates PSU data on the cancellation authorisation resource if needed. +// +//There are several possible Update PSU Data requests in the context of a cancellation authorisation within the payment initiation services needed, +//which depends on the SCA approach: +// +//* Redirect SCA Approach: +//A specific Update PSU Data Request is applicable for +// * the selection of authentication methods, before choosing the actual SCA approach. +//* Decoupled SCA Approach: +//A specific Update PSU Data Request is only applicable for +//* adding the PSU Identification, if not provided yet in the Payment Initiation Request or the Account Information Consent Request, or if no OAuth2 access token is used, or +//* the selection of authentication methods. +//* Embedded SCA Approach: +//The Update PSU Data Request might be used +//* to add credentials as a first factor authentication data of the PSU and +//* to select the authentication method and +//* transaction authorisation. +// +//The SCA Approach might depend on the chosen SCA method. +//For that reason, the following possible Update PSU Data request can apply to all SCA approaches: +// +//* Select an SCA method in case of several SCA methods are available for the customer. +// +//There are the following request types on this access path: +//* Update PSU Identification +//* Update PSU Authentication +//* Select PSU Autorization Method +// WARNING: This method need a reduced header, +// therefore many optional elements are not present. +// Maybe in a later version the access path will change. +//* Transaction Authorisation +// WARNING: This method need a reduced header, +// therefore many optional elements are not present. +// Maybe in a later version the access path will change. +//""" +// +// resourceDocs += ResourceDoc( +// updatePaymentCancellationPsuDataTransactionAuthorisation, +// apiVersion, +// nameOf(updatePaymentCancellationPsuDataTransactionAuthorisation), +// "PUT", +// "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENT_ID/cancellation-authorisations/AUTHORISATION_ID", +// "Update PSU Data for payment initiation cancellation (transactionAuthorisation)", +// generalUpdatePaymentCancellationPsuDataSummary(false), +// json.parse("""{"scaAuthenticationData":"123"}"""), +// json.parse("""{ +// "scaStatus":"finalised", +// "psuMessage":"Please check your SMS at a mobile device.", +// "_links":{ +// "scaStatus":"/v1.3/payments/sepa-credit-transfers/PAYMENT_ID/4f4a8b7f-9968-4183-92ab-ca512b396bfc" +// } +// }"""), +// List(AuthenticatedUserIsRequired, UnknownError), +// ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil +// ) +// +// lazy val updatePaymentCancellationPsuDataTransactionAuthorisation : OBPEndpoint = { +// case paymentService :: paymentProduct :: paymentId:: "cancellation-authorisations" :: authorisationId :: Nil JsonPut json -> _ if checkTransactionAuthorisation(json) => { +// cc => +// for { +// (_, callContext) <- authenticatedAccess(cc) +// _ <- passesPsd2Pisp(callContext) +// failMsg = s"$InvalidJsonFormat The Json body should be the $UpdatePaymentPsuDataJson " +// transactionAuthorisation <- NewStyle.function.tryons(failMsg, 400, callContext) { +// json.extract[TransactionAuthorisation] +// } +// +// _ <- NewStyle.function.tryons(checkPaymentServerTypeError(paymentService),404, callContext) { +// PaymentServiceTypes.withName(paymentService.replaceAll("-","_")) +// } +// _ <- NewStyle.function.tryons(checkPaymentProductError(paymentProduct),404, callContext) { +// TransactionRequestTypes.withName(paymentProduct.replaceAll("-","_").toUpperCase) +// } +// //Map obp transaction request id with BerlinGroup PaymentId +// transactionRequestId = TransactionRequestId(paymentId) +// (existingTransactionRequest, callContext) <- NewStyle.function.getTransactionRequestImpl(transactionRequestId, callContext) +// _ <- Helper.booleanToFuture(failMsg= CannotUpdatePSUDataCancellation, cc=callContext) { +// existingTransactionRequest.status == TransactionRequestStatus.INITIATED.toString || +// existingTransactionRequest.status == TransactionRequestStatus.CANCELLATION_PENDING.toString || +// existingTransactionRequest.status == TransactionRequestStatus.COMPLETED.toString +// } +// (_, callContext) <- NewStyle.function.getTransactionRequestImpl(TransactionRequestId(paymentId), callContext) +// (challenge, callContext) <- NewStyle.function.validateChallengeAnswerC4( +// ChallengeType.BERLIN_GROUP_PAYMENT_CHALLENGE, +// Some(paymentId), +// None, +// authorisationId, +// transactionAuthorisation.scaAuthenticationData, +// SuppliedAnswerType.PLAIN_TEXT_VALUE, +// callContext +// ) +// +// (fromAccount, callContext) <- NewStyle.function.checkBankAccountExists( +// BankId(existingTransactionRequest.from.bank_id), +// AccountId(existingTransactionRequest.from.account_id), +// callContext +// ) +// _ <- challenge.scaStatus match { +// case Some(status) if status == StrongCustomerAuthenticationStatus.finalised => // finalised +// NewStyle.function.saveTransactionRequestStatusImpl(existingTransactionRequest.id, CANCELLED.toString, callContext) +// case Some(status) if status == StrongCustomerAuthenticationStatus.failed => // failed +// NewStyle.function.saveTransactionRequestStatusImpl(existingTransactionRequest.id, REJECTED.toString, callContext) +// case _ => // all other cases +// Future(Full(true)) +// } +// } yield { +// (JSONFactory_BERLIN_GROUP_1_3.createStartPaymentCancellationAuthorisationJson( +// challenge, +// paymentService, +// paymentProduct, +// paymentId +// ), callContext) +// } +// } +// } +// +// resourceDocs += ResourceDoc( +// updatePaymentCancellationPsuDataUpdatePsuAuthentication, +// apiVersion, +// nameOf(updatePaymentCancellationPsuDataUpdatePsuAuthentication), +// "PUT", +// "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENT_ID/cancellation-authorisations/AUTHORISATION_ID", +// "Update PSU Data for payment initiation cancellation (updatePsuAuthentication)", +// generalUpdatePaymentCancellationPsuDataSummary(true), +// json.parse("""{ "psuData":{"password":"start12" }}"""), +// json.parse("""{ +// "scaStatus": "psuAuthenticated", +// "_links": { +// "authoriseTransaction": {"href": "/psd2/v1.3/payments/1234-wertiq-983/authorisations/123auth456"} +// } +// }"""), +// List(AuthenticatedUserIsRequired, UnknownError), +// ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil +// ) +// +// lazy val updatePaymentCancellationPsuDataUpdatePsuAuthentication : OBPEndpoint = { +// case paymentService :: paymentProduct :: paymentId:: "cancellation-authorisations" :: authorisationId :: Nil JsonPut json -> _ if checkUpdatePsuAuthentication(json)=> { +// cc => +// for { +// (_, callContext) <- authenticatedAccess(cc) +// } yield { +// (net.liftweb.json.parse( +// """{ +// "scaStatus": "psuAuthenticated", +// "_links": { +// "authoriseTransaction": {"href": "/psd2/v1.3/payments/1234-wertiq-983/authorisations/123auth456"} +// } +// }"""), callContext) +// } +// } +// } +// +// resourceDocs += ResourceDoc( +// updatePaymentCancellationPsuDataSelectPsuAuthenticationMethod, +// apiVersion, +// nameOf(updatePaymentCancellationPsuDataSelectPsuAuthenticationMethod), +// "PUT", +// "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENT_ID/cancellation-authorisations/AUTHORISATION_ID", +// "Update PSU Data for payment initiation cancellation (selectPsuAuthenticationMethod)", +// generalUpdatePaymentCancellationPsuDataSummary(true), +// json.parse("""{"authenticationMethodId":""}"""), +// json.parse("""{ +// "scaStatus": "scaMethodSelected", +// "chosenScaMethod": { +// "authenticationType": "SMS_OTP", +// "authenticationMethodId": "myAuthenticationID"}, +// "challengeData": { +// "otpMaxLength": 6, +// "otpFormat": "integer"}, +// "_links": { +// "authoriseTransaction": {"href": "/psd2/v1.3/payments/1234-wertiq-983/authorisations/123auth456"} +// } +// }"""), +// List(AuthenticatedUserIsRequired, UnknownError), +// ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil +// ) +// +// lazy val updatePaymentCancellationPsuDataSelectPsuAuthenticationMethod : OBPEndpoint = { +// case paymentService :: paymentProduct :: paymentId:: "cancellation-authorisations" :: authorisationId :: Nil JsonPut json -> _ if checkSelectPsuAuthenticationMethod(json)=> { +// cc => +// for { +// (_, callContext) <- authenticatedAccess(cc) +// } yield { +// (net.liftweb.json.parse( +// """{ +// "scaStatus": "scaMethodSelected", +// "chosenScaMethod": { +// "authenticationType": "SMS_OTP", +// "authenticationMethodId": "myAuthenticationID"}, +// "challengeData": { +// "otpMaxLength": 6, +// "otpFormat": "integer"}, +// "_links": { +// "authoriseTransaction": {"href": "/psd2/v1.3/payments/1234-wertiq-983/authorisations/123auth456"} +// } +// }"""), callContext) +// } +// } +// } +// +// resourceDocs += ResourceDoc( +// updatePaymentCancellationPsuDataAuthorisationConfirmation, +// apiVersion, +// nameOf(updatePaymentCancellationPsuDataAuthorisationConfirmation), +// "PUT", +// "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENT_ID/cancellation-authorisations/AUTHORISATION_ID", +// "Update PSU Data for payment initiation cancellation (authorisationConfirmation)", +// generalUpdatePaymentCancellationPsuDataSummary(true), +// json.parse("""{"confirmationCode":"confirmationCode"}"""), +// json.parse("""{ +// "scaStatus": "finalised", +// "_links":{ +// "status": {"href":"/v1.3/payments/sepa-credit-transfers/qwer3456tzui7890/status"} +// } +// }"""), +// List(AuthenticatedUserIsRequired, UnknownError), +// ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil +// ) +// +// lazy val updatePaymentCancellationPsuDataAuthorisationConfirmation : OBPEndpoint = { +// case paymentService :: paymentProduct :: paymentId:: "cancellation-authorisations" :: authorisationId :: Nil JsonPut json -> _ if checkAuthorisationConfirmation(json)=> { +// cc => +// for { +// (_, callContext) <- authenticatedAccess(cc) +// } yield { +// (net.liftweb.json.parse( +// """{ +// "scaStatus": "finalised", +// "_links":{ +// "status": {"href":"/v1.3/payments/sepa-credit-transfers/qwer3456tzui7890/status"} +// } +// }"""), callContext) +// } +// } +// } +// +// +// def generalUpdatePaymentPsuDataSumarry(isMockedData: Boolean) = +// s"""${mockedDataText(isMockedData)} +//This methods updates PSU data on the authorisation resource if needed. +//It may authorise a payment within the Embedded SCA Approach where needed. +// +//Independently from the SCA Approach it supports e.g. the selection of +//the authentication method and a non-SCA PSU authentication. +// +//There are several possible Update PSU Data requests in the context of payment initiation services needed, +//which depends on the SCA approach: +// +//* Redirect SCA Approach: +//A specific Update PSU Data Request is applicable for +// * the selection of authentication methods, before choosing the actual SCA approach. +//* Decoupled SCA Approach: +//A specific Update PSU Data Request is only applicable for +//* adding the PSU Identification, if not provided yet in the Payment Initiation Request or the Account Information Consent Request, or if no OAuth2 access token is used, or +//* the selection of authentication methods. +//* Embedded SCA Approach: +//The Update PSU Data Request might be used +//* to add credentials as a first factor authentication data of the PSU and +//* to select the authentication method and +//* transaction authorisation. +// +//The SCA Approach might depend on the chosen SCA method. +//For that reason, the following possible Update PSU Data request can apply to all SCA approaches: +// +//* Select an SCA method in case of several SCA methods are available for the customer. +// +//There are the following request types on this access path: +//* Update PSU Identification +//* Update PSU Authentication +//* Select PSU Autorization Method +// WARNING: This method need a reduced header, +// therefore many optional elements are not present. +// Maybe in a later version the access path will change. +//* Transaction Authorisation +// WARNING: This method need a reduced header, +// therefore many optional elements are not present. +// Maybe in a later version the access path will change. +// +// NOTE: For this endpoint, for sandbox mode, the `scaAuthenticationData` is fixed value: 123. To make the process work. +// Normally the app use will get SMS/EMAIL to get the value for this process. +// +//""" +// +// resourceDocs += ResourceDoc( +// updatePaymentPsuDataTransactionAuthorisation, +// apiVersion, +// nameOf(updatePaymentPsuDataTransactionAuthorisation), +// "PUT", +// "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENT_ID/authorisations/AUTHORISATION_ID", +// "Update PSU data for payment initiation (transactionAuthorisation)", +// generalUpdatePaymentPsuDataSumarry(false), +// json.parse("""{"scaAuthenticationData":"123"}"""), +// json.parse("""{ +// "scaStatus": "finalised", +// "psuMessage": "Please check your SMS at a mobile device.", +// "_links": { +// "scaStatus": {"href":"/v1.3/payments/sepa-credit-transfers/88695566-6642-46d5-9985-0d824624f507"} +// } +// }"""), +// List(AuthenticatedUserIsRequired, UnknownError), +// ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil +// ) +// +// lazy val updatePaymentPsuDataTransactionAuthorisation : OBPEndpoint = { +// case paymentService :: paymentProduct :: paymentId:: "authorisations" :: authorisationId :: Nil JsonPut json -> _ if checkTransactionAuthorisation(json) => { +// cc => +// for { +// (Full(u), callContext) <- authenticatedAccess(cc) +// _ <- passesPsd2Pisp(callContext) +// failMsg = s"$InvalidJsonFormat The Json body should be the $TransactionAuthorisation " +// transactionAuthorisationJson <- NewStyle.function.tryons(failMsg, 400, callContext) { +// json.extract[TransactionAuthorisation] +// } +// +// _ <- NewStyle.function.tryons(checkPaymentServerTypeError(paymentService),404, callContext) { +// PaymentServiceTypes.withName(paymentService.replaceAll("-","_")) +// } +// _ <- NewStyle.function.tryons(checkPaymentProductError(paymentProduct),404, callContext) { +// TransactionRequestTypes.withName(paymentProduct.replaceAll("-","_").toUpperCase) +// } +// //Map obp transaction request id with BerlinGroup PaymentId +// transactionRequestId = TransactionRequestId(paymentId) +// (existingTransactionRequest, callContext) <- NewStyle.function.getTransactionRequestImpl(transactionRequestId, callContext) +// _ <- Helper.booleanToFuture(failMsg= CannotUpdatePSUData, cc=callContext) { +// existingTransactionRequest.status == TransactionStatus.RCVD.code +// } +// (_, callContext) <- NewStyle.function.getChallenge(authorisationId, callContext) +// (challenge, callContext) <- NewStyle.function.validateChallengeAnswerC4( +// ChallengeType.BERLIN_GROUP_PAYMENT_CHALLENGE, +// Some(paymentId), +// None, +// authorisationId, +// transactionAuthorisationJson.scaAuthenticationData, +// SuppliedAnswerType.PLAIN_TEXT_VALUE, +// callContext +// ) +// +// (fromAccount, callContext) <- NewStyle.function.checkBankAccountExists( +// BankId(existingTransactionRequest.from.bank_id), +// AccountId(existingTransactionRequest.from.account_id), +// callContext +// ) +// _ <- challenge.scaStatus match { +// case Some(status) if status == StrongCustomerAuthenticationStatus.finalised => // finalised +// NewStyle.function.createTransactionAfterChallengeV210(fromAccount, existingTransactionRequest, callContext) map { +// response => +// NewStyle.function.saveTransactionRequestStatusImpl(existingTransactionRequest.id, COMPLETED.toString, callContext) +// } +// case Some(status) if status == StrongCustomerAuthenticationStatus.failed => // failed +// NewStyle.function.saveTransactionRequestStatusImpl(existingTransactionRequest.id, REJECTED.toString, callContext) +// case _ => // started and all other cases +// Future(Full(true)) +// } +// } yield { +// (JSONFactory_BERLIN_GROUP_1_3.createUpdatePaymentPsuDataTransactionAuthorisationJson(challenge), callContext) +// } +// } +// } +// +// resourceDocs += ResourceDoc( +// updatePaymentPsuDataUpdatePsuAuthentication, +// apiVersion, +// nameOf(updatePaymentPsuDataUpdatePsuAuthentication), +// "PUT", +// "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENT_ID/authorisations/AUTHORISATION_ID", +// "Update PSU data for payment initiation (updatePsuAuthentication)", +// generalUpdatePaymentPsuDataSumarry(true), +// json.parse("""{"psuData": {"password": "start12"}}""".stripMargin), +// json.parse("""{ +// "scaStatus": "finalised", +// "_links": { +// "scaStatus": {"href":"/v1.3/payments/sepa-credit-transfers/88695566-6642-46d5-9985-0d824624f507"} +// } +// }"""), +// List(AuthenticatedUserIsRequired, UnknownError), +// ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil +// ) +// +// lazy val updatePaymentPsuDataUpdatePsuAuthentication : OBPEndpoint = { +// case paymentService :: paymentProduct :: paymentId:: "authorisations" :: authorisationid :: Nil JsonPut json -> _ if checkUpdatePsuAuthentication(json) => { +// cc => +// for { +// (Full(u), callContext) <- authenticatedAccess(cc) +// +// } yield { +// (liftweb.json.parse( +// """{ +// "scaStatus": "finalised", +// "_links": { +// "scaStatus": {"href":"/v1.3/payments/sepa-credit-transfers/88695566-6642-46d5-9985-0d824624f507"} +// } +// }"""), callContext) +// } +// } +// } +// +// resourceDocs += ResourceDoc( +// updatePaymentPsuDataSelectPsuAuthenticationMethod, +// apiVersion, +// nameOf(updatePaymentPsuDataSelectPsuAuthenticationMethod), +// "PUT", +// "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENT_ID/authorisations/AUTHORISATION_ID", +// "Update PSU data for payment initiation (selectPsuAuthenticationMethod)", +// generalUpdatePaymentPsuDataSumarry(true), +// json.parse("""{"authenticationMethodId":""}"""), +// json.parse( +// """{ +// "scaStatus": "scaMethodSelected", +// "chosenScaMethod": { +// "authenticationType": "SMS_OTP", +// "authenticationMethodId": "myAuthenticationID"}, +// "challengeData": { +// "otpMaxLength": 6, +// "otpFormat": "integer"}, +// "_links": { +// "authoriseTransaction": {"href": "/psd2/v1.3/payments/1234-wertiq-983/authorisations/123auth456"} +// } +// }"""), +// List(AuthenticatedUserIsRequired, UnknownError), +// ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil +// ) +// +// lazy val updatePaymentPsuDataSelectPsuAuthenticationMethod : OBPEndpoint = { +// case paymentService :: paymentProduct :: paymentId:: "authorisations" :: authorisationid :: Nil JsonPut json -> _ if checkSelectPsuAuthenticationMethod(json) => { +// cc => +// for { +// (Full(u), callContext) <- authenticatedAccess(cc) +// +// } yield { +// (liftweb.json.parse( +// """{ +// "scaStatus": "scaMethodSelected", +// "chosenScaMethod": { +// "authenticationType": "SMS_OTP", +// "authenticationMethodId": "myAuthenticationID"}, +// "challengeData": { +// "otpMaxLength": 6, +// "otpFormat": "integer"}, +// "_links": { +// "authoriseTransaction": {"href": "/psd2/v1.3/payments/1234-wertiq-983/authorisations/123auth456"} +// } +// }"""), callContext) +// } +// } +// } +// +// resourceDocs += ResourceDoc( +// updatePaymentPsuDataAuthorisationConfirmation, +// apiVersion, +// nameOf(updatePaymentPsuDataAuthorisationConfirmation), +// "PUT", +// "/PAYMENT_SERVICE/PAYMENT_PRODUCT/PAYMENT_ID/authorisations/AUTHORISATION_ID", +// "Update PSU data for payment initiation (authorisationConfirmation)", +// generalUpdatePaymentPsuDataSumarry(true), +// json.parse("""{"confirmationCode":"confirmationCode"}"""), +// json.parse( +// """{ +// "scaStatus": "finalised", +// "_links":{ +// "status": {"href":"/v1.3/payments/sepa-credit-transfers/qwer3456tzui7890/status"} +// } +// }"""), +// List(AuthenticatedUserIsRequired, UnknownError), +// ApiTag("Payment Initiation Service (PIS)") :: apiTagBerlinGroupM :: Nil +// ) +// +// lazy val updatePaymentPsuDataAuthorisationConfirmation : OBPEndpoint = { +// case paymentService :: paymentProduct :: paymentId:: "authorisations" :: authorisationid :: Nil JsonPut json -> _ if checkAuthorisationConfirmation(json) => { +// cc => +// for { +// (Full(u), callContext) <- authenticatedAccess(cc) +// +// } yield { +// (liftweb.json.parse( +// """{ +// "scaStatus": "finalised", +// "_links":{ +// "status": {"href":"/v1.3/payments/sepa-credit-transfers/qwer3456tzui7890/status"} +// } +// }"""), callContext) +// } +// } +// } +// +//} +// +// +// diff --git a/obp-api/src/main/scala/code/api/berlin/group/v1_3/SigningBasketsApi.scala b/obp-api/src/main/scala/code/api/berlin/group/v1_3/SigningBasketsApi.scala index ba30f9e4bf..0a2d91a34b 100644 --- a/obp-api/src/main/scala/code/api/berlin/group/v1_3/SigningBasketsApi.scala +++ b/obp-api/src/main/scala/code/api/berlin/group/v1_3/SigningBasketsApi.scala @@ -1,544 +1,544 @@ -package code.api.builder.SigningBasketsApi - -import scala.language.implicitConversions -import code.api.berlin.group.ConstantsBG -import code.api.berlin.group.v1_3.JSONFactory_BERLIN_GROUP_1_3.{PostSigningBasketJsonV13, UpdatePaymentPsuDataJson, createSigningBasketResponseJson, createStartSigningBasketAuthorisationJson, getSigningBasketResponseJson, getSigningBasketStatusResponseJson} -import code.api.berlin.group.v1_3.{JSONFactory_BERLIN_GROUP_1_3, JvalueCaseClass} -import code.api.util.APIUtil._ -import code.api.util.ApiTag._ -import code.api.util.ErrorMessages._ -import code.api.util.NewStyle -import code.api.util.NewStyle.HttpCode -import code.api.util.newstyle.SigningBasketNewStyle -import code.bankconnectors.Connector -import code.signingbaskets.SigningBasketX -import code.util.Helper.booleanToFuture -import com.github.dwickern.macros.NameOf.nameOf -import com.openbankproject.commons.ExecutionContext.Implicits.global -import com.openbankproject.commons.model.enums.TransactionRequestStatus.{COMPLETED, REJECTED} -import com.openbankproject.commons.model.enums.{ChallengeType, StrongCustomerAuthenticationStatus,SuppliedAnswerType} -import com.openbankproject.commons.model.{ChallengeTrait, TransactionRequestId} -import com.openbankproject.commons.util.ApiVersion -import net.liftweb.common.{Box, Empty, Full} -import net.liftweb.http.js.JE.JsRaw -import net.liftweb.http.rest.RestHelper -import net.liftweb.json -import net.liftweb.json._ - -import scala.collection.mutable.ArrayBuffer -import scala.concurrent.Future - -object APIMethods_SigningBasketsApi extends RestHelper { - val apiVersion = ConstantsBG.berlinGroupVersion1 - val resourceDocs = ArrayBuffer[ResourceDoc]() - val apiRelations = ArrayBuffer[ApiRelation]() - protected implicit def JvalueToSuper(what: JValue): JvalueCaseClass = JvalueCaseClass(what) - - val endpoints = - createSigningBasket :: - deleteSigningBasket :: - getSigningBasket :: - getSigningBasketAuthorisation :: - getSigningBasketScaStatus :: - getSigningBasketStatus :: - startSigningBasketAuthorisation :: - updateSigningBasketPsuData :: - Nil - - - resourceDocs += ResourceDoc( - createSigningBasket, - apiVersion, - nameOf(createSigningBasket), - "POST", - "/signing-baskets", - "Create a signing basket resource", - s"""${mockedDataText(false)} -Create a signing basket resource for authorising several transactions with one SCA method. -The resource identifications of these transactions are contained in the payload of this access method -""", - PostSigningBasketJsonV13(paymentIds = Some(List("123qwert456789", "12345qwert7899")), None), - json.parse("""{ - "basketId" : "1234-basket-567", - "challengeData" : { - "otpMaxLength" : 0, - "additionalInformation" : "additionalInformation", - "image" : "image", - "imageLink" : "http://example.com/aeiou", - "otpFormat" : "characters", - "data" : [ "data", "data" ] - }, - "scaMethods" : "", - "tppMessages" : [ { - "path" : "path", - "code" : { }, - "text" : { }, - "category" : { } - }, { - "path" : "path", - "code" : { }, - "text" : { }, - "category" : { } - } ], - "_links" : { - "scaStatus" : "/v1.3/payments/sepa-credit-transfers/1234-wertiq-983", - "startAuthorisationWithEncryptedPsuAuthentication" : "/v1.3/payments/sepa-credit-transfers/1234-wertiq-983", - "scaRedirect" : "/v1.3/payments/sepa-credit-transfers/1234-wertiq-983", - "startAuthorisationWithAuthenticationMethodSelection" : "/v1.3/payments/sepa-credit-transfers/1234-wertiq-983", - "startAuthorisationWithPsuAuthentication" : "/v1.3/payments/sepa-credit-transfers/1234-wertiq-983", - "scaOAuth" : "/v1.3/payments/sepa-credit-transfers/1234-wertiq-983", - "self" : "/v1.3/payments/sepa-credit-transfers/1234-wertiq-983", - "startAuthorisationWithPsuIdentification" : "/v1.3/payments/sepa-credit-transfers/1234-wertiq-983", - "startAuthorisation" : "/v1.3/payments/sepa-credit-transfers/1234-wertiq-983", - "startAuthorisationWithTransactionAuthorisation" : "/v1.3/payments/sepa-credit-transfers/1234-wertiq-983", - "status" : "/v1.3/payments/sepa-credit-transfers/1234-wertiq-983" - }, - "chosenScaMethod" : "", - "transactionStatus" : "ACCP", - "psuMessage" : { } -}"""), - List(AuthenticatedUserIsRequired, UnknownError), - apiTagSigningBaskets :: Nil - ) - - lazy val createSigningBasket : OBPEndpoint = { - case "signing-baskets" :: Nil JsonPost jsonPost -> _ => { - cc => - for { - (Full(u), callContext) <- authenticatedAccess(cc) - _ <- passesPsd2Pisp(callContext) - failMsg = s"$InvalidJsonFormat The Json body should be the $PostSigningBasketJsonV13 " - postJson <- NewStyle.function.tryons(failMsg, 400, callContext) { - jsonPost.extract[PostSigningBasketJsonV13] - } - _ <- booleanToFuture(failMsg, cc = callContext) { - // One of them MUST be defined. Otherwise, post json is treated as empty one. - !(jsonPost.extract[PostSigningBasketJsonV13].paymentIds.isEmpty && - jsonPost.extract[PostSigningBasketJsonV13].consentIds.isEmpty) - } - signingBasket <- Future { - SigningBasketX.signingBasketProvider.vend.createSigningBasket( - postJson.paymentIds, - postJson.consentIds, - ) - } map { - i => connectorEmptyResponse(i, callContext) - } - } yield { - (createSigningBasketResponseJson(signingBasket), HttpCode.`201`(callContext)) - } - } - } - - resourceDocs += ResourceDoc( - deleteSigningBasket, - apiVersion, - nameOf(deleteSigningBasket), - "DELETE", - "/signing-baskets/BASKETID", - "Delete the signing basket", - s"""${mockedDataText(false)} -Delete the signing basket structure as long as no (partial) authorisation has yet been applied. -The undlerying transactions are not affected by this deletion. - -Remark: The signing basket as such is not deletable after a first (partial) authorisation has been applied. -Nevertheless, single transactions might be cancelled on an individual basis on the XS2A interface. -""", - EmptyBody, - EmptyBody, - List(AuthenticatedUserIsRequired, UnknownError), - apiTagSigningBaskets :: Nil - ) - - lazy val deleteSigningBasket : OBPEndpoint = { - case "signing-baskets" :: basketid :: Nil JsonDelete _ => { - cc => - for { - (Full(u), callContext) <- authenticatedAccess(cc) - _ <- passesPsd2Pisp(callContext) - _ <- Future { - SigningBasketX.signingBasketProvider.vend.deleteSigningBasket(basketid) - } map { - i => connectorEmptyResponse(i, callContext) - } - } yield { - (JsRaw(""), HttpCode.`204`(callContext)) - } - } - } - - resourceDocs += ResourceDoc( - getSigningBasket, - apiVersion, - nameOf(getSigningBasket), - "GET", - "/signing-baskets/BASKETID", - "Returns the content of an signing basket object.", - s"""${mockedDataText(false)} -Returns the content of an signing basket object.""", - EmptyBody, - json.parse("""{ - "transactionStatus" : "ACCP", - "payments" : "", - "consents" : "" -}"""), - List(AuthenticatedUserIsRequired, UnknownError), - apiTagSigningBaskets :: Nil - ) - - lazy val getSigningBasket : OBPEndpoint = { - case "signing-baskets" :: basketid :: Nil JsonGet _ => { - cc => - for { - (Full(u), callContext) <- authenticatedAccess(cc) - _ <- passesPsd2Pisp(callContext) - basket <- Future { - SigningBasketX.signingBasketProvider.vend.getSigningBasketByBasketId(basketid) - } map { - i => connectorEmptyResponse(i, callContext) - } - } yield { - (getSigningBasketResponseJson(basket), HttpCode.`200`(callContext)) - } - } - } - - resourceDocs += ResourceDoc( - getSigningBasketAuthorisation, - apiVersion, - nameOf(getSigningBasketAuthorisation), - "GET", - "/signing-baskets/BASKETID/authorisations", - "Get Signing Basket Authorisation Sub-Resources Request", - s"""${mockedDataText(false)} -Read a list of all authorisation subresources IDs which have been created. - -This function returns an array of hyperlinks to all generated authorisation sub-resources. -""", - EmptyBody, - json.parse("""{ - "authorisationIds" : "" -}"""), - List(AuthenticatedUserIsRequired, UnknownError), - apiTagSigningBaskets :: Nil - ) - - lazy val getSigningBasketAuthorisation : OBPEndpoint = { - case "signing-baskets" :: basketid:: "authorisations" :: Nil JsonGet _ => { - cc => - for { - (Full(u), callContext) <- authenticatedAccess(cc) - _ <- passesPsd2Pisp(callContext) - (challenges, callContext) <- NewStyle.function.getChallengesByBasketId(basketid, callContext) - } yield { - (JSONFactory_BERLIN_GROUP_1_3.AuthorisationJsonV13(challenges.map(_.challengeId)), HttpCode.`200`(callContext)) - } - } - } - - resourceDocs += ResourceDoc( - getSigningBasketScaStatus, - apiVersion, - nameOf(getSigningBasketScaStatus), - "GET", - "/signing-baskets/BASKETID/authorisations/AUTHORISATIONID", - "Read the SCA status of the signing basket authorisation", - s"""${mockedDataText(false)} -This method returns the SCA status of a signing basket's authorisation sub-resource. -""", - EmptyBody, - json.parse("""{ - "scaStatus" : "psuAuthenticated" -}"""), - List(AuthenticatedUserIsRequired, UnknownError), - apiTagSigningBaskets :: Nil - ) - - lazy val getSigningBasketScaStatus : OBPEndpoint = { - case "signing-baskets" :: basketId:: "authorisations" :: authorisationId :: Nil JsonGet _ => { - cc => - for { - (Full(u), callContext) <- authenticatedAccess(cc) - _ <- passesPsd2Pisp(callContext) - _ <- Future(SigningBasketX.signingBasketProvider.vend.getSigningBasketByBasketId(basketId)) map { - unboxFullOrFail(_, callContext, s"$ConsentNotFound ($basketId)", 403) - } - (challenges, callContext) <- NewStyle.function.getChallengesByBasketId(basketId, callContext) - } yield { - val challengeStatus = challenges.filter(_.challengeId == authorisationId) - .flatMap(_.scaStatus).headOption.map(_.toString).getOrElse("None") - (JSONFactory_BERLIN_GROUP_1_3.ScaStatusJsonV13(challengeStatus), HttpCode.`200`(callContext)) - } - } - } - - resourceDocs += ResourceDoc( - getSigningBasketStatus, - apiVersion, - nameOf(getSigningBasketStatus), - "GET", - "/signing-baskets/BASKETID/status", - "Read the status of the signing basket", - s"""${mockedDataText(false)} -Returns the status of a signing basket object. -""", - EmptyBody, - json.parse("""{ - "transactionStatus" : "RCVD" -}"""), - List(AuthenticatedUserIsRequired, UnknownError), - apiTagSigningBaskets :: Nil - ) - - lazy val getSigningBasketStatus : OBPEndpoint = { - case "signing-baskets" :: basketid:: "status" :: Nil JsonGet _ => - cc => - for { - (Full(u), callContext) <- authenticatedAccess(cc) - _ <- passesPsd2Pisp(callContext) - basket <- Future { - SigningBasketX.signingBasketProvider.vend.getSigningBasketByBasketId(basketid) - } map { - i => connectorEmptyResponse(i, callContext) - } - } yield { - (getSigningBasketStatusResponseJson(basket), HttpCode.`200`(callContext)) - } - } - - resourceDocs += ResourceDoc( - startSigningBasketAuthorisation, - apiVersion, - nameOf(startSigningBasketAuthorisation), - "POST", - "/signing-baskets/BASKETID/authorisations", - "Start the authorisation process for a signing basket", - s"""${mockedDataText(false)} -Create an authorisation sub-resource and start the authorisation process of a signing basket. -The message might in addition transmit authentication and authorisation related data. - -This method is iterated n times for a n times SCA authorisation in a -corporate context, each creating an own authorisation sub-endpoint for -the corresponding PSU authorising the signing-baskets. - -The ASPSP might make the usage of this access method unnecessary in case -of only one SCA process needed, since the related authorisation resource -might be automatically created by the ASPSP after the submission of the -payment data with the first POST signing basket call. - -The start authorisation process is a process which is needed for creating a new authorisation -or cancellation sub-resource. - -This applies in the following scenarios: - - * The ASPSP has indicated with an 'startAuthorisation' hyperlink in the preceeding Payment - Initiation Response that an explicit start of the authorisation process is needed by the TPP. - The 'startAuthorisation' hyperlink can transport more information about data which needs to be - uploaded by using the extended forms. - * 'startAuthorisationWithPsuIdentfication', - * 'startAuthorisationWithPsuAuthentication' #TODO - * 'startAuthorisationWithAuthentciationMethodSelection' - * The related payment initiation cannot yet be executed since a multilevel SCA is mandated. - * The ASPSP has indicated with an 'startAuthorisation' hyperlink in the preceeding - Payment Cancellation Response that an explicit start of the authorisation process is needed by the TPP. - The 'startAuthorisation' hyperlink can transport more information about data which needs to be uploaded - by using the extended forms as indicated above. - * The related payment cancellation request cannot be applied yet since a multilevel SCA is mandate for - executing the cancellation. - * The signing basket needs to be authorised yet. -""", - EmptyBody, - json.parse("""{ - "challengeData" : { - "otpMaxLength" : 0, - "additionalInformation" : "additionalInformation", - "image" : "image", - "imageLink" : "http://example.com/aeiou", - "otpFormat" : "characters", - "data" : "data" - }, - "scaMethods" : "", - "scaStatus" : "psuAuthenticated", - "_links" : { - "scaStatus" : "/v1.3/payments/sepa-credit-transfers/1234-wertiq-983", - "startAuthorisationWithEncryptedPsuAuthentication" : "/v1.3/payments/sepa-credit-transfers/1234-wertiq-983", - "scaRedirect" : "/v1.3/payments/sepa-credit-transfers/1234-wertiq-983", - "selectAuthenticationMethod" : "/v1.3/payments/sepa-credit-transfers/1234-wertiq-983", - "startAuthorisationWithPsuAuthentication" : "/v1.3/payments/sepa-credit-transfers/1234-wertiq-983", - "authoriseTransaction" : "/v1.3/payments/sepa-credit-transfers/1234-wertiq-983", - "scaOAuth" : "/v1.3/payments/sepa-credit-transfers/1234-wertiq-983", - "updatePsuIdentification" : "/v1.3/payments/sepa-credit-transfers/1234-wertiq-983" - }, - "chosenScaMethod" : "", - "psuMessage" : { } -}"""), - List(AuthenticatedUserIsRequired, UnknownError), - apiTagSigningBaskets :: Nil - ) - - lazy val startSigningBasketAuthorisation : OBPEndpoint = { - case "signing-baskets" :: basketId :: "authorisations" :: Nil JsonPost _ => { - cc => - for { - (Full(u), callContext) <- authenticatedAccess(cc) - _ <- passesPsd2Pisp(callContext) - (challenges, callContext) <- NewStyle.function.createChallengesC3( - List(u.userId), - ChallengeType.BERLIN_GROUP_SIGNING_BASKETS_CHALLENGE, - None, - getSuggestedDefaultScaMethod(), - Some(StrongCustomerAuthenticationStatus.received), - None, - Some(basketId), - None, - callContext - ) - //NOTE: in OBP it support multiple challenges, but in Berlin Group it has only one challenge. The following guard is to make sure it return the 1st challenge properly. - challenge <- NewStyle.function.tryons(InvalidConnectorResponseForCreateChallenge, 400, callContext) { - challenges.head - } - } yield { - (createStartSigningBasketAuthorisationJson(basketId, challenge), HttpCode.`201`(callContext)) - } - } - } - - resourceDocs += ResourceDoc( - updateSigningBasketPsuData, - apiVersion, - nameOf(updateSigningBasketPsuData), - "PUT", - "/signing-baskets/BASKETID/authorisations/AUTHORISATIONID", - "Update PSU Data for signing basket", - s"""${mockedDataText(false)} -This method update PSU data on the signing basket resource if needed. -It may authorise a igning basket within the Embedded SCA Approach where needed. - -Independently from the SCA Approach it supports e.g. the selection of -the authentication method and a non-SCA PSU authentication. - -This methods updates PSU data on the cancellation authorisation resource if needed. - -There are several possible Update PSU Data requests in the context of a consent request if needed, -which depends on the SCA approach: - -* Redirect SCA Approach: - A specific Update PSU Data Request is applicable for - * the selection of authentication methods, before choosing the actual SCA approach. -* Decoupled SCA Approach: - A specific Update PSU Data Request is only applicable for - * adding the PSU Identification, if not provided yet in the Payment Initiation Request or the Account Information Consent Request, or if no OAuth2 access token is used, or - * the selection of authentication methods. -* Embedded SCA Approach: - The Update PSU Data Request might be used - * to add credentials as a first factor authentication data of the PSU and - * to select the authentication method and - * transaction authorisation. - -The SCA Approach might depend on the chosen SCA method. -For that reason, the following possible Update PSU Data request can apply to all SCA approaches: - -* Select an SCA method in case of several SCA methods are available for the customer. - -There are the following request types on this access path: - * Update PSU Identification - * Update PSU Authentication - * Select PSU Autorization Method - WARNING: This method need a reduced header, - therefore many optional elements are not present. - Maybe in a later version the access path will change. - * Transaction Authorisation - WARNING: This method need a reduced header, - therefore many optional elements are not present. - Maybe in a later version the access path will change. -""", - json.parse("""{"scaAuthenticationData":"123"}"""), - json.parse("""{ - "scaStatus":"finalised", - "authorisationId":"4f4a8b7f-9968-4183-92ab-ca512b396bfc", - "psuMessage":"Please check your SMS at a mobile device.", - "_links":{ - "scaStatus":"/v1.3/payments/sepa-credit-transfers/PAYMENT_ID/4f4a8b7f-9968-4183-92ab-ca512b396bfc" - } - }"""), - List(AuthenticatedUserIsRequired, UnknownError), - apiTagSigningBaskets :: Nil - ) - - lazy val updateSigningBasketPsuData : OBPEndpoint = { - case "signing-baskets" :: basketId:: "authorisations" :: authorisationId :: Nil JsonPut json -> _ => - cc => - for { - (Full(u), callContext) <- authenticatedAccess(cc) - _ <- passesPsd2Pisp(callContext) - failMsg = s"$InvalidJsonFormat The Json body should be the $UpdatePaymentPsuDataJson " - updateBasketPsuDataJson <- NewStyle.function.tryons(failMsg, 400, callContext) { - json.extract[UpdatePaymentPsuDataJson] - } - _ <- SigningBasketNewStyle.checkSigningBasketPayments(basketId, callContext) - // Validate a challenge answer and get an error if any - (boxedChallenge: Box[ChallengeTrait], callContext) <- NewStyle.function.validateChallengeAnswerC5( - ChallengeType.BERLIN_GROUP_SIGNING_BASKETS_CHALLENGE, - None, - None, - Some(basketId), - authorisationId, - updateBasketPsuDataJson.scaAuthenticationData, - SuppliedAnswerType.PLAIN_TEXT_VALUE, - callContext - ) - // Get the challenge after validation - (challenge: ChallengeTrait, callContext) <- NewStyle.function.getChallenge(authorisationId, callContext) - _ <- challenge.scaStatus match { - case Some(status) if status.toString == StrongCustomerAuthenticationStatus.finalised.toString => // finalised - Future { - val basket = SigningBasketX.signingBasketProvider.vend.getSigningBasketByBasketId(basketId) - val existAll: Box[Boolean] = - basket.flatMap(_.payments.map(_.forall(i => Connector.connector.vend.getTransactionRequestImpl(TransactionRequestId(i), callContext).isDefined))) - val alreadyCompleted: List[String] = - basket.flatMap(_.payments).getOrElse(Nil).filter { i => - Connector.connector.vend.getTransactionRequestImpl(TransactionRequestId(i), callContext) - .exists(_._1.status == COMPLETED.toString) - } - if (alreadyCompleted.nonEmpty) { - unboxFullOrFail(Empty, callContext, s"$InvalidConnectorResponse Some of paymentIds [${alreadyCompleted.mkString(",")}] are already completed") - } else if (existAll.getOrElse(false)) { - basket.map { i => - i.payments.map(_.map { i => - NewStyle.function.saveTransactionRequestStatusImpl(TransactionRequestId(i), COMPLETED.toString, callContext) - Connector.connector.vend.getTransactionRequestImpl(TransactionRequestId(i), callContext) map { t => - Connector.connector.vend.makePaymentV400(t._1, None, callContext) - } - }) - } - SigningBasketX.signingBasketProvider.vend.saveSigningBasketStatus(basketId, ConstantsBG.SigningBasketsStatus.ACTC.toString) - unboxFullOrFail(boxedChallenge, callContext, s"$InvalidConnectorResponse validateChallengeAnswerC5") - } else { // Fail due to unexisting payment - val paymentIds = basket.flatMap(_.payments).getOrElse(Nil).mkString(",") - unboxFullOrFail(Empty, callContext, s"$InvalidConnectorResponse Some of paymentIds [${paymentIds}] are invalid") - } - } - case Some(status) if status.toString == StrongCustomerAuthenticationStatus.failed.toString => // failed - Future { - // Reject all related transaction requests - val basket = SigningBasketX.signingBasketProvider.vend.getSigningBasketByBasketId(basketId) - basket.map { i => - i.payments.map(_.map { i => - NewStyle.function.saveTransactionRequestStatusImpl(TransactionRequestId(i), REJECTED.toString, callContext) - }) - } - // Fail in case of an error message - unboxFullOrFail(boxedChallenge, callContext, s"$InvalidConnectorResponse validateChallengeAnswerC5") - } - case _ => // Fail in case of an error message - Future(unboxFullOrFail(Empty, callContext, s"$InvalidConnectorResponse getChallenge")) - } - } yield { - (JSONFactory_BERLIN_GROUP_1_3.createStartPaymentAuthorisationJson(challenge), callContext) - } - } - -} - - - +//package code.api.builder.SigningBasketsApi +// +//import scala.language.implicitConversions +//import code.api.berlin.group.ConstantsBG +//import code.api.berlin.group.v1_3.JSONFactory_BERLIN_GROUP_1_3.{PostSigningBasketJsonV13, UpdatePaymentPsuDataJson, createSigningBasketResponseJson, createStartSigningBasketAuthorisationJson, getSigningBasketResponseJson, getSigningBasketStatusResponseJson} +//import code.api.berlin.group.v1_3.{JSONFactory_BERLIN_GROUP_1_3, JvalueCaseClass} +//import code.api.util.APIUtil._ +//import code.api.util.ApiTag._ +//import code.api.util.ErrorMessages._ +//import code.api.util.NewStyle +//import code.api.util.NewStyle.HttpCode +//import code.api.util.newstyle.SigningBasketNewStyle +//import code.bankconnectors.Connector +//import code.signingbaskets.SigningBasketX +//import code.util.Helper.booleanToFuture +//import com.github.dwickern.macros.NameOf.nameOf +//import com.openbankproject.commons.ExecutionContext.Implicits.global +//import com.openbankproject.commons.model.enums.TransactionRequestStatus.{COMPLETED, REJECTED} +//import com.openbankproject.commons.model.enums.{ChallengeType, StrongCustomerAuthenticationStatus,SuppliedAnswerType} +//import com.openbankproject.commons.model.{ChallengeTrait, TransactionRequestId} +//import com.openbankproject.commons.util.ApiVersion +//import net.liftweb.common.{Box, Empty, Full} +//import net.liftweb.http.js.JE.JsRaw +//import net.liftweb.http.rest.RestHelper +//import net.liftweb.json +//import net.liftweb.json._ +// +//import scala.collection.mutable.ArrayBuffer +//import scala.concurrent.Future +// +//object APIMethods_SigningBasketsApi extends RestHelper { +// val apiVersion = ConstantsBG.berlinGroupVersion1 +// val resourceDocs = ArrayBuffer[ResourceDoc]() +// val apiRelations = ArrayBuffer[ApiRelation]() +// protected implicit def JvalueToSuper(what: JValue): JvalueCaseClass = JvalueCaseClass(what) +// +// val endpoints = +// createSigningBasket :: +// deleteSigningBasket :: +// getSigningBasket :: +// getSigningBasketAuthorisation :: +// getSigningBasketScaStatus :: +// getSigningBasketStatus :: +// startSigningBasketAuthorisation :: +// updateSigningBasketPsuData :: +// Nil +// +// +// resourceDocs += ResourceDoc( +// createSigningBasket, +// apiVersion, +// nameOf(createSigningBasket), +// "POST", +// "/signing-baskets", +// "Create a signing basket resource", +// s"""${mockedDataText(false)} +//Create a signing basket resource for authorising several transactions with one SCA method. +//The resource identifications of these transactions are contained in the payload of this access method +//""", +// PostSigningBasketJsonV13(paymentIds = Some(List("123qwert456789", "12345qwert7899")), None), +// json.parse("""{ +// "basketId" : "1234-basket-567", +// "challengeData" : { +// "otpMaxLength" : 0, +// "additionalInformation" : "additionalInformation", +// "image" : "image", +// "imageLink" : "http://example.com/aeiou", +// "otpFormat" : "characters", +// "data" : [ "data", "data" ] +// }, +// "scaMethods" : "", +// "tppMessages" : [ { +// "path" : "path", +// "code" : { }, +// "text" : { }, +// "category" : { } +// }, { +// "path" : "path", +// "code" : { }, +// "text" : { }, +// "category" : { } +// } ], +// "_links" : { +// "scaStatus" : "/v1.3/payments/sepa-credit-transfers/1234-wertiq-983", +// "startAuthorisationWithEncryptedPsuAuthentication" : "/v1.3/payments/sepa-credit-transfers/1234-wertiq-983", +// "scaRedirect" : "/v1.3/payments/sepa-credit-transfers/1234-wertiq-983", +// "startAuthorisationWithAuthenticationMethodSelection" : "/v1.3/payments/sepa-credit-transfers/1234-wertiq-983", +// "startAuthorisationWithPsuAuthentication" : "/v1.3/payments/sepa-credit-transfers/1234-wertiq-983", +// "scaOAuth" : "/v1.3/payments/sepa-credit-transfers/1234-wertiq-983", +// "self" : "/v1.3/payments/sepa-credit-transfers/1234-wertiq-983", +// "startAuthorisationWithPsuIdentification" : "/v1.3/payments/sepa-credit-transfers/1234-wertiq-983", +// "startAuthorisation" : "/v1.3/payments/sepa-credit-transfers/1234-wertiq-983", +// "startAuthorisationWithTransactionAuthorisation" : "/v1.3/payments/sepa-credit-transfers/1234-wertiq-983", +// "status" : "/v1.3/payments/sepa-credit-transfers/1234-wertiq-983" +// }, +// "chosenScaMethod" : "", +// "transactionStatus" : "ACCP", +// "psuMessage" : { } +//}"""), +// List(AuthenticatedUserIsRequired, UnknownError), +// apiTagSigningBaskets :: Nil +// ) +// +// lazy val createSigningBasket : OBPEndpoint = { +// case "signing-baskets" :: Nil JsonPost jsonPost -> _ => { +// cc => +// for { +// (Full(u), callContext) <- authenticatedAccess(cc) +// _ <- passesPsd2Pisp(callContext) +// failMsg = s"$InvalidJsonFormat The Json body should be the $PostSigningBasketJsonV13 " +// postJson <- NewStyle.function.tryons(failMsg, 400, callContext) { +// jsonPost.extract[PostSigningBasketJsonV13] +// } +// _ <- booleanToFuture(failMsg, cc = callContext) { +// // One of them MUST be defined. Otherwise, post json is treated as empty one. +// !(jsonPost.extract[PostSigningBasketJsonV13].paymentIds.isEmpty && +// jsonPost.extract[PostSigningBasketJsonV13].consentIds.isEmpty) +// } +// signingBasket <- Future { +// SigningBasketX.signingBasketProvider.vend.createSigningBasket( +// postJson.paymentIds, +// postJson.consentIds, +// ) +// } map { +// i => connectorEmptyResponse(i, callContext) +// } +// } yield { +// (createSigningBasketResponseJson(signingBasket), HttpCode.`201`(callContext)) +// } +// } +// } +// +// resourceDocs += ResourceDoc( +// deleteSigningBasket, +// apiVersion, +// nameOf(deleteSigningBasket), +// "DELETE", +// "/signing-baskets/BASKETID", +// "Delete the signing basket", +// s"""${mockedDataText(false)} +//Delete the signing basket structure as long as no (partial) authorisation has yet been applied. +//The undlerying transactions are not affected by this deletion. +// +//Remark: The signing basket as such is not deletable after a first (partial) authorisation has been applied. +//Nevertheless, single transactions might be cancelled on an individual basis on the XS2A interface. +//""", +// EmptyBody, +// EmptyBody, +// List(AuthenticatedUserIsRequired, UnknownError), +// apiTagSigningBaskets :: Nil +// ) +// +// lazy val deleteSigningBasket : OBPEndpoint = { +// case "signing-baskets" :: basketid :: Nil JsonDelete _ => { +// cc => +// for { +// (Full(u), callContext) <- authenticatedAccess(cc) +// _ <- passesPsd2Pisp(callContext) +// _ <- Future { +// SigningBasketX.signingBasketProvider.vend.deleteSigningBasket(basketid) +// } map { +// i => connectorEmptyResponse(i, callContext) +// } +// } yield { +// (JsRaw(""), HttpCode.`204`(callContext)) +// } +// } +// } +// +// resourceDocs += ResourceDoc( +// getSigningBasket, +// apiVersion, +// nameOf(getSigningBasket), +// "GET", +// "/signing-baskets/BASKETID", +// "Returns the content of an signing basket object.", +// s"""${mockedDataText(false)} +//Returns the content of an signing basket object.""", +// EmptyBody, +// json.parse("""{ +// "transactionStatus" : "ACCP", +// "payments" : "", +// "consents" : "" +//}"""), +// List(AuthenticatedUserIsRequired, UnknownError), +// apiTagSigningBaskets :: Nil +// ) +// +// lazy val getSigningBasket : OBPEndpoint = { +// case "signing-baskets" :: basketid :: Nil JsonGet _ => { +// cc => +// for { +// (Full(u), callContext) <- authenticatedAccess(cc) +// _ <- passesPsd2Pisp(callContext) +// basket <- Future { +// SigningBasketX.signingBasketProvider.vend.getSigningBasketByBasketId(basketid) +// } map { +// i => connectorEmptyResponse(i, callContext) +// } +// } yield { +// (getSigningBasketResponseJson(basket), HttpCode.`200`(callContext)) +// } +// } +// } +// +// resourceDocs += ResourceDoc( +// getSigningBasketAuthorisation, +// apiVersion, +// nameOf(getSigningBasketAuthorisation), +// "GET", +// "/signing-baskets/BASKETID/authorisations", +// "Get Signing Basket Authorisation Sub-Resources Request", +// s"""${mockedDataText(false)} +//Read a list of all authorisation subresources IDs which have been created. +// +//This function returns an array of hyperlinks to all generated authorisation sub-resources. +//""", +// EmptyBody, +// json.parse("""{ +// "authorisationIds" : "" +//}"""), +// List(AuthenticatedUserIsRequired, UnknownError), +// apiTagSigningBaskets :: Nil +// ) +// +// lazy val getSigningBasketAuthorisation : OBPEndpoint = { +// case "signing-baskets" :: basketid:: "authorisations" :: Nil JsonGet _ => { +// cc => +// for { +// (Full(u), callContext) <- authenticatedAccess(cc) +// _ <- passesPsd2Pisp(callContext) +// (challenges, callContext) <- NewStyle.function.getChallengesByBasketId(basketid, callContext) +// } yield { +// (JSONFactory_BERLIN_GROUP_1_3.AuthorisationJsonV13(challenges.map(_.challengeId)), HttpCode.`200`(callContext)) +// } +// } +// } +// +// resourceDocs += ResourceDoc( +// getSigningBasketScaStatus, +// apiVersion, +// nameOf(getSigningBasketScaStatus), +// "GET", +// "/signing-baskets/BASKETID/authorisations/AUTHORISATIONID", +// "Read the SCA status of the signing basket authorisation", +// s"""${mockedDataText(false)} +//This method returns the SCA status of a signing basket's authorisation sub-resource. +//""", +// EmptyBody, +// json.parse("""{ +// "scaStatus" : "psuAuthenticated" +//}"""), +// List(AuthenticatedUserIsRequired, UnknownError), +// apiTagSigningBaskets :: Nil +// ) +// +// lazy val getSigningBasketScaStatus : OBPEndpoint = { +// case "signing-baskets" :: basketId:: "authorisations" :: authorisationId :: Nil JsonGet _ => { +// cc => +// for { +// (Full(u), callContext) <- authenticatedAccess(cc) +// _ <- passesPsd2Pisp(callContext) +// _ <- Future(SigningBasketX.signingBasketProvider.vend.getSigningBasketByBasketId(basketId)) map { +// unboxFullOrFail(_, callContext, s"$ConsentNotFound ($basketId)", 403) +// } +// (challenges, callContext) <- NewStyle.function.getChallengesByBasketId(basketId, callContext) +// } yield { +// val challengeStatus = challenges.filter(_.challengeId == authorisationId) +// .flatMap(_.scaStatus).headOption.map(_.toString).getOrElse("None") +// (JSONFactory_BERLIN_GROUP_1_3.ScaStatusJsonV13(challengeStatus), HttpCode.`200`(callContext)) +// } +// } +// } +// +// resourceDocs += ResourceDoc( +// getSigningBasketStatus, +// apiVersion, +// nameOf(getSigningBasketStatus), +// "GET", +// "/signing-baskets/BASKETID/status", +// "Read the status of the signing basket", +// s"""${mockedDataText(false)} +//Returns the status of a signing basket object. +//""", +// EmptyBody, +// json.parse("""{ +// "transactionStatus" : "RCVD" +//}"""), +// List(AuthenticatedUserIsRequired, UnknownError), +// apiTagSigningBaskets :: Nil +// ) +// +// lazy val getSigningBasketStatus : OBPEndpoint = { +// case "signing-baskets" :: basketid:: "status" :: Nil JsonGet _ => +// cc => +// for { +// (Full(u), callContext) <- authenticatedAccess(cc) +// _ <- passesPsd2Pisp(callContext) +// basket <- Future { +// SigningBasketX.signingBasketProvider.vend.getSigningBasketByBasketId(basketid) +// } map { +// i => connectorEmptyResponse(i, callContext) +// } +// } yield { +// (getSigningBasketStatusResponseJson(basket), HttpCode.`200`(callContext)) +// } +// } +// +// resourceDocs += ResourceDoc( +// startSigningBasketAuthorisation, +// apiVersion, +// nameOf(startSigningBasketAuthorisation), +// "POST", +// "/signing-baskets/BASKETID/authorisations", +// "Start the authorisation process for a signing basket", +// s"""${mockedDataText(false)} +//Create an authorisation sub-resource and start the authorisation process of a signing basket. +//The message might in addition transmit authentication and authorisation related data. +// +//This method is iterated n times for a n times SCA authorisation in a +//corporate context, each creating an own authorisation sub-endpoint for +//the corresponding PSU authorising the signing-baskets. +// +//The ASPSP might make the usage of this access method unnecessary in case +//of only one SCA process needed, since the related authorisation resource +//might be automatically created by the ASPSP after the submission of the +//payment data with the first POST signing basket call. +// +//The start authorisation process is a process which is needed for creating a new authorisation +//or cancellation sub-resource. +// +//This applies in the following scenarios: +// +// * The ASPSP has indicated with an 'startAuthorisation' hyperlink in the preceeding Payment +// Initiation Response that an explicit start of the authorisation process is needed by the TPP. +// The 'startAuthorisation' hyperlink can transport more information about data which needs to be +// uploaded by using the extended forms. +// * 'startAuthorisationWithPsuIdentfication', +// * 'startAuthorisationWithPsuAuthentication' #TODO +// * 'startAuthorisationWithAuthentciationMethodSelection' +// * The related payment initiation cannot yet be executed since a multilevel SCA is mandated. +// * The ASPSP has indicated with an 'startAuthorisation' hyperlink in the preceeding +// Payment Cancellation Response that an explicit start of the authorisation process is needed by the TPP. +// The 'startAuthorisation' hyperlink can transport more information about data which needs to be uploaded +// by using the extended forms as indicated above. +// * The related payment cancellation request cannot be applied yet since a multilevel SCA is mandate for +// executing the cancellation. +// * The signing basket needs to be authorised yet. +//""", +// EmptyBody, +// json.parse("""{ +// "challengeData" : { +// "otpMaxLength" : 0, +// "additionalInformation" : "additionalInformation", +// "image" : "image", +// "imageLink" : "http://example.com/aeiou", +// "otpFormat" : "characters", +// "data" : "data" +// }, +// "scaMethods" : "", +// "scaStatus" : "psuAuthenticated", +// "_links" : { +// "scaStatus" : "/v1.3/payments/sepa-credit-transfers/1234-wertiq-983", +// "startAuthorisationWithEncryptedPsuAuthentication" : "/v1.3/payments/sepa-credit-transfers/1234-wertiq-983", +// "scaRedirect" : "/v1.3/payments/sepa-credit-transfers/1234-wertiq-983", +// "selectAuthenticationMethod" : "/v1.3/payments/sepa-credit-transfers/1234-wertiq-983", +// "startAuthorisationWithPsuAuthentication" : "/v1.3/payments/sepa-credit-transfers/1234-wertiq-983", +// "authoriseTransaction" : "/v1.3/payments/sepa-credit-transfers/1234-wertiq-983", +// "scaOAuth" : "/v1.3/payments/sepa-credit-transfers/1234-wertiq-983", +// "updatePsuIdentification" : "/v1.3/payments/sepa-credit-transfers/1234-wertiq-983" +// }, +// "chosenScaMethod" : "", +// "psuMessage" : { } +//}"""), +// List(AuthenticatedUserIsRequired, UnknownError), +// apiTagSigningBaskets :: Nil +// ) +// +// lazy val startSigningBasketAuthorisation : OBPEndpoint = { +// case "signing-baskets" :: basketId :: "authorisations" :: Nil JsonPost _ => { +// cc => +// for { +// (Full(u), callContext) <- authenticatedAccess(cc) +// _ <- passesPsd2Pisp(callContext) +// (challenges, callContext) <- NewStyle.function.createChallengesC3( +// List(u.userId), +// ChallengeType.BERLIN_GROUP_SIGNING_BASKETS_CHALLENGE, +// None, +// getSuggestedDefaultScaMethod(), +// Some(StrongCustomerAuthenticationStatus.received), +// None, +// Some(basketId), +// None, +// callContext +// ) +// //NOTE: in OBP it support multiple challenges, but in Berlin Group it has only one challenge. The following guard is to make sure it return the 1st challenge properly. +// challenge <- NewStyle.function.tryons(InvalidConnectorResponseForCreateChallenge, 400, callContext) { +// challenges.head +// } +// } yield { +// (createStartSigningBasketAuthorisationJson(basketId, challenge), HttpCode.`201`(callContext)) +// } +// } +// } +// +// resourceDocs += ResourceDoc( +// updateSigningBasketPsuData, +// apiVersion, +// nameOf(updateSigningBasketPsuData), +// "PUT", +// "/signing-baskets/BASKETID/authorisations/AUTHORISATIONID", +// "Update PSU Data for signing basket", +// s"""${mockedDataText(false)} +//This method update PSU data on the signing basket resource if needed. +//It may authorise a igning basket within the Embedded SCA Approach where needed. +// +//Independently from the SCA Approach it supports e.g. the selection of +//the authentication method and a non-SCA PSU authentication. +// +//This methods updates PSU data on the cancellation authorisation resource if needed. +// +//There are several possible Update PSU Data requests in the context of a consent request if needed, +//which depends on the SCA approach: +// +//* Redirect SCA Approach: +// A specific Update PSU Data Request is applicable for +// * the selection of authentication methods, before choosing the actual SCA approach. +//* Decoupled SCA Approach: +// A specific Update PSU Data Request is only applicable for +// * adding the PSU Identification, if not provided yet in the Payment Initiation Request or the Account Information Consent Request, or if no OAuth2 access token is used, or +// * the selection of authentication methods. +//* Embedded SCA Approach: +// The Update PSU Data Request might be used +// * to add credentials as a first factor authentication data of the PSU and +// * to select the authentication method and +// * transaction authorisation. +// +//The SCA Approach might depend on the chosen SCA method. +//For that reason, the following possible Update PSU Data request can apply to all SCA approaches: +// +//* Select an SCA method in case of several SCA methods are available for the customer. +// +//There are the following request types on this access path: +// * Update PSU Identification +// * Update PSU Authentication +// * Select PSU Autorization Method +// WARNING: This method need a reduced header, +// therefore many optional elements are not present. +// Maybe in a later version the access path will change. +// * Transaction Authorisation +// WARNING: This method need a reduced header, +// therefore many optional elements are not present. +// Maybe in a later version the access path will change. +//""", +// json.parse("""{"scaAuthenticationData":"123"}"""), +// json.parse("""{ +// "scaStatus":"finalised", +// "authorisationId":"4f4a8b7f-9968-4183-92ab-ca512b396bfc", +// "psuMessage":"Please check your SMS at a mobile device.", +// "_links":{ +// "scaStatus":"/v1.3/payments/sepa-credit-transfers/PAYMENT_ID/4f4a8b7f-9968-4183-92ab-ca512b396bfc" +// } +// }"""), +// List(AuthenticatedUserIsRequired, UnknownError), +// apiTagSigningBaskets :: Nil +// ) +// +// lazy val updateSigningBasketPsuData : OBPEndpoint = { +// case "signing-baskets" :: basketId:: "authorisations" :: authorisationId :: Nil JsonPut json -> _ => +// cc => +// for { +// (Full(u), callContext) <- authenticatedAccess(cc) +// _ <- passesPsd2Pisp(callContext) +// failMsg = s"$InvalidJsonFormat The Json body should be the $UpdatePaymentPsuDataJson " +// updateBasketPsuDataJson <- NewStyle.function.tryons(failMsg, 400, callContext) { +// json.extract[UpdatePaymentPsuDataJson] +// } +// _ <- SigningBasketNewStyle.checkSigningBasketPayments(basketId, callContext) +// // Validate a challenge answer and get an error if any +// (boxedChallenge: Box[ChallengeTrait], callContext) <- NewStyle.function.validateChallengeAnswerC5( +// ChallengeType.BERLIN_GROUP_SIGNING_BASKETS_CHALLENGE, +// None, +// None, +// Some(basketId), +// authorisationId, +// updateBasketPsuDataJson.scaAuthenticationData, +// SuppliedAnswerType.PLAIN_TEXT_VALUE, +// callContext +// ) +// // Get the challenge after validation +// (challenge: ChallengeTrait, callContext) <- NewStyle.function.getChallenge(authorisationId, callContext) +// _ <- challenge.scaStatus match { +// case Some(status) if status.toString == StrongCustomerAuthenticationStatus.finalised.toString => // finalised +// Future { +// val basket = SigningBasketX.signingBasketProvider.vend.getSigningBasketByBasketId(basketId) +// val existAll: Box[Boolean] = +// basket.flatMap(_.payments.map(_.forall(i => Connector.connector.vend.getTransactionRequestImpl(TransactionRequestId(i), callContext).isDefined))) +// val alreadyCompleted: List[String] = +// basket.flatMap(_.payments).getOrElse(Nil).filter { i => +// Connector.connector.vend.getTransactionRequestImpl(TransactionRequestId(i), callContext) +// .exists(_._1.status == COMPLETED.toString) +// } +// if (alreadyCompleted.nonEmpty) { +// unboxFullOrFail(Empty, callContext, s"$InvalidConnectorResponse Some of paymentIds [${alreadyCompleted.mkString(",")}] are already completed") +// } else if (existAll.getOrElse(false)) { +// basket.map { i => +// i.payments.map(_.map { i => +// NewStyle.function.saveTransactionRequestStatusImpl(TransactionRequestId(i), COMPLETED.toString, callContext) +// Connector.connector.vend.getTransactionRequestImpl(TransactionRequestId(i), callContext) map { t => +// Connector.connector.vend.makePaymentV400(t._1, None, callContext) +// } +// }) +// } +// SigningBasketX.signingBasketProvider.vend.saveSigningBasketStatus(basketId, ConstantsBG.SigningBasketsStatus.ACTC.toString) +// unboxFullOrFail(boxedChallenge, callContext, s"$InvalidConnectorResponse validateChallengeAnswerC5") +// } else { // Fail due to unexisting payment +// val paymentIds = basket.flatMap(_.payments).getOrElse(Nil).mkString(",") +// unboxFullOrFail(Empty, callContext, s"$InvalidConnectorResponse Some of paymentIds [${paymentIds}] are invalid") +// } +// } +// case Some(status) if status.toString == StrongCustomerAuthenticationStatus.failed.toString => // failed +// Future { +// // Reject all related transaction requests +// val basket = SigningBasketX.signingBasketProvider.vend.getSigningBasketByBasketId(basketId) +// basket.map { i => +// i.payments.map(_.map { i => +// NewStyle.function.saveTransactionRequestStatusImpl(TransactionRequestId(i), REJECTED.toString, callContext) +// }) +// } +// // Fail in case of an error message +// unboxFullOrFail(boxedChallenge, callContext, s"$InvalidConnectorResponse validateChallengeAnswerC5") +// } +// case _ => // Fail in case of an error message +// Future(unboxFullOrFail(Empty, callContext, s"$InvalidConnectorResponse getChallenge")) +// } +// } yield { +// (JSONFactory_BERLIN_GROUP_1_3.createStartPaymentAuthorisationJson(challenge), callContext) +// } +// } +// +//} +// +// +// diff --git a/obp-api/src/main/scala/code/api/util/NewStyle.scala b/obp-api/src/main/scala/code/api/util/NewStyle.scala index 77571b44ea..231dce369b 100644 --- a/obp-api/src/main/scala/code/api/util/NewStyle.scala +++ b/obp-api/src/main/scala/code/api/util/NewStyle.scala @@ -4,7 +4,7 @@ package code.api.util import org.apache.pekko.http.scaladsl.model.HttpMethod import code.DynamicEndpoint.{DynamicEndpointProvider, DynamicEndpointT} import code.api.Constant.{SYSTEM_READ_ACCOUNTS_BERLIN_GROUP_VIEW_ID, SYSTEM_READ_BALANCES_BERLIN_GROUP_VIEW_ID} -import code.api.builder.PaymentInitiationServicePISApi.APIMethods_PaymentInitiationServicePISApi.checkPaymentServerTypeError +// checkPaymentServerTypeError was inlined from the retired BG v1.3 PIS builder (see PaymentInitiationServicePISApi.scala) import code.api.cache.Caching import code.api.dynamic.endpoint.helper.DynamicEndpointHelper import code.api.dynamic.entity.helper.{DynamicEntityHelper, DynamicEntityInfo} @@ -1178,7 +1178,9 @@ object NewStyle extends MdcLoggable{ transactionRequestBody.asInstanceOf[PeriodicSepaCreditTransfersBerlinGroupV13], callContext: Option[CallContext] ) - }else Future(throw new RuntimeException(checkPaymentServerTypeError(paymentServiceType.toString))) + }else Future(throw new RuntimeException( + s"${InvalidTransactionRequestType.replaceAll("TRANSACTION_REQUEST_TYPE", "PAYMENT_SERVICE in the URL.")}: '${paymentServiceType}'.It should be `payments` or `periodic-payments` for now, will support `bulk-payments` soon" + )) response map { i => (unboxFullOrFail(i._1, callContext, s"$InvalidConnectorResponseForCreateTransactionRequestBGV1", 400), i._2) diff --git a/obp-api/src/main/scala/code/api/util/http4s/ErrorResponseConverter.scala b/obp-api/src/main/scala/code/api/util/http4s/ErrorResponseConverter.scala index 101d142bec..1d838a7a08 100644 --- a/obp-api/src/main/scala/code/api/util/http4s/ErrorResponseConverter.scala +++ b/obp-api/src/main/scala/code/api/util/http4s/ErrorResponseConverter.scala @@ -3,12 +3,15 @@ package code.api.util.http4s import cats.effect._ import code.api.APIFailureNewStyle import code.api.JsonResponseException +import code.api.berlin.group.ConstantsBG +import code.api.berlin.group.v1_3.JSONFactory_BERLIN_GROUP_1_3.{ErrorMessageBG, ErrorMessagesBG} import code.api.util.APIUtil.JsonResponseExtractor +import code.api.util.BerlinGroupError import code.api.util.ErrorMessages._ import code.api.util.CallContext import net.liftweb.common.{Failure => LiftFailure} import net.liftweb.json.JsonParser.parse -import net.liftweb.json.compactRender +import net.liftweb.json.{Extraction, compactRender} import net.liftweb.json.JsonDSL._ import org.http4s._ import org.http4s.headers.`Content-Type` @@ -64,7 +67,7 @@ object ErrorResponseConverter { code: Int, message: String ) - + /** * Convert error response to JSON string using Lift JSON. */ @@ -72,6 +75,17 @@ object ErrorResponseConverter { val json = ("code" -> error.code) ~ ("message" -> error.message) compactRender(json) } + + private def isBerlinGroupRequest(callContext: CallContext): Boolean = + callContext.url.contains(ConstantsBG.berlinGroupVersion1.urlPrefix) + + /** Mirror the BG error body from APIUtil.failedJsonResponse for http4s paths. */ + private def toBgErrorBody(statusCode: Int, message: String, callContext: CallContext): String = { + val pathOpt = Some(callContext.url) + val codeText = BerlinGroupError.translateToBerlinGroupError(statusCode.toString, message) + val errBg = ErrorMessagesBG(tppMessages = List(ErrorMessageBG(category = "ERROR", code = codeText, path = pathOpt, text = message))) + compactRender(Extraction.decompose(errBg)) + } /** * Convert any error to http4s Response[IO]. @@ -100,11 +114,12 @@ object ErrorResponseConverter { /** Build a JSON error response using the supplied status code verbatim (used for * JsonResponseException, whose embedded JsonResponse already carries the final code). */ private def jsonErrorResponse(code: Int, message: String, callContext: CallContext): IO[Response[IO]] = { - val errorJson = OBPErrorResponse(code, message) + val body = if (isBerlinGroupRequest(callContext)) toBgErrorBody(code, message, callContext) + else toJsonString(OBPErrorResponse(code, message)) val status = org.http4s.Status.fromInt(code).getOrElse(org.http4s.Status.BadRequest) IO.pure( Response[IO](status) - .withEntity(toJsonString(errorJson)) + .withEntity(body) .withContentType(jsonContentType) .putHeaders(org.http4s.Header.Raw(CIString("Correlation-Id"), callContext.correlationId)) ) @@ -133,11 +148,12 @@ object ErrorResponseConverter { */ def apiFailureToResponse(failure: APIFailureNewStyle, callContext: CallContext): IO[Response[IO]] = { val resolvedCode = resolveStatusCode(failure.failCode, failure.failMsg, callContext) - val errorJson = OBPErrorResponse(resolvedCode, failure.failMsg) + val body = if (isBerlinGroupRequest(callContext)) toBgErrorBody(resolvedCode, failure.failMsg, callContext) + else toJsonString(OBPErrorResponse(resolvedCode, failure.failMsg)) val status = org.http4s.Status.fromInt(resolvedCode).getOrElse(org.http4s.Status.BadRequest) IO.pure( Response[IO](status) - .withEntity(toJsonString(errorJson)) + .withEntity(body) .withContentType(jsonContentType) .putHeaders(org.http4s.Header.Raw(CIString("Correlation-Id"), callContext.correlationId)) ) @@ -148,10 +164,11 @@ object ErrorResponseConverter { * Returns 400 Bad Request with failure message. */ def boxFailureToResponse(failure: LiftFailure, callContext: CallContext): IO[Response[IO]] = { - val errorJson = OBPErrorResponse(400, failure.msg) + val body = if (isBerlinGroupRequest(callContext)) toBgErrorBody(400, failure.msg, callContext) + else toJsonString(OBPErrorResponse(400, failure.msg)) IO.pure( Response[IO](org.http4s.Status.BadRequest) - .withEntity(toJsonString(errorJson)) + .withEntity(body) .withContentType(jsonContentType) .putHeaders(org.http4s.Header.Raw(CIString("Correlation-Id"), callContext.correlationId)) ) @@ -163,24 +180,27 @@ object ErrorResponseConverter { */ def unknownErrorToResponse(e: Throwable, callContext: CallContext): IO[Response[IO]] = { logger.error(s"unknownErrorToResponse says: 500 returned (correlationId=${callContext.correlationId})", e) - val errorJson = OBPErrorResponse(500, s"$UnknownError: ${e.getMessage}") + val message = s"$UnknownError: ${e.getMessage}" + val body = if (isBerlinGroupRequest(callContext)) toBgErrorBody(500, message, callContext) + else toJsonString(OBPErrorResponse(500, message)) IO.pure( Response[IO](org.http4s.Status.InternalServerError) - .withEntity(toJsonString(errorJson)) + .withEntity(body) .withContentType(jsonContentType) .putHeaders(org.http4s.Header.Raw(CIString("Correlation-Id"), callContext.correlationId)) ) } - + /** * Create error response with specific status code and message. */ def createErrorResponse(statusCode: Int, message: String, callContext: CallContext): IO[Response[IO]] = { - val errorJson = OBPErrorResponse(statusCode, message) + val body = if (isBerlinGroupRequest(callContext)) toBgErrorBody(statusCode, message, callContext) + else toJsonString(OBPErrorResponse(statusCode, message)) val status = org.http4s.Status.fromInt(statusCode).getOrElse(org.http4s.Status.BadRequest) IO.pure( Response[IO](status) - .withEntity(toJsonString(errorJson)) + .withEntity(body) .withContentType(jsonContentType) .putHeaders(org.http4s.Header.Raw(CIString("Correlation-Id"), callContext.correlationId)) ) diff --git a/obp-api/src/main/scala/code/api/util/http4s/Http4sApp.scala b/obp-api/src/main/scala/code/api/util/http4s/Http4sApp.scala index 91e6241a43..06158366e0 100644 --- a/obp-api/src/main/scala/code/api/util/http4s/Http4sApp.scala +++ b/obp-api/src/main/scala/code/api/util/http4s/Http4sApp.scala @@ -134,6 +134,8 @@ object Http4sApp { .orElse(code.api.berlin.group.v2.Http4sBGv2.wrappedRoutes.run(req)) .orElse(ukV20Routes.run(req)) .orElse(ukV31Routes.run(req)) + .orElse(code.api.berlin.group.v1_3.Http4sBGv13.wrappedRoutes.run(req)) + .orElse(code.api.berlin.group.v1_3.Http4sBGv13Alias.wrappedRoutes.run(req)) .orElse(v400Routes.run(req)) .orElse(v310Routes.run(req)) .orElse(v300Routes.run(req)) diff --git a/obp-api/src/main/scala/code/api/util/http4s/Http4sSupport.scala b/obp-api/src/main/scala/code/api/util/http4s/Http4sSupport.scala index 67a46e26ee..01faf73c89 100644 --- a/obp-api/src/main/scala/code/api/util/http4s/Http4sSupport.scala +++ b/obp-api/src/main/scala/code/api/util/http4s/Http4sSupport.scala @@ -625,8 +625,8 @@ object Http4sCallContextBuilder { */ object ResourceDocMatcher extends code.util.Helper.MdcLoggable { - // API prefix pattern: /obp/vX.X.X - private val apiPrefixPattern = """^/obp/v\d+\.\d+\.\d+""".r + // API prefix pattern: //v — handles both OBP (/obp/vX.X.X) and BG (/berlin-group/v1.3) + private val apiPrefixPattern = """^/[^/]+/v[\d.]+""".r // Pre-built index type: (VERB, apiVersion, segmentCount) -> candidates type ResourceDocIndex = Map[(String, String, Int), List[ResourceDoc]] diff --git a/obp-api/src/main/scala/code/api/util/http4s/ResourceDocMiddleware.scala b/obp-api/src/main/scala/code/api/util/http4s/ResourceDocMiddleware.scala index 721f4fcca4..bc3c71a362 100644 --- a/obp-api/src/main/scala/code/api/util/http4s/ResourceDocMiddleware.scala +++ b/obp-api/src/main/scala/code/api/util/http4s/ResourceDocMiddleware.scala @@ -225,7 +225,8 @@ object ResourceDocMiddleware extends MdcLoggable { val initialContext = ValidationContext(callContext = cc) // Validation order MUST match Lift's wrappedWithAuthCheck (APIUtil.scala:1934-1969): - // auth → bank → roles → account → view → counterparty + // beforeAuthenticateInterceptors (= validateQueryParams / validateAuthType) first, + // then auth → bank → roles → account → view → counterparty // → afterAuthenticateInterceptors (= Force-Error / AuthType / JsonSchema) // Per Lift's own comment: "A Bank MUST be checked before Roles. In opposite case // we get next paradox: We set non existing bank → We get error that we don't @@ -236,7 +237,8 @@ object ResourceDocMiddleware extends MdcLoggable { // doc's role names) when Force-Error: OBP-20006 is sent and the natural // role check would also fail. val result: Validation[ValidationContext] = for { - context <- authenticate(req, resourceDoc, initialContext) + context <- validateDuplicateQueryParams(cc, initialContext) + context <- authenticate(req, resourceDoc, context) context <- validateBank(pathParams, context) context <- authorizeRoles(resourceDoc, pathParams, context) context <- validateAccount(pathParams, context) @@ -458,6 +460,32 @@ object ResourceDocMiddleware extends MdcLoggable { } } + /** + * Port of `APIUtil.validateQueryParams` (a `beforeAuthenticateInterceptor` in Lift). + * Rejects requests with duplicate query-parameter names with 400 + * `DuplicateQueryParameters`. Returns a plain OBP `{"message":"..."}` body (not BG + * format) to match Lift's `createErrorJsonResponse` output — the test asserts on + * `ErrorMessage.message`, not `ErrorMessagesBG.tppMessages`. + */ + private def validateDuplicateQueryParams(cc: CallContext, ctx: ValidationContext): Validation[ValidationContext] = { + import DSL._ + val queryString = if (cc.url.contains("?")) cc.url.split("\\?", 2)(1) else "" + val paramNames = queryString.split("&").map(s => s.split("=", 2)(0)).filter(_.nonEmpty) + val hasDuplicates = paramNames.groupBy(identity).exists(_._2.length > 1) + if (hasDuplicates) { + import net.liftweb.json.JsonDSL._ + import net.liftweb.json.compactRender + // Match Lift's createErrorJsonResponse: {"code": 400, "message": "OBP-XXXXX: ..."} + // The test asserts extract[ErrorMessage].message where ErrorMessage(code: Int, message: String). + val body = compactRender(("code" -> 400) ~ ("message" -> code.api.util.ErrorMessages.DuplicateQueryParameters)) + val resp = Response[IO](org.http4s.Status.BadRequest) + .withEntity(body.getBytes("UTF-8")) + .withContentType(jsonContentType) + EitherT.leftT[IO, ValidationContext](resp) + } else + success(ctx) + } + /** Bank validation: checks BANK_ID and fetches bank */ private def validateBank(pathParams: Map[String, String], ctx: ValidationContext): Validation[ValidationContext] = { diff --git a/obp-api/src/test/scala/code/api/berlin/group/v1_3/AccountInformationServiceAISApiTest.scala b/obp-api/src/test/scala/code/api/berlin/group/v1_3/AccountInformationServiceAISApiTest.scala index 13f5190039..39a9f527bc 100644 --- a/obp-api/src/test/scala/code/api/berlin/group/v1_3/AccountInformationServiceAISApiTest.scala +++ b/obp-api/src/test/scala/code/api/berlin/group/v1_3/AccountInformationServiceAISApiTest.scala @@ -3,7 +3,7 @@ package code.api.berlin.group.v1_3 import code.api.Constant 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._ -import code.api.builder.AccountInformationServiceAISApi.APIMethods_AccountInformationServiceAISApi +import code.api.berlin.group.v1_3.Http4sBGv13AIS import code.api.util.APIUtil import code.api.util.APIUtil.OAuth._ import code.api.util.ErrorMessages._ @@ -23,38 +23,39 @@ import java.time.format.DateTimeFormatter class AccountInformationServiceAISApiTest extends BerlinGroupServerSetupV1_3 with DefaultUsers { - object getAccountList extends Tag(nameOf(APIMethods_AccountInformationServiceAISApi.getAccountList)) - - object getAccountDetails extends Tag(nameOf(APIMethods_AccountInformationServiceAISApi.getAccountDetails)) + object getAccountList extends Tag(nameOf(Http4sBGv13AIS.getAccountList)) + + object getAccountDetails extends Tag(nameOf(Http4sBGv13AIS.getAccountDetails)) - object getBalances extends Tag(nameOf(APIMethods_AccountInformationServiceAISApi.getBalances)) + object getBalances extends Tag(nameOf(Http4sBGv13AIS.getBalances)) - object getTransactionList extends Tag(nameOf(APIMethods_AccountInformationServiceAISApi.getTransactionList)) + object getTransactionList extends Tag(nameOf(Http4sBGv13AIS.getTransactionList)) - object getTransactionDetails extends Tag(nameOf(APIMethods_AccountInformationServiceAISApi.getTransactionDetails)) + object getTransactionDetails extends Tag(nameOf(Http4sBGv13AIS.getTransactionDetails)) - object getCardAccountTransactionList extends Tag(nameOf(APIMethods_AccountInformationServiceAISApi.getCardAccountTransactionList)) + object getCardAccountTransactionList extends Tag(nameOf(Http4sBGv13AIS.getCardAccountTransactionList)) - object createConsent extends Tag(nameOf(APIMethods_AccountInformationServiceAISApi.createConsent)) + object createConsent extends Tag(nameOf(Http4sBGv13AIS.createConsent)) - object deleteConsent extends Tag(nameOf(APIMethods_AccountInformationServiceAISApi.deleteConsent)) + object deleteConsent extends Tag(nameOf(Http4sBGv13AIS.deleteConsent)) - object getConsentInformation extends Tag(nameOf(APIMethods_AccountInformationServiceAISApi.getConsentInformation)) + object getConsentInformation extends Tag(nameOf(Http4sBGv13AIS.getConsentInformation)) - object getConsentStatus extends Tag(nameOf(APIMethods_AccountInformationServiceAISApi.getConsentStatus)) + object getConsentStatus extends Tag(nameOf(Http4sBGv13AIS.getConsentStatus)) - object startConsentAuthorisationTransactionAuthorisation extends Tag(nameOf(APIMethods_AccountInformationServiceAISApi.startConsentAuthorisationTransactionAuthorisation)) - object startConsentAuthorisationUpdatePsuAuthentication extends Tag(nameOf(APIMethods_AccountInformationServiceAISApi.startConsentAuthorisationUpdatePsuAuthentication)) - object startConsentAuthorisationSelectPsuAuthenticationMethod extends Tag(nameOf(APIMethods_AccountInformationServiceAISApi.startConsentAuthorisationSelectPsuAuthenticationMethod)) + // body-dispatch variants — use string literals since handlers are unified in http4s + object startConsentAuthorisationTransactionAuthorisation extends Tag("startConsentAuthorisationTransactionAuthorisation") + object startConsentAuthorisationUpdatePsuAuthentication extends Tag("startConsentAuthorisationUpdatePsuAuthentication") + object startConsentAuthorisationSelectPsuAuthenticationMethod extends Tag("startConsentAuthorisationSelectPsuAuthenticationMethod") - object getConsentAuthorisation extends Tag(nameOf(APIMethods_AccountInformationServiceAISApi.getConsentAuthorisation)) + object getConsentAuthorisation extends Tag(nameOf(Http4sBGv13AIS.getConsentAuthorisation)) - object getConsentScaStatus extends Tag(nameOf(APIMethods_AccountInformationServiceAISApi.getConsentScaStatus)) + object getConsentScaStatus extends Tag(nameOf(Http4sBGv13AIS.getConsentScaStatus)) - object updateConsentsPsuDataTransactionAuthorisation extends Tag(nameOf(APIMethods_AccountInformationServiceAISApi.updateConsentsPsuDataTransactionAuthorisation)) - object updateConsentsPsuDataUpdatePsuAuthentication extends Tag(nameOf(APIMethods_AccountInformationServiceAISApi.updateConsentsPsuDataUpdatePsuAuthentication)) - object updateConsentsPsuDataUpdateSelectPsuAuthenticationMethod extends Tag(nameOf(APIMethods_AccountInformationServiceAISApi.updateConsentsPsuDataUpdateSelectPsuAuthenticationMethod)) - object updateConsentsPsuDataUpdateAuthorisationConfirmation extends Tag(nameOf(APIMethods_AccountInformationServiceAISApi.updateConsentsPsuDataUpdateAuthorisationConfirmation)) + object updateConsentsPsuDataTransactionAuthorisation extends Tag("updateConsentsPsuDataTransactionAuthorisation") + object updateConsentsPsuDataUpdatePsuAuthentication extends Tag("updateConsentsPsuDataUpdatePsuAuthentication") + object updateConsentsPsuDataUpdateSelectPsuAuthenticationMethod extends Tag("updateConsentsPsuDataUpdateSelectPsuAuthenticationMethod") + object updateConsentsPsuDataUpdateAuthorisationConfirmation extends Tag("updateConsentsPsuDataUpdateAuthorisationConfirmation") def getNextMonthDate(): String = { val nextMonthDate = LocalDate.now().plusMonths(1) diff --git a/obp-api/src/test/scala/code/api/berlin/group/v1_3/ConfirmationOfFundsServicePIISApiTest.scala b/obp-api/src/test/scala/code/api/berlin/group/v1_3/ConfirmationOfFundsServicePIISApiTest.scala index 08e0c6f58c..843a008bc1 100644 --- a/obp-api/src/test/scala/code/api/berlin/group/v1_3/ConfirmationOfFundsServicePIISApiTest.scala +++ b/obp-api/src/test/scala/code/api/berlin/group/v1_3/ConfirmationOfFundsServicePIISApiTest.scala @@ -2,7 +2,7 @@ package code.api.berlin.group.v1_3 import code.api.berlin.group.v1_3.JSONFactory_BERLIN_GROUP_1_3.ErrorMessagesBG import com.openbankproject.commons.model.ErrorMessage -import code.api.builder.ConfirmationOfFundsServicePIISApi.APIMethods_ConfirmationOfFundsServicePIISApi +import code.api.berlin.group.v1_3.{Http4sBGv13PIIS => APIMethods_ConfirmationOfFundsServicePIISApi} import code.api.util.APIUtil.OAuth._ import code.api.util.ErrorMessages.{BankAccountNotFound, BankAccountNotFoundByIban, InvalidJsonContent, InvalidJsonFormat} import code.model.dataAccess.{BankAccountRouting, MappedBankAccount} @@ -21,8 +21,8 @@ class ConfirmationOfFundsServicePIISApiTest extends BerlinGroupServerSetupV1_3 w val checkAvailabilityOfFundsJsonBody = APIMethods_ConfirmationOfFundsServicePIISApi .resourceDocs - .filter( _.partialFunction == APIMethods_ConfirmationOfFundsServicePIISApi.checkAvailabilityOfFunds) - .head.exampleRequestBody.asInstanceOf[JvalueCaseClass] //All the Json String convert to JvalueCaseClass implicitly + .filter(_.partialFunctionName == "checkAvailabilityOfFunds") + .head.exampleRequestBody.asInstanceOf[JvalueCaseClass] //All the Json String convert to JvalueCaseClass implicitly .jvalueToCaseclass diff --git a/obp-api/src/test/scala/code/api/berlin/group/v1_3/PaymentInitiationServicePISApiTest.scala b/obp-api/src/test/scala/code/api/berlin/group/v1_3/PaymentInitiationServicePISApiTest.scala index 9e0ae6afa0..ed5603ff76 100644 --- a/obp-api/src/test/scala/code/api/berlin/group/v1_3/PaymentInitiationServicePISApiTest.scala +++ b/obp-api/src/test/scala/code/api/berlin/group/v1_3/PaymentInitiationServicePISApiTest.scala @@ -4,7 +4,7 @@ import code.api.BerlinGroup.ScaStatus import code.api.Constant.SYSTEM_INITIATE_PAYMENTS_BERLIN_GROUP_VIEW_ID import code.api.berlin.group.v1_3.JSONFactory_BERLIN_GROUP_1_3.{CancelPaymentResponseJson, CancellationJsonV13, ErrorMessagesBG, InitiatePaymentResponseJson, StartPaymentAuthorisationJson} import code.api.berlin.group.v1_3.model.{ScaStatusResponse, TransactionStatus, UpdatePsuAuthenticationResponse} -import code.api.builder.PaymentInitiationServicePISApi.APIMethods_PaymentInitiationServicePISApi +import code.api.berlin.group.v1_3.Http4sBGv13PIS import code.api.util.APIUtil.OAuth._ import code.api.util.APIUtil.extractErrorMessageCode import code.api.util.ErrorMessages._ @@ -21,31 +21,30 @@ import org.scalatest.Tag class PaymentInitiationServicePISApiTest extends BerlinGroupServerSetupV1_3 with DefaultUsers { object PIS extends Tag("Payment Initiation Service (PIS)") - object initiatePayment extends Tag(nameOf(APIMethods_PaymentInitiationServicePISApi.initiatePayments)) - object getPaymentInformation extends Tag(nameOf(APIMethods_PaymentInitiationServicePISApi.getPaymentInformation)) - object getPaymentInitiationStatus extends Tag(nameOf(APIMethods_PaymentInitiationServicePISApi.getPaymentInitiationStatus)) - - object startPaymentAuthorisationTransactionAuthorisation extends Tag(nameOf(APIMethods_PaymentInitiationServicePISApi.startPaymentAuthorisationTransactionAuthorisation )) - object startPaymentAuthorisationUpdatePsuAuthentication extends Tag(nameOf(APIMethods_PaymentInitiationServicePISApi.startPaymentAuthorisationUpdatePsuAuthentication)) - object startPaymentAuthorisationSelectPsuAuthenticationMethod extends Tag(nameOf(APIMethods_PaymentInitiationServicePISApi.startPaymentAuthorisationSelectPsuAuthenticationMethod)) - object getPaymentInitiationAuthorisation extends Tag(nameOf(APIMethods_PaymentInitiationServicePISApi.getPaymentInitiationAuthorisation)) - object getPaymentInitiationScaStatus extends Tag(nameOf(APIMethods_PaymentInitiationServicePISApi.getPaymentInitiationScaStatus)) - object updatePaymentPsuDataTransactionAuthorisation extends Tag(nameOf(APIMethods_PaymentInitiationServicePISApi.updatePaymentPsuDataTransactionAuthorisation)) - object updatePaymentPsuDataUpdatePsuAuthentication extends Tag(nameOf(APIMethods_PaymentInitiationServicePISApi.updatePaymentPsuDataUpdatePsuAuthentication)) - object updatePaymentPsuDataSelectPsuAuthenticationMethod extends Tag(nameOf(APIMethods_PaymentInitiationServicePISApi.updatePaymentPsuDataSelectPsuAuthenticationMethod)) - object updatePaymentPsuDataAuthorisationConfirmation extends Tag(nameOf(APIMethods_PaymentInitiationServicePISApi.updatePaymentPsuDataAuthorisationConfirmation)) - - - object cancelPayment extends Tag(nameOf(APIMethods_PaymentInitiationServicePISApi.cancelPayment)) - object startPaymentInitiationCancellationAuthorisationTransactionAuthorisation extends Tag(nameOf(APIMethods_PaymentInitiationServicePISApi.startPaymentInitiationCancellationAuthorisationTransactionAuthorisation)) - object startPaymentInitiationCancellationAuthorisationUpdatePsuAuthentication extends Tag(nameOf(APIMethods_PaymentInitiationServicePISApi.startPaymentInitiationCancellationAuthorisationUpdatePsuAuthentication)) - object startPaymentInitiationCancellationAuthorisationSelectPsuAuthenticationMethod extends Tag(nameOf(APIMethods_PaymentInitiationServicePISApi.startPaymentInitiationCancellationAuthorisationSelectPsuAuthenticationMethod)) - object getPaymentInitiationCancellationAuthorisationInformation extends Tag(nameOf(APIMethods_PaymentInitiationServicePISApi.getPaymentInitiationCancellationAuthorisationInformation)) - object getPaymentCancellationScaStatus extends Tag(nameOf(APIMethods_PaymentInitiationServicePISApi.getPaymentCancellationScaStatus)) - object updatePaymentCancellationPsuDataTransactionAuthorisation extends Tag(nameOf(APIMethods_PaymentInitiationServicePISApi.updatePaymentCancellationPsuDataTransactionAuthorisation)) - object updatePaymentCancellationPsuDataUpdatePsuAuthentication extends Tag(nameOf(APIMethods_PaymentInitiationServicePISApi.updatePaymentCancellationPsuDataUpdatePsuAuthentication)) - object updatePaymentCancellationPsuDataSelectPsuAuthenticationMethod extends Tag(nameOf(APIMethods_PaymentInitiationServicePISApi.updatePaymentCancellationPsuDataSelectPsuAuthenticationMethod)) - object updatePaymentCancellationPsuDataAuthorisationConfirmation extends Tag(nameOf(APIMethods_PaymentInitiationServicePISApi.updatePaymentCancellationPsuDataAuthorisationConfirmation)) + object initiatePayment extends Tag(nameOf(Http4sBGv13PIS.initiatePayments)) + object getPaymentInformation extends Tag(nameOf(Http4sBGv13PIS.getPaymentInformation)) + object getPaymentInitiationStatus extends Tag(nameOf(Http4sBGv13PIS.getPaymentInitiationStatus)) + // body-dispatch variants — use string literals since handlers are unified in http4s + object startPaymentAuthorisationTransactionAuthorisation extends Tag("startPaymentAuthorisationTransactionAuthorisation") + object startPaymentAuthorisationUpdatePsuAuthentication extends Tag("startPaymentAuthorisationUpdatePsuAuthentication") + object startPaymentAuthorisationSelectPsuAuthenticationMethod extends Tag("startPaymentAuthorisationSelectPsuAuthenticationMethod") + object getPaymentInitiationAuthorisation extends Tag(nameOf(Http4sBGv13PIS.getPaymentInitiationAuthorisation)) + object getPaymentInitiationScaStatus extends Tag(nameOf(Http4sBGv13PIS.getPaymentInitiationScaStatus)) + object updatePaymentPsuDataTransactionAuthorisation extends Tag("updatePaymentPsuDataTransactionAuthorisation") + object updatePaymentPsuDataUpdatePsuAuthentication extends Tag("updatePaymentPsuDataUpdatePsuAuthentication") + object updatePaymentPsuDataSelectPsuAuthenticationMethod extends Tag("updatePaymentPsuDataSelectPsuAuthenticationMethod") + object updatePaymentPsuDataAuthorisationConfirmation extends Tag("updatePaymentPsuDataAuthorisationConfirmation") + + object cancelPayment extends Tag(nameOf(Http4sBGv13PIS.cancelPayment)) + object startPaymentInitiationCancellationAuthorisationTransactionAuthorisation extends Tag("startPaymentInitiationCancellationAuthorisationTransactionAuthorisation") + object startPaymentInitiationCancellationAuthorisationUpdatePsuAuthentication extends Tag("startPaymentInitiationCancellationAuthorisationUpdatePsuAuthentication") + object startPaymentInitiationCancellationAuthorisationSelectPsuAuthenticationMethod extends Tag("startPaymentInitiationCancellationAuthorisationSelectPsuAuthenticationMethod") + object getPaymentInitiationCancellationAuthorisationInformation extends Tag(nameOf(Http4sBGv13PIS.getPaymentInitiationCancellationAuthorisationInformation)) + object getPaymentCancellationScaStatus extends Tag(nameOf(Http4sBGv13PIS.getPaymentCancellationScaStatus)) + object updatePaymentCancellationPsuDataTransactionAuthorisation extends Tag("updatePaymentCancellationPsuDataTransactionAuthorisation") + object updatePaymentCancellationPsuDataUpdatePsuAuthentication extends Tag("updatePaymentCancellationPsuDataUpdatePsuAuthentication") + object updatePaymentCancellationPsuDataSelectPsuAuthenticationMethod extends Tag("updatePaymentCancellationPsuDataSelectPsuAuthenticationMethod") + object updatePaymentCancellationPsuDataAuthorisationConfirmation extends Tag("updatePaymentCancellationPsuDataAuthorisationConfirmation") feature(s"test the BG v1.3 -${initiatePayment.name}") { scenario("Failed Case - Wrong Json format Body", BerlinGroupV1_3, PIS, initiatePayment) { @@ -376,10 +375,9 @@ class PaymentInitiationServicePISApiTest extends BerlinGroupServerSetupV1_3 with paymentInitiationScaStatus should be (ScaStatus.received.toString) Then(s"We can test the ${updatePaymentPsuDataTransactionAuthorisation.name}") - val updatePaymentPsuDataJsonBody = APIMethods_PaymentInitiationServicePISApi - .resourceDocs - .filter( _.partialFunction == APIMethods_PaymentInitiationServicePISApi.updatePaymentPsuDataTransactionAuthorisation) - .head.exampleRequestBody.asInstanceOf[JvalueCaseClass] //All the Json String convert to JvalueCaseClass implicitly + val updatePaymentPsuDataJsonBody = Http4sBGv13PIS.resourceDocs + .filter(_.partialFunctionName == "updatePaymentPsuDataTransactionAuthorisation") + .head.exampleRequestBody.asInstanceOf[JvalueCaseClass] //All the Json String convert to JvalueCaseClass implicitly .jvalueToCaseclass val requestUpdatePaymentPsuData = (V1_3_BG / PaymentServiceTypes.payments.toString / TransactionRequestTypes.SEPA_CREDIT_TRANSFERS.toString / paymentId / "authorisations"/authorisationId).PUT <@ (user1) diff --git a/obp-api/src/test/scala/code/api/berlin/group/v1_3/SigningBasketServiceSBSApiTest.scala b/obp-api/src/test/scala/code/api/berlin/group/v1_3/SigningBasketServiceSBSApiTest.scala index 31eacb283d..e51ac5b983 100644 --- a/obp-api/src/test/scala/code/api/berlin/group/v1_3/SigningBasketServiceSBSApiTest.scala +++ b/obp-api/src/test/scala/code/api/berlin/group/v1_3/SigningBasketServiceSBSApiTest.scala @@ -4,7 +4,7 @@ import code.api.Constant.SYSTEM_INITIATE_PAYMENTS_BERLIN_GROUP_VIEW_ID import code.api.berlin.group.ConstantsBG import code.api.berlin.group.v1_3.JSONFactory_BERLIN_GROUP_1_3.{AuthorisationJsonV13, ErrorMessagesBG, InitiatePaymentResponseJson, PostSigningBasketJsonV13, ScaStatusJsonV13, SigningBasketGetResponseJson, SigningBasketResponseJson, StartPaymentAuthorisationJson} import code.api.berlin.group.v1_3.model.TransactionStatus -import code.api.builder.SigningBasketsApi.APIMethods_SigningBasketsApi +import code.api.berlin.group.v1_3.{Http4sBGv13SigningBaskets => APIMethods_SigningBasketsApi} import code.api.util.APIUtil.OAuth._ import code.api.util.ErrorMessages._ import code.model.dataAccess.BankAccountRouting diff --git a/obp-api/src/test/scala/code/util/APIUtilHeavyTest.scala b/obp-api/src/test/scala/code/util/APIUtilHeavyTest.scala index 080350d263..1ea667182f 100644 --- a/obp-api/src/test/scala/code/util/APIUtilHeavyTest.scala +++ b/obp-api/src/test/scala/code/util/APIUtilHeavyTest.scala @@ -30,7 +30,7 @@ package code.util import code.api.Constant.SYSTEM_OWNER_VIEW_ID import code.api.UKOpenBanking.v3_1_0.Http4sUKOBv310AccountAccess import code.api.berlin.group.ConstantsBG -import code.api.builder.AccountInformationServiceAISApi.APIMethods_AccountInformationServiceAISApi +import code.api.berlin.group.v1_3.Http4sBGv13AIS import code.api.util._ import code.api.v4_0_0.{Http4s400, OBPAPI4_0_0, V400ServerSetup} import code.setup.PropsReset @@ -128,16 +128,24 @@ class APIUtilHeavyTest extends V400ServerSetup with PropsReset { allowedOperationIds2 contains("OBPv4.0.0-callsLimit") should be (false) - val bgResourceDocsV13 = APIMethods_AccountInformationServiceAISApi.resourceDocs - val bgEndpointsV13 = APIMethods_AccountInformationServiceAISApi.endpoints + // BG v1.3 is fully on http4s; getAllowedResourceDocs needs non-null partialFunctions, + // so filter Http4sBGv13AIS.resourceDocs directly by props instead (mirrors UK v3.1 pattern). + val bgResourceDocsV13 = Http4sBGv13AIS.resourceDocs + def filterBGv13Docs() = { + val disabledIds = APIUtil.getDisabledEndpointOperationIds().toSet + val enabledIds = APIUtil.getEnabledEndpointOperationIds().toSet + bgResourceDocsV13.filter(rd => + !disabledIds.contains(rd.operationId) && + (enabledIds.contains(rd.operationId) || enabledIds.isEmpty) + ).map(_.operationId).toList + } setPropsValues( "api_disabled_endpoints" -> s"[BG${bgVersion}-createConsent,BG${bgVersion}-deleteConsent]", "api_enabled_endpoints" -> "[]" ) - val allowedEndpoints3: List[APIUtil.ResourceDoc] = APIUtil.getAllowedResourceDocs(bgEndpointsV13, bgResourceDocsV13).toList - val allowedOperationIds3 = allowedEndpoints3.map(_.operationId) + val allowedOperationIds3 = filterBGv13Docs() allowedOperationIds3 contains(s"BG${bgVersion}-getCardAccountTransactionList") should be (true) allowedOperationIds3 contains(s"BG${bgVersion}-createConsent") should be (false)