Standing Order - WIP

This commit is contained in:
Marko Milić 2019-11-12 12:46:02 +01:00
parent a3e9684b7f
commit 67101f256d
14 changed files with 378 additions and 8 deletions

View File

@ -89,6 +89,7 @@ import code.scheduler.DatabaseDriverScheduler
import code.scope.{MappedScope, MappedUserScope}
import code.snippet.{OAuthAuthorisation, OAuthWorkedThanks}
import code.socialmedia.MappedSocialMedia
import code.standingorders.StandingOrder
import code.taxresidence.MappedTaxResidence
import code.transaction.MappedTransaction
import code.transactionChallenge.MappedExpectedChallengeAnswer
@ -665,5 +666,6 @@ object ToSchemify {
DynamicData,
AccountIdMapping,
DirectDebit,
StandingOrder,
)++ APIBuilder_Connector.allAPIBuilderModels
}

View File

@ -15,7 +15,7 @@ import code.api.v3_0_0.JSONFactory300.createBranchJsonV300
import code.api.v3_0_0.custom.JSONFactoryCustom300
import code.api.v3_0_0.{LobbyJsonV330, _}
import code.api.v3_1_0.{AccountBalanceV310, AccountsBalancesV310Json, BadLoginStatusJson, ContactDetailsJson, InviteeJson, ObpApiLoopbackJson, PhysicalCardWithAttributesJsonV310, PutUpdateCustomerEmailJsonV310, _}
import code.api.v4_0_0.{APIInfoJson400, AccountTagJSON, AccountTagsJSON, DirectDebitJsonV400, EnergySource400, HostedAt400, HostedBy400, ModeratedAccountJSON400, ModeratedCoreAccountJsonV400, PostAccountTagJSON, PostCustomerPhoneNumberJsonV400, PostDirectDebitJsonV400}
import code.api.v4_0_0.{APIInfoJson400, AccountTagJSON, AccountTagsJSON, DirectDebitJsonV400, EnergySource400, HostedAt400, HostedBy400, ModeratedAccountJSON400, ModeratedCoreAccountJsonV400, PostAccountTagJSON, PostCustomerPhoneNumberJsonV400, PostDirectDebitJsonV400, PostStandingOrderJsonV400, StandingOrderJsonV400}
import code.branches.Branches.{Branch, DriveUpString, LobbyString}
import code.consent.ConsentStatus
import code.sandbox.SandboxData
@ -2619,7 +2619,8 @@ object SwaggerDefinitionsJSON {
can_add_transaction_request_to_own_account = true, //added following two for payments
can_add_transaction_request_to_any_account = true,
can_see_bank_account_credit_limit = true,
can_create_direct_debit = true
can_create_direct_debit = true,
can_create_standing_order = true
)
val viewsJsonV300 = ViewsJsonV300(
@ -3523,6 +3524,25 @@ object SwaggerDefinitionsJSON {
date_expires = new Date(),
date_cancelled = new Date(),
active = true
)
val postStandingOrderJsonV400 = PostStandingOrderJsonV400(
customer_id = customerIdExample.value,
user_id = userIdExample.value,
date_signed = Some(DateWithDayExampleObject),
date_starts = DateWithDayExampleObject,
date_expires = Some(DateWithDayExampleObject)
)
val standingrderJsonV400 = StandingOrderJsonV400(
direct_debit_id = "aa0533bd-eb22-4bff-af75-d45240361b05",
bank_id = bankIdExample.value,
account_id = accountIdExample.value,
customer_id = customerIdExample.value,
user_id = userIdExample.value,
date_signed = new Date(),
date_starts = new Date(),
date_expires = new Date(),
date_cancelled = new Date(),
active = true
)
//The common error or success format.

View File

@ -401,6 +401,9 @@ object ApiRole {
case class CanCreateDirectDebitAtOneBank(requiresBankId: Boolean = true) extends ApiRole
lazy val canCreateDirectDebitAtOneBank = CanCreateDirectDebitAtOneBank()
case class CanCreateStandingOrderAtOneBank(requiresBankId: Boolean = true) extends ApiRole
lazy val canCreateStandingOrderAtOneBank = CanCreateStandingOrderAtOneBank()
private val roles = ReflectUtils.getFieldsNameToValue[ApiRole](this).values.toList
lazy val rolesMappedToClasses = roles.map(_.getClass)

View File

@ -13,6 +13,7 @@ object ApiTag {
val apiTagBank = ResourceDocTag("Bank")
val apiTagAccount = ResourceDocTag("Account")
val apiTagDirectDebit = ResourceDocTag("Direct-Debit")
val apiTagStandingOrder = ResourceDocTag("Standing-Order")
val apiTagAccountMetadata = ResourceDocTag("Account-Metadata")
val apiTagAccountApplication = ResourceDocTag("Account-Application")
val apiTagAccountPublic = ResourceDocTag("Account-Public")

View File

@ -23,6 +23,7 @@ import code.fx.{FXRate, MappedFXRate, fx}
import code.metadata.counterparties.Counterparties
import code.methodrouting.{MethodRoutingProvider, MethodRoutingT}
import code.model._
import code.standingorders.StandingOrderTrait
import code.transactionChallenge.ExpectedChallengeAnswer
import code.usercustomerlinks.UserCustomerLink
import code.util.Helper
@ -1561,6 +1562,27 @@ object NewStyle {
}
}
def createStandingOrder(bankId: String,
accountId: String,
customerId: String,
userId: String,
dateSigned: Date,
dateStarts: Date,
dateExpires: Option[Date],
callContext: Option[CallContext]): OBPReturnType[StandingOrderTrait] = {
Connector.connector.vend.createStandingOrder(
bankId,
accountId,
customerId,
userId,
dateSigned,
dateStarts,
dateExpires,
callContext) map {
i => (connectorEmptyResponse(i._1, callContext), i._2)
}
}
}
}

View File

@ -153,7 +153,8 @@ case class ViewJsonV300(
val can_add_transaction_request_to_own_account: Boolean, //added following two for payments
val can_add_transaction_request_to_any_account: Boolean,
val can_see_bank_account_credit_limit: Boolean,
val can_create_direct_debit: Boolean
val can_create_direct_debit: Boolean,
val can_create_standing_order: Boolean
)
case class BasicViewJson(
@ -721,7 +722,8 @@ object JSONFactory300{
can_add_transaction_request_to_own_account = view.canAddTransactionRequestToOwnAccount, //added following two for payments
can_add_transaction_request_to_any_account = view.canAddTransactionRequestToAnyAccount,
can_see_bank_account_credit_limit = view.canSeeBankAccountCreditLimit,
can_create_direct_debit = view.canCreateDirectDebit
can_create_direct_debit = view.canCreateDirectDebit,
can_create_standing_order = view.canCreateStandingOrder
)
}
def createBasicViewJSON(view : View) : BasicViewJson = {

View File

@ -7,7 +7,7 @@ import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON._
import code.api.util.APIUtil.{fullBoxOrException, _}
import code.api.util.ApiRole._
import code.api.util.ApiTag._
import code.api.util.ErrorMessages.{AccountNotFound, AllowedAttemptsUsedUp, BankAccountNotFound, BankNotFound, CounterpartyBeneficiaryPermit, DynamicEntityOperationNotAllowed, InsufficientAuthorisationToCreateBank, InsufficientAuthorisationToCreateTransactionRequest, InvalidAccountIdFormat, InvalidBankIdFormat, InvalidChallengeAnswer, InvalidChallengeType, InvalidChargePolicy, InvalidISOCurrencyCode, InvalidJsonFormat, InvalidNumber, InvalidTransactionRequesChallengeId, InvalidTransactionRequestCurrency, InvalidTransactionRequestType, NoViewPermission, NotPositiveAmount, TransactionDisabled, TransactionRequestStatusNotInitiated, TransactionRequestTypeHasChanged, UnknownError, UserCustomerLinksNotFoundForUser, UserHasMissingRoles, UserNoPermissionAccessView, UserNotFoundByUserId, UserNotLoggedIn, ViewNotFound}
import code.api.util.ErrorMessages.{AccountNotFound, AllowedAttemptsUsedUp, BankAccountNotFound, BankNotFound, CounterpartyBeneficiaryPermit, CounterpartyNotFoundByCounterpartyId, CustomerNotFoundByCustomerId, DynamicEntityOperationNotAllowed, InsufficientAuthorisationToCreateBank, InsufficientAuthorisationToCreateTransactionRequest, InvalidAccountIdFormat, InvalidBankIdFormat, InvalidChallengeAnswer, InvalidChallengeType, InvalidChargePolicy, InvalidISOCurrencyCode, InvalidJsonFormat, InvalidNumber, InvalidTransactionRequesChallengeId, InvalidTransactionRequestCurrency, InvalidTransactionRequestType, NoViewPermission, NotPositiveAmount, TransactionDisabled, TransactionRequestStatusNotInitiated, TransactionRequestTypeHasChanged, UnknownError, UserCustomerLinksNotFoundForUser, UserHasMissingRoles, UserNoPermissionAccessView, UserNotFoundByUserId, UserNotLoggedIn, ViewNotFound}
import code.api.util.ExampleValue.{dynamicEntityRequestBodyExample, dynamicEntityResponseBodyExample}
import code.api.util.NewStyle.HttpCode
import code.api.util._
@ -1482,6 +1482,13 @@ trait APIMethods400 {
directDebitJsonV400,
List(
UserNotLoggedIn,
BankNotFound,
BankAccountNotFound,
NoViewPermission,
InvalidJsonFormat,
CustomerNotFoundByCustomerId,
UserNotFoundByUserId,
CounterpartyNotFoundByCounterpartyId,
UnknownError
),
Catalogs(notCore, notPSD2, notOBWG),
@ -1539,6 +1546,13 @@ trait APIMethods400 {
directDebitJsonV400,
List(
UserNotLoggedIn,
BankNotFound,
BankAccountNotFound,
NoViewPermission,
InvalidJsonFormat,
CustomerNotFoundByCustomerId,
UserNotFoundByUserId,
CounterpartyNotFoundByCounterpartyId,
UnknownError
),
Catalogs(notCore, notPSD2, notOBWG),
@ -1577,6 +1591,125 @@ trait APIMethods400 {
}
}
resourceDocs += ResourceDoc(
createStandingOrder,
implementedInApiVersion,
nameOf(createStandingOrder),
"POST",
"/banks/BANK_ID/accounts/ACCOUNT_ID/VIEW_ID/standing-order",
"Create Standing Order",
s"""Create standing order for an account.
|
|${authenticationRequiredMessage(true)}
|
|""",
postStandingOrderJsonV400,
standingrderJsonV400,
List(
UserNotLoggedIn,
BankNotFound,
BankAccountNotFound,
NoViewPermission,
InvalidJsonFormat,
CustomerNotFoundByCustomerId,
UserNotFoundByUserId,
UnknownError
),
Catalogs(notCore, notPSD2, notOBWG),
List(apiTagStandingOrder, apiTagAccount, apiTagNewStyle))
lazy val createStandingOrder : OBPEndpoint = {
case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: ViewId(viewId) :: "standing-order" :: Nil JsonPost json -> _ => {
cc =>
for {
(Full(u), callContext) <- authorizedAccess(cc)
(_, callContext) <- NewStyle.function.getBank(bankId, callContext)
(_, callContext) <- NewStyle.function.getBankAccount(bankId, accountId, callContext)
view <- NewStyle.function.view(viewId, BankIdAccountId(bankId, accountId), callContext)
_ <- Helper.booleanToFuture(failMsg = s"$NoViewPermission can_create_standing_order. Current ViewId($viewId)") {
view.canCreateStandingOrder == true
}
failMsg = s"$InvalidJsonFormat The Json body should be the $PostStandingOrderJsonV400 "
postJson <- NewStyle.function.tryons(failMsg, 400, callContext) {
json.extract[PostStandingOrderJsonV400]
}
(_, callContext) <- NewStyle.function.getCustomerByCustomerId(postJson.customer_id, callContext)
_ <- Users.users.vend.getUserByUserIdFuture(postJson.user_id) map {
x => unboxFullOrFail(x, callContext, s"$UserNotFoundByUserId Current UserId(${postJson.user_id})")
}
(directDebit, callContext) <- NewStyle.function.createStandingOrder(
bankId.value,
accountId.value,
postJson.customer_id,
postJson.user_id,
if (postJson.date_signed.isDefined) postJson.date_signed.get else new Date(),
postJson.date_starts,
postJson.date_expires,
callContext)
} yield {
(JSONFactory400.createStandingOrderJSON(directDebit), HttpCode.`201`(callContext))
}
}
}
resourceDocs += ResourceDoc(
createStandingOrderManagement,
implementedInApiVersion,
nameOf(createStandingOrderManagement),
"POST",
"/management/banks/BANK_ID/accounts/ACCOUNT_ID/standing-order",
"Create Standing Order(management)",
s"""Create standing order for an account.
|
|${authenticationRequiredMessage(true)}
|
|""",
postStandingOrderJsonV400,
standingrderJsonV400,
List(
UserNotLoggedIn,
BankNotFound,
BankAccountNotFound,
NoViewPermission,
InvalidJsonFormat,
CustomerNotFoundByCustomerId,
UserNotFoundByUserId,
UnknownError
),
Catalogs(notCore, notPSD2, notOBWG),
List(apiTagStandingOrder, apiTagAccount, apiTagNewStyle))
lazy val createStandingOrderManagement : OBPEndpoint = {
case "management" :: "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: "standing-order" :: Nil JsonPost json -> _ => {
cc =>
for {
(Full(u), callContext) <- authorizedAccess(cc)
(_, callContext) <- NewStyle.function.getBank(bankId, callContext)
(_, callContext) <- NewStyle.function.getBankAccount(bankId, accountId, callContext)
_ <- NewStyle.function.hasEntitlement("", u.userId, ApiRole.canCreateStandingOrderAtOneBank, callContext)
failMsg = s"$InvalidJsonFormat The Json body should be the $PostStandingOrderJsonV400 "
postJson <- NewStyle.function.tryons(failMsg, 400, callContext) {
json.extract[PostStandingOrderJsonV400]
}
(_, callContext) <- NewStyle.function.getCustomerByCustomerId(postJson.customer_id, callContext)
_ <- Users.users.vend.getUserByUserIdFuture(postJson.user_id) map {
x => unboxFullOrFail(x, callContext, s"$UserNotFoundByUserId Current UserId(${postJson.user_id})")
}
(directDebit, callContext) <- NewStyle.function.createStandingOrder(
bankId.value,
accountId.value,
postJson.customer_id,
postJson.user_id,
if (postJson.date_signed.isDefined) postJson.date_signed.get else new Date(),
postJson.date_starts,
postJson.date_expires,
callContext)
} yield {
(JSONFactory400.createStandingOrderJSON(directDebit), HttpCode.`201`(callContext))
}
}
}
}

View File

@ -40,6 +40,7 @@ import code.api.v3_1_0.AccountAttributeResponseJson
import code.api.v3_1_0.JSONFactory310.createAccountAttributeJson
import code.directdebit.DirectDebitTrait
import code.model.ModeratedBankAccount
import code.standingorders.StandingOrderTrait
import code.transactionrequests.TransactionRequests.TransactionChallengeTypes
import com.openbankproject.commons.model._
@ -165,6 +166,24 @@ case class DirectDebitJsonV400(direct_debit_id: String,
date_cancelled: Date,
active: Boolean)
case class PostStandingOrderJsonV400(customer_id: String,
user_id: String,
date_signed: Option[Date],
date_starts: Date,
date_expires: Option[Date]
)
case class StandingOrderJsonV400(direct_debit_id: String,
bank_id: String,
account_id: String,
customer_id: String,
user_id: String,
date_signed: Date,
date_starts: Date,
date_expires: Date,
date_cancelled: Date,
active: Boolean)
object JSONFactory400 {
def createBankJSON400(bank: Bank): BankJson400 = {
val obp = BankRoutingJsonV121("OBP", bank.bankId.value)
@ -312,6 +331,18 @@ object JSONFactory400 {
date_expires = directDebit.dateExpires,
active = directDebit.active)
}
def createStandingOrderJSON(standingOrder: StandingOrderTrait): StandingOrderJsonV400 = {
StandingOrderJsonV400(direct_debit_id = standingOrder.standingOrderId,
bank_id = standingOrder.bankId,
account_id = standingOrder.accountId,
customer_id = standingOrder.customerId,
user_id = standingOrder.userId,
date_signed = standingOrder.dateSigned,
date_cancelled = standingOrder.dateCancelled,
date_starts = standingOrder.dateStarts,
date_expires = standingOrder.dateExpires,
active = standingOrder.active)
}
}

View File

@ -26,6 +26,7 @@ import code.fx.fx.TTL
import code.management.ImporterAPI.ImporterTransaction
import code.model.dataAccess.ResourceUser
import code.model.toUserExtended
import code.standingorders.StandingOrderTrait
import code.transactionrequests.TransactionRequests.TransactionRequestTypes._
import code.transactionrequests.TransactionRequests._
import code.transactionrequests.{TransactionRequestTypeCharge, TransactionRequests}
@ -1890,4 +1891,15 @@ trait Connector extends MdcLoggable with CustomJsonFormats{
dateStarts: Date,
dateExpires: Option[Date],
callContext: Option[CallContext]): OBPReturnType[Box[DirectDebitTrait]] = Future{(Failure(setUnimplementedError), callContext)}
def createStandingOrder(bankId: String,
accountId: String,
customerId: String,
userId: String,
dateSigned: Date,
dateStarts: Date,
dateExpires: Option[Date],
callContext: Option[CallContext]): OBPReturnType[Box[StandingOrderTrait]] = Future {
(Failure(setUnimplementedError), callContext)
}
}

View File

@ -42,6 +42,7 @@ import code.productattribute.ProductAttributeX
import code.productcollection.ProductCollectionX
import code.productcollectionitem.ProductCollectionItems
import code.products.MappedProduct
import code.standingorders.{StandingOrderTrait, StandingOrders}
import code.taxresidence.TaxResidenceX
import code.transaction.MappedTransaction
import code.transactionChallenge.ExpectedChallengeAnswer
@ -2878,13 +2879,32 @@ object LocalMappedConnector extends Connector with MdcLoggable {
val result = DirectDebits.directDebitProvider.vend.createDirectDebit(
bankId,
accountId,
customerId,
userId,
counterpartyId,
customerId,
counterpartyId,
userId,
dateSigned,
dateStarts,
dateExpires)
(result, callContext)
}
override def createStandingOrder(bankId: String,
accountId: String,
customerId: String,
userId: String,
dateSigned: Date,
dateStarts: Date,
dateExpires: Option[Date],
callContext: Option[CallContext]): OBPReturnType[Box[StandingOrderTrait]] = Future {
val result = StandingOrders.provider.vend.createStandingOrder(
bankId,
accountId,
customerId,
userId,
dateSigned,
dateStarts,
dateExpires)
(result, callContext)
}
}

View File

@ -0,0 +1,79 @@
package code.standingorders
import java.util.Date
import code.api.util.APIUtil
import code.util.UUIDString
import net.liftweb.common.Box
import net.liftweb.mapper._
object MappedStandingOrderProvider extends StandingOrderProvider {
def createStandingOrder(bankId: String,
accountId: String,
customerId: String,
userId: String,
dateSigned: Date,
dateStarts: Date,
dateExpires: Option[Date]
): Box[StandingOrder] = Box.tryo {
StandingOrder.create
.BankId(bankId)
.AccountId(accountId)
.CustomerId(customerId)
.UserId(userId)
.DateSigned(dateSigned)
.DateStarts(dateStarts)
.DateExpires(if (dateExpires.isDefined) dateExpires.get else null)
.Active(true)
.saveMe()
}
def getStandingOrdersByBankAccount(bankId: String, accountId: String): List[StandingOrder] = {
StandingOrder.findAll(
By(StandingOrder.BankId, bankId),
By(StandingOrder.AccountId, accountId),
OrderBy(StandingOrder.updatedAt, Descending))
}
def getStandingOrdersByCustomer(customerId: String): List[StandingOrder] = {
StandingOrder.findAll(
By(StandingOrder.CustomerId, customerId),
OrderBy(StandingOrder.updatedAt, Descending))
}
def getStandingOrdersByUser(userId: String): List[StandingOrder] = {
StandingOrder.findAll(
By(StandingOrder.UserId, userId),
OrderBy(StandingOrder.updatedAt, Descending))
}
}
class StandingOrder extends StandingOrderTrait with LongKeyedMapper[StandingOrder] with IdPK with CreatedUpdated {
def getSingleton: StandingOrder.type = StandingOrder
object StandingOrderId extends UUIDString(this) {
override def defaultValue = APIUtil.generateUUID()
}
object BankId extends UUIDString(this)
object AccountId extends UUIDString(this)
object CustomerId extends UUIDString(this)
object UserId extends UUIDString(this)
object DateSigned extends MappedDateTime(this)
object DateCancelled extends MappedDateTime(this)
object DateStarts extends MappedDateTime(this)
object DateExpires extends MappedDateTime(this)
object Active extends MappedBoolean(this)
override def standingOrderId: String = StandingOrderId.get
override def bankId: String = BankId.get
override def accountId: String = AccountId.get
override def customerId: String = CustomerId.get
override def userId: String = UserId.get
override def dateSigned: Date = DateSigned.get
override def dateCancelled: Date = DateCancelled.get
override def dateExpires: Date = DateExpires.get
override def dateStarts: Date = DateStarts.get
override def active: Boolean = Active.get
}
object StandingOrder extends StandingOrder with LongKeyedMetaMapper[StandingOrder] {
override def dbIndexes: List[BaseIndex[StandingOrder]] = UniqueIndex(BankId, AccountId, CustomerId) :: super.dbIndexes
}

View File

@ -0,0 +1,38 @@
package code.standingorders
import java.util.Date
import net.liftweb.common.Box
import net.liftweb.util.SimpleInjector
object StandingOrders extends SimpleInjector {
val provider = new Inject(buildOne _) {}
def buildOne: StandingOrderProvider = MappedStandingOrderProvider
}
trait StandingOrderProvider {
def createStandingOrder(bankId: String,
accountId: String,
customerId: String,
userId: String,
dateSigned: Date,
dateStarts: Date,
dateExpires: Option[Date]
): Box[StandingOrderTrait]
def getStandingOrdersByCustomer(customerId: String) : List[StandingOrderTrait]
def getStandingOrdersByUser(userId: String) : List[StandingOrderTrait]
}
trait StandingOrderTrait {
def standingOrderId: String
def bankId: String
def accountId: String
def customerId: String
def userId: String
def dateSigned: Date
def dateCancelled: Date
def dateStarts: Date
def dateExpires: Date
def active: Boolean
}

View File

@ -269,6 +269,9 @@ class ViewDefinition extends View with LongKeyedMapper[ViewDefinition] with Many
object canCreateDirectDebit_ extends MappedBoolean(this){
override def defaultValue = false
}
object canCreateStandingOrder_ extends MappedBoolean(this){
override def defaultValue = false
}
//Important! If you add a field, be sure to handle it here in this function
def setFromViewData(viewData : ViewSpecification) = {
@ -364,6 +367,7 @@ class ViewDefinition extends View with LongKeyedMapper[ViewDefinition] with Many
canAddTransactionRequestToAnyAccount_(actions.exists(_ == "can_add_transaction_request_to_any_account"))
canSeeBankAccountCreditLimit_(actions.exists(_ == "can_see_bank_account_credit_limit"))
canCreateDirectDebit_(actions.exists(_ == "can_create_direct_debit"))
canCreateStandingOrder_(actions.exists(_ == "can_create_standing_order"))
}
@ -476,6 +480,7 @@ class ViewDefinition extends View with LongKeyedMapper[ViewDefinition] with Many
def canSeeBankAccountCreditLimit: Boolean = canSeeBankAccountCreditLimit_.get
def canCreateDirectDebit: Boolean = canCreateDirectDebit_.get
def canCreateStandingOrder: Boolean = canCreateStandingOrder_.get
//TODO: if you add new permissions here, remember to set them wherever views are created
// (e.g. BankAccountCreationDispatcher)
}

View File

@ -396,4 +396,6 @@ trait View {
def canSeeBankAccountCreditLimit: Boolean
def canCreateDirectDebit: Boolean
def canCreateStandingOrder: Boolean
}