Skip to content

Commit 96318c6

Browse files
authored
Merge pull request #2623 from hongwei1/develop
feature/added the Hold payments and getHoldingAccounts endpoints
2 parents 02989dc + db86231 commit 96318c6

File tree

11 files changed

+282
-18
lines changed

11 files changed

+282
-18
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,4 @@ marketing_diagram_generation/outputs/*
3939
.specstory
4040
project/project
4141
coursier
42+
metals.sbt

obp-api/src/main/resources/props/sample.props.template

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,7 @@ transactionRequests_enabled=false
362362
transactionRequests_connector=mapped
363363

364364
## Transaction Request Types that are supported on this server. Possible values might include SANDBOX_TAN, COUNTERPARTY, SEPA, FREE_FORM
365-
transactionRequests_supported_types=SANDBOX_TAN,COUNTERPARTY,SEPA,ACCOUNT_OTP,ACCOUNT,SIMPLE
365+
transactionRequests_supported_types=SANDBOX_TAN,COUNTERPARTY,SEPA,ACCOUNT_OTP,ACCOUNT,SIMPLE,HOLD
366366

367367
## Transaction request challenge threshold. Level at which challenge is created and needs to be answered.
368368
## The Currency is EUR unless set with transactionRequests_challenge_currency.
@@ -1075,6 +1075,7 @@ database_messages_scheduler_interval=3600
10751075
# -- SCA (Strong Customer Authentication) method for OTP challenge-------
10761076
# ACCOUNT_OTP_INSTRUCTION_TRANSPORT=DUMMY
10771077
# SIMPLE_OTP_INSTRUCTION_TRANSPORT=DUMMY
1078+
# HOLD_OTP_INSTRUCTION_TRANSPORT=DUMMY
10781079
# SEPA_OTP_INSTRUCTION_TRANSPORT=DUMMY
10791080
# FREE_FORM_OTP_INSTRUCTION_TRANSPORT=DUMMY
10801081
# COUNTERPARTY_OTP_INSTRUCTION_TRANSPORT=DUMMY

obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5820,6 +5820,12 @@ object SwaggerDefinitionsJSON {
58205820
description = descriptionExample.value
58215821
)
58225822

5823+
// HOLD sample (V600)
5824+
lazy val transactionRequestBodyHoldJsonV600 = TransactionRequestBodyHoldJsonV600(
5825+
value = amountOfMoneyJsonV121,
5826+
description = descriptionExample.value
5827+
)
5828+
58235829
//The common error or success format.
58245830
//Just some helper format to use in Json
58255831
case class NotSupportedYet()

obp-api/src/main/scala/code/api/util/migration/Migration.scala

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ object Migration extends MdcLoggable {
8181
populateTheFieldDeletedAtResourceUser(startedBeforeSchemifier)
8282
populateTheFieldIsActiveAtProductAttribute(startedBeforeSchemifier)
8383
alterColumnUsernameProviderFirstnameAndLastnameAtAuthUser(startedBeforeSchemifier)
84+
populateMissingProviderAtAuthUser(startedBeforeSchemifier)
8485
alterColumnEmailAtResourceUser(startedBeforeSchemifier)
8586
alterColumnNameAtProductFee(startedBeforeSchemifier)
8687
addFastFirehoseAccountsView(startedBeforeSchemifier)
@@ -347,6 +348,17 @@ object Migration extends MdcLoggable {
347348
}
348349
}
349350
}
351+
private def populateMissingProviderAtAuthUser(startedBeforeSchemifier: Boolean): Boolean = {
352+
if(startedBeforeSchemifier == true) {
353+
logger.warn(s"Migration.database.populateMissingProviderAtAuthUser(true) cannot be run before Schemifier.")
354+
true
355+
} else {
356+
val name = nameOf(populateMissingProviderAtAuthUser(startedBeforeSchemifier))
357+
runOnce(name) {
358+
MigrationOfAuthUser.populateMissingProviderWithLocalIdentity(name)
359+
}
360+
}
361+
}
350362
private def alterColumnEmailAtResourceUser(startedBeforeSchemifier: Boolean): Boolean = {
351363
if(startedBeforeSchemifier == true) {
352364
logger.warn(s"Migration.database.alterColumnEmailAtResourceUser(true) cannot be run before Schemifier.")

obp-api/src/main/scala/code/api/util/migration/MigrationOfAuthUser.scala

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package code.api.util.migration
33
import java.time.format.DateTimeFormatter
44
import java.time.{ZoneId, ZonedDateTime}
55

6+
import code.api.Constant
67
import code.api.util.APIUtil
78
import code.api.util.migration.Migration.{DbFunction, saveLog}
89
import code.util.Helper
@@ -74,6 +75,45 @@ object MigrationOfAuthUser {
7475
}
7576
}
7677

78+
def populateMissingProviderWithLocalIdentity(name: String): Boolean = {
79+
DbFunction.tableExists(AuthUser) match {
80+
case true =>
81+
val startDate = System.currentTimeMillis()
82+
val commitId: String = APIUtil.gitCommit
83+
var isSuccessful = false
84+
85+
// Make back up
86+
DbFunction.makeBackUpOfTable(AuthUser)
87+
88+
val updatedRows =
89+
for {
90+
user <- AuthUser.findAll()
91+
providerValue = Option(user.provider.get).map(_.trim).getOrElse("") if providerValue.isEmpty
92+
} yield {
93+
user.provider(Constant.localIdentityProvider).saveMe()
94+
}
95+
96+
val endDate = System.currentTimeMillis()
97+
val comment: String =
98+
s"""Updated number of rows:
99+
|${updatedRows.size}
100+
|""".stripMargin
101+
isSuccessful = true
102+
saveLog(name, commitId, isSuccessful, startDate, endDate, comment)
103+
isSuccessful
104+
105+
case false =>
106+
val startDate = System.currentTimeMillis()
107+
val commitId: String = APIUtil.gitCommit
108+
val isSuccessful = false
109+
val endDate = System.currentTimeMillis()
110+
val comment: String =
111+
s"""${AuthUser._dbTableNameLC} table does not exist""".stripMargin
112+
saveLog(name, commitId, isSuccessful, startDate, endDate, comment)
113+
isSuccessful
114+
}
115+
}
116+
77117
def dropIndexAtColumnUsername(name: String): Boolean = {
78118
DbFunction.tableExists(AuthUser) match {
79119
case true =>

obp-api/src/main/scala/code/api/v6_0_0/APIMethods600.scala

Lines changed: 104 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,22 @@
11
package code.api.v6_0_0
22

3-
import code.api.{APIFailureNewStyle, DirectLogin, ObpApiFailure}
4-
import code.api.v6_0_0.JSONFactory600
3+
import code.accountattribute.AccountAttributeX
4+
import code.api.{DirectLogin, ObpApiFailure}
55
import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON._
66
import code.api.util.APIUtil._
7-
import code.api.util.ApiRole.{CanCreateEntitlementAtOneBank, CanReadDynamicResourceDocsAtOneBank, canCreateBank, canDeleteRateLimiting, canReadCallLimits, canSetCallLimits}
7+
import code.api.util.ApiRole._
88
import code.api.util.ApiTag._
99
import code.api.util.ErrorMessages.{$UserNotLoggedIn, InvalidDateFormat, InvalidJsonFormat, UnknownError, _}
1010
import code.api.util.FutureUtil.EndpointContext
11-
import code.api.util.{APIUtil, ErrorMessages, NewStyle, RateLimitingUtil}
1211
import code.api.util.NewStyle.HttpCode
13-
import code.api.v5_0_0.{JSONFactory500, PostBankJson500}
12+
import code.api.util.{APIUtil, ErrorMessages, NewStyle, RateLimitingUtil}
13+
import code.api.v3_0_0.JSONFactory300
14+
import code.api.v5_0_0.JSONFactory500
1415
import code.api.v6_0_0.JSONFactory600.{createActiveCallLimitsJsonV600, createCallLimitJsonV600, createCurrentUsageJson}
1516
import code.bankconnectors.LocalMappedConnectorInternal
1617
import code.bankconnectors.LocalMappedConnectorInternal._
1718
import code.entitlement.Entitlement
19+
import code.model._
1820
import code.ratelimiting.RateLimitingDI
1921
import code.util.Helper
2022
import code.util.Helper.SILENCE_IS_GOLDEN
@@ -23,11 +25,10 @@ import com.github.dwickern.macros.NameOf.nameOf
2325
import com.openbankproject.commons.ExecutionContext.Implicits.global
2426
import com.openbankproject.commons.model._
2527
import com.openbankproject.commons.util.{ApiVersion, ScannedApiVersion}
26-
import net.liftweb.common.{Box, Empty, Full}
28+
import net.liftweb.common.{Empty, Full}
2729
import net.liftweb.http.rest.RestHelper
2830

2931
import java.text.SimpleDateFormat
30-
import java.util.Date
3132
import scala.collection.immutable.{List, Nil}
3233
import scala.collection.mutable.ArrayBuffer
3334
import scala.concurrent.Future
@@ -49,6 +50,102 @@ trait APIMethods600 {
4950
val codeContext = CodeContext(staticResourceDocs, apiRelations)
5051

5152

53+
staticResourceDocs += ResourceDoc(
54+
createTransactionRequestHold,
55+
implementedInApiVersion,
56+
nameOf(createTransactionRequestHold),
57+
"POST",
58+
"/banks/BANK_ID/accounts/ACCOUNT_ID/owner/transaction-request-types/HOLD/transaction-requests",
59+
"Create Transaction Request (HOLD)",
60+
s"""
61+
|
62+
|Create a transaction request to move funds from the account to its Holding Account.
63+
|If the Holding Account does not exist, it will be created automatically.
64+
|
65+
|${transactionRequestGeneralText}
66+
|
67+
""".stripMargin,
68+
transactionRequestBodyHoldJsonV600,
69+
transactionRequestWithChargeJSON400,
70+
List(
71+
$UserNotLoggedIn,
72+
$BankNotFound,
73+
$BankAccountNotFound,
74+
InsufficientAuthorisationToCreateTransactionRequest,
75+
InvalidTransactionRequestType,
76+
InvalidJsonFormat,
77+
NotPositiveAmount,
78+
InvalidTransactionRequestCurrency,
79+
TransactionDisabled,
80+
UnknownError
81+
),
82+
List(apiTagTransactionRequest, apiTagPSD2PIS, apiTagPsd2)
83+
)
84+
85+
lazy val createTransactionRequestHold: OBPEndpoint = {
86+
case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: ViewId(viewId) :: "transaction-request-types" ::
87+
"HOLD" :: "transaction-requests" :: Nil JsonPost json -> _ =>
88+
cc => implicit val ec = EndpointContext(Some(cc))
89+
val transactionRequestType = TransactionRequestType("HOLD")
90+
LocalMappedConnectorInternal.createTransactionRequest(bankId, accountId, viewId , transactionRequestType, json)
91+
}
92+
93+
// --- GET Holding Account by Parent ---
94+
staticResourceDocs += ResourceDoc(
95+
getHoldingAccountByReleaser,
96+
implementedInApiVersion,
97+
nameOf(getHoldingAccountByReleaser),
98+
"GET",
99+
"/banks/BANK_ID/accounts/ACCOUNT_ID/VIEW_ID/holding-accounts",
100+
"Get Holding Accounts By Releaser",
101+
s"""
102+
|
103+
|Return the first Holding Account linked to the given releaser account via account attribute `RELEASER_ACCOUNT_ID`.
104+
|Response is wrapped in a list and includes account attributes.
105+
|
106+
""".stripMargin,
107+
EmptyBody,
108+
moderatedCoreAccountsJsonV300,
109+
List(
110+
$UserNotLoggedIn,
111+
$BankNotFound,
112+
$BankAccountNotFound,
113+
$UserNoPermissionAccessView,
114+
UnknownError
115+
),
116+
List(apiTagAccount)
117+
)
118+
119+
lazy val getHoldingAccountByReleaser: OBPEndpoint = {
120+
case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: ViewId(viewId) :: "holding-accounts" :: Nil JsonGet _ =>
121+
cc => implicit val ec = EndpointContext(Some(cc))
122+
for {
123+
(user @Full(u), _, _, view, callContext) <- SS.userBankAccountView
124+
// Find accounts by attribute RELEASER_ACCOUNT_ID
125+
(accountIdsBox, callContext) <- AccountAttributeX.accountAttributeProvider.vend.getAccountIdsByParams(bankId, Map("RELEASER_ACCOUNT_ID" -> List(accountId.value))) map { ids => (ids, callContext) }
126+
accountIds = accountIdsBox.getOrElse(Nil)
127+
// load the first holding account
128+
holdingOpt <- {
129+
def firstHolding(ids: List[String]): Future[Option[BankAccount]] = ids match {
130+
case Nil => Future.successful(None)
131+
case id :: tail =>
132+
NewStyle.function.getBankAccount(bankId, AccountId(id), callContext).flatMap { case (acc, cc) =>
133+
if (acc.accountType == "HOLDING") Future.successful(Some(acc)) else firstHolding(tail)
134+
}
135+
}
136+
firstHolding(accountIds)
137+
}
138+
holding <- NewStyle.function.tryons($BankAccountNotFound, 404, callContext) { holdingOpt.get }
139+
moderatedAccount <- Future { holding.moderatedBankAccount(view, BankIdAccountId(holding.bankId, holding.accountId), user, callContext) } map {
140+
x => unboxFullOrFail(x, callContext, UnknownError)
141+
}
142+
(attributes, callContext) <- NewStyle.function.getAccountAttributesByAccount(bankId, holding.accountId, callContext)
143+
} yield {
144+
val accountsJson = JSONFactory300.createFirehoseCoreBankAccountJSON(List(moderatedAccount), Some(attributes))
145+
(accountsJson, HttpCode.`200`(callContext))
146+
}
147+
}
148+
52149
staticResourceDocs += ResourceDoc(
53150
getCurrentCallsLimit,
54151
implementedInApiVersion,

obp-api/src/main/scala/code/api/v6_0_0/JSONFactory6.0.0.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,12 @@ case class TransactionRequestBodyEthSendRawTransactionJsonV600(
128128
description: String
129129
)
130130

131+
// ---------------- HOLD models (V600) ----------------
132+
case class TransactionRequestBodyHoldJsonV600(
133+
value: AmountOfMoneyJsonV121,
134+
description: String
135+
) extends TransactionRequestCommonBodyJSON
136+
131137
case class UserJsonV600(
132138
user_id: String,
133139
email : String,

0 commit comments

Comments
 (0)