Wallet — 회원 지갑 (포인트/크레딧 원장)
회원의 잔액형 가치수단을 관리하는 원장(ledger) 플러그인입니다. (ADR-107/060, v1.7.5)
| 유형 | 성격 | 환불 | 만료 |
|---|---|---|---|
충전 크레딧 paid_credits | 유상 충전 (선수금) | ✅ | 없음 |
보너스 크레딧 bonus_credits | 무상 지급 (프로모션·감사) | ❌ | 건별(lot) |
포인트 points | 활동 보상 | ❌ | 건별(lot) |
유상/무상은 회계·법적 성격이 달라 계정 차원에서 분리됩니다. 사용(spend) 시 보너스(만료 임박분 먼저) → 충전 순으로 차감되고, 환불은 충전 크레딧만 가능합니다.
운영자 가이드
활성화 — 항상 노출, 사이트별 사용 토글 (ADR-096)
지갑 기능은 기본으로 항상 탑재됩니다(별도 설치 불필요). 설정 탭(사이트 설정 › 지갑·상품권)은 항상 보이며, 사이트별 사용 on/off 는 플러그인 관리 화면에서 토글합니다. 사용하지 않는 사이트는 끄면 관리 메뉴·회원 화면·지갑결제가 자동으로 숨겨집니다(기본은 켜짐).
php artisan migrate # wallet_accounts / wallet_transactions / wallet_lots
개발자 참고: 프로젝트 고유 화면에서 지갑 기능을 노출/숨김 처리하려면
app(\App\Core\Base\Plugin\Activation\Contracts\PluginActivationInterface::class)->isEnabledForCurrentScope('wallet')를 참조하세요 — 관리 리소스·회원 라우트·지갑결제가 모두 이 값 을 게이트로 씁니다.
관리 화면
- 지갑 계정: 회원별 유형 잔액 조회 + 수동 적립/차감(재인증 step-up) + 현황 위젯(유상/보너스/포인트 잔액 = 미상환 부채 + 30일 내 만료 예정)
- 지갑 거래: 전 원장 조회(멱등키·사유·거래후 잔액)
- 만료 배치:
wallet:expire-lots(스케줄 daily 03:10 —--check-integrity로 원장 무결성 점검)
회원 화면
/user/wallet — 유형별 잔액 + 만료 예정(30일) 안내 + 최근 거래 + (Voucher 활성 시) 상품권 등록 링크.
정책 설정 (관리페이지 · 권장)
사이트 설정 › 지갑·상품권 › 지갑 탭에서 사이트(SaaS/Tenant) 단위로 설정합니다 — 서버 접근 없이 즉시 반영:
| 항목 | 설명 |
|---|---|
| 포인트/보너스 만료일 | 적립 시 기본 만료 기간. 0 = 무만료. 비우면 상위/기본값 상속 |
| 내 지갑 페이지 | 회 원 self-service 페이지 노출 on/off |
| 수동 조정 재인증 | 관리자 수동 적립/차감 시 step-up 요구 + 유효시간(60~3600초) |
우선순위는 Tenant 설정 > SaaS 설정 > .env > 코드 기본이며, 미설정 사이트는 기존 동작을 그대로 따릅니다(하위호환).
🔒 보안: step-up 정책(재인증 on/off·시간)을 바꾸는 저장 자체에 재인증이 요구되고 감사로그에 남습니다(조용한 무력화 차단).
만료 기본값 (.env 폴백)
관리페이지에서 미설정 시 폴백으로 사용됩니다.
WALLET_POINTS_EXPIRE_DAYS=365 # 0=무만료
WALLET_BONUS_EXPIRE_DAYS=180
개발자 가이드
Core 계약만 사용하세요 (plugin 클래스 직접 참조 금지):
use App\Core\Base\Wallet\Contracts\WalletInterface; // debit/credit/refund
use App\Core\Base\Wallet\Contracts\WalletSpenderInterface; // spend(횡단 차감)/spendableBalance
// 적립 (멱등 — requestId 필수)
app(WalletInterface::class)->credit($user, WalletTransactionType::PointAward, 100,
requestId: "signup-bonus-{$user->id}", reason: 'signup bonus');
// 결제성 차감 (bonus→paid 자동)
app(WalletSpenderInterface::class)->spend($user, 5000, requestId: "order-{$order->id}");
- 이벤트:
WalletCredited/WalletDebited/WalletRefunded/WalletLotExpired - 적립 규칙(언제 얼마)은 프로젝트 몫 — 예시
plugins/Wallet/docs/examples/PurchasePointRewarder.php - Commerce 연동(지갑 전액결제·환불 역분개)은 Commerce 문서 참조
이벤트 전달 계약 (v1.7.5)
네 Wallet 이벤트는 모두 DB transaction과 원자적인 commit-after 계약을 따릅니다.
| 이벤트 | 전달하는 대표 정보 |
|---|---|
WalletCredited | 적립 transaction |
WalletDebited | 차감 transaction |
WalletRefunded | 원거래와 환불 transaction |
WalletLotExpired | 만료 transaction, lot ID, 만료액 |
- 활성 transaction 밖에서 발행하면 listener가 즉시 실행됩니다.
- transaction 안에서는 안쪽 savepoint commit이 아니라 최외곽 commit 이후 실행됩니다.
- 최외곽 transaction이 rollback되면 보류된 listener 호출도 폐기됩니다. 실패 후 재시도되는 attempt의 알림·webhook이 먼저 나가는 것을 막습니다.
- 같은 transaction에서 여러 Wallet 이벤트를 발행하면 기존 등록 순서를 유지하며, 이벤트 이름과 payload도 변경되지 않습니다.
commit-after는 DB commit과 listener 실행의 순서만 보장합니다. 외부 알림·webhook·메시지 브로커의
exactly-once delivery나 transactional outbox를 제공하지 않습니다. 소비자는 Wallet transaction의
request_id 같은 안정적인 식별자로 멱등 처리해야 하며, commit 후 listener 실패와 queue 재시도 정책도
별도로 다뤄야 합니다.