Skip to content

Commit b078f7c

Browse files
authored
[FEATURE/#42] 금융사 연동 삭제 API 구현 (#43)
## 🔗 Related Issue <!-- 이슈 번호를 작성하여 종료시켜주세요 --> - Closes #42 ## 📝 Summary <!-- 작업한 기능을 설명해주세요 --> ### 1. 금융사 연동 삭제 API 구현 - Codef API(v1/account/delete)를 호출하여 실제 금융사 연동 해제 처리 - CodefConnection 삭제 시 Cascade 설정 및 @SQLDelete 어노테이션을 통해 하위 엔티티(BankAccount, Card)도 자동으로 isActive = false 처리되도록 구현 - Goal(목표) 연동 해제 로직은 추후 구현을 위해 TODO 주석 처리 ### 2. Swagger / API 명세 정합성 개선 - 스웨거 예시 응답값과 실제 응답값 불일치 수정 - SpringDoc 3.x 표준에 맞게 DTO 필드의 `@Schema(required = true)` → `requiredMode = Schema.RequiredMode.REQUIRED`로 변경 - 요청/응답 DTO의 필드 의미가 명확히 드러나도록 응답 필드 구조 정리 ## 🔄 Changes <!-- 구체적으로 어떤 파일/로직이 변경되었는지 체크해주세요 --> - [x] API 변경 (추가/수정) - [ ] 데이터 및 도메인 변경 (DB, 비즈니스 로직) - [ ] 설정 또는 인프라 관련 변경 - [x] 리팩토링 ## 💬 Questions & Review Points <!-- 리뷰어가 특별히 봐주었으면 하는 부분이 있다면 작성해주세요 --> 연동 삭제 시 금융사 계좌가 함께 삭제되기 때문에, 계좌와 연결된 목표(Goal) 연동 해제 로직이 필요합니다. 해당 부분은 목표 도메인 PR(#37) merge 이후 추가 구현하여 반영할 예정입니다 ## 📸 API Test Results (Swagger) <!-- API 테스트 스크린샷 첨부 --> <img width="1614" height="1273" alt="image" src="https://github.com/user-attachments/assets/7547715c-9cbb-4d14-b7eb-558ecd5c6ee1" /> ## ✅ Checklist - [x] API 테스트 완료 - [x] 테스트 결과 사진 첨부 - [x] 빌드 성공 확인 (./gradlew build)
1 parent 38595ed commit b078f7c

13 files changed

Lines changed: 184 additions & 152 deletions

File tree

src/main/java/org/umc/valuedi/domain/asset/controller/AssetControllerDocs.java

Lines changed: 35 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@
1414

1515
import java.util.List;
1616

17-
@Tag(name = "Asset", description = "자산(계좌/은행) 관련 API")
17+
@Tag(name = "Asset", description = "자산(계좌/카드) 관련 API")
1818
public interface AssetControllerDocs {
1919

20-
@Operation(summary = "연동된 카드 목록 조회 API", description = "현재 사용자가 연동한 카드 리스트를 조회합니다.")
20+
@Operation(summary = "연동된 전체 카드 목록 조회 API", description = "현재 사용자가 연동한 모든 카드를 조회합니다.")
2121
@ApiResponses({
2222
@io.swagger.v3.oas.annotations.responses.ApiResponse(
2323
responseCode = "200",
@@ -31,56 +31,25 @@ public interface AssetControllerDocs {
3131
"isSuccess": true,
3232
"code": "COMMON200",
3333
"message": "성공입니다.",
34-
"result": [
35-
{
36-
"cardName": "현대카드 M Edition3",
37-
"cardNumber": "4321-****-****-1234",
38-
"cardType": "CREDIT",
39-
"connectedAt": "2024-05-22T09:00:00"
40-
},
41-
{
42-
"cardName": "신한 Deep Dream",
43-
"cardNumber": "5336-****-****-5678",
44-
"cardType": "CHECK",
45-
"connectedAt": "2024-05-23T11:20:00"
46-
}
47-
]
48-
}
49-
"""
50-
)
51-
)
52-
),
53-
@io.swagger.v3.oas.annotations.responses.ApiResponse(
54-
responseCode = "401",
55-
description = "에러 - 인증되지 않은 사용자",
56-
content = @Content(
57-
schema = @Schema(implementation = ApiResponse.class),
58-
examples = @ExampleObject(
59-
name = "인증 에러 예시",
60-
value = """
61-
{
62-
"isSuccess": false,
63-
"code": "AUTH401_1",
64-
"message": "로그인이 필요합니다.",
65-
"result": null
66-
}
67-
"""
68-
)
69-
)
70-
),
71-
@io.swagger.v3.oas.annotations.responses.ApiResponse(
72-
responseCode = "500",
73-
description = "에러 - 서버 내부 오류 (데이터 조회 실패)",
74-
content = @Content(
75-
schema = @Schema(implementation = ApiResponse.class),
76-
examples = @ExampleObject(
77-
name = "서버 에러 예시",
78-
value = """
79-
{
80-
"isSuccess": false,
81-
"code": "COMMON500",
82-
"message": "서버 에러, 관리자에게 문의 바랍니다.",
83-
"result": null
34+
"result": {
35+
"cardList": [
36+
{
37+
"cardName": "현대카드 M Edition3",
38+
"cardNoMasked": "4321-****-****-1234",
39+
"cardType": "CREDIT",
40+
"organization": "0302",
41+
"createdAt": "2024-05-22T09:00:00"
42+
},
43+
{
44+
"cardName": "신한 Deep Dream",
45+
"cardNoMasked": "5336-****-****-5678",
46+
"cardType": "CHECK",
47+
"organization": "0304",
48+
"createdAt": "2024-05-23T11:20:00"
49+
}
50+
],
51+
"totalCount": 2
52+
}
8453
}
8554
"""
8655
)
@@ -177,56 +146,24 @@ public interface AssetControllerDocs {
177146
"message": "성공입니다.",
178147
"result": [
179148
{
180-
"bankName": "국민은행",
181-
"bankCode": "004",
182-
"connectedAt": "2024-05-20T10:00:00"
149+
"id": 1,
150+
"organizationCode": "0004",
151+
"organizationName": "KB국민은행",
152+
"connectedAt": "2024-05-20T10:00:00",
153+
"status": "ACTIVE"
183154
},
184155
{
185-
"bankName": "신한은행",
186-
"bankCode": "088",
187-
"connectedAt": "2024-05-21T15:30:00"
156+
"id": 2,
157+
"organizationCode": "0088",
158+
"organizationName": "신한은행",
159+
"connectedAt": "2024-05-21T15:30:00",
160+
"status": "ACTIVE"
188161
}
189162
]
190163
}
191164
"""
192165
)
193166
)
194-
),
195-
@io.swagger.v3.oas.annotations.responses.ApiResponse(
196-
responseCode = "401",
197-
description = "에러 - 인증되지 않은 사용자",
198-
content = @Content(
199-
schema = @Schema(implementation = ApiResponse.class),
200-
examples = @ExampleObject(
201-
name = "인증 에러 예시",
202-
value = """
203-
{
204-
"isSuccess": false,
205-
"code": "AUTH401_1",
206-
"message": "로그인이 필요합니다.",
207-
"result": null
208-
}
209-
"""
210-
)
211-
)
212-
),
213-
@io.swagger.v3.oas.annotations.responses.ApiResponse(
214-
responseCode = "500",
215-
description = "에러 - CODEF API 연동 오류",
216-
content = @Content(
217-
schema = @Schema(implementation = ApiResponse.class),
218-
examples = @ExampleObject(
219-
name = "외부 API 연동 실패 예시",
220-
value = """
221-
{
222-
"isSuccess": false,
223-
"code": "BANK500_1",
224-
"message": "은행 연동 데이터를 가져오는 중 오류가 발생했습니다.",
225-
"result": null
226-
}
227-
"""
228-
)
229-
)
230167
)
231168
})
232169
ApiResponse<List<BankResDTO.BankConnection>> getBanks();
@@ -249,8 +186,8 @@ public interface AssetControllerDocs {
249186
"accountList": [
250187
{
251188
"accountName": "KB나라사랑우대통장",
252-
"balanceAmount": "150000",
253-
"organization": "004",
189+
"balanceAmount": 150000,
190+
"organization": "0004",
254191
"createdAt": "2024-05-20T10:00:00"
255192
}
256193
],
@@ -282,8 +219,8 @@ public interface AssetControllerDocs {
282219
"accountList": [
283220
{
284221
"accountName": "KB나라사랑우대통장",
285-
"balanceAmount": "150000",
286-
"organization": "004",
222+
"balanceAmount": 150000,
223+
"organization": "0004",
287224
"createdAt": "2024-05-20T10:00:00"
288225
}
289226
],

src/main/java/org/umc/valuedi/domain/asset/entity/BankAccount.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import jakarta.persistence.*;
44
import lombok.*;
5+
import org.hibernate.annotations.SQLDelete;
6+
import org.hibernate.annotations.SQLRestriction;
57
import org.umc.valuedi.domain.asset.enums.AccountGroup;
68
import org.umc.valuedi.global.entity.BaseEntity;
79
import org.umc.valuedi.domain.connection.entity.CodefConnection;
@@ -15,6 +17,8 @@
1517
@AllArgsConstructor(access = AccessLevel.PRIVATE)
1618
@Getter
1719
@Table(name = "bank_account")
20+
@SQLDelete(sql = "UPDATE bank_account SET is_active = false, updated_at = CURRENT_TIMESTAMP WHERE id = ?")
21+
@SQLRestriction("is_active = true")
1822
public class BankAccount extends BaseEntity {
1923
@Id
2024
@GeneratedValue(strategy = GenerationType.IDENTITY)
@@ -84,4 +88,8 @@ public class BankAccount extends BaseEntity {
8488
public void assignConnection(CodefConnection connection) {
8589
this.codefConnection = connection;
8690
}
91+
92+
public void deactivate() {
93+
this.isActive = false;
94+
}
8795
}

src/main/java/org/umc/valuedi/domain/asset/entity/Card.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import jakarta.persistence.*;
44
import lombok.*;
5+
import org.hibernate.annotations.SQLDelete;
6+
import org.hibernate.annotations.SQLRestriction;
57
import org.umc.valuedi.global.entity.BaseEntity;
68
import org.umc.valuedi.domain.connection.entity.CodefConnection;
79

@@ -21,6 +23,8 @@
2123
)
2224
}
2325
)
26+
@SQLDelete(sql = "UPDATE card SET is_active = false, updated_at = CURRENT_TIMESTAMP WHERE id = ?")
27+
@SQLRestriction("is_active = true")
2428
public class Card extends BaseEntity {
2529
@Id
2630
@GeneratedValue(strategy = GenerationType.IDENTITY)
@@ -52,4 +56,8 @@ public class Card extends BaseEntity {
5256
public void assignConnection(CodefConnection connection) {
5357
this.codefConnection = connection;
5458
}
59+
60+
public void deactivate() {
61+
this.isActive = false;
62+
}
5563
}

src/main/java/org/umc/valuedi/domain/connection/controller/ConnectionController.java

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,11 @@
33
import lombok.RequiredArgsConstructor;
44
import org.springframework.web.bind.annotation.*;
55
import org.umc.valuedi.domain.connection.dto.res.ConnectionResDTO;
6+
import org.umc.valuedi.domain.connection.exception.code.ConnectionSuccessCode;
67
import org.umc.valuedi.domain.connection.service.ConnectionCommandService;
78
import org.umc.valuedi.domain.connection.service.ConnectionQueryService;
89
import org.umc.valuedi.global.apiPayload.ApiResponse;
910
import org.umc.valuedi.domain.connection.dto.req.ConnectionReqDTO;
10-
import org.umc.valuedi.global.apiPayload.code.GeneralSuccessCode;
11-
import org.umc.valuedi.global.external.codef.exception.code.CodefSuccessCode;
1211

1312
import java.util.List;
1413

@@ -25,19 +24,27 @@ public ApiResponse<Void> connect(
2524
// @CurrentMember Long memberId,
2625
@RequestBody ConnectionReqDTO.Connect request
2726
) {
28-
Long memberId = 1L; // 임시
27+
Long memberId = 1L;
2928
connectionCommandService.connect(memberId, request);
30-
return ApiResponse.onSuccess(CodefSuccessCode.CODEF_CONNECTION_SUCCESS, null);
29+
return ApiResponse.onSuccess(ConnectionSuccessCode.CONNECTION_SUCCESS, null);
3130
}
3231

3332
@GetMapping
3433
public ApiResponse<List<ConnectionResDTO.Connection>> getAllConnections(
3534
// @CurrentMember Long memberId
3635
) {
37-
Long memberId = 1L; // 임시
36+
Long memberId = 1L;
3837
List<ConnectionResDTO.Connection> connections = connectionQueryService.getAllConnections(memberId);
39-
return ApiResponse.onSuccess(GeneralSuccessCode.OK, connections);
38+
return ApiResponse.onSuccess(ConnectionSuccessCode.CONNECTION_LIST_FETCH_SUCCESS, connections);
4039
}
4140

42-
// TODO: DELETE /api/codef/connections/{connectionId}
41+
@DeleteMapping("/{connectionId}")
42+
public ApiResponse<Void> disconnect(
43+
// @CurrentMember Long memberId,
44+
@PathVariable Long connectionId
45+
) {
46+
Long memberId = 1L;
47+
connectionCommandService.disconnect(memberId, connectionId);
48+
return ApiResponse.onSuccess(ConnectionSuccessCode.CONNECTION_DELETE_SUCCESS, null);
49+
}
4350
}

src/main/java/org/umc/valuedi/domain/connection/controller/ConnectionControllerDocs.java

Lines changed: 52 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
package org.umc.valuedi.domain.connection.controller;
22

33
import io.swagger.v3.oas.annotations.Operation;
4+
import io.swagger.v3.oas.annotations.Parameter;
45
import io.swagger.v3.oas.annotations.media.Content;
56
import io.swagger.v3.oas.annotations.media.ExampleObject;
67
import io.swagger.v3.oas.annotations.media.Schema;
78
import io.swagger.v3.oas.annotations.parameters.RequestBody;
89
import io.swagger.v3.oas.annotations.responses.ApiResponses;
910
import io.swagger.v3.oas.annotations.tags.Tag;
11+
import org.springframework.web.bind.annotation.PathVariable;
1012
import org.umc.valuedi.domain.connection.dto.req.ConnectionReqDTO;
1113
import org.umc.valuedi.domain.connection.dto.res.ConnectionResDTO;
1214
import org.umc.valuedi.global.apiPayload.ApiResponse;
@@ -75,15 +77,15 @@ ApiResponse<Void> connect(
7577
"message": "성공입니다.",
7678
"result": [
7779
{
78-
"id": 1,
79-
"organization": "국민은행",
80-
"type": "BANK",
80+
"connectionId": 1,
81+
"organization": "0020",
82+
"type": "BK",
8183
"connectedAt": "2024-05-20T10:00:00"
8284
},
8385
{
84-
"id": 2,
85-
"organization": "현대카드",
86-
"type": "CARD",
86+
"connectionId": 2,
87+
"organization": "0309",
88+
"type": "CD",
8789
"connectedAt": "2024-05-22T09:00:00"
8890
}
8991
]
@@ -94,4 +96,47 @@ ApiResponse<Void> connect(
9496
)
9597
})
9698
ApiResponse<List<ConnectionResDTO.Connection>> getAllConnections();
97-
}
99+
100+
@Operation(summary = "금융사 연동 해제 API", description = "특정 금융사(은행/카드사)와의 연동을 해제합니다. 연동 해제 시 해당 금융사에 속한 모든 계좌 및 카드가 비활성화됩니다.")
101+
@ApiResponses({
102+
@io.swagger.v3.oas.annotations.responses.ApiResponse(
103+
responseCode = "200",
104+
description = "성공 - 연동 해제 완료",
105+
content = @Content(
106+
schema = @Schema(implementation = ApiResponse.class),
107+
examples = @ExampleObject(
108+
name = "성공 예시",
109+
value = """
110+
{
111+
"isSuccess": true,
112+
"code": "CONNECTION200_1",
113+
"message": "성공적으로 금융사 연동이 삭제되었습니다.",
114+
"result": null
115+
}
116+
"""
117+
)
118+
)
119+
),
120+
@io.swagger.v3.oas.annotations.responses.ApiResponse(
121+
responseCode = "404",
122+
description = "에러 - 연동 정보를 찾을 수 없음",
123+
content = @Content(
124+
schema = @Schema(implementation = ApiResponse.class),
125+
examples = @ExampleObject(
126+
name = "실패 예시",
127+
value = """
128+
{
129+
"isSuccess": false,
130+
"code": "CONNECTION404_1",
131+
"message": "해당 연동 정보를 찾을 수 없습니다.",
132+
"result": null
133+
}
134+
"""
135+
)
136+
)
137+
)
138+
})
139+
ApiResponse<Void> disconnect(
140+
@Parameter(description = "해제할 연동 ID (connectionId)") @PathVariable Long connectionId
141+
);
142+
}

src/main/java/org/umc/valuedi/domain/connection/converter/ConnectionConverter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public BankResDTO.BankConnection toBankConnectionDTO(CodefConnection connection)
2828
*/
2929
public ConnectionResDTO.Connection toConnectionDTO(CodefConnection connection) {
3030
return ConnectionResDTO.Connection.builder()
31-
.id(connection.getId())
31+
.connectionId(connection.getId())
3232
.organizationCode(connection.getOrganization())
3333
.organizationName(Organization.getNameByCode(connection.getOrganization()))
3434
.businessType(connection.getBusinessType())

0 commit comments

Comments
 (0)