SEAN TEAM
Work · DIGITAL RECEIPT PLATFORM --:-- 서울 · 로컬 스택에서 도는 MVP

영수증은
처음부터
디지털이어야 합니다.

Receipt

품목도, 부가세도, 승인번호도이미 POS 안에 다 있습니다.종이에 찍혀 버려질 뿐입니다.

ABOUT · WHAT IT DOES

Digital Receipt 는 결제하는 순간 POS 가 만든 영수증을 고객 앱으로 바로 보내는 전자 영수증 플랫폼입니다. 고객은 계산대에서 30초마다 바뀌는 QR 을 보여 주고, 직원은 멤버십 바코드처럼 찍습니다. 영수증은 반품 기한과 보증 만료일이 계산된 채로 도착합니다. 사진을 찍어 올리거나 메일로 받는 방식이 아니라, POS 의 구조화된 기록을 그대로 넘깁니다.

한 매장 파일럿을 돌릴 수 있는 로컬 빌드입니다. 여기 걸어 둔 POS 시뮬레이터는 화면만 있고 서버는 연결돼 있지 않습니다. 공개 출시는 별도 체크리스트를 먼저 통과해야 합니다.

01 — Checkout

계산은 그대로, 영수증 주소만 붙습니다.

Digital Receipt POS 시뮬레이터 실제 화면 — 고객 QR · 결제 · 영수증 3단계, 장바구니 3품목(공급가액 ₩41,500 · 부가세 ₩4,150 · 결제 금액 ₩45,650), 오른쪽에 실패 실험 스위치와 거래 이벤트 기록
  1. 고객 QR → 결제 → 영수증결제 흐름은 그대로 두고, 영수증을 보낼 주소만 연결합니다.
  2. 실패 실험실중복 제출 · 금액 불일치 · 품목 누락을 일부러 만들어 봅니다.
  3. 가맹점에는 가명만매장은 고객을 자기 매장에서만 통하는 가명으로 봅니다.
02 — Customer app

영수증이 반품 기한을 달고 도착합니다.

고객 앱 홈 실제 화면 — 이번 달 구매 ₩472,500, 5건의 디지털 영수증, ‘반품 마감이 가까워요’ 안내와 날짜별 영수증
영수증 QR 실제 화면 — 회전하는 QR 과 남은 초, ‘오프라인 사용 가능’, ‘90초 이내 · 한 번만 사용 · 개인정보 없음’
인사이트 실제 화면 — 카테고리별 합계와 ‘AI는 사용하지 않아요’ 안내
  1. 이번 달 구매영수증마다 반품 가능 기간이 남은 날짜로 붙습니다.
  2. 영수증 QR90초 이내 · 한 번만 사용 · 개인정보 없음. 네트워크 없이 만들어집니다.
  3. 규칙 기반 인사이트합계와 분류는 영수증 데이터로만 계산합니다. AI 는 쓰지 않습니다.
03 — From counter to app

한 번의 스캔, 네 개의 장면.

앞의 셋은 POS 시뮬레이터, 마지막은 고객 앱 화면입니다. ②의 토큰은 저장소의 공개 데모 키로 만든 실제 형식의 RC1 토큰입니다.

  1. ① Cart

    계산은 평소처럼

    품목과 공급가액, 부가세, 결제 금액은 POS 가 이미 구조화해 들고 있습니다. 이 기록을 종이에 찍기 전에 가로채는 것이 출발점입니다.

  2. ② Customer QR

    RC1 토큰 하나

    QR 에는 RC1:kid:counter:mac 형식의 토큰이 들어 있습니다. MAC 은 HMAC-SHA256 을 65비트로 자른 값이고, 30초 단위 시각은 계산에만 섞이고 전송되지 않습니다. 서버는 마지막으로 본 카운터를 기억해, 찍어 간 화면을 다시 쓰지 못하게 합니다.

  3. ③ Failure drills

    일부러 망가뜨려 봅니다

    같은 거래를 두 번 보내고, 합계에 100원을 더해 봅니다. 서버는 Idempotency-Key 와 가맹점·거래번호 자연키 두 겹으로 중복을 막고, 금액과 품목·결제 합계가 맞지 않으면 받지 않습니다.

  4. ④ Lands in the app

    반품 기한이 붙어서

    영수증은 수집되는 순간의 반품 정책을 함께 저장합니다. 매장이 내일 정책을 바꿔도 지난주 영수증의 기한은 움직이지 않고, 앱은 남은 날짜를 셉니다.

POS 시뮬레이터 장바구니 — 아로마 핸드워시 ₩29,000, 리넨 키친 타월 ₩12,000, 쇼핑백 ₩500, 결제 금액 ₩45,650
고객 QR 스캔 칸에 RC1 토큰을 붙여 넣은 상태 — ‘고객 연결하기’ 버튼이 켜졌다
실패 실험실에서 중복 제출과 금액 불일치를 켠 상태 — 결제 금액이 ₩45,750 으로 100원 늘었다
고객 앱 홈 — ‘반품 마감이 가까워요’ 안내와 OLIVE YOUNG ₩32,800 영수증의 ‘반품 가능 · 9일 남음’

영수증 한 장에 붙는 것들.

기한, 분류, 기록의 주인. 모두 규칙으로 정해지고, 서버와 앱이 같은 규칙 패키지로 계산합니다.

RC1HMAC-SHA256 영수증 QR 실제 화면 — QR 과 남은 10초, ‘90초 이내 · 한 번만 사용 · 개인정보 없음’ 30초마다 바뀌는 QR네트워크 없이 기기 안의 비밀키로 만듭니다. 고정된 개인정보가 없고, 같은 고객도 가맹점마다 다른 가명으로 보입니다.
규칙 기반 인사이트 실제 화면 — 전자제품 ₩359,000 등 카테고리별 합계와 ‘AI는 사용하지 않아요’ AI 없는 분류카테고리와 합계는 영수증의 구조화된 데이터로 규칙에 따라 계산합니다. 정확성의 문제이자 단가의 문제입니다.
SQLite내보내기 첫 실행 안내 실제 화면 — ‘구매 기록은 당신 것입니다.’ 기기 안에 저장해 비행기 모드에서도 열린다는 설명 기록은 기기에영수증은 기기 안 SQLite 에 캐시돼 비행기 모드에서도 열립니다. 언제든 JSON 으로 내보낼 수 있고, 계정 삭제는 7일 유예 뒤에 처리됩니다.
OTP 복구 시작 화면 실제 캡처 — ‘영수증을 받을 준비만 하면 됩니다.’ 설명과 전화번호 입력 칸 전화번호는 원문으로 두지 않습니다전화번호로 시작하면 기기를 잃어도 구매 내역을 되찾습니다. 번호는 인증에만 쓰고, 서버에는 복구용 식별자만 남깁니다.

POS 요청마다 1시간짜리 POS 토큰HMAC 본문 서명5분 시계 허용nonce 중복 차단Idempotency-Key가맹점·거래번호 자연키해시 체인 감사 로그 검사가 붙습니다.

그리고 계산대를 향한 호출은 실패해도 판매를 막지 않는 쪽으로 끝납니다. 응답이 늦으면 종이를 찍고 나중에 다시 보냅니다.

01

판매를 막지 않습니다

영수증 때문에 계산대 줄이 느려지면 도입될 수 없습니다. 가맹점 앱의 POS 호출은 전부 4~8초의 시간 제한을 둡니다. 끝없이 기다리는 요청은 실패가 아니라 멈춤이기 때문입니다.

02

영수증은 덧붙이기만 합니다

영수증 행은 고쳐 쓰지 않습니다. 환불과 취소는 조정 행과 이벤트로 쌓고, 상태는 거기서 다시 계산합니다. 판매보다 먼저 온 환불은 24시간 보관했다가 판매가 들어오면 적용합니다.

03

정책은 수집 시점에 고정

반품 정책은 영수증이 들어오는 순간 스냅숏으로 함께 저장됩니다. 옛 영수증을 그릴 때 현재 정책을 다시 읽지 않습니다. 가맹점의 정책 변경이 이미 받은 기한을 옮기면 안 되기 때문입니다.

04

규칙 엔진은 순수합니다

packages/domain 에는 입출력도, 난수도, Date.now() 도 없습니다. 현재 시각은 항상 인자로 받습니다. 런타임 의존성이 없고, 서버와 앱이 같은 패키지를 그대로 가져다 씁니다.

05

돈은 정수와 통화

금액은 최소 단위 정수와 통화 코드로 다닙니다. 원화에는 소수점이 없고, 100 으로 몰래 나누거나 곱하지 않습니다. 맨 숫자가 금액으로 모듈 경계를 넘지 않게 합니다.

06

로그에 남기지 않는 것

품목, 전화번호, 이메일, QR 페이로드, MAC 값은 어느 로그에도 쓰지 않습니다. 리뷰 체크리스트가 아니라 저장소 규칙에 적힌 불변 조건입니다.

04 — Counted from the repo · 2026-09-25
28,731줄의 TypeScript · 테스트 제외
335개의 테스트 케이스 · 파일 27개
38개의 화면 · 고객 앱 20 + 가맹점 앱 18
19편의 명세 문서 · 6,127줄
15개의 규칙 엔진 골든 픽스처
ROLE
기획 · 명세 · 풀스택 개발
API
NestJS 12 · Fastify · PostgreSQL 또는 암호화 로컬 파일
APP
Expo 57 · React Native 0.86 — 고객 앱과 가맹점 앱
WEB
POS 시뮬레이터 · 가맹점 대시보드 (React 19 · Vite)
RULES
@ledger/domain — 순수 TypeScript, 런타임 의존성 없음
QR
RC1 · HMAC-SHA256 65비트 · 30초 슬롯
STATUS
RUNNABLE MVP · LOCAL STACK
WHY

만들면서 스스로에게
물은 것들.

왜 사진이나 메일이 아니라 POS 인가요?

문제는 영수증이 종이라는 것이 아닙니다. 깨끗하게 구조화된 구매 기록이 POS 안에 이미 있는데, 고객에게 오는 길에 파괴된다는 것입니다. 반품, 보증, 지출 집계, 구매 증명까지 그 뒤의 일은 전부 사진에서 다시 복원해야 합니다. 그래서 프린터 앞에서 가로챕니다.

QR 을 누가 찍어 가면요?

코드는 기기의 비밀키와 카운터로 만든 MAC 이고, 30초 슬롯이 계산에 섞입니다. 서버는 앞뒤 3슬롯까지만 받아 주고 마지막으로 본 카운터를 기록합니다. 그래서 찍어 간 화면은 90초가 지나거나 고객이 다음에 찍는 순간 무효가 됩니다. 코드 안에는 고정된 개인정보가 없습니다.

가맹점은 고객에 대해 무엇을 아나요?

자기 매장에서만 통하는 가명 하나입니다. 가맹점별 솔트로 사용자 ID 를 HMAC 한 값이라, 같은 고객도 체인마다 다른 ID 가 됩니다. 매장을 가로질러 구매 이력을 잇는 일은 구조적으로 막혀 있습니다.

데이터는 어디에 저장되나요?

로컬 개발에서는 AES-256-GCM 으로 암호화한 상태 파일과, 권한 0600 인 별도 키 파일을 씁니다. 운영에서 DATABASE_URL 을 주면 PostgreSQL 에 잠금을 건 암호화 스냅숏으로 저장합니다. 이것은 한 매장 파일럿용 호환 방식이라고 README 에 적어 두었고, 매장이 늘기 전에 정규화된 테이블로 옮겨야 합니다.

출시까지 남은 것은?

공개 출시는 코드 완료와 별개로 봅니다. 동의 문구, 보관 기간, 알림 분류, 보증 표시 같은 국내 법률 쟁점은 명세 문서 한 편에 따로 모았고, 코드로 답하지 말고 표시만 하라고 저장소 규칙에 적었습니다. 스토어 심사와 보안 점검도 출시 체크리스트의 외부 관문으로 남아 있습니다.

영수증은 입구이고,구매 기록이 제품입니다.

프로젝트 전체 보기 →