refactor(core): keep plan prices, cloud quotas and public URLs in one contract (WS-A)
Prices, quotas and site URLs were copied by hand into the edge functions, admin, desktop and the landing site, and the copies disagreed (Payple billed 9,900/29,900 KRW, admin labels said 12,900/24,900 KRW and $9.9/$19.9, the site said 2,900/8,900 KRW). - packages/core/src/plan-catalog.ts is the single source for PLAN_PRICE_KRW (Free 0 / Pro 2,900 / Pro+ 8,900 a month) and PLAN_QUOTA. - packages/core/src/web-urls.ts is the single source for the public origin, the /app web-app base path, SITE_URLS and billingUrl(). - Deno cannot bundle packages/core, so scripts/ci/sync-core-contract.mjs generates _shared/core-contract.generated.ts; `npm run contract:check` fails on drift (same pattern as version:sync). - Payple checkout, renewal and webhook amount checks now bill the catalog price, so existing subscribers move to the new price at their next renewal. quota.ts, team-contract.ts and the tests read the generated values. - Admin MRR/ARR is computed in KRW from the catalog; license labels, the release link and desktop PREMIUM_LLM limits derive from core; the site imports prices and quotas directly. Policy: docs/REFACTOR_POLICY.md Wave 3, W3-1 and W3-2.
This commit is contained in:
parent
e689683b72
commit
88f24d84a1
21 changed files with 568 additions and 152 deletions
43
packages/core/src/web-urls.ts
Normal file
43
packages/core/src/web-urls.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
// packages/core/src/web-urls.ts
|
||||
// 공개 웹 주소 SSOT — 결제·법률·다운로드·초대 링크는 모두 여기서 만든다.
|
||||
//
|
||||
// 한 도메인 아래 두 앱이 있다.
|
||||
// / 랜딩 사이트(site/, Cloudflare Pages) — 다운로드, 법률 문서, 초대 수락
|
||||
// /app/... 웹앱(apps/web, Next basePath '/app') — 로그인, 결제, 대시보드
|
||||
// 사이트 브리지 워커(server/cloudflare-site-bridge)가 /app 요청을 웹앱으로 보낸다.
|
||||
// Deno 쪽 사본은 plan-catalog.ts 와 같은 방식으로 생성된다. 이 파일도 순수 값만 둔다.
|
||||
|
||||
import type { PaidPlanTier } from './plan-catalog'
|
||||
|
||||
export const PUBLIC_SITE_ORIGIN = 'https://d3ro.chanpaca.net'
|
||||
|
||||
/** apps/web 의 Next basePath. next.config 와 브리지 워커 라우팅이 이 값을 따른다. */
|
||||
export const WEB_APP_BASE_PATH = '/app'
|
||||
|
||||
export const WEB_APP_URL = `${PUBLIC_SITE_ORIGIN}${WEB_APP_BASE_PATH}`
|
||||
|
||||
export const SITE_URLS = {
|
||||
home: `${PUBLIC_SITE_ORIGIN}/`,
|
||||
download: `${PUBLIC_SITE_ORIGIN}/#download`,
|
||||
privacy: `${PUBLIC_SITE_ORIGIN}/privacy/`,
|
||||
terms: `${PUBLIC_SITE_ORIGIN}/terms/`,
|
||||
deleteAccount: `${PUBLIC_SITE_ORIGIN}/delete-account/`,
|
||||
acceptInvite: `${PUBLIC_SITE_ORIGIN}/accept-invite/`,
|
||||
} as const
|
||||
|
||||
/** 결제 결과로 돌아올 때 붙는 쿼리. apps/web 결제 페이지가 읽는 이름과 같다. */
|
||||
export type BillingReturn = 'success' | 'canceled'
|
||||
|
||||
/**
|
||||
* 웹 결제 페이지 URL.
|
||||
* - `tier`: 고를 요금제를 미리 선택한다.
|
||||
* - `result`: 외부 결제(Stripe 등)에서 돌아올 때의 결과.
|
||||
*/
|
||||
export function billingUrl(options: { tier?: PaidPlanTier; result?: BillingReturn } = {}): string {
|
||||
const params = new URLSearchParams()
|
||||
if (options.tier) params.set('tier', options.tier)
|
||||
if (options.result === 'success') params.set('success', '1')
|
||||
if (options.result === 'canceled') params.set('canceled', '1')
|
||||
const query = params.toString()
|
||||
return `${WEB_APP_URL}/billing${query ? `?${query}` : ''}`
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue