@feelflow/ffid-sdk 5.5.0 → 5.7.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.
@@ -842,7 +842,7 @@ function createProfileMethods(deps) {
842
842
  }
843
843
 
844
844
  // src/client/version-check.ts
845
- var SDK_VERSION = "5.5.0";
845
+ var SDK_VERSION = "5.7.0";
846
846
  var SDK_USER_AGENT = `FFID-SDK/${SDK_VERSION} (TypeScript)`;
847
847
  var SDK_VERSION_HEADER = "X-FFID-SDK-Version";
848
848
  function sdkHeaders() {
@@ -2247,7 +2247,7 @@ function isExpired(isoTimestamp, nowMs) {
2247
2247
  return parsed < nowMs;
2248
2248
  }
2249
2249
  function computeEffectiveStatusFromSession(input, nowMs = Date.now()) {
2250
- const { status, currentPeriodEnd, trialEnd, pastDueSince } = input;
2250
+ const { status, currentPeriodEnd, trialEnd, pastDueSince, cancelAtPeriodEnd = false } = input;
2251
2251
  switch (status) {
2252
2252
  case "trialing": {
2253
2253
  const relevantEnd = trialEnd ?? currentPeriodEnd;
@@ -2256,7 +2256,8 @@ function computeEffectiveStatusFromSession(input, nowMs = Date.now()) {
2256
2256
  }
2257
2257
  case "active": {
2258
2258
  const expired = isExpired(currentPeriodEnd, nowMs);
2259
- return expired === true ? "expired" : "active";
2259
+ if (expired === true) return "expired";
2260
+ return cancelAtPeriodEnd ? "active_canceling" : "active";
2260
2261
  }
2261
2262
  case "past_due": {
2262
2263
  const baselineIso = pastDueSince ?? currentPeriodEnd;
@@ -2285,6 +2286,7 @@ function computeEffectiveStatusFromSession(input, nowMs = Date.now()) {
2285
2286
  // src/subscriptions/userinfo-helpers.ts
2286
2287
  var ACCESS_GRANTING_EFFECTIVE_STATUSES = [
2287
2288
  "active",
2289
+ "active_canceling",
2288
2290
  "past_due_grace"
2289
2291
  ];
2290
2292
  var BLOCKING_EFFECTIVE_STATUSES = [
@@ -2338,6 +2340,12 @@ var EXT_CHECK_ENDPOINT = "/api/v1/subscriptions/ext/check";
2338
2340
  var DEFAULT_ALLOW_GRACE = true;
2339
2341
  var DEFAULT_SERVICE_ACCESS_FAIL_POLICY = "failClosed";
2340
2342
  var SERVICE_ACCESS_DENIED_CODE = "SERVICE_ACCESS_DENIED";
2343
+ var DENIAL_REASON_BY_DENYING_STATUS = {
2344
+ blocked: "blocked",
2345
+ canceled: "canceled",
2346
+ expired: "expired",
2347
+ trial_expired: "trial_expired"
2348
+ };
2341
2349
  function resolveServiceAccessDenialReason(params, allowGrace) {
2342
2350
  if (params.hasAccess) {
2343
2351
  return null;
@@ -2348,10 +2356,10 @@ function resolveServiceAccessDenialReason(params, allowGrace) {
2348
2356
  if (params.isGrace && !allowGrace) {
2349
2357
  return "grace_disallowed";
2350
2358
  }
2351
- if (params.effectiveStatus === "active" || params.effectiveStatus === "past_due_grace") {
2359
+ if (ACCESS_GRANTING_EFFECTIVE_STATUSES.includes(params.effectiveStatus)) {
2352
2360
  return "blocked";
2353
2361
  }
2354
- return params.effectiveStatus;
2362
+ return DENIAL_REASON_BY_DENYING_STATUS[params.effectiveStatus];
2355
2363
  }
2356
2364
  function toServiceAccessDecision(response, allowGrace) {
2357
2365
  const effectiveStatus = response.effectiveStatus ?? null;
@@ -3522,6 +3530,7 @@ function FFIDOrganizationSwitcher({
3522
3530
  }
3523
3531
  var ACCESS_GRANTING_STATUSES = [
3524
3532
  "active",
3533
+ "active_canceling",
3525
3534
  "past_due_grace"
3526
3535
  ];
3527
3536
  var BLOCKING_STATUSES = [
@@ -3558,7 +3567,8 @@ function useSubscription() {
3558
3567
  // のため `computeEffectiveStatusFromSession` 内で currentPeriodEnd を
3559
3568
  // fallback に使う (Stripe は period end 近辺で past_due にする)。
3560
3569
  // Canonical な verdict が必要な consumer は `/ext/check` を直接呼ぶ。
3561
- pastDueSince: void 0
3570
+ pastDueSince: void 0,
3571
+ cancelAtPeriodEnd: subscription.cancelAtPeriodEnd
3562
3572
  });
3563
3573
  }, [subscription]);
3564
3574
  const isGrace = effectiveStatus === "past_due_grace";
@@ -840,7 +840,7 @@ function createProfileMethods(deps) {
840
840
  }
841
841
 
842
842
  // src/client/version-check.ts
843
- var SDK_VERSION = "5.5.0";
843
+ var SDK_VERSION = "5.7.0";
844
844
  var SDK_USER_AGENT = `FFID-SDK/${SDK_VERSION} (TypeScript)`;
845
845
  var SDK_VERSION_HEADER = "X-FFID-SDK-Version";
846
846
  function sdkHeaders() {
@@ -2245,7 +2245,7 @@ function isExpired(isoTimestamp, nowMs) {
2245
2245
  return parsed < nowMs;
2246
2246
  }
2247
2247
  function computeEffectiveStatusFromSession(input, nowMs = Date.now()) {
2248
- const { status, currentPeriodEnd, trialEnd, pastDueSince } = input;
2248
+ const { status, currentPeriodEnd, trialEnd, pastDueSince, cancelAtPeriodEnd = false } = input;
2249
2249
  switch (status) {
2250
2250
  case "trialing": {
2251
2251
  const relevantEnd = trialEnd ?? currentPeriodEnd;
@@ -2254,7 +2254,8 @@ function computeEffectiveStatusFromSession(input, nowMs = Date.now()) {
2254
2254
  }
2255
2255
  case "active": {
2256
2256
  const expired = isExpired(currentPeriodEnd, nowMs);
2257
- return expired === true ? "expired" : "active";
2257
+ if (expired === true) return "expired";
2258
+ return cancelAtPeriodEnd ? "active_canceling" : "active";
2258
2259
  }
2259
2260
  case "past_due": {
2260
2261
  const baselineIso = pastDueSince ?? currentPeriodEnd;
@@ -2283,6 +2284,7 @@ function computeEffectiveStatusFromSession(input, nowMs = Date.now()) {
2283
2284
  // src/subscriptions/userinfo-helpers.ts
2284
2285
  var ACCESS_GRANTING_EFFECTIVE_STATUSES = [
2285
2286
  "active",
2287
+ "active_canceling",
2286
2288
  "past_due_grace"
2287
2289
  ];
2288
2290
  var BLOCKING_EFFECTIVE_STATUSES = [
@@ -2336,6 +2338,12 @@ var EXT_CHECK_ENDPOINT = "/api/v1/subscriptions/ext/check";
2336
2338
  var DEFAULT_ALLOW_GRACE = true;
2337
2339
  var DEFAULT_SERVICE_ACCESS_FAIL_POLICY = "failClosed";
2338
2340
  var SERVICE_ACCESS_DENIED_CODE = "SERVICE_ACCESS_DENIED";
2341
+ var DENIAL_REASON_BY_DENYING_STATUS = {
2342
+ blocked: "blocked",
2343
+ canceled: "canceled",
2344
+ expired: "expired",
2345
+ trial_expired: "trial_expired"
2346
+ };
2339
2347
  function resolveServiceAccessDenialReason(params, allowGrace) {
2340
2348
  if (params.hasAccess) {
2341
2349
  return null;
@@ -2346,10 +2354,10 @@ function resolveServiceAccessDenialReason(params, allowGrace) {
2346
2354
  if (params.isGrace && !allowGrace) {
2347
2355
  return "grace_disallowed";
2348
2356
  }
2349
- if (params.effectiveStatus === "active" || params.effectiveStatus === "past_due_grace") {
2357
+ if (ACCESS_GRANTING_EFFECTIVE_STATUSES.includes(params.effectiveStatus)) {
2350
2358
  return "blocked";
2351
2359
  }
2352
- return params.effectiveStatus;
2360
+ return DENIAL_REASON_BY_DENYING_STATUS[params.effectiveStatus];
2353
2361
  }
2354
2362
  function toServiceAccessDecision(response, allowGrace) {
2355
2363
  const effectiveStatus = response.effectiveStatus ?? null;
@@ -3520,6 +3528,7 @@ function FFIDOrganizationSwitcher({
3520
3528
  }
3521
3529
  var ACCESS_GRANTING_STATUSES = [
3522
3530
  "active",
3531
+ "active_canceling",
3523
3532
  "past_due_grace"
3524
3533
  ];
3525
3534
  var BLOCKING_STATUSES = [
@@ -3556,7 +3565,8 @@ function useSubscription() {
3556
3565
  // のため `computeEffectiveStatusFromSession` 内で currentPeriodEnd を
3557
3566
  // fallback に使う (Stripe は period end 近辺で past_due にする)。
3558
3567
  // Canonical な verdict が必要な consumer は `/ext/check` を直接呼ぶ。
3559
- pastDueSince: void 0
3568
+ pastDueSince: void 0,
3569
+ cancelAtPeriodEnd: subscription.cancelAtPeriodEnd
3560
3570
  });
3561
3571
  }, [subscription]);
3562
3572
  const isGrace = effectiveStatus === "past_due_grace";
@@ -1,34 +1,34 @@
1
1
  'use strict';
2
2
 
3
- var chunkJKP3MBTT_cjs = require('../chunk-JKP3MBTT.cjs');
3
+ var chunkFXIDKFXQ_cjs = require('../chunk-FXIDKFXQ.cjs');
4
4
 
5
5
 
6
6
 
7
7
  Object.defineProperty(exports, "FFIDAnnouncementBadge", {
8
8
  enumerable: true,
9
- get: function () { return chunkJKP3MBTT_cjs.FFIDAnnouncementBadge; }
9
+ get: function () { return chunkFXIDKFXQ_cjs.FFIDAnnouncementBadge; }
10
10
  });
11
11
  Object.defineProperty(exports, "FFIDAnnouncementList", {
12
12
  enumerable: true,
13
- get: function () { return chunkJKP3MBTT_cjs.FFIDAnnouncementList; }
13
+ get: function () { return chunkFXIDKFXQ_cjs.FFIDAnnouncementList; }
14
14
  });
15
15
  Object.defineProperty(exports, "FFIDInquiryForm", {
16
16
  enumerable: true,
17
- get: function () { return chunkJKP3MBTT_cjs.FFIDInquiryForm; }
17
+ get: function () { return chunkFXIDKFXQ_cjs.FFIDInquiryForm; }
18
18
  });
19
19
  Object.defineProperty(exports, "FFIDLoginButton", {
20
20
  enumerable: true,
21
- get: function () { return chunkJKP3MBTT_cjs.FFIDLoginButton; }
21
+ get: function () { return chunkFXIDKFXQ_cjs.FFIDLoginButton; }
22
22
  });
23
23
  Object.defineProperty(exports, "FFIDOrganizationSwitcher", {
24
24
  enumerable: true,
25
- get: function () { return chunkJKP3MBTT_cjs.FFIDOrganizationSwitcher; }
25
+ get: function () { return chunkFXIDKFXQ_cjs.FFIDOrganizationSwitcher; }
26
26
  });
27
27
  Object.defineProperty(exports, "FFIDSubscriptionBadge", {
28
28
  enumerable: true,
29
- get: function () { return chunkJKP3MBTT_cjs.FFIDSubscriptionBadge; }
29
+ get: function () { return chunkFXIDKFXQ_cjs.FFIDSubscriptionBadge; }
30
30
  });
31
31
  Object.defineProperty(exports, "FFIDUserMenu", {
32
32
  enumerable: true,
33
- get: function () { return chunkJKP3MBTT_cjs.FFIDUserMenu; }
33
+ get: function () { return chunkFXIDKFXQ_cjs.FFIDUserMenu; }
34
34
  });
@@ -1,3 +1,3 @@
1
- export { T as FFIDAnnouncementBadge, av as FFIDAnnouncementBadgeClassNames, aw as FFIDAnnouncementBadgeProps, U as FFIDAnnouncementList, ax as FFIDAnnouncementListClassNames, ay as FFIDAnnouncementListProps, a1 as FFIDInquiryForm, a2 as FFIDInquiryFormCategoryItem, a3 as FFIDInquiryFormClassNames, a4 as FFIDInquiryFormLegalLayout, a5 as FFIDInquiryFormOrganization, a6 as FFIDInquiryFormPlaceholderContext, a7 as FFIDInquiryFormPrefill, a8 as FFIDInquiryFormProps, a9 as FFIDInquiryFormSubmitData, aa as FFIDInquiryFormSubmitResult, ac as FFIDLoginButton, az as FFIDLoginButtonProps, ah as FFIDOrganizationSwitcher, aA as FFIDOrganizationSwitcherClassNames, aB as FFIDOrganizationSwitcherProps, am as FFIDSubscriptionBadge, aC as FFIDSubscriptionBadgeClassNames, aD as FFIDSubscriptionBadgeProps, ao as FFIDUserMenu, aE as FFIDUserMenuClassNames, aF as FFIDUserMenuProps } from '../index-lAvUS_oz.cjs';
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 FFIDInquiryFormLegalLayout, Z as FFIDInquiryFormOrganization, _ as FFIDInquiryFormPlaceholderContext, $ as FFIDInquiryFormPrefill, a0 as FFIDInquiryFormProps, a1 as FFIDInquiryFormSubmitData, a2 as FFIDInquiryFormSubmitResult, a4 as FFIDLoginButton, ap as FFIDLoginButtonProps, a7 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-EFp_ZgK9.cjs';
2
2
  import 'react/jsx-runtime';
3
3
  import 'react';
@@ -1,3 +1,3 @@
1
- export { T as FFIDAnnouncementBadge, av as FFIDAnnouncementBadgeClassNames, aw as FFIDAnnouncementBadgeProps, U as FFIDAnnouncementList, ax as FFIDAnnouncementListClassNames, ay as FFIDAnnouncementListProps, a1 as FFIDInquiryForm, a2 as FFIDInquiryFormCategoryItem, a3 as FFIDInquiryFormClassNames, a4 as FFIDInquiryFormLegalLayout, a5 as FFIDInquiryFormOrganization, a6 as FFIDInquiryFormPlaceholderContext, a7 as FFIDInquiryFormPrefill, a8 as FFIDInquiryFormProps, a9 as FFIDInquiryFormSubmitData, aa as FFIDInquiryFormSubmitResult, ac as FFIDLoginButton, az as FFIDLoginButtonProps, ah as FFIDOrganizationSwitcher, aA as FFIDOrganizationSwitcherClassNames, aB as FFIDOrganizationSwitcherProps, am as FFIDSubscriptionBadge, aC as FFIDSubscriptionBadgeClassNames, aD as FFIDSubscriptionBadgeProps, ao as FFIDUserMenu, aE as FFIDUserMenuClassNames, aF as FFIDUserMenuProps } from '../index-lAvUS_oz.js';
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 FFIDInquiryFormLegalLayout, Z as FFIDInquiryFormOrganization, _ as FFIDInquiryFormPlaceholderContext, $ as FFIDInquiryFormPrefill, a0 as FFIDInquiryFormProps, a1 as FFIDInquiryFormSubmitData, a2 as FFIDInquiryFormSubmitResult, a4 as FFIDLoginButton, ap as FFIDLoginButtonProps, a7 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-EFp_ZgK9.js';
2
2
  import 'react/jsx-runtime';
3
3
  import 'react';
@@ -1 +1 @@
1
- export { FFIDAnnouncementBadge, FFIDAnnouncementList, FFIDInquiryForm, FFIDLoginButton, FFIDOrganizationSwitcher, FFIDSubscriptionBadge, FFIDUserMenu } from '../chunk-WNYMSUHN.js';
1
+ export { FFIDAnnouncementBadge, FFIDAnnouncementList, FFIDInquiryForm, FFIDLoginButton, FFIDOrganizationSwitcher, FFIDSubscriptionBadge, FFIDUserMenu } from '../chunk-YAJN36SD.js';
@@ -1,4 +1,4 @@
1
- import { E as EffectiveSubscriptionStatus } from './types-5g_Bg6Ey.cjs';
1
+ import { E as EffectiveSubscriptionStatus } from './types-s3y11uT6.js';
2
2
 
3
3
  /**
4
4
  * Inquiry types exposed by the FFID SDK.
@@ -576,6 +576,67 @@ interface FFIDSeatChangePreviewResponse {
576
576
  preview: FFIDSeatChangePreview;
577
577
  }
578
578
 
579
+ /** Member role in an organization */
580
+ type FFIDMemberRole = 'owner' | 'admin' | 'member' | 'viewer';
581
+ /** Member role assignable through organization member management APIs */
582
+ type FFIDAssignableMemberRole = Exclude<FFIDMemberRole, 'owner'>;
583
+ /** Member status in an organization */
584
+ type FFIDMemberStatus = 'active' | 'invited' | 'suspended';
585
+ /** Organization member returned by the ext members API */
586
+ interface FFIDOrganizationMember {
587
+ /** User ID (UUID) */
588
+ userId: string;
589
+ /** Email address */
590
+ email: string;
591
+ /** Display name */
592
+ displayName: string | null;
593
+ /** Avatar URL */
594
+ avatarUrl: string | null;
595
+ /** Role in the organization */
596
+ role: FFIDMemberRole;
597
+ /** Membership status */
598
+ status: FFIDMemberStatus;
599
+ /**
600
+ * Whether the member's email address is confirmed.
601
+ *
602
+ * `null` means FFID could not determine the auth user confirmation state
603
+ * (for example, the auth user row was not readable).
604
+ */
605
+ emailVerified: boolean | null;
606
+ /** When the user joined */
607
+ joinedAt: string | null;
608
+ /** When the user was invited */
609
+ invitedAt: string | null;
610
+ /** Last sign-in timestamp */
611
+ lastSignInAt: string | null;
612
+ }
613
+ /** Response from listMembers (ext) */
614
+ interface FFIDListMembersResponse {
615
+ organizationId: string;
616
+ members: FFIDOrganizationMember[];
617
+ }
618
+ /** Request body for addMember (ext) */
619
+ interface FFIDAddMemberRequest {
620
+ email: string;
621
+ role?: FFIDAssignableMemberRole;
622
+ }
623
+ /** Params object for addMember (ext), matching the existing organization members methods */
624
+ interface FFIDAddMemberParams extends FFIDAddMemberRequest {
625
+ organizationId: string;
626
+ }
627
+ /** Response from addMember (ext) */
628
+ interface FFIDAddMemberResponse {
629
+ member: FFIDOrganizationMember;
630
+ }
631
+ /** Response from updateMemberRole (ext) */
632
+ interface FFIDUpdateMemberRoleResponse {
633
+ member: FFIDOrganizationMember;
634
+ }
635
+ /** Response from removeMember (ext) */
636
+ interface FFIDRemoveMemberResponse {
637
+ message: string;
638
+ }
639
+
579
640
  /**
580
641
  * FFID SDK Type Definitions
581
642
  *
@@ -663,6 +724,16 @@ interface FFIDSubscription {
663
724
  currentPeriodEnd: string | null;
664
725
  /** Trial end date (null if not a trial or no trial_end set) */
665
726
  trialEnd: string | null;
727
+ /**
728
+ * Whether the subscription is scheduled to cancel at the end of the current
729
+ * billing period (Stripe `cancel_at_period_end`). Optional for backward
730
+ * compatibility with FFID backends that pre-date #3460 — when absent,
731
+ * `useSubscription` treats the value as `false` and surfaces plain `active`.
732
+ * When `true` and the period has not yet ended, `effectiveStatus` becomes
733
+ * `active_canceling` so consumers can render a cancellation-scheduled badge
734
+ * + reactivation CTA without losing access.
735
+ */
736
+ cancelAtPeriodEnd?: boolean;
666
737
  /** Service seat model (available when sourced from OAuth userinfo) */
667
738
  seatModel?: FFIDSeatModel | undefined;
668
739
  /** Member role in the resolved organization context */
@@ -900,59 +971,6 @@ interface FFIDCreatePortalParams {
900
971
  returnUrl: string;
901
972
  }
902
973
 
903
- /** Member role in an organization */
904
- type FFIDMemberRole = 'owner' | 'admin' | 'member' | 'viewer';
905
- /** Member role assignable through organization member management APIs */
906
- type FFIDAssignableMemberRole = Exclude<FFIDMemberRole, 'owner'>;
907
- /** Member status in an organization */
908
- type FFIDMemberStatus = 'active' | 'invited' | 'suspended';
909
- /** Organization member returned by the ext members API */
910
- interface FFIDOrganizationMember {
911
- /** User ID (UUID) */
912
- userId: string;
913
- /** Email address */
914
- email: string;
915
- /** Display name */
916
- displayName: string | null;
917
- /** Avatar URL */
918
- avatarUrl: string | null;
919
- /** Role in the organization */
920
- role: FFIDMemberRole;
921
- /** Membership status */
922
- status: FFIDMemberStatus;
923
- /** When the user joined */
924
- joinedAt: string | null;
925
- /** When the user was invited */
926
- invitedAt: string | null;
927
- /** Last sign-in timestamp */
928
- lastSignInAt: string | null;
929
- }
930
- /** Response from listMembers (ext) */
931
- interface FFIDListMembersResponse {
932
- organizationId: string;
933
- members: FFIDOrganizationMember[];
934
- }
935
- /** Request body for addMember (ext) */
936
- interface FFIDAddMemberRequest {
937
- email: string;
938
- role?: FFIDAssignableMemberRole;
939
- }
940
- /** Params object for addMember (ext), matching the existing organization members methods */
941
- interface FFIDAddMemberParams extends FFIDAddMemberRequest {
942
- organizationId: string;
943
- }
944
- /** Response from addMember (ext) */
945
- interface FFIDAddMemberResponse {
946
- member: FFIDOrganizationMember;
947
- }
948
- /** Response from updateMemberRole (ext) */
949
- interface FFIDUpdateMemberRoleResponse {
950
- member: FFIDOrganizationMember;
951
- }
952
- /** Response from removeMember (ext) */
953
- interface FFIDRemoveMemberResponse {
954
- message: string;
955
- }
956
974
  /**
957
975
  * User profile for the authenticated user (returned by `getProfile` / `updateProfile`).
958
976
  *
@@ -1,4 +1,4 @@
1
- import { E as EffectiveSubscriptionStatus } from './types-5g_Bg6Ey.js';
1
+ import { E as EffectiveSubscriptionStatus } from './types-s3y11uT6.cjs';
2
2
 
3
3
  /**
4
4
  * Inquiry types exposed by the FFID SDK.
@@ -576,6 +576,67 @@ interface FFIDSeatChangePreviewResponse {
576
576
  preview: FFIDSeatChangePreview;
577
577
  }
578
578
 
579
+ /** Member role in an organization */
580
+ type FFIDMemberRole = 'owner' | 'admin' | 'member' | 'viewer';
581
+ /** Member role assignable through organization member management APIs */
582
+ type FFIDAssignableMemberRole = Exclude<FFIDMemberRole, 'owner'>;
583
+ /** Member status in an organization */
584
+ type FFIDMemberStatus = 'active' | 'invited' | 'suspended';
585
+ /** Organization member returned by the ext members API */
586
+ interface FFIDOrganizationMember {
587
+ /** User ID (UUID) */
588
+ userId: string;
589
+ /** Email address */
590
+ email: string;
591
+ /** Display name */
592
+ displayName: string | null;
593
+ /** Avatar URL */
594
+ avatarUrl: string | null;
595
+ /** Role in the organization */
596
+ role: FFIDMemberRole;
597
+ /** Membership status */
598
+ status: FFIDMemberStatus;
599
+ /**
600
+ * Whether the member's email address is confirmed.
601
+ *
602
+ * `null` means FFID could not determine the auth user confirmation state
603
+ * (for example, the auth user row was not readable).
604
+ */
605
+ emailVerified: boolean | null;
606
+ /** When the user joined */
607
+ joinedAt: string | null;
608
+ /** When the user was invited */
609
+ invitedAt: string | null;
610
+ /** Last sign-in timestamp */
611
+ lastSignInAt: string | null;
612
+ }
613
+ /** Response from listMembers (ext) */
614
+ interface FFIDListMembersResponse {
615
+ organizationId: string;
616
+ members: FFIDOrganizationMember[];
617
+ }
618
+ /** Request body for addMember (ext) */
619
+ interface FFIDAddMemberRequest {
620
+ email: string;
621
+ role?: FFIDAssignableMemberRole;
622
+ }
623
+ /** Params object for addMember (ext), matching the existing organization members methods */
624
+ interface FFIDAddMemberParams extends FFIDAddMemberRequest {
625
+ organizationId: string;
626
+ }
627
+ /** Response from addMember (ext) */
628
+ interface FFIDAddMemberResponse {
629
+ member: FFIDOrganizationMember;
630
+ }
631
+ /** Response from updateMemberRole (ext) */
632
+ interface FFIDUpdateMemberRoleResponse {
633
+ member: FFIDOrganizationMember;
634
+ }
635
+ /** Response from removeMember (ext) */
636
+ interface FFIDRemoveMemberResponse {
637
+ message: string;
638
+ }
639
+
579
640
  /**
580
641
  * FFID SDK Type Definitions
581
642
  *
@@ -663,6 +724,16 @@ interface FFIDSubscription {
663
724
  currentPeriodEnd: string | null;
664
725
  /** Trial end date (null if not a trial or no trial_end set) */
665
726
  trialEnd: string | null;
727
+ /**
728
+ * Whether the subscription is scheduled to cancel at the end of the current
729
+ * billing period (Stripe `cancel_at_period_end`). Optional for backward
730
+ * compatibility with FFID backends that pre-date #3460 — when absent,
731
+ * `useSubscription` treats the value as `false` and surfaces plain `active`.
732
+ * When `true` and the period has not yet ended, `effectiveStatus` becomes
733
+ * `active_canceling` so consumers can render a cancellation-scheduled badge
734
+ * + reactivation CTA without losing access.
735
+ */
736
+ cancelAtPeriodEnd?: boolean;
666
737
  /** Service seat model (available when sourced from OAuth userinfo) */
667
738
  seatModel?: FFIDSeatModel | undefined;
668
739
  /** Member role in the resolved organization context */
@@ -900,59 +971,6 @@ interface FFIDCreatePortalParams {
900
971
  returnUrl: string;
901
972
  }
902
973
 
903
- /** Member role in an organization */
904
- type FFIDMemberRole = 'owner' | 'admin' | 'member' | 'viewer';
905
- /** Member role assignable through organization member management APIs */
906
- type FFIDAssignableMemberRole = Exclude<FFIDMemberRole, 'owner'>;
907
- /** Member status in an organization */
908
- type FFIDMemberStatus = 'active' | 'invited' | 'suspended';
909
- /** Organization member returned by the ext members API */
910
- interface FFIDOrganizationMember {
911
- /** User ID (UUID) */
912
- userId: string;
913
- /** Email address */
914
- email: string;
915
- /** Display name */
916
- displayName: string | null;
917
- /** Avatar URL */
918
- avatarUrl: string | null;
919
- /** Role in the organization */
920
- role: FFIDMemberRole;
921
- /** Membership status */
922
- status: FFIDMemberStatus;
923
- /** When the user joined */
924
- joinedAt: string | null;
925
- /** When the user was invited */
926
- invitedAt: string | null;
927
- /** Last sign-in timestamp */
928
- lastSignInAt: string | null;
929
- }
930
- /** Response from listMembers (ext) */
931
- interface FFIDListMembersResponse {
932
- organizationId: string;
933
- members: FFIDOrganizationMember[];
934
- }
935
- /** Request body for addMember (ext) */
936
- interface FFIDAddMemberRequest {
937
- email: string;
938
- role?: FFIDAssignableMemberRole;
939
- }
940
- /** Params object for addMember (ext), matching the existing organization members methods */
941
- interface FFIDAddMemberParams extends FFIDAddMemberRequest {
942
- organizationId: string;
943
- }
944
- /** Response from addMember (ext) */
945
- interface FFIDAddMemberResponse {
946
- member: FFIDOrganizationMember;
947
- }
948
- /** Response from updateMemberRole (ext) */
949
- interface FFIDUpdateMemberRoleResponse {
950
- member: FFIDOrganizationMember;
951
- }
952
- /** Response from removeMember (ext) */
953
- interface FFIDRemoveMemberResponse {
954
- message: string;
955
- }
956
974
  /**
957
975
  * User profile for the authenticated user (returned by `getProfile` / `updateProfile`).
958
976
  *
@@ -41,6 +41,10 @@ interface FFIDCacheConfig {
41
41
  * branch on a single value:
42
42
  *
43
43
  * - `active` — normal paying subscription; grant full access.
44
+ * - `active_canceling` — paying subscription with `cancel_at_period_end=true`.
45
+ * Access stays live until `currentPeriodEnd`; callers SHOULD surface a
46
+ * "cancellation scheduled" badge and a reactivation CTA. Session-only
47
+ * refinement of `active` — see the asymmetry note below.
44
48
  * - `past_due_grace` — payment failed but FFID is still retrying. Service
45
49
  * SHOULD keep access and surface a recovery banner.
46
50
  * - `blocked` — payment dunning exhausted (`past_due` over grace,
@@ -53,10 +57,27 @@ interface FFIDCacheConfig {
53
57
  * This is the FFID-internal runtime-expired case (see
54
58
  * `getEffectiveSubscriptionStatus` on the server). Deny access.
55
59
  *
60
+ * **SDK / server asymmetry — `active_canceling` is session-only (#3460).**
61
+ * The backend `EffectiveSubscriptionStatus` in
62
+ * `src/lib/common/subscription-helpers.ts` does NOT currently include
63
+ * `active_canceling`; cancellation-scheduled subscriptions surface as plain
64
+ * `'active'` on `/api/v1/subscriptions/ext/check` and `/oauth/userinfo`, and
65
+ * consumers that branch on the server verdict will not see the new literal.
66
+ * The SDK enriches `computeEffectiveStatusFromSession` with
67
+ * `cancelAtPeriodEnd` (carried on `FFIDSubscription` for FFID backends from
68
+ * #3460 onward — older backends omit the field and the helper defaults to
69
+ * `false`, preserving the pre-#3460 `'active'` verdict) so `useSubscription`
70
+ * can surface a "cancellation scheduled" badge without waiting for an
71
+ * `/ext/check` round-trip. The asymmetry is safe because the new literal is
72
+ * access-granting (no security regression on the userinfo path); when the
73
+ * server's `EffectiveSubscriptionStatus` is later extended to emit
74
+ * `active_canceling`, this docstring MUST be updated to drop the
75
+ * session-only note.
76
+ *
56
77
  * @see /api/v1/subscriptions/ext/check
57
78
  * @see docs/03-implementation/EXPIRED_CONTRACT_HANDLING.md
58
79
  */
59
- type EffectiveSubscriptionStatus = 'active' | 'past_due_grace' | 'blocked' | 'canceled' | 'trial_expired' | 'expired';
80
+ type EffectiveSubscriptionStatus = 'active' | 'active_canceling' | 'past_due_grace' | 'blocked' | 'canceled' | 'trial_expired' | 'expired';
60
81
 
61
82
  /**
62
83
  * Canonical service-access types for subscription lifecycle decisions.
@@ -230,6 +251,16 @@ interface FFIDSubscription {
230
251
  currentPeriodEnd: string | null;
231
252
  /** Trial end date (null if not a trial or no trial_end set) */
232
253
  trialEnd: string | null;
254
+ /**
255
+ * Whether the subscription is scheduled to cancel at the end of the current
256
+ * billing period (Stripe `cancel_at_period_end`). Optional for backward
257
+ * compatibility with FFID backends that pre-date #3460 — when absent,
258
+ * `useSubscription` treats the value as `false` and surfaces plain `active`.
259
+ * When `true` and the period has not yet ended, `effectiveStatus` becomes
260
+ * `active_canceling` so consumers can render a cancellation-scheduled badge
261
+ * + reactivation CTA without losing access.
262
+ */
263
+ cancelAtPeriodEnd?: boolean;
233
264
  /** Service seat model (available when sourced from OAuth userinfo) */
234
265
  seatModel?: FFIDSeatModel | undefined;
235
266
  /** Member role in the resolved organization context */
@@ -606,59 +637,6 @@ interface FFIDCreatePortalParams {
606
637
  returnUrl: string;
607
638
  }
608
639
 
609
- /** Member role in an organization */
610
- type FFIDMemberRole = 'owner' | 'admin' | 'member' | 'viewer';
611
- /** Member role assignable through organization member management APIs */
612
- type FFIDAssignableMemberRole = Exclude<FFIDMemberRole, 'owner'>;
613
- /** Member status in an organization */
614
- type FFIDMemberStatus = 'active' | 'invited' | 'suspended';
615
- /** Organization member returned by the ext members API */
616
- interface FFIDOrganizationMember {
617
- /** User ID (UUID) */
618
- userId: string;
619
- /** Email address */
620
- email: string;
621
- /** Display name */
622
- displayName: string | null;
623
- /** Avatar URL */
624
- avatarUrl: string | null;
625
- /** Role in the organization */
626
- role: FFIDMemberRole;
627
- /** Membership status */
628
- status: FFIDMemberStatus;
629
- /** When the user joined */
630
- joinedAt: string | null;
631
- /** When the user was invited */
632
- invitedAt: string | null;
633
- /** Last sign-in timestamp */
634
- lastSignInAt: string | null;
635
- }
636
- /** Response from listMembers (ext) */
637
- interface FFIDListMembersResponse {
638
- organizationId: string;
639
- members: FFIDOrganizationMember[];
640
- }
641
- /** Request body for addMember (ext) */
642
- interface FFIDAddMemberRequest {
643
- email: string;
644
- role?: FFIDAssignableMemberRole;
645
- }
646
- /** Params object for addMember (ext), matching the existing organization members methods */
647
- interface FFIDAddMemberParams extends FFIDAddMemberRequest {
648
- organizationId: string;
649
- }
650
- /** Response from addMember (ext) */
651
- interface FFIDAddMemberResponse {
652
- member: FFIDOrganizationMember;
653
- }
654
- /** Response from updateMemberRole (ext) */
655
- interface FFIDUpdateMemberRoleResponse {
656
- member: FFIDOrganizationMember;
657
- }
658
- /** Response from removeMember (ext) */
659
- interface FFIDRemoveMemberResponse {
660
- message: string;
661
- }
662
640
  /**
663
641
  * User profile for the authenticated user (returned by `getProfile` / `updateProfile`).
664
642
  *
@@ -1570,4 +1548,4 @@ interface FFIDInquiryFormPlaceholderContext {
1570
1548
  }
1571
1549
  declare function FFIDInquiryForm({ mode, prefill, organizations, preselectedOrganizationId, categories, termsVersion, privacyVersion, termsHref, privacyHref, turnstileToken, turnstileSlot, onSubmit, onChange, legalLayout, messagePlaceholder, requireCategorySelection, unstyled, classNames, locale, className, }: FFIDInquiryFormProps): react_jsx_runtime.JSX.Element;
1572
1550
 
1573
- export { type FFIDInquiryCategory as $, type FFIDInquiryCreateResponse as A, type FFIDAuthMode as B, type FFIDLogger as C, type FFIDCacheAdapter as D, type FFIDUser as E, type FFIDSubscriptionStatus as F, type FFIDOrganization as G, type FFIDSubscription as H, type FFIDSubscriptionContextValue as I, type EffectiveSubscriptionStatus as J, type FFIDOAuthUserInfoSubscription as K, type FFIDAnnouncementsClientConfig as L, type ListAnnouncementsOptions as M, type FFIDAnnouncementsApiResponse as N, type AnnouncementListResponse as O, type FFIDAnnouncementsLogger as P, type Announcement as Q, type AnnouncementStatus as R, type AnnouncementType as S, FFIDAnnouncementBadge as T, FFIDAnnouncementList as U, type FFIDAnnouncementsError as V, type FFIDAnnouncementsErrorCode as W, type FFIDAnnouncementsServerResponse as X, type FFIDAssignableMemberRole as Y, type FFIDCacheConfig as Z, type FFIDContextValue as _, type FFIDConfig as a, type FFIDInquiryCategorySite2026 as a0, FFIDInquiryForm as a1, type FFIDInquiryFormCategoryItem as a2, type FFIDInquiryFormClassNames as a3, type FFIDInquiryFormLegalLayout as a4, type FFIDInquiryFormOrganization as a5, type FFIDInquiryFormPlaceholderContext as a6, type FFIDInquiryFormPrefill as a7, type FFIDInquiryFormProps as a8, type FFIDInquiryFormSubmitData as a9, type FFIDOrganizationSwitcherClassNames as aA, type FFIDOrganizationSwitcherProps as aB, type FFIDSubscriptionBadgeClassNames as aC, type FFIDSubscriptionBadgeProps as aD, type FFIDUserMenuClassNames as aE, type FFIDUserMenuProps as aF, type FFIDInquiryFormSubmitResult as aa, type FFIDJwtClaims as ab, FFIDLoginButton as ac, type FFIDMemberStatus as ad, type FFIDOAuthTokenResponse as ae, type FFIDOAuthUserInfoMemberRole as af, type FFIDOrganizationMember as ag, FFIDOrganizationSwitcher as ah, type FFIDRedirectErrorCode as ai, type FFIDSeatModel as aj, type FFIDServiceAccessDenialReason as ak, type FFIDServiceAccessFailPolicy as al, FFIDSubscriptionBadge as am, type FFIDTokenIntrospectionResponse as an, FFIDUserMenu as ao, FFID_INQUIRY_CATEGORIES as ap, FFID_INQUIRY_CATEGORIES_SITE_2026 as aq, type UseFFIDAnnouncementsOptions as ar, type UseFFIDAnnouncementsReturn as as, isFFIDInquiryCategorySite2026 as at, useFFIDAnnouncements as au, type FFIDAnnouncementBadgeClassNames as av, type FFIDAnnouncementBadgeProps as aw, type FFIDAnnouncementListClassNames as ax, type FFIDAnnouncementListProps as ay, type FFIDLoginButtonProps as az, type FFIDApiResponse as b, type FFIDSessionResponse as c, type FFIDRedirectResult as d, type FFIDError as e, type FFIDSubscriptionCheckResponse as f, type FFIDCheckServiceAccessParams as g, type FFIDServiceAccessDecision as h, type FFIDListMembersResponse as i, type FFIDAddMemberParams as j, type FFIDAddMemberResponse as k, type FFIDAddMemberRequest as l, type FFIDMemberRole as m, type FFIDUpdateMemberRoleResponse as n, type FFIDRemoveMemberResponse as o, type FFIDProfileCallOptions as p, type FFIDUserProfile as q, type FFIDUpdateUserProfileRequest as r, type FFIDAnalyticsConfig as s, type FFIDCreateCheckoutParams as t, type FFIDCheckoutSessionResponse as u, type FFIDCreatePortalParams as v, type FFIDPortalSessionResponse as w, type FFIDVerifyAccessTokenOptions as x, type FFIDOAuthUserInfo as y, type FFIDInquiryCreateParams as z };
1551
+ export { type FFIDInquiryFormPrefill as $, type FFIDSubscriptionContextValue as A, type FFIDOAuthUserInfoSubscription 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 FFIDInquiryFormLegalLayout as Y, type FFIDInquiryFormOrganization as Z, type FFIDInquiryFormPlaceholderContext as _, type FFIDConfig as a, type FFIDInquiryFormProps as a0, type FFIDInquiryFormSubmitData as a1, type FFIDInquiryFormSubmitResult as a2, type FFIDJwtClaims as a3, FFIDLoginButton as a4, type FFIDOAuthTokenResponse as a5, type FFIDOAuthUserInfoMemberRole as a6, FFIDOrganizationSwitcher as a7, type FFIDRedirectErrorCode as a8, type FFIDSeatModel as a9, type FFIDServiceAccessDenialReason as aa, type FFIDServiceAccessFailPolicy 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 FFIDCheckServiceAccessParams as g, type FFIDServiceAccessDecision as h, type FFIDProfileCallOptions as i, type FFIDUserProfile as j, type FFIDUpdateUserProfileRequest as k, type FFIDAnalyticsConfig as l, type FFIDCreateCheckoutParams as m, type FFIDCheckoutSessionResponse as n, type FFIDCreatePortalParams as o, type FFIDPortalSessionResponse as p, type FFIDVerifyAccessTokenOptions as q, type FFIDOAuthUserInfo as r, type FFIDInquiryCreateParams as s, type FFIDInquiryCreateResponse as t, type FFIDAuthMode as u, type FFIDLogger as v, type FFIDCacheAdapter as w, type FFIDUser as x, type FFIDOrganization as y, type FFIDSubscription as z };