모든 비교 글

백엔드

주문 금액 계산 엔진 보수

Java 21·Spring Boot 3 주문 금액 계산 API의 할인 순서, 원 단위 반올림, 주문 단위 할인 배분, 배송비와 입력 검증 오류를 공개 계약을 유지한 채 보수하고 경계 테스트를 추가한다.

발행일
2026년 7월 28일
비교 모델
GPT-5.6 Luna, GPT-5.6 Sol, GPT-5.6 Terra
참여 범위
이 과업에서 실행한 모델만 비교
데이터
발행 콘텐츠

Task

성공 조건

  • 공개 DTO·enum·JSON 필드·컨트롤러 경로와 빌드 설정을 유지하고, 계산 규칙은 서비스 또는 도메인 코드에 둔다.
  • 상품·회원·쿠폰 할인을 정해진 순서와 대상에 적용하고, 금액을 부동소수점 없이 원 단위 버림으로 계산한다.
  • 회원·쿠폰 할인을 최대 잔여법으로 정확히 배분하며, 동률은 입력 순서를 따르고 0원 대상도 안전하게 처리한다.
  • 쿠폰 임계값·상한과 할인 후 상품 금액 기준의 기본·원격 배송비를 모든 경계에서 정확히 계산한다.
  • null·공백·음수·잘못된 수량·미정의 enum·JSON 숫자 변환 및 산술 오버플로를 계약된 400 오류로 처리한다.
  • 공통 프롬프트의 필수 시나리오와 행별 금액을 MockMvc로 검증하고, 격리된 작업 사본에서 실제 테스트 결과를 남긴다.

Common conditions

핵심 조건

  • Java 21·Spring Boot 3의 기존 주문 금액 API 계약과 빌드 설정을 유지하면서 계산 서비스와 최소 오류 처리를 보수한다.
  • 상품→회원→쿠폰 순서, BOOK 회원 할인 제외, 최대 잔여법 배분, 쿠폰 임계값·상한, 할인 후 배송비를 정수 연산으로 구현한다.
  • 잘못된 입력과 모든 long 오버플로를 동일한 400 JSON으로 처리하고, 13개 필수 시나리오와 행별 배분을 MockMvc로 검증한다.

Model results

모델별 결과

GPT-5.6 LunaOpenAI / GPT-5.6 Luna / 추론 high / 2026-07-28

정수·BigInteger 기반 계산과 최대 잔여법 배분, 통합 400 오류 처리를 4개 구현 파일과 1개 MockMvc 테스트 파일에 담은 보수안.

gpt-5.6-luna-high

구현 요약

  • 주문 행별 상품 할인과 주문 단위 회원·쿠폰 할인을 요구된 순서로 계산하도록 OrderPricingService를 보수했습니다.
  • 회원 할인과 쿠폰 할인은 BigInteger 기반의 최대 잔여법으로 비례 배분하고, 소수 부분이 같은 경우 입력 순서를 유지합니다.
  • 상품 할인율은 버림 처리하며, 패션 상품의 수량 조건을 3개 이상으로 적용했습니다.
  • 쿠폰 임계값과 배송비를 모든 선행 할인 적용 후 상품 금액 기준으로 계산했습니다.
  • Math.multiplyExact, Math.addExact, Math.subtractExact와 JSON 파싱 오류 처리를 사용해 개별 곱셈·합산·최종 계산 오버플로를 계약된 400 응답으로 변환했습니다.
  • null, 빈 주문, 공백 상품 ID, 음수 단가, 잘못된 수량, null 필드, 정의되지 않은 enum을 INVALID_PRICING_REQUEST 오류로 처리했습니다.

변경 파일

  • verification/work/12-order-pricing-engine/gpt-5.6-luna-high/src/main/java/com/samebrief/pricing/service/OrderPricingService.java
  • verification/work/12-order-pricing-engine/gpt-5.6-luna-high/src/main/java/com/samebrief/pricing/service/InvalidPricingRequestException.java
  • verification/work/12-order-pricing-engine/gpt-5.6-luna-high/src/main/java/com/samebrief/pricing/api/PricingController.java
  • verification/work/12-order-pricing-engine/gpt-5.6-luna-high/src/main/java/com/samebrief/pricing/api/PricingExceptionHandler.java
  • verification/work/12-order-pricing-engine/gpt-5.6-luna-high/src/test/java/com/samebrief/pricing/OrderPricingControllerTest.java

공개 DTO·enum·JSON 필드명·컨트롤러 경로·Gradle 설정은 변경하지 않았습니다.

검증

실행 명령:

./gradlew test

결과:

BUILD SUCCESSFUL
tests=10, failures=0, errors=0

구현 테스트에서는 상품 할인 버림, 패션 수량 경계, 회원 할인 대상 제외, 최대 잔여법 배분, 쿠폰 임계값·상한, 할인 후 배송비, 원격 배송비, 무료 배송 경계, 입력 오류와 곱셈·합산 오버플로를 검증했습니다.

트레이드오프

금액 비례 계산의 중간 곱셈이 long을 넘을 수 있으므로 표준 라이브러리의 BigInteger를 사용했습니다. 금액 도메인에 맞는 정확성을 확보하는 대신 단순한 long 연산보다 구현과 계산 비용이 조금 증가합니다.

전체 원문 보기

Recommendation

이 과업의 추천 모델: GPT-5.6 Sol

세 구현 모두 같은 독립 채점 22개를 전부 통과해 기능 정확성은 동률이다. GPT-5.6 Sol은 제출물 안에 31개 MockMvc 테스트를 남겨 nullable 필드, 범위 밖 JSON 숫자, 세 종류 최종 산술 오버플로와 0원 행까지 가장 넓게 회귀 방지했고, 구현 변경 범위도 5개 파일로 유지해 종합 추천한다.

추천 근거 보기

판단 조건

  1. null·공백·음수·잘못된 수량·미정의 enum·JSON 숫자 변환 및 산술 오버플로를 계약된 400 오류로 처리한다.
  2. 공통 프롬프트의 필수 시나리오와 행별 금액을 MockMvc로 검증하고, 격리된 작업 사본에서 실제 테스트 결과를 남긴다.

반영한 평가 항목

정확성 · 완성도 · 실무성 · 가독성 · 효율성

잘 맞는 경우

  • 주문 계산 규칙을 보수한 뒤 경계 회귀 테스트까지 넓게 남겨야 하는 백엔드 작업
  • 부동소수점 없이 비례 배분과 long 오버플로를 함께 다뤄야 하는 금액 계산 코드

주의할 점

  • 추천 우위는 독립 채점의 기능 차이가 아니라 자체 테스트 범위와 제출 완성도에서 나온다.
  • 검증은 로컬 MockMvc 수준이며 실제 서버 배포, 성능, 동시 요청과 운영 관측성은 확인하지 않았다.

Prompt and environment

공통 프롬프트와 실행 환경

프롬프트 전문 보기
# 12. 주문 금액 계산 엔진 보수

## 과업

Java 21과 Spring Boot 3으로 작성된 주문 금액 계산 API의 기존 구현을 보수해 주세요. 현재 구현에는 할인 적용 순서, 원 단위 반올림, 주문 단위 할인 배분, 배송비 계산과 입력 검증 오류가 섞여 있습니다.

공개 API 계약은 변경하지 않고 모든 계산 규칙을 만족하도록 구현을 수정한 뒤, 경계 조건을 검증하는 자동화 테스트를 추가하세요.

## 비교 실행 격리 규칙

이 과업을 여러 Codex 모델에 맡겨 비교할 때는 아래 절차를 반드시 지킵니다. `<run-id>`는 해당 실행에 지정한 출력 파일명에서 `.md`를 뺀 값입니다. 예: `gpt-5.6-luna-high`.

### 실행 준비

1. 각 모델 실행을 시작하기 전에 새 대화나 새 세션을 열어 이전 모델의 컨텍스트를 완전히 비웁니다.
2. 편집자는 수정되지 않은 원본 하네스를 모델별 전용 작업 디렉터리에 복사합니다.

   ```bash
   pricing_run_dir="verification/work/12-order-pricing-engine/<run-id>"
   if [ -e "$pricing_run_dir" ]; then
     echo "작업 디렉터리가 이미 존재합니다: $pricing_run_dir"
     exit 1
   fi
   mkdir -p "$pricing_run_dir"
   rsync -a \
     --exclude .gradle \
     --exclude build \
     verification/harnesses/spring-boot-3-java21-pricing/ \
     "$pricing_run_dir/"
   ```

3. 기존 작업 디렉터리를 재사용하거나 그 위에 다시 복사하지 않습니다. 같은 모델을 재실행할 때도 새 `<run-id>`를 사용합니다.
4. 모델의 작업 디렉터리는 `verification/work/12-order-pricing-engine/<run-id>`로 고정합니다.
5. 모든 모델에 이 `brief.md` 전문과 자신의 `<run-id>`만 전달합니다. 프롬프트, reasoning effort와 사용 가능한 도구 조건도 모델마다 같게 유지합니다.

### 모델이 지켜야 할 범위

- `verification/harnesses/spring-boot-3-java21-pricing` 원본을 직접 수정하지 않습니다.
- 자신에게 지정된 `verification/work/12-order-pricing-engine/<run-id>` 안의 코드와 테스트만 수정합니다.
- 다른 모델의 작업 디렉터리, `content/comparisons/12-order-pricing-engine/outputs`, 게시용 `index.md`, 편집자용 채점 자료와 이전 모델의 응답을 열람하거나 참고하지 않습니다.
- 다른 모델이 만든 코드, 테스트, 설명을 복사하거나 이어서 작업하지 않습니다.
- 구현과 자체 테스트가 끝나기 전에는 결과 Markdown을 작성하지 않습니다.
- 자체 작업 사본에서 `./gradlew test`를 실제로 실행한 뒤, 변경 파일·테스트 결과·발견한 오류·해결 방식과 트레이드오프를 마지막 응답에 정리합니다.

### 실행 완료 후

1. 편집자는 모델의 마지막 응답을 `content/comparisons/12-order-pricing-engine/outputs/<run-id>.md`에 저장합니다.
2. 모델별 작업 사본은 서로 덮어쓰지 않고 채점이 끝날 때까지 보존합니다.
3. 독립 채점은 모델 실행과 컨텍스트가 종료된 뒤 편집자가 동일한 채점 스위트로 각각 수행합니다.
4. 다음 모델을 실행하기 전에 다시 새 컨텍스트를 시작합니다.
5. 세 결과와 독립 채점 기록이 모두 준비된 뒤에만 게시용 `index.md`를 만듭니다.

## 실행 환경

- Java 21
- Gradle 8.x
- Spring Boot 3.x, Spring Web MVC
- JUnit 5, MockMvc
- 금액 단위는 대한민국 원
- 외부 데이터베이스, 메시지 브로커, 캐시, 파일 시스템과 외부 API는 사용하지 않음
- 실행 명령은 `./gradlew test`

## 제공되는 시작 코드 계약

시작 저장소에는 실행 가능한 Gradle 애플리케이션과 아래 구성요소가 들어 있습니다.

```text
com.samebrief.pricing
├── PricingApplication.java
├── api
│   ├── PricingController.java
│   ├── PricingErrorResponse.java
│   ├── PricingRequest.java
│   └── PricingResponse.java
├── domain
│   ├── Category.java
│   ├── CouponType.java
│   ├── MemberLevel.java
│   └── Region.java
└── service
    └── OrderPricingService.java
```

요청·응답 DTO와 enum의 공개 필드, JSON 이름, 컨트롤러 경로는 기존 API 계약이므로 변경할 수 없습니다. `PricingErrorResponse`는 오류 응답 형식을 고정하기 위해 제공되지만 시작 코드에서는 사용되지 않습니다. 계산 서비스와 내부 구현은 자유롭게 고칠 수 있고, 필요한 내부 helper와 테스트를 추가할 수 있습니다.

`build.gradle.kts`, Gradle Wrapper와 애플리케이션 시작 클래스는 변경하지 마세요. 새로운 외부 의존성도 추가하지 마세요. JDK 표준 라이브러리는 제한 없이 사용할 수 있습니다.

## API 계약

`POST /api/v1/orders/price`는 다음 형식의 JSON을 받습니다.

```json
{
  "lines": [
    {
      "productId": "food-1",
      "category": "FOOD",
      "unitPrice": 10000,
      "quantity": 2
    }
  ],
  "memberLevel": "REGULAR",
  "couponType": "NONE",
  "region": "LOCAL"
}
```

정상 응답은 HTTP 200과 다음 형식의 JSON을 반환합니다.

```json
{
  "itemSubtotal": 20000,
  "itemDiscount": 0,
  "memberDiscount": 0,
  "couponDiscount": 0,
  "merchandiseTotal": 20000,
  "shippingFee": 3000,
  "grandTotal": 23000,
  "lines": [
    {
      "productId": "food-1",
      "grossAmount": 20000,
      "itemDiscount": 0,
      "memberDiscount": 0,
      "couponDiscount": 0,
      "finalAmount": 20000
    }
  ]
}
```

모든 응답 금액은 소수점 없는 0 이상의 정수여야 합니다.

## 계산 규칙

아래 순서를 바꾸지 않습니다.

### 1. 상품 합계

- 각 주문 행의 `grossAmount`는 `unitPrice × quantity`입니다.
- `itemSubtotal`은 모든 `grossAmount`의 합입니다.
- 같은 `productId`가 여러 행에 있어도 합치지 않고 입력 순서대로 각각 계산합니다.

### 2. 상품 할인

- `FOOD`: 상품 할인이 없습니다.
- `BOOK`: 수량과 관계없이 해당 행 `grossAmount`의 10%를 할인합니다.
- `FASHION`: 해당 행의 `quantity`가 3개 이상일 때만 `grossAmount`의 15%를 할인합니다.
- 각 행에서 할인액의 소수점 이하는 버립니다.
- `itemDiscount`는 각 행의 상품 할인 합계입니다.

### 3. 회원 할인

- `REGULAR`은 회원 할인이 없습니다.
- 회원 할인 대상은 `BOOK`이 아닌 행이고, 대상 금액은 각 행의 상품 할인 후 금액입니다.
- `GOLD`는 회원 할인 대상 금액 합계의 5%를 할인합니다.
- 주문 전체 회원 할인액의 소수점 이하는 한 번만 버립니다. 행별로 반올림하거나 버리지 않습니다.
- 계산된 회원 할인액을 대상 행의 상품 할인 후 금액에 비례해 배분합니다.
- 회원 할인 대상 행이 없거나 대상 금액 합계가 0원이면 회원 할인과 행별 배분액은 모두 0원입니다.

### 4. 쿠폰 할인

쿠폰은 상품 할인과 회원 할인을 모두 적용한 뒤 사용합니다.

- `NONE`: 할인이 없습니다.
- `FIXED_5000`: 쿠폰 적용 직전 상품 금액이 30,000원 이상이면 5,000원을 할인하고, 미만이면 할인하지 않습니다.
- `RATE_10`: 쿠폰 적용 직전 상품 금액이 50,000원 이상이면 10%를 할인합니다. 소수점 이하는 버리고 최대 할인액은 10,000원입니다.
- 쿠폰 할인은 남아 있는 상품 금액을 초과할 수 없습니다.
- 계산된 쿠폰 할인액을 모든 행의 회원 할인 후 금액에 비례해 배분합니다.
- 쿠폰 적용 직전 상품 금액이 0원이면 쿠폰 할인과 행별 배분액은 모두 0원입니다.

### 5. 주문 단위 할인의 행별 배분

회원 할인과 쿠폰 할인은 각각 아래 방식으로 배분합니다.

1. 각 대상 행에 `전체 할인액 × 행의 대상 금액 ÷ 모든 대상 행의 금액 합계`를 계산하고 소수점 이하를 버립니다.
2. 버림 때문에 남은 금액은 버리기 전 소수 부분이 큰 행부터 1원씩 배분합니다.
3. 소수 부분이 같으면 원래 요청에서 앞에 있는 행을 우선합니다.
4. 어느 행도 해당 단계 직전 금액보다 많은 할인을 받을 수 없습니다.
5. 행별 배분액의 합은 주문 전체 할인액과 정확히 같아야 합니다.
6. 대상 금액 합계가 0원이면 배분하지 않고 모든 행에 0원을 기록합니다. 0으로 나누어 서버 오류가 발생해서는 안 됩니다.

부동소수점 오차가 결과에 영향을 주지 않게 구현하세요.

### 6. 상품 결제액과 배송비

- 각 행의 `finalAmount`는 `grossAmount - itemDiscount - memberDiscount - couponDiscount`입니다.
- `merchandiseTotal`은 모든 행 `finalAmount`의 합입니다.
- 배송비는 모든 할인을 적용한 `merchandiseTotal`을 기준으로 계산합니다. `itemSubtotal`을 기준으로 하지 않습니다.
- `merchandiseTotal`이 50,000원 이상이면 기본 배송비는 0원, 미만이면 3,000원입니다.
- `REMOTE` 지역은 `merchandiseTotal`과 관계없이 5,000원의 추가 배송비가 붙습니다. `LOCAL` 지역에는 추가 배송비가 없습니다.
- 응답의 `shippingFee`는 기본 배송비와 추가 배송비의 합입니다. 기본 배송비가 0원인 `REMOTE` 주문의 `shippingFee`는 5,000원입니다.
- `grandTotal`은 `merchandiseTotal + shippingFee`입니다.

## 입력 오류

다음 입력은 HTTP 400과 아래 오류 형식으로 처리합니다.

```json
{
  "code": "INVALID_PRICING_REQUEST",
  "message": "주문 정보를 확인해 주세요."
}
```

- `lines`가 `null`이거나 비어 있음
- 주문 행, `productId`, `category`, `unitPrice`, `quantity`, `memberLevel`, `couponType`, `region` 중 하나가 `null`
- `productId`가 빈 문자열이거나 공백 문자열
- `unitPrice`가 음수
- `quantity`가 1 미만
- 곱셈, 합산 또는 최종 계산이 `long` 범위를 벗어남
- 요청 JSON의 enum 값이 정의되지 않은 문자열

`unitPrice`가 0인 행은 유효한 입력이며 `grossAmount` 0원으로 계산합니다. 0원 행이 섞여 있어도 할인 배분과 배송비 계산은 위 규칙 그대로 동작해야 합니다.

`long` 범위를 벗어나는 경우는 세 가지를 모두 포함합니다. 첫째는 `unitPrice × quantity`처럼 개별 곱셈이 넘치는 경우, 둘째는 여러 행의 금액을 합산할 때 넘치는 경우, 셋째는 요청 JSON의 숫자 자체가 `long`으로 변환되지 않는 경우입니다. 세 경우 모두 같은 400 응답으로 처리합니다.

잘못된 입력이 서버 오류나 음수 금액 응답으로 이어져서는 안 됩니다.

## 필수 검증 시나리오

JUnit 5와 MockMvc 테스트로 최소한 다음 시나리오를 검증하세요. 각 시나리오는 `memberLevel`·`couponType`·`region`을 표기한 순서로 고정합니다. 금액은 모두 `unitPrice` × `quantity` 형태로 적었습니다.

1. `REGULAR`·`NONE`·`LOCAL`에서 `FOOD` 10,000원 2개와 `BOOK` 15,000원 1개를 주문하면 `itemSubtotal` 35,000원, `itemDiscount` 1,500원, `merchandiseTotal` 33,500원, `shippingFee` 3,000원, `grandTotal` 36,500원이고 `BOOK` 행의 `finalAmount`는 13,500원이다.
2. `REGULAR`·`NONE`·`LOCAL`에서 `FASHION` 10,001원 3개를 주문하면 상품 할인은 버림이 적용된 4,500원이고 해당 행의 `finalAmount`는 25,503원이다.
3. `GOLD`·`NONE`·`LOCAL`에서 `FOOD` 10,001원 1개와 `FOOD` 10,010원 1개를 주문하면, 대상 금액 합계 20,011원의 5%인 1,000.55원을 주문 단위로 한 번만 버린 1,000원이 전체 회원 할인이다. 버림 소수 부분이 큰 첫 행이 잔여 1원을 받아 두 행에 각각 500원씩 배분되고, `finalAmount`는 9,501원과 9,510원, `grandTotal`은 22,011원이다.
4. `REGULAR`·`FIXED_5000`·`LOCAL`에서 각각 10,000원인 `FOOD` 세 행을 주문하면 쿠폰 할인 5,000원이 입력 순서대로 1,667원, 1,667원, 1,666원 배분되고 `grandTotal`은 28,000원이다.
5. `REGULAR`·`RATE_10`·`LOCAL`에서 각각 40,000원인 `FOOD` 세 행을 주문하면 쿠폰 적용 직전 금액이 120,000원이므로 쿠폰 할인은 상한이 적용된 10,000원이고, 3,334원·3,333원·3,333원으로 배분되며 `shippingFee`는 0원, `grandTotal`은 110,000원이다.
6. `REGULAR`·`FIXED_5000`·`LOCAL`에서 `FOOD` 52,000원 1개를 주문하면 `merchandiseTotal`이 47,000원이 되어 기본 배송비 3,000원이 붙고 `grandTotal`은 50,000원이다.
7. `REGULAR`·`NONE`·`REMOTE`에서 `FOOD` 60,000원 1개를 주문하면 기본 배송비는 0원이지만 추가 배송비가 붙어 `shippingFee` 5,000원, `grandTotal` 65,000원이다.
8. 회원 할인 대상 제외를 두 입력으로 각각 검증한다.
   - `GOLD`·`NONE`·`LOCAL`에서 `BOOK` 10,000원 1개와 `FOOD` 20,000원 1개를 주문하면 전체 회원 할인 1,000원이 모두 `FOOD` 행에 배분되고 `BOOK` 행의 `memberDiscount`는 0원이며, `finalAmount`는 9,000원과 19,000원, `grandTotal`은 31,000원이다.
   - `GOLD`·`NONE`·`LOCAL`에서 `BOOK` 10,000원 1개만 주문하면 회원 할인 대상 금액 합계가 0원이므로 `memberDiscount`는 0원이고 `grandTotal`은 12,000원이다.
9. `REGULAR`·`FIXED_5000`·`LOCAL`에서 `FOOD` 29,999원 1개를 주문하면 쿠폰 적용 직전 금액이 임계값 미만이므로 쿠폰 할인은 0원이고 `grandTotal`은 32,999원이다.
10. `REGULAR`·`RATE_10`·`LOCAL`에서 `FOOD` 49,999원 1개를 주문하면 쿠폰 적용 직전 금액이 임계값 미만이므로 쿠폰 할인은 0원이고 `grandTotal`은 52,999원이다.
11. `REGULAR`·`NONE`·`LOCAL`에서 `FASHION` 10,000원 2개를 주문하면 수량 조건을 만족하지 않아 상품 할인은 0원이고 `grandTotal`은 23,000원이다.
12. `REGULAR`·`NONE`·`LOCAL`에서 `FOOD` 25,000원 2개를 주문하면 `merchandiseTotal`이 정확히 50,000원이므로 `shippingFee`는 0원이고 `grandTotal`은 50,000원이다.
13. 다음 입력이 모두 계약된 400 응답을 반환한다.
   - `lines`가 빈 배열
   - `lines`가 `null`
   - `productId`가 공백 문자열
   - `unitPrice`가 `-1`
   - `quantity`가 `0`
   - `region`이 `null`
   - `memberLevel`이 `"PLATINUM"`처럼 정의되지 않은 문자열
   - `unitPrice` `9223372036854775807`과 `quantity` `2`의 곱셈 오버플로
   - `unitPrice`가 각각 `9223372036854775807`인 두 행의 합산 오버플로

테스트는 최종 합계만 확인하지 말고 주문 전체 할인액, 행별 할인 배분액과 각 행의 최종 금액도 함께 검증해야 합니다. 위 시나리오 중 3·5·8은 행별 배분액을 확인하지 않으면 통과 여부를 판정할 수 없습니다.

## 구현 조건

- 금액 계산에 `double`과 `float`을 사용하지 않습니다.
- 공개 API 계약을 테스트에 맞춰 임의로 변경하지 않습니다.
- 검증을 우회하기 위한 입력별 하드코딩을 하지 않습니다.
- 요청 DTO를 계산 중 변경하거나 요청의 `lines` 순서를 바꾸지 않습니다.
- Controller에는 HTTP 요청·응답 변환만 두고 계산 규칙은 서비스 또는 도메인 코드에 둡니다.
- 계산 규칙마다 별도의 클래스 계층이나 디자인 패턴을 만들 필요는 없습니다. 현재 요구사항에 필요한 수준으로 구현합니다.
- 데이터베이스, 영속 엔티티, 비동기 처리와 동시성 제어는 이 과업 범위에 포함하지 않습니다.

## 제출물

- 시작 저장소의 코드를 직접 수정하고 테스트를 추가하세요.
- 변경 이유를 설명하기 전에 구현과 테스트를 완료하고 `./gradlew test`를 실제로 실행하세요.
- 마지막 응답에는 변경한 파일, 테스트 결과, 발견한 기존 오류, 선택한 해결 방식과 주요 트레이드오프를 간략히 정리하세요.
- 명세 밖 기능이나 외부 인프라는 추가하지 마세요.

## 최종 요청

주어진 코드베이스를 조사해 주문 금액 계산 API를 위 계약에 맞게 보수하고, 모든 필수 시나리오와 경계 조건을 로컬 자동화 테스트로 검증해 주세요.
  • Java 21 · Gradle 8.14.3
  • Spring Boot 3.5.16 · Spring Web MVC
  • JUnit 5 · MockMvc · 대한민국 원 단위
  • 고정 메모리 계산 · 외부 인프라 없음