구독형 (SaaS) — 처음부터 판매까지
월간·연간으로 정기결제하는 SaaS를 파는 방식입니다. 결제와 갱신, 해지는 새몰이 처리하고 내 서비스는 구독 여부만 확인하면 됩니다.
가격을 매기려면 사업자 인증이 필요합니다. 개인사업자나 법인으로 정산 정보를 등록하고 인증을 마쳐야 유료 요금제를 설정할 수 있습니다. 비사업자는 0원(무료) 상품만 등록됩니다. 인증과 상관없이 연동 자체는 지금 그대로 따라 할 수 있습니다.
한눈에 보기
결제·갱신·해지·환불 회수는 전부 새몰이 처리합니다. 판매자가 구현할 것은 단 하나 — 내 서비스에 들어온 사용자가 유효한 구독자인지 확인하는 부분입니다.
인증 연동 방식 고르기
구독 확인 방법은 2가지입니다. 두 방식 사이에 추가 플랜 조건은 없습니다 — 구독형 상품을 등록할 수 있는 플랜(Pro 이상)이면 어느 것을 골라도 됩니다. 등록 후 연동 정보 카드에서 필요한 자격증명을 확인합니다.
| 방식 | 이런 서비스에 맞음 | 판매자가 구현할 것 |
|---|---|---|
| API Key | 자체 계정 체계가 이미 있는 서비스, CLI·데스크톱 도구 | 구매자가 받은 키를 입력받아 verifyEntitlement 호출 (아래 4단계) |
| 새몰 계정으로 로그인 | "새몰 계정으로 로그인" 버튼을 붙이는 웹 서비스 | SDK SaemolOAuth로 로그인 연동 (아래 5단계) |
scope로 정합니다 —identity(로그인만) 또는 entitlement(로그인 + 구독 상태·플랜, 기본값). 같은 상품에서 페이지마다 다르게 요청할 수 있어, 이 때문에 상품을 나눌 필요가 없습니다.따라하기
상품 등록 — 구독형 + 월/년 요금제
가격 방식을 구독형으로 고르면 전달 방식은 구독형(SaaS) 또는 수동 전달만 표시됩니다. 월간 또는 연간 유료 요금제가 최소 1개 필요합니다(정기결제 주기).
가격 방식 *
전달 방식 *
요금제 * 필수
플랜 이름 *
가격 (원) *
주기 *
인증 연동 방식 선택
위 표에서 고른 방식을 선택합니다. 새몰 계정으로 로그인을 고르면 동의 후 내 서비스로 복귀할 OAuth Redirect URI가 필수입니다.
인증 연동 방식 *
신원만 받을지 구독 정보까지 받을지는 로그인 버튼을 만들 때 코드에서 정합니다 — 등록 시점에 고를 필요가 없습니다
OAuth Redirect URI (선택)
새몰 계정으로 로그인 선택 시 필수 — 동의 후 복귀할 내 서비스 주소. 쉼표로 여러 개 등록, 정확히 일치하는 주소만 허용됩니다
웹훅 URL *
연동 정보 확인 — 시크릿과 OAuth 클라이언트
등록 직후(심사 전에도) 상품 수정 페이지의 연동 정보 카드에서 자격증명이 발급되어 있습니다.
연동 정보 — 구독형 (SaaS)
연동 시크릿 (API 호출 시 X-Saemol-Secret 헤더)
a3f8c2…e91b테스트 시크릿 (샌드박스 — 테스트 키 전용)
test_7d21…f04aOAuth 클라이언트 — Client ID / Client Secret
mfc_92be14… · mfcs_5a77c1…방식 1 — API Key: 권한 확인 API 호출
구매자에게 자동 발급된 API Key(mfk_…)를 내 서비스에서 입력받아, 로그인·요청 시점에 확인합니다. 자체 계정 체계가 이미 있거나 CLI·데스크톱 도구라면 이 방식이 가장 간단합니다.
전달 정보
API Key
mfk_5f2a…c81d구독이 활성화되었습니다. 아래 API Key로 서비스에 인증하거나, 판매자 안내에 따라 계정을 연동하세요.
import { Saemol } from '@saemol/sdk'
const saemol = new Saemol({ secret: process.env.SAEMOL_SECRET! })
const ent = await saemol.verifyEntitlement({ apiKey: userApiKey })
if (!ent.valid) return deny() // 만료·해지·환불 포함
ent.plan_name // 'Pro' — 구매한 플랜
ent.plan_features // 플랜별 기능 목록 (상품 설정)
ent.current_period_end // 이번 구독 주기 종료 시각응답을 1~5분 캐시하면 요청마다 호출하지 않아도 됩니다. 해지·환불은 웹훅으로도 즉시 통지됩니다.
방식 2 — 새몰 계정으로 로그인 (SSO / OAuth 2.0)
내 서비스에 “새몰 계정으로 로그인” 버튼을 붙이는 방식입니다. 구매자는 키 입력 없이 로그인만으로 연동됩니다. 신원만 받을지, 구독 정보까지 받을지는 로그인 버튼의 scope로 정합니다 — 동의 화면에 뜨는 항목도 그에 따라 달라집니다.
TaskFlow
로그인만 (scope: identity)
TaskFlow
로그인 + 구독 정보 (scope: entitlement)
import { SaemolOAuth } from '@saemol/sdk'
export const oauth = new SaemolOAuth({
clientId: process.env.SAEMOL_OAUTH_CLIENT_ID!,
clientSecret: process.env.SAEMOL_OAUTH_CLIENT_SECRET!,
redirectUri: 'https://app.taskflow.io/auth/saemol/callback',
})// state(CSRF 토큰)는 SDK가 만들어 돌려줍니다 — 세션에 저장했다가 콜백에서 대조
// scope 생략 = entitlement(로그인 + 구독). 신원만 받으려면 { scope: 'identity' }
const { url, state } = oauth.createAuthorizationUrl()
session.oauthState = state
res.redirect(url)import { isSubscriptionActive, hasPlanFeature } from '@saemol/sdk'
// GET /auth/saemol/callback?code=mfoc_…&state=…
// 오류 확인 → state 검증 → 토큰 교환 → userinfo까지 한 번에 처리합니다
const { user, tokens } = await oauth.handleCallback({
url: req.url,
expectedState: session.oauthState,
})
// user → { sub, name, email, entitlement?: { valid, plan_name, plan_features, … } | null }
// scope='identity'로 요청했다면 entitlement는 없습니다 — 신원만 연결하고 끝냅니다.
if (user.entitlement) {
if (!isSubscriptionActive(user)) return showSubscribePage()
if (hasPlanFeature(user, 'advanced')) enableAdvancedFeatures()
}
createSession({
saemolUserId: user.sub,
plan: user.entitlement?.plan_name ?? null,
refreshToken: tokens.refresh_token, // 갱신용으로 보관
})액세스 토큰은 1시간, 리프레시 토큰은 30일 유효합니다. 만료되면 oauth.refresh()로 갱신하세요 — 갱신 시 두 토큰이 모두 새로 발급(회전)되므로 새 리프레시 토큰을 반드시 저장해야 합니다. 이전 값은 즉시 무효가 됩니다.
import { SaemolOAuthError } from '@saemol/sdk'
try {
user = await oauth.getUserInfo({ accessToken })
} catch (err) {
if (err instanceof SaemolOAuthError && err.code === 'invalid_token') {
const tokens = await oauth.refresh({ refreshToken })
await saveTokens(tokens) // refresh_token이 회전됨 — 새 값 저장 필수
user = await oauth.getUserInfo({ accessToken: tokens.access_token })
} else {
throw err
}
}handleCallback이 SaemolOAuthError(code: 'access_denied')를 던집니다.state가 일치하지 않으면 토큰 교환 전에 state_mismatch로 중단합니다 — 위조된 콜백이 세션을 만들지 못하게 막는 지점입니다.구독 상태 웹훅 수신
갱신·만료·해지는 웹훅으로 통지됩니다. 구독 수명주기 4종은 모두 기본(basic) 이벤트라 SDK 티어와 무관하게 발송됩니다 — Business 전용인 일부 확장 이벤트와 달리 별도 조건이 없습니다.
| 이벤트 | 시점 | 판매자 처리 |
|---|---|---|
subscription.created | 첫 결제 성공 | 환영 메일·온보딩 |
subscription.renewed | 갱신 결제 성공 | 이용 기간 연장 반영 |
subscription.cancelled | 해지 예약 | 주기말 종료 예정 안내 |
subscription.expired | 구독 종료 확정 | 접근 차단·재구독 유도 |
서명 검증 방법은 웹훅 가이드 참고. 수신이 없어도 verify·userinfo가 항상 최신 상태를 반환하므로 안전합니다.
출시 전 체크리스트
- 월간/연간 유료 요금제가 1개 이상 있다 (정기결제 주기)
- 테스트 키로 verify(또는 새몰 계정 로그인)와 웹훅까지 확인했다 — 개발 단계부터 연동
- 로그인 방식 사용 시: Redirect URI가 운영 도메인과 정확히 일치한다
- 해지·환불 시 접근 차단이 동작한다 (verify 재확인 또는 웹훅 처리)