尚未收集仓库信息。
official website for dozy
翻译准备中
尚未检测到更新。GitHub 发布版本后会显示在这里。
正在加载完整 README。
查看完整 README ↗Official marketing site + web subscription for Dozy — Next.js on Vercel.
Same plan as iOS: $19/month with a 3-day free trial, then subscribe to use the product.
| Route / API | Purpose |
|---|---|
POST /api/checkout | Create Checkout Session (mode: subscription, trial_period_days: 3) |
POST /api/checkout/complete | Success-page session sync + cookie (webhook remains authoritative) |
POST /api/webhooks/stripe | Fulfill entitlement on Checkout + subscription lifecycle events |
POST /api/portal | Stripe Customer Portal (manage / cancel) |
GET /api/entitlement | Current entitlement for the signed session |
/subscribe | Paywall |
/success, /cancel | Checkout return URLs |
/app | Gated product shell (requires active or trialing) |
Copy .env.example → .env.local and set:
STRIPE_SECRET_KEY — prefer a restricted API key (rk_)STRIPE_WEBHOOK_SECRETNEXT_PUBLIC_STRIPE_PUBLISHABLE_KEYSTRIPE_PRICE_ID — Price for $19/monthNEXT_PUBLIC_APP_URLSESSION_SECRETJUNCTION_API_KEY / JUNCTION_ENVIRONMENT — optional; cloud sleep sources (Oura / Garmin / Whoop via Junction)Apple Health stays on-device in the iOS app. For Oura / Garmin / Whoop cloud connect:
| Route | Purpose |
|---|---|
POST /api/auth/apple | Exchange an Apple identity token for an app session |
POST /api/junction/user | Create/map Junction user |
POST /api/junction/link-token | Open Junction Link widget |
POST /api/junction/demo-connect | Sandbox synthetic provider (no wearable account) |
GET /api/junction/sleep | Normalized sleep summaries |
POST /api/webhooks/junction | Sleep created/updated webhooks |
/junction/connected | Link redirect landing |
The iOS app signs in once with POST /api/auth/apple ({ "identityToken": "<Sign in with Apple identity token>" }). The server checks Apple's signature, issuer, audience (APPLE_BUNDLE_ID, default charvibansall.dozy) and expiry, then returns { userId, sessionToken, expiresAt }. The session lasts 90 days and is signed with SESSION_SECRET.
The app sends Authorization: Bearer <sessionToken> on /api/junction/user, /link-token, /sleep and /demo-connect. A session only works for the clientUserId it was issued for (401 without a valid session, 403 for someone else's id).
JUNCTION_ENVIRONMENT=production: a session is always required.JUNCTION_REQUIRE_AUTH=true to require it there too.Run the checks with npm test.
Sandbox demo providers: oura, whoop_v2, apple_health_kit, freestyle_libre. Garmin has no demo — needs a real Garmin Connect login via Link. Whoop production also needs bring-your-own Whoop OAuth. See Project store docs/junction-setup.md.
price_… id into STRIPE_PRICE_ID.subscription_data.trial_period_days: 3 (no need to hard-code trial on the Price).https://<your-domain>/api/webhooks/stripe for at least:
checkout.session.completedcheckout.session.async_payment_succeededcustomer.subscription.createdcustomer.subscription.updatedcustomer.subscription.deletedinvoice.paidinvoice.payment_failedcharge.refunded, charge.dispute.created, radar.early_fraud_warning.createdstripe listen --forward-to localhost:3000/api/webhooks/stripeWebhook handlers upsert subscription status into a durable local store (.data/entitlements.json when the filesystem is writable) and the app sets a signed dozy_entitlement cookie after Checkout. Protected /app routes require status active or trialing. Lookups reconcile from the Stripe Subscriptions API when the local cache is cold (e.g. serverless).
If you charge US/EU customers, consider enabling Stripe Tax and active tax registrations before turning on automatic_tax.
npm install
npm run dev
npm run build