@feelflow/ffid-sdk 4.0.0 → 4.2.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 CHANGED
@@ -291,6 +291,37 @@ export default function Layout({ children }: { children: React.ReactNode }) {
291
291
 
292
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
293
 
294
+ ### Server-side service access decision
295
+
296
+ API route / middleware では `checkServiceAccess()` を使う。FFID の `/api/v1/subscriptions/ext/check` が返す canonical decision をそのまま受け取り、外部サービス側で `past_due_since + 7d`、`current_period_end + 7d`、`payment_failed_at + 7d` のような lifecycle date math を再実装しない。
297
+
298
+ ```ts
299
+ import { createFFIDClient } from '@feelflow/ffid-sdk'
300
+
301
+ const ffid = createFFIDClient({
302
+ serviceCode: 'flow-board-ai',
303
+ scope: '',
304
+ authMode: 'service-key',
305
+ serviceApiKey: process.env.FFID_SERVICE_API_KEY!,
306
+ })
307
+
308
+ const { data: access, error } = await ffid.checkServiceAccess({
309
+ userId,
310
+ organizationId,
311
+ allowGrace: true,
312
+ })
313
+
314
+ if (error || !access?.hasAccess) {
315
+ return Response.redirect('/contract-required')
316
+ }
317
+ ```
318
+
319
+ - `checkServiceAccess()` は **`data.hasAccess` が唯一の gate**。`result.error` は入力 validation など SDK が decision を作れない場合にだけ返る。
320
+ - `hasAccess`: FFID が決めた canonical access decision。`allowGrace=false` のときだけ SDK 側で `past_due_grace` を deny に変換する。
321
+ - `effectiveStatus`: `active` / `past_due_grace` / `blocked` / `canceled` / `trial_expired` / `expired`。
322
+ - `gracePeriodEndsAt`: `past_due_grace` が `blocked` に変わる時刻。表示・警告用であり、アクセス判定の source of truth にしない。
323
+ - `failPolicy`: 現在は `failClosed` 固定。FFID に到達できない、または canonical decision を取得できない場合は `result.error` ではなく `data.hasAccess=false` / `denialReason='ffid_unreachable'` / `data.error` を返す。呼び出し側は `result.error` だけで通過判定しない。
324
+
294
325
  ### getProfile() / updateProfile()
295
326
 
296
327
  ログイン中ユーザー自身のプロフィールを取得・更新するメソッド(`createFFIDClient` から呼び出し)。
@@ -808,7 +808,7 @@ function createProfileMethods(deps) {
808
808
  }
809
809
 
810
810
  // src/client/version-check.ts
811
- var SDK_VERSION = "4.0.0";
811
+ var SDK_VERSION = "4.2.0";
812
812
  var SDK_USER_AGENT = `FFID-SDK/${SDK_VERSION} (TypeScript)`;
813
813
  var SDK_VERSION_HEADER = "X-FFID-SDK-Version";
814
814
  function sdkHeaders() {
@@ -2224,6 +2224,73 @@ var FFID_ERROR_CODES = {
2224
2224
  TOKEN_VERIFICATION_ERROR: "TOKEN_VERIFICATION_ERROR"
2225
2225
  };
2226
2226
  var EXT_CHECK_ENDPOINT = "/api/v1/subscriptions/ext/check";
2227
+ var DEFAULT_ALLOW_GRACE = true;
2228
+ var DEFAULT_SERVICE_ACCESS_FAIL_POLICY = "failClosed";
2229
+ var SERVICE_ACCESS_DENIED_CODE = "SERVICE_ACCESS_DENIED";
2230
+ var ACCESS_GRANTING_EFFECTIVE_STATUSES = ["active", "past_due_grace"];
2231
+ var BLOCKING_EFFECTIVE_STATUSES = [
2232
+ "blocked",
2233
+ "canceled",
2234
+ "expired",
2235
+ "trial_expired"
2236
+ ];
2237
+ function resolveServiceAccessDenialReason(params, allowGrace) {
2238
+ if (params.hasAccess) {
2239
+ return null;
2240
+ }
2241
+ if (params.effectiveStatus === null) {
2242
+ return "no_subscription";
2243
+ }
2244
+ if (params.isGrace && !allowGrace) {
2245
+ return "grace_disallowed";
2246
+ }
2247
+ if (params.effectiveStatus === "active" || params.effectiveStatus === "past_due_grace") {
2248
+ return "blocked";
2249
+ }
2250
+ return params.effectiveStatus;
2251
+ }
2252
+ function toServiceAccessDecision(response, allowGrace) {
2253
+ const effectiveStatus = response.effectiveStatus ?? null;
2254
+ const serverHasAccess = response.hasAccess ?? (effectiveStatus !== null && ACCESS_GRANTING_EFFECTIVE_STATUSES.includes(effectiveStatus));
2255
+ const isGrace = response.isGrace ?? effectiveStatus === "past_due_grace";
2256
+ const hasAccess = serverHasAccess && (allowGrace || !isGrace);
2257
+ const isBlocked = response.isBlocked ?? (effectiveStatus === null || effectiveStatus !== null && BLOCKING_EFFECTIVE_STATUSES.includes(effectiveStatus));
2258
+ return {
2259
+ hasAccess,
2260
+ effectiveStatus,
2261
+ isGrace,
2262
+ isBlocked: isBlocked || !hasAccess,
2263
+ allowGrace,
2264
+ failPolicy: DEFAULT_SERVICE_ACCESS_FAIL_POLICY,
2265
+ denialReason: resolveServiceAccessDenialReason({ effectiveStatus, hasAccess, isGrace }, allowGrace),
2266
+ organizationId: response.organizationId,
2267
+ subscriptionId: response.subscriptionId,
2268
+ status: response.status,
2269
+ planCode: response.planCode,
2270
+ currentPeriodEnd: response.currentPeriodEnd,
2271
+ gracePeriodEndsAt: response.gracePeriodEndsAt ?? null,
2272
+ reactivatable: response.reactivatable ?? false
2273
+ };
2274
+ }
2275
+ function failClosedServiceAccessDecision(params, error) {
2276
+ return {
2277
+ hasAccess: false,
2278
+ effectiveStatus: null,
2279
+ isGrace: false,
2280
+ isBlocked: true,
2281
+ allowGrace: params.allowGrace ?? DEFAULT_ALLOW_GRACE,
2282
+ failPolicy: DEFAULT_SERVICE_ACCESS_FAIL_POLICY,
2283
+ denialReason: "ffid_unreachable",
2284
+ organizationId: params.organizationId || null,
2285
+ subscriptionId: null,
2286
+ status: null,
2287
+ planCode: null,
2288
+ currentPeriodEnd: null,
2289
+ gracePeriodEndsAt: null,
2290
+ reactivatable: false,
2291
+ error
2292
+ };
2293
+ }
2227
2294
  function resolveRedirectUri(raw, logger) {
2228
2295
  if (raw === null) return null;
2229
2296
  try {
@@ -2462,6 +2529,57 @@ function createFFIDClient(config) {
2462
2529
  `${EXT_CHECK_ENDPOINT}?${query.toString()}`
2463
2530
  );
2464
2531
  }
2532
+ async function checkServiceAccess(params) {
2533
+ const failPolicy = params.failPolicy ?? DEFAULT_SERVICE_ACCESS_FAIL_POLICY;
2534
+ if (failPolicy !== DEFAULT_SERVICE_ACCESS_FAIL_POLICY) {
2535
+ return {
2536
+ error: createError(
2537
+ "VALIDATION_ERROR",
2538
+ `failPolicy \u306F ${DEFAULT_SERVICE_ACCESS_FAIL_POLICY} \u306E\u307F\u6307\u5B9A\u3067\u304D\u307E\u3059`
2539
+ )
2540
+ };
2541
+ }
2542
+ const subscriptionParams = {
2543
+ organizationId: params.organizationId
2544
+ };
2545
+ if (params.userId !== void 0) {
2546
+ subscriptionParams.userId = params.userId;
2547
+ }
2548
+ const subscriptionResult = await checkSubscription(subscriptionParams);
2549
+ if (subscriptionResult.error) {
2550
+ if (subscriptionResult.error.code === "VALIDATION_ERROR") {
2551
+ return { error: subscriptionResult.error };
2552
+ }
2553
+ return {
2554
+ data: failClosedServiceAccessDecision(params, subscriptionResult.error)
2555
+ };
2556
+ }
2557
+ return {
2558
+ data: toServiceAccessDecision(
2559
+ subscriptionResult.data,
2560
+ params.allowGrace ?? DEFAULT_ALLOW_GRACE
2561
+ )
2562
+ };
2563
+ }
2564
+ async function requireServiceAccess(params) {
2565
+ const result = await checkServiceAccess(params);
2566
+ if (result.error) {
2567
+ return result;
2568
+ }
2569
+ if (!result.data.hasAccess) {
2570
+ const error = createError(
2571
+ SERVICE_ACCESS_DENIED_CODE,
2572
+ `FFID service access denied: ${result.data.denialReason ?? "unknown"}`
2573
+ );
2574
+ if (result.data.error) {
2575
+ error.details = { cause: result.data.error };
2576
+ }
2577
+ return {
2578
+ error
2579
+ };
2580
+ }
2581
+ return result;
2582
+ }
2465
2583
  const { createCheckoutSession, createPortalSession } = createBillingMethods({
2466
2584
  fetchWithAuth,
2467
2585
  createError
@@ -2560,6 +2678,8 @@ function createFFIDClient(config) {
2560
2678
  exchangeCodeForTokens,
2561
2679
  refreshAccessToken,
2562
2680
  checkSubscription,
2681
+ checkServiceAccess,
2682
+ requireServiceAccess,
2563
2683
  listMembers,
2564
2684
  updateMemberRole,
2565
2685
  removeMember,
@@ -806,7 +806,7 @@ function createProfileMethods(deps) {
806
806
  }
807
807
 
808
808
  // src/client/version-check.ts
809
- var SDK_VERSION = "4.0.0";
809
+ var SDK_VERSION = "4.2.0";
810
810
  var SDK_USER_AGENT = `FFID-SDK/${SDK_VERSION} (TypeScript)`;
811
811
  var SDK_VERSION_HEADER = "X-FFID-SDK-Version";
812
812
  function sdkHeaders() {
@@ -2222,6 +2222,73 @@ var FFID_ERROR_CODES = {
2222
2222
  TOKEN_VERIFICATION_ERROR: "TOKEN_VERIFICATION_ERROR"
2223
2223
  };
2224
2224
  var EXT_CHECK_ENDPOINT = "/api/v1/subscriptions/ext/check";
2225
+ var DEFAULT_ALLOW_GRACE = true;
2226
+ var DEFAULT_SERVICE_ACCESS_FAIL_POLICY = "failClosed";
2227
+ var SERVICE_ACCESS_DENIED_CODE = "SERVICE_ACCESS_DENIED";
2228
+ var ACCESS_GRANTING_EFFECTIVE_STATUSES = ["active", "past_due_grace"];
2229
+ var BLOCKING_EFFECTIVE_STATUSES = [
2230
+ "blocked",
2231
+ "canceled",
2232
+ "expired",
2233
+ "trial_expired"
2234
+ ];
2235
+ function resolveServiceAccessDenialReason(params, allowGrace) {
2236
+ if (params.hasAccess) {
2237
+ return null;
2238
+ }
2239
+ if (params.effectiveStatus === null) {
2240
+ return "no_subscription";
2241
+ }
2242
+ if (params.isGrace && !allowGrace) {
2243
+ return "grace_disallowed";
2244
+ }
2245
+ if (params.effectiveStatus === "active" || params.effectiveStatus === "past_due_grace") {
2246
+ return "blocked";
2247
+ }
2248
+ return params.effectiveStatus;
2249
+ }
2250
+ function toServiceAccessDecision(response, allowGrace) {
2251
+ const effectiveStatus = response.effectiveStatus ?? null;
2252
+ const serverHasAccess = response.hasAccess ?? (effectiveStatus !== null && ACCESS_GRANTING_EFFECTIVE_STATUSES.includes(effectiveStatus));
2253
+ const isGrace = response.isGrace ?? effectiveStatus === "past_due_grace";
2254
+ const hasAccess = serverHasAccess && (allowGrace || !isGrace);
2255
+ const isBlocked = response.isBlocked ?? (effectiveStatus === null || effectiveStatus !== null && BLOCKING_EFFECTIVE_STATUSES.includes(effectiveStatus));
2256
+ return {
2257
+ hasAccess,
2258
+ effectiveStatus,
2259
+ isGrace,
2260
+ isBlocked: isBlocked || !hasAccess,
2261
+ allowGrace,
2262
+ failPolicy: DEFAULT_SERVICE_ACCESS_FAIL_POLICY,
2263
+ denialReason: resolveServiceAccessDenialReason({ effectiveStatus, hasAccess, isGrace }, allowGrace),
2264
+ organizationId: response.organizationId,
2265
+ subscriptionId: response.subscriptionId,
2266
+ status: response.status,
2267
+ planCode: response.planCode,
2268
+ currentPeriodEnd: response.currentPeriodEnd,
2269
+ gracePeriodEndsAt: response.gracePeriodEndsAt ?? null,
2270
+ reactivatable: response.reactivatable ?? false
2271
+ };
2272
+ }
2273
+ function failClosedServiceAccessDecision(params, error) {
2274
+ return {
2275
+ hasAccess: false,
2276
+ effectiveStatus: null,
2277
+ isGrace: false,
2278
+ isBlocked: true,
2279
+ allowGrace: params.allowGrace ?? DEFAULT_ALLOW_GRACE,
2280
+ failPolicy: DEFAULT_SERVICE_ACCESS_FAIL_POLICY,
2281
+ denialReason: "ffid_unreachable",
2282
+ organizationId: params.organizationId || null,
2283
+ subscriptionId: null,
2284
+ status: null,
2285
+ planCode: null,
2286
+ currentPeriodEnd: null,
2287
+ gracePeriodEndsAt: null,
2288
+ reactivatable: false,
2289
+ error
2290
+ };
2291
+ }
2225
2292
  function resolveRedirectUri(raw, logger) {
2226
2293
  if (raw === null) return null;
2227
2294
  try {
@@ -2460,6 +2527,57 @@ function createFFIDClient(config) {
2460
2527
  `${EXT_CHECK_ENDPOINT}?${query.toString()}`
2461
2528
  );
2462
2529
  }
2530
+ async function checkServiceAccess(params) {
2531
+ const failPolicy = params.failPolicy ?? DEFAULT_SERVICE_ACCESS_FAIL_POLICY;
2532
+ if (failPolicy !== DEFAULT_SERVICE_ACCESS_FAIL_POLICY) {
2533
+ return {
2534
+ error: createError(
2535
+ "VALIDATION_ERROR",
2536
+ `failPolicy \u306F ${DEFAULT_SERVICE_ACCESS_FAIL_POLICY} \u306E\u307F\u6307\u5B9A\u3067\u304D\u307E\u3059`
2537
+ )
2538
+ };
2539
+ }
2540
+ const subscriptionParams = {
2541
+ organizationId: params.organizationId
2542
+ };
2543
+ if (params.userId !== void 0) {
2544
+ subscriptionParams.userId = params.userId;
2545
+ }
2546
+ const subscriptionResult = await checkSubscription(subscriptionParams);
2547
+ if (subscriptionResult.error) {
2548
+ if (subscriptionResult.error.code === "VALIDATION_ERROR") {
2549
+ return { error: subscriptionResult.error };
2550
+ }
2551
+ return {
2552
+ data: failClosedServiceAccessDecision(params, subscriptionResult.error)
2553
+ };
2554
+ }
2555
+ return {
2556
+ data: toServiceAccessDecision(
2557
+ subscriptionResult.data,
2558
+ params.allowGrace ?? DEFAULT_ALLOW_GRACE
2559
+ )
2560
+ };
2561
+ }
2562
+ async function requireServiceAccess(params) {
2563
+ const result = await checkServiceAccess(params);
2564
+ if (result.error) {
2565
+ return result;
2566
+ }
2567
+ if (!result.data.hasAccess) {
2568
+ const error = createError(
2569
+ SERVICE_ACCESS_DENIED_CODE,
2570
+ `FFID service access denied: ${result.data.denialReason ?? "unknown"}`
2571
+ );
2572
+ if (result.data.error) {
2573
+ error.details = { cause: result.data.error };
2574
+ }
2575
+ return {
2576
+ error
2577
+ };
2578
+ }
2579
+ return result;
2580
+ }
2463
2581
  const { createCheckoutSession, createPortalSession } = createBillingMethods({
2464
2582
  fetchWithAuth,
2465
2583
  createError
@@ -2558,6 +2676,8 @@ function createFFIDClient(config) {
2558
2676
  exchangeCodeForTokens,
2559
2677
  refreshAccessToken,
2560
2678
  checkSubscription,
2679
+ checkServiceAccess,
2680
+ requireServiceAccess,
2561
2681
  listMembers,
2562
2682
  updateMemberRole,
2563
2683
  removeMember,
@@ -1,34 +1,34 @@
1
1
  'use strict';
2
2
 
3
- var chunkXQL4YSTL_cjs = require('../chunk-XQL4YSTL.cjs');
3
+ var chunkU4XDH7TI_cjs = require('../chunk-U4XDH7TI.cjs');
4
4
 
5
5
 
6
6
 
7
7
  Object.defineProperty(exports, "FFIDAnnouncementBadge", {
8
8
  enumerable: true,
9
- get: function () { return chunkXQL4YSTL_cjs.FFIDAnnouncementBadge; }
9
+ get: function () { return chunkU4XDH7TI_cjs.FFIDAnnouncementBadge; }
10
10
  });
11
11
  Object.defineProperty(exports, "FFIDAnnouncementList", {
12
12
  enumerable: true,
13
- get: function () { return chunkXQL4YSTL_cjs.FFIDAnnouncementList; }
13
+ get: function () { return chunkU4XDH7TI_cjs.FFIDAnnouncementList; }
14
14
  });
15
15
  Object.defineProperty(exports, "FFIDInquiryForm", {
16
16
  enumerable: true,
17
- get: function () { return chunkXQL4YSTL_cjs.FFIDInquiryForm; }
17
+ get: function () { return chunkU4XDH7TI_cjs.FFIDInquiryForm; }
18
18
  });
19
19
  Object.defineProperty(exports, "FFIDLoginButton", {
20
20
  enumerable: true,
21
- get: function () { return chunkXQL4YSTL_cjs.FFIDLoginButton; }
21
+ get: function () { return chunkU4XDH7TI_cjs.FFIDLoginButton; }
22
22
  });
23
23
  Object.defineProperty(exports, "FFIDOrganizationSwitcher", {
24
24
  enumerable: true,
25
- get: function () { return chunkXQL4YSTL_cjs.FFIDOrganizationSwitcher; }
25
+ get: function () { return chunkU4XDH7TI_cjs.FFIDOrganizationSwitcher; }
26
26
  });
27
27
  Object.defineProperty(exports, "FFIDSubscriptionBadge", {
28
28
  enumerable: true,
29
- get: function () { return chunkXQL4YSTL_cjs.FFIDSubscriptionBadge; }
29
+ get: function () { return chunkU4XDH7TI_cjs.FFIDSubscriptionBadge; }
30
30
  });
31
31
  Object.defineProperty(exports, "FFIDUserMenu", {
32
32
  enumerable: true,
33
- get: function () { return chunkXQL4YSTL_cjs.FFIDUserMenu; }
33
+ get: function () { return chunkU4XDH7TI_cjs.FFIDUserMenu; }
34
34
  });
@@ -1,3 +1,3 @@
1
- export { N as FFIDAnnouncementBadge, an as FFIDAnnouncementBadgeClassNames, ao as FFIDAnnouncementBadgeProps, O as FFIDAnnouncementList, ap as FFIDAnnouncementListClassNames, aq as FFIDAnnouncementListProps, W as FFIDInquiryForm, X as FFIDInquiryFormCategoryItem, Y as FFIDInquiryFormClassNames, Z as FFIDInquiryFormLegalLayout, _ as FFIDInquiryFormOrganization, $ as FFIDInquiryFormPlaceholderContext, a0 as FFIDInquiryFormPrefill, a1 as FFIDInquiryFormProps, a2 as FFIDInquiryFormSubmitData, a3 as FFIDInquiryFormSubmitResult, a5 as FFIDLoginButton, ar as FFIDLoginButtonProps, ab as FFIDOrganizationSwitcher, as as FFIDOrganizationSwitcherClassNames, at as FFIDOrganizationSwitcherProps, ae as FFIDSubscriptionBadge, au as FFIDSubscriptionBadgeClassNames, av as FFIDSubscriptionBadgeProps, ag as FFIDUserMenu, aw as FFIDUserMenuClassNames, ax as FFIDUserMenuProps } from '../index-Cn8-3hgb.cjs';
1
+ export { P as FFIDAnnouncementBadge, ar as FFIDAnnouncementBadgeClassNames, as as FFIDAnnouncementBadgeProps, Q as FFIDAnnouncementList, at as FFIDAnnouncementListClassNames, au as FFIDAnnouncementListProps, Y as FFIDInquiryForm, Z as FFIDInquiryFormCategoryItem, _ as FFIDInquiryFormClassNames, $ as FFIDInquiryFormLegalLayout, a0 as FFIDInquiryFormOrganization, a1 as FFIDInquiryFormPlaceholderContext, a2 as FFIDInquiryFormPrefill, a3 as FFIDInquiryFormProps, a4 as FFIDInquiryFormSubmitData, a5 as FFIDInquiryFormSubmitResult, a7 as FFIDLoginButton, av as FFIDLoginButtonProps, ad as FFIDOrganizationSwitcher, aw as FFIDOrganizationSwitcherClassNames, ax as FFIDOrganizationSwitcherProps, ai as FFIDSubscriptionBadge, ay as FFIDSubscriptionBadgeClassNames, az as FFIDSubscriptionBadgeProps, ak as FFIDUserMenu, aA as FFIDUserMenuClassNames, aB as FFIDUserMenuProps } from '../index-DXgTH5vK.cjs';
2
2
  import 'react/jsx-runtime';
3
3
  import 'react';
@@ -1,3 +1,3 @@
1
- export { N as FFIDAnnouncementBadge, an as FFIDAnnouncementBadgeClassNames, ao as FFIDAnnouncementBadgeProps, O as FFIDAnnouncementList, ap as FFIDAnnouncementListClassNames, aq as FFIDAnnouncementListProps, W as FFIDInquiryForm, X as FFIDInquiryFormCategoryItem, Y as FFIDInquiryFormClassNames, Z as FFIDInquiryFormLegalLayout, _ as FFIDInquiryFormOrganization, $ as FFIDInquiryFormPlaceholderContext, a0 as FFIDInquiryFormPrefill, a1 as FFIDInquiryFormProps, a2 as FFIDInquiryFormSubmitData, a3 as FFIDInquiryFormSubmitResult, a5 as FFIDLoginButton, ar as FFIDLoginButtonProps, ab as FFIDOrganizationSwitcher, as as FFIDOrganizationSwitcherClassNames, at as FFIDOrganizationSwitcherProps, ae as FFIDSubscriptionBadge, au as FFIDSubscriptionBadgeClassNames, av as FFIDSubscriptionBadgeProps, ag as FFIDUserMenu, aw as FFIDUserMenuClassNames, ax as FFIDUserMenuProps } from '../index-Cn8-3hgb.js';
1
+ export { P as FFIDAnnouncementBadge, ar as FFIDAnnouncementBadgeClassNames, as as FFIDAnnouncementBadgeProps, Q as FFIDAnnouncementList, at as FFIDAnnouncementListClassNames, au as FFIDAnnouncementListProps, Y as FFIDInquiryForm, Z as FFIDInquiryFormCategoryItem, _ as FFIDInquiryFormClassNames, $ as FFIDInquiryFormLegalLayout, a0 as FFIDInquiryFormOrganization, a1 as FFIDInquiryFormPlaceholderContext, a2 as FFIDInquiryFormPrefill, a3 as FFIDInquiryFormProps, a4 as FFIDInquiryFormSubmitData, a5 as FFIDInquiryFormSubmitResult, a7 as FFIDLoginButton, av as FFIDLoginButtonProps, ad as FFIDOrganizationSwitcher, aw as FFIDOrganizationSwitcherClassNames, ax as FFIDOrganizationSwitcherProps, ai as FFIDSubscriptionBadge, ay as FFIDSubscriptionBadgeClassNames, az as FFIDSubscriptionBadgeProps, ak as FFIDUserMenu, aA as FFIDUserMenuClassNames, aB as FFIDUserMenuProps } from '../index-DXgTH5vK.js';
2
2
  import 'react/jsx-runtime';
3
3
  import 'react';
@@ -1 +1 @@
1
- export { FFIDAnnouncementBadge, FFIDAnnouncementList, FFIDInquiryForm, FFIDLoginButton, FFIDOrganizationSwitcher, FFIDSubscriptionBadge, FFIDUserMenu } from '../chunk-P35ULJLM.js';
1
+ export { FFIDAnnouncementBadge, FFIDAnnouncementList, FFIDInquiryForm, FFIDLoginButton, FFIDOrganizationSwitcher, FFIDSubscriptionBadge, FFIDUserMenu } from '../chunk-WI645CPU.js';
@@ -1,3 +1,5 @@
1
+ import { E as EffectiveSubscriptionStatus } from './types-5g_Bg6Ey.cjs';
2
+
1
3
  /**
2
4
  * Inquiry types exposed by the FFID SDK.
3
5
  *
@@ -183,6 +185,91 @@ interface FFIDCacheConfig {
183
185
  ttl: number;
184
186
  }
185
187
 
188
+ /**
189
+ * Canonical service-access types for subscription lifecycle decisions.
190
+ */
191
+
192
+ /** Subscription status values matching the FFID platform's SubscriptionStatus type */
193
+ type FFIDSubscriptionStatus = 'trialing' | 'active' | 'past_due' | 'canceled' | 'pending_invoice' | 'paused' | 'incomplete' | 'incomplete_expired' | 'unpaid';
194
+ interface FFIDSubscriptionCheckResponse {
195
+ hasActiveSubscription: boolean;
196
+ /**
197
+ * Canonical access decision returned by FFID's `/subscriptions/ext/check`.
198
+ *
199
+ * This is the server-side source of truth for service gates. Consumers
200
+ * should not recompute access from `currentPeriodEnd`, `past_due_since`, or
201
+ * local payment timestamps.
202
+ */
203
+ hasAccess?: boolean;
204
+ /** True when `effectiveStatus === 'past_due_grace'`. */
205
+ isGrace?: boolean;
206
+ /** True when FFID's canonical effective status denies service access. */
207
+ isBlocked?: boolean;
208
+ organizationId: string | null;
209
+ subscriptionId: string | null;
210
+ status: FFIDSubscriptionStatus | null;
211
+ planCode: string | null;
212
+ currentPeriodEnd: string | null;
213
+ /**
214
+ * Semantic FFID access-control status. `null` means the organization has no
215
+ * subscription row for this service.
216
+ */
217
+ effectiveStatus?: EffectiveSubscriptionStatus | null;
218
+ /**
219
+ * ISO timestamp at which `past_due_grace` flips to `blocked`; null outside
220
+ * the grace window.
221
+ */
222
+ gracePeriodEndsAt?: string | null;
223
+ /** Whether a canceled subscription can be resumed via a re-subscription flow. */
224
+ reactivatable?: boolean;
225
+ }
226
+ type FFIDServiceAccessFailPolicy = 'failClosed';
227
+ type FFIDServiceAccessDenialReason = 'no_subscription' | 'grace_disallowed' | 'blocked' | 'canceled' | 'expired' | 'trial_expired' | 'ffid_unreachable';
228
+ interface FFIDCheckServiceAccessParams {
229
+ userId?: string;
230
+ organizationId: string;
231
+ /**
232
+ * Whether `past_due_grace` should keep access open.
233
+ *
234
+ * @default true
235
+ */
236
+ allowGrace?: boolean;
237
+ /**
238
+ * Error policy when FFID cannot return a canonical decision.
239
+ *
240
+ * Currently only `failClosed` is supported: network/server/parse failures
241
+ * become `hasAccess=false` decisions with `denialReason='ffid_unreachable'`
242
+ * and the root cause in `decision.error`. Treat `hasAccess` as the gate.
243
+ */
244
+ failPolicy?: FFIDServiceAccessFailPolicy;
245
+ }
246
+ interface FFIDServiceAccessError {
247
+ code: string;
248
+ message: string;
249
+ details?: unknown;
250
+ }
251
+ interface FFIDServiceAccessDecision {
252
+ hasAccess: boolean;
253
+ effectiveStatus: EffectiveSubscriptionStatus | null;
254
+ isGrace: boolean;
255
+ isBlocked: boolean;
256
+ allowGrace: boolean;
257
+ failPolicy: FFIDServiceAccessFailPolicy;
258
+ denialReason: FFIDServiceAccessDenialReason | null;
259
+ organizationId: string | null;
260
+ subscriptionId: string | null;
261
+ status: FFIDSubscriptionStatus | null;
262
+ planCode: string | null;
263
+ currentPeriodEnd: string | null;
264
+ gracePeriodEndsAt: string | null;
265
+ reactivatable: boolean;
266
+ /**
267
+ * Present when the decision was produced by the SDK fail-closed policy
268
+ * rather than by a successful FFID response.
269
+ */
270
+ error?: FFIDServiceAccessError;
271
+ }
272
+
186
273
  /** Billing interval for subscriptions */
187
274
  type FFIDBillingInterval = 'monthly' | 'yearly';
188
275
  /**
@@ -736,16 +823,7 @@ type FFIDApiResponse<T> = {
736
823
  data?: undefined;
737
824
  error: FFIDError;
738
825
  };
739
- /** Subscription status values matching the FFID platform's SubscriptionStatus type */
740
- type FFIDSubscriptionStatus = 'trialing' | 'active' | 'past_due' | 'canceled' | 'pending_invoice' | 'paused' | 'incomplete' | 'incomplete_expired' | 'unpaid';
741
- interface FFIDSubscriptionCheckResponse {
742
- hasActiveSubscription: boolean;
743
- organizationId: string | null;
744
- subscriptionId: string | null;
745
- status: FFIDSubscriptionStatus | null;
746
- planCode: string | null;
747
- currentPeriodEnd: string | null;
748
- }
826
+
749
827
  /**
750
828
  * Checkout session response from billing checkout endpoint
751
829
  */
@@ -1140,6 +1218,8 @@ declare function createFFIDClient(config: FFIDConfig): {
1140
1218
  userId?: string;
1141
1219
  organizationId: string;
1142
1220
  }) => Promise<FFIDApiResponse<FFIDSubscriptionCheckResponse>>;
1221
+ checkServiceAccess: (params: FFIDCheckServiceAccessParams) => Promise<FFIDApiResponse<FFIDServiceAccessDecision>>;
1222
+ requireServiceAccess: (params: FFIDCheckServiceAccessParams) => Promise<FFIDApiResponse<FFIDServiceAccessDecision>>;
1143
1223
  listMembers: (params: {
1144
1224
  organizationId: string;
1145
1225
  }) => Promise<FFIDApiResponse<FFIDListMembersResponse>>;
@@ -1,3 +1,5 @@
1
+ import { E as EffectiveSubscriptionStatus } from './types-5g_Bg6Ey.js';
2
+
1
3
  /**
2
4
  * Inquiry types exposed by the FFID SDK.
3
5
  *
@@ -183,6 +185,91 @@ interface FFIDCacheConfig {
183
185
  ttl: number;
184
186
  }
185
187
 
188
+ /**
189
+ * Canonical service-access types for subscription lifecycle decisions.
190
+ */
191
+
192
+ /** Subscription status values matching the FFID platform's SubscriptionStatus type */
193
+ type FFIDSubscriptionStatus = 'trialing' | 'active' | 'past_due' | 'canceled' | 'pending_invoice' | 'paused' | 'incomplete' | 'incomplete_expired' | 'unpaid';
194
+ interface FFIDSubscriptionCheckResponse {
195
+ hasActiveSubscription: boolean;
196
+ /**
197
+ * Canonical access decision returned by FFID's `/subscriptions/ext/check`.
198
+ *
199
+ * This is the server-side source of truth for service gates. Consumers
200
+ * should not recompute access from `currentPeriodEnd`, `past_due_since`, or
201
+ * local payment timestamps.
202
+ */
203
+ hasAccess?: boolean;
204
+ /** True when `effectiveStatus === 'past_due_grace'`. */
205
+ isGrace?: boolean;
206
+ /** True when FFID's canonical effective status denies service access. */
207
+ isBlocked?: boolean;
208
+ organizationId: string | null;
209
+ subscriptionId: string | null;
210
+ status: FFIDSubscriptionStatus | null;
211
+ planCode: string | null;
212
+ currentPeriodEnd: string | null;
213
+ /**
214
+ * Semantic FFID access-control status. `null` means the organization has no
215
+ * subscription row for this service.
216
+ */
217
+ effectiveStatus?: EffectiveSubscriptionStatus | null;
218
+ /**
219
+ * ISO timestamp at which `past_due_grace` flips to `blocked`; null outside
220
+ * the grace window.
221
+ */
222
+ gracePeriodEndsAt?: string | null;
223
+ /** Whether a canceled subscription can be resumed via a re-subscription flow. */
224
+ reactivatable?: boolean;
225
+ }
226
+ type FFIDServiceAccessFailPolicy = 'failClosed';
227
+ type FFIDServiceAccessDenialReason = 'no_subscription' | 'grace_disallowed' | 'blocked' | 'canceled' | 'expired' | 'trial_expired' | 'ffid_unreachable';
228
+ interface FFIDCheckServiceAccessParams {
229
+ userId?: string;
230
+ organizationId: string;
231
+ /**
232
+ * Whether `past_due_grace` should keep access open.
233
+ *
234
+ * @default true
235
+ */
236
+ allowGrace?: boolean;
237
+ /**
238
+ * Error policy when FFID cannot return a canonical decision.
239
+ *
240
+ * Currently only `failClosed` is supported: network/server/parse failures
241
+ * become `hasAccess=false` decisions with `denialReason='ffid_unreachable'`
242
+ * and the root cause in `decision.error`. Treat `hasAccess` as the gate.
243
+ */
244
+ failPolicy?: FFIDServiceAccessFailPolicy;
245
+ }
246
+ interface FFIDServiceAccessError {
247
+ code: string;
248
+ message: string;
249
+ details?: unknown;
250
+ }
251
+ interface FFIDServiceAccessDecision {
252
+ hasAccess: boolean;
253
+ effectiveStatus: EffectiveSubscriptionStatus | null;
254
+ isGrace: boolean;
255
+ isBlocked: boolean;
256
+ allowGrace: boolean;
257
+ failPolicy: FFIDServiceAccessFailPolicy;
258
+ denialReason: FFIDServiceAccessDenialReason | null;
259
+ organizationId: string | null;
260
+ subscriptionId: string | null;
261
+ status: FFIDSubscriptionStatus | null;
262
+ planCode: string | null;
263
+ currentPeriodEnd: string | null;
264
+ gracePeriodEndsAt: string | null;
265
+ reactivatable: boolean;
266
+ /**
267
+ * Present when the decision was produced by the SDK fail-closed policy
268
+ * rather than by a successful FFID response.
269
+ */
270
+ error?: FFIDServiceAccessError;
271
+ }
272
+
186
273
  /** Billing interval for subscriptions */
187
274
  type FFIDBillingInterval = 'monthly' | 'yearly';
188
275
  /**
@@ -736,16 +823,7 @@ type FFIDApiResponse<T> = {
736
823
  data?: undefined;
737
824
  error: FFIDError;
738
825
  };
739
- /** Subscription status values matching the FFID platform's SubscriptionStatus type */
740
- type FFIDSubscriptionStatus = 'trialing' | 'active' | 'past_due' | 'canceled' | 'pending_invoice' | 'paused' | 'incomplete' | 'incomplete_expired' | 'unpaid';
741
- interface FFIDSubscriptionCheckResponse {
742
- hasActiveSubscription: boolean;
743
- organizationId: string | null;
744
- subscriptionId: string | null;
745
- status: FFIDSubscriptionStatus | null;
746
- planCode: string | null;
747
- currentPeriodEnd: string | null;
748
- }
826
+
749
827
  /**
750
828
  * Checkout session response from billing checkout endpoint
751
829
  */
@@ -1140,6 +1218,8 @@ declare function createFFIDClient(config: FFIDConfig): {
1140
1218
  userId?: string;
1141
1219
  organizationId: string;
1142
1220
  }) => Promise<FFIDApiResponse<FFIDSubscriptionCheckResponse>>;
1221
+ checkServiceAccess: (params: FFIDCheckServiceAccessParams) => Promise<FFIDApiResponse<FFIDServiceAccessDecision>>;
1222
+ requireServiceAccess: (params: FFIDCheckServiceAccessParams) => Promise<FFIDApiResponse<FFIDServiceAccessDecision>>;
1143
1223
  listMembers: (params: {
1144
1224
  organizationId: string;
1145
1225
  }) => Promise<FFIDApiResponse<FFIDListMembersResponse>>;