비버웍스
경력근거: 결제 승인과 취소 엔진, 원천사 및 VAN사 연동 중계 엔진, 매입, 대사, 정산 등 PG 전 영역의 API 및 DB 스키마 직접 설계
비버웍스는 식당·카페·무인매장 같은 오프라인 매장에 포스·키오스크·테이블오더를 구독으로 공급하고, 결제 정산 플랫폼 비버페이와 전자지급결제대행 서비스 비버PG를 운영해요. 비버페이는 스마트폰을 카드단말기로 쓰는 앱으로, 단말기 없이 결제를 받아야 하는 꽃집·케이크 가게·방문 판매자 같은 사장님이 카드 결제와 취소를 한 앱에서 해요.
공고는 이 자리가 할 일로 '결제 승인과 취소 엔진'부터 정산까지 PG 전 영역의 설계를 꼽고, 자격 요건에 '결제, 금융, 정산 등 데이터 정합성이 1원 단위까지 엄격하게 관리되어야 하는 핵심 도메인' 경험을 적었어요. 카드 매출은 결제한 날이 아니라 정해진 영업일 수가 지난 뒤 수수료를 뗀 금액으로 들어오고, 그사이 주말·공휴일이나 취소가 끼면 들어오는 날과 금액이 결제할 때 생각한 것과 달라져요. 결제와 결제 사이에 정산 주기나 수수료율이 바뀌면, 결제마다 어느 조건이 붙는지에 따라 들어오는 날과 금액이 갈려요.
공고는 에이전틱 AI를 1차 개발 도구로 들여 결제 시스템을 바닥부터 새로 구축하고 있다고 적었어요. 이 자리는 무엇을 어떻게 만들지 직접 정하고 비버PG 시스템을 설계·구축·운영하게 돼요.
주요 업무로 결제 승인과 취소 엔진, 원천사·VAN사 연동 중계 엔진, 매입·대사·정산 등 PG 전 영역의 API와 DB 스키마 설계를 꼽았어요. 자격 요건에는 요구사항부터 RESTful API 명세와 DB 원장 스키마까지 처음부터 끝까지 설계해 본 경험을 적었어요.
자격 요건에 결제·금융·정산처럼 데이터 정합성을 1원 단위까지 엄격하게 관리해야 하는 도메인 경험을 적었어요. 우대사항에는 복잡한 수수료 체계와 영세·중소 가맹점 차액정산 같은 국내 PG 업무를 모델링해 본 경험을 꼽았어요.
비버페이로 카드 결제를 받는 사장님이 재료비·월세처럼 돈 나갈 날을 앞두고 휴대폰에서 여는 입금 예정 화면이에요. 화면을 연 그 시각을 기준으로, 앞으로 들어올 입금마다 어느 결제가 얼마로 들어오는지, 취소된 결제가 그 입금에서 빠지는지 다음 입금에서 차감되는지 알려 줘요. 받은 데이터를 담을 DB 설계와 기술·구조는 지원자가 정해요.
비버페이로 카드 결제를 받는 케이크 가게·꽃집·카페 같은 가맹점 사장님이에요. 카드 매출은 수수료를 뗀 금액이 며칠 뒤 영업일에 들어오고, 이미 받은 결제가 취소되면 입금에서 빠지기도 하고 들어온 뒤 다음 입금에서 차감되기도 해서, 언제 얼마가 들어올지 미리 잡기 어려워요.
비버페이로 카드 결제를 받는 가맹점이에요. 한 행이 가맹점 한 곳이고 merchant_id 가 키예요. merchant_name 은 사장님 화면에 보이는 가맹점 이름으로 가맹점마다 다르고, business_type 은 업종이에요. 다른 표의 merchant_id 는 모두 이 표의 merchant_id 를 가리켜요. 큰 데이터는 가맹점 330곳 전부이고, 시작용은 그 가운데 솔솔 케이크 공방(MC-10229) 한 곳을 같은 값으로 잘라 낸 것이에요.
전체 1행 가운데 앞 1행이에요.
| merchant_id | merchant_name | business_type |
|---|---|---|
| MC-10229 | 솔솔 케이크 공방 | 디저트·케이크 |
1행 전부를 JSON 파일 하나로 받을 수 있어요.
같은 형식으로 만든 330행이에요. 위 데이터가 이 안에 그대로 들어 있어요(한 줄에 한 행, gzip 압축).
미리보기와 내려받는 데이터 모두 이 형식(JSON Schema)을 따라요.
{
"type": "object",
"description": "가맹점 한 곳. 키는 merchant_id.",
"additionalProperties": false,
"required": [
"merchant_id",
"merchant_name",
"business_type"
],
"properties": {
"merchant_id": {
"type": "string",
"pattern": "^MC-[0-9]{5}$",
"description": "가맹점 번호. 이 표의 키예요."
},
"merchant_name": {
"type": "string",
"description": "사장님 화면에 보이는 가맹점 이름. 가맹점마다 달라요."
},
"business_type": {
"type": "string",
"enum": [
"카페",
"디저트·케이크",
"꽃집",
"네일·속눈썹",
"타투",
"무인 매장",
"팝업·행사 판매",
"방문 수리·시공",
"펜션"
],
"description": "업종."
}
}
}가맹점마다 적용된 수수료와 정산 주기의 기록이에요. 한 행이 한 가맹점에 valid_from 시각부터 적용된 조건이고, (merchant_id, valid_from) 묶음이 키예요. merchant_id 는 merchants.merchant_id 를 가리켜요. 한 조건은 같은 가맹점의 다음 행 valid_from 전까지 그 가맹점의 승인에 적용되고, valid_from 은 payment_events.event_at 과 같은 결제 서버 시계로 적은 한국 시간이에요. 이 데이터에서는 가맹점의 매출 구간(sales_tier)에 따라 수수료율이 정해져요. fee_rate_bp 는 부가세를 포함한 수수료율을 만분율 정수로 적은 값이고(220 이면 2.20%), 한 결제의 수수료는 승인 금액에 이 요율을 곱해 원 미만을 버린 금액이에요. settlement_days 는 가맹점이 고른 정산 주기로, 승인한 날(한국 날짜)의 다음 영업일부터 세어 몇 번째 영업일에 입금되는지를 뜻해요. 가맹점에 처음 적용된 조건, 2026년 8월 14일 0시 승인분부터 다시 매긴 매출 구간의 조건(구간이 그대로여도 행이 있어요), 가맹점이 정산 주기를 줄인 조건이 각각 한 행이에요. 큰 데이터는 가맹점 330곳의 조건 649행 전부이고, 시작용은 솔솔 케이크 공방의 조건 3행 전부예요.
전체 3행 가운데 앞 3행이에요.
| merchant_id | valid_from | sales_tier | fee_rate_bp | settlement_days |
|---|---|---|---|---|
| MC-10229 | 2026-05-11T00:00:00+09:00 | 영세 | 220 | 5 |
| MC-10229 | 2026-08-14T00:00:00+09:00 | 영세 | 220 | 5 |
| MC-10229 | 2026-10-08T00:00:00+09:00 | 영세 | 220 | 2 |
3행 전부를 JSON 파일 하나로 받을 수 있어요.
같은 형식으로 만든 649행이에요. 위 데이터가 이 안에 그대로 들어 있어요(한 줄에 한 행, gzip 압축).
미리보기와 내려받는 데이터 모두 이 형식(JSON Schema)을 따라요.
{
"type": "object",
"description": "한 가맹점에 valid_from 부터 적용된 조건. 키는 (merchant_id, valid_from) 묶음.",
"additionalProperties": false,
"required": [
"merchant_id",
"valid_from",
"sales_tier",
"fee_rate_bp",
"settlement_days"
],
"properties": {
"merchant_id": {
"type": "string",
"pattern": "^MC-[0-9]{5}$",
"description": "조건이 적용되는 가맹점. merchants.merchant_id 를 가리켜요."
},
"valid_from": {
"type": "string",
"format": "date-time",
"description": "이 조건이 적용되기 시작한 승인 시각. 결제 서버 시계의 한국 시간(+09:00) ISO 8601. 같은 가맹점의 다음 행 valid_from 전까지 적용돼요."
},
"sales_tier": {
"type": "string",
"enum": [
"영세",
"중소1",
"중소2",
"중소3",
"일반"
],
"description": "가맹점의 매출 구간."
},
"fee_rate_bp": {
"type": "integer",
"minimum": 1,
"description": "부가세를 포함한 수수료율, 만분율 정수(220 = 2.20%). 수수료는 승인 금액 × fee_rate_bp ÷ 10000 에서 원 미만을 버린 금액이에요."
},
"settlement_days": {
"type": "integer",
"enum": [
1,
2,
3,
5,
7
],
"description": "가맹점이 고른 정산 주기. 승인한 한국 날짜의 다음 영업일부터 세어 이 수만큼째 영업일에 입금돼요."
}
}
}입금일을 세는 데 쓰는 달력이에요. 한 행이 하루이고 date(한국 날짜)가 키예요. is_business_day 가 true 인 날만 영업일이고, 토요일·일요일과 공휴일·대체공휴일은 false 예요. holiday_name 은 공휴일 이름이고 공휴일이 아니면 null 이에요. 결제와 취소는 영업일이 아닌 날에도 기록되지만 입금은 영업일에만 있어요. 다른 표가 이 표를 키로 가리키지는 않아요. 큰 데이터는 2026년 8월 1일부터 12월 31일까지 153일(추석 연휴, 광복절·개천절 대체공휴일, 한글날 포함)이고, 시작용은 그 가운데 10월 1일부터 16일까지 16일을 그대로 잘라 낸 것이에요.
전체 16행 가운데 앞 5행이에요.
| date | is_business_day | holiday_name |
|---|---|---|
| 2026-10-01 | true | — |
| 2026-10-02 | true | — |
| 2026-10-03 | false | 개천절 |
| 2026-10-04 | false | — |
| 2026-10-05 | false | 개천절 대체공휴일 |
16행 전부를 JSON 파일 하나로 받을 수 있어요.
같은 형식으로 만든 153행이에요. 위 데이터가 이 안에 그대로 들어 있어요(한 줄에 한 행, gzip 압축).
미리보기와 내려받는 데이터 모두 이 형식(JSON Schema)을 따라요.
{
"type": "object",
"description": "하루. 키는 date.",
"additionalProperties": false,
"required": [
"date",
"is_business_day",
"holiday_name"
],
"properties": {
"date": {
"type": "string",
"format": "date",
"description": "한국 날짜 YYYY-MM-DD. 이 표의 키예요."
},
"is_business_day": {
"type": "boolean",
"description": "입금이 있는 영업일이면 true. 토·일요일과 공휴일·대체공휴일은 false 예요."
},
"holiday_name": {
"type": [
"string",
"null"
],
"description": "공휴일 이름. 공휴일이 아니면 null 이에요."
}
}
}가맹점이 받은 카드 결제의 승인과 취소 기록이에요. 한 행이 기록 한 건이고 event_id 가 키예요. 같은 결제의 승인과 취소는 같은 payment_id 로 이어지고, 한 결제에는 승인(approval) 한 건과 많아야 취소(cancel) 한 건이 있어요. 취소 행은 언제나 같은 payment_id 의 승인 행보다 늦게 기록됐고, 같은 표 안에 그 승인 행이 있어요. merchant_id 는 merchants.merchant_id 를 가리켜요. amount_krw 는 원 단위 정수이고, 이 데이터의 취소는 모두 전액 취소라 취소 행의 금액은 승인 금액과 같아요. event_at 은 결제 서버가 승인·취소를 기록한 한국 시간이에요. 이 데이터에서는 취소가 그 결제의 정산일 0시(한국 시간)보다 앞서 기록되면 그 결제가 정산일 입금에서 빠지고, 정산일 0시부터 기록되면 정산일 입금에는 들어간 뒤 취소가 기록된 한국 날짜의 다음 영업일 입금에서 승인 금액에서 그때 뗀 수수료를 뺀 금액만큼 차감돼요. 차감이 잡힌 날의 입금 합계가 0원보다 작아지는 경우는 이 데이터에 없어요. 정산 주기가 바뀌기 직전과 직후의 결제, 밤늦게나 자정 직후에 기록된 결제·취소, 정산일 전날 밤과 정산일 아침에 들어온 취소, 입금된 뒤에 들어온 취소, 공휴일·주말과 가게가 쉬는 날이 사이에 낀 결제가 들어 있어요. 큰 데이터는 2026년 8월 1일부터 10월 13일 23시 59분 59초까지 가맹점 330곳의 기록 330,710건 전부이고, 시작용은 솔솔 케이크 공방의 10월 1일 0시부터 13일 끝까지 기록 37건 전부예요(그 안의 취소는 모두 같은 구간의 승인을 가리켜요).
전체 37행 가운데 앞 5행이에요.
| event_id | payment_id | merchant_id | event_type | amount_krw | event_at |
|---|---|---|---|---|---|
| EV-30270204 | PM-20266067 | MC-10229 | approval | 7,500 | 2026-10-01T13:05:41+09:00 |
| EV-30270964 | PM-20266822 | MC-10229 | approval | 68,000 | 2026-10-01T15:20:09+09:00 |
| EV-30272092 | PM-20267935 | MC-10229 | approval | 36,000 | 2026-10-01T18:40:27+09:00 |
| EV-30274231 | PM-20270033 | MC-10229 | approval | 45,000 | 2026-10-02T11:05:18+09:00 |
| EV-30276333 | PM-20272097 | MC-10229 | approval | 9,000 | 2026-10-02T16:30:52+09:00 |
37행 전부를 JSON 파일 하나로 받을 수 있어요.
같은 형식으로 만든 330,710행이에요. 위 데이터가 이 안에 그대로 들어 있어요(한 줄에 한 행, gzip 압축).
미리보기와 내려받는 데이터 모두 이 형식(JSON Schema)을 따라요.
{
"type": "object",
"description": "승인 또는 취소 한 건. 키는 event_id.",
"additionalProperties": false,
"required": [
"event_id",
"payment_id",
"merchant_id",
"event_type",
"amount_krw",
"event_at"
],
"properties": {
"event_id": {
"type": "string",
"pattern": "^EV-[0-9]{8}$",
"description": "기록 번호. 이 표의 키예요."
},
"payment_id": {
"type": "string",
"pattern": "^PM-[0-9]{8}$",
"description": "결제 번호. 같은 결제의 승인과 취소가 같은 값이에요. 결제마다 승인 한 건, 취소는 많아야 한 건이에요."
},
"merchant_id": {
"type": "string",
"pattern": "^MC-[0-9]{5}$",
"description": "결제를 받은 가맹점. merchants.merchant_id 를 가리켜요. 같은 결제의 승인과 취소는 같은 가맹점이에요."
},
"event_type": {
"type": "string",
"enum": [
"approval",
"cancel"
],
"description": "approval 은 승인, cancel 은 취소예요."
},
"amount_krw": {
"type": "integer",
"minimum": 1,
"description": "승인 또는 취소 금액(원, 정수). 취소는 모두 전액 취소라 승인 금액과 같아요."
},
"event_at": {
"type": "string",
"format": "date-time",
"description": "결제 서버가 승인·취소를 기록한 시각. 한국 시간(+09:00) ISO 8601, 초 단위. merchant_terms.valid_from·schedule_views.viewed_at 과 같은 시계예요."
}
}
}사장님이 휴대폰 앱에서 입금 예정 화면을 연 기록이에요. 한 행이 화면을 한 번 연 것이고 view_id 가 키예요. merchant_id 는 그 사장님의 가맹점으로 merchants.merchant_id 를 가리켜요. viewed_at 은 앱의 요청을 결제 서버가 받은 한국 시간으로 payment_events.event_at 과 같은 시계이고, 이 기능은 viewed_at 을 '지금'으로 삼아요. 그 시각 뒤에 기록된 결제·취소는 화면을 연 때에는 아직 일어나지 않은 일이에요. 같은 날 여러 번 연 기록과, 화면을 연 뒤에 취소나 새 결제가 들어온 경우가 있어요. 결제·취소 기록이 8월 1일 0시부터라, 그 전부터 정산 조건이 적용된 가맹점의 기록은 7월 결제가 가장 긴 정산 주기로도 모두 입금된 뒤인 8월 12일부터 있고, 8월 1일 이후에 처음 조건이 적용된 가맹점의 기록은 그날부터 있어요. 큰 데이터는 이 기간 가맹점 사장님들이 연 기록 8,742건 전부이고, 시작용은 솔솔 케이크 공방 사장님이 10월 13일에 연 3건 전부예요.
전체 3행 가운데 앞 3행이에요.
| view_id | merchant_id | viewed_at |
|---|---|---|
| VW-4009925 | MC-10229 | 2026-10-13T08:40:12+09:00 |
| VW-4009970 | MC-10229 | 2026-10-13T13:55:41+09:00 |
| VW-4010038 | MC-10229 | 2026-10-13T21:20:05+09:00 |
3행 전부를 JSON 파일 하나로 받을 수 있어요.
같은 형식으로 만든 8,742행이에요. 위 데이터가 이 안에 그대로 들어 있어요(한 줄에 한 행, gzip 압축).
미리보기와 내려받는 데이터 모두 이 형식(JSON Schema)을 따라요.
{
"type": "object",
"description": "입금 예정 화면을 한 번 연 기록. 키는 view_id.",
"additionalProperties": false,
"required": [
"view_id",
"merchant_id",
"viewed_at"
],
"properties": {
"view_id": {
"type": "string",
"pattern": "^VW-[0-9]{7}$",
"description": "화면을 연 기록의 번호. 이 표의 키예요."
},
"merchant_id": {
"type": "string",
"pattern": "^MC-[0-9]{5}$",
"description": "화면을 연 사장님의 가맹점. merchants.merchant_id 를 가리켜요."
},
"viewed_at": {
"type": "string",
"format": "date-time",
"description": "앱의 요청을 결제 서버가 받은 시각. 한국 시간(+09:00) ISO 8601. 이 기능이 '지금'으로 삼는 시각이에요."
}
}
}