Skip to content
isdnetworks
Go back

빠뜨리지 않는 방법을 먼저 정했다

결제 관련 코드를 전부 찾아야 했다. 세 번 찾았는데 매번 다른 결과가 나왔다.

Table of contents

Open Table of contents

증상 — 세 번 찾아 세 번 다른 결과

세 번의 결과가 이랬다.

$ grep -rn "payment" --include=*.php src/ | wc -l
412

$ grep -rni "payment\|pay_\|결제" --include=*.php src/ | wc -l
1,204

$ grep -rni "payment\|pay\|결제\|billing" --include=*.php --include=*.js src/ | wc -l
1,882

찾는 말을 바꿀 때마다 결과가 달라졌고 어느 것이 전부인지 알 수 없었다.

세 결과를 겹쳐 보면 한 번만 나온 파일이 꽤 됐다. 마지막 것이 가장 많이 나왔다는 사실이 그것이 전부라는 근거는 되지 않는다.

찾는 방법을 정하지 않고 찾기 시작한 것이 문제였다. 방법이 매번 다르면 결과도 매번 달라진다.

payment 만 넣으면 pay_state 를 쓰는 파일이 빠지고 pay 까지 넣으면 paycheck 같은 무관한 말이 섞인다. 검색어를 넓히는 것과 정확해지는 것이 같은 방향이 아니었다.

무엇을 찾는지부터 정했다

결제 관련 코드라는 말이 무엇을 가리키는지부터 적었다.

찾는 것
  1. 결제 API 를 부르는 자리
  2. 결제 결과를 받는 자리
  3. 결제 상태를 읽거나 쓰는 자리
  4. 결제 금액을 계산하는 자리

넷으로 나누고 나니 각각 어디를 봐야 하는지가 달라졌다.

하나의 검색어로 넷을 한꺼번에 잡으려 했던 것이 앞의 실패였다. 부르는 자리와 계산하는 자리는 애초에 같은 말로 적혀 있지 않다.

PaymentClient 를 부르는 자리에는 그 클래스 이름이 적혀 있지만 금액을 더하는 자리에는 payment 라는 말이 없다. 넷 중 어느 것을 찾느냐에 따라 실마리가 되는 말이 아예 달랐다.

조치 — 넷을 각각 다른 방법으로

1번은 부르는 클래스 이름으로 찾았다.

$ grep -rn "PaymentClient\|PgGateway" --include=*.php src/

2번은 콜백이 들어오는 자리라서 라우팅에서 찾았다.

$ grep -rn "pay.*callback\|pay.*return\|pay.*notify" application/config/routes.php

3번은 컬럼 이름이 정해져 있어 그것으로 찾았다.

$ grep -rn "pay_state\|payment_no\|paid_amount\|pay_date" --include=*.php src/

세 가지는 각각 찾을 실마리가 분명했다. PgGatewayroutes.phppay_state 처럼 한 가지로만 적히는 이름이 있었기 때문이다.

4번이 어려웠다. 금액을 담는 변수 이름이 제각각이었다.

$ grep -rn "amount\|price\|금액" --include=*.php src/ | wc -l
3,102

3,102줄은 읽어서 가려낼 수 있는 양이 아니었다. 이름으로 찾는 길이 막혔으니 다른 실마리가 필요했다.

데이터에서 거꾸로 찾았다

코드 쪽 이름은 제각각이어도 표 이름은 하나다.

SELECT DISTINCT table_name FROM information_schema.columns
WHERE table_schema = 'order'
  AND (column_name LIKE '%pay%' OR column_name LIKE '%amount%');

표 열한 개가 나왔고 그다음은 그 이름으로 코드를 뒤졌다.

$ for t in $(cat tables.txt); do
    echo "=== $t"; grep -rln "$t" --include=*.php src/
  done

변수 이름은 사람마다 다르게 짓지만 표 이름은 한 가지로 적힌다.

이쪽으로 찾으니 4번이 열한 개 표를 건드리는 파일 목록으로 좁혀졌다. 이름에서 자료로 실마리를 옮긴 것이 이번에 효과가 컸다.

information_schema 쪽은 컬럼 이름이 paid_amounttotal_price 든 한 번에 잡힌다. 코드에서 amount 로 3,102줄을 받던 것이 표 열한 개와 그것을 읽는 파일 목록으로 정리됐다.

검증 — 돌 때 잡는 장치

찾는 것으로 끝내지 않고 놓친 것이 드러나게 했다.

// PaymentClient 를 안 거치고 직접 부르는 것을 잡는다
class HttpClient {
    public function request(string $url, ...) {
        if (str_contains($url, PG_HOST) && !$this->calledFromPaymentClient()) {
            log_message('error', "결제 API 직접 호출: " . $this->callerInfo());
        }
        ...
    }
}

PG_HOST 로 나가는데 PaymentClient 를 안 거친 호출이면 기록에 남는다.

며칠 돌리니 두 곳이 걸렸는데 앞서 찾기에서 안 나온 자리였다.

$host = $config['pg_' . $env . '_host'];
$url = 'https://' . $host . '/pay';

주소를 문자열로 조립해서 부르고 있어 어떤 검색어로도 안 잡힌다.

정적으로 찾는 것에는 이런 한계가 있었다. 실제로 돌 때 잡는 것을 같이 둬야 그 구멍이 메워진다.

callerInfo 가 남긴 파일과 줄 번호가 있어서 걸린 자리를 바로 열어 볼 수 있었다. 기록에 호출자를 안 남겼으면 무언가 직접 부른다는 것만 알고 어디인지는 다시 찾아야 했다.

찾은 것과 찾은 방법을 남겼다

목록에 결과만 적지 않고 방법도 같이 적었다.

결제 관련 코드 목록 (2017-10-15)

API 호출        src/Payment/PaymentClient.php
                src/Legacy/pay_direct.php      ← 정리 대상
콜백 수신       application/controllers/PayCallback.php
                legacy/pay/return.php
상태 읽기/쓰기   (14곳, 별첨)
금액 계산       src/Order/AmountCalculator.php
                legacy/order/calc.php

찾은 방법
  1. 클래스명 검색
  2. 라우팅 검색
  3. 컬럼명 검색
  4. 결제 테이블을 건드리는 파일 역추적
  5. 실행 중 직접 호출 감지 (3일간)

다음에 같은 방법으로 찾으면 같은 결과가 나온다는 것이 이 다섯 줄의 값어치였다.

pay_direct.php 처럼 정리 대상이라고 표시한 것도 그 자리에 함께 적었다. 목록을 다시 만드는 사람이 이미 판단이 끝난 항목을 두고 같은 고민을 하지 않게 된다.

목록은 새로 생기는 것이 있으면 그날부터 낡는다.

// PaymentClient 를 거치지 않으면 예외
if (!$this->isAllowedCaller()) {
    throw new LogicException('결제 API 는 PaymentClient 를 통해 호출합니다.');
}

기록만 남기던 것을 예외로 바꿔서 새로 만들 때 그 자리에서 걸리게 했다.

예외로 바꾸기 전에 며칠 동안 기록만 남기게 두고 정상적인 호출이 걸리지 않는지 먼저 봤다. 바로 예외로 걸었으면 멀쩡한 결제가 막혔을 수도 있다.

정리


Share this post on:

Previous Post
전용 테이블을 공용으로 바꾸는 일
Next Post
2,217개의 금칙어