From cd275f30af2a8569a75faa67bc087d18d5b8e3a0 Mon Sep 17 00:00:00 2001
From: sieun0322 <lce322@naver.com>
Date: Tue, 28 Oct 2025 23:38:11 +0900
Subject: [PATCH 1/5] =?UTF-8?q?feat:=20=ED=9A=8C=EC=9B=90=EA=B0=80?=
 =?UTF-8?q?=EC=9E=85=20=ED=86=B5=ED=95=A9=20=ED=85=8C=EC=8A=A4=ED=8A=B8?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../loopers/application/user/UserFacade.java  | 25 +++++++
 .../loopers/application/user/UserInfo.java    | 14 ++++
 .../java/com/loopers/domain/user/User.java    | 56 ++++++++++++++
 .../loopers/domain/user/UserRepository.java   |  9 +++
 .../com/loopers/domain/user/UserService.java  | 26 +++++++
 .../user/UserJpaRepository.java               |  8 ++
 .../user/UserRepositoryImpl.java              | 26 +++++++
 .../interfaces/api/user/UserCreateV1Dto.java  | 21 ++++++
 .../interfaces/api/user/UserV1ApiSpec.java    | 27 +++++++
 .../interfaces/api/user/UserV1Controller.java | 35 +++++++++
 .../com/loopers/domain/user/UserTest.java     | 13 ++++
 .../interfaces/api/UserV1ApiE2ETest.java      | 73 +++++++++++++++++++
 12 files changed, 333 insertions(+)
 create mode 100644 apps/commerce-api/src/main/java/com/loopers/application/user/UserFacade.java
 create mode 100644 apps/commerce-api/src/main/java/com/loopers/application/user/UserInfo.java
 create mode 100644 apps/commerce-api/src/main/java/com/loopers/domain/user/User.java
 create mode 100644 apps/commerce-api/src/main/java/com/loopers/domain/user/UserRepository.java
 create mode 100644 apps/commerce-api/src/main/java/com/loopers/domain/user/UserService.java
 create mode 100644 apps/commerce-api/src/main/java/com/loopers/infrastructure/user/UserJpaRepository.java
 create mode 100644 apps/commerce-api/src/main/java/com/loopers/infrastructure/user/UserRepositoryImpl.java
 create mode 100644 apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserCreateV1Dto.java
 create mode 100644 apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1ApiSpec.java
 create mode 100644 apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Controller.java
 create mode 100644 apps/commerce-api/src/test/java/com/loopers/domain/user/UserTest.java
 create mode 100644 apps/commerce-api/src/test/java/com/loopers/interfaces/api/UserV1ApiE2ETest.java

diff --git a/apps/commerce-api/src/main/java/com/loopers/application/user/UserFacade.java b/apps/commerce-api/src/main/java/com/loopers/application/user/UserFacade.java
new file mode 100644
index 000000000..30e3c1a69
--- /dev/null
+++ b/apps/commerce-api/src/main/java/com/loopers/application/user/UserFacade.java
@@ -0,0 +1,25 @@
+package com.loopers.application.user;
+
+import com.loopers.domain.user.User;
+import com.loopers.domain.user.UserService;
+import com.loopers.interfaces.api.user.UserCreateV1Dto;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Component;
+
+@RequiredArgsConstructor
+@Component
+public class UserFacade {
+  private final UserService userService;
+
+  public UserInfo join(UserCreateV1Dto.UserRequest requestDto) {
+    User user = userService.join(User.create(requestDto.userId(), requestDto.email(), requestDto.birthday(), requestDto.gender()));
+    return UserInfo.from(user);
+  }
+
+  public UserInfo getUser(Long id) {
+    User user = userService.getUser(id);
+    return UserInfo.from(user);
+  }
+
+}
diff --git a/apps/commerce-api/src/main/java/com/loopers/application/user/UserInfo.java b/apps/commerce-api/src/main/java/com/loopers/application/user/UserInfo.java
new file mode 100644
index 000000000..46f7d934a
--- /dev/null
+++ b/apps/commerce-api/src/main/java/com/loopers/application/user/UserInfo.java
@@ -0,0 +1,14 @@
+package com.loopers.application.user;
+
+import com.loopers.domain.user.User;
+
+public record UserInfo(String userId, String email, String birthday, String gender) {
+  public static UserInfo from(User model) {
+    return new UserInfo(
+        model.getUserId(),
+        model.getEmail(),
+        model.getBirthday().toString(),
+        model.getGender()
+    );
+  }
+}
diff --git a/apps/commerce-api/src/main/java/com/loopers/domain/user/User.java b/apps/commerce-api/src/main/java/com/loopers/domain/user/User.java
new file mode 100644
index 000000000..313267570
--- /dev/null
+++ b/apps/commerce-api/src/main/java/com/loopers/domain/user/User.java
@@ -0,0 +1,56 @@
+package com.loopers.domain.user;
+
+import com.loopers.domain.BaseEntity;
+import com.loopers.support.error.CoreException;
+import com.loopers.support.error.ErrorType;
+import jakarta.persistence.Entity;
+import jakarta.persistence.Table;
+import lombok.Getter;
+
+import java.time.LocalDate;
+import java.time.format.DateTimeParseException;
+
+@Entity
+@Table(name = "User")
+@Getter
+public class User extends BaseEntity {
+  private String userId;
+  private String email;
+  private LocalDate birthday;
+  private String gender;
+
+  protected User() {
+  }
+
+  private User(String userId, String email, String birthday, String gender) {
+    if (email == null || !email.contains("@")) {
+      throw new CoreException(
+          ErrorType.BAD_REQUEST,
+          "이메일 형식이 잘못되었습니다."
+      );
+    }
+    try {
+      this.birthday = LocalDate.parse(birthday);
+    } catch (DateTimeParseException e) {
+      throw new CoreException(
+          ErrorType.BAD_REQUEST,
+          "생년월일 형식이 유효하지 않습니다. (예: yyyy-MM-dd)"
+      );
+    }
+    if (gender == null || gender.isBlank()) {
+      throw new CoreException(
+          ErrorType.BAD_REQUEST,
+          "성별정보가 없습니다."
+      );
+    }
+    this.userId = userId;
+    this.email = email;
+    this.birthday = LocalDate.parse(birthday);
+    this.gender = gender;
+  }
+
+  public static User create(String userId, String email, String birthday, String gender) {
+    return new User(userId, email, birthday, gender);
+  }
+
+}
diff --git a/apps/commerce-api/src/main/java/com/loopers/domain/user/UserRepository.java b/apps/commerce-api/src/main/java/com/loopers/domain/user/UserRepository.java
new file mode 100644
index 000000000..6cfeb0420
--- /dev/null
+++ b/apps/commerce-api/src/main/java/com/loopers/domain/user/UserRepository.java
@@ -0,0 +1,9 @@
+package com.loopers.domain.user;
+
+import java.util.Optional;
+
+public interface UserRepository {
+  User save(User user);
+
+  Optional<User> find(Long id);
+}
diff --git a/apps/commerce-api/src/main/java/com/loopers/domain/user/UserService.java b/apps/commerce-api/src/main/java/com/loopers/domain/user/UserService.java
new file mode 100644
index 000000000..1d59f348a
--- /dev/null
+++ b/apps/commerce-api/src/main/java/com/loopers/domain/user/UserService.java
@@ -0,0 +1,26 @@
+
+package com.loopers.domain.user;
+
+import com.loopers.support.error.CoreException;
+import com.loopers.support.error.ErrorType;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+@RequiredArgsConstructor
+@Component
+public class UserService {
+
+  private final UserRepository userRepository;
+
+  @Transactional
+  public User join(User user) {
+    return userRepository.save(user);
+  }
+
+  @Transactional(readOnly = true)
+  public User getUser(Long id) {
+    return userRepository.find(id)
+        .orElseThrow(() -> new CoreException(ErrorType.NOT_FOUND, "[id = " + id + "] 예시를 찾을 수 없습니다."));
+  }
+}
diff --git a/apps/commerce-api/src/main/java/com/loopers/infrastructure/user/UserJpaRepository.java b/apps/commerce-api/src/main/java/com/loopers/infrastructure/user/UserJpaRepository.java
new file mode 100644
index 000000000..30627b36a
--- /dev/null
+++ b/apps/commerce-api/src/main/java/com/loopers/infrastructure/user/UserJpaRepository.java
@@ -0,0 +1,8 @@
+package com.loopers.infrastructure.user;
+
+import com.loopers.domain.example.ExampleModel;
+import com.loopers.domain.user.User;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface UserJpaRepository extends JpaRepository<User, Long> {
+}
diff --git a/apps/commerce-api/src/main/java/com/loopers/infrastructure/user/UserRepositoryImpl.java b/apps/commerce-api/src/main/java/com/loopers/infrastructure/user/UserRepositoryImpl.java
new file mode 100644
index 000000000..0ddf78f1a
--- /dev/null
+++ b/apps/commerce-api/src/main/java/com/loopers/infrastructure/user/UserRepositoryImpl.java
@@ -0,0 +1,26 @@
+package com.loopers.infrastructure.user;
+
+import com.loopers.domain.example.ExampleModel;
+import com.loopers.domain.example.ExampleRepository;
+import com.loopers.domain.user.User;
+import com.loopers.domain.user.UserRepository;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Component;
+
+import java.util.Optional;
+
+@RequiredArgsConstructor
+@Component
+public class UserRepositoryImpl implements UserRepository {
+  private final UserJpaRepository jpaRepository;
+
+  @Override
+  public User save(User user) {
+    return jpaRepository.save(user);
+  }
+
+  @Override
+  public Optional<User> find(Long id) {
+    return jpaRepository.findById(id);
+  }
+}
diff --git a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserCreateV1Dto.java b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserCreateV1Dto.java
new file mode 100644
index 000000000..a4b5d44a8
--- /dev/null
+++ b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserCreateV1Dto.java
@@ -0,0 +1,21 @@
+package com.loopers.interfaces.api.user;
+
+import com.loopers.application.user.UserInfo;
+
+import java.time.LocalDate;
+
+public class UserCreateV1Dto {
+  public record UserRequest(String userId, String email, String birthday, String gender) {
+  }
+
+  public record UserResponse(String userId, String email, String birthday, String gender) {
+    public static UserResponse from(UserInfo info) {
+      return new UserResponse(
+          info.userId(),
+          info.email(),
+          info.birthday().toString(),
+          info.gender()
+      );
+    }
+  }
+}
diff --git a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1ApiSpec.java b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1ApiSpec.java
new file mode 100644
index 000000000..4bd4bfa35
--- /dev/null
+++ b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1ApiSpec.java
@@ -0,0 +1,27 @@
+package com.loopers.interfaces.api.user;
+
+import com.loopers.interfaces.api.ApiResponse;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.tags.Tag;
+
+@Tag(name = "User V1 API", description = "Loopers 예시 API 입니다.")
+public interface UserV1ApiSpec {
+  @Operation(
+      summary = "회원가입",
+      description = "유저 회원가입 합니다."
+  )
+  ApiResponse<UserCreateV1Dto.UserResponse> join(
+      @Schema(name = "예시 ID", description = "조회할 예시의 ID")
+          UserCreateV1Dto.UserRequest userRequest
+  );
+
+  @Operation(
+      summary = "예시 조회",
+      description = "ID로 예시를 조회합니다."
+  )
+  ApiResponse<UserCreateV1Dto.UserResponse> getUser(
+      @Schema(name = "예시 ID", description = "조회할 예시의 ID")
+          Long exampleId
+  );
+}
diff --git a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Controller.java b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Controller.java
new file mode 100644
index 000000000..a627b8365
--- /dev/null
+++ b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Controller.java
@@ -0,0 +1,35 @@
+package com.loopers.interfaces.api.user;
+
+import com.loopers.application.user.UserFacade;
+import com.loopers.application.user.UserInfo;
+import com.loopers.interfaces.api.ApiResponse;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.*;
+
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/api/v1/users")
+public class UserV1Controller implements UserV1ApiSpec {
+
+  private final UserFacade userFacade;
+
+  @PostMapping("")
+  @ResponseStatus(HttpStatus.CREATED)
+  @Override
+  public ApiResponse<UserCreateV1Dto.UserResponse> join(@RequestBody UserCreateV1Dto.UserRequest requestDto) {
+    UserInfo info = userFacade.join(requestDto);
+    UserCreateV1Dto.UserResponse response = UserCreateV1Dto.UserResponse.from(info);
+    return ApiResponse.success(response);
+  }
+
+  @GetMapping("/{userId}")
+  @Override
+  public ApiResponse<UserCreateV1Dto.UserResponse> getUser(
+      @PathVariable(value = "userId") Long userId
+  ) {
+    UserInfo info = userFacade.getUser(userId);
+    UserCreateV1Dto.UserResponse response = UserCreateV1Dto.UserResponse.from(info);
+    return ApiResponse.success(response);
+  }
+}
diff --git a/apps/commerce-api/src/test/java/com/loopers/domain/user/UserTest.java b/apps/commerce-api/src/test/java/com/loopers/domain/user/UserTest.java
new file mode 100644
index 000000000..01bfcb3e1
--- /dev/null
+++ b/apps/commerce-api/src/test/java/com/loopers/domain/user/UserTest.java
@@ -0,0 +1,13 @@
+package com.loopers.domain.user;
+import org.junit.jupiter.api.Test;
+import static org.assertj.core.api.Assertions.assertThat;
+
+class UserTest {
+  User user;
+
+  @Test
+  void joinUserTest() {
+    user = User.create("user1", "user1@test.XXX", "1999-01-01", "F");
+    assertThat(user).isNotNull();
+  }
+}
diff --git a/apps/commerce-api/src/test/java/com/loopers/interfaces/api/UserV1ApiE2ETest.java b/apps/commerce-api/src/test/java/com/loopers/interfaces/api/UserV1ApiE2ETest.java
new file mode 100644
index 000000000..f5648d24e
--- /dev/null
+++ b/apps/commerce-api/src/test/java/com/loopers/interfaces/api/UserV1ApiE2ETest.java
@@ -0,0 +1,73 @@
+package com.loopers.interfaces.api;
+
+import com.loopers.infrastructure.example.ExampleJpaRepository;
+import com.loopers.interfaces.api.user.UserCreateV1Dto;
+import com.loopers.utils.DatabaseCleanUp;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.web.client.TestRestTemplate;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+
+import static org.assertj.core.api.Assertions.*;
+
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+public class UserV1ApiE2ETest {
+
+  private final TestRestTemplate testRestTemplate;
+  private final ExampleJpaRepository exampleJpaRepository;
+  private final DatabaseCleanUp databaseCleanUp;
+
+  @Autowired
+  public UserV1ApiE2ETest(
+      TestRestTemplate testRestTemplate,
+      ExampleJpaRepository exampleJpaRepository,
+      DatabaseCleanUp databaseCleanUp
+  ) {
+    this.testRestTemplate = testRestTemplate;
+    this.exampleJpaRepository = exampleJpaRepository;
+    this.databaseCleanUp = databaseCleanUp;
+  }
+
+  @Nested
+  class Join {
+    @Test
+    void 회원가입_성공() {
+      //given
+      UserCreateV1Dto.UserRequest req = new UserCreateV1Dto.UserRequest("user1", "user1@test.XXX", "1999-01-01", "F");
+
+      //when
+      String url = "/api/v1/users";
+      ParameterizedTypeReference<ApiResponse<UserCreateV1Dto.UserResponse>> resType = new ParameterizedTypeReference<>() {
+      };
+      ResponseEntity<ApiResponse<UserCreateV1Dto.UserResponse>> res = testRestTemplate.exchange(url, HttpMethod.POST, new HttpEntity<>(req), resType);
+
+      //then
+      assertThat(res.getStatusCode()).isEqualTo(HttpStatus.CREATED);
+      assertThat(res.getBody().data().userId()).isNotNull();
+      assertThat(res.getBody().data().userId()).isEqualTo(req.userId());
+    }
+
+    @Test
+    void 회원가입_성별없음_실패() {
+      //given
+      UserCreateV1Dto.UserRequest req = new UserCreateV1Dto.UserRequest("user1", "user1@test.XXX", "1999-01-01", null);
+
+      //when
+      String url = "/api/v1/users";
+      ParameterizedTypeReference<ApiResponse<UserCreateV1Dto.UserResponse>> resType = new ParameterizedTypeReference<>() {
+      };
+      ResponseEntity<ApiResponse<UserCreateV1Dto.UserResponse>> res = testRestTemplate.exchange(url, HttpMethod.POST, new HttpEntity<>(req), resType);
+
+      //then
+      assertThat(res.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
+      assertThat(res.getBody().meta().message()).contains("성별정보가 없습니다.");
+    }
+  }
+}

From 239c95ac3973c920e1bae3d2553e035dfeb86929 Mon Sep 17 00:00:00 2001
From: sieun0322 <lce322@naver.com>
Date: Thu, 30 Oct 2025 01:16:05 +0900
Subject: [PATCH 2/5] =?UTF-8?q?feat:=20=EB=82=B4=EC=A0=95=EB=B3=B4=20?=
 =?UTF-8?q?=EC=A1=B0=ED=9A=8C=ED=95=98=EA=B8=B0=20=ED=85=8C=EC=8A=A4?=
 =?UTF-8?q?=ED=8A=B8?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../loopers/application/user/UserFacade.java  |   9 +-
 .../loopers/application/user/UserInfo.java    |   5 +-
 .../domain/user/{User.java => UserModel.java} |  25 +++--
 .../loopers/domain/user/UserRepository.java   |   6 +-
 .../com/loopers/domain/user/UserService.java  |  14 ++-
 .../user/UserJpaRepository.java               |   9 +-
 .../user/UserRepositoryImpl.java              |  14 ++-
 .../interfaces/api/user/UserCreateV1Dto.java  |   1 +
 .../interfaces/api/user/UserV1ApiSpec.java    |   2 +-
 .../interfaces/api/user/UserV1Controller.java |   2 +-
 .../loopers/domain/user/UserModelTest.java    |  43 +++++++
 .../user/UserServiceIntegrationTest.java      | 106 ++++++++++++++++++
 .../com/loopers/domain/user/UserTest.java     |  13 ---
 ...2ETest.java => UserModelV1ApiE2ETest.java} |  58 ++++++++--
 14 files changed, 254 insertions(+), 53 deletions(-)
 rename apps/commerce-api/src/main/java/com/loopers/domain/user/{User.java => UserModel.java} (56%)
 create mode 100644 apps/commerce-api/src/test/java/com/loopers/domain/user/UserModelTest.java
 create mode 100644 apps/commerce-api/src/test/java/com/loopers/domain/user/UserServiceIntegrationTest.java
 delete mode 100644 apps/commerce-api/src/test/java/com/loopers/domain/user/UserTest.java
 rename apps/commerce-api/src/test/java/com/loopers/interfaces/api/{UserV1ApiE2ETest.java => UserModelV1ApiE2ETest.java} (57%)

diff --git a/apps/commerce-api/src/main/java/com/loopers/application/user/UserFacade.java b/apps/commerce-api/src/main/java/com/loopers/application/user/UserFacade.java
index 30e3c1a69..f4e3e79c3 100644
--- a/apps/commerce-api/src/main/java/com/loopers/application/user/UserFacade.java
+++ b/apps/commerce-api/src/main/java/com/loopers/application/user/UserFacade.java
@@ -1,9 +1,8 @@
 package com.loopers.application.user;
 
-import com.loopers.domain.user.User;
+import com.loopers.domain.user.UserModel;
 import com.loopers.domain.user.UserService;
 import com.loopers.interfaces.api.user.UserCreateV1Dto;
-import lombok.Getter;
 import lombok.RequiredArgsConstructor;
 import org.springframework.stereotype.Component;
 
@@ -13,12 +12,12 @@ public class UserFacade {
   private final UserService userService;
 
   public UserInfo join(UserCreateV1Dto.UserRequest requestDto) {
-    User user = userService.join(User.create(requestDto.userId(), requestDto.email(), requestDto.birthday(), requestDto.gender()));
+    UserModel user = userService.join(UserModel.create(requestDto.userId(), requestDto.email(), requestDto.birthday(), requestDto.gender()));
     return UserInfo.from(user);
   }
 
-  public UserInfo getUser(Long id) {
-    User user = userService.getUser(id);
+  public UserInfo getUser(String userId) {
+    UserModel user = userService.getUser(userId);
     return UserInfo.from(user);
   }
 
diff --git a/apps/commerce-api/src/main/java/com/loopers/application/user/UserInfo.java b/apps/commerce-api/src/main/java/com/loopers/application/user/UserInfo.java
index 46f7d934a..13c484e04 100644
--- a/apps/commerce-api/src/main/java/com/loopers/application/user/UserInfo.java
+++ b/apps/commerce-api/src/main/java/com/loopers/application/user/UserInfo.java
@@ -1,9 +1,10 @@
 package com.loopers.application.user;
 
-import com.loopers.domain.user.User;
+import com.loopers.domain.user.UserModel;
 
 public record UserInfo(String userId, String email, String birthday, String gender) {
-  public static UserInfo from(User model) {
+  public static UserInfo from(UserModel model) {
+    if (model == null) return null;
     return new UserInfo(
         model.getUserId(),
         model.getEmail(),
diff --git a/apps/commerce-api/src/main/java/com/loopers/domain/user/User.java b/apps/commerce-api/src/main/java/com/loopers/domain/user/UserModel.java
similarity index 56%
rename from apps/commerce-api/src/main/java/com/loopers/domain/user/User.java
rename to apps/commerce-api/src/main/java/com/loopers/domain/user/UserModel.java
index 313267570..b8e3197fb 100644
--- a/apps/commerce-api/src/main/java/com/loopers/domain/user/User.java
+++ b/apps/commerce-api/src/main/java/com/loopers/domain/user/UserModel.java
@@ -9,21 +9,30 @@
 
 import java.time.LocalDate;
 import java.time.format.DateTimeParseException;
+import java.util.regex.Pattern;
 
 @Entity
-@Table(name = "User")
+@Table(name = "user")
 @Getter
-public class User extends BaseEntity {
+public class UserModel extends BaseEntity {
   private String userId;
   private String email;
   private LocalDate birthday;
   private String gender;
 
-  protected User() {
+  protected UserModel() {
   }
 
-  private User(String userId, String email, String birthday, String gender) {
-    if (email == null || !email.contains("@")) {
+  private UserModel(String userId, String email, String birthday, String gender) {
+
+    if (!Pattern.compile("^[a-zA-Z0-9]{1,10}$").matcher(userId).matches()) {
+      throw new CoreException(
+          ErrorType.BAD_REQUEST,
+          "아이디 형식이 잘못되었습니다.(영문 및 숫자 1~10자 이내)"
+      );
+    }
+    if (email == null || !email.contains("@")
+        || !Pattern.compile("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,6}$").matcher(email).matches()) {
       throw new CoreException(
           ErrorType.BAD_REQUEST,
           "이메일 형식이 잘못되었습니다."
@@ -34,7 +43,7 @@ private User(String userId, String email, String birthday, String gender) {
     } catch (DateTimeParseException e) {
       throw new CoreException(
           ErrorType.BAD_REQUEST,
-          "생년월일 형식이 유효하지 않습니다. (예: yyyy-MM-dd)"
+          "생년월일 형식이 유효하지 않습니다."
       );
     }
     if (gender == null || gender.isBlank()) {
@@ -49,8 +58,8 @@ private User(String userId, String email, String birthday, String gender) {
     this.gender = gender;
   }
 
-  public static User create(String userId, String email, String birthday, String gender) {
-    return new User(userId, email, birthday, gender);
+  public static UserModel create(String userId, String email, String birthday, String gender) {
+    return new UserModel(userId, email, birthday, gender);
   }
 
 }
diff --git a/apps/commerce-api/src/main/java/com/loopers/domain/user/UserRepository.java b/apps/commerce-api/src/main/java/com/loopers/domain/user/UserRepository.java
index 6cfeb0420..1035f1846 100644
--- a/apps/commerce-api/src/main/java/com/loopers/domain/user/UserRepository.java
+++ b/apps/commerce-api/src/main/java/com/loopers/domain/user/UserRepository.java
@@ -3,7 +3,9 @@
 import java.util.Optional;
 
 public interface UserRepository {
-  User save(User user);
+  UserModel save(UserModel user);
 
-  Optional<User> find(Long id);
+  boolean existsByUserId(String userId);
+  
+  Optional<UserModel> findByUserId(String userId);
 }
diff --git a/apps/commerce-api/src/main/java/com/loopers/domain/user/UserService.java b/apps/commerce-api/src/main/java/com/loopers/domain/user/UserService.java
index 1d59f348a..615b2035a 100644
--- a/apps/commerce-api/src/main/java/com/loopers/domain/user/UserService.java
+++ b/apps/commerce-api/src/main/java/com/loopers/domain/user/UserService.java
@@ -14,13 +14,19 @@ public class UserService {
   private final UserRepository userRepository;
 
   @Transactional
-  public User join(User user) {
+  public UserModel join(UserModel user) {
+    if (userRepository.existsByUserId(user.getUserId())) {
+      throw new CoreException(ErrorType.BAD_REQUEST, "이미 가입된 ID 입니다.");
+    }
     return userRepository.save(user);
   }
 
   @Transactional(readOnly = true)
-  public User getUser(Long id) {
-    return userRepository.find(id)
-        .orElseThrow(() -> new CoreException(ErrorType.NOT_FOUND, "[id = " + id + "] 예시를 찾을 수 없습니다."));
+  public UserModel getUser(String userId) {
+    UserModel userModel = userRepository.findByUserId(userId).orElse(null);
+    if (userModel == null) {
+      throw new CoreException(ErrorType.NOT_FOUND, "존재하지 않는 유저입니다.");
+    }
+    return userModel;
   }
 }
diff --git a/apps/commerce-api/src/main/java/com/loopers/infrastructure/user/UserJpaRepository.java b/apps/commerce-api/src/main/java/com/loopers/infrastructure/user/UserJpaRepository.java
index 30627b36a..bd867ae25 100644
--- a/apps/commerce-api/src/main/java/com/loopers/infrastructure/user/UserJpaRepository.java
+++ b/apps/commerce-api/src/main/java/com/loopers/infrastructure/user/UserJpaRepository.java
@@ -1,8 +1,11 @@
 package com.loopers.infrastructure.user;
 
-import com.loopers.domain.example.ExampleModel;
-import com.loopers.domain.user.User;
+import com.loopers.domain.user.UserModel;
 import org.springframework.data.jpa.repository.JpaRepository;
 
-public interface UserJpaRepository extends JpaRepository<User, Long> {
+import java.util.Optional;
+
+public interface UserJpaRepository extends JpaRepository<UserModel, Long> {
+  boolean existsByUserId(String userId);
+  Optional<UserModel> findByUserId(String userId);
 }
diff --git a/apps/commerce-api/src/main/java/com/loopers/infrastructure/user/UserRepositoryImpl.java b/apps/commerce-api/src/main/java/com/loopers/infrastructure/user/UserRepositoryImpl.java
index 0ddf78f1a..ca467658e 100644
--- a/apps/commerce-api/src/main/java/com/loopers/infrastructure/user/UserRepositoryImpl.java
+++ b/apps/commerce-api/src/main/java/com/loopers/infrastructure/user/UserRepositoryImpl.java
@@ -1,8 +1,6 @@
 package com.loopers.infrastructure.user;
 
-import com.loopers.domain.example.ExampleModel;
-import com.loopers.domain.example.ExampleRepository;
-import com.loopers.domain.user.User;
+import com.loopers.domain.user.UserModel;
 import com.loopers.domain.user.UserRepository;
 import lombok.RequiredArgsConstructor;
 import org.springframework.stereotype.Component;
@@ -15,12 +13,16 @@ public class UserRepositoryImpl implements UserRepository {
   private final UserJpaRepository jpaRepository;
 
   @Override
-  public User save(User user) {
+  public UserModel save(UserModel user) {
     return jpaRepository.save(user);
   }
 
   @Override
-  public Optional<User> find(Long id) {
-    return jpaRepository.findById(id);
+  public boolean existsByUserId(String userId) {
+    return jpaRepository.existsByUserId(userId);
+  }
+  @Override
+  public Optional<UserModel> findByUserId(String userId) {
+    return jpaRepository.findByUserId(userId);
   }
 }
diff --git a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserCreateV1Dto.java b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserCreateV1Dto.java
index a4b5d44a8..837984c43 100644
--- a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserCreateV1Dto.java
+++ b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserCreateV1Dto.java
@@ -10,6 +10,7 @@ public record UserRequest(String userId, String email, String birthday, String g
 
   public record UserResponse(String userId, String email, String birthday, String gender) {
     public static UserResponse from(UserInfo info) {
+      if (info == null) return null;
       return new UserResponse(
           info.userId(),
           info.email(),
diff --git a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1ApiSpec.java b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1ApiSpec.java
index 4bd4bfa35..c3b633d3f 100644
--- a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1ApiSpec.java
+++ b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1ApiSpec.java
@@ -22,6 +22,6 @@ ApiResponse<UserCreateV1Dto.UserResponse> join(
   )
   ApiResponse<UserCreateV1Dto.UserResponse> getUser(
       @Schema(name = "예시 ID", description = "조회할 예시의 ID")
-          Long exampleId
+          String userId
   );
 }
diff --git a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Controller.java b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Controller.java
index a627b8365..280bf2c19 100644
--- a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Controller.java
+++ b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Controller.java
@@ -26,7 +26,7 @@ public ApiResponse<UserCreateV1Dto.UserResponse> join(@RequestBody UserCreateV1D
   @GetMapping("/{userId}")
   @Override
   public ApiResponse<UserCreateV1Dto.UserResponse> getUser(
-      @PathVariable(value = "userId") Long userId
+      @PathVariable(value = "userId") String userId
   ) {
     UserInfo info = userFacade.getUser(userId);
     UserCreateV1Dto.UserResponse response = UserCreateV1Dto.UserResponse.from(info);
diff --git a/apps/commerce-api/src/test/java/com/loopers/domain/user/UserModelTest.java b/apps/commerce-api/src/test/java/com/loopers/domain/user/UserModelTest.java
new file mode 100644
index 000000000..0fa60696c
--- /dev/null
+++ b/apps/commerce-api/src/test/java/com/loopers/domain/user/UserModelTest.java
@@ -0,0 +1,43 @@
+package com.loopers.domain.user;
+
+import com.loopers.support.error.CoreException;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+class UserModelTest {
+  UserModel userModel;
+
+  @Nested
+  class Join {
+    @Test
+    void ID_검증_실패() {
+      assertThatThrownBy(() -> {
+        userModel = UserModel.create("12345678910", "user1@test.XXX", "1999-01-01", "F");
+      }).isInstanceOf(CoreException.class).hasMessageContaining("아이디 형식이 잘못되었습니다.(영문 및 숫자 1~10자 이내)");
+    }
+
+    @Test
+    void 이메일_검증_실패() {
+      assertThatThrownBy(() -> {
+        userModel = UserModel.create("user1", "user1test.XXX", "1999-01-01", "F");
+      }).isInstanceOf(CoreException.class).hasMessageContaining("이메일 형식이 잘못되었습니다.");
+    }
+
+    @Test
+    void 생년월일_검증_실패() {
+      assertThatThrownBy(() -> {
+        userModel = UserModel.create("user1", "user1@test.XXX", "19990101", "F");
+      }).isInstanceOf(CoreException.class).hasMessageContaining("생년월일 형식이 유효하지 않습니다.");
+    }
+
+    @Test
+    void User_객체생성_성공() {
+      userModel = UserModel.create("user1", "user1@test.XXX", "1999-01-01", "F");
+      assertThat(userModel).isNotNull();
+      assertThat(userModel.getUserId()).isEqualTo("user1");
+    }
+  }
+}
diff --git a/apps/commerce-api/src/test/java/com/loopers/domain/user/UserServiceIntegrationTest.java b/apps/commerce-api/src/test/java/com/loopers/domain/user/UserServiceIntegrationTest.java
new file mode 100644
index 000000000..67d455105
--- /dev/null
+++ b/apps/commerce-api/src/test/java/com/loopers/domain/user/UserServiceIntegrationTest.java
@@ -0,0 +1,106 @@
+package com.loopers.domain.user;
+
+import com.loopers.infrastructure.user.UserJpaRepository;
+import com.loopers.support.error.CoreException;
+import com.loopers.utils.DatabaseCleanUp;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.Spy;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.jupiter.api.Assertions.assertAll;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.*;
+
+@SpringBootTest
+class UserServiceIntegrationTest {
+  @Autowired
+  private UserService userService;
+
+  @Spy
+  private UserJpaRepository userJpaRepository;
+
+  @Autowired
+  private DatabaseCleanUp databaseCleanUp;
+
+  @AfterEach
+  void tearDown() {
+    databaseCleanUp.truncateAllTables();
+  }
+
+  @DisplayName("회원가입을 할 때,")
+  @Nested
+  class Join {
+    @DisplayName("회원 가입시 User 저장이 수행된다. ( spy 검증 )")
+    @Test
+    void returnsUserInfo_whenValidIdIsProvided() {
+      // arrange
+      UserModel userModel = UserModel.create("user1", "user1@test.XXX", "1999-01-01", "F");
+
+      // act
+      userService.join(userModel);
+
+      // assert
+      assertAll(
+          () -> verify(userJpaRepository, times(1)).save(userModel)
+      );
+    }
+
+    @DisplayName("이미 가입된 ID 로 회원가입 시도 시, 실패한다.")
+    @Test
+    void throwsException_whenInvalidIdIsProvided() {
+      // arrange
+      UserModel userModel = UserModel.create("user1", "user1@test.XXX", "1999-01-01", "F");
+      userService.join(userModel);
+
+      // act
+      //verify(userJpaRepository, times(1)).existsByUserId(userModel.getUserId());
+      assertThatThrownBy(() -> {
+        userService.join(userModel);
+      }).isInstanceOf(CoreException.class).hasMessageContaining("이미 가입된 ID 입니다.");
+
+    }
+  }
+
+  @DisplayName("내 정보 조회할 때,")
+  @Nested
+  class Get {
+    @DisplayName("해당 ID 의 회원이 존재할 경우, 회원 정보가 반환된다.")
+    @Test
+    void returnsUserInfo_whenValidIdIsProvided() {
+      // arrange
+      UserModel userModel = UserModel.create("user1", "user1@test.XXX", "1999-01-01", "F");
+      userService.join(userModel);
+
+      // act
+      UserModel result = userService.getUser(userModel.getUserId());
+
+      // assert
+      assertAll(
+          () -> assertThat(result).isNotNull(),
+          () -> assertThat(result.getUserId()).isEqualTo(userModel.getUserId()),
+          () -> assertThat(result.getEmail()).isEqualTo(userModel.getEmail()),
+          () -> assertThat(result.getBirthday()).isEqualTo(userModel.getBirthday()),
+          () -> assertThat(result.getGender()).isEqualTo(userModel.getGender())
+      );
+    }
+
+    @DisplayName("해당 ID 의 회원이 존재하지 않을 경우, null 이 반환된다.")
+    @Test
+    void throwsException_whenInvalidIdIsProvided() {
+      // arrange
+      UserModel userModel = UserModel.create("user1", "user1@test.XXX", "1999-01-01", "F");
+
+      // act
+      UserModel result = userService.getUser(userModel.getUserId());
+
+      // assert
+      assertThat(result).isNull();
+    }
+  }
+}
diff --git a/apps/commerce-api/src/test/java/com/loopers/domain/user/UserTest.java b/apps/commerce-api/src/test/java/com/loopers/domain/user/UserTest.java
deleted file mode 100644
index 01bfcb3e1..000000000
--- a/apps/commerce-api/src/test/java/com/loopers/domain/user/UserTest.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.loopers.domain.user;
-import org.junit.jupiter.api.Test;
-import static org.assertj.core.api.Assertions.assertThat;
-
-class UserTest {
-  User user;
-
-  @Test
-  void joinUserTest() {
-    user = User.create("user1", "user1@test.XXX", "1999-01-01", "F");
-    assertThat(user).isNotNull();
-  }
-}
diff --git a/apps/commerce-api/src/test/java/com/loopers/interfaces/api/UserV1ApiE2ETest.java b/apps/commerce-api/src/test/java/com/loopers/interfaces/api/UserModelV1ApiE2ETest.java
similarity index 57%
rename from apps/commerce-api/src/test/java/com/loopers/interfaces/api/UserV1ApiE2ETest.java
rename to apps/commerce-api/src/test/java/com/loopers/interfaces/api/UserModelV1ApiE2ETest.java
index f5648d24e..13ffcb41c 100644
--- a/apps/commerce-api/src/test/java/com/loopers/interfaces/api/UserV1ApiE2ETest.java
+++ b/apps/commerce-api/src/test/java/com/loopers/interfaces/api/UserModelV1ApiE2ETest.java
@@ -1,9 +1,10 @@
 package com.loopers.interfaces.api;
 
-import com.loopers.infrastructure.example.ExampleJpaRepository;
+import com.loopers.domain.user.UserModel;
+import com.loopers.infrastructure.user.UserJpaRepository;
 import com.loopers.interfaces.api.user.UserCreateV1Dto;
 import com.loopers.utils.DatabaseCleanUp;
-import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -15,26 +16,31 @@
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
 
-import static org.assertj.core.api.Assertions.*;
+import static org.assertj.core.api.Assertions.assertThat;
 
 @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
-public class UserV1ApiE2ETest {
+public class UserModelV1ApiE2ETest {
 
   private final TestRestTemplate testRestTemplate;
-  private final ExampleJpaRepository exampleJpaRepository;
+  private final UserJpaRepository userJpaRepository;
   private final DatabaseCleanUp databaseCleanUp;
 
   @Autowired
-  public UserV1ApiE2ETest(
+  public UserModelV1ApiE2ETest(
       TestRestTemplate testRestTemplate,
-      ExampleJpaRepository exampleJpaRepository,
+      UserJpaRepository userJpaRepository,
       DatabaseCleanUp databaseCleanUp
   ) {
     this.testRestTemplate = testRestTemplate;
-    this.exampleJpaRepository = exampleJpaRepository;
+    this.userJpaRepository = userJpaRepository;
     this.databaseCleanUp = databaseCleanUp;
   }
 
+  @AfterEach
+  void tearDown() {
+    databaseCleanUp.truncateAllTables();
+  }
+
   @Nested
   class Join {
     @Test
@@ -70,4 +76,40 @@ class Join {
       assertThat(res.getBody().meta().message()).contains("성별정보가 없습니다.");
     }
   }
+
+  @Nested
+  class Get {
+    @Test
+    void 존재하지_않는_ID_실패() {
+      //given
+      String userId = "user1";
+
+      //when
+      String url = "/api/v1/users/" + userId;
+      ParameterizedTypeReference<ApiResponse<UserCreateV1Dto.UserResponse>> resType = new ParameterizedTypeReference<>() {
+      };
+      ResponseEntity<ApiResponse<UserCreateV1Dto.UserResponse>> res = testRestTemplate.exchange(url, HttpMethod.GET, new HttpEntity<>(null), resType);
+
+      //then
+      assertThat(res.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
+    }
+
+    @Test
+    void 내정보조회_성공() {
+      //given
+      UserModel userModel = UserModel.create("user1", "user1@test.XXX", "1999-01-01", "F");
+      userJpaRepository.save(userModel);
+
+      //when
+      String url = "/api/v1/users/" + userModel.getUserId();
+      ParameterizedTypeReference<ApiResponse<UserCreateV1Dto.UserResponse>> resType = new ParameterizedTypeReference<>() {
+      };
+      ResponseEntity<ApiResponse<UserCreateV1Dto.UserResponse>> res = testRestTemplate.exchange(url, HttpMethod.GET, new HttpEntity<>(null), resType);
+
+      //then
+      assertThat(res.getStatusCode()).isEqualTo(HttpStatus.OK);
+      assertThat(res.getBody().data().userId()).isNotNull();
+      assertThat(res.getBody().data().userId()).isEqualTo(userModel.getUserId());
+    }
+  }
 }

From 9d31c8ebac5e16731218e3a789b6f6aa24b3ba14 Mon Sep 17 00:00:00 2001
From: sieun0322 <lce322@naver.com>
Date: Fri, 31 Oct 2025 03:59:05 +0900
Subject: [PATCH 3/5] =?UTF-8?q?feat:=20=ED=8F=AC=EC=9D=B8=ED=8A=B8=20?=
 =?UTF-8?q?=EC=A1=B0=ED=9A=8C=ED=95=98=EA=B8=B0=20=ED=85=8C=EC=8A=A4?=
 =?UTF-8?q?=ED=8A=B8?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../application/point/PointFacade.java        | 24 ++++++
 .../loopers/application/point/PointInfo.java  | 15 ++++
 .../loopers/application/user/UserInfo.java    |  4 +-
 .../com/loopers/domain/point/PointModel.java  | 61 ++++++++++++++
 .../loopers/domain/point/PointRepository.java | 16 ++++
 .../loopers/domain/point/PointService.java    | 25 ++++++
 .../com/loopers/domain/user/UserModel.java    | 18 +++-
 .../com/loopers/domain/user/UserService.java  | 10 ++-
 .../point/PointJpaRepository.java             | 16 ++++
 .../point/PointRepositoryImpl.java            | 30 +++++++
 .../api/point/PointCreateV1Dto.java           | 20 +++++
 .../interfaces/api/point/PointV1ApiSpec.java  | 25 ++++++
 .../api/point/PointV1Controller.java          | 29 +++++++
 .../interfaces/api/user/UserV1ApiSpec.java    |  8 +-
 .../interfaces/api/user/UserV1Controller.java |  2 +-
 .../loopers/domain/point/PointModelTest.java  | 75 +++++++++++++++++
 .../point/PointServiceIntegrationTest.java    | 75 +++++++++++++++++
 .../loopers/domain/user/UserModelTest.java    | 80 +++++++++++++++---
 .../user/UserServiceIntegrationTest.java      | 26 +++---
 .../interfaces/api/PointV1ApiE2ETest.java     | 82 +++++++++++++++++++
 ...1ApiE2ETest.java => UserV1ApiE2ETest.java} | 38 +++++----
 21 files changed, 624 insertions(+), 55 deletions(-)
 create mode 100644 apps/commerce-api/src/main/java/com/loopers/application/point/PointFacade.java
 create mode 100644 apps/commerce-api/src/main/java/com/loopers/application/point/PointInfo.java
 create mode 100644 apps/commerce-api/src/main/java/com/loopers/domain/point/PointModel.java
 create mode 100644 apps/commerce-api/src/main/java/com/loopers/domain/point/PointRepository.java
 create mode 100644 apps/commerce-api/src/main/java/com/loopers/domain/point/PointService.java
 create mode 100644 apps/commerce-api/src/main/java/com/loopers/infrastructure/point/PointJpaRepository.java
 create mode 100644 apps/commerce-api/src/main/java/com/loopers/infrastructure/point/PointRepositoryImpl.java
 create mode 100644 apps/commerce-api/src/main/java/com/loopers/interfaces/api/point/PointCreateV1Dto.java
 create mode 100644 apps/commerce-api/src/main/java/com/loopers/interfaces/api/point/PointV1ApiSpec.java
 create mode 100644 apps/commerce-api/src/main/java/com/loopers/interfaces/api/point/PointV1Controller.java
 create mode 100644 apps/commerce-api/src/test/java/com/loopers/domain/point/PointModelTest.java
 create mode 100644 apps/commerce-api/src/test/java/com/loopers/domain/point/PointServiceIntegrationTest.java
 create mode 100644 apps/commerce-api/src/test/java/com/loopers/interfaces/api/PointV1ApiE2ETest.java
 rename apps/commerce-api/src/test/java/com/loopers/interfaces/api/{UserModelV1ApiE2ETest.java => UserV1ApiE2ETest.java} (79%)

diff --git a/apps/commerce-api/src/main/java/com/loopers/application/point/PointFacade.java b/apps/commerce-api/src/main/java/com/loopers/application/point/PointFacade.java
new file mode 100644
index 000000000..36aa6d92d
--- /dev/null
+++ b/apps/commerce-api/src/main/java/com/loopers/application/point/PointFacade.java
@@ -0,0 +1,24 @@
+package com.loopers.application.point;
+
+import com.loopers.domain.point.PointModel;
+import com.loopers.domain.point.PointService;
+import com.loopers.domain.user.UserModel;
+import com.loopers.domain.user.UserService;
+import com.loopers.interfaces.api.point.PointCreateV1Dto;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Component;
+
+import java.math.BigDecimal;
+
+@RequiredArgsConstructor
+@Component
+public class PointFacade {
+  private final UserService userService;
+  private final PointService pointService;
+  
+  public BigDecimal getPoint(String userId) {
+    UserModel user = userService.getUser(userId);
+    return pointService.getAmount(userId);
+  }
+
+}
diff --git a/apps/commerce-api/src/main/java/com/loopers/application/point/PointInfo.java b/apps/commerce-api/src/main/java/com/loopers/application/point/PointInfo.java
new file mode 100644
index 000000000..ff51218d9
--- /dev/null
+++ b/apps/commerce-api/src/main/java/com/loopers/application/point/PointInfo.java
@@ -0,0 +1,15 @@
+package com.loopers.application.point;
+
+import com.loopers.domain.point.PointModel;
+
+import java.math.BigDecimal;
+
+public record PointInfo(String userId, BigDecimal amount) {
+  public static PointInfo from(PointModel model) {
+    if (model == null) return null;
+    return new PointInfo(
+        model.getUser().getUserId(),
+        model.getAmount()
+    );
+  }
+}
diff --git a/apps/commerce-api/src/main/java/com/loopers/application/user/UserInfo.java b/apps/commerce-api/src/main/java/com/loopers/application/user/UserInfo.java
index 13c484e04..df777bb86 100644
--- a/apps/commerce-api/src/main/java/com/loopers/application/user/UserInfo.java
+++ b/apps/commerce-api/src/main/java/com/loopers/application/user/UserInfo.java
@@ -1,10 +1,12 @@
 package com.loopers.application.user;
 
 import com.loopers.domain.user.UserModel;
+import com.loopers.support.error.CoreException;
+import com.loopers.support.error.ErrorType;
 
 public record UserInfo(String userId, String email, String birthday, String gender) {
   public static UserInfo from(UserModel model) {
-    if (model == null) return null;
+    if (model == null) throw new CoreException(ErrorType.NOT_FOUND, "유저정보를 찾을수 없습니다.");
     return new UserInfo(
         model.getUserId(),
         model.getEmail(),
diff --git a/apps/commerce-api/src/main/java/com/loopers/domain/point/PointModel.java b/apps/commerce-api/src/main/java/com/loopers/domain/point/PointModel.java
new file mode 100644
index 000000000..a8c544c55
--- /dev/null
+++ b/apps/commerce-api/src/main/java/com/loopers/domain/point/PointModel.java
@@ -0,0 +1,61 @@
+package com.loopers.domain.point;
+
+import com.loopers.domain.BaseEntity;
+import com.loopers.domain.user.UserModel;
+import com.loopers.support.error.CoreException;
+import com.loopers.support.error.ErrorType;
+import jakarta.persistence.*;
+import lombok.Getter;
+
+import java.math.BigDecimal;
+import java.util.Objects;
+
+@Entity
+@Table(name = "point")
+@Getter
+public class PointModel extends BaseEntity {
+  private BigDecimal amount;
+
+  @OneToOne
+  @JoinColumn(name = "userId", unique = true, nullable = false)
+  private UserModel user;
+
+  public void setUser(UserModel user) {
+    this.user = user;
+    if (user.getPoint() != this) {
+      user.setPoint(this);
+    }
+  }
+
+  protected PointModel() {
+    this.amount = BigDecimal.ZERO;
+  }
+
+  private PointModel(UserModel user, BigDecimal amount) {
+    Objects.requireNonNull(user, "UserModel must not be null for PointModel creation.");
+
+    this.setUser(user);
+    this.amount = amount;
+  }
+
+  public static PointModel create(UserModel user, BigDecimal amount) {
+    return new PointModel(user, amount);
+  }
+
+  public PointModel charge(BigDecimal amountToChange) {
+    if (amountToChange == null || amountToChange.compareTo(BigDecimal.ZERO) <= 0) {
+      throw new CoreException(ErrorType.BAD_REQUEST, "충전 금액은 0보다 커야 합니다.");
+    }
+    return new PointModel(this.user, this.amount.add(amountToChange));
+  }
+
+  public PointModel use(BigDecimal amountToChange) {
+    if (amountToChange == null || amountToChange.compareTo(BigDecimal.ZERO) <= 0) {
+      throw new CoreException(ErrorType.BAD_REQUEST, "차감 금액은 0보다 커야 합니다.");
+    }
+    if (this.amount.compareTo(amountToChange) < 0) {
+      throw new CoreException(ErrorType.BAD_REQUEST, "잔액이 부족합니다.");
+    }
+    return new PointModel(this.user, this.amount.subtract(amountToChange));
+  }
+}
diff --git a/apps/commerce-api/src/main/java/com/loopers/domain/point/PointRepository.java b/apps/commerce-api/src/main/java/com/loopers/domain/point/PointRepository.java
new file mode 100644
index 000000000..2e2259647
--- /dev/null
+++ b/apps/commerce-api/src/main/java/com/loopers/domain/point/PointRepository.java
@@ -0,0 +1,16 @@
+package com.loopers.domain.point;
+
+import jakarta.persistence.LockModeType;
+import org.springframework.data.jpa.repository.Lock;
+
+import java.util.Optional;
+
+public interface PointRepository {
+  PointModel save(PointModel user);
+
+  Optional<PointModel> findByUserId(String userId);
+
+  @Lock(LockModeType.PESSIMISTIC_WRITE)
+  Optional<PointModel> findByUserIdForUpdate(String userId);
+
+}
diff --git a/apps/commerce-api/src/main/java/com/loopers/domain/point/PointService.java b/apps/commerce-api/src/main/java/com/loopers/domain/point/PointService.java
new file mode 100644
index 000000000..d10532c83
--- /dev/null
+++ b/apps/commerce-api/src/main/java/com/loopers/domain/point/PointService.java
@@ -0,0 +1,25 @@
+
+package com.loopers.domain.point;
+
+import com.loopers.support.error.CoreException;
+import com.loopers.support.error.ErrorType;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.awt.*;
+import java.math.BigDecimal;
+
+@RequiredArgsConstructor
+@Component
+public class PointService {
+
+  private final PointRepository pointRepository;
+
+  @Transactional(readOnly = true)
+  public BigDecimal getAmount(String userId) {
+    return pointRepository.findByUserId(userId)
+        .map(PointModel::getAmount)
+        .orElse(null);
+  }
+}
diff --git a/apps/commerce-api/src/main/java/com/loopers/domain/user/UserModel.java b/apps/commerce-api/src/main/java/com/loopers/domain/user/UserModel.java
index b8e3197fb..500eae5ef 100644
--- a/apps/commerce-api/src/main/java/com/loopers/domain/user/UserModel.java
+++ b/apps/commerce-api/src/main/java/com/loopers/domain/user/UserModel.java
@@ -1,12 +1,13 @@
 package com.loopers.domain.user;
 
 import com.loopers.domain.BaseEntity;
+import com.loopers.domain.point.PointModel;
 import com.loopers.support.error.CoreException;
 import com.loopers.support.error.ErrorType;
-import jakarta.persistence.Entity;
-import jakarta.persistence.Table;
+import jakarta.persistence.*;
 import lombok.Getter;
 
+import java.math.BigDecimal;
 import java.time.LocalDate;
 import java.time.format.DateTimeParseException;
 import java.util.regex.Pattern;
@@ -20,12 +21,22 @@ public class UserModel extends BaseEntity {
   private LocalDate birthday;
   private String gender;
 
+  @OneToOne(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = false)
+  private PointModel point;
+
+  public void setPoint(PointModel point) {
+    this.point = point;
+    if (point.getUser() != this) {
+      point.setUser(this);
+    }
+  }
+
   protected UserModel() {
   }
 
   private UserModel(String userId, String email, String birthday, String gender) {
 
-    if (!Pattern.compile("^[a-zA-Z0-9]{1,10}$").matcher(userId).matches()) {
+    if (!Pattern.compile("^(?=.*[a-zA-Z])(?=.*[0-9])[a-zA-Z0-9]{1,10}$").matcher(userId).matches()) {
       throw new CoreException(
           ErrorType.BAD_REQUEST,
           "아이디 형식이 잘못되었습니다.(영문 및 숫자 1~10자 이내)"
@@ -56,6 +67,7 @@ private UserModel(String userId, String email, String birthday, String gender) {
     this.email = email;
     this.birthday = LocalDate.parse(birthday);
     this.gender = gender;
+    this.point = PointModel.create(this, BigDecimal.TEN);
   }
 
   public static UserModel create(String userId, String email, String birthday, String gender) {
diff --git a/apps/commerce-api/src/main/java/com/loopers/domain/user/UserService.java b/apps/commerce-api/src/main/java/com/loopers/domain/user/UserService.java
index 615b2035a..dbb459db6 100644
--- a/apps/commerce-api/src/main/java/com/loopers/domain/user/UserService.java
+++ b/apps/commerce-api/src/main/java/com/loopers/domain/user/UserService.java
@@ -7,6 +7,8 @@
 import org.springframework.stereotype.Component;
 import org.springframework.transaction.annotation.Transactional;
 
+import java.util.Optional;
+
 @RequiredArgsConstructor
 @Component
 public class UserService {
@@ -23,10 +25,10 @@ public UserModel join(UserModel user) {
 
   @Transactional(readOnly = true)
   public UserModel getUser(String userId) {
-    UserModel userModel = userRepository.findByUserId(userId).orElse(null);
-    if (userModel == null) {
-      throw new CoreException(ErrorType.NOT_FOUND, "존재하지 않는 유저입니다.");
+    //Ask: userId도 UserModel 안에 넣어서 왔으면, userId Model에서 검증 가능
+    if (userId == null || userId.isBlank()) {
+      throw new CoreException(ErrorType.BAD_REQUEST, "ID가 없습니다.");
     }
-    return userModel;
+    return userRepository.findByUserId(userId).orElse(null);
   }
 }
diff --git a/apps/commerce-api/src/main/java/com/loopers/infrastructure/point/PointJpaRepository.java b/apps/commerce-api/src/main/java/com/loopers/infrastructure/point/PointJpaRepository.java
new file mode 100644
index 000000000..6b59de1fd
--- /dev/null
+++ b/apps/commerce-api/src/main/java/com/loopers/infrastructure/point/PointJpaRepository.java
@@ -0,0 +1,16 @@
+package com.loopers.infrastructure.point;
+
+import com.loopers.domain.point.PointModel;
+import com.loopers.domain.user.UserModel;
+import jakarta.persistence.LockModeType;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Lock;
+
+import java.awt.*;
+import java.util.Optional;
+
+public interface PointJpaRepository extends JpaRepository<PointModel, Long> {
+  boolean existsByUserUserId(String userId);
+
+  Optional<PointModel> findByUserUserId(String userId);
+}
diff --git a/apps/commerce-api/src/main/java/com/loopers/infrastructure/point/PointRepositoryImpl.java b/apps/commerce-api/src/main/java/com/loopers/infrastructure/point/PointRepositoryImpl.java
new file mode 100644
index 000000000..f370715f0
--- /dev/null
+++ b/apps/commerce-api/src/main/java/com/loopers/infrastructure/point/PointRepositoryImpl.java
@@ -0,0 +1,30 @@
+package com.loopers.infrastructure.point;
+
+import com.loopers.domain.point.PointModel;
+import com.loopers.domain.point.PointRepository;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Component;
+
+import java.util.Optional;
+
+@RequiredArgsConstructor
+@Component
+public class PointRepositoryImpl implements PointRepository {
+  private final PointJpaRepository jpaRepository;
+
+  @Override
+  public PointModel save(PointModel point) {
+    return jpaRepository.save(point);
+  }
+
+  @Override
+  public Optional<PointModel> findByUserId(String userId) {
+    return jpaRepository.findByUserUserId(userId);
+  }
+
+  @Override
+  public Optional<PointModel> findByUserIdForUpdate(String userId) {
+    return jpaRepository.findByUserUserId(userId);
+  }
+
+}
diff --git a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/point/PointCreateV1Dto.java b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/point/PointCreateV1Dto.java
new file mode 100644
index 000000000..fd82b8ec4
--- /dev/null
+++ b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/point/PointCreateV1Dto.java
@@ -0,0 +1,20 @@
+package com.loopers.interfaces.api.point;
+
+import com.loopers.application.point.PointInfo;
+
+import java.math.BigDecimal;
+
+public class PointCreateV1Dto {
+  public record PointRequest(String userId, BigDecimal amount) {
+  }
+
+  public record PointResponse(String userId, BigDecimal amount) {
+    public static PointResponse from(PointInfo info) {
+      if (info == null) return null;
+      return new PointResponse(
+          info.userId(),
+          info.amount()
+      );
+    }
+  }
+}
diff --git a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/point/PointV1ApiSpec.java b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/point/PointV1ApiSpec.java
new file mode 100644
index 000000000..9f252e3b0
--- /dev/null
+++ b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/point/PointV1ApiSpec.java
@@ -0,0 +1,25 @@
+package com.loopers.interfaces.api.point;
+
+import com.loopers.interfaces.api.ApiResponse;
+import com.loopers.interfaces.api.user.UserCreateV1Dto;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotBlank;
+import org.springframework.web.bind.annotation.RequestHeader;
+
+import java.math.BigDecimal;
+
+@Tag(name = "Point V1 API", description = "Loopers 예시 API 입니다.")
+public interface PointV1ApiSpec {
+  @Operation(
+      summary = "예시 조회",
+      description = "ID로 예시를 조회합니다."
+  )
+  @Valid
+  ApiResponse<BigDecimal> getPoint(
+      @Schema(name = "예시 ID", description = "조회할 예시의 ID")
+      @RequestHeader(value = "X-USER-ID", required = false) String userId
+  );
+}
diff --git a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/point/PointV1Controller.java b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/point/PointV1Controller.java
new file mode 100644
index 000000000..5128ab500
--- /dev/null
+++ b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/point/PointV1Controller.java
@@ -0,0 +1,29 @@
+package com.loopers.interfaces.api.point;
+
+import com.loopers.application.point.PointFacade;
+import com.loopers.application.point.PointFacade;
+import com.loopers.application.point.PointInfo;
+import com.loopers.interfaces.api.ApiResponse;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.*;
+
+import java.math.BigDecimal;
+
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/api/v1/user/point")
+public class PointV1Controller implements PointV1ApiSpec {
+
+  private final PointFacade pointFacade;
+
+  @GetMapping("")
+  @Override
+  public ApiResponse<BigDecimal> getPoint(@RequestHeader(value = "X-USER-ID", required = false) String userId
+  ) {
+    return ApiResponse.success(pointFacade.getPoint(userId));
+  }
+}
diff --git a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1ApiSpec.java b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1ApiSpec.java
index c3b633d3f..914849a4f 100644
--- a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1ApiSpec.java
+++ b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1ApiSpec.java
@@ -12,16 +12,16 @@ public interface UserV1ApiSpec {
       description = "유저 회원가입 합니다."
   )
   ApiResponse<UserCreateV1Dto.UserResponse> join(
-      @Schema(name = "예시 ID", description = "조회할 예시의 ID")
+      @Schema(name = "유저 ID", description = "조회할 유저의 ID")
           UserCreateV1Dto.UserRequest userRequest
   );
 
   @Operation(
-      summary = "예시 조회",
-      description = "ID로 예시를 조회합니다."
+      summary = "유저 조회",
+      description = "ID로 유저를 조회합니다."
   )
   ApiResponse<UserCreateV1Dto.UserResponse> getUser(
-      @Schema(name = "예시 ID", description = "조회할 예시의 ID")
+      @Schema(name = "유저 ID", description = "조회할 유저ID")
           String userId
   );
 }
diff --git a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Controller.java b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Controller.java
index 280bf2c19..b511e79cd 100644
--- a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Controller.java
+++ b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Controller.java
@@ -9,7 +9,7 @@
 
 @RequiredArgsConstructor
 @RestController
-@RequestMapping("/api/v1/users")
+@RequestMapping("/api/v1/user")
 public class UserV1Controller implements UserV1ApiSpec {
 
   private final UserFacade userFacade;
diff --git a/apps/commerce-api/src/test/java/com/loopers/domain/point/PointModelTest.java b/apps/commerce-api/src/test/java/com/loopers/domain/point/PointModelTest.java
new file mode 100644
index 000000000..7d227dd94
--- /dev/null
+++ b/apps/commerce-api/src/test/java/com/loopers/domain/point/PointModelTest.java
@@ -0,0 +1,75 @@
+package com.loopers.domain.point;
+
+import com.loopers.domain.user.UserModel;
+import com.loopers.support.error.CoreException;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+import java.math.BigDecimal;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+class PointModelTest {
+  UserModel userModel;
+  PointModel pointModel;
+
+  @BeforeEach
+  void setup() {
+    userModel = UserModel.create("user1", "user1@test.XXX", "1999-01-01", "F");
+  }
+
+  @Nested
+  class Use {
+
+    @Test
+    void 실패_포인트사용() {
+      // arrange
+      pointModel = PointModel.create(userModel, BigDecimal.ZERO);
+
+      // act, assert
+      assertThatThrownBy(() -> {
+        pointModel.use(BigDecimal.TEN);
+      }).isInstanceOf(CoreException.class).hasMessageContaining("잔액이 부족합니다.");
+    }
+
+    @Test
+    void 성공_포인트사용() {
+      // arrange
+      pointModel = PointModel.create(userModel, new BigDecimal(20));
+
+      // act
+      PointModel usedPointModel = pointModel.use(new BigDecimal(5));
+
+      // assert
+      assertThat(usedPointModel.getAmount()).isEqualTo(new BigDecimal(15));
+    }
+  }
+
+  @Nested
+  class Charge {
+    @Test
+    void 실패_포인트충전() {
+      // arrange
+      pointModel = PointModel.create(userModel, new BigDecimal(20));
+
+      // act, assert
+      assertThatThrownBy(() -> {
+        pointModel.charge(BigDecimal.ZERO);
+      }).isInstanceOf(CoreException.class).hasMessageContaining("충전 금액은 0보다 커야 합니다.");
+    }
+
+    @Test
+    void 성공_포인트충전() {
+      // arrange
+      pointModel = PointModel.create(userModel, new BigDecimal(20));
+
+      // act
+      PointModel chargedPointModel = pointModel.charge(new BigDecimal(5));
+
+      // assert
+      assertThat(chargedPointModel.getAmount()).isEqualTo(new BigDecimal(25));
+    }
+  }
+}
diff --git a/apps/commerce-api/src/test/java/com/loopers/domain/point/PointServiceIntegrationTest.java b/apps/commerce-api/src/test/java/com/loopers/domain/point/PointServiceIntegrationTest.java
new file mode 100644
index 000000000..8a95b70f1
--- /dev/null
+++ b/apps/commerce-api/src/test/java/com/loopers/domain/point/PointServiceIntegrationTest.java
@@ -0,0 +1,75 @@
+package com.loopers.domain.point;
+
+import com.loopers.domain.user.UserModel;
+import com.loopers.infrastructure.point.PointJpaRepository;
+import com.loopers.infrastructure.user.UserJpaRepository;
+import com.loopers.utils.DatabaseCleanUp;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.bean.override.mockito.MockitoSpyBean;
+
+import java.math.BigDecimal;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertAll;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+
+@SpringBootTest
+class PointServiceIntegrationTest {
+  @Autowired
+  private PointService pointService;
+
+  @Autowired
+  private UserJpaRepository userJpaRepository;
+
+  @MockitoSpyBean
+  private PointJpaRepository pointJpaRepository;
+
+  @Autowired
+  private DatabaseCleanUp databaseCleanUp;
+
+  @AfterEach
+  void tearDown() {
+    databaseCleanUp.truncateAllTables();
+  }
+
+  @DisplayName("포인트 조회할 때")
+  @Nested
+  class Get {
+    @DisplayName("해당 ID 의 회원이 존재할 경우, 보유 포인트가 반환")
+    @Test
+    void 성공_존재하는_유저ID() {
+      // arrange
+      BigDecimal JOIN_POINT = BigDecimal.TEN;
+      UserModel userModel = UserModel.create("user1", "user1@test.XXX", "1999-01-01", "F");
+      userJpaRepository.save(userModel);
+
+      // act
+      BigDecimal pointAmt = pointService.getAmount(userModel.getUserId());
+
+      // assert(회원가입시, 기본포인트 10)
+      assertAll(
+          () -> assertThat(pointAmt).isNotNull(),
+          () -> assertEquals(0, pointAmt.compareTo(JOIN_POINT))
+      );
+    }
+
+    @DisplayName("해당 ID 의 회원이 존재하지 않을 경우, null 이 반환된다.")
+    @Test
+    void 성공_존재하지않는_유저ID() {
+      // arrange (등록된 회원 없음)
+      String userId = "userId";
+
+      // act
+      BigDecimal pointAmt = pointService.getAmount(userId);
+
+      // assert
+      assertThat(pointAmt).isNull();
+    }
+  }
+}
diff --git a/apps/commerce-api/src/test/java/com/loopers/domain/user/UserModelTest.java b/apps/commerce-api/src/test/java/com/loopers/domain/user/UserModelTest.java
index 0fa60696c..fb1cf7de4 100644
--- a/apps/commerce-api/src/test/java/com/loopers/domain/user/UserModelTest.java
+++ b/apps/commerce-api/src/test/java/com/loopers/domain/user/UserModelTest.java
@@ -1,6 +1,7 @@
 package com.loopers.domain.user;
 
 import com.loopers.support.error.CoreException;
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 
@@ -9,35 +10,92 @@
 
 class UserModelTest {
   UserModel userModel;
+  String validMsg = "";
 
   @Nested
-  class Join {
+  class Valid_ID {
+    @BeforeEach
+    void setup() {
+      validMsg = "아이디 형식이 잘못되었습니다.(영문 및 숫자 1~10자 이내)";
+    }
+
+    @Test
+    void 실패_ID_숫자만() {
+      assertThatThrownBy(() -> {
+        userModel = UserModel.create("12345", "user1@test.XXX", "1999-01-01", "F");
+      }).isInstanceOf(CoreException.class).hasMessageContaining(validMsg);
+    }
+
     @Test
-    void ID_검증_실패() {
+    void 실패_ID_문자만() {
+      assertThatThrownBy(() -> {
+        userModel = UserModel.create("hello", "user1@test.XXX", "1999-01-01", "F");
+      }).isInstanceOf(CoreException.class).hasMessageContaining(validMsg);
+    }
+
+    @Test
+    void 실패_ID_특수문자() {
+      assertThatThrownBy(() -> {
+        userModel = UserModel.create("12345!", "user1@test.XXX", "1999-01-01", "F");
+      }).isInstanceOf(CoreException.class).hasMessageContaining(validMsg);
+    }
+
+    @Test
+    void 실패_ID_10자이상오류() {
       assertThatThrownBy(() -> {
         userModel = UserModel.create("12345678910", "user1@test.XXX", "1999-01-01", "F");
-      }).isInstanceOf(CoreException.class).hasMessageContaining("아이디 형식이 잘못되었습니다.(영문 및 숫자 1~10자 이내)");
+      }).isInstanceOf(CoreException.class).hasMessageContaining(validMsg);
+    }
+  }
+
+  @Nested
+  class Valid_Email {
+    @BeforeEach
+    void setup() {
+      validMsg = "이메일 형식이 잘못되었습니다.";
     }
 
     @Test
-    void 이메일_검증_실패() {
+    void 실패_이메일_기호없음() {
       assertThatThrownBy(() -> {
         userModel = UserModel.create("user1", "user1test.XXX", "1999-01-01", "F");
-      }).isInstanceOf(CoreException.class).hasMessageContaining("이메일 형식이 잘못되었습니다.");
+      }).isInstanceOf(CoreException.class).hasMessageContaining(validMsg);
+    }
+
+    @Test
+    void 실패_이메일_한글포함() {
+      assertThatThrownBy(() -> {
+        userModel = UserModel.create("user1", "ㄱuser1@test.XXX", "1999-01-01", "F");
+      }).isInstanceOf(CoreException.class).hasMessageContaining(validMsg);
+    }
+  }
+
+  @Nested
+  class Valid_Birthday {
+    @BeforeEach
+    void setup() {
+      validMsg = "생년월일 형식이 유효하지 않습니다.";
     }
 
     @Test
-    void 생년월일_검증_실패() {
+    void 실패_생년월일_형식오류() {
       assertThatThrownBy(() -> {
         userModel = UserModel.create("user1", "user1@test.XXX", "19990101", "F");
-      }).isInstanceOf(CoreException.class).hasMessageContaining("생년월일 형식이 유효하지 않습니다.");
+      }).isInstanceOf(CoreException.class).hasMessageContaining(validMsg);
     }
 
     @Test
-    void User_객체생성_성공() {
-      userModel = UserModel.create("user1", "user1@test.XXX", "1999-01-01", "F");
-      assertThat(userModel).isNotNull();
-      assertThat(userModel.getUserId()).isEqualTo("user1");
+    void 실패_생년월일_날짜오류() {
+      assertThatThrownBy(() -> {
+        userModel = UserModel.create("user1", "user1@test.XXX", "1999-13-01", "F");
+      }).isInstanceOf(CoreException.class).hasMessageContaining(validMsg);
     }
   }
+
+  @Test
+  void 성공_User_객체생성() {
+    userModel = UserModel.create("user1", "user1@test.XXX", "1999-01-01", "F");
+    assertThat(userModel).isNotNull();
+    assertThat(userModel.getUserId()).isEqualTo("user1");
+  }
 }
diff --git a/apps/commerce-api/src/test/java/com/loopers/domain/user/UserServiceIntegrationTest.java b/apps/commerce-api/src/test/java/com/loopers/domain/user/UserServiceIntegrationTest.java
index 67d455105..38c617d52 100644
--- a/apps/commerce-api/src/test/java/com/loopers/domain/user/UserServiceIntegrationTest.java
+++ b/apps/commerce-api/src/test/java/com/loopers/domain/user/UserServiceIntegrationTest.java
@@ -3,26 +3,24 @@
 import com.loopers.infrastructure.user.UserJpaRepository;
 import com.loopers.support.error.CoreException;
 import com.loopers.utils.DatabaseCleanUp;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.DisplayName;
-import org.junit.jupiter.api.Nested;
-import org.junit.jupiter.api.Test;
-import org.mockito.Spy;
+import org.junit.jupiter.api.*;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.bean.override.mockito.MockitoSpyBean;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.junit.jupiter.api.Assertions.assertAll;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
 
 @SpringBootTest
 class UserServiceIntegrationTest {
   @Autowired
   private UserService userService;
 
-  @Spy
+  @MockitoSpyBean
   private UserJpaRepository userJpaRepository;
 
   @Autowired
@@ -33,7 +31,7 @@ void tearDown() {
     databaseCleanUp.truncateAllTables();
   }
 
-  @DisplayName("회원가입을 할 때,")
+  @DisplayName("회원가입을 할 때")
   @Nested
   class Join {
     @DisplayName("회원 가입시 User 저장이 수행된다. ( spy 검증 )")
@@ -59,20 +57,20 @@ void throwsException_whenInvalidIdIsProvided() {
       userService.join(userModel);
 
       // act
-      //verify(userJpaRepository, times(1)).existsByUserId(userModel.getUserId());
+      verify(userJpaRepository, times(1)).save(userModel);
       assertThatThrownBy(() -> {
         userService.join(userModel);
       }).isInstanceOf(CoreException.class).hasMessageContaining("이미 가입된 ID 입니다.");
-
+      verify(userJpaRepository, times(1)).save(userModel);
     }
   }
 
-  @DisplayName("내 정보 조회할 때,")
+  @DisplayName("내 정보 조회할 때")
   @Nested
   class Get {
     @DisplayName("해당 ID 의 회원이 존재할 경우, 회원 정보가 반환된다.")
     @Test
-    void returnsUserInfo_whenValidIdIsProvided() {
+    void 성공_존재하는_유저ID() {
       // arrange
       UserModel userModel = UserModel.create("user1", "user1@test.XXX", "1999-01-01", "F");
       userService.join(userModel);
@@ -92,7 +90,7 @@ void returnsUserInfo_whenValidIdIsProvided() {
 
     @DisplayName("해당 ID 의 회원이 존재하지 않을 경우, null 이 반환된다.")
     @Test
-    void throwsException_whenInvalidIdIsProvided() {
+    void 실패_존재하지_않는_유저ID() {
       // arrange
       UserModel userModel = UserModel.create("user1", "user1@test.XXX", "1999-01-01", "F");
 
diff --git a/apps/commerce-api/src/test/java/com/loopers/interfaces/api/PointV1ApiE2ETest.java b/apps/commerce-api/src/test/java/com/loopers/interfaces/api/PointV1ApiE2ETest.java
new file mode 100644
index 000000000..f1d76773f
--- /dev/null
+++ b/apps/commerce-api/src/test/java/com/loopers/interfaces/api/PointV1ApiE2ETest.java
@@ -0,0 +1,82 @@
+package com.loopers.interfaces.api;
+
+import com.loopers.domain.user.UserModel;
+import com.loopers.infrastructure.user.UserJpaRepository;
+import com.loopers.utils.DatabaseCleanUp;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.web.client.TestRestTemplate;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.*;
+
+import java.math.BigDecimal;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+public class PointV1ApiE2ETest {
+
+  private final TestRestTemplate testRestTemplate;
+  private final UserJpaRepository userJpaRepository;
+  private final DatabaseCleanUp databaseCleanUp;
+
+  @Autowired
+  public PointV1ApiE2ETest(
+      TestRestTemplate testRestTemplate,
+      UserJpaRepository userJpaRepository,
+      DatabaseCleanUp databaseCleanUp
+  ) {
+    this.testRestTemplate = testRestTemplate;
+    this.userJpaRepository = userJpaRepository;
+    this.databaseCleanUp = databaseCleanUp;
+  }
+
+  @AfterEach
+  void tearDown() {
+    databaseCleanUp.truncateAllTables();
+  }
+
+  @Nested
+  class Get {
+    @Test
+    void 포인트조회_성공() {
+      //given
+      BigDecimal JOIN_POINT = BigDecimal.TEN;
+
+      UserModel userModel = UserModel.create("user1", "user1@test.XXX", "1999-01-01", "F");
+      userJpaRepository.save(userModel);
+
+      HttpHeaders headers = new HttpHeaders();
+      headers.set("X-USER-ID", userModel.getUserId());
+
+      //when
+      String url = "/api/v1/user/point";
+      ParameterizedTypeReference<ApiResponse<BigDecimal>> resType = new ParameterizedTypeReference<>() {
+      };
+      ResponseEntity<ApiResponse<BigDecimal>> res = testRestTemplate.exchange(url, HttpMethod.GET, new HttpEntity<>(headers), resType);
+
+      //then
+      assertThat(res.getStatusCode()).isEqualTo(HttpStatus.OK);
+      assertThat(res.getBody().data()).isNotNull();
+      assertEquals(0, res.getBody().data().compareTo(JOIN_POINT));
+    }
+
+    @Test
+    void ID없음_실패() {
+      //given
+
+      //when
+      String url = "/api/v1/user/point";
+      ParameterizedTypeReference<ApiResponse<BigDecimal>> resType = new ParameterizedTypeReference<>() {
+      };
+      ResponseEntity<ApiResponse<BigDecimal>> res = testRestTemplate.exchange(url, HttpMethod.GET, new HttpEntity<>(null), resType);
+
+      //then
+      assertThat(res.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
+    }
+  }
+}
diff --git a/apps/commerce-api/src/test/java/com/loopers/interfaces/api/UserModelV1ApiE2ETest.java b/apps/commerce-api/src/test/java/com/loopers/interfaces/api/UserV1ApiE2ETest.java
similarity index 79%
rename from apps/commerce-api/src/test/java/com/loopers/interfaces/api/UserModelV1ApiE2ETest.java
rename to apps/commerce-api/src/test/java/com/loopers/interfaces/api/UserV1ApiE2ETest.java
index 13ffcb41c..bd7eb27ca 100644
--- a/apps/commerce-api/src/test/java/com/loopers/interfaces/api/UserModelV1ApiE2ETest.java
+++ b/apps/commerce-api/src/test/java/com/loopers/interfaces/api/UserV1ApiE2ETest.java
@@ -1,7 +1,7 @@
 package com.loopers.interfaces.api;
 
 import com.loopers.domain.user.UserModel;
-import com.loopers.infrastructure.user.UserJpaRepository;
+import com.loopers.domain.user.UserService;
 import com.loopers.interfaces.api.user.UserCreateV1Dto;
 import com.loopers.utils.DatabaseCleanUp;
 import org.junit.jupiter.api.AfterEach;
@@ -16,23 +16,29 @@
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
 
+import java.util.function.Function;
+
 import static org.assertj.core.api.Assertions.assertThat;
 
+
 @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
-public class UserModelV1ApiE2ETest {
+public class UserV1ApiE2ETest {
+
+  private static final String ENDPOINT = "/api/v1/user";
+  private static final Function<String, String> ENDPOINT_GET = id -> "/api/v1/user/" + id;
 
   private final TestRestTemplate testRestTemplate;
-  private final UserJpaRepository userJpaRepository;
+  private final UserService userService;
   private final DatabaseCleanUp databaseCleanUp;
 
   @Autowired
-  public UserModelV1ApiE2ETest(
+  public UserV1ApiE2ETest(
       TestRestTemplate testRestTemplate,
-      UserJpaRepository userJpaRepository,
+      UserService userService,
       DatabaseCleanUp databaseCleanUp
   ) {
     this.testRestTemplate = testRestTemplate;
-    this.userJpaRepository = userJpaRepository;
+    this.userService = userService;
     this.databaseCleanUp = databaseCleanUp;
   }
 
@@ -44,15 +50,14 @@ void tearDown() {
   @Nested
   class Join {
     @Test
-    void 회원가입_성공() {
+    void 성공_회원가입() {
       //given
       UserCreateV1Dto.UserRequest req = new UserCreateV1Dto.UserRequest("user1", "user1@test.XXX", "1999-01-01", "F");
 
       //when
-      String url = "/api/v1/users";
       ParameterizedTypeReference<ApiResponse<UserCreateV1Dto.UserResponse>> resType = new ParameterizedTypeReference<>() {
       };
-      ResponseEntity<ApiResponse<UserCreateV1Dto.UserResponse>> res = testRestTemplate.exchange(url, HttpMethod.POST, new HttpEntity<>(req), resType);
+      ResponseEntity<ApiResponse<UserCreateV1Dto.UserResponse>> res = testRestTemplate.exchange(ENDPOINT, HttpMethod.POST, new HttpEntity<>(req), resType);
 
       //then
       assertThat(res.getStatusCode()).isEqualTo(HttpStatus.CREATED);
@@ -61,15 +66,14 @@ class Join {
     }
 
     @Test
-    void 회원가입_성별없음_실패() {
+    void 실패_회원가입_성별없음() {
       //given
       UserCreateV1Dto.UserRequest req = new UserCreateV1Dto.UserRequest("user1", "user1@test.XXX", "1999-01-01", null);
 
       //when
-      String url = "/api/v1/users";
       ParameterizedTypeReference<ApiResponse<UserCreateV1Dto.UserResponse>> resType = new ParameterizedTypeReference<>() {
       };
-      ResponseEntity<ApiResponse<UserCreateV1Dto.UserResponse>> res = testRestTemplate.exchange(url, HttpMethod.POST, new HttpEntity<>(req), resType);
+      ResponseEntity<ApiResponse<UserCreateV1Dto.UserResponse>> res = testRestTemplate.exchange(ENDPOINT, HttpMethod.POST, new HttpEntity<>(req), resType);
 
       //then
       assertThat(res.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
@@ -80,12 +84,12 @@ class Join {
   @Nested
   class Get {
     @Test
-    void 존재하지_않는_ID_실패() {
+    void 실패_존재하지_않는_ID() {
       //given
       String userId = "user1";
 
       //when
-      String url = "/api/v1/users/" + userId;
+      String url = ENDPOINT_GET.apply(userId);
       ParameterizedTypeReference<ApiResponse<UserCreateV1Dto.UserResponse>> resType = new ParameterizedTypeReference<>() {
       };
       ResponseEntity<ApiResponse<UserCreateV1Dto.UserResponse>> res = testRestTemplate.exchange(url, HttpMethod.GET, new HttpEntity<>(null), resType);
@@ -95,13 +99,13 @@ class Get {
     }
 
     @Test
-    void 내정보조회_성공() {
+    void 성공_정보조회() {
       //given
       UserModel userModel = UserModel.create("user1", "user1@test.XXX", "1999-01-01", "F");
-      userJpaRepository.save(userModel);
+      userService.join(userModel);
 
       //when
-      String url = "/api/v1/users/" + userModel.getUserId();
+      String url = ENDPOINT_GET.apply(userModel.getUserId());
       ParameterizedTypeReference<ApiResponse<UserCreateV1Dto.UserResponse>> resType = new ParameterizedTypeReference<>() {
       };
       ResponseEntity<ApiResponse<UserCreateV1Dto.UserResponse>> res = testRestTemplate.exchange(url, HttpMethod.GET, new HttpEntity<>(null), resType);

From c4b67f72836a0b85e9386ee7889c21876480ff1a Mon Sep 17 00:00:00 2001
From: sieun0322 <lce322@naver.com>
Date: Fri, 31 Oct 2025 14:42:31 +0900
Subject: [PATCH 4/5] =?UTF-8?q?feat:=ED=8F=AC=EC=9D=B8=ED=8A=B8=20?=
 =?UTF-8?q?=EC=B6=A9=EC=A0=84=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EB=B0=8F=20?=
 =?UTF-8?q?=EC=84=A4=EB=AA=85=20=EC=88=98=EC=A0=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../application/point/PointFacade.java        | 14 +++--
 .../com/loopers/domain/point/PointModel.java  |  8 +--
 .../loopers/domain/point/PointRepository.java |  3 +-
 .../loopers/domain/point/PointService.java    | 14 ++++-
 .../loopers/domain/user/UserRepository.java   |  2 +-
 .../user/UserRepositoryImpl.java              |  1 +
 .../interfaces/api/point/PointV1ApiSpec.java  | 20 +++++--
 .../api/point/PointV1Controller.java          | 14 +++--
 .../loopers/domain/point/PointModelTest.java  | 36 ++++++------
 .../point/PointServiceIntegrationTest.java    | 50 +++++++++++++++--
 .../loopers/domain/user/UserModelTest.java    |  7 ++-
 .../user/UserServiceIntegrationTest.java      | 20 ++++---
 .../interfaces/api/PointV1ApiE2ETest.java     | 55 ++++++++++++++++++-
 .../interfaces/api/UserV1ApiE2ETest.java      | 31 +++++++----
 14 files changed, 208 insertions(+), 67 deletions(-)

diff --git a/apps/commerce-api/src/main/java/com/loopers/application/point/PointFacade.java b/apps/commerce-api/src/main/java/com/loopers/application/point/PointFacade.java
index 36aa6d92d..92b3efd79 100644
--- a/apps/commerce-api/src/main/java/com/loopers/application/point/PointFacade.java
+++ b/apps/commerce-api/src/main/java/com/loopers/application/point/PointFacade.java
@@ -1,10 +1,10 @@
 package com.loopers.application.point;
 
-import com.loopers.domain.point.PointModel;
 import com.loopers.domain.point.PointService;
 import com.loopers.domain.user.UserModel;
 import com.loopers.domain.user.UserService;
-import com.loopers.interfaces.api.point.PointCreateV1Dto;
+import com.loopers.support.error.CoreException;
+import com.loopers.support.error.ErrorType;
 import lombok.RequiredArgsConstructor;
 import org.springframework.stereotype.Component;
 
@@ -15,10 +15,16 @@
 public class PointFacade {
   private final UserService userService;
   private final PointService pointService;
-  
+
   public BigDecimal getPoint(String userId) {
     UserModel user = userService.getUser(userId);
-    return pointService.getAmount(userId);
+    return pointService.getAmount(user.getUserId());
+  }
+
+  public BigDecimal charge(String userId, BigDecimal chargeAmt) {
+    UserModel user = userService.getUser(userId);
+    if (user == null) throw new CoreException(ErrorType.NOT_FOUND, "유저정보를 찾을수 없습니다.");
+    return pointService.charge(user, chargeAmt);
   }
 
 }
diff --git a/apps/commerce-api/src/main/java/com/loopers/domain/point/PointModel.java b/apps/commerce-api/src/main/java/com/loopers/domain/point/PointModel.java
index a8c544c55..db3321685 100644
--- a/apps/commerce-api/src/main/java/com/loopers/domain/point/PointModel.java
+++ b/apps/commerce-api/src/main/java/com/loopers/domain/point/PointModel.java
@@ -42,20 +42,20 @@ public static PointModel create(UserModel user, BigDecimal amount) {
     return new PointModel(user, amount);
   }
 
-  public PointModel charge(BigDecimal amountToChange) {
+  public void charge(BigDecimal amountToChange) {
     if (amountToChange == null || amountToChange.compareTo(BigDecimal.ZERO) <= 0) {
       throw new CoreException(ErrorType.BAD_REQUEST, "충전 금액은 0보다 커야 합니다.");
     }
-    return new PointModel(this.user, this.amount.add(amountToChange));
+    this.amount = this.amount.add(amountToChange);
   }
 
-  public PointModel use(BigDecimal amountToChange) {
+  public void use(BigDecimal amountToChange) {
     if (amountToChange == null || amountToChange.compareTo(BigDecimal.ZERO) <= 0) {
       throw new CoreException(ErrorType.BAD_REQUEST, "차감 금액은 0보다 커야 합니다.");
     }
     if (this.amount.compareTo(amountToChange) < 0) {
       throw new CoreException(ErrorType.BAD_REQUEST, "잔액이 부족합니다.");
     }
-    return new PointModel(this.user, this.amount.subtract(amountToChange));
+    this.amount = this.amount.subtract(amountToChange);
   }
 }
diff --git a/apps/commerce-api/src/main/java/com/loopers/domain/point/PointRepository.java b/apps/commerce-api/src/main/java/com/loopers/domain/point/PointRepository.java
index 2e2259647..35d2dc95c 100644
--- a/apps/commerce-api/src/main/java/com/loopers/domain/point/PointRepository.java
+++ b/apps/commerce-api/src/main/java/com/loopers/domain/point/PointRepository.java
@@ -6,11 +6,12 @@
 import java.util.Optional;
 
 public interface PointRepository {
-  PointModel save(PointModel user);
 
   Optional<PointModel> findByUserId(String userId);
 
   @Lock(LockModeType.PESSIMISTIC_WRITE)
   Optional<PointModel> findByUserIdForUpdate(String userId);
 
+  PointModel save(PointModel point);
+
 }
diff --git a/apps/commerce-api/src/main/java/com/loopers/domain/point/PointService.java b/apps/commerce-api/src/main/java/com/loopers/domain/point/PointService.java
index d10532c83..9554dcac0 100644
--- a/apps/commerce-api/src/main/java/com/loopers/domain/point/PointService.java
+++ b/apps/commerce-api/src/main/java/com/loopers/domain/point/PointService.java
@@ -1,14 +1,15 @@
 
 package com.loopers.domain.point;
 
+import com.loopers.domain.user.UserModel;
 import com.loopers.support.error.CoreException;
 import com.loopers.support.error.ErrorType;
 import lombok.RequiredArgsConstructor;
 import org.springframework.stereotype.Component;
 import org.springframework.transaction.annotation.Transactional;
 
-import java.awt.*;
 import java.math.BigDecimal;
+import java.util.Optional;
 
 @RequiredArgsConstructor
 @Component
@@ -22,4 +23,15 @@ public BigDecimal getAmount(String userId) {
         .map(PointModel::getAmount)
         .orElse(null);
   }
+
+  @Transactional
+  public BigDecimal charge(UserModel user, BigDecimal chargeAmt) {
+    Optional<PointModel> pointOpt = pointRepository.findByUserIdForUpdate(user.getUserId());
+    if (pointOpt.isEmpty()) {
+      throw new CoreException(ErrorType.NOT_FOUND, "현재 포인트 정보를 찾을수 없습니다.");
+    }
+    pointOpt.get().charge(chargeAmt);
+    pointRepository.save(pointOpt.get());
+    return getAmount(user.getUserId());
+  }
 }
diff --git a/apps/commerce-api/src/main/java/com/loopers/domain/user/UserRepository.java b/apps/commerce-api/src/main/java/com/loopers/domain/user/UserRepository.java
index 1035f1846..b7f64a18f 100644
--- a/apps/commerce-api/src/main/java/com/loopers/domain/user/UserRepository.java
+++ b/apps/commerce-api/src/main/java/com/loopers/domain/user/UserRepository.java
@@ -6,6 +6,6 @@ public interface UserRepository {
   UserModel save(UserModel user);
 
   boolean existsByUserId(String userId);
-  
+
   Optional<UserModel> findByUserId(String userId);
 }
diff --git a/apps/commerce-api/src/main/java/com/loopers/infrastructure/user/UserRepositoryImpl.java b/apps/commerce-api/src/main/java/com/loopers/infrastructure/user/UserRepositoryImpl.java
index ca467658e..3e33c3ff7 100644
--- a/apps/commerce-api/src/main/java/com/loopers/infrastructure/user/UserRepositoryImpl.java
+++ b/apps/commerce-api/src/main/java/com/loopers/infrastructure/user/UserRepositoryImpl.java
@@ -21,6 +21,7 @@ public UserModel save(UserModel user) {
   public boolean existsByUserId(String userId) {
     return jpaRepository.existsByUserId(userId);
   }
+
   @Override
   public Optional<UserModel> findByUserId(String userId) {
     return jpaRepository.findByUserId(userId);
diff --git a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/point/PointV1ApiSpec.java b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/point/PointV1ApiSpec.java
index 9f252e3b0..7e86ed59c 100644
--- a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/point/PointV1ApiSpec.java
+++ b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/point/PointV1ApiSpec.java
@@ -1,12 +1,11 @@
 package com.loopers.interfaces.api.point;
 
 import com.loopers.interfaces.api.ApiResponse;
-import com.loopers.interfaces.api.user.UserCreateV1Dto;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.media.Schema;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import jakarta.validation.Valid;
-import jakarta.validation.constraints.NotBlank;
+import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestHeader;
 
 import java.math.BigDecimal;
@@ -14,12 +13,23 @@
 @Tag(name = "Point V1 API", description = "Loopers 예시 API 입니다.")
 public interface PointV1ApiSpec {
   @Operation(
-      summary = "예시 조회",
-      description = "ID로 예시를 조회합니다."
+      summary = "포인트 조회",
+      description = "ID로 포인트 조회합니다."
   )
   @Valid
   ApiResponse<BigDecimal> getPoint(
-      @Schema(name = "예시 ID", description = "조회할 예시의 ID")
+      @Schema(name = "사용자 ID", description = "조회할 사용자의 ID")
       @RequestHeader(value = "X-USER-ID", required = false) String userId
   );
+
+  @Operation(
+      summary = "포인트 충전",
+      description = "ID로 포인트를 충전합니다."
+  )
+  @Valid
+  ApiResponse<BigDecimal> charge(
+      @Schema(name = "사용자 ID", description = "충전할 사용자의 ID")
+      @RequestHeader(value = "X-USER-ID", required = false) String userId
+      , @RequestBody BigDecimal amount
+  );
 }
diff --git a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/point/PointV1Controller.java b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/point/PointV1Controller.java
index 5128ab500..211efbe2a 100644
--- a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/point/PointV1Controller.java
+++ b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/point/PointV1Controller.java
@@ -1,14 +1,8 @@
 package com.loopers.interfaces.api.point;
 
 import com.loopers.application.point.PointFacade;
-import com.loopers.application.point.PointFacade;
-import com.loopers.application.point.PointInfo;
 import com.loopers.interfaces.api.ApiResponse;
-import jakarta.validation.Valid;
-import jakarta.validation.constraints.NotBlank;
-import jakarta.validation.constraints.NotNull;
 import lombok.RequiredArgsConstructor;
-import org.springframework.http.HttpStatus;
 import org.springframework.web.bind.annotation.*;
 
 import java.math.BigDecimal;
@@ -26,4 +20,12 @@ public ApiResponse<BigDecimal> getPoint(@RequestHeader(value = "X-USER-ID", requ
   ) {
     return ApiResponse.success(pointFacade.getPoint(userId));
   }
+
+  @PostMapping("/charge")
+  @Override
+  public ApiResponse<BigDecimal> charge(@RequestHeader(value = "X-USER-ID", required = false) String userId
+      , @RequestBody BigDecimal amount
+  ) {
+    return ApiResponse.success(pointFacade.charge(userId, amount));
+  }
 }
diff --git a/apps/commerce-api/src/test/java/com/loopers/domain/point/PointModelTest.java b/apps/commerce-api/src/test/java/com/loopers/domain/point/PointModelTest.java
index 7d227dd94..52f08f71f 100644
--- a/apps/commerce-api/src/test/java/com/loopers/domain/point/PointModelTest.java
+++ b/apps/commerce-api/src/test/java/com/loopers/domain/point/PointModelTest.java
@@ -3,6 +3,7 @@
 import com.loopers.domain.user.UserModel;
 import com.loopers.support.error.CoreException;
 import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 
@@ -20,56 +21,59 @@ void setup() {
     userModel = UserModel.create("user1", "user1@test.XXX", "1999-01-01", "F");
   }
 
+  @DisplayName("포인트 충전")
   @Nested
-  class Use {
-
+  class Charge {
+    @DisplayName("단위테스트1-0 이하의 정수로 포인트를 충전 시 실패한다.")
     @Test
-    void 실패_포인트사용() {
+    void 실패_포인트충전_0() {
       // arrange
       pointModel = PointModel.create(userModel, BigDecimal.ZERO);
 
       // act, assert
       assertThatThrownBy(() -> {
-        pointModel.use(BigDecimal.TEN);
-      }).isInstanceOf(CoreException.class).hasMessageContaining("잔액이 부족합니다.");
+        pointModel.charge(BigDecimal.ZERO);
+      }).isInstanceOf(CoreException.class).hasMessageContaining("충전 금액은 0보다 커야 합니다.");
     }
 
     @Test
-    void 성공_포인트사용() {
+    void 성공_포인트충전() {
       // arrange
       pointModel = PointModel.create(userModel, new BigDecimal(20));
 
       // act
-      PointModel usedPointModel = pointModel.use(new BigDecimal(5));
+      pointModel.charge(new BigDecimal(5));
 
       // assert
-      assertThat(usedPointModel.getAmount()).isEqualTo(new BigDecimal(15));
+      assertThat(pointModel.getAmount()).isEqualTo(new BigDecimal(25));
     }
   }
 
   @Nested
-  class Charge {
+  class Use {
+
     @Test
-    void 실패_포인트충전() {
+    void 실패_포인트사용() {
       // arrange
-      pointModel = PointModel.create(userModel, new BigDecimal(20));
+      pointModel = PointModel.create(userModel, BigDecimal.ZERO);
 
       // act, assert
       assertThatThrownBy(() -> {
-        pointModel.charge(BigDecimal.ZERO);
-      }).isInstanceOf(CoreException.class).hasMessageContaining("충전 금액은 0보다 커야 합니다.");
+        pointModel.use(BigDecimal.TEN);
+      }).isInstanceOf(CoreException.class).hasMessageContaining("잔액이 부족합니다.");
     }
 
     @Test
-    void 성공_포인트충전() {
+    void 성공_포인트사용() {
       // arrange
       pointModel = PointModel.create(userModel, new BigDecimal(20));
 
       // act
-      PointModel chargedPointModel = pointModel.charge(new BigDecimal(5));
+      pointModel.use(new BigDecimal(5));
 
       // assert
-      assertThat(chargedPointModel.getAmount()).isEqualTo(new BigDecimal(25));
+      assertThat(pointModel.getAmount()).isEqualTo(new BigDecimal(15));
     }
   }
+
 }
diff --git a/apps/commerce-api/src/test/java/com/loopers/domain/point/PointServiceIntegrationTest.java b/apps/commerce-api/src/test/java/com/loopers/domain/point/PointServiceIntegrationTest.java
index 8a95b70f1..ac000465c 100644
--- a/apps/commerce-api/src/test/java/com/loopers/domain/point/PointServiceIntegrationTest.java
+++ b/apps/commerce-api/src/test/java/com/loopers/domain/point/PointServiceIntegrationTest.java
@@ -3,6 +3,7 @@
 import com.loopers.domain.user.UserModel;
 import com.loopers.infrastructure.point.PointJpaRepository;
 import com.loopers.infrastructure.user.UserJpaRepository;
+import com.loopers.support.error.CoreException;
 import com.loopers.utils.DatabaseCleanUp;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.DisplayName;
@@ -13,8 +14,10 @@
 import org.springframework.test.context.bean.override.mockito.MockitoSpyBean;
 
 import java.math.BigDecimal;
+import java.util.Optional;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.junit.jupiter.api.Assertions.assertAll;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
@@ -38,10 +41,10 @@ void tearDown() {
     databaseCleanUp.truncateAllTables();
   }
 
-  @DisplayName("포인트 조회할 때")
+  @DisplayName("포인트 조회")
   @Nested
   class Get {
-    @DisplayName("해당 ID 의 회원이 존재할 경우, 보유 포인트가 반환")
+    @DisplayName("통합테스트1-해당 ID 의 회원이 존재할 경우, 보유 포인트가 반환")
     @Test
     void 성공_존재하는_유저ID() {
       // arrange
@@ -55,13 +58,13 @@ class Get {
       // assert(회원가입시, 기본포인트 10)
       assertAll(
           () -> assertThat(pointAmt).isNotNull(),
-          () -> assertEquals(0, pointAmt.compareTo(JOIN_POINT))
+          () -> assertEquals(0, pointAmt.compareTo(new BigDecimal(10)))
       );
     }
 
-    @DisplayName("해당 ID 의 회원이 존재하지 않을 경우, null 이 반환된다.")
+    @DisplayName("통합테스트2-해당 ID 의 회원이 존재하지 않을 경우, null이 반환")
     @Test
-    void 성공_존재하지않는_유저ID() {
+    void 실패_존재하지않는_유저ID() {
       // arrange (등록된 회원 없음)
       String userId = "userId";
 
@@ -72,4 +75,41 @@ class Get {
       assertThat(pointAmt).isNull();
     }
   }
+
+  @DisplayName("포인트 충전")
+  @Nested
+  class Charge {
+    //Ask: 유저 조회는 Facade에서, Service 에 User 객체 전송
+    @DisplayName("통합테스트1-존재하지 않는 유저 ID 로 충전을 시도한 경우, 실패")
+    @Test
+    void 실패_존재하지않는_유저ID() {
+      // arrange (등록된 회원 없음)
+      UserModel userModel = UserModel.create("user1", "user1@test.XXX", "1999-01-01", "F");
+
+      // act, assert
+      assertThatThrownBy(() -> {
+        pointService.charge(userModel, BigDecimal.TEN);
+      }).isInstanceOf(CoreException.class).hasMessageContaining("현재 포인트 정보를 찾을수 없습니다.");
+
+    }
+
+    @DisplayName("존재하는 유저 ID로 충전할 경우, 보유 포인트가 반환")
+    @Test
+    void 성공_존재하는_유저ID() {
+      // arrange
+      BigDecimal JOIN_POINT = BigDecimal.TEN;
+      UserModel userModel = UserModel.create("user1", "user1@test.XXX", "1999-01-01", "F");
+      userJpaRepository.save(userModel);
+      Optional<UserModel> savedUserModel = userJpaRepository.findByUserId(userModel.getUserId());
+      // act
+      BigDecimal pointAmt = pointService.charge(savedUserModel.get(), BigDecimal.TEN);
+
+      // assert(회원가입시, 기본포인트 10)
+      assertAll(
+          () -> assertThat(pointAmt).isNotNull(),
+          () -> assertEquals(0, pointAmt.compareTo(new BigDecimal(20)))
+      );
+    }
+
+  }
 }
diff --git a/apps/commerce-api/src/test/java/com/loopers/domain/user/UserModelTest.java b/apps/commerce-api/src/test/java/com/loopers/domain/user/UserModelTest.java
index fb1cf7de4..e7399e1d0 100644
--- a/apps/commerce-api/src/test/java/com/loopers/domain/user/UserModelTest.java
+++ b/apps/commerce-api/src/test/java/com/loopers/domain/user/UserModelTest.java
@@ -2,6 +2,7 @@
 
 import com.loopers.support.error.CoreException;
 import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 
@@ -12,8 +13,9 @@ class UserModelTest {
   UserModel userModel;
   String validMsg = "";
 
+  @DisplayName("화원가입-단위테스트1")
   @Nested
-  class Valid_ID {
+  class ValidID {
     @BeforeEach
     void setup() {
       validMsg = "아이디 형식이 잘못되었습니다.(영문 및 숫자 1~10자 이내)";
@@ -48,6 +50,7 @@ void setup() {
     }
   }
 
+  @DisplayName("화원가입-단위테스트2")
   @Nested
   class Valid_Email {
     @BeforeEach
@@ -70,6 +73,8 @@ void setup() {
     }
   }
 
+
+  @DisplayName("화원가입-단위테스트3")
   @Nested
   class Valid_Birthday {
     @BeforeEach
diff --git a/apps/commerce-api/src/test/java/com/loopers/domain/user/UserServiceIntegrationTest.java b/apps/commerce-api/src/test/java/com/loopers/domain/user/UserServiceIntegrationTest.java
index 38c617d52..a3a300d5c 100644
--- a/apps/commerce-api/src/test/java/com/loopers/domain/user/UserServiceIntegrationTest.java
+++ b/apps/commerce-api/src/test/java/com/loopers/domain/user/UserServiceIntegrationTest.java
@@ -11,6 +11,7 @@
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.junit.jupiter.api.Assertions.assertAll;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
@@ -31,12 +32,12 @@ void tearDown() {
     databaseCleanUp.truncateAllTables();
   }
 
-  @DisplayName("회원가입을 할 때")
+  @DisplayName("회원가입")
   @Nested
   class Join {
-    @DisplayName("회원 가입시 User 저장이 수행된다. ( spy 검증 )")
+    @DisplayName("통합테스트1-회원 가입시 User 저장이 수행된다. ( spy 검증 )")
     @Test
-    void returnsUserInfo_whenValidIdIsProvided() {
+    void 성공_회원가입() {
       // arrange
       UserModel userModel = UserModel.create("user1", "user1@test.XXX", "1999-01-01", "F");
 
@@ -45,13 +46,14 @@ void returnsUserInfo_whenValidIdIsProvided() {
 
       // assert
       assertAll(
-          () -> verify(userJpaRepository, times(1)).save(userModel)
+          () -> verify(userJpaRepository, times(1)).save(userModel),
+          () -> assertThrows(CoreException.class, () -> userService.join(userModel))
       );
     }
 
-    @DisplayName("이미 가입된 ID 로 회원가입 시도 시, 실패한다.")
+    @DisplayName("통합테스트2-이미 가입된 ID 로 회원가입 시도 시, 실패한다.")
     @Test
-    void throwsException_whenInvalidIdIsProvided() {
+    void 실패_이미_가입된ID() {
       // arrange
       UserModel userModel = UserModel.create("user1", "user1@test.XXX", "1999-01-01", "F");
       userService.join(userModel);
@@ -65,10 +67,10 @@ void throwsException_whenInvalidIdIsProvided() {
     }
   }
 
-  @DisplayName("내 정보 조회할 때")
+  @DisplayName("내 정보 조회")
   @Nested
   class Get {
-    @DisplayName("해당 ID 의 회원이 존재할 경우, 회원 정보가 반환된다.")
+    @DisplayName("통합테스트1-해당 ID 의 회원이 존재할 경우, 회원 정보가 반환된다.")
     @Test
     void 성공_존재하는_유저ID() {
       // arrange
@@ -88,7 +90,7 @@ class Get {
       );
     }
 
-    @DisplayName("해당 ID 의 회원이 존재하지 않을 경우, null 이 반환된다.")
+    @DisplayName("통합테스트2-해당 ID 의 회원이 존재하지 않을 경우, null 이 반환된다.")
     @Test
     void 실패_존재하지_않는_유저ID() {
       // arrange
diff --git a/apps/commerce-api/src/test/java/com/loopers/interfaces/api/PointV1ApiE2ETest.java b/apps/commerce-api/src/test/java/com/loopers/interfaces/api/PointV1ApiE2ETest.java
index f1d76773f..b80060562 100644
--- a/apps/commerce-api/src/test/java/com/loopers/interfaces/api/PointV1ApiE2ETest.java
+++ b/apps/commerce-api/src/test/java/com/loopers/interfaces/api/PointV1ApiE2ETest.java
@@ -4,6 +4,7 @@
 import com.loopers.infrastructure.user.UserJpaRepository;
 import com.loopers.utils.DatabaseCleanUp;
 import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -40,20 +41,22 @@ void tearDown() {
     databaseCleanUp.truncateAllTables();
   }
 
+  @DisplayName("포인트 조회")
   @Nested
   class Get {
+    @DisplayName("E2E테스트1-포인트 조회에할 경우, 보유 포인트가 반환")
     @Test
-    void 포인트조회_성공() {
+    void 성공_포인트조회() {
       //given
       BigDecimal JOIN_POINT = BigDecimal.TEN;
 
       UserModel userModel = UserModel.create("user1", "user1@test.XXX", "1999-01-01", "F");
       userJpaRepository.save(userModel);
 
+      //when
       HttpHeaders headers = new HttpHeaders();
       headers.set("X-USER-ID", userModel.getUserId());
 
-      //when
       String url = "/api/v1/user/point";
       ParameterizedTypeReference<ApiResponse<BigDecimal>> resType = new ParameterizedTypeReference<>() {
       };
@@ -65,8 +68,9 @@ class Get {
       assertEquals(0, res.getBody().data().compareTo(JOIN_POINT));
     }
 
+    @DisplayName("E2E테스트2-X-USER-ID가 없을 경우, 400 Bad Request 반환")
     @Test
-    void ID없음_실패() {
+    void 실패_ID없음_400() {
       //given
 
       //when
@@ -79,4 +83,49 @@ class Get {
       assertThat(res.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
     }
   }
+
+  @DisplayName("포인트 충전")
+  @Nested
+  class Charge {
+    @DisplayName("E2E테스트1-존재하는 유저가 1000원을 충전할 경우, 충전된 보유총량을 응답으로 반환한다.")
+    @Test
+    void 성공_포인트충전() {
+      //given
+      BigDecimal CHARGE_POINT = BigDecimal.TEN;
+
+      UserModel userModel = UserModel.create("user1", "user1@test.XXX", "1999-01-01", "F");
+      userJpaRepository.save(userModel);
+
+      HttpHeaders headers = new HttpHeaders();
+      headers.set("X-USER-ID", userModel.getUserId());
+
+      //when
+      String url = "/api/v1/user/point/charge";
+      ParameterizedTypeReference<ApiResponse<BigDecimal>> resType = new ParameterizedTypeReference<>() {
+      };
+      ResponseEntity<ApiResponse<BigDecimal>> res = testRestTemplate.exchange(url, HttpMethod.POST, new HttpEntity<>(new BigDecimal(1_000), headers), resType);
+
+      //then
+      assertThat(res.getStatusCode()).isEqualTo(HttpStatus.OK);
+      assertThat(res.getBody().data()).isNotNull();
+      assertEquals(0, res.getBody().data().compareTo(new BigDecimal(1_010)));
+    }
+
+    @DisplayName("E2E테스트2-X-존재하지 않는 유저로 요청할 경우, 400 Not Found 응답을 반환")
+    @Test
+    void 실패_ID없음_400() {
+      //given
+      HttpHeaders headers = new HttpHeaders();
+      headers.set("X-USER-ID", "user1");
+
+      //when
+      String url = "/api/v1/user/point/charge";
+      ParameterizedTypeReference<ApiResponse<BigDecimal>> resType = new ParameterizedTypeReference<>() {
+      };
+      ResponseEntity<ApiResponse<BigDecimal>> res = testRestTemplate.exchange(url, HttpMethod.POST, new HttpEntity<>(BigDecimal.TEN, headers), resType);
+
+      //then
+      assertThat(res.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
+    }
+  }
 }
diff --git a/apps/commerce-api/src/test/java/com/loopers/interfaces/api/UserV1ApiE2ETest.java b/apps/commerce-api/src/test/java/com/loopers/interfaces/api/UserV1ApiE2ETest.java
index bd7eb27ca..6c5952457 100644
--- a/apps/commerce-api/src/test/java/com/loopers/interfaces/api/UserV1ApiE2ETest.java
+++ b/apps/commerce-api/src/test/java/com/loopers/interfaces/api/UserV1ApiE2ETest.java
@@ -3,8 +3,10 @@
 import com.loopers.domain.user.UserModel;
 import com.loopers.domain.user.UserService;
 import com.loopers.interfaces.api.user.UserCreateV1Dto;
+import com.loopers.support.error.CoreException;
 import com.loopers.utils.DatabaseCleanUp;
 import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -47,8 +49,10 @@ void tearDown() {
     databaseCleanUp.truncateAllTables();
   }
 
+  @DisplayName("회원 가입")
   @Nested
   class Join {
+    @DisplayName("회원 가입-E2E테스트1")
     @Test
     void 성공_회원가입() {
       //given
@@ -65,6 +69,7 @@ class Join {
       assertThat(res.getBody().data().userId()).isEqualTo(req.userId());
     }
 
+    @DisplayName("회원 가입-E2E테스트2")
     @Test
     void 실패_회원가입_성별없음() {
       //given
@@ -81,39 +86,43 @@ class Join {
     }
   }
 
+  @DisplayName("내 정보 조회")
   @Nested
   class Get {
+    @DisplayName("E2E테스트1")
     @Test
-    void 실패_존재하지_않는_ID() {
+    void 성공_정보조회() {
       //given
-      String userId = "user1";
+      UserModel userModel = UserModel.create("user1", "user1@test.XXX", "1999-01-01", "F");
+      userService.join(userModel);
 
       //when
-      String url = ENDPOINT_GET.apply(userId);
+      String url = ENDPOINT_GET.apply(userModel.getUserId());
       ParameterizedTypeReference<ApiResponse<UserCreateV1Dto.UserResponse>> resType = new ParameterizedTypeReference<>() {
       };
       ResponseEntity<ApiResponse<UserCreateV1Dto.UserResponse>> res = testRestTemplate.exchange(url, HttpMethod.GET, new HttpEntity<>(null), resType);
 
       //then
-      assertThat(res.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
+      assertThat(res.getStatusCode()).isEqualTo(HttpStatus.OK);
+      assertThat(res.getBody().data().userId()).isNotNull();
+      assertThat(res.getBody().data().userId()).isEqualTo(userModel.getUserId());
     }
 
+    @DisplayName("E2E테스트2")
     @Test
-    void 성공_정보조회() {
+    void 실패_존재하지_않는_ID() {
       //given
-      UserModel userModel = UserModel.create("user1", "user1@test.XXX", "1999-01-01", "F");
-      userService.join(userModel);
+      String userId = "user1";
 
       //when
-      String url = ENDPOINT_GET.apply(userModel.getUserId());
+      String url = ENDPOINT_GET.apply(userId);
       ParameterizedTypeReference<ApiResponse<UserCreateV1Dto.UserResponse>> resType = new ParameterizedTypeReference<>() {
       };
       ResponseEntity<ApiResponse<UserCreateV1Dto.UserResponse>> res = testRestTemplate.exchange(url, HttpMethod.GET, new HttpEntity<>(null), resType);
 
       //then
-      assertThat(res.getStatusCode()).isEqualTo(HttpStatus.OK);
-      assertThat(res.getBody().data().userId()).isNotNull();
-      assertThat(res.getBody().data().userId()).isEqualTo(userModel.getUserId());
+      assertThat(res.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
     }
+
   }
 }

From 5500f045d207661c2b1e97dfd25caf910aa7eed6 Mon Sep 17 00:00:00 2001
From: sieun0322 <lce322@naver.com>
Date: Fri, 31 Oct 2025 16:12:37 +0900
Subject: [PATCH 5/5] =?UTF-8?q?fix:=20=EC=98=A4=ED=83=80=20=EC=88=98?=
 =?UTF-8?q?=EC=A0=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../infrastructure/point/PointJpaRepository.java       | 10 ++++++----
 .../infrastructure/point/PointRepositoryImpl.java      |  2 +-
 .../com/loopers/interfaces/api/user/UserV1ApiSpec.java |  2 +-
 .../java/com/loopers/domain/point/PointModelTest.java  |  2 +-
 .../java/com/loopers/domain/user/UserModelTest.java    |  6 +++---
 .../com/loopers/interfaces/api/PointV1ApiE2ETest.java  |  4 ++--
 6 files changed, 14 insertions(+), 12 deletions(-)

diff --git a/apps/commerce-api/src/main/java/com/loopers/infrastructure/point/PointJpaRepository.java b/apps/commerce-api/src/main/java/com/loopers/infrastructure/point/PointJpaRepository.java
index 6b59de1fd..2c436109d 100644
--- a/apps/commerce-api/src/main/java/com/loopers/infrastructure/point/PointJpaRepository.java
+++ b/apps/commerce-api/src/main/java/com/loopers/infrastructure/point/PointJpaRepository.java
@@ -1,16 +1,18 @@
 package com.loopers.infrastructure.point;
 
 import com.loopers.domain.point.PointModel;
-import com.loopers.domain.user.UserModel;
-import jakarta.persistence.LockModeType;
 import org.springframework.data.jpa.repository.JpaRepository;
-import org.springframework.data.jpa.repository.Lock;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
 
-import java.awt.*;
 import java.util.Optional;
 
 public interface PointJpaRepository extends JpaRepository<PointModel, Long> {
   boolean existsByUserUserId(String userId);
 
   Optional<PointModel> findByUserUserId(String userId);
+
+  @Query("SELECT p FROM PointModel p WHERE p.user.userId = :userId")
+  Optional<PointModel> findByUserUserIdForUpdate(@Param("userId") String userId);
+
 }
diff --git a/apps/commerce-api/src/main/java/com/loopers/infrastructure/point/PointRepositoryImpl.java b/apps/commerce-api/src/main/java/com/loopers/infrastructure/point/PointRepositoryImpl.java
index f370715f0..cce08040d 100644
--- a/apps/commerce-api/src/main/java/com/loopers/infrastructure/point/PointRepositoryImpl.java
+++ b/apps/commerce-api/src/main/java/com/loopers/infrastructure/point/PointRepositoryImpl.java
@@ -24,7 +24,7 @@ public Optional<PointModel> findByUserId(String userId) {
 
   @Override
   public Optional<PointModel> findByUserIdForUpdate(String userId) {
-    return jpaRepository.findByUserUserId(userId);
+    return jpaRepository.findByUserUserIdForUpdate(userId);
   }
 
 }
diff --git a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1ApiSpec.java b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1ApiSpec.java
index 914849a4f..3fdb9902f 100644
--- a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1ApiSpec.java
+++ b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1ApiSpec.java
@@ -12,7 +12,7 @@ public interface UserV1ApiSpec {
       description = "유저 회원가입 합니다."
   )
   ApiResponse<UserCreateV1Dto.UserResponse> join(
-      @Schema(name = "유저 ID", description = "조회할 유저의 ID")
+      @Schema(description = "회원가입 요청 정보")
           UserCreateV1Dto.UserRequest userRequest
   );
 
diff --git a/apps/commerce-api/src/test/java/com/loopers/domain/point/PointModelTest.java b/apps/commerce-api/src/test/java/com/loopers/domain/point/PointModelTest.java
index 52f08f71f..02bcd0387 100644
--- a/apps/commerce-api/src/test/java/com/loopers/domain/point/PointModelTest.java
+++ b/apps/commerce-api/src/test/java/com/loopers/domain/point/PointModelTest.java
@@ -24,7 +24,7 @@ void setup() {
   @DisplayName("포인트 충전")
   @Nested
   class Charge {
-    @DisplayName("단위테스트1-0 이하의 정수로 포인트를 충전 시 실패한다.")
+    @DisplayName("단위테스트1-0으로 포인트를 충전 시 실패한다.")
     @Test
     void 실패_포인트충전_0() {
       // arrange
diff --git a/apps/commerce-api/src/test/java/com/loopers/domain/user/UserModelTest.java b/apps/commerce-api/src/test/java/com/loopers/domain/user/UserModelTest.java
index e7399e1d0..159dbdde4 100644
--- a/apps/commerce-api/src/test/java/com/loopers/domain/user/UserModelTest.java
+++ b/apps/commerce-api/src/test/java/com/loopers/domain/user/UserModelTest.java
@@ -13,7 +13,7 @@ class UserModelTest {
   UserModel userModel;
   String validMsg = "";
 
-  @DisplayName("화원가입-단위테스트1")
+  @DisplayName("회원가입-단위테스트1")
   @Nested
   class ValidID {
     @BeforeEach
@@ -50,7 +50,7 @@ void setup() {
     }
   }
 
-  @DisplayName("화원가입-단위테스트2")
+  @DisplayName("회원가입-단위테스트2")
   @Nested
   class Valid_Email {
     @BeforeEach
@@ -74,7 +74,7 @@ void setup() {
   }
 
 
-  @DisplayName("화원가입-단위테스트3")
+  @DisplayName("회원가입-단위테스트3")
   @Nested
   class Valid_Birthday {
     @BeforeEach
diff --git a/apps/commerce-api/src/test/java/com/loopers/interfaces/api/PointV1ApiE2ETest.java b/apps/commerce-api/src/test/java/com/loopers/interfaces/api/PointV1ApiE2ETest.java
index b80060562..4914412f7 100644
--- a/apps/commerce-api/src/test/java/com/loopers/interfaces/api/PointV1ApiE2ETest.java
+++ b/apps/commerce-api/src/test/java/com/loopers/interfaces/api/PointV1ApiE2ETest.java
@@ -44,7 +44,7 @@ void tearDown() {
   @DisplayName("포인트 조회")
   @Nested
   class Get {
-    @DisplayName("E2E테스트1-포인트 조회에할 경우, 보유 포인트가 반환")
+    @DisplayName("E2E테스트1-포인트 조회할 경우, 보유 포인트가 반환")
     @Test
     void 성공_포인트조회() {
       //given
@@ -111,7 +111,7 @@ class Charge {
       assertEquals(0, res.getBody().data().compareTo(new BigDecimal(1_010)));
     }
 
-    @DisplayName("E2E테스트2-X-존재하지 않는 유저로 요청할 경우, 400 Not Found 응답을 반환")
+    @DisplayName("E2E테스트2-존재하지 않는 유저로 요청할 경우, 404 Not Found 응답을 반환")
     @Test
     void 실패_ID없음_400() {
       //given
