feat: 카드 결제 알림 자동인식(미확인 내역) 백엔드

- account_entry pending/notif_key 컬럼 추가(멱등 마이그레이션)
- CardNotificationParser: 카드사·금액·가맹점·승인/취소 파싱
- POST /entries/notification(미확인 지출 생성, 중복방지, 카드 자동매칭)
- POST /entries/{id}/confirm(확정), GET /entries/pending/count

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
ByungCheol
2026-06-03 18:51:30 +09:00
co-authored by Claude Opus 4.8
parent c1f97364aa
commit 8fec057752
10 changed files with 314 additions and 3 deletions
@@ -6,6 +6,8 @@ import com.sb.web.account.dto.AccountSummary;
import com.sb.web.account.dto.AccountTagRequest;
import com.sb.web.account.dto.AccountTagResponse;
import com.sb.web.account.dto.CategoryStat;
import com.sb.web.account.dto.ConfirmRequest;
import com.sb.web.account.dto.NotificationRequest;
import com.sb.web.account.dto.NetWorthPoint;
import com.sb.web.account.dto.NetWorthResponse;
import com.sb.web.account.dto.PeriodStat;
@@ -23,6 +25,7 @@ import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
/**
* 가계부 API. (/api/account/** — 로그인 필요, 본인 데이터만 접근)
@@ -202,4 +205,34 @@ public class AccountController {
accountService.delete(id, current.getId());
return ResponseEntity.noContent().build();
}
/* ===== 카드 결제 알림 자동인식 ===== */
/** 미확인(확인 필요) 내역 개수 */
@GetMapping("/entries/pending/count")
public Map<String, Integer> pendingCount(
@RequestAttribute(AuthInterceptor.CURRENT_MEMBER) SessionUser current) {
return Map.of("count", accountService.countPending(current.getId()));
}
/** 카드 결제 푸시 알림 → 미확인 지출 내역 생성 (네이티브 알림 리스너가 호출). 생성 안 되면 204 */
@PostMapping("/entries/notification")
public ResponseEntity<AccountEntryResponse> fromNotification(
@RequestBody NotificationRequest req,
@RequestAttribute(AuthInterceptor.CURRENT_MEMBER) SessionUser current) {
AccountEntryResponse created = accountService.createFromNotification(req, current.getId());
if (created == null) {
return ResponseEntity.noContent().build();
}
return ResponseEntity.status(HttpStatus.CREATED).body(created);
}
/** 미확인 내역 확정 (분류/계좌 보정) */
@PostMapping("/entries/{id}/confirm")
public AccountEntryResponse confirm(
@PathVariable Long id,
@RequestBody(required = false) ConfirmRequest req,
@RequestAttribute(AuthInterceptor.CURRENT_MEMBER) SessionUser current) {
return accountService.confirm(id, req, current.getId());
}
}