Phase 4: Ecosystem — 마켓플레이스 활성화 및 수익화
마켓플레이스에 리뷰/평점, 검색/추천, 결제 시스템, Server Module 지원, 모듈 간 연동, 퍼블리셔 정산을 추가하여 자생적 생태계를 완성한다.
1. 목표
- 모듈 리뷰/평점 시스템
- 검색/추천 엔진
- 유료 모듈 결제 시스템
- Server Module 지원 (Webhook 기반 headless)
- 모듈 간 EntityLink 연동
- 퍼블리셔 정산 시스템
- 플랫폼 거버넌스 (정책, 분쟁 처리)
2. 리뷰/평점 시스템
2.1 데이터베이스
migrations/006_reviews.up.sql
CREATE TABLE module_reviews (
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
module_key TEXT NOT NULL,
family_id TEXT NOT NULL,
user_uid TEXT NOT NULL,
rating SMALLINT NOT NULL CHECK (rating BETWEEN 1 AND 5),
title TEXT DEFAULT '',
body TEXT DEFAULT '',
status TEXT NOT NULL DEFAULT 'visible'
CHECK (status IN ('visible','hidden','flagged')),
helpful_count INTEGER NOT NULL DEFAULT 0,
publisher_reply TEXT,
publisher_reply_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE(module_key, family_id, user_uid)
);
CREATE INDEX idx_reviews_module ON module_reviews(module_key);
CREATE INDEX idx_reviews_rating ON module_reviews(module_key, rating);
-- 리뷰 유용성 투표
CREATE TABLE review_votes (
review_id TEXT NOT NULL REFERENCES module_reviews(id) ON DELETE CASCADE,
user_uid TEXT NOT NULL,
is_helpful BOOLEAN NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
PRIMARY KEY (review_id, user_uid)
);
2.2 API
# 리뷰 조회
GET /api/marketplace/modules/:key/reviews
?sort=recent|helpful|rating_high|rating_low&page=1
# 리뷰 작성 (인증 필요, 설치한 사용자만)
POST /api/families/:fid/modules/:key/reviews
Body: { "rating": 5, "title": "...", "body": "..." }
# 리뷰 수정
PUT /api/families/:fid/modules/:key/reviews/:id
# 리뷰 유용성 투표
POST /api/marketplace/reviews/:id/vote
Body: { "helpful": true }
# 퍼블리셔 답변
POST /api/publisher/modules/:key/reviews/:id/reply
Body: { "reply": "감사합니다..." }
2.3 평점 집계
모듈 설치 후 7일 경과 시 → 앱 내 리뷰 요청 다이얼로그 (1회만)
평점 계산:
modules.rating_avg = AVG(module_reviews.rating)
modules.rating_count = COUNT(module_reviews)
→ 리뷰 INSERT/UPDATE/DELETE 시 트리거로 자동 갱신
2.4 Flutter UI
ModuleDetailScreen
└── 리뷰 섹션
├── 평점 요약 (별점 분포 바 차트)
├── 리뷰 목록
│ ├── 작성자 (익명: "가족A")
│ ├── 별점 + 날짜
│ ├── 본문
│ ├── 퍼블리셔 답변 (있으면)
│ └── [유용해요] 버튼
└── [리뷰 쓰기] 버튼 (설치한 사용자만)
3. 검색/추천 엔진
3.1 검색
현재 (Phase 1): display_name ILIKE '%query%' — 충분
Phase 4 확장:
-- PostgreSQL Full-Text Search (한국어)
ALTER TABLE modules ADD COLUMN search_vector tsvector;
-- 트리거로 자동 업데이트
CREATE OR REPLACE FUNCTION update_module_search_vector() RETURNS trigger AS $$
BEGIN
NEW.search_vector :=
setweight(to_tsvector('simple', coalesce(NEW.display_name, '')), 'A') ||
setweight(to_tsvector('simple', coalesce(NEW.description, '')), 'B') ||
setweight(to_tsvector('simple', coalesce(array_to_string(
(SELECT array_agg(t) FROM jsonb_array_elements_text(
coalesce(NEW.manifest->'tags', '[]'::jsonb)
) t), ' '), '')), 'C');
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_module_search_vector
BEFORE INSERT OR UPDATE ON modules
FOR EACH ROW EXECUTE FUNCTION update_module_search_vector();
CREATE INDEX idx_modules_search ON modules USING gin(search_vector);
검색 API 확장:
GET /api/marketplace/modules?q=가계부&sort=relevance
sort 옵션:
- relevance (FTS 랭킹)
- popular (install_count DESC)
- rating (rating_avg DESC, rating_count DESC)
- recent (created_at DESC)
3.2 추천
추천 알고리즘 (규칙 기반 → ML 이전 단계):
1. 인기 모듈: install_count 상위 + rating_avg >= 4.0
2. 카테고리 기반: 사용자가 설치한 모듈의 카테고리와 같은 카테고리
3. 함께 설치: "이 모듈을 설치한 가족이 함께 사용하는 모듈"
4. 신규 모듈: 최근 2주 내 published, rating_avg >= 4.0
5. 에디터 추천: modules.manifest->'featured' = true (수동 선정)
추천 API:
GET /api/marketplace/featured
Response:
{
"banner": [ModuleSummary], # 에디터 추천 (최대 5개)
"popular": [ModuleSummary], # 인기 TOP 10
"newAndNoteworthy": [ModuleSummary], # 신규 + 고평점
"forYou": [ModuleSummary], # 개인화 (설치 기반)
"byCategory": {
"lifestyle": [ModuleSummary],
"finance": [ModuleSummary],
...
}
}
3.3 Flutter 마켓플레이스 UI 확장
MarketplaceTab (Phase 1)
↓ Phase 4 확장
├── 추천 배너 (PageView 슬라이더)
├── 검색바 (자동완성)
├── 카테고리 가로 스크롤 칩
├── "인기 모듈" 가로 카드 스크롤
├── "새로 나온 모듈" 가로 카드 스크롤
├── "당신의 가족에게 추천" 가로 카드 스크롤
└── 전체 모듈 그리드 (무한 스크롤)
4. 결제 시스템
4.1 구조
사용자 ──[결제]──▶ 앱스토어 (IAP) ──▶ homb 서버 (영수증 검증)
또는 │
PG (토스페이먼츠) │
▼
┌──────────────┐
│ 정산 시스템 │
│ 70% 퍼블리셔 │
│ 30% homb │
└──────────────┘
4.2 결제 모델
| 모델 | 설명 | 결제 방식 |
|---|---|---|
| Free | 무료 | - |
| Paid | 1회 구매 | IAP 또는 PG |
| Freemium | 기본 무료, 프리미엄 기능 유료 | 모듈 내 IAP |
| Subscription | 월정액 | IAP 구독 또는 PG 정기결제 |
4.3 데이터베이스
migrations/007_billing.up.sql
-- 결제 내역
CREATE TABLE payments (
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
family_id TEXT NOT NULL,
module_key TEXT NOT NULL,
user_uid TEXT NOT NULL,
amount_krw INTEGER NOT NULL,
currency TEXT NOT NULL DEFAULT 'KRW',
payment_method TEXT NOT NULL CHECK (payment_method IN ('iap_apple','iap_google','pg_toss')),
transaction_id TEXT NOT NULL UNIQUE,
receipt_data TEXT,
status TEXT NOT NULL DEFAULT 'completed'
CHECK (status IN ('pending','completed','refunded','failed')),
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX idx_payments_family ON payments(family_id);
CREATE INDEX idx_payments_module ON payments(module_key);
CREATE INDEX idx_payments_transaction ON payments(transaction_id);
-- 구독
CREATE TABLE subscriptions (
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
family_id TEXT NOT NULL,
module_key TEXT NOT NULL,
user_uid TEXT NOT NULL,
plan TEXT NOT NULL,
amount_krw INTEGER NOT NULL,
billing_cycle TEXT NOT NULL CHECK (billing_cycle IN ('monthly','yearly')),
status TEXT NOT NULL DEFAULT 'active'
CHECK (status IN ('active','cancelled','expired','past_due')),
current_period_start TIMESTAMPTZ NOT NULL,
current_period_end TIMESTAMPTZ NOT NULL,
cancelled_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE(family_id, module_key)
);
-- 퍼블리셔 정산
CREATE TABLE publisher_payouts (
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
publisher_id TEXT NOT NULL REFERENCES publishers(id),
period_start DATE NOT NULL,
period_end DATE NOT NULL,
gross_amount INTEGER NOT NULL, -- 총 매출
platform_fee INTEGER NOT NULL, -- homb 수수료 (30%)
net_amount INTEGER NOT NULL, -- 정산 금액 (70%)
status TEXT NOT NULL DEFAULT 'pending'
CHECK (status IN ('pending','processing','completed','failed')),
paid_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX idx_payouts_publisher ON publisher_payouts(publisher_id);
CREATE INDEX idx_payouts_period ON publisher_payouts(period_start, period_end);
-- 퍼블리셔 계좌 정보
CREATE TABLE publisher_bank_accounts (
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
publisher_id TEXT NOT NULL REFERENCES publishers(id) UNIQUE,
bank_code TEXT NOT NULL,
account_number TEXT NOT NULL, -- 암호화 저장
account_holder TEXT NOT NULL,
verified BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
4.4 API
# 결제
POST /api/families/:fid/modules/:key/purchase
Body: { "paymentMethod": "pg_toss", "amount": 3900 }
POST /api/families/:fid/modules/:key/verify-receipt
Body: { "platform": "apple", "receiptData": "..." }
# 구독
POST /api/families/:fid/modules/:key/subscribe
Body: { "plan": "premium", "billingCycle": "monthly" }
POST /api/families/:fid/modules/:key/cancel-subscription
# 퍼블리셔 정산
GET /api/publisher/payouts # 정산 내역
GET /api/publisher/payouts/:id # 정산 상세
GET /api/publisher/revenue # 수익 요약
?from=2026-01&to=2026-04
PUT /api/publisher/bank-account # 계좌 등록/수정
4.5 Flutter 결제 UI
ModuleDetailScreen
└── 가격 표시
├── "무료" → [설치] 버튼
├── "₩3,900" → [구매] 버튼
│ └── 결제 시트 (토스페이먼츠 SDK 또는 IAP)
└── "월 ₩1,900" → [구독] 버튼
└── 구독 안내 + 결제
설정 > 구독 관리
├── 활성 구독 목록
├── 결제 내역
└── [해지] 버튼
5. Server Module 지원
5.1 개요
UI 없이 백엔드에서만 동작하는 모듈. 외부 사업체가 자체 서버를 운영하며, homb의 Webhook을 수신하고 REST API로 데이터를 읽고 쓴다.
5.2 인증
Server Module → homb Runtime API
Authorization: Bearer <module_api_key>
homb → Server Module (Webhook)
X-Homb-Signature: sha256={HMAC(payload, webhook_secret)}
5.3 동작 예시: AI 사진 정리 모듈
1. 가족이 "AI 사진 정리" 모듈 설치
2. homb → Webhook: module.installed
3. 모듈 서버: API 키로 homb Runtime API 접근
4. 새 사진 업로드 시 → Webhook: memory.created (향후 이벤트 확장)
5. 모듈 서버: 사진 분석 → 태그/앨범 자동 분류
6. 모듈 서버 → homb Runtime API: activity.post("사진 20장을 자동 분류했어요")
7. 모듈 서버 → homb Runtime API: notifications.send("새 앨범이 생성되었어요")
5.4 추가 Webhook 이벤트 (Phase 4 확장)
| 이벤트 | 트리거 |
|---|---|
memory.created | 새 추억(사진) 생성 |
memory.updated | 추억 수정 |
calendar.event_created | 캘린더 이벤트 생성 |
channel.message_sent | 채널 메시지 전송 |
module.data_changed | 모듈 데이터 변경 |
5.5 모듈 건강 상태 모니터링
매 5분: homb → GET {module_health_check_url}
- 200 → healthy
- 그 외 → unhealthy (3회 연속 시 사용자에게 알림)
모듈 manifest에 추가:
"health_check_url": "https://api.example.com/health"
6. 모듈 간 EntityLink 연동
6.1 개요
모듈 간 데이터를 안전하게 공유하는 메커니즘. 직접 데이터 접근이 아닌, 참조(링크) 기반.
6.2 시나리오
여행 모듈 ←→ 가계부 모듈
1. 여행 모듈에서 "제주도 여행" trip 생성
2. 가계부 모듈에서 지출 기록 시 "관련 여행" 선택
3. EntityLink 생성:
source: { moduleKey: "com.homb.budget", entityType: "transaction", entityId: "tx_001" }
target: { moduleKey: "travel", entityType: "trip", entityId: "trip_001" }
relationType: "expense_of"
4. 여행 모듈에서 "이 여행의 총 지출" 표시 가능
6.3 API
# EntityLink 생성 (양쪽 모듈 모두 설치되어 있어야 함)
POST /api/runtime/:moduleKey/families/:fid/entity-links
Body: {
"targetModuleKey": "travel",
"targetEntityType": "trip",
"targetEntityId": "trip_001",
"sourceEntityType": "transaction",
"sourceEntityId": "tx_001",
"relationType": "expense_of"
}
# 내 모듈 엔티티에 연결된 링크 조회
GET /api/runtime/:moduleKey/families/:fid/entity-links
?entityType=transaction&entityId=tx_001
# 다른 모듈의 링크 가능한 엔티티 목록 (읽기 전용)
GET /api/runtime/:moduleKey/families/:fid/linkable-entities
?targetModuleKey=travel&targetEntityType=trip
6.4 권한
- EntityLink 생성: 양쪽 모듈이 모두
entity_link.write권한을 manifest에 선언 - 링크 대상 엔티티 조회:
entity_link.read권한 - 실제 데이터 접근: 불가 (메타데이터만 — entityId, entityType, label)
7. 플랫폼 거버넌스
7.1 개발자 정책
1. 모듈은 명시된 권한 범위 내에서만 데이터를 수집/처리해야 한다
2. 사용자 데이터를 외부로 전송할 경우 개인정보처리방침에 명시해야 한다
3. 광고를 포함하는 모듈은 manifest에 "contains_ads": true 표시해야 한다
4. 악의적 행위(스팸, 피싱, 악성코드) 발견 시 즉시 정지 처리
5. 불법 콘텐츠, 차별, 혐오 표현을 포함하는 모듈은 반려/정지
6. API Rate Limit을 우회하려는 시도 시 API 키 폐기
7.2 분쟁 처리
사용자 신고
↓
homb 팀 확인 (48시간 이내)
├── 정당한 신고 → 모듈 정지 + 퍼블리셔에 시정 요구
│ ├── 시정 완료 → 정지 해제
│ └── 미시정 (7일) → 영구 삭제
└── 부당한 신고 → 기각 + 사용자에 안내
7.3 환불 정책
- 구매 후 48시간 이내: 무조건 환불
- 구매 후 48시간 ~ 7일: 모듈 미사용 시 환불
- 구매 후 7일 이후: 환불 불가 (퍼블리셔 직접 협의)
- 구독: 현재 결제 기간 종료 시 해지, 비례 환불 없음
8. 작업 목록
8.1 리뷰/평점
| # | 작업 | 예상 |
|---|---|---|
| R1 | 006_reviews.up.sql 마이그레이션 | 0.5d |
| R2 | 리뷰 CRUD API (model, repo, service, handler) | 2d |
| R3 | 평점 트리거 (INSERT/UPDATE → modules.rating_avg 갱신) | 0.5d |
| R4 | 유용성 투표 API | 0.5d |
| R5 | 퍼블리셔 답변 API | 0.5d |
| R6 | Flutter 리뷰 섹션 UI | 1.5d |
| R7 | Flutter 리뷰 작성 다이얼로그 | 0.5d |
| R8 | 리뷰 요청 다이얼로그 (설치 7일 후) | 0.5d |
8.2 검색/추천
| # | 작업 | 예상 |
|---|---|---|
| S1 | PostgreSQL FTS 설정 + search_vector | 0.5d |
| S2 | 검색 API 확장 (relevance 정렬) | 0.5d |
| S3 | 추천 알고리즘 구현 (규칙 기반) | 1.5d |
| S4 | /api/marketplace/featured 확장 | 0.5d |
| S5 | Flutter 마켓플레이스 탭 리디자인 (배너, 섹션) | 2d |
| S6 | Flutter 검색 자동완성 | 0.5d |
8.3 결제
| # | 작업 | 예상 |
|---|---|---|
| P1 | 007_billing.up.sql 마이그레이션 | 0.5d |
| P2 | 결제 모델/서비스 구현 | 2d |
| P3 | 토스페이먼츠 PG 연동 | 2d |
| P4 | Apple IAP 영수증 검증 | 1.5d |
| P5 | Google Play IAP 영수증 검증 | 1.5d |
| P6 | 구독 관리 (생성, 갱신, 해지) | 2d |
| P7 | 정산 시스템 (월별 집계, 정산서 생성) | 2d |
| P8 | 퍼블리셔 계좌 등록/검증 | 1d |
| P9 | 정산 대시보드 (Publisher Dashboard) | 1d |
| P10 | Flutter 결제 UI | 2d |
| P11 | Flutter 구독 관리 UI | 1d |
8.4 Server Module
| # | 작업 | 예상 |
|---|---|---|
| SM1 | 추가 Webhook 이벤트 정의 + 발송 연동 | 1.5d |
| SM2 | 모듈 건강 상태 모니터링 (cron) | 1d |
| SM3 | Server Module 전용 manifest 필드 | 0.5d |
| SM4 | 샘플 Server Module 개발 (AI 사진 태깅 등) | 3d |
8.5 EntityLink
| # | 작업 | 예상 |
|---|---|---|
| E1 | EntityLink API (생성, 조회) | 1d |
| E2 | linkable-entities 조회 API | 0.5d |
| E3 | Flutter 엔티티 링크 선택 UI | 1d |
| E4 | JS Bridge에 entityLink API 추가 | 0.5d |
8.6 거버넌스
| # | 작업 | 예상 |
|---|---|---|
| G1 | 신고 API + Admin 신고 관리 | 1d |
| G2 | 환불 API + 처리 로직 | 1d |
| G3 | 개발자 정책 문서 작성 | 0.5d |
| G4 | 퍼블리셔 약관 문서 작성 | 0.5d |
9. 완료 기준
- 사용자가 모듈에 리뷰/평점을 남기고, 모듈 상세에 표시됨
- 마켓플레이스에서 키워드 검색 시 관련도 기반 결과 반환
- 추천 섹션에 인기/신규/개인화 모듈 표시
- 유료 모듈 구매 → 결제 → 설치 전체 플로우 동작
- 구독 모듈 가입/해지 동작
- 퍼블리셔가 월별 정산 내역을 대시보드에서 확인
- Server Module이 Webhook 수신 → API 호출 → 결과 반영 동작
- 두 모듈 간 EntityLink 생성/조회 동작
- 사용자 신고 → Admin 처리 → 모듈 정지 플로우 동작
- 환불 요청 → 자동/수동 처리 동작
10. Phase 4 이후 — 장기 로드맵
| 항목 | 설명 |
|---|---|
| ML 추천 | 사용 패턴 기반 개인화 추천 (collaborative filtering) |
| 모듈 번들 | 여러 모듈을 묶어 할인 판매 ("신혼부부 패키지") |
| 퍼블리셔 등급 | Bronze → Silver → Gold (실적 기반, 수수료 차등) |
| A/B 테스트 | 모듈 스크린샷/설명 A/B 테스트 도구 |
| 국제화 | 다국어 모듈 manifest, 현지화된 마켓플레이스 |
| 파트너십 | 대형 서비스 연동 (은행, 학교, 병원 등) |
| Open API v2 | GraphQL 지원, 실시간 구독 (SSE/WebSocket) |