Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package co.nilin.opex.api.core.inout

import java.time.LocalDateTime

enum class BankAccountApprovalRequestStatus {
PENDING, APPROVED, REJECTED
}

data class BankAccountApprovalRequestFilter(
val userId: String? = null,
val status: BankAccountApprovalRequestStatus? = null,
val createDateFrom: Long? = null,
val createDateTo: Long? = null,
val limit: Int = 10,
val offset: Int = 0,
val ascendingByTime: Boolean = false,
)

data class BankAccountApprovalAdminResponse(
var id: Long,
var bankAccountId: Long,
var userId: String,
var status: BankAccountApprovalRequestStatus,
var createDate: LocalDateTime,
var updateDate: LocalDateTime? = null,
var updater: String? = null,
var description: String? = null,
var name: String? = null,
var cardNumber: String? = null,
var iban: String? = null,
var accountNumber: String? = null,
var bank: String? = null,
var bankAccountStatus: BankAccountStatus? = null,
var firstName: String? = null,
var lastName: String? = null
)

data class BankAccountApprovalUserResponse(
var bankAccountId: Long,
var status: BankAccountApprovalRequestStatus,
var createDate: LocalDateTime,
var description: String? = null
)

data class UpdateBankAccountApprovalBody(
val id: Long,
val description: String? = null,
val status: BankAccountApprovalRequestStatus
)
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ data class OffChainGatewayUpdateCommand(
override var withdrawMax: BigDecimal? = BigDecimal.ZERO,
override var displayOrder: Int? = null,

) : CurrencyGatewayCommand(
) : CurrencyGatewayUpdateCommand(
currencySymbol,
gatewayUuid,
isDepositActive,
Expand All @@ -60,8 +60,6 @@ data class OffChainGatewayUpdateCommand(
depositMax,
withdrawMin,
withdrawMax,
null,
null,
displayOrder
)

Expand All @@ -85,7 +83,7 @@ data class OnChainGatewayUpdateCommand(
override var withdrawMin: BigDecimal? = BigDecimal.ZERO,
override var withdrawMax: BigDecimal? = BigDecimal.ZERO,
override var displayOrder: Int? = null,
) : CurrencyGatewayCommand(
) : CurrencyGatewayUpdateCommand(
currencySymbol,
gatewayUuid,
isDepositActive,
Expand All @@ -97,8 +95,6 @@ data class OnChainGatewayUpdateCommand(
depositMax,
withdrawMin,
withdrawMax,
null,
null,
displayOrder
)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package co.nilin.opex.api.core.inout

data class ShahkarInquiryRequest(
val nationalCode: String,
val mobile: String
)

data class IdentitySimilarityInquiryRequest(
val nationalCode: String,
val birthDate: Long,
val firstName: String,
val lastName: String
)

data class CardOwnershipInquiryRequest(
val cardNumber: String,
val nationalCode: String,
val birthDate: Long
)

data class IbanOwnershipInquiryRequest(
val iban: String,
val nationalCode: String,
val birthDate: Long
)

data class CardInfoInquiryRequest(
val cardNumber: String
)

data class IbanInfoInquiryRequest(
val iban: String
)

data class ShahkarInquiryResponse(
val matched: Boolean? = null,
val code: String? = null,
val message: String? = null
)

data class IdentitySimilarityInquiryResponse(
val firstNameSimilarityPercentage: Int? = null,
val lastNameSimilarityPercentage: Int? = null,
val code: String? = null,
val message: String? = null
)

data class VerifyOwnershipInquiryResponse(
val matched: Boolean? = null,
val code: String? = null,
val message: String? = null
)

data class IbanInfoInquiryResponse(
val ibanInfo: IbanInfoDetails? = null,
val code: String? = null,
val message: String? = null
)

data class IbanInfoDetails(
val bank: String? = null,
val depositNumber: String? = null,
val iban: String? = null
)
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,41 @@ interface ProfileProxy {
suspend fun addBankAccount(token: String, request: AddBankAccountRequest): BankAccountResponse
suspend fun getBankAccounts(token: String): List<BankAccountResponse>
suspend fun deleteBankAccount(token: String, id: Long)

suspend fun getUserBankAccountApprovalRequest(token: String, bankAccountId: Long): BankAccountApprovalUserResponse

//Bank Account approval requests
suspend fun getBankAccountApprovalRequests(
token: String,
request: BankAccountApprovalRequestFilter
): List<BankAccountApprovalAdminResponse>

suspend fun getBankAccountApprovalRequest(token: String, requestId: Long): BankAccountApprovalAdminResponse

suspend fun updateBankAccountApprovalRequest(
token: String,
request: UpdateBankAccountApprovalBody
): BankAccountApprovalAdminResponse

//Manual identity inquiries
suspend fun shahkarInquiry(token: String, request: ShahkarInquiryRequest): ShahkarInquiryResponse

suspend fun identitySimilarityInquiry(
token: String,
request: IdentitySimilarityInquiryRequest
): IdentitySimilarityInquiryResponse

suspend fun cardOwnershipInquiry(
token: String,
request: CardOwnershipInquiryRequest
): VerifyOwnershipInquiryResponse

suspend fun ibanOwnershipInquiry(
token: String,
request: IbanOwnershipInquiryRequest
): VerifyOwnershipInquiryResponse

suspend fun cardInfoInquiry(token: String, request: CardInfoInquiryRequest): IbanInfoInquiryResponse

suspend fun ibanInfoInquiry(token: String, request: IbanInfoInquiryRequest): IbanInfoInquiryResponse
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package co.nilin.opex.api.ports.opex.controller

import co.nilin.opex.api.core.inout.BankAccountApprovalAdminResponse
import co.nilin.opex.api.core.inout.BankAccountApprovalRequestFilter
import co.nilin.opex.api.core.inout.UpdateBankAccountApprovalBody
import co.nilin.opex.api.core.spi.ProfileProxy
import co.nilin.opex.api.ports.opex.util.jwtAuthentication
import co.nilin.opex.api.ports.opex.util.tokenValue
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.Parameter
import io.swagger.v3.oas.annotations.media.ArraySchema
import io.swagger.v3.oas.annotations.media.Content
import io.swagger.v3.oas.annotations.media.Schema
import io.swagger.v3.oas.annotations.responses.ApiResponse
import io.swagger.v3.oas.annotations.security.SecurityRequirement
import io.swagger.v3.oas.annotations.tags.Tag
import org.springframework.http.MediaType
import org.springframework.security.core.annotation.CurrentSecurityContext
import org.springframework.security.core.context.SecurityContext
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.PutMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController

@RestController
@RequestMapping("/opex/v1/admin/bank-account")
@Tag(
name = "Bank Account Admin",
description = "Admin review of bank accounts whose identity inquiry could not be completed."
)
class BankAccountAdminController(
private val profileProxy: ProfileProxy
) {

@PostMapping("/approval-requests", produces = [MediaType.APPLICATION_JSON_VALUE])
@Operation(
summary = "Get bank account approval requests",
description = """POST /opex/v1/admin/bank-account/approval-requests.
Security: Bearer admin-token required. Required authority: ROLE_admin.
Behavior: Lists bank account review requests, newest first unless `ascendingByTime` is true. Requests exist only while admin approval is enabled: with automatic verification off every account creates one, and with it on only an inquiry the provider could not answer (timeout, provider down) does. A provider answer that rejects the user's data rejects the account instead of creating a request. Each row carries the account under review and the owner's name, so no second call is needed.
Filters: `userId`, `status`, `createDateFrom` and `createDateTo` (epoch millis), `limit`, `offset`, `ascendingByTime`. Every filter is optional.
Allowed values:
- status: PENDING, APPROVED, REJECTED.
- bankAccountStatus: WAITING, VERIFIED, REJECTED.""",
security = [SecurityRequirement(name = "bearerAuth")],
responses = [
ApiResponse(
responseCode = "200",
description = "Successful response.",
content = [Content(
mediaType = "application/json",
array = ArraySchema(schema = Schema(implementation = BankAccountApprovalAdminResponse::class))
)]
),
ApiResponse(
responseCode = "401",
description = "Unauthorized. Bearer token is missing, invalid, or expired. No response body.",
content = [Content()]
),
ApiResponse(
responseCode = "403",
description = "Forbidden. Required authority is missing: ROLE_admin. No response body.",
content = [Content()]
)
]
)
suspend fun getApprovalRequests(
@RequestBody request: BankAccountApprovalRequestFilter,
@Parameter(hidden = true)
@CurrentSecurityContext securityContext: SecurityContext
): List<BankAccountApprovalAdminResponse> {
return profileProxy.getBankAccountApprovalRequests(
securityContext.jwtAuthentication().tokenValue(),
request
)
}

@GetMapping("/approval-request/{id}", produces = [MediaType.APPLICATION_JSON_VALUE])
@Operation(
summary = "Get bank account approval request",
description = """GET /opex/v1/admin/bank-account/approval-request/{id}.
Security: Bearer admin-token required. Required authority: ROLE_admin.
Behavior: Returns one review request with the account under review and the owner's first and last name.
Allowed values:
- status: PENDING, APPROVED, REJECTED.""",
security = [SecurityRequirement(name = "bearerAuth")],
responses = [
ApiResponse(
responseCode = "200",
description = "Successful response.",
content = [Content(
mediaType = "application/json",
schema = Schema(implementation = BankAccountApprovalAdminResponse::class)
)]
),
ApiResponse(
responseCode = "401",
description = "Unauthorized. Bearer token is missing, invalid, or expired. No response body.",
content = [Content()]
),
ApiResponse(
responseCode = "403",
description = "Forbidden. Required authority is missing: ROLE_admin. No response body.",
content = [Content()]
),
ApiResponse(responseCode = "404", description = "No request with this id.", content = [Content()])
]
)
suspend fun getApprovalRequest(
@Parameter(name = "id", description = "Approval request id.", required = true)
@PathVariable("id") id: Long,
@Parameter(hidden = true)
@CurrentSecurityContext securityContext: SecurityContext
): BankAccountApprovalAdminResponse {
return profileProxy.getBankAccountApprovalRequest(securityContext.jwtAuthentication().tokenValue(), id)
}

@PutMapping("/approval-request", produces = [MediaType.APPLICATION_JSON_VALUE])
@Operation(
summary = "Update bank account approval request status",
description = """PUT /opex/v1/admin/bank-account/approval-request.
Security: Bearer admin-token required. Required authority: ROLE_admin.
Validation: `id` and `status` are required. `status` must be APPROVED or REJECTED, and only a PENDING request can be decided on.
Behavior: Closes the review and moves the bank account with it: APPROVED verifies the account, REJECTED rejects it. The acting admin is recorded. Only the status changes; the account data itself is never edited here.
Allowed values:
- status: APPROVED, REJECTED.""",
security = [SecurityRequirement(name = "bearerAuth")],
responses = [
ApiResponse(
responseCode = "200",
description = "Successful response.",
content = [Content(
mediaType = "application/json",
schema = Schema(implementation = BankAccountApprovalAdminResponse::class)
)]
),
ApiResponse(
responseCode = "400",
description = "The request is not pending, or the status is not APPROVED or REJECTED, or the same card or iban was verified for another user in the meantime.",
content = [Content()]
),
ApiResponse(
responseCode = "401",
description = "Unauthorized. Bearer token is missing, invalid, or expired. No response body.",
content = [Content()]
),
ApiResponse(
responseCode = "403",
description = "Forbidden. Required authority is missing: ROLE_admin. No response body.",
content = [Content()]
),
ApiResponse(responseCode = "404", description = "No request with this id.", content = [Content()])
]
)
suspend fun updateRequestStatus(
@RequestBody request: UpdateBankAccountApprovalBody,
@Parameter(hidden = true)
@CurrentSecurityContext securityContext: SecurityContext
): BankAccountApprovalAdminResponse {
return profileProxy.updateBankAccountApprovalRequest(
securityContext.jwtAuthentication().tokenValue(),
request
)
}
}
Loading
Loading