Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
8dd8011
♻️ refactor: 입금 주소 확인하기 API 로직 변경 (각 코인타입, 네트워크 타입 -> 최상단 반환)
rlawngjs0313 Feb 9, 2026
717d844
✨ feat: 회원 타입 기본값 추가
komascode Feb 10, 2026
04f828f
✨ feat: 회원 타입 기본값 로직 추가
komascode Feb 10, 2026
7b9637d
♻️ refactor: Swagger 서버 주소 추가
rlawngjs0313 Feb 10, 2026
79a4044
♻️ [Refactor] Swagger 서버 주소 추가
rlawngjs0313 Feb 10, 2026
17ea706
♻️ refactor: 간편 비밀번호 재설정 리팩토링 (전화번호 -> JWT형식 토큰)
rlawngjs0313 Feb 10, 2026
1e58efb
♻️ refactor: 도메인 적용에 따라 CORS, Swagger 서버 URI 추가
rlawngjs0313 Feb 10, 2026
fc4ab45
♻️ refactor: 입금 주소 확인하기 API 로직 변경 (각 코인타입, 네트워크 타입 -> 최상단 반환)
rlawngjs0313 Feb 9, 2026
fa31af7
♻️ refactor: 입금 주소 확인하기 API 최초 사용자 테스트 결과 업데이트
rlawngjs0313 Feb 9, 2026
35004a1
Merge remote-tracking branch 'origin/Feat/#67' into Feat/#67
rlawngjs0313 Feb 10, 2026
e7c007c
♻️ refactor: 입금 주소 생성하기 API 거래소에서 오는 응답 (currency) 사용
rlawngjs0313 Feb 10, 2026
665b1cd
✨ [Feat] 회원 타입 미입력 시 디폴트 값 추가
rlawngjs0313 Feb 10, 2026
abfa5cd
♻️ [Refactor] 간편 비밀번호 재설정 리팩토링 (전화번호 -> JWT형식 토큰)
rlawngjs0313 Feb 10, 2026
f114441
♻️ [Refactor] 도메인 적용에 따라 CORS, Swagger 서버 URI 추가
rlawngjs0313 Feb 10, 2026
cc10586
♻️ [Refactor] 입금 주소 확인하기, 생성하기 API 로직 개편
rlawngjs0313 Feb 10, 2026
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
Expand Up @@ -57,10 +57,9 @@ public record SignupRequest(
@Schema(description = "AES 암호화된 6자리 간편비밀번호 (Base64)", example = "ItfrsoB1J0hl3O60mahB1A==")
String simplePassword,

@Schema(description = "회원 타입 (INDIVIDUAL: 개인, CORPORATION: 법인)",
@Schema(description = "회원 타입 (미입력 시 INDIVIDUAL 기본값)",
example = "INDIVIDUAL",
allowableValues = {"INDIVIDUAL", "CORPORATION"})
@NotNull(message = "회원 타입은 필수입니다.")
MemberType memberType,

@Schema(description = "바이오 인증 등록 여부 (true: 등록, false: 나중에)", example = "false")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.example.scoi.domain.auth.exception.code.AuthErrorCode;
import com.example.scoi.domain.member.dto.MemberReqDTO;
import com.example.scoi.domain.member.entity.Member;
import com.example.scoi.domain.member.enums.MemberType;
import com.example.scoi.domain.member.entity.MemberToken;
import com.example.scoi.domain.member.repository.MemberRepository;
import com.example.scoi.domain.member.repository.MemberTokenRepository;
Expand Down Expand Up @@ -200,7 +201,7 @@ public AuthResDTO.SignupResponse signup(AuthReqDTO.SignupRequest request) {
.koreanName(request.koreanName())
.residentNumber(request.residentNumber())
.simplePassword(hashedPassword)
.memberType(request.memberType())
.memberType(request.memberType() != null ? request.memberType() : MemberType.INDIVIDUAL)
.isBioRegistered(request.isBioRegistered() != null ? request.isBioRegistered() : false)
.build();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,6 @@
import com.example.scoi.domain.member.enums.ExchangeType;
import com.example.scoi.global.apiPayload.ApiResponse;
import com.example.scoi.global.apiPayload.code.BaseSuccessCode;

import com.example.scoi.domain.charge.exception.ChargeException;
import com.example.scoi.domain.charge.exception.code.ChargeErrorCode;
import com.example.scoi.domain.member.enums.ExchangeType;
import com.example.scoi.global.security.userdetails.CustomUserDetails;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
Expand Down Expand Up @@ -70,19 +66,12 @@ public ApiResponse<BalanceResDTO.BalanceListDTO> getBalances(

// 입금 주소 확인하기
@GetMapping("/deposits/address")
public ApiResponse<List<ChargeResDTO.GetDepositAddress>> getDepositAddress(
public ApiResponse<String> getDepositAddress(
@AuthenticationPrincipal CustomUserDetails user,
@RequestParam ExchangeType exchangeType,
@RequestParam(defaultValue = "") List<String> coinType,
@RequestParam(defaultValue = "") List<String> netType
@RequestParam ExchangeType exchangeType
){
BaseSuccessCode code = ChargeSuccessCode.OK;
return ApiResponse.onSuccess(code, chargeService.getDepositAddress(
user.getUsername(),
exchangeType,
coinType.stream().map(String::toUpperCase).toList(),
netType.stream().map(String::toUpperCase).toList()
));
return ApiResponse.onSuccess(code, chargeService.getDepositAddress(user.getUsername(), exchangeType));
}

// 입금 주소 생성하기
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,11 @@ ApiResponse<BalanceResDTO.BalanceListDTO> getBalances(

@Operation(
summary = "입금 주소 확인하기 API By 김주헌",
description = "코인의 입금 주소를 확인합니다. 리스트로 보내실때 꼭! 인덱스 맞춰서 보내주세요. 입금 주소가 없는 경우, 해당 코인은 제외하고 응답이 옵니다."
description = "코인의 입금 주소를 확인합니다. 해당 유저의 입금 주소 전체를 조회하고 최상단에 잇는 입금 주소를 반환합니다."
)
ApiResponse<List<ChargeResDTO.GetDepositAddress>> getDepositAddress(
ApiResponse<String> getDepositAddress(
@AuthenticationPrincipal CustomUserDetails user,
@RequestParam ExchangeType exchangeType,
@RequestParam(defaultValue = "") List<String> coinType,
@RequestParam(defaultValue = "") List<String> netType
@RequestParam ExchangeType exchangeType
);

@Operation(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public enum ChargeErrorCode implements BaseErrorCode {
"주문 내역을 찾을 수 없습니다."),
ADDRESS_NOT_FOUND(HttpStatus.NOT_FOUND,
"CHARGE404_3",
"해당 코인의 입금 주소가 없습니다."),
"해당 유저의 입금 주소가 없습니다."),
;

private final HttpStatus status;
Expand Down
178 changes: 41 additions & 137 deletions src/main/java/com/example/scoi/domain/charge/service/ChargeService.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@
import com.example.scoi.domain.charge.exception.ChargeException;
import com.example.scoi.domain.charge.exception.code.ChargeErrorCode;
import com.example.scoi.domain.member.enums.ExchangeType;
import com.example.scoi.domain.member.repository.MemberApiKeyRepository;
import com.example.scoi.domain.member.exception.MemberException;
import com.example.scoi.domain.member.repository.MemberRepository;
import com.example.scoi.global.client.BithumbClient;
import com.example.scoi.global.client.UpbitClient;
import com.example.scoi.global.client.converter.BithumbConverter;
Expand All @@ -27,9 +25,7 @@

import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
@RequiredArgsConstructor
Expand All @@ -40,8 +36,6 @@ public class ChargeService {
private final JwtApiUtil jwtApiUtil;
private final BithumbClient bithumbClient;
private final UpbitClient upbitClient;
private final MemberRepository memberRepository;
private final MemberApiKeyRepository memberApiKeyRepository;

// 원화 충전 요청하기
public ChargeResDTO.ChargeKrw chargeKrw(
Expand Down Expand Up @@ -300,146 +294,56 @@ public BalanceResDTO.BalanceListDTO getBalancesByPhone(String phoneNumber, Excha
}

// 입금 주소 확인하기
public List<ChargeResDTO.GetDepositAddress> getDepositAddress(
public String getDepositAddress(
String phoneNumber,
ExchangeType exchangeType,
List<String> coinType,
List<String> netType
ExchangeType exchangeType
) {

// 거래소별 요청 보내기
String token;
List<ChargeResDTO.GetDepositAddress> result = new ArrayList<>();
Map<String, String> bindError = new HashMap<>();
switch (exchangeType){
case UPBIT:
for (int idx = 0; idx < coinType.size(); idx++) {
try {
token = jwtApiUtil
.createUpBitJwt(
phoneNumber,
"currency=" + coinType.get(idx) + "&net_type=" + netType.get(idx),
null
);

UpbitResDTO.GetDepositAddress upbitResult = upbitClient
.getDepositAddress(token, coinType.get(idx), netType.get(idx));

// 그게 아닌 경우
result.add(
ChargeConverter.toGetDepositAddress(
upbitResult.currency(),
upbitResult.deposit_address()
)
);
// JWT 토큰 제작 실패
} catch (GeneralSecurityException e) {
throw new ChargeException(ChargeErrorCode.EXCHANGE_BAD_REQUEST);
// 해당 코인의 입금주소가 없는 경우
} catch (FeignException.NotFound e) {
ObjectMapper objectMapper = new ObjectMapper();
ClientErrorDTO.Errors error = objectMapper.readValue(e.contentUTF8(), ClientErrorDTO.Errors.class);

if (!error.error().name().equals("coin_address_not_found")) {
throw new ChargeException(ChargeErrorCode.EXCHANGE_BAD_REQUEST);
}
// 잘못된 파라미터, 거래소에서 지원하지 않는 코인
} catch (FeignException.BadRequest e) {
ObjectMapper objectMapper = new ObjectMapper();
ClientErrorDTO.Errors error = objectMapper.readValue(e.contentUTF8(), ClientErrorDTO.Errors.class);

// 잘못된 파라미터인 경우: DTO 코인 정보를 잘못 기입
switch (error.error().name()) {

// 잘못된 파라미터 입력
case "validation_error":
case "invalid_parameter":
throw new ChargeException(ChargeErrorCode.WRONG_COIN_TYPE);

// 거래소에서 지원하지 않는 코인
case "currency does not have a valid value":
bindError.put(coinType.get(idx), netType.get(idx));
break;

default:
throw new ChargeException(ChargeErrorCode.EXCHANGE_BAD_REQUEST);
}
// JWT 토큰 생성 오류
} catch (FeignException.Unauthorized e) {
throw new ChargeException(ChargeErrorCode.EXCHANGE_BAD_REQUEST);
}
}
break;
String result;
try{
switch (exchangeType) {
case UPBIT:
token = jwtApiUtil.createUpBitJwt(phoneNumber, null, null);
List<UpbitResDTO.GetDepositAddress> upbitResult = upbitClient.getDepositAddresses(token);

// USDT, USDC 중 가장 먼저 찾은 입금 주소를 result로
result = upbitResult.stream().filter(depositAddress ->
depositAddress.currency().equals("USDT") || depositAddress.currency().equals("USDC"))
.findFirst()
.orElseThrow(() -> new ChargeException(ChargeErrorCode.ADDRESS_NOT_FOUND))
.deposit_address();

break;
case BITHUMB:
for (int idx = 0; idx < coinType.size(); idx++){
try {
token = jwtApiUtil
.createBithumbJwt(
phoneNumber,
"currency="+coinType.get(idx)+"&net_type="+netType.get(idx),
null
);

BithumbResDTO.GetDepositAddress bithumbResult = bithumbClient
.getDepositAddress(token, coinType.get(idx), netType.get(idx));

// 그게 아닌 경우
result.add(
ChargeConverter.toGetDepositAddress(
bithumbResult.currency(),
bithumbResult.deposit_address()
)
);
// JWT 토큰 제작 실패
} catch (GeneralSecurityException e) {
throw new ChargeException(ChargeErrorCode.EXCHANGE_BAD_REQUEST);
// 해당 코인의 입금주소가 없는 경우
} catch (FeignException.NotFound e) {
ObjectMapper objectMapper = new ObjectMapper();
ClientErrorDTO.Errors error = objectMapper.readValue(e.contentUTF8(), ClientErrorDTO.Errors.class);

if (!error.error().name().equals("coin_address_not_found")) {
throw new ChargeException(ChargeErrorCode.EXCHANGE_BAD_REQUEST);
}
// 잘못된 파라미터, 거래소에서 지원하지 않는 코인
} catch (FeignException.BadRequest e) {
ObjectMapper objectMapper = new ObjectMapper();
ClientErrorDTO.Errors error = objectMapper.readValue(e.contentUTF8(), ClientErrorDTO.Errors.class);

// 잘못된 파라미터인 경우: DTO 코인 정보를 잘못 기입
switch (error.error().name()) {

// 잘못된 파라미터 입력
case "validation_error":
case "invalid_parameter":
throw new ChargeException(ChargeErrorCode.WRONG_COIN_TYPE);

// 네트워크 미지원: 넘기기
case "request_for_address_of_not_supported_currency":
bindError.put(coinType.get(idx), netType.get(idx));
break;

// 거래소에서 지원하지 않는 코인
case "currency does not have a valid value":
throw new ChargeException(ChargeErrorCode.NOT_SUPPORT_COIN);

default:
throw new ChargeException(ChargeErrorCode.EXCHANGE_BAD_REQUEST);
}
// JWT 토큰 생성 오류
} catch (FeignException.Unauthorized e) {
throw new ChargeException(ChargeErrorCode.EXCHANGE_BAD_REQUEST);
}
}
token = jwtApiUtil.createBithumbJwt(phoneNumber, null, null);
List<BithumbResDTO.GetDepositAddress> bithumbResult = bithumbClient.getDepositAddresses(token);

// USDT, USDC 중 가장 먼저 찾은 입금 주소를 result로
result = bithumbResult.stream().filter(depositAddress ->
depositAddress.currency().equals("USDT") || depositAddress.currency().equals("USDC"))
.findFirst()
.orElseThrow(() -> new ChargeException(ChargeErrorCode.ADDRESS_NOT_FOUND))
.deposit_address();
break;
default:
throw new ChargeException(ChargeErrorCode.WRONG_EXCHANGE_TYPE);
}
}
// 거래소 JWT 토큰 오류
} catch (GeneralSecurityException e) {
throw new ChargeException(ChargeErrorCode.EXCHANGE_API_KEY_NOT_FOUND);
// 거래소 JWT 토큰 인증 오류
} catch (FeignException.Unauthorized e) {
ObjectMapper objectMapper = new ObjectMapper();
ClientErrorDTO.Errors error = objectMapper.readValue(e.contentUTF8(), ClientErrorDTO.Errors.class);

// 만약 bindError에 값이 있으면
if (!bindError.isEmpty()){
throw new ChargeException(ChargeErrorCode.ADDRESS_NOT_FOUND, bindError);
if (error.error().name().equals("out_of_scope")) {
throw new ChargeException(ChargeErrorCode.EXCHANGE_FORBIDDEN);
}
throw new ChargeException(ChargeErrorCode.EXCHANGE_BAD_REQUEST);
}

return result;
}

Expand Down Expand Up @@ -469,7 +373,7 @@ public List<String> createDepositAddress(
UpbitResDTO.CreateDepositAddress upbitResult = upbitClient
.createDepositAddress(token, upbitDto);

result.add(coin);
result.add(upbitResult.currency());
}
break;
case BITHUMB:
Expand All @@ -487,7 +391,7 @@ public List<String> createDepositAddress(
BithumbResDTO.CreateDepositAddress bithumbResult = bithumbClient
.createDepositAddress(token, bithumbDto);

result.add(coin);
result.add(bithumbResult.currency());
}
break;
default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import com.example.scoi.global.security.userdetails.CustomUserDetails;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import java.util.List;
Expand Down Expand Up @@ -53,7 +52,7 @@ public ApiResponse<Map<String, String>> changePassword(
// 간편 비밀번호 재설정
@PostMapping("/members/me/password/reset")
public ApiResponse<Void> resetPassword(
@Validated @RequestBody MemberReqDTO.ResetPassword dto,
@RequestBody MemberReqDTO.ResetPassword dto,
@AuthenticationPrincipal CustomUserDetails user
){
BaseSuccessCode code = MemberSuccessCode.RESET_SIMPLE_PASSWORD;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.example.scoi.domain.member.dto;

import com.example.scoi.domain.member.enums.ExchangeType;
import jakarta.validation.constraints.Pattern;

public class MemberReqDTO {

Expand All @@ -13,8 +12,7 @@ public record ChangePassword(

// 간편 비밀번호 재설정
public record ResetPassword(
@Pattern(regexp = "^\\d{11}$")
String phoneNumber,
String verificationCode,
String newPassword
){}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ public Void resetPassword(
.orElseThrow(() -> new MemberException(MemberErrorCode.MEMBER_NOT_FOUND));

// 인증된 전화번호인지 확인
if (!redisUtil.exists(VERIFICATION_PREFIX+dto.phoneNumber())){
if (!redisUtil.exists(VERIFICATION_PREFIX+dto.verificationCode())){
throw new MemberException(MemberErrorCode.UNVERIFIED_PHONE_NUMBER);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
package com.example.scoi.global.client;

import com.example.scoi.domain.member.dto.MemberReqDTO;
import com.example.scoi.domain.transfer.dto.TransferReqDTO;
import com.example.scoi.global.client.dto.BithumbReqDTO;
import com.example.scoi.global.client.dto.BithumbResDTO;
import com.example.scoi.global.client.dto.UpbitResDTO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;

Expand Down Expand Up @@ -75,6 +73,12 @@ BithumbResDTO.GetDepositAddress getDepositAddress(
@RequestParam("net_type") String netType
);

// 전체 입금 주소 조회
@GetMapping("/v1/deposits/coin_addresses")
List<BithumbResDTO.GetDepositAddress> getDepositAddresses(
@RequestHeader("Authorization") String token
);

// 이체 출금 가능 정보
// 쿼리 파라미터 O
@GetMapping("/v1/withdraws/chance")
Expand Down
Loading