와이어프레임 · 저충실도 · 화면 26/N (신규)

캐시 충전 · 후원 결제

통합 후원 플랫폼(가칭) 기획서 §00 “캐시 모델” 결정사항 기준. 검증에서 최대 결손으로 지적됐던 결제 축을 화면으로 확정합니다 — 좌측은 도너가 직접 충전하는 화면, 우측은 잔액이 부족한 상태에서 시그니처를 클릭했을 때 뜨는 결제 모달입니다.
①은 /me 셸의 콘텐츠 영역입니다. 검색·계정 모드 전환·메인 컬러 미리보기·🔔 알림·다크모드가 있는 전역 헤더와 좌측 사이드바는 라이브 허브(live-hub.html) 와이어프레임에서 정의하며 여기서는 반복해 그리지 않습니다. ②는 모달이라 전역 헤더 위에 뜨며 해당 없음.
또도독 실구현 승계. 이 흐름은 새로 설계한 게 아니라 또도독에 이미 동작 중인 로직(donations_advanced.go · user_wallet_auto_billing)을 그대로 옮긴 것입니다 — 잔액이 모자라면 충전하고 이어서 차감합니다. 다만 원본에는 실제 PG 호출이 없습니다 — donations_advanced.go 는 UPDATE users SET points = points + … 로 충전을 흉내 냅니다(wallet.go: “아직 PG 연동 전”). 그래서 원본에서는 전부가 한 트랜잭션에 들어갑니다. 우리는 PG 를 실제로 붙이므로 승인만 트랜잭션 밖으로 뺍니다(⑨ · 정정 v2.46). 화면상으로는 “시그니처 클릭 = 바로 후원”이지만, 원장에는 항상 충전·차감 2건이 남습니다. 이 원칙을 지켜야 환불·정산·회계가 어긋나지 않습니다.
1
보유 캐시
3,000 캐시
유상 2,000 (환불 가능) 보너스·프로모션 1,000 (환불 불가)
1캐시 = 1원 · 최소 충전 1,000원(이상은 자유 입력)
2

충전 금액

1,000
5,000
10,000
30,000
50,000
100,000
직접입력
10,000 캐시
3

결제 수단

카드
계좌이체
간편결제
4
충전 캐시10,000
부가세(10%)1,000
결제 금액11,000원
11,000원 결제하기
5
자동 충전 사용
잔액이 1,000캐시 미만이면임계값
10,000캐시 자동 충전정액
등록 카드신한 ****1234 · 변경
10
미사용 캐시 환불 — 안내 문구만, 기능 없음
· 환불 대상은 유상 충전분이며, 프로모션 캐시는 잔여 캐시에 포함되지 않습니다.
· 충전 시 함께 지급된 보너스 캐시는 환불 시 회수됩니다.
· 환불 수수료는 환불 대상 캐시의 10%이며, 1만원 이하 건은 1,000원이 공제됩니다.
· 결제일로부터 일정 기간이 지나 결제 대행사 시스템을 이용할 수 없는 경우 계좌이체로 환불되며, 수수료를 제외한 금액을 매월 말일에 입금해 드립니다.
환불은 고객센터로 문의해 주세요.
1588-0000 · 평일 10:00–18:00
⑨ 처리 순서 — PG 승인은 트랜잭션 밖이다 (정정 v2.46)
1결제 의도 기록
payment_intents · 먼저 커밋
→
2PG 승인 호출
트랜잭션 밖 · approved 커밋
→
3잔액 행 잠금
본 트랜잭션 시작 · FOR UPDATE
→
4충전 반영 · 후원 차감
payments · 원장 charge → donate
→
5donations · alert_queue
Σ 검증 → 커밋 → 알림 송출
⚠ 전체 롤백은 우리 DB 안에서만 완전합니다. 3~5 에서 실패하면 롤백되지만 2 의 승인은 되돌아가지 않습니다 — 도너 돈은 이미 나간 상태입니다. 그래서 승인을 트랜잭션 밖으로 빼고 의도를 먼저 기록합니다. approved 인데 반영되지 않은 건은 v_payment_intent_gap 이 매분 잡아 취소하거나 사후 반영합니다(§14.0 ⑬).
⑩ 결제 결과 — 도너가 보는 화면은 3갈래다 (정정 v2.46)
A · 승인 + 반영 성공
정상 경로. 캐시가 늘고 후원이 기록되며 알림이 나간다. payment_intents 는 settled 로 닫힌다.
→ 후원 완료 · 영수증
B · 승인 거절
카드사가 거절했다(한도 초과 등). 본 트랜잭션이 시작되기 전이라 차감도 후원도 없다 — 이때만 “차감되지 않았습니다”가 참이다.
→ states ⑤ 결제 실패
C · 승인 후 반영 실패 ★ 신규
승인은 났고 우리 트랜잭션이 실패했다. 돈은 이미 카드에서 빠져나갔다. approved 인데 payment_id 가 비어 있는 상태로 남아 대사 배치가 매분 잡는다.
→ states ⑬ 승인됨 · 반영 확인 중
⚠ 이전 판은 A · B 두 갈래만 그렸습니다. C 를 B 로 보내면 돈이 나간 사람에게 “차감되지 않았습니다”라고 말하게 됩니다.

주석 — 기획서 필드 대응표

#영역필드타입옵션 값 / 제약사항입력 예시
① 캐시 충전 화면 — /me/wallet
1보유 캐시잔액readonly(자동)wallets.balance_paid_cash + wallets.balance_bonus_cash 의 합 — 각각 CHECK 로 음수 불가. 1캐시=1원 고정(환율 개념 없음). 정정 v2.46 — v2.30 에서 단일 balance_cash 가 유상/무상 2버킷으로 쪼개졌는데 이 표만 옛 컬럼명을 쓰고 있었다(같은 화면 ① 영역은 이미 나뉘어 그려져 있었다)3,000
2충전 금액금액 프리셋chip 단일선택 × 61천/5천/1만/3만/5만/10만 + 직접입력. 최소 충전 1,000원으로 확정(v2.29) — 최저 프리셋을 1,000으로 신설. 프리셋은 편의용 바로가기일 뿐 제약이 아니며, 직접입력으로 임의 금액 지정 가능. 1회 한도는 §13 미결정10,000
2충전 금액직접 입력number1,000원 이상 정수 — 그 위로는 1원 단위 자유(1,500 · 12,340 모두 허용). 검증은 “1,000 미만” 하나뿐이며, 미달 시에만 결제 버튼 비활성 + 인라인 안내. 프리셋 선택 시 이 칸에 자동 반영10,000
3결제 수단수단 선택chip 단일선택카드 / 계좌이체 / 간편결제 — payments.method. PG사 선정은 §13 미결정"카드"
4결제 요약부가세 · 결제금액readonly(자동 계산)충전 캐시의 10%를 부가세로 가산해 실결제액 산출 — v2.28 공급가액·부가세를 payments.supply_amount_krw / vat_amount_krw로 분리 저장(합계 = amount_krw) — 기존엔 담을 컬럼이 없어 세금계산서·대사가 불가능했음. 별도 가산 방식 채택10,000 + 1,000 = 11,000원
4결제 요약결제하기button클릭 시 PG 결제창 → 승인 성공 시 payments INSERT + wallet_transactions(charge) + 잔액 증가가 한 트랜잭션"11,000원 결제하기"
5자동 충전사용 여부toggleauto_billing_settings.is_enabled — 기본 OFF. OFF면 잔액 부족 시 오른쪽 결제 모달이 뜨고, ON이면 모달 없이 즉시 자동 충전 후 후원ON
5자동 충전발동 임계값numberthreshold_balance — 이 잔액 미만이면 자동충전 발동1,000
5자동 충전충전 방식 / 금액radio + numberbilling_type shortfall(부족분만) / fixed(정액) + charge_amountfixed · 10,000
5자동 충전등록 카드readonly + 변경 버튼billing_key_enc(PG 빌링키, 암호화). 카드번호 원본은 저장하지 않고 마스킹된 표시용 문자열만 보관"신한 ****1234"
미사용 캐시 환불 — 안내 문구 전용(v2.30)
10환불 안내정책 문구readonly text이 블록에는 입력 필드도 버튼도 없습니다. 대상 범위(유상 충전분만) · 프로모션 캐시 제외 · 보너스 회수 · 수수료 MAX(대상액×10%, 1,000원) · 계좌이체 시 매월 말일 입금 — 이 4가지를 문구로만 고지합니다—
10환불 안내고객센터 연락처readonly(전화번호·운영시간)환불 접수는 고객센터 전화가 유일한 경로입니다. 서비스에는 신청 폼·심사 상태·진행 조회 화면을 두지 않으며, 본인 확인·계좌 수집·지급 처리는 상담 과정과 운영자 도구에서 이뤄집니다(§13 운영자 도구와 동일하게 범위 제외)"1588-0000 · 평일 10:00–18:00"
—원장이 서비스가 책임지는 범위시스템 동작신청 기능은 없어도 원장은 이쪽 책임입니다 — ① 환불 대상액을 계산할 수 있게 wallets를 유상/무상으로 분리 보관 ② 회수 대상 보너스를 특정할 수 있게 cash_grants에 지급 출처 기록 ③ 환불 집행 시 wallet_transactions에 refund·clawback으로 반영—
② 결제 확인 모달 — 잔액 부족 시에만 노출
6후원 대상상품 썸네일 · 이름 · 가격readonlygift_items.image_url / name / price_cash — 무엇에 결제하는지 명확히 보여주기 위해 상품 정보를 모달에 반복 노출"시그니처명 A · 5,000캐시"
7부족 안내보유 · 부족 금액readonly(자동 계산)보유 잔액과 부족분을 분리해서 표시 — 사용자가 "얼마가 실제로 빠져나가는지" 오해하지 않게 하는 게 이 블록의 목적"보유 3,000 · 부족 2,000"
7결제 요약추가 충전액readonly(자동 계산)billing_type=shortfall이면 부족분만, fixed면 설정된 정액. 부가세 포함해 실결제액 산출2,000 + VAT = 2,200원
8CTA결제하고 후원하기button문구를 "후원하기"가 아니라 "결제하고 후원하기"로 명시 — 결제가 함께 일어난다는 사실을 버튼에서 숨기지 않음. 자동충전 ON이면 이 모달 자체가 생략됨"결제하고 후원하기"
⑨ 트랜잭션 처리 — 화면 요소는 아니지만 이 화면의 동작 계약
9원자성단일 트랜잭션시스템 동작잔액 조회(FOR UPDATE) → PG 승인 → 충전 → 차감 → donations INSERT까지 한 트랜잭션. 어느 단계든 실패 시 전체 롤백되어 결제만 되고 후원이 안 되는 상태가 생기지 않음실패 시 "결제 실패, 후원되지 않았습니다"
9원장wallet_transactions 2건시스템 동작즉결이어도 charge(+) / donate(−) 2건을 반드시 남김. 각 건에 balance_after 스냅샷을 기록해 원장만으로 잔액 재구성·검증이 가능해야 함+2,000 → 5,000 / −5,000 → 0
9동시성행 잠금시스템 동작SELECT balance FOR UPDATE로 잔액 행을 잠가, 동시에 여러 후원이 들어와도 잔액이 음수로 빠지지 않게 함—
통합 후원 플랫폼(가칭) · 와이어프레임 v4 · 저충실도 · 기준 문서: 통합 플랫폼 기획서 §00 캐시 모델 결정사항 · §14 wallets/payments/auto_billing_settings · 또도독 donations_advanced.go 실구현 승계 · v2(v2.29): 최소 충전 1,000원 확정(이상은 1원 단위 자유 입력) — 프리셋 최저값 1,000원 신설, 직접입력 검증을 “1,000 이상” 단일 규칙으로 정리 · v3(v2.30): 미사용 캐시 환불 정책 반영 — 잔액을 유상/무상으로 분리 표기, 환불 정책 안내 문구 추가(신청 기능 없음 — 고객센터 전화가 유일한 접수 경로) · v4(재감사 보완): ①이 /me 셸 콘텐츠 영역이며 전역 헤더(🔔 알림 등)는 라이브 허브 와이어프레임에서 정의됨을 명시하는 안내 추가 · 2026-08-14