# 결제 기록하기

결제, 환불, 구독 해지처럼 서버만 확실히 아는 일을 allob 에 남겨요. 두 가지 방법이 있어요.

- **allob 이 결제 알림을 직접 받기(추천)**: 제품에 Dodo Payments(브랜드를 고른 연결)나 RevenueCat(프로젝트를 고른 연결)을 연결해 두면 allob 이 알림(웹훅) 주소를 만들어 그 서비스에 등록해요. 명령을 따로 실행하지 않아도 되고, 프로젝트에 웹훅 코드를 넣지 않아도 돼요. 웹의 제품 › 연결 › 매출 아래 **결제 알림**에서 상태와 받은 결제를 봐요.
- **서버에서 직접 보내기**: 다른 결제 서비스를 쓰거나 결제 처리 코드에서 직접 보내고 싶을 때 써요. 이 페이지 아래 '서버에서 직접 보내기'에 있어요.

## allob 이 결제 알림 받기

allob 은 알림마다 결제 서비스의 서명을 확인한 뒤 이렇게 해요.

1. 결제 한 줄을 남겨요. 제품, 결제한 기기, 첫 유입 경로(utm), 금액, 통화, 환불이 들어가요. 같은 알림이 다시 와도 한 번만 세요.
2. 새 결제가 들어오면 폰과 브라우저로 바로 알려요. 아래 [새 결제 알림](#새-결제-알림)에 있어요.
3. 스키마에 있는 서버 이벤트(`purchase`·`refund`·`trial_start`·`sub_start`·`sub_renew`·`sub_cancel`·`sub_expire`)를 남겨요. 스키마에 없는 이벤트는 건너뛰어요. `purchase` 는 새 결제(한 번 사는 결제·돈을 낸 구독 시작)에만 남겨요. 구독 갱신은 `sub_renew` 하나라서 퍼널·결제 전환율·결제 건수에 들어가지 않아요.
4. 웹 결제는 연결해 둔 광고·분석 도구(Meta 전환 API, TikTok Events API, GA4 Measurement Protocol)로도 보내요.
5. 손님 이름·메일은 남기지 않아요. 결제 서비스의 손님 id 도 그대로 두지 않고 계정마다 다른 값으로 바꿔 남겨요. 같은 손님은 같은 값이라 결제한 사람 수는 그대로 세요.

:::prompt
https://allob.app/docs/payments.md 를 읽고 결제 알림을 allob 이 받게 해 줘. 결제 서비스는 Dodo(또는 RevenueCat)야.
1. allob payments hooks 로 알림 주소가 있는지 봐. 없으면 제품의 Dodo 연결에 브랜드가, RevenueCat 연결에 프로젝트가 골라져 있는지 확인해(고르면 allob 이 주소를 만들어). 연결로 만들 수 없다는 경고가 있으면 allob payments connect 로 만들고, 내가 그 서비스에서 할 단계를 알려 줘.
2. 체크아웃을 만드는 코드에서 allob SDK 의 context() 를 결제 metadata 에 넣고, 같은 주문 ID 를 tx 와 purchase(value, transactionId) 에 써 줘. tx 가 없으면 결제가 광고·분석 도구로 가지 않아. 브라우저에서 purchase 를 안 부르면 주문마다 다른 ID 를 tx 로 넣어.
3. 시험 결제를 하나 보내고 allob payments list 로 들어왔는지 확인해 줘.
:::

### 알림 주소는 연결하면 생겨요

제품의 결제 연결이 준비되면 allob 이 알림 주소를 만들어 그 서비스에 등록해요. 따로 할 일은 없어요.

- **Dodo**: 제품의 Dodo 연결(운영 키)에서 이 제품의 브랜드를 골랐을 때
- **RevenueCat**: 제품의 RevenueCat 연결에서 프로젝트를 골랐을 때

연결을 넣거나, 브랜드·프로젝트를 고르거나, 다시 연결하면 바로 만들어요. 그 밖에도 한 시간마다 확인해서 빠진 제품에 만들어요. 이미 주소가 있으면 아무것도 하지 않아요.

- 알림을 직접 끊은 적이 있는 제품에는 다시 만들지 않아요. 다시 받으려면 `allob payments connect <서비스>` 를 실행해요.
- 시험(test) 키 연결로는 만들지 않아요. 시험 결제는 `--test` 로 따로 만들어요.
- 연결한 키나 로그인에 웹훅을 만들 권한이 없으면 그 연결에 경고가 떠요(연결 화면과 할 일). 아래 서비스별 안내대로 직접 넣으면 경고가 사라져요.

### 이미 서버에서 결제 이벤트를 보내고 있다면(기록만)

제품 서버가 이미 `purchase`·`refund` 같은 결제 서버 이벤트를 allob 에 보내고 있다면, allob 이 같은 이벤트를 또 쓰면 결제가 두 번 세져요. 그래서 allob 이 주소를 만들 때 이렇게 정해요.

- 최근 90일(이벤트를 보관하는 기간) 안에 제품 서버가 스키마의 결제 서버 이벤트(`purchase`·`refund`·`trial_start`·`sub_start`·`sub_renew`·`sub_cancel`·`sub_expire`) 중 하나라도 보냈으면 **기록만**으로 만들어요.
- 아니면 위 1~5 를 다 하는 보통 알림으로 만들어요.

**기록만** 알림은 결제 줄(매출·결제한 사람 수·받은 결제 목록)과 새 결제 알림만 해요. 서버 이벤트를 쓰지 않고, 광고·분석 도구(Meta 전환 API·TikTok Events API·GA4 Measurement Protocol)로도 보내지 않아요. 전환도 제품 서버가 맡고 있다고 보는 거예요. 받은 결제 목록에는 `skip:record_only` 로 보여요.

보통 알림인데 나중에 제품 서버도 같은 이벤트를 보내기 시작하면, 하루 한 번 확인해서 결제 알림 줄에 **결제가 두 번 세지고 있어요** 경고를 띄워요(`allob payments hooks` 에도). 웹의 **기록만으로 바꾸기**를 누르거나 이렇게 바꿔요.

```sh
allob payments connect dodo --record-only        # 이미 받고 있는 주소를 기록만으로 바꿔요(RevenueCat 도 같아요)
```

다시 이벤트를 쓰게 하려면 알림을 끊고(`allob payments disconnect <id> --yes`) `allob payments connect <서비스>` 로 새로 만들어요.

### Dodo Payments

제품에 Dodo 를 연결하고 브랜드를 골랐다면 할 일이 없어요. allob 이 그 연결 키로 Dodo 에 엔드포인트를 만들고 서명 비밀까지 받아 와요. 직접 만들거나 다시 만들 때는 이 명령을 써요.

```sh
allob payments connect dodo
```

웹에서는 결제 알림 › **결제 알림 받기** › Dodo Payments 를 누르면 같은 일을 해요. 알림을 끊으면 Dodo 쪽 엔드포인트도 같이 지워요.

Dodo 연결이 없거나 연결한 키로 웹훅을 만들 수 없으면 직접 넣어요.

1. Dodo 대시보드 › Developer › Webhooks 에서 엔드포인트를 추가하고, 서명 비밀(`whsec_…`)의 복사 단추를 눌러요.
2. 바로 아래 명령을 실행하면 클립보드의 비밀로 받는 주소가 만들어져요(웹에서는 서명 비밀 입력란에 붙여 넣어요).

```sh
allob payments connect dodo --secret @clipboard
```

3. 나온 주소(`https://e.allob.app/hooks/ph_…`)를 Dodo 엔드포인트 주소에 넣어요.

서명 비밀은 클립보드(`--secret @clipboard`), 표준 입력(`--secret -`), 터미널에서 묻는 칸으로만 받아요. 명령에 비밀을 그대로 적으면 셸 기록에 남아서 받지 않아요. 에이전트(MCP)는 비밀을 거치지 않는 첫 길(연결로 만들기)만 하고, 안 되면 사람이 터미널에서 할 명령을 알려 줘요.

체크아웃을 만들 때 브라우저 SDK 의 `context()` 를 통째로 결제 metadata 에 넣어 주세요. 그래야 어느 방문자의 결제인지 알 수 있어요.

```ts
const ctx = allob.context();               // { sid, us, uc, un, ttclid?, fbp?, fbc?, ttp?, gaCid?, ua?, url?, noTools? }
await createCheckout({ ..., metadata: { ...ctx, tx: orderId } });
allob.purchase(price, orderId);            // 브라우저의 Purchase — 서버 전환과 같은 거래 ID 로 하나로 묶여요
```

- `tx` 는 꼭 넣어 주세요. 그 값이 광고 도구의 중복 제거 ID 가 돼요. `tx` 가 없는 결제는 기록만 하고 광고·분석 도구로는 보내지 않아요. 브라우저 `purchase` 와 ID 가 달라 도구가 한 결제를 두 번 세게 되거든요. 브라우저에서 `purchase` 를 부르지 않는다면 주문마다 다른 ID 면 무엇이든 괜찮아요. 구독 갱신 결제는 `tx` 가 없어도 Dodo 결제 ID 로 보내요(브라우저 `purchase` 가 없는 결제라서요).
- 광고·분석 도구를 써도 되는 브라우저(동의를 받았거나 동의가 필요 없는 곳)라면 `context()` 가 광고 쿠키(`fbp`·`fbc`·`ttp`)·GA 클라이언트 ID(`gaCid`)·브라우저 정보(`ua`)·지금 페이지 주소(`url`)도 함께 줘요. 주소는 쿼리를 빼고, 주소 규칙(`privacy`)을 쓰는 제품이면 그 규칙으로 개인정보를 뺀 주소예요. Meta 전환 API 는 브라우저 정보(`ua`)가 있어야 받고, 광고 쿠키가 있어야 광고 클릭과 잘 이어져요. 동의가 없는 브라우저는 이 값 대신 `noTools` 만 줘요.
- 서비스 서버가 체크아웃을 만든다면 그 요청의 IP 와 User-Agent 를 metadata 의 `ip`·`ua` 에 직접 넣어도 돼요.
- `context()` 가 `null` 이면(계측을 끈 브라우저) 넣지 않아도 돼요.

### RevenueCat

제품에 RevenueCat 을 연결하고 프로젝트를 골랐다면 할 일이 없어요. allob 이 그 연결로 RevenueCat 프로젝트에 웹훅을 만들고 Authorization 헤더 값까지 넣어요. 직접 만들거나 다시 만들 때는 이 명령을 써요.

```sh
allob payments connect revenuecat
```

웹에서는 결제 알림 › **결제 알림 받기** › RevenueCat 을 누르면 같은 일을 해요. 알림을 끊으면 RevenueCat 쪽 웹훅도 같이 지워요. RevenueCat 로그인으로 연결했다면 늘 이렇게 돼요. 로그인이 끊겼으면 먼저 RevenueCat 을 다시 연결하라고 알려 줘요. 비밀 키로 연결했다면 키에 Project configuration 쓰기(Write) 권한이 있어야 해요.

RevenueCat 연결이 없거나 연결로 웹훅을 만들 수 없으면 직접 넣어요. 같은 명령이 주소와 Authorization 헤더 값을 보여 줘요. 그 두 값을 RevenueCat › Project settings › Integrations › Webhooks 에 넣어요. 헤더 값은 이때 한 번만 보여 줘요. 웹에서도 같은 값을 한 번만 보여 주고, **RevenueCat 에 넣었어요**를 체크해야 닫혀요. 잃어버리면 알림을 끊고 새로 만들어요.

앱 SDK 의 `buyerSid()` 를 RevenueCat 의 app user ID 로 쓰거나, 손님 속성 `allob_sid` 에 넣어 주세요. 그래야 결제가 기기와 이어져요. 앱이 유입 경로를 알고 있으면 `allob_us`·`allob_uc`·`allob_un`(utm source·campaign·content)에도 넣어 주세요.

### 새 결제 알림

새 결제가 들어오면 폰과 브라우저로 결제 한 건마다 바로 알려요. 금고 › 승인 방법에서 알림을 켠 기기로 와요.

- 제목은 제품 이름과 금액이에요(예: "Diary 결제 ₩46,900", 환불은 "Diary 환불 ₩46,900"). 아래 줄에는 구매·첫 구독·구독 갱신·환불 중 무엇인지와 스토어(App Store·Google Play) 또는 결제 서비스가 나와요.
- 알리는 것: 한 번 사는 결제, 돈을 낸 첫 구독, 구독 갱신, 환불이에요. 무료 체험 시작, 해지, 만료, 0원 결제는 알리지 않아요.
- 시험 결제, [내 결제](#내-결제-빼기)(뺀 기기·운영자 계정), 3일보다 오래된 결제(결제 서비스가 다시 보낸 지난 알림)는 알리지 않아요. 같은 알림이 다시 와도 한 번만 알려요.
- 손님 이름·메일은 넣지 않아요.
- 누르면 폰 앱은 그 제품 화면, 웹은 지표 › 매출의 결제 보기가 열려요.
- 끄려면 계정 설정 › 알림의 **새 결제·환불**을 꺼요(폰 앱은 설정 › 알림). 보통 단계 알림이라 보통 단계를 끄면 이 알림도 오지 않아요. 멤버는 받지 않아요.

### 구독 갱신

구독이 갱신되며 낸 돈은 매출(받은 돈)에는 들어가지만 새 결제는 아니에요. 그래서 결제 건수·광고로 온 결제(CAC)에 넣지 않고 "구독 갱신"으로 따로 세요.

- RevenueCat 은 갱신(`RENEWAL`)을 알려 줘요.
- Dodo 는 갱신 결제도 결제 알림(`payment.succeeded`)으로 와요. 같은 구독에 앞서 돈을 낸 결제가 있으면 갱신(`sub_renew`)으로 남겨요(0원 결제는 앞선 결제로 치지 않아요). 같이 오는 구독 갱신 알림(`subscription.renewed`)은 서버 이벤트로 남기지 않아요(갱신을 두 번 세지 않게).

### 무료 체험

무료 체험을 시작한 건 결제가 아니에요. 그래서 매출, 결제 건수, 결제한 사람 수, 결제 전환율, 광고로 온 결제에 넣지 않고 `trial_start`(체험 시작)로 따로 남겨요. `purchase` 이벤트도 남기지 않아요.

- 체험이 유료로 바뀐 첫 결제는 갱신이 아니라 새 결제(`sub_start` + `purchase`)로 세요. 체험을 시작할 때의 기기와 유입 경로를 그대로 이어받아요.
- RevenueCat 은 체험 시작(`INITIAL_PURCHASE` 의 `period_type` 이 `TRIAL`)과 유료로 바뀐 결제(`RENEWAL` 의 `is_trial_conversion`)를 알려 줘요.
- Dodo 는 체험 기간 안에 켜진 구독을 체험 시작으로 봐요. 체험을 시작할 때 오는 0원 결제(결제 수단 확인)는 결제로 세지 않아요. 체험이 끝나 처음 낸 돈은 RevenueCat 과 똑같이 `sub_start` + `purchase` 예요. 그때 Dodo 가 같이 보내는 구독 갱신 알림은 서버 이벤트로 남기지 않아요(위 [구독 갱신](#구독-갱신)).
- 체험 중에 자동 갱신을 끄거나 체험이 그대로 끝나면 구독 해지(`sub_cancel`)·구독 끝남(`sub_expire`)으로 남겨요. 체험 중엔 돈이 오가지 않아 환불로 세지 않아요.
- 체험이 얼마나 유료로 바뀌는지는 지표 › 매출 › **결제 보기**의 무료 체험에서 봐요([지표 보기](/docs/metrics#무료-체험)).

### 제품 하나에 결제 계정 하나

알림 주소는 제품마다 만들어요. 그래서 결제 계정도 제품마다 나눠 주세요.

- **Dodo**: 제품마다 브랜드를 따로 만들고, 제품의 Dodo 연결에서 그 브랜드를 골라요. 다른 브랜드의 결제 알림은 받아도 기록하지 않아요.
- **RevenueCat**: 제품마다 프로젝트 하나예요. 같은 프로젝트를 쓰는 제품이 이미 있으면 두 번째 제품에는 주소를 만들지 않아요.

### 이미 웹훅을 받고 있다면(릴레이)

결제 알림을 받는 서버가 이미 있으면, 받은 본문과 `webhook-*` 헤더(RevenueCat 은 Authorization 헤더)를 **바꾸지 않고** allob 주소로 그대로 넘겨요. allob 은 같은 서명 확인을 해요. 본문을 한 글자라도 바꾸면 서명이 맞지 않아요.

```ts
ctx.waitUntil(fetch(ALLOB_HOOK_URL, { method: "POST", headers: req.headers, body: rawBody }).catch(() => {}));
```

### 광고·분석 도구로 보내기

제품 › 연결에서 **Meta Conversions API**, **TikTok Events API**, **GA4 Measurement Protocol** 을 연결하면 웹 결제가 Purchase 로 함께 가요. 중복 제거 ID 는 브라우저의 `purchase(value, transactionId)` 와 같은 값이라 도구가 한 결제로 쳐요. 결제 알림을 받다 끊겨서 다시 보내게 돼도 같은 ID 라 Meta·TikTok 은 이벤트 ID 로 한 번만 세요. GA4 도 같은 거래 ID 의 구매는 보통 한 번만 세요.

보내지 않는 결제도 있어요.

| 경우 | 까닭 |
|---|---|
| 시험 결제(`--test` 주소, RevenueCat 의 환경이 `PRODUCTION` 이 아닌 결제) | 매출·광고 성과에 섞이지 않게 |
| 기록만 알림으로 받은 결제 | 제품 서버가 서버 이벤트와 전환을 보내요(`skip:record_only`) — 위 [기록만](#이미-서버에서-결제-이벤트를-보내고-있다면기록만) |
| 뺀 기기·브라우저·운영자 계정의 결제 | 내 결제가 광고 성과를 부풀리지 않게(`skip:excluded`) — 아래 [내 결제 빼기](#내-결제-빼기) |
| `context()` 가 `noTools` 를 알린 구매자 | 동의를 받지 않은 나라의 구매자 |
| 기기 ID(`sid`)가 없는 웹 결제 | 어느 방문자의 결제인지 몰라 도구가 광고와 잇지 못해요(`skip:no_sid`) |
| 체크아웃 metadata 에 `tx` 가 없는 웹 결제(구독 갱신 결제는 빼고) | 보내면 브라우저 `purchase` 와 중복 제거 ID 가 달라 도구가 한 결제를 두 번 세요(`skip:no_tx`). 최근 14일 안에 이런 결제가 있으면 결제 알림 줄과 `allob payments hooks` 에 안내가 떠요 |
| Meta 는 브라우저 정보(`ua`)가 없는 결제 | Meta 가 웹 전환으로 받지 않아요(`skip:no_user_agent`) |
| 앱 결제(RevenueCat) | 앱 광고 도구는 앱 SDK 몫 |
| 환불·해지 | 도구에는 구매만 보내요 |
| Meta·TikTok 은 7일이 지난 결제 | 도구가 받지 않아요 |

받은 결제와 어디로 보냈는지는 웹의 결제 알림 › **받은 결제 보기**나 명령으로 봐요. 손님 id·나라 같은 손님 정보는 보이지 않아요. 도구마다 `ok`, 보내지 않았으면 `skip:<까닭>`, 실패했으면 `fail:<까닭>` 으로 나와요. 기간·종류별 목록과 광고로 온 결제는 지표 › 매출의 **결제 보기**에서 봐요([지표 보기](/docs/metrics)).

```sh
allob payments list
allob payments hooks       # 주소마다 마지막으로 받은 때, 기록만인지, 두 번 세는 중인지
```

## 서버에서 직접 보내기

다른 결제 서비스를 쓰거나, 결제 처리 코드에서 직접 보내고 싶을 때 써요. 브라우저나 앱이 넘겨준 기기 ID 를 함께 보내면, 그 결제가 어느 방문자의 퍼널과 유입 경로에서 나왔는지 알 수 있어요.

이렇게 보내면서 Dodo·RevenueCat 도 연결해 두면, allob 이 만드는 결제 알림은 [기록만](#이미-서버에서-결제-이벤트를-보내고-있다면기록만)이 돼요. 매출과 새 결제 알림은 allob 이 받고, 결제 이벤트는 이 코드가 보낸 것만 세요.

### 에이전트에게 맡기기

:::prompt
https://allob.app/docs/payments.md 를 읽고 결제 알림(웹훅)을 받는 코드에서 allob 으로 서버 이벤트를 보내게 해 줘.
1. 스키마에 결제 이벤트("source": "server")가 없으면 추가하고 allob schema push 해.
2. allob server-key create --label payments 로 서버 키를 만들고, 이 프로젝트 호스팅의 비밀 값(환경 변수)으로 ALLOB_SERVER_KEY 에 넣는 방법을 알려 줘. 키를 코드나 저장소에 남기지 마.
3. allob.json 의 "sdk" 에 server 경로를 웹훅 코드가 있는 폴더 안(예: "server/src/allob")으로 추가하고 allob sdk pull 로 server.ts 를 받아.
4. 결제가 확인된 뒤 serverEvents() 로 이벤트를 보내. 결제 metadata 의 sid 를 같이 넣고, 결제 ID 로 이벤트 ID 를 만들어서 같은 알림이 두 번 와도 한 번만 세게 해.
5. allob 전송이 실패해도 결제 처리는 계속되게 해.
:::

:::warning
서버 키는 비밀 값이에요. 웹사이트나 앱 코드에 넣으면 누구나 볼 수 있어요. 서버의 환경 변수에만 넣으세요.
:::

### 1. 결제 이벤트 정의하기

이벤트에 `"source": "server"` 를 붙이면 서버 이벤트가 돼요. 서버 이벤트는 웹·앱 SDK 에는 생기지 않고 `server.ts` 에만 생겨요.

```json
"tracking": { "category": { "unknown": "?" } },
"enums": { "plans": ["monthly", "yearly"] },
"events": {
  "purchase": {
    "label": "결제(서버)",
    "source": "server",
    "props": { "plan": { "enum": "$plans" }, "value": { "column": "double1" } },
    "order": ["plan"]
  }
}
```

`tracking.category.unknown` 은 결제 서비스에서 온 값이 정해진 값 목록에 없을 때 대신 넣을 글자예요. 이 필드가 있어야 `server.ts` 에 `known()` 함수가 생겨요.

### 2. 서버 키 만들기

```sh
allob server-key create --label payments
```

키는 만들 때 한 번만 보여 줘요. 바로 서버의 비밀 값으로 저장하세요. Cloudflare Workers 라면 한 번에 넣을 수 있어요.

```sh
allob server-key create --label payments --json | jq -r .key | wrangler secret put ALLOB_SERVER_KEY
```

키 목록은 `allob server-key list`(키 값은 보이지 않아요), 키를 폐기하려면 `allob server-key revoke <id>` 를 실행해요. 폐기한 키는 1분 안에 멈춰요.

### 3. server.ts 받기

`allob.json` 의 `"sdk"` 에 `server` 경로를 추가해요. 결제 웹훅 코드가 있는 폴더 안을 가리키면 돼요. 웹 SDK 와 같은 폴더여도 괜찮아요.

```json
"sdk": { "web": "web/src/allob", "server": "server/src/allob" }
```

```sh
allob sdk pull                     # sdk 의 폴더를 모두 받기
allob sdk pull --target server     # server.ts 가 든 폴더만 받기(같은 폴더의 웹 SDK 도 함께)
```

아래 예시의 `./allob/` 은 이 폴더를 말해요.

### 4. 이벤트 보내기

```ts
import { serverEvents, known, values } from "./allob/server.ts";

export default {
  async fetch(req, env, ctx) {
    const payment = await verifyWebhook(req, env);   // 결제 서비스 서명 확인이 먼저
    const allob = serverEvents({ key: env.ALLOB_SERVER_KEY });
    const send = allob
      .purchase(
        { plan: known(values.purchase.plan, payment.metadata.plan), value: payment.amount / 100 },
        { sid: payment.metadata.sid, us: payment.metadata.us, uc: payment.metadata.uc, id: shortId(payment.id) },
      )
      .catch((e) => console.error(e));
    ctx.waitUntil(send);   // 결제 처리를 기다리게 하지 않아요
    return new Response("ok");
  },
};
```

| 인자 | 설명 |
|---|---|
| 첫 번째 | 스키마에 정의한 이벤트 속성 |
| `sid` | 기기 ID. 웹은 `context().sid`, 앱은 `buyerSid()` 값을 결제 metadata 로 받아 넣어요. 없으면 결제는 기록되지만 어느 기기의 결제인지는 알 수 없어요 |
| `us`·`uc`·`un` | 유입 경로(utm). 웹의 `context()` 값을 그대로 넣어요 |
| `id` | 이벤트 ID. 같은 알림이 다시 와도 한 번만 세요. 결제 ID 를 줄인 소문자·숫자 8~16자(예: sha256 앞 16자) |
| `at` | 실제로 일어난 시각(30일 이내). 없으면 받은 시각 |
| `test` | `true` 면 시험 이벤트로 보고 기록하지 않아요(시험 결제·시험 계정). 테스트 모드 알림이면 넣어 주세요 |
| `off` | 웹의 `isOff()` 값을 넣어요. 계측에서 뺀 브라우저(내 브라우저)의 이벤트는 기록하지 않아요 |
| `ua` | 방문자의 브라우저 정보(`context().ua`). 봇이면 기록하지 않아요 |

- 이벤트마다 함수가 생겨요(`purchase`, `subCancelled` 등). 여러 이벤트를 한 번에 보내려면 `send([...])` 를 써요(최대 20개).
- 결제 서비스에서 온 값은 `known(values.<이벤트>.<속성>, 값)` 으로 감싸요. 값 목록에 없으면 `tracking.category.unknown` 에 적은 글자(위 예에서는 `"?"`)로 바뀌어요.

### 응답과 에러

응답은 `{ accepted, rejected: [{ index, reason }], skipped: [{ index, reason }] }` 형식이에요. 받지 못한 항목(`rejected`)은 이유(모르는 이벤트, 값 형식, 너무 오래된 시각 등)가 함께 와요. 일부러 기록하지 않은 항목(`skipped` — 시험, 뺀 기기·브라우저, 봇)은 고칠 게 없어요. 네트워크 오류나 잘못된 키는 예외로 던지니, 결제 처리가 멈추지 않게 호출하는 쪽에서 잡아 주세요. 개발 환경처럼 키가 없을 때는 보내지 않고 넘어가게 해 두면 편해요.

### 확인하기

```sh
allob ingest events
```

최근 24시간 동안 받은 이벤트가 보여요. 1분 정도 늦게 나타나요. 실제 결제로 시험하기 어렵다면 결제 서비스의 테스트 모드 알림을 쓰세요. 테스트 결제는 `test: true` 를 넣으면 지표에 남지 않아요.

## 내 결제 빼기

내가 직접 해 본 결제는 진짜 돈이라 매출에는 그대로 들어가요. 대신 결제 수, 결제한 사람 수, 광고로 온 결제, 퍼널, 광고 도구로 보내는 전환에서는 빠져요. 결제 목록에는 **제외** 꼬리표가 붙어요. 이렇게 빠지는 결제는 셋이에요.

- 뺀 기기의 결제 — 결제의 기기 ID(`sid`)가 제품 설정 › 데이터 거르기의 뺀 기기 목록에 있을 때
- 뺀 브라우저의 결제 — 결제 정보(metadata)에 웹 `isOff()` 로 `off` 가 실려 왔을 때([내 방문 빼기](/docs/web#내-방문-빼기))
- 운영자 계정의 결제 — 제품 설정 › 데이터 거르기 › **운영자 계정**에 넣은 메일이나 사용자 ID 로 결제했을 때(Dodo 는 손님 메일·손님 ID, RevenueCat 은 앱 사용자 ID). allob 은 넣은 값을 해시로만 보관해요

Dodo 알림에는 시험 결제인지 표시가 없어요. 그래서 시험 모드의 결제 알림은 `--test` 로 만든 받는 주소로 따로 받아야 시험 결제로 걸러져요.
