Skip to content
isdnetworks
Go back

상태에 머문 것만으로 부과했다

가짜 송장 의심으로 부과된 건의 이의 문의가 왔다. 실제로 발송했다는 주장이어서 부과 로직을 봤다.

Table of contents

Open Table of contents

상황 — 실제를 안 보는 조건 넷

커맨드는 이것이었다.

// AddPenaltyRevisionForFakeInvoice

조건이 넷 있었다.

① 상품주문 상태 = 출고완료
② 채널이 예외 목록에 없음
③ 입점사가 예외 목록에 없음
④ 출고 레코드 생성일이 영업일 기준 6일 이전

AddPenaltyRevisionForFakeInvoice 의 조건 넷 어디에도 실제 배송 여부를 보는 것이 없다.

출고완료 상태에 오래 머물러 있음

가짜 송장 의심

출고완료 에 머문 시간만으로 판정하는 구조였다.

조건 ④의 출고 레코드 생성일 이 체류를 재는 기준이 되는데 그 값이 실제 출고 시점과 다르면 판정도 함께 어긋난다.

왜 체류로 잡나

실제 배송을 확인하려면 택배사 추적을 봐야 한다.

[추적 가능]  확인 후 판정
[추적 안 됨]  판정 못 함

가짜 송장이면 추적 자체가 안 될 가능성이 높다.

번호만 만들고 실제로는 안 보내는 것이라 택배사 쪽에 기록이 안 생긴다. 확인할 자료가 없다는 것이 곧 그 건의 성격인 셈이다.

추적으로 판정하면 정보가 없는 동안 아무 판단도 못 하고 가짜일수록 더 오래 못 잡는다. 잡으려는 대상이 그 방식에서 가장 잘 빠져나간다.

정상 배송  →  며칠 안에 배송완료로 넘어감
가짜 송장  →  출고완료에 머물러 있음

그래서 역으로 배송완료 로 안 넘어가는 것을 잡는다. 확인할 수 없는 것을 확인하는 대신 확인할 수 있는 것으로 대신 판정하는 방식이다.

대응 — 먼저 걸고 나중에 푸는 순서

이 방식은 실제로 보낸 건까지 걸린다는 것을 전제한다.

실제로 보냈는데

추적 연동 문제로 상태가 안 넘어감

부과됨

추적 연동이 늦으면 우리 쪽 사정으로 출고완료 에 남아 부과된다.

정상 발송인데 배송완료 로만 안 넘어간 건이 여기 걸린다. 이 오탐을 줄이는 대신 가짜를 놓치지 않는 쪽을 고른 것이다.

그래서 철회 처리가 붙어 있었다.

배송 추적에서 배송완료가 확인되면

해당 패널티 자동 제거

나중에 배송완료 가 추적에서 확인되면 그 패널티를 스스로 푼다.

[확인 후 부과]  가짜 송장은 영영 확인 안 됨 → 부과 못 함
[먼저 부과]     가짜는 남고 정상은 풀림

목적이 가짜를 잡는 것이면 이 순서가 맞고 놓치는 비용이 잘못 거는 비용보다 크다고 본 설계다.

이의 대응이 달라졌다

구조를 알고 나니 물어볼 것이 바뀌었다.

[전]  "실제로 발송했나"를 확인 — 증빙 요청
[후]  "왜 상태가 안 넘어갔나"를 확인

실제로 발송했는지는 이 판정과 무관한 물음이었다.

부과 조건 넷 어디에도 발송 여부가 없으니 증빙을 받아도 판정이 안 바뀐다. 물어야 할 것은 출고완료 에서 왜 안 넘어갔느냐였다.

봐야 할 것이 둘로 좁혀졌다.

① 상태가 왜 출고완료에 머물러 있나
② 출고 레코드의 생성일이 언제인가

둘째인 출고 레코드 생성일 이 이번 건의 답이었고 그 값이 실제 출고와 달랐다.

원인 — 두 설계가 만나는 자리

교환 재출고 건은 새 행을 만들지 않고 기존 행을 고치므로 출고 레코드 생성일 이 원본 출고일로 남는다.

원본 출고   3월 2일
재발송      3월 14일
생성일      3월 2일 (그대로)

6영업일 계산 → 이미 초과

재발송 직후인데 6영업일 이 이미 지난 것으로 계산된다.

출고 레코드 생성일 이 최근 동작 시점을 안 가리키기 때문인데 실제로는 3월 14일에 나갔지만 기준은 3월 2일에 머물러 있다.

[덮어쓰기 설계]  생성일이 최근 동작 시점이 아님
[체류 판정]      생성일 기준으로 경과를 잼

덮어쓰기 설계 는 행 수를 줄이려는 선택이고 체류 판정 은 확인할 수 없는 것을 대신 판정하려는 선택이다.

각각만 보면 둘 다 합리적이고 둘이 만나는 자리에서만 오탐이 생기므로 나란히 놓고 봐야 보인다.

제약 — 코드에 박힌 예외 목록

조건 둘과 셋을 보니 목록이 코드에 있었다.

->whereNotIn('channel', ['채널A', '채널B', '채널C', '채널D'])
->whereNotIn('provider_id', [특정 값들])

whereNotIn 목록에 한 줄을 넣으려면 코드를 고치고 배포해야 한다.

운영이 판단하는 값이 개발 배포에 묶여 있는 셈이라 penalty_exception 같은 표로 빼면 화면에서 넣을 수 있다.

penalty_exception
  type      channel / provider
  value
  reason
  created_by

reasoncreated_by 를 같이 두면 나중에 왜 예외인지도 남는다.

다만 코드에 둔 데는 이유가 있을 수도 있다. 돈이 걸린 판정이라 커밋 이력과 리뷰를 거치게 하려던 것일 수 있고 어느 쪽이 나은지는 이 목록을 얼마나 자주 바꾸느냐에 달렸다.

커맨드가 매일 자정에 도는 것도 눈에 걸렸는데 그래서 자동 부과는 부과 시각이 전부 자정 근처에 몰린다. 부과 시각만 보고도 자동인지 수동인지 갈리는 셈이라 이번 조사에서도 그 구분을 썼다.

정리


Share this post on:

Previous Post
신뢰할 수 없는 축
Next Post
지금 규약과 어긋난 옛 설정