출금을 내부 이체로 바꿔 달라는 요청이 둘 왔다. 하나는 됐고 하나는 실패 팝업이 떴다.
Table of contents
Open Table of contents
증상 — 같은 유형의 두 건
두 건의 출금 상태를 봤다.
건 A signed (서명 완료, 미전파)
건 B broadcast (전파 완료)
signed 와 broadcast 로 서로 달랐다.
같은 유형의 요청인데 화면에서의 결과가 갈린 이유가 여기 있을 것 같았다. 그래서 updateWithdrawalType 이 이 값을 어떻게 다루는지 봤다.
원인 — 상태별로 갈렸다
전환 로직에 상태 분기가 있었다.
public function updateWithdrawalType($withdrawal, $newType)
{
if ($withdrawal->status === 'broadcast') {
return $this->fail('철회 불가');
}
// signed 면 타입 직접 변경
$withdrawal->type = $newType;
...
}
broadcast 면 철회 불가로 막고 signed 면 타입을 그대로 바꾼다.
broadcast 까지 간 건은 체인에 나갔으니 그 자리에서 막는 것이 정책상 맞았다. 그런데 건 B 는 철회가 안 되자 취소 신청 자체를 시도했고 거기서도 실패했다.
제약 — 가능 금액과 대체 트랜잭션
취소 신청 검증을 봤다.
$cancelable = $this->calcCancelableAmount($withdrawal);
if ($cancelable <= 0) {
return $this->fail('취소 가능 금액 없음');
}
calcCancelableAmount 가 0 을 내면 신청이 거부된다.
계산 안쪽에 조건이 하나 더 있었다.
private function calcCancelableAmount($withdrawal)
{
if (!$withdrawal->lock_active) {
return 0;
}
...
}
원본의 잔액 잠금 플래그 lock_active 가 0 이면 가능 금액이 0 이 된다.
더 보니 조건이 하나 더 있었다. 수수료를 올려 다시 보낸 대체 트랜잭션이 이미 확정 상태였다.
대체 트랜잭션 status confirmed
confirmed 면 그 대체 건을 먼저 정리해야 한다.
코인이 나갔는데 내부 이체로 바꾸면 나간 코인의 처리가 안 정해진다. confirmed 인 대체 건의 정리가 선행되지 않으면 그것이 어디에도 속하지 않는 상태로 남는다.
전체 흐름 — 셋이 순서였다
조건이 셋이고 순서가 있었다.
1. 출금 상태 확인 → 전파 완료면 직접 처리 필요
2. 대체 트랜잭션 확인 → 확정이면 정리 선행
3. 잠금 플래그 확인 → 0 이면 가능 금액 0
1 이 통과해야 2 를 보는 순서이고 하나라도 안 맞으면 거기서 막힌다.
그런데 화면에서는 실패만 보인다. 어느 조건에서 막혔는지를 안 알려 주니 운영자는 문의를 넣는 수밖에 없다.
검증 — 셋을 한 번에 뽑는 쿼리
이 유형의 문의를 받으면 셋을 함께 뽑는다.
-- 1. 출금 상태
SELECT status FROM withdrawals WHERE id = ?;
-- 2. 대체 트랜잭션
SELECT id, status FROM withdrawal_replacements
WHERE origin_withdrawal_id = ?;
-- 3. 잠금 플래그
SELECT lock_active FROM balance_locks WHERE withdrawal_id = ?;
status 와 대체 건의 status 와 lock_active 가 한 번에 나온다.
세 값을 나란히 놓으면 어디서 막혔는지가 바로 갈린다. 막힌 자리마다 대응도 달랐다.
1 에서 막힘 → 직접 처리 (정식 경로 불가)
2 에서 막힘 → 대체 건 정리 선행
3 에서 막힘 → 플래그 확인. 왜 0 인지 조사
화면의 같은 실패 팝업이 실제로는 세 가지 다른 상황을 덮고 있었다.
대응 — 메시지에 조건을 담기
근본은 실패 팝업이 이유를 안 알려 주는 데 있었다.
[보이는 것] 실패했습니다
[실제] 조건 셋 중 하나에서 막힘
진단 쿼리를 운영자에게 주는 것과 메시지에 조건을 담는 것 두 가지 방법 중 뒤쪽을 골랐다. 이유는 조건을 표시하면 문의 자체가 줄어서다.
"전파 완료 상태에서는 철회할 수 없습니다"
"대체 트랜잭션이 확정 상태입니다"
"취소 가능 금액이 없습니다"
같은 실패인데 세 가지로 다르게 안내된다.
체인에 나가 전환할 수 없다는 것과 금액이 남지 않았다는 것은 받는 쪽에서 할 일이 다르다. 이것을 별도 개선 항목으로 적었다.
정리
- 같은 요청이 상태에 따라 되고 안 된다
- 전환 조건이 여럿이고 순서가 있다
- 출금 상태와 대체 트랜잭션과 잠금 플래그 셋을 확인한다
- 하나라도 안 맞으면 막히는데 화면은 실패만 보여 준다
- 진단 쿼리 셋을 한 번에 뽑으면 어디서 막혔는지 나온다
- 같은 실패인데 대응이 셋으로 갈린다
- 메시지에 조건을 표시하면 문의가 준다