@feelflow/ffid-sdk 2.18.0 → 2.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +70 -7
- package/dist/{chunk-EK2W67BW.cjs → chunk-BBXUZS4U.cjs} +89 -8
- package/dist/{chunk-IEYXT3LA.js → chunk-SXYB5QM3.js} +89 -9
- package/dist/components/index.cjs +8 -8
- package/dist/components/index.d.cts +1 -1
- package/dist/components/index.d.ts +1 -1
- package/dist/components/index.js +1 -1
- package/dist/{index--S6rLHjr.d.cts → index-0D2vYSLq.d.cts} +87 -2
- package/dist/{index--S6rLHjr.d.ts → index-0D2vYSLq.d.ts} +87 -2
- package/dist/index.cjs +62 -28
- package/dist/index.d.cts +181 -11
- package/dist/index.d.ts +181 -11
- package/dist/index.js +33 -4
- package/dist/server/index.cjs +1 -1
- package/dist/server/index.js +1 -1
- package/dist/webhooks/index.d.cts +71 -5
- package/dist/webhooks/index.d.ts +71 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -206,16 +206,51 @@ const {
|
|
|
206
206
|
|
|
207
207
|
```tsx
|
|
208
208
|
const {
|
|
209
|
-
subscription,
|
|
210
|
-
planCode,
|
|
211
|
-
isActive,
|
|
212
|
-
isTrialing,
|
|
213
|
-
isCanceled,
|
|
214
|
-
|
|
215
|
-
|
|
209
|
+
subscription, // FFIDSubscription | null - 現在のサブスクリプション
|
|
210
|
+
planCode, // string | null - プランコード
|
|
211
|
+
isActive, // boolean - DB ステータスが 'active'
|
|
212
|
+
isTrialing, // boolean - トライアル中
|
|
213
|
+
isCanceled, // boolean - 解約済み
|
|
214
|
+
isTrialExpired, // boolean - トライアル期間超過
|
|
215
|
+
effectiveStatus, // EffectiveSubscriptionStatus | null - 意味論的アクセス制御値
|
|
216
|
+
isBlocked, // boolean - blocked / expired / canceled / trial_expired
|
|
217
|
+
isGrace, // boolean - past_due_grace (支払い失敗の猶予期間中)
|
|
218
|
+
hasPlan, // (plans: string | string[]) => boolean - プラン確認
|
|
219
|
+
hasAccess, // () => boolean - アクセス権確認 (active || past_due_grace)
|
|
220
|
+
hasAccessLegacy, // () => boolean - 旧セマンティクス (active || trialing, pre-2.19)
|
|
216
221
|
} = useSubscription()
|
|
217
222
|
```
|
|
218
223
|
|
|
224
|
+
`effectiveStatus` は `/api/v1/subscriptions/ext/check` が返す意味論的ステータスと同じ値を取る。詳細は [契約期限切れハンドリング](#契約期限切れハンドリング) を参照。
|
|
225
|
+
|
|
226
|
+
### useRequireActiveSubscription()
|
|
227
|
+
|
|
228
|
+
契約が期限切れ/遮断状態のときに自動でリダイレクトするフック。
|
|
229
|
+
|
|
230
|
+
```tsx
|
|
231
|
+
'use client'
|
|
232
|
+
import { useRouter } from 'next/navigation'
|
|
233
|
+
import { useRequireActiveSubscription } from '@feelflow/ffid-sdk'
|
|
234
|
+
|
|
235
|
+
function ProtectedShell({ children }: { children: React.ReactNode }) {
|
|
236
|
+
const router = useRouter()
|
|
237
|
+
const { loading } = useRequireActiveSubscription({
|
|
238
|
+
redirectTo: (status) =>
|
|
239
|
+
status === 'canceled' ? '/contract-ended' : '/contract-required',
|
|
240
|
+
onRedirect: (url) => router.replace(url),
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
if (loading) return <FullPageSpinner />
|
|
244
|
+
return <>{children}</>
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
オプション:
|
|
249
|
+
|
|
250
|
+
- `redirectTo`: `string` または `(status: EffectiveSubscriptionStatus) => string`
|
|
251
|
+
- `allowGrace` (default: `true`): `past_due_grace` を通過させるか
|
|
252
|
+
- `onRedirect` (optional): 独自のリダイレクト関数(未指定時は `window.location.href`)
|
|
253
|
+
|
|
219
254
|
### withSubscription()
|
|
220
255
|
|
|
221
256
|
サブスクリプション確認HOC。
|
|
@@ -228,6 +263,34 @@ const PremiumFeature = withSubscription(MyComponent, {
|
|
|
228
263
|
})
|
|
229
264
|
```
|
|
230
265
|
|
|
266
|
+
## 契約期限切れハンドリング
|
|
267
|
+
|
|
268
|
+
契約が失効したり解約されたとき、外部サービス側で適切にアクセスを遮断し、ユーザーを再契約動線に案内する必要があります。SDK は 3 層構成(トークン検証 / 契約チェック / Webhook 受信)をサポートしています。
|
|
269
|
+
|
|
270
|
+
### 最小構成(Next.js App Router)
|
|
271
|
+
|
|
272
|
+
```tsx
|
|
273
|
+
'use client'
|
|
274
|
+
import { useRouter } from 'next/navigation'
|
|
275
|
+
import { useRequireActiveSubscription } from '@feelflow/ffid-sdk'
|
|
276
|
+
|
|
277
|
+
export default function Layout({ children }: { children: React.ReactNode }) {
|
|
278
|
+
const router = useRouter()
|
|
279
|
+
const { loading, effectiveStatus } = useRequireActiveSubscription({
|
|
280
|
+
redirectTo: '/contract-required',
|
|
281
|
+
onRedirect: (url) => router.replace(url),
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
if (loading) return <Spinner />
|
|
285
|
+
if (effectiveStatus !== 'active' && effectiveStatus !== 'past_due_grace') {
|
|
286
|
+
return null // リダイレクト発火中
|
|
287
|
+
}
|
|
288
|
+
return <>{children}</>
|
|
289
|
+
}
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
**詳細な実装レシピ**(middleware、Express、UI 分岐、Webhook 受信、fixture API を使ったテスト、`EffectiveSubscriptionStatus` 別の UI 推奨動作まで) → [docs/03-implementation/EXPIRED_CONTRACT_HANDLING.md](https://github.com/feel-flow/feelflow-id-platform/blob/develop/docs/03-implementation/EXPIRED_CONTRACT_HANDLING.md)
|
|
293
|
+
|
|
231
294
|
### getProfile() / updateProfile()
|
|
232
295
|
|
|
233
296
|
ログイン中ユーザー自身のプロフィールを取得・更新するメソッド(`createFFIDClient` から呼び出し)。
|
|
@@ -807,7 +807,7 @@ function createProfileMethods(deps) {
|
|
|
807
807
|
}
|
|
808
808
|
|
|
809
809
|
// src/client/version-check.ts
|
|
810
|
-
var SDK_VERSION = "2.
|
|
810
|
+
var SDK_VERSION = "2.19.0";
|
|
811
811
|
var SDK_USER_AGENT = `FFID-SDK/${SDK_VERSION} (TypeScript)`;
|
|
812
812
|
var SDK_VERSION_HEADER = "X-FFID-SDK-Version";
|
|
813
813
|
function sdkHeaders() {
|
|
@@ -3162,6 +3162,67 @@ function FFIDOrganizationSwitcher({
|
|
|
3162
3162
|
] })
|
|
3163
3163
|
] });
|
|
3164
3164
|
}
|
|
3165
|
+
|
|
3166
|
+
// src/subscriptions/types.ts
|
|
3167
|
+
var PAST_DUE_GRACE_PERIOD_DAYS = 7;
|
|
3168
|
+
var HOURS_PER_DAY = 24;
|
|
3169
|
+
var MINUTES_PER_HOUR = 60;
|
|
3170
|
+
var SECONDS_PER_MINUTE = 60;
|
|
3171
|
+
var MS_PER_SECOND2 = 1e3;
|
|
3172
|
+
var MS_PER_DAY = HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE * MS_PER_SECOND2;
|
|
3173
|
+
|
|
3174
|
+
// src/subscriptions/compute-effective-status.ts
|
|
3175
|
+
function isExpired(isoTimestamp, nowMs) {
|
|
3176
|
+
if (!isoTimestamp) return null;
|
|
3177
|
+
const parsed = Date.parse(isoTimestamp);
|
|
3178
|
+
if (Number.isNaN(parsed)) return null;
|
|
3179
|
+
return parsed < nowMs;
|
|
3180
|
+
}
|
|
3181
|
+
function computeEffectiveStatusFromSession(input, nowMs = Date.now()) {
|
|
3182
|
+
const { status, currentPeriodEnd, trialEnd, pastDueSince } = input;
|
|
3183
|
+
switch (status) {
|
|
3184
|
+
case "trialing": {
|
|
3185
|
+
const relevantEnd = trialEnd ?? currentPeriodEnd;
|
|
3186
|
+
const expired = isExpired(relevantEnd, nowMs);
|
|
3187
|
+
return expired === true ? "trial_expired" : "active";
|
|
3188
|
+
}
|
|
3189
|
+
case "active": {
|
|
3190
|
+
const expired = isExpired(currentPeriodEnd, nowMs);
|
|
3191
|
+
return expired === true ? "expired" : "active";
|
|
3192
|
+
}
|
|
3193
|
+
case "past_due": {
|
|
3194
|
+
const baselineIso = pastDueSince ?? currentPeriodEnd;
|
|
3195
|
+
if (!baselineIso) return "blocked";
|
|
3196
|
+
const baselineMs = Date.parse(baselineIso);
|
|
3197
|
+
if (Number.isNaN(baselineMs)) return "blocked";
|
|
3198
|
+
const graceEndMs = baselineMs + PAST_DUE_GRACE_PERIOD_DAYS * MS_PER_DAY;
|
|
3199
|
+
return graceEndMs > nowMs ? "past_due_grace" : "blocked";
|
|
3200
|
+
}
|
|
3201
|
+
case "pending_invoice":
|
|
3202
|
+
return "active";
|
|
3203
|
+
case "paused":
|
|
3204
|
+
case "incomplete":
|
|
3205
|
+
return "active";
|
|
3206
|
+
case "canceled":
|
|
3207
|
+
return "canceled";
|
|
3208
|
+
case "unpaid":
|
|
3209
|
+
case "incomplete_expired":
|
|
3210
|
+
return "blocked";
|
|
3211
|
+
default: {
|
|
3212
|
+
return "blocked";
|
|
3213
|
+
}
|
|
3214
|
+
}
|
|
3215
|
+
}
|
|
3216
|
+
var ACCESS_GRANTING_STATUSES = [
|
|
3217
|
+
"active",
|
|
3218
|
+
"past_due_grace"
|
|
3219
|
+
];
|
|
3220
|
+
var BLOCKING_STATUSES = [
|
|
3221
|
+
"blocked",
|
|
3222
|
+
"expired",
|
|
3223
|
+
"canceled",
|
|
3224
|
+
"trial_expired"
|
|
3225
|
+
];
|
|
3165
3226
|
function useSubscription() {
|
|
3166
3227
|
const context = useFFIDContext();
|
|
3167
3228
|
const client = useFFIDClient();
|
|
@@ -3179,12 +3240,23 @@ function useSubscription() {
|
|
|
3179
3240
|
const isActive = subscription?.status === "active";
|
|
3180
3241
|
const isTrialing = subscription?.status === "trialing";
|
|
3181
3242
|
const isCanceled = subscription?.status === "canceled";
|
|
3182
|
-
const
|
|
3183
|
-
if (!
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3243
|
+
const effectiveStatus = react.useMemo(() => {
|
|
3244
|
+
if (!subscription) return null;
|
|
3245
|
+
return computeEffectiveStatusFromSession({
|
|
3246
|
+
status: subscription.status,
|
|
3247
|
+
currentPeriodEnd: subscription.currentPeriodEnd,
|
|
3248
|
+
trialEnd: subscription.trialEnd,
|
|
3249
|
+
// TODO(別Issue): FFID backend の session API (`/api/auth/session`) に
|
|
3250
|
+
// `pastDueSince` (ISO timestamp) を追加し、ここで渡す。現状は undefined
|
|
3251
|
+
// のため `computeEffectiveStatusFromSession` 内で currentPeriodEnd を
|
|
3252
|
+
// fallback に使う (Stripe は period end 近辺で past_due にする)。
|
|
3253
|
+
// Canonical な verdict が必要な consumer は `/ext/check` を直接呼ぶ。
|
|
3254
|
+
pastDueSince: void 0
|
|
3255
|
+
});
|
|
3256
|
+
}, [subscription]);
|
|
3257
|
+
const isGrace = effectiveStatus === "past_due_grace";
|
|
3258
|
+
const isBlocked = effectiveStatus !== null && BLOCKING_STATUSES.includes(effectiveStatus);
|
|
3259
|
+
const isTrialExpired = effectiveStatus === "trial_expired";
|
|
3188
3260
|
const hasPlan = react.useCallback(
|
|
3189
3261
|
(plans) => {
|
|
3190
3262
|
if (!planCode) return false;
|
|
@@ -3194,6 +3266,10 @@ function useSubscription() {
|
|
|
3194
3266
|
[planCode]
|
|
3195
3267
|
);
|
|
3196
3268
|
const hasAccess = react.useCallback(() => {
|
|
3269
|
+
if (effectiveStatus === null) return false;
|
|
3270
|
+
return ACCESS_GRANTING_STATUSES.includes(effectiveStatus);
|
|
3271
|
+
}, [effectiveStatus]);
|
|
3272
|
+
const hasAccessLegacy = react.useCallback(() => {
|
|
3197
3273
|
if (isTrialExpired) return false;
|
|
3198
3274
|
return isActive || isTrialing;
|
|
3199
3275
|
}, [isActive, isTrialing, isTrialExpired]);
|
|
@@ -3204,8 +3280,12 @@ function useSubscription() {
|
|
|
3204
3280
|
isTrialing,
|
|
3205
3281
|
isTrialExpired,
|
|
3206
3282
|
isCanceled,
|
|
3283
|
+
effectiveStatus,
|
|
3284
|
+
isBlocked,
|
|
3285
|
+
isGrace,
|
|
3207
3286
|
hasPlan,
|
|
3208
|
-
hasAccess
|
|
3287
|
+
hasAccess,
|
|
3288
|
+
hasAccessLegacy
|
|
3209
3289
|
};
|
|
3210
3290
|
}
|
|
3211
3291
|
function withSubscription(Component, options = {}) {
|
|
@@ -4441,6 +4521,7 @@ exports.FFIDUserMenu = FFIDUserMenu;
|
|
|
4441
4521
|
exports.FFID_ANNOUNCEMENTS_ERROR_CODES = FFID_ANNOUNCEMENTS_ERROR_CODES;
|
|
4442
4522
|
exports.FFID_INQUIRY_CATEGORIES = FFID_INQUIRY_CATEGORIES;
|
|
4443
4523
|
exports.FFID_INQUIRY_CATEGORIES_SITE_2026 = FFID_INQUIRY_CATEGORIES_SITE_2026;
|
|
4524
|
+
exports.computeEffectiveStatusFromSession = computeEffectiveStatusFromSession;
|
|
4444
4525
|
exports.createFFIDAnnouncementsClient = createFFIDAnnouncementsClient;
|
|
4445
4526
|
exports.createFFIDClient = createFFIDClient;
|
|
4446
4527
|
exports.createTokenStore = createTokenStore;
|
|
@@ -805,7 +805,7 @@ function createProfileMethods(deps) {
|
|
|
805
805
|
}
|
|
806
806
|
|
|
807
807
|
// src/client/version-check.ts
|
|
808
|
-
var SDK_VERSION = "2.
|
|
808
|
+
var SDK_VERSION = "2.19.0";
|
|
809
809
|
var SDK_USER_AGENT = `FFID-SDK/${SDK_VERSION} (TypeScript)`;
|
|
810
810
|
var SDK_VERSION_HEADER = "X-FFID-SDK-Version";
|
|
811
811
|
function sdkHeaders() {
|
|
@@ -3160,6 +3160,67 @@ function FFIDOrganizationSwitcher({
|
|
|
3160
3160
|
] })
|
|
3161
3161
|
] });
|
|
3162
3162
|
}
|
|
3163
|
+
|
|
3164
|
+
// src/subscriptions/types.ts
|
|
3165
|
+
var PAST_DUE_GRACE_PERIOD_DAYS = 7;
|
|
3166
|
+
var HOURS_PER_DAY = 24;
|
|
3167
|
+
var MINUTES_PER_HOUR = 60;
|
|
3168
|
+
var SECONDS_PER_MINUTE = 60;
|
|
3169
|
+
var MS_PER_SECOND2 = 1e3;
|
|
3170
|
+
var MS_PER_DAY = HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE * MS_PER_SECOND2;
|
|
3171
|
+
|
|
3172
|
+
// src/subscriptions/compute-effective-status.ts
|
|
3173
|
+
function isExpired(isoTimestamp, nowMs) {
|
|
3174
|
+
if (!isoTimestamp) return null;
|
|
3175
|
+
const parsed = Date.parse(isoTimestamp);
|
|
3176
|
+
if (Number.isNaN(parsed)) return null;
|
|
3177
|
+
return parsed < nowMs;
|
|
3178
|
+
}
|
|
3179
|
+
function computeEffectiveStatusFromSession(input, nowMs = Date.now()) {
|
|
3180
|
+
const { status, currentPeriodEnd, trialEnd, pastDueSince } = input;
|
|
3181
|
+
switch (status) {
|
|
3182
|
+
case "trialing": {
|
|
3183
|
+
const relevantEnd = trialEnd ?? currentPeriodEnd;
|
|
3184
|
+
const expired = isExpired(relevantEnd, nowMs);
|
|
3185
|
+
return expired === true ? "trial_expired" : "active";
|
|
3186
|
+
}
|
|
3187
|
+
case "active": {
|
|
3188
|
+
const expired = isExpired(currentPeriodEnd, nowMs);
|
|
3189
|
+
return expired === true ? "expired" : "active";
|
|
3190
|
+
}
|
|
3191
|
+
case "past_due": {
|
|
3192
|
+
const baselineIso = pastDueSince ?? currentPeriodEnd;
|
|
3193
|
+
if (!baselineIso) return "blocked";
|
|
3194
|
+
const baselineMs = Date.parse(baselineIso);
|
|
3195
|
+
if (Number.isNaN(baselineMs)) return "blocked";
|
|
3196
|
+
const graceEndMs = baselineMs + PAST_DUE_GRACE_PERIOD_DAYS * MS_PER_DAY;
|
|
3197
|
+
return graceEndMs > nowMs ? "past_due_grace" : "blocked";
|
|
3198
|
+
}
|
|
3199
|
+
case "pending_invoice":
|
|
3200
|
+
return "active";
|
|
3201
|
+
case "paused":
|
|
3202
|
+
case "incomplete":
|
|
3203
|
+
return "active";
|
|
3204
|
+
case "canceled":
|
|
3205
|
+
return "canceled";
|
|
3206
|
+
case "unpaid":
|
|
3207
|
+
case "incomplete_expired":
|
|
3208
|
+
return "blocked";
|
|
3209
|
+
default: {
|
|
3210
|
+
return "blocked";
|
|
3211
|
+
}
|
|
3212
|
+
}
|
|
3213
|
+
}
|
|
3214
|
+
var ACCESS_GRANTING_STATUSES = [
|
|
3215
|
+
"active",
|
|
3216
|
+
"past_due_grace"
|
|
3217
|
+
];
|
|
3218
|
+
var BLOCKING_STATUSES = [
|
|
3219
|
+
"blocked",
|
|
3220
|
+
"expired",
|
|
3221
|
+
"canceled",
|
|
3222
|
+
"trial_expired"
|
|
3223
|
+
];
|
|
3163
3224
|
function useSubscription() {
|
|
3164
3225
|
const context = useFFIDContext();
|
|
3165
3226
|
const client = useFFIDClient();
|
|
@@ -3177,12 +3238,23 @@ function useSubscription() {
|
|
|
3177
3238
|
const isActive = subscription?.status === "active";
|
|
3178
3239
|
const isTrialing = subscription?.status === "trialing";
|
|
3179
3240
|
const isCanceled = subscription?.status === "canceled";
|
|
3180
|
-
const
|
|
3181
|
-
if (!
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3241
|
+
const effectiveStatus = useMemo(() => {
|
|
3242
|
+
if (!subscription) return null;
|
|
3243
|
+
return computeEffectiveStatusFromSession({
|
|
3244
|
+
status: subscription.status,
|
|
3245
|
+
currentPeriodEnd: subscription.currentPeriodEnd,
|
|
3246
|
+
trialEnd: subscription.trialEnd,
|
|
3247
|
+
// TODO(別Issue): FFID backend の session API (`/api/auth/session`) に
|
|
3248
|
+
// `pastDueSince` (ISO timestamp) を追加し、ここで渡す。現状は undefined
|
|
3249
|
+
// のため `computeEffectiveStatusFromSession` 内で currentPeriodEnd を
|
|
3250
|
+
// fallback に使う (Stripe は period end 近辺で past_due にする)。
|
|
3251
|
+
// Canonical な verdict が必要な consumer は `/ext/check` を直接呼ぶ。
|
|
3252
|
+
pastDueSince: void 0
|
|
3253
|
+
});
|
|
3254
|
+
}, [subscription]);
|
|
3255
|
+
const isGrace = effectiveStatus === "past_due_grace";
|
|
3256
|
+
const isBlocked = effectiveStatus !== null && BLOCKING_STATUSES.includes(effectiveStatus);
|
|
3257
|
+
const isTrialExpired = effectiveStatus === "trial_expired";
|
|
3186
3258
|
const hasPlan = useCallback(
|
|
3187
3259
|
(plans) => {
|
|
3188
3260
|
if (!planCode) return false;
|
|
@@ -3192,6 +3264,10 @@ function useSubscription() {
|
|
|
3192
3264
|
[planCode]
|
|
3193
3265
|
);
|
|
3194
3266
|
const hasAccess = useCallback(() => {
|
|
3267
|
+
if (effectiveStatus === null) return false;
|
|
3268
|
+
return ACCESS_GRANTING_STATUSES.includes(effectiveStatus);
|
|
3269
|
+
}, [effectiveStatus]);
|
|
3270
|
+
const hasAccessLegacy = useCallback(() => {
|
|
3195
3271
|
if (isTrialExpired) return false;
|
|
3196
3272
|
return isActive || isTrialing;
|
|
3197
3273
|
}, [isActive, isTrialing, isTrialExpired]);
|
|
@@ -3202,8 +3278,12 @@ function useSubscription() {
|
|
|
3202
3278
|
isTrialing,
|
|
3203
3279
|
isTrialExpired,
|
|
3204
3280
|
isCanceled,
|
|
3281
|
+
effectiveStatus,
|
|
3282
|
+
isBlocked,
|
|
3283
|
+
isGrace,
|
|
3205
3284
|
hasPlan,
|
|
3206
|
-
hasAccess
|
|
3285
|
+
hasAccess,
|
|
3286
|
+
hasAccessLegacy
|
|
3207
3287
|
};
|
|
3208
3288
|
}
|
|
3209
3289
|
function withSubscription(Component, options = {}) {
|
|
@@ -4426,4 +4506,4 @@ function FFIDInquiryForm({
|
|
|
4426
4506
|
);
|
|
4427
4507
|
}
|
|
4428
4508
|
|
|
4429
|
-
export { DEFAULT_API_BASE_URL, FFIDAnnouncementBadge, FFIDAnnouncementList, FFIDInquiryForm, FFIDLoginButton, FFIDOrganizationSwitcher, FFIDProvider, FFIDSDKError, FFIDSubscriptionBadge, FFIDUserMenu, FFID_ANNOUNCEMENTS_ERROR_CODES, FFID_INQUIRY_CATEGORIES, FFID_INQUIRY_CATEGORIES_SITE_2026, createFFIDAnnouncementsClient, createFFIDClient, createTokenStore, generateCodeChallenge, generateCodeVerifier, isFFIDInquiryCategorySite2026, normalizeRedirectUri, retrieveCodeVerifier, storeCodeVerifier, useFFID, useFFIDAnnouncements, useFFIDContext, useSubscription, withSubscription };
|
|
4509
|
+
export { DEFAULT_API_BASE_URL, FFIDAnnouncementBadge, FFIDAnnouncementList, FFIDInquiryForm, FFIDLoginButton, FFIDOrganizationSwitcher, FFIDProvider, FFIDSDKError, FFIDSubscriptionBadge, FFIDUserMenu, FFID_ANNOUNCEMENTS_ERROR_CODES, FFID_INQUIRY_CATEGORIES, FFID_INQUIRY_CATEGORIES_SITE_2026, computeEffectiveStatusFromSession, createFFIDAnnouncementsClient, createFFIDClient, createTokenStore, generateCodeChallenge, generateCodeVerifier, isFFIDInquiryCategorySite2026, normalizeRedirectUri, retrieveCodeVerifier, storeCodeVerifier, useFFID, useFFIDAnnouncements, useFFIDContext, useSubscription, withSubscription };
|
|
@@ -1,34 +1,34 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkBBXUZS4U_cjs = require('../chunk-BBXUZS4U.cjs');
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
Object.defineProperty(exports, "FFIDAnnouncementBadge", {
|
|
8
8
|
enumerable: true,
|
|
9
|
-
get: function () { return
|
|
9
|
+
get: function () { return chunkBBXUZS4U_cjs.FFIDAnnouncementBadge; }
|
|
10
10
|
});
|
|
11
11
|
Object.defineProperty(exports, "FFIDAnnouncementList", {
|
|
12
12
|
enumerable: true,
|
|
13
|
-
get: function () { return
|
|
13
|
+
get: function () { return chunkBBXUZS4U_cjs.FFIDAnnouncementList; }
|
|
14
14
|
});
|
|
15
15
|
Object.defineProperty(exports, "FFIDInquiryForm", {
|
|
16
16
|
enumerable: true,
|
|
17
|
-
get: function () { return
|
|
17
|
+
get: function () { return chunkBBXUZS4U_cjs.FFIDInquiryForm; }
|
|
18
18
|
});
|
|
19
19
|
Object.defineProperty(exports, "FFIDLoginButton", {
|
|
20
20
|
enumerable: true,
|
|
21
|
-
get: function () { return
|
|
21
|
+
get: function () { return chunkBBXUZS4U_cjs.FFIDLoginButton; }
|
|
22
22
|
});
|
|
23
23
|
Object.defineProperty(exports, "FFIDOrganizationSwitcher", {
|
|
24
24
|
enumerable: true,
|
|
25
|
-
get: function () { return
|
|
25
|
+
get: function () { return chunkBBXUZS4U_cjs.FFIDOrganizationSwitcher; }
|
|
26
26
|
});
|
|
27
27
|
Object.defineProperty(exports, "FFIDSubscriptionBadge", {
|
|
28
28
|
enumerable: true,
|
|
29
|
-
get: function () { return
|
|
29
|
+
get: function () { return chunkBBXUZS4U_cjs.FFIDSubscriptionBadge; }
|
|
30
30
|
});
|
|
31
31
|
Object.defineProperty(exports, "FFIDUserMenu", {
|
|
32
32
|
enumerable: true,
|
|
33
|
-
get: function () { return
|
|
33
|
+
get: function () { return chunkBBXUZS4U_cjs.FFIDUserMenu; }
|
|
34
34
|
});
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { M as FFIDAnnouncementBadge, al as FFIDAnnouncementBadgeClassNames, am as FFIDAnnouncementBadgeProps, N as FFIDAnnouncementList, an as FFIDAnnouncementListClassNames, ao as FFIDAnnouncementListProps, V as FFIDInquiryForm, W as FFIDInquiryFormCategoryItem, X as FFIDInquiryFormClassNames, Y as FFIDInquiryFormOrganization, Z as FFIDInquiryFormPlaceholderContext, _ as FFIDInquiryFormPrefill, $ as FFIDInquiryFormProps, a0 as FFIDInquiryFormSubmitData, a1 as FFIDInquiryFormSubmitResult, a3 as FFIDLoginButton, ap as FFIDLoginButtonProps, a9 as FFIDOrganizationSwitcher, aq as FFIDOrganizationSwitcherClassNames, ar as FFIDOrganizationSwitcherProps, ac as FFIDSubscriptionBadge, as as FFIDSubscriptionBadgeClassNames, at as FFIDSubscriptionBadgeProps, ae as FFIDUserMenu, au as FFIDUserMenuClassNames, av as FFIDUserMenuProps } from '../index-0D2vYSLq.cjs';
|
|
2
2
|
import 'react/jsx-runtime';
|
|
3
3
|
import 'react';
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { M as FFIDAnnouncementBadge, al as FFIDAnnouncementBadgeClassNames, am as FFIDAnnouncementBadgeProps, N as FFIDAnnouncementList, an as FFIDAnnouncementListClassNames, ao as FFIDAnnouncementListProps, V as FFIDInquiryForm, W as FFIDInquiryFormCategoryItem, X as FFIDInquiryFormClassNames, Y as FFIDInquiryFormOrganization, Z as FFIDInquiryFormPlaceholderContext, _ as FFIDInquiryFormPrefill, $ as FFIDInquiryFormProps, a0 as FFIDInquiryFormSubmitData, a1 as FFIDInquiryFormSubmitResult, a3 as FFIDLoginButton, ap as FFIDLoginButtonProps, a9 as FFIDOrganizationSwitcher, aq as FFIDOrganizationSwitcherClassNames, ar as FFIDOrganizationSwitcherProps, ac as FFIDSubscriptionBadge, as as FFIDSubscriptionBadgeClassNames, at as FFIDSubscriptionBadgeProps, ae as FFIDUserMenu, au as FFIDUserMenuClassNames, av as FFIDUserMenuProps } from '../index-0D2vYSLq.js';
|
|
2
2
|
import 'react/jsx-runtime';
|
|
3
3
|
import 'react';
|
package/dist/components/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { FFIDAnnouncementBadge, FFIDAnnouncementList, FFIDInquiryForm, FFIDLoginButton, FFIDOrganizationSwitcher, FFIDSubscriptionBadge, FFIDUserMenu } from '../chunk-
|
|
1
|
+
export { FFIDAnnouncementBadge, FFIDAnnouncementList, FFIDInquiryForm, FFIDLoginButton, FFIDOrganizationSwitcher, FFIDSubscriptionBadge, FFIDUserMenu } from '../chunk-SXYB5QM3.js';
|
|
@@ -22,6 +22,42 @@ interface FFIDCacheConfig {
|
|
|
22
22
|
ttl: number;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Subscription access-control types (Chain of Pacts / Issue #2443).
|
|
27
|
+
*
|
|
28
|
+
* Defines `EffectiveSubscriptionStatus` — the semantic status surfaced by
|
|
29
|
+
* `/api/v1/subscriptions/ext/check` for external services that need a single
|
|
30
|
+
* value to drive access-control decisions. Mirrors the server-side type in
|
|
31
|
+
* `src/lib/common/subscription-helpers.ts` so the FFID backend, SDK, and
|
|
32
|
+
* consuming services agree on the same vocabulary.
|
|
33
|
+
*/
|
|
34
|
+
/**
|
|
35
|
+
* Semantic subscription status used for access-control decisions by external
|
|
36
|
+
* services.
|
|
37
|
+
*
|
|
38
|
+
* Unlike the raw DB `FFIDSubscriptionStatus` (which keeps the Stripe-aligned
|
|
39
|
+
* status machine, e.g. `past_due` / `incomplete` / `incomplete_expired`), this
|
|
40
|
+
* narrowed union flattens dunning-window vs. hard-block cases so callers can
|
|
41
|
+
* branch on a single value:
|
|
42
|
+
*
|
|
43
|
+
* - `active` — normal paying subscription; grant full access.
|
|
44
|
+
* - `past_due_grace` — payment failed but FFID is still retrying. Service
|
|
45
|
+
* SHOULD keep access and surface a recovery banner.
|
|
46
|
+
* - `blocked` — payment dunning exhausted (`past_due` over grace,
|
|
47
|
+
* `unpaid`, `incomplete_expired`, `paused`, `incomplete`). Deny access.
|
|
48
|
+
* - `canceled` — contract ended (voluntary or auto-cancel). Deny access;
|
|
49
|
+
* check the accompanying `reactivatable` flag before showing a restart CTA.
|
|
50
|
+
* - `trial_expired` — `status = 'trialing'` but the trial end is in the past.
|
|
51
|
+
* DB row is still `trialing`; deny access and prompt for a paid plan.
|
|
52
|
+
* - `expired` — `status = 'active'` but `current_period_end` is in the past.
|
|
53
|
+
* This is the FFID-internal runtime-expired case (see
|
|
54
|
+
* `getEffectiveSubscriptionStatus` on the server). Deny access.
|
|
55
|
+
*
|
|
56
|
+
* @see /api/v1/subscriptions/ext/check
|
|
57
|
+
* @see docs/03-implementation/EXPIRED_CONTRACT_HANDLING.md
|
|
58
|
+
*/
|
|
59
|
+
type EffectiveSubscriptionStatus = 'active' | 'past_due_grace' | 'blocked' | 'canceled' | 'trial_expired' | 'expired';
|
|
60
|
+
|
|
25
61
|
/**
|
|
26
62
|
* FFID SDK Type Definitions
|
|
27
63
|
*
|
|
@@ -284,10 +320,59 @@ interface FFIDSubscriptionContextValue {
|
|
|
284
320
|
isTrialExpired: boolean;
|
|
285
321
|
/** Whether subscription is canceled */
|
|
286
322
|
isCanceled: boolean;
|
|
323
|
+
/**
|
|
324
|
+
* Semantic access-control status for the current subscription.
|
|
325
|
+
*
|
|
326
|
+
* Always computed locally from the session response via
|
|
327
|
+
* `computeEffectiveStatusFromSession` (status + currentPeriodEnd + trialEnd).
|
|
328
|
+
* The session endpoint does not currently surface an authoritative
|
|
329
|
+
* effectiveStatus, so this is a best-effort approximation suitable for UI
|
|
330
|
+
* decisions that tolerate ~1 request worth of staleness.
|
|
331
|
+
*
|
|
332
|
+
* For authoritative decisions (e.g., paywall gates on billing-critical UI,
|
|
333
|
+
* grace-window banners that depend on `gracePeriodEndsAt`, or reactivation
|
|
334
|
+
* CTAs that depend on `reactivatable`), call the server-side
|
|
335
|
+
* `/api/v1/subscriptions/ext/check` endpoint directly — it returns a richer
|
|
336
|
+
* payload (`effectiveStatus`, `gracePeriodEndsAt`, `reactivatable`) computed
|
|
337
|
+
* against the live DB.
|
|
338
|
+
*
|
|
339
|
+
* `null` when there is no subscription for the current service.
|
|
340
|
+
*
|
|
341
|
+
* @see {@link EffectiveSubscriptionStatus}
|
|
342
|
+
*/
|
|
343
|
+
effectiveStatus: EffectiveSubscriptionStatus | null;
|
|
344
|
+
/**
|
|
345
|
+
* Whether the subscription is in an access-blocking terminal state
|
|
346
|
+
* (`blocked` | `expired` | `canceled` | `trial_expired`).
|
|
347
|
+
*/
|
|
348
|
+
isBlocked: boolean;
|
|
349
|
+
/**
|
|
350
|
+
* Whether the subscription is inside the payment-failure grace window
|
|
351
|
+
* (`past_due_grace`). Access is still granted by `hasAccess`, but UIs
|
|
352
|
+
* should surface a recovery banner.
|
|
353
|
+
*/
|
|
354
|
+
isGrace: boolean;
|
|
287
355
|
/** Check if user has specific plan */
|
|
288
356
|
hasPlan: (plans: string | string[]) => boolean;
|
|
289
|
-
/**
|
|
357
|
+
/**
|
|
358
|
+
* Whether the user currently has access.
|
|
359
|
+
*
|
|
360
|
+
* Post Chain-of-Pacts (Issue #2444): `true` when
|
|
361
|
+
* `effectiveStatus === 'active' || effectiveStatus === 'past_due_grace'`.
|
|
362
|
+
* Legacy consumers that need the pre-2444 semantics
|
|
363
|
+
* (`status === 'active' || status === 'trialing'`) can use
|
|
364
|
+
* `hasAccessLegacy()`.
|
|
365
|
+
*/
|
|
290
366
|
hasAccess: () => boolean;
|
|
367
|
+
/**
|
|
368
|
+
* Pre-2444 `hasAccess` semantics: `true` when the raw DB status is
|
|
369
|
+
* `active` or `trialing` (and, for trialing, the trial is not runtime-
|
|
370
|
+
* expired). Kept as a backwards-compatibility escape hatch so services
|
|
371
|
+
* that have not yet migrated to the effective-status model keep working.
|
|
372
|
+
*
|
|
373
|
+
* @deprecated Prefer `hasAccess()` or branch on `effectiveStatus` directly.
|
|
374
|
+
*/
|
|
375
|
+
hasAccessLegacy: () => boolean;
|
|
291
376
|
}
|
|
292
377
|
/**
|
|
293
378
|
* Logger interface for SDK debug output
|
|
@@ -1301,4 +1386,4 @@ interface FFIDInquiryFormPlaceholderContext {
|
|
|
1301
1386
|
}
|
|
1302
1387
|
declare function FFIDInquiryForm({ mode, prefill, organizations, preselectedOrganizationId, categories, termsVersion, privacyVersion, termsHref, privacyHref, turnstileToken, turnstileSlot, onSubmit, onChange, separateLegalCheckboxes, messagePlaceholder, requireCategorySelection, unstyled, classNames, locale, className, }: FFIDInquiryFormProps): react_jsx_runtime.JSX.Element;
|
|
1303
1388
|
|
|
1304
|
-
export { type
|
|
1389
|
+
export { type FFIDInquiryFormProps as $, type FFIDSubscription as A, type FFIDSubscriptionContextValue as B, type FFIDAnnouncementsClientConfig as C, type FFIDAnnouncementsApiResponse as D, type EffectiveSubscriptionStatus as E, type FFIDSubscriptionStatus as F, type AnnouncementListResponse as G, type FFIDAnnouncementsLogger as H, type Announcement as I, type AnnouncementStatus as J, type AnnouncementType as K, type ListAnnouncementsOptions as L, FFIDAnnouncementBadge as M, FFIDAnnouncementList as N, type FFIDAnnouncementsError as O, type FFIDAnnouncementsErrorCode as P, type FFIDAnnouncementsServerResponse as Q, type FFIDCacheConfig as R, type FFIDContextValue as S, type FFIDInquiryCategory as T, type FFIDInquiryCategorySite2026 as U, FFIDInquiryForm as V, type FFIDInquiryFormCategoryItem as W, type FFIDInquiryFormClassNames as X, type FFIDInquiryFormOrganization as Y, type FFIDInquiryFormPlaceholderContext as Z, type FFIDInquiryFormPrefill as _, type FFIDConfig as a, type FFIDInquiryFormSubmitData as a0, type FFIDInquiryFormSubmitResult as a1, type FFIDJwtClaims as a2, FFIDLoginButton as a3, type FFIDMemberStatus as a4, type FFIDOAuthTokenResponse as a5, type FFIDOAuthUserInfoMemberRole as a6, type FFIDOAuthUserInfoSubscription as a7, type FFIDOrganizationMember as a8, FFIDOrganizationSwitcher as a9, type FFIDRedirectErrorCode as aa, type FFIDSeatModel as ab, FFIDSubscriptionBadge as ac, type FFIDTokenIntrospectionResponse as ad, FFIDUserMenu as ae, FFID_INQUIRY_CATEGORIES as af, FFID_INQUIRY_CATEGORIES_SITE_2026 as ag, type UseFFIDAnnouncementsOptions as ah, type UseFFIDAnnouncementsReturn as ai, isFFIDInquiryCategorySite2026 as aj, useFFIDAnnouncements as ak, type FFIDAnnouncementBadgeClassNames as al, type FFIDAnnouncementBadgeProps as am, type FFIDAnnouncementListClassNames as an, type FFIDAnnouncementListProps as ao, type FFIDLoginButtonProps as ap, type FFIDOrganizationSwitcherClassNames as aq, type FFIDOrganizationSwitcherProps as ar, type FFIDSubscriptionBadgeClassNames as as, type FFIDSubscriptionBadgeProps as at, type FFIDUserMenuClassNames as au, type FFIDUserMenuProps as av, type FFIDApiResponse as b, type FFIDSessionResponse as c, type FFIDRedirectResult as d, type FFIDError as e, type FFIDSubscriptionCheckResponse as f, type FFIDListMembersResponse as g, type FFIDMemberRole as h, type FFIDUpdateMemberRoleResponse as i, type FFIDRemoveMemberResponse as j, type FFIDProfileCallOptions as k, type FFIDUserProfile as l, type FFIDUpdateUserProfileRequest as m, type FFIDCreateCheckoutParams as n, type FFIDCheckoutSessionResponse as o, type FFIDCreatePortalParams as p, type FFIDPortalSessionResponse as q, type FFIDVerifyAccessTokenOptions as r, type FFIDOAuthUserInfo as s, type FFIDInquiryCreateParams as t, type FFIDInquiryCreateResponse as u, type FFIDAuthMode as v, type FFIDLogger as w, type FFIDCacheAdapter as x, type FFIDUser as y, type FFIDOrganization as z };
|