개인프로젝트
📌 오늘 한 일 한눈에 보기
| 구분 | 내용 |
|---|
| 새 기능 | 앱 결제, 앱 관리자 콘솔, 푸시 알림(FCM), 앱 소셜 로그인 |
| 추가 기능 | 변호사 승인 취소, 관리자 → 변호사 1:1 메시지 |
| 버그 수정 | 결제 탭 500 에러, 팝업 빨간 화면, 간헐적 테스트 실패 |
| 개선 | 환불 문구 정리 ("결제 취소" → "바로 환불") |
1. 앱 결제 (토스페이먼츠)
앱에서도 웹과 똑같이 결제할 수 있게 했다.
상품
- 등기부 분석 이용권: 무료 5회를 다 쓰면 1회 990원
- 원하는 변호사 직접 선택: 1회 2,900원 구현 방식
- 토스 결제창을 앱 안의 WebView로 띄운다.
- 결제가 끝나 돌아오는 주소를 앱이 가로채서 서버에 승인을 요청한다.
- 금액은 서버가 자기 주문과 대조해서 검증한다. 앱이 보낸 금액을 그대로 믿지 않는다.
- 결제 중 카드사 앱을 여는 안드로이드
intent:// 주소는 일반 링크처럼 열리지 않아서, 앱 주소로
바꿔 여는 처리를 넣었다.
환불 정책
| 이용권 상태 | 버튼 | 처리 방식 |
|---|
| 아직 안 씀 | 바로 환불 | 관리자 승인 없이 즉시 환불 |
| 이미 씀 | 환불 요청 | 사유를 적어 요청 → 관리자가 승인 또는 거절 |
💡 처음에는 즉시 환불 버튼 이름이 "결제 취소"였다.
동작은 맞았지만 사용자가 환불 버튼인지 알아보기 어려워서, 문구를 전부 "환불" 기준으로 바꿨다.
2. 앱 관리자 콘솔
웹 관리자 페이지를 앱 하단 탭 4개로 옮겼다.
- 변호사 승인: 자격 서류(PDF·JPG)를 휴대폰 기본 앱으로 열어 보고 승인하거나 거절
- 신고 관리: 신고된 대화 원문을 확인하고 이용 정지 또는 기각
- 회원 관리: 회원과 변호사를 검색하고 이용 정지·해제
- 결제 관리: 환불 요청 승인·거절
웹·앱 공통으로 추가한 기능
① 변호사 승인 취소
- 누르면 바로 처리되지 않고, "정말로 ○○○ 변호사의 승인을 취소하시겠습니까?" 확인창을 먼저
띄운다.
- 취소하면 거절이 아니라 승인 대기로 되돌린다. 나중에 다시 승인할 수 있게 하기 위해서다.
- 그 변호사는 즉시 로그아웃되고, 새 상담 배정에서도 빠진다. ② 관리자 → 변호사 1:1 메시지
- 관리자가 변호사 한 명을 골라 안내 메시지를 보낸다.
- 변호사는 알림함의 안 읽음 뱃지와 앱 푸시로 알 수 있다.
- 관리자 화면에서는 메시지마다 읽었는지, 언제 읽었는지가 보인다.
- 운영 안내용이라 답장은 할 수 없는 단방향으로 만들었다.
3. 푸시 알림 (Firebase Cloud Messaging)
웹은 브라우저 웹 푸시를 이미 쓰고 있었고, 앱에는 FCM을 붙였다.
| 받는 사람 | 알림 내용 |
|---|
| 회원 | 변호사 답장 |
| 변호사 | 새 문의, 회원 메시지, 관리자 메시지 |
-
알림을 누르면 해당 대화방(또는 알림함)이 바로 열린다.
-
앱을 보고 있는 중이면 시스템 알림 대신 화면 아래 알림줄로 보여준다.
-
로그아웃하면 그 기기 토큰을 지워서 더 이상 알림이 오지 않는다.
-
서버는 Firebase Admin SDK 대신 HTTP v1 API를 직접 호출한다. OCR 때문에 이미 쓰던 구글 인증
라이브러리를 재사용해서 의존성이 늘지 않았다.
-
Firebase 설정 파일이 없어도 앱은 빌드되고 푸시만 꺼지게 만들었다. 팀원 누구나 Firebase 없이
개발할 수 있다.
-
iOS는 푸시 권한과 백그라운드 알림 설정을 프로젝트에 반영해 두었다. 실제 확인은 Mac에서 해야
한다.
4. 앱 소셜 로그인 (구글·카카오·네이버)
앱 전용 SDK를 쓰려면 각 사 개발자 콘솔에 앱을 따로 등록해야 한다.
그래서 웹의 소셜 로그인 흐름을 앱 안의 브라우저 탭으로 재사용했다.
🔐 문제: 앱으로 돌아오는 주소는 가로챌 수 있다
로그인 후 앱으로 돌아올 때 쓰는 projectguard:// 같은 주소는 다른 앱도 등록할 수 있다.
세션 토큰을 그대로 실어 보내면 탈취될 위험이 있다.
✅ 해결: PKCE 방식 적용
- 앱이 비밀값(verifier)을 만들고, 그 해시값만 웹에 넘긴다.
- 로그인이 끝나면 웹은 세션 대신 2분짜리 1회용 코드만 앱으로 보낸다.
- 앱은 코드 + 처음 만든 비밀값을 서버에 보내 세션을 받는다. → 다른 앱이 코드를 가로채도 비밀값이 없으니 로그인할 수 없다.
🐛 고친 버그들
1) 관리자 결제 탭 500 에러
- 증상: "환불 요청", "환불 완료" 탭을 열기만 해도 500 에러가 났다. 처음엔 데이터가 없어서인 줄
알았다.
- 원인: H2 DB가 상태(enum) 칸을 처음 만들 때의 값 3개(
READY, PAID, FAILED)만 받도록 굳어
있었다. 나중에 추가한 REFUNDED 같은 값은 조회만 해도 거부됐고, 실제 환불 저장도 실패할
상황이었다.
- 해결:
- 모든 상태 칸을 일반 문자열(
varchar(30))로 변경
- Hibernate가 기존 DB 칸도 자동으로 바꿔줘서 재시작만으로 해결
- 같은 실수를 다시 하면 잡아내는 테스트 추가
2) 이용 정지를 누르면 앱이 빨간 화면
- 원인: 사유 입력 팝업이 닫히자마자 입력칸을 정리했는데, 팝업은 닫히는 애니메이션 동안 그
입력칸을 한 번 더 그렸다.
- 해결: 팝업이 입력칸을 직접 가지고 있다가, 완전히 사라질 때 정리하도록 공용 팝업으로
바꿨다. 같은 구조였던 가입 거절, 환불 거절, 환불 요청 팝업도 함께 해결됐다. 3) 가끔씩만 실패하던 테스트
- 증상: 전체 테스트를 돌리면 소켓 알림 테스트가 가끔 실패하고, 단독으로 돌리면 통과했다.
- 원인: 처음엔 "PC가 바빠서"라고 넘겼지만, 두 번 연속 실패해서 들여다봤다. 클라이언트 연결이
끝난 시점과 서버가 그 연결을 등록하는 시점 사이에 짧은 틈이 있었고, 그 사이에 나간 알림이
사라지고 있었다.
- 해결: 테스트가 서버 등록을 기다린 뒤 알림을 만들도록 수정했다.
💡 오늘 배운 점
- "가끔 실패"에는 원인이 있다. 부하 탓으로 넘기기 전에 타이밍과 순서부터 의심하자.
- 500 에러는 데이터 문제가 아니라 거의 항상 진짜 오류다. 추측하기 전에 로그부터 보자.
- 자동화 테스트는 한 단계마다 화면을 확인한다. 확인 없이 터치를 연달아 보냈다가, 메뉴 크기가
달라 엉뚱한 입력칸에 값이 들어간 적이 있다.
- 화면 문구도 기능이다. 동작이 맞아도 버튼 이름이 헷갈리면 사용자는 기능이 없는 줄 안다.