판매를 막지 않습니다
영수증 때문에 계산대 줄이 느려지면 도입될 수 없습니다. 가맹점 앱의 POS 호출은 전부 4~8초의 시간 제한을 둡니다. 끝없이 기다리는 요청은 실패가 아니라 멈춤이기 때문입니다.
Digital Receipt 는 결제하는 순간 POS 가 만든 영수증을 고객 앱으로 바로 보내는 전자 영수증 플랫폼입니다. 고객은 계산대에서 30초마다 바뀌는 QR 을 보여 주고, 직원은 멤버십 바코드처럼 찍습니다. 영수증은 반품 기한과 보증 만료일이 계산된 채로 도착합니다. 사진을 찍어 올리거나 메일로 받는 방식이 아니라, POS 의 구조화된 기록을 그대로 넘깁니다.
한 매장 파일럿을 돌릴 수 있는 로컬 빌드입니다. 여기 걸어 둔 POS 시뮬레이터는 화면만 있고 서버는 연결돼 있지 않습니다. 공개 출시는 별도 체크리스트를 먼저 통과해야 합니다.




앞의 셋은 POS 시뮬레이터, 마지막은 고객 앱 화면입니다. ②의 토큰은 저장소의 공개 데모 키로 만든 실제 형식의 RC1 토큰입니다.
품목과 공급가액, 부가세, 결제 금액은 POS 가 이미 구조화해 들고 있습니다. 이 기록을 종이에 찍기 전에 가로채는 것이 출발점입니다.
QR 에는 RC1:kid:counter:mac 형식의 토큰이 들어 있습니다. MAC 은 HMAC-SHA256 을 65비트로 자른 값이고, 30초 단위 시각은 계산에만 섞이고 전송되지 않습니다. 서버는 마지막으로 본 카운터를 기억해, 찍어 간 화면을 다시 쓰지 못하게 합니다.
같은 거래를 두 번 보내고, 합계에 100원을 더해 봅니다. 서버는 Idempotency-Key 와 가맹점·거래번호 자연키 두 겹으로 중복을 막고, 금액과 품목·결제 합계가 맞지 않으면 받지 않습니다.
영수증은 수집되는 순간의 반품 정책을 함께 저장합니다. 매장이 내일 정책을 바꿔도 지난주 영수증의 기한은 움직이지 않고, 앱은 남은 날짜를 셉니다.




기한, 분류, 기록의 주인. 모두 규칙으로 정해지고, 서버와 앱이 같은 규칙 패키지로 계산합니다.
30초마다 바뀌는 QR네트워크 없이 기기 안의 비밀키로 만듭니다. 고정된 개인정보가 없고, 같은 고객도 가맹점마다 다른 가명으로 보입니다.
AI 없는 분류카테고리와 합계는 영수증의 구조화된 데이터로 규칙에 따라 계산합니다. 정확성의 문제이자 단가의 문제입니다.
기록은 기기에영수증은 기기 안 SQLite 에 캐시돼 비행기 모드에서도 열립니다. 언제든 JSON 으로 내보낼 수 있고, 계정 삭제는 7일 유예 뒤에 처리됩니다.
전화번호는 원문으로 두지 않습니다전화번호로 시작하면 기기를 잃어도 구매 내역을 되찾습니다. 번호는 인증에만 쓰고, 서버에는 복구용 식별자만 남깁니다.
그리고 계산대를 향한 호출은 실패해도 판매를 막지 않는 쪽으로 끝납니다. 응답이 늦으면 종이를 찍고 나중에 다시 보냅니다.
영수증 때문에 계산대 줄이 느려지면 도입될 수 없습니다. 가맹점 앱의 POS 호출은 전부 4~8초의 시간 제한을 둡니다. 끝없이 기다리는 요청은 실패가 아니라 멈춤이기 때문입니다.
영수증 행은 고쳐 쓰지 않습니다. 환불과 취소는 조정 행과 이벤트로 쌓고, 상태는 거기서 다시 계산합니다. 판매보다 먼저 온 환불은 24시간 보관했다가 판매가 들어오면 적용합니다.
반품 정책은 영수증이 들어오는 순간 스냅숏으로 함께 저장됩니다. 옛 영수증을 그릴 때 현재 정책을 다시 읽지 않습니다. 가맹점의 정책 변경이 이미 받은 기한을 옮기면 안 되기 때문입니다.
packages/domain 에는 입출력도, 난수도, Date.now() 도 없습니다. 현재 시각은 항상 인자로 받습니다. 런타임 의존성이 없고, 서버와 앱이 같은 패키지를 그대로 가져다 씁니다.
금액은 최소 단위 정수와 통화 코드로 다닙니다. 원화에는 소수점이 없고, 100 으로 몰래 나누거나 곱하지 않습니다. 맨 숫자가 금액으로 모듈 경계를 넘지 않게 합니다.
품목, 전화번호, 이메일, QR 페이로드, MAC 값은 어느 로그에도 쓰지 않습니다. 리뷰 체크리스트가 아니라 저장소 규칙에 적힌 불변 조건입니다.
@ledger/domain — 순수 TypeScript, 런타임 의존성 없음문제는 영수증이 종이라는 것이 아닙니다. 깨끗하게 구조화된 구매 기록이 POS 안에 이미 있는데, 고객에게 오는 길에 파괴된다는 것입니다. 반품, 보증, 지출 집계, 구매 증명까지 그 뒤의 일은 전부 사진에서 다시 복원해야 합니다. 그래서 프린터 앞에서 가로챕니다.
코드는 기기의 비밀키와 카운터로 만든 MAC 이고, 30초 슬롯이 계산에 섞입니다. 서버는 앞뒤 3슬롯까지만 받아 주고 마지막으로 본 카운터를 기록합니다. 그래서 찍어 간 화면은 90초가 지나거나 고객이 다음에 찍는 순간 무효가 됩니다. 코드 안에는 고정된 개인정보가 없습니다.
자기 매장에서만 통하는 가명 하나입니다. 가맹점별 솔트로 사용자 ID 를 HMAC 한 값이라, 같은 고객도 체인마다 다른 ID 가 됩니다. 매장을 가로질러 구매 이력을 잇는 일은 구조적으로 막혀 있습니다.
로컬 개발에서는 AES-256-GCM 으로 암호화한 상태 파일과, 권한 0600 인 별도 키 파일을 씁니다. 운영에서 DATABASE_URL 을 주면 PostgreSQL 에 잠금을 건 암호화 스냅숏으로 저장합니다. 이것은 한 매장 파일럿용 호환 방식이라고 README 에 적어 두었고, 매장이 늘기 전에 정규화된 테이블로 옮겨야 합니다.
공개 출시는 코드 완료와 별개로 봅니다. 동의 문구, 보관 기간, 알림 분류, 보증 표시 같은 국내 법률 쟁점은 명세 문서 한 편에 따로 모았고, 코드로 답하지 말고 표시만 하라고 저장소 규칙에 적었습니다. 스토어 심사와 보안 점검도 출시 체크리스트의 외부 관문으로 남아 있습니다.