Skip to content

feat: 0원 체크아웃 지원#55

Open
JaeHyuckSa wants to merge 3 commits into
mainfrom
fix/zero-won-checkout
Open

feat: 0원 체크아웃 지원#55
JaeHyuckSa wants to merge 3 commits into
mainfrom
fix/zero-won-checkout

Conversation

@JaeHyuckSa

@JaeHyuckSa JaeHyuckSa commented Jul 8, 2026

Copy link
Copy Markdown
Member

배경

가격 합계가 0원인 주문은 외부 PG 결제를 만들 수 없지만, 서비스 관점에서는 재고 차감, 주문 완료 상태, 결제 완료 알림, 관리자 주문 목록에서 일반 완료 주문처럼 처리되어야 합니다. 기존 유료 결제 경로는 PortOne 양수 금액 결제를 전제로 해서 0원 체크아웃을 별도 완료 경로로 분리할 필요가 있었습니다.

AS-IS

  • 0원 단건/장바구니 주문도 PortOne 결제 준비 경로를 타서 양수 결제 금액 제약에 막힙니다.
  • PG 거래가 없는 주문 완료 상태를 표현하는 내부 PaymentHistory 생성 경로가 없습니다.
  • 결제 준비 이후 재고, 판매 가능 상태, 인당 제한이 바뀌어도 webhook 완료 시점에서 같은 기준으로 재검증하지 못합니다.
  • 장바구니 추가/삭제와 체크아웃이 겹칠 때 stale write 또는 중복 장바구니가 발생할 수 있는 구간이 있습니다.
  • 관리자 주문/환불 스키마에서 PG 거래가 없는 완료 주문의 imp_id=null 케이스와 환불 불가 응답이 명확하지 않습니다.

TO-BE

  • 0원 단건/장바구니 주문은 내부 무료 체크아웃 경로에서 주문 가능성을 잠금 기반으로 재검증한 뒤 완료 처리합니다.
  • 무료 체크아웃은 active 주문 상품을 paid로 전환하고 imp_id=null, price=0인 완료 PaymentHistory를 생성합니다.
  • 양수 금액 주문은 기존처럼 PortOne 결제 준비와 webhook 완료 경로를 사용합니다.
  • 유료 webhook 완료도 무료 체크아웃과 같은 allocation 재검증 경로를 공유하고, 완료 불가 상태면 PortOne 결제를 취소합니다.
  • 장바구니 추가/삭제 경로는 사용자 또는 주문 단위 잠금 뒤 상태를 다시 확인해 체크아웃 race를 줄입니다.
  • 공개/관리자 API 스키마에 0원 주문 응답, latest_imp_id=null, PG 거래가 없는 환불 요청의 validation error를 반영합니다.

테스트

  • pre-commit run --all-files --show-diff-on-failure
  • GitHub Actions Run lint 통과
  • GitHub Actions Run pytest (all tests) 통과
  • GitHub Actions Verify shop coverage is 100% 통과

@JaeHyuckSa JaeHyuckSa changed the title fix: support zero-won checkout fix: 0원 체크아웃 지원 Jul 8, 2026
@JaeHyuckSa JaeHyuckSa requested review from earthyoung and y00eunji July 8, 2026 15:48
@JaeHyuckSa JaeHyuckSa changed the title fix: 0원 체크아웃 지원 feat: 0원 체크아웃 지원 Jul 8, 2026
@JaeHyuckSa JaeHyuckSa requested review from MU-Software and Copilot July 8, 2026 15:48

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

0원(무료) 주문을 PortOne 결제 없이 내부 완료 경로로 처리할 수 있도록 결제/주문 검증 흐름을 분리·보강하고, 유료 webhook 완료 또한 동일한 재검증(재고/한도/옵션/티켓정보) 경로를 공유하도록 정리한 PR입니다. 또한 장바구니 갱신/체크아웃 레이스를 줄이기 위해 잠금 기반 재검증을 추가하고, 0원 주문 응답/환불 불가 케이스를 API 및 스키마·테스트에 반영했습니다.

Changes:

  • 무료(0원) 체크아웃 완료 서비스(complete_free_checkout) 도입 및 주문/알림/히스토리 생성 경로 추가
  • 상품/장바구니 “주문 가능” 검증에서 0원 허용(음수만 거부) 및 체크아웃 시점 잠금 기반 재검증/레이스 완화
  • webhook 완료 처리 시 재검증 공유 및 실패 시 PortOne 결제 취소(가능한 경우) + 0원 주문의 환불/영수증/관리자 API 스키마 보강

Reviewed changes

Copilot reviewed 30 out of 30 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
app/shop/payment_history/services.py 무료 체크아웃 완료 및 체크아웃 재검증(잠금/검증 payload 구성) 유틸 추가
app/shop/payment_history/serializers.py PortOne webhook validate/create에서 0원 결제 거부 및 체크아웃 재검증 공유
app/shop/serializers/cart_validation/product.py 상품 단위 합계 0원 허용(음수만 거부), 담기 시 사용자 락+재검증 및 cart row 재확인 추가
app/shop/serializers/cart_validation/cart.py 빈 cart 분리 에러(EMPTY) 및 0원 허용(음수만 거부)로 cart 검증 로직 조정
app/shop/order/views/orders.py 단건/장바구니 체크아웃에서 prepared_price=0이면 내부 무료 완료 경로로 즉시 완료 응답
app/shop/order/views/carts.py cart item 삭제 시 주문 row 잠금/PH 존재 재확인으로 체크아웃 레이스 완화
app/shop/order/serializers/dto.py 단건 주문 생성 201 응답 스키마용 DTO 추가(0원 즉시 완료 vs 유료 카트 공통 스키마)
app/shop/patron.py 후원자 목록 집계에서 donation_price>0 조건 추가로 0원 주문 제외
app/core/const/shop_error_messages.py 0원 허용에 맞춰 “최소 0원” 메시지 및 webhook 실패 코드(ORDER_NOT_ORDERABLE) 추가
app/shop/conftest.py order_factory에 product_price/imp_id 옵션 추가(무료 완료/imp_id null 케이스 테스트 지원)
app/shop/test/product_orderable_test.py 0원 상품 허용 및 음수 거부 케이스 테스트로 수정
app/shop/test/cart_orderable_test.py 빈 cart 에러 분리 및 0원 cart 허용/음수 거부 테스트로 수정
app/shop/test/patron_api_test.py 0원(후원 가능 설정이어도) 후원자 목록 제외 테스트 추가
app/shop/test/refund_serializer_test.py 무료 완료(imp_id=None) 주문의 환불 거부 + PortOne cancel 미호출 테스트 추가
app/shop/payment_history/test/free_checkout_service_test.py 무료 체크아웃 완료/승격/중복 처리/검증 실패 시나리오 전용 테스트 신설
app/shop/payment_history/test/webhook_serializer_test.py 0원 amount webhook 거부, 재검증 실패 시 cancel 호출, 티켓정보 사전 준비 등 테스트 보강
app/shop/payment_history/test/webhook_view_test.py 테스트 픽스처가 재검증 요구조건(티켓정보 등)과 충돌하지 않도록 설정 보정
app/shop/payment_history/test/tasks_test.py 0원 완료 알림 컨텍스트(first_paid_price=0 등) 테스트 추가
app/shop/payment_history/test/concurrent_webhook_test.py webhook 동시성 테스트에서 티켓정보 준비 추가
app/shop/order/test/orders_api_test.py 단건/카트 0원 체크아웃 즉시 완료 + PortOne/webhook 미사용 보장 테스트 추가
app/shop/order/test/orders_schema_test.py 단건 주문 생성 201 응답 스키마가 oneOf 없이 단일 ref 임을 검증하는 테스트 신설
app/shop/order/test/cart_api_test.py free checkout 경쟁 상황에서 cart add/remove 동작 및 락 전략 관련 동시성 테스트 보강
app/shop/order/test/order_model_test.py 0원 완료(imp_id=None) 주문의 환불 불가 사유/imp_id 처리 테스트 추가
app/shop/order/test/opr_model_test.py 0원 완료 OPR 환불 불가 사유 테스트 추가
app/shop/order/test/soft_delete_filtering_test.py webhook paid 전이 시 티켓정보 선행 조건을 만족하도록 테스트 보강
app/admin_api/views/shop/orders.py 관리자 환불 액션의 400 ValidationError 스키마 응답 명시
app/admin_api/serializers/shop/orders.py latest_imp_id가 null 가능해 ReadOnlyField로 직렬화 안정화
app/admin_api/test/shop/products_api_test.py 관리자 상품 생성에서 0원 가격 허용 테스트 추가
app/admin_api/test/shop/orders_api_test.py 관리자 목록/환불에서 0원 완료 주문 표시 및 환불 거부(PortOne cancel 미호출) + 스키마 테스트 추가
app/admin_api/test/shop/order_notifications_api_test.py 관리자 알림 미리보기에서 0원 완료 주문 컨텍스트 포함 테스트 추가

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread app/shop/payment_history/services.py
Comment thread app/shop/payment_history/services.py
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants