Skip to content

구매 배송 처리와 상품 상태 관리

작성: 0chil

배송은 기존처럼 페이지 전체의 /common/delivery_trace.php 링크에서 찾은 첫 유효 송장을 구매에 적용한다. 송장 링크의 order_id를 확인하고 배송사와 invoice_no를 읽는다. 송장이 여러 개이면 경고를 남기고 첫 송장을 사용한다. 상품별 배송 상태나 상품 링크, xans-myshop-orderhistorydetailbasic 클래스가 없어도 송장 처리는 동작한다. 송장 번호의 퍼센트 인코딩이 잘못되어 디코딩할 수 없으면 해당 송장만 건너뛰고 나머지 송장과 상품 상태를 계속 파싱한다.

상품 상태는 input[name=order_id]로 요청한 주문인지 확인한 뒤 /product/detail.html?... 링크와 가장 가까운 상품 영역에서 읽는다.

형태상품 영역상태 위치예시
PC 표tr.xans-record-상품 영역 안의 .txtEm낙낙샵, 브레이크 사일런스, 바이더컬러, 빈하우스
PC 표의 상태 문단tr.xans-record-td.state > p게릴라라디오
PC 표의 직접 텍스트tr.xans-record-td.state의 ownText()잭오프
카드.ec-base-prdInfo.xans-record-.prdFoot .txtStatus빈티지욱
카드의 직접 텍스트.ec-base-prdInfo.xans-record-.prdFoot > .gLeft의 ownText()빈포레

정상 상태는 입금전, 주문접수중, 상품준비중, 배송준비중, 배송대기, 배송중, 배송완료, 구매확정이다. 상태 문구에서 한글 외 문자(공백·숫자·기호·영문 등)를 제거한 뒤 정상 여부를 판별하고, 관리 대상의 상태도 같은 값으로 저장한다. 예를 들어 배송 준비중 (1)은 배송준비중으로 판별한다. 정상 목록에 없는 취소·반품의 접수·처리 중·완료 상태와 새로운 상태 문구도 관리 대상으로 포함한다. 옵션이나 숨겨진 버튼의 문구는 상태로 읽지 않는다. 상품 링크는 확인되지만 상태를 읽지 못하거나 한글이 남지 않으면 statusText = null로 관리 대상에 포함한다.

상품 링크의 조상에 .displaynone 또는 [hidden]이 있으면 해당 영역을 수집하지 않는다. 바이더컬러처럼 숨겨진 중복 상품 영역을 상태 미확인으로 잘못 등록하지 않도록 하며, 상태 문구 안의 숨겨진 내용과 링크·버튼 문구도 제외한다.

SellerWebsiteOrderManagement에는 sellerThirdPartyOrder에서 가져온 셀러 ID, 외부 주문번호, 주문 ID, 구매 ID와 HTML의 상품 링크, 최초 상태 문구, 참고 상품명(productName), 해소 여부(resolved, 최초 false)를 저장한다. 상품명은 같은 상품 영역의 보이는 .ec-product-name에서 읽고 숨겨진 문구를 제외하며, 줄바꿈·공백은 텍스트로 정리한다. 이름이 없거나 비어 있으면 null로 저장한다. 참고용 이름이 배송 처리를 막지 않도록 저장 시 최대 255자로 제한한다. 해소 전에는 처리완료 시각(resolvedAt)과 처리 방법(resolutionMethod)이 null이며, 수동 해소 시 함께 기록한다. 상대 링크는 셀러 웹사이트 기준의 절대 링크로 변환하며 쿼리는 그대로 보존한다. 상품번호 추출이나 내부 구매상품 링크 매칭은 하지 않는다.

.ec-product-name이 없는 형태는 같은 상품 영역의 /product/detail.html?... 링크 중 보이는 텍스트가 있는 링크에서 참고 상품명을 읽는다. 썸네일의 빈 링크와 숨겨진 링크는 제외한다.

중복 기준은 구매 ID와 상품 링크다. 상태가 바뀌거나 해소 후 다시 관측되어도 같은 구매·링크의 관리 항목을 추가 생성하거나 갱신하지 않는다. 정상 상태로 돌아와도 자동 해소하지 않는다. 관리자가 기존 주문 관리 기능으로 실제 취소·환불 등을 처리하고, 관리 항목은 별도로 수동 해소한다.

새 상품 관리 기록이 생성되면 같은 트랜잭션에서 관리 대상 주문도 등록한다. 주문 단위 관리 표시를 해제한 뒤 새 상품 관리 기록이 생기면 다시 등록하지만, 기존 링크의 반복 관측은 재등록하지 않는다. 주문 단위 해제와 상품별 해소는 서로 독립적이다.

DeliveryStartTrackingJob.Handler에서 관리 항목을 저장하고 기존 OrderDeliveryService.updateDeliveries를 직접 호출한다. 추적 상태의 TRACKING → PROCESSING 조건부 갱신으로 같은 추적 작업의 중복 실행을 막으며, 관리 항목 저장을 위한 추가 주문 잠금은 사용하지 않는다. 배송 처리에 원래 있던 주문 잠금은 유지한다.

Job은 구매상품 ID·상태만 Querydsl DTO projection으로 조회하고, 구매의 발주완료 상품 ID 전체를 기존 배송 서비스에 전달한다. 구매에서 이미 배송이 시작된 상품이 있으면 새 송장을 반영하지 않는다. 내부에서 이미 취소된 상품은 배송 대상에서 제외되며, 웹사이트에서 관리 대상으로 식별되었다는 이유로 배송에서 제외하지 않는다. 배송 시작 기록도 구매 단위로 저장한다.

송장이 확인되면 추적을 종료한다. 송장이 없으면 관리 항목만 저장하고 추적을 유지한다. Job의 TransactionTemplate 안에서 송장 입력·배송 기록·관리 항목·추적 상태 변경을 함께 처리한다. 실패하면 함께 롤백한 뒤 다시 TRACKING으로 전환한다.

관리자가 구매상품을 취소했을 때 해당 구매의 모든 구매상품이 CANCELED이면, 같은 취소 트랜잭션에서 해당 구매의 배송 시작 추적을 종료한다. TRACKING·PROCESSING 기록을 TRACKED로 변경하며 다른 구매의 추적은 유지한다. 예약된 Job은 종료 상태를 확인해 건너뛰고, 실행 중인 Job의 PROCESSING 조건부 갱신도 실패하므로 추적을 다시 시작하지 않는다. 취소되지 않은 구매상품이 하나라도 남아 있으면 추적을 유지한다.

두 API 모두 ADMIN 권한이 필요하다. Controller는 요청과 응답을 담당하며, SellerWebsiteOrderManagementAdminService에서 조회·해소 로직과 트랜잭션을 처리한다.

메서드경로동작
GET/admin/seller-website/order-managements관리 항목 페이지 조회
POST/admin/seller-website/order-managements/{managementId}/resolutions수동 해소

목록에는 zeroBasedPageNumber(0 이상)와 pageSize(1 이상)가 필수다. resolved의 기본값은 false이며 orderId, purchaseId를 선택적으로 지정할 수 있다. 결과는 ID 내림차순의 PageResponse로 반환하고, 각 항목은 ID와 저장 필드 및 생성·수정 시각을 포함한다. 해소된 항목은 resolved=true로 조회한다.

해소 요청에는 처리 방법을 필수로 전달한다. resolutionMethod는 공백만 입력할 수 없으며 최대 255자다. 앞뒤 공백을 제거해 저장한다.

{"resolutionMethod": "판매자 취소 확인 후 상품 부분취소 및 환불 처리"}

해소 시 resolved=true와 서버의 현재 시각인 resolvedAt, 전달한 resolutionMethod를 함께 저장한다. 목록 응답에도 두 필드를 포함한다. 이미 해소된 항목은 최초 처리 시각과 방법을 유지하며, 유효한 해소 요청에 본문 없는 204를 반환한다. 없는 ID에는 400과 SELLER_WEBSITE_ORDER_MANAGEMENT_NOT_FOUND 오류 코드를 반환한다. 해소는 주문·결제 상태에 영향을 주지 않는다.

운영 관리의 주문 상세 API(GET /admin/orders/{orderId})는 해당 주문의 미처리·처리 완료 기록 전체를 sellerWebsiteOrderManagements로 반환한다. 주문 ID 인덱스를 사용해 한 번에 조회하며 ID 내림차순으로 반환한다.

화면은 주문 상태 아래 ‘관리 대상’ 목록에 미해소 항목을 표시한다. 셀러명·구매 ID와 참고 상품명, 감지 상태, 상품 링크, 감지 시각을 제공하며 구매 링크를 클릭하면 해당 구매 영역으로 이동해 강조한다. 상품명은 판매처 HTML에서 읽은 참고값이며 내부 구매상품과 매칭한 결과가 아니다. 이름이 없으면 ‘미확인’, 상태 문구가 null이면 ‘상태 미확인’으로 표시한다. 관리 목록에는 외부 주문번호를 표시하지 않는다. 관리 기록이 없는 주문에는 이 영역을 표시하지 않는다.

각 항목의 ‘해소 처리’에서 처리 방법을 필수로 입력하면 위 해소 API를 호출한다. 처리 중에는 중복 제출과 닫기를 막고, 실패하면 입력 내용을 유지하며 오류를 표시한다. 성공 후 최신 주문을 다시 조회해 미해소 목록을 갱신한다. 해소된 항목은 ‘처리 완료 기록’에서 처리완료 시각과 처리 방법을 확인할 수 있다.

운영 환경은 ddl-auto: validate이므로 배포 전에 필요한 스키마 변경을 적용한다.

기존 배송 시작 테이블은 변경하지 않는다. 기존 미해소 웹사이트 기록을 주문 단위 관리 대상으로 등록하는 일회성 SQL은 관리 대상 주문의 적용 절차를 따른다.

이미 모든 구매상품이 취소되었지만 배송 시작 추적이 남아 있는 구매는 기존 취소 구매 추적 종료 SQL로 정리한다. 실제 구매·주문이 일치하고 구매상품이 한 개 이상 존재하며 모두 CANCELED인 경우에만 TRACKING·PROCESSING을 TRACKED로 변경한다. 상품이 없거나 취소되지 않은 상품·상태 미확인 상품이 있으면 제외한다.

이미 TRACKED인 주문의 상태를 수집하려면 대상 주문을 선정해 TRACKING으로 전환해야 한다. 추적 종료 이후 새로 발생하는 상태 변경은 계속 감시하지 않는다.

Job 테스트는 파서 결과를 mock으로 제공해 최초 관리 항목 보존과 구매 전체 배송을 검증한다. 도메인 테스트는 처리완료 기록과 최초 기록 보존을, Repository 테스트는 필터·페이지·정렬을, 관리자 API 테스트는 권한·요청 바인딩·조회 계약·수동 해소·반복 해소 응답을 검증한다.