결제 관련 API 가 둘이었다.
결제 시도
결제 확인
한 번에 안 끝난다.
Table of contents
Open Table of contents
상태가 셋이었다
확인 쪽 응답에 상태 값이 있었다.
status initiated, completed, failed
시작됨과 완료됨과 실패다. initiated 가 그 사이 상태이고 시도했지만 아직 결과를 모르는 자리다.
원인 — 두 단계로 나뉜 이유
결제가 왜 두 단계인지 생각해 봤다. 외부 결제 시스템을 거치니 우리 서버에서 결제 시스템으로 가고 거기서 결과가 온다.
결제 시스템의 응답이 바로 안 올 수 있다. 바로 오면 한 번에 끝나지만 늦게 오면 시도만 기록하고 나중에 확인해야 한다.
CURLOPT_TIMEOUT 을 넉넉히 잡아도 그 안에 안 올 수 있다. 그 결과가 언제 오는지를 우리가 정할 수 없으니 시도하는 것과 결과를 확인하는 것이 나뉘어야 했다.
미확정이 필요했다
이해가 된 지점이 이것이다. 미확정 상태가 없으면 시도했는데 결과가 안 온 건이 실패로 보인다.
상태가 성공과 실패 둘뿐이면 시도 중을 표현할 자리가 없다. 실패로 기록해 두면 나중에 성공 응답이 왔을 때 이미 처리된 뒤라 돈은 나갔는데 우리 쪽에서는 실패로 남는 상황이 생긴다.
실제로는 성공했는데 응답만 늦은 것일 수 있으니 미확정으로 두고 응답이 오면 그때 확정한다. 그리고 status 가 initiated 인 채로 오래 남은 건을 골라 crontab 이 다시 묻게 하는 것도 그 상태가 있어야 짤 수 있다.
결과 값을 같이 줬다
확인 API 응답을 봤다.
status
rvs_payment_id 결제 id
user_coin 구매 후 user의 코인
user_coin 이 같이 온다. 코인이 안 오면 결제 확인을 하고 다시 유저 정보를 조회해야 하는데 같이 오면 한 번에 끝나므로 호출이 한 번 준다.
그리고 그 시점의 값이라 확실하다. 따로 조회하면 그 사이에 또 바뀔 수 있다.
이름과 값의 방향
API 가 하나 더 있었다.
기존 구매 여부 조회
is_first (0: 없음, 1: 있음)
첫 결제인지를 묻는 API 인데 여기서 걸린 것이 있다. is_first 는 첫 번째인가로 읽히는데 설명은 기존 구매 여부이고 값은 없으면 0이고 있으면 1이다.
is_first = 1 이면 기존 구매가 있다는 뜻이라 첫 구매가 아니다. 이름과 값의 방향이 반대다.
이것을 그대로 읽으면 if (is_first) 일 때 첫 구매 보너스를 지급하게 되고 그러면 기존 구매자에게 첫 구매 보너스를 준다. 오류도 안 나고 조용히 반대로 돈다.
has_purchased 나 is_returning 같은 이름이면 값과 방향이 맞았을 것이다. has_purchased = 1 은 구매한 적 있음이라 이름만 읽어도 값의 뜻이 맞는다.
이 API 가 왜 있는지도 생각해 봤다. 첫 결제에 보너스를 주는 경우가 많으니 결제 화면에서 첫 구매인지를 알아야 하고 첫 구매면 보너스 문구를 기존 구매면 일반 문구를 보여 준다. 화면을 그리기 전에 필요한 값이다.
비슷한 것이 하나 더 있었는데 로그인 응답의 is_new 다.
is_new (0: 기존유저, 1: 신규유저)
이것은 이름과 값의 방향이 맞는다. 같은 설계서 안에서 한쪽은 맞고 한쪽은 어긋난다.
이름 짓는 규칙이 정해져 있지 않으면 사람마다 때마다 갈린다. is_new 는 맞게 지었고 is_first 는 어긋났는데 같은 문서 안에서다. 나중에 읽는 사람이 매번 설명을 확인해야 한다.
정리
- 외부 결제를 거치면 시도와 확인이 나뉜다
CURLOPT_TIMEOUT을 넉넉히 잡아도 그 안에 안 올 수 있다initiated같은 미확정이 없으면 응답이 늦은 건이 실패로 보인다- 실패로 기록하면 나중에 성공 응답이 왔을 때 어긋난다
- 미확정으로 오래 남은 건을 골라 다시 묻는 것도 그 상태가 있어야 짠다
user_coin처럼 결과 값을 같이 내려주면 호출이 한 번 준다- 따로 물어보면 그 사이에 값이 또 바뀔 수 있다
is_first인데 값은 기존 구매가 있는지였다- 그대로 쓰면 오류 없이 조용히 반대로 돈다
is_new는 맞고is_first는 어긋나 같은 문서 안에서 갈린다