주문은 100건인데 정산은 98건이다 · 취소와 환불 · 실습
취소 요청 20건을 규칙대로 처리하기
목표
취소 요청 20건을 받은 순서대로 처리하면서, 승인취소와 매입취소를 가르고, 부분 취소 금액을 계산하고, 환불이 원결제 수단으로 돌아가지 못하는 건을 따로 세워 봅니다.
왜 중요한가
"취소해 주세요" 는 화면에서 단추 하나지만 시스템에서는 최소한 두 가지 다른 일입니다. 매입 전이면 카드사에 잡아 둔 한도를 풀어 주는 승인취소 이고, 매입 뒤면 이미 청구한 금액을 되돌리는 매입취소 입니다. 앞의 것은 우리 매출에 애초에 올라간 적이 없으므로 정산에 아무 흔적을 남기지 않고, 뒤의 것은 매출과 취소가 둘 다 남습니다. 이 차이를 모르고 대사하면 취소 건수가 두 배로 보입니다.
승인취소는 쪼갤 수 없습니다. 카드사에 잡아 둔 한도는 통째로 풀거나 그대로 두는 것뿐입니다. 그래서 매입 전 주문에 "이 품목만 빼 주세요" 가 오면 시스템은 전부 취소하고 다시 주문을 받거나, 매입까지 기다렸다가 부분 취소해야 합니다. 요구사항 회의에서 "부분 취소도 되게 해 주세요" 가 나오면 먼저 물어야 하는 것이 "매입 전입니까 후입니까" 입니다.
돌아갈 곳이 없는 환불도 있습니다. 가상계좌로 받은 돈은 원결제 수단이라는 것이 없어서 고객의 환불 계좌로 이체해야 하고, 계좌를 모르면 환불이 그 자리에서 멈춥니다. 카드 매입취소도 시간이 지나면 원카드로 돌아가지 못합니다. 이 건들은 조용히 사라지는 것이 아니라 누군가 손으로 처리할 때까지 남아 있으므로, 목록으로 뽑아내지 못하면 고객이 먼저 전화를 겁니다.
이 실습이 정한 값
환불 가능 기간·포인트 환급 순서는 회사와 결제대행사 계약마다 다릅니다. 실무에서는 결제 정책 문서에 있고, 이 실습에서는 아래로 못 박습니다.
- 기준일은
2026-09-30, 환불 가능 기간은 매입일로부터180일입니다. - 요청은
requested_at오름차순, 같으면cancel_id오름차순으로 처리합니다. 순서가 판정을 바꿉니다. - 판정은 위에서부터 봅니다. 그 주문이 이미 전체 취소된 뒤면
DUP,captured_at이 비어 있고 부분 요청이면REJECT,captured_at이 비어 있고 전체 요청이면VOID,captured_at이 있으면REFUND. - 금액:
VOID는goods_amount + ship_fee.REFUND가 전체면goods_amount + ship_fee에서 이미 부분 취소된 품목 금액을 뺀 값.REFUND가 부분이면 그 품목의item_amount이고 배송비는 건드리지 않습니다.REJECT와DUP은 0원입니다. - 환불 수단: 취소 금액 중 먼저 그 주문에서 아직 돌려주지 않은 포인트 사용액만큼을 포인트로 돌려주고, 남은 금액이 현금성 환불입니다. 현금성 금액이 0이면
NONE. 가상계좌는refund_account가 있으면TRANSFER, 없으면HOLD. 카드는VOID면ORIGIN_CARD,REFUND면 매입일에서 기준일까지가 180일 이내일 때만ORIGIN_CARD이고 넘으면HOLD.
단계
1. 오른쪽 예시의 생성기를 /root/com/make_refund.py 로 저장하고 python3 make_refund.py 로 실행하세요. payments.csv(결제 24건), ritems.csv(품목 53줄), cancels.csv(취소 요청 20건)가 만들어집니다.
2. 요청마다 판정을 /root/com/cancel_kind.csv 에 cancel_id,order_id,captured,kind,action 머리글로 적으세요. captured 는 Y 또는 N, kind 는 요청 파일의 값 그대로입니다.
3. 요청마다 취소 금액을 /root/com/cancel_amount.csv 에 cancel_id,action,amount 머리글로 적으세요.
4. 요청마다 포인트분과 현금분을 갈라 /root/com/refund_method.csv 에 cancel_id,point_amount,cash_amount,cash_method 머리글로 적으세요.
5. 현금성 환불이 멈춘 건을 /root/com/hold.csv 에 cancel_id,order_id,cash_amount,reason 머리글로 적으세요. reason 은 돌려줄 계좌가 없으면 NO_REFUND_ACCOUNT, 기간이 지났으면 WINDOW_EXPIRED 입니다.
6. 거절·중복 요청을 /root/com/rejected.csv 에 cancel_id,order_id,action,reason 머리글로 적으세요. reason 은 REJECT 면 PARTIAL_BEFORE_CAPTURE, DUP 이면 ALREADY_CANCELED 입니다.
7. /root/com/refund_total.txt 에 requests=, void_count=, refund_count=, reject_count=, dup_count=, point_total=, card_total=, transfer_total=, hold_count=, hold_total= 열 줄을 적으세요. 금액 합계는 현금성 환불만 수단별로 더한 값입니다.
8. /root/com/refund_report.md 에 ## 승인취소와 매입취소, ## 부분 취소의 금액, ## 원결제 수단으로 못 돌아갈 때, ## 거절과 중복, ## 시스템에 남겨야 할 것 다섯 절로 보고서를 쓰세요.
참고
- 판정 함수를 한 번 만들어 두고 2 부터 7 까지 그 결과를 여러 모양으로 내보내는 편이 훨씬 빠릅니다.
- 흔한 실수 1: 요청을 파일에 적힌 순서대로 처리하는 것.
requested_at으로 정렬하지 않으면 중복 판정이 뒤집힙니다. - 흔한 실수 2: 포인트를 비율로 나누는 것. 이 실습의 규칙은 포인트분을 먼저 채우는 것입니다.
- 흔한 실수 3: 보류를 "환불 실패" 로 묶는 것. 계좌가 없어 멈춘 것과 기간이 지나 멈춘 것은 다음에 할 일이 다릅니다.
단계 8개
- 결제·품목·취소 요청 데이터 만들기
- 승인취소와 매입취소 가르기
- 취소 금액 계산하기
- 포인트분과 현금분 가르기
- 돌려주지 못하고 멈춘 건 뽑기
- 거절과 중복을 따로 세우기
- 수단별 합계 내기
- 취소·환불 점검 보고서 쓰기