@feelflow/ffid-sdk 7.2.0 → 8.0.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.
@@ -158,7 +158,7 @@ function normalizeUserinfo(raw) {
158
158
  emailVerified: raw.email_verified,
159
159
  name: raw.name,
160
160
  picture: raw.picture,
161
- companyName: raw.company_name ?? null,
161
+ companyName: null,
162
162
  department: raw.department ?? null,
163
163
  position: raw.position ?? null,
164
164
  phoneNumber: raw.phone_number ?? null,
@@ -467,7 +467,7 @@ function createVerifyAccessToken(deps) {
467
467
  email_verified: introspectResponse.email_verified,
468
468
  name: introspectResponse.name ?? null,
469
469
  picture: introspectResponse.picture ?? null,
470
- company_name: introspectResponse.company_name ?? null,
470
+ company_name: null,
471
471
  department: introspectResponse.department ?? null,
472
472
  position: introspectResponse.position ?? null,
473
473
  phone_number: introspectResponse.phone_number ?? null,
@@ -718,6 +718,24 @@ function validateSeatChangePreview(preview) {
718
718
  `SDK: server returned malformed SeatChangePreview \u2014 estimateReason must be one of 'no_stripe_data' | 'custom_pricing' | 'stripe_error' (got ${JSON.stringify(p.estimateReason)})`
719
719
  );
720
720
  }
721
+ if (p.willApplyAtPeriodEnd !== void 0 && typeof p.willApplyAtPeriodEnd !== "boolean") {
722
+ throw new FFIDSDKError(
723
+ MALFORMED_SEAT_PREVIEW_CODE,
724
+ `SDK: server returned malformed SeatChangePreview \u2014 willApplyAtPeriodEnd must be boolean when present (got ${typeof p.willApplyAtPeriodEnd})`
725
+ );
726
+ }
727
+ if (p.effectiveDate !== void 0 && p.effectiveDate !== null && typeof p.effectiveDate !== "string") {
728
+ throw new FFIDSDKError(
729
+ MALFORMED_SEAT_PREVIEW_CODE,
730
+ `SDK: server returned malformed SeatChangePreview \u2014 effectiveDate must be string or null when present (got ${typeof p.effectiveDate})`
731
+ );
732
+ }
733
+ if (p.willApplyAtPeriodEnd === true && p.proratedAmount !== 0) {
734
+ throw new FFIDSDKError(
735
+ MALFORMED_SEAT_PREVIEW_CODE,
736
+ `SDK: server returned malformed SeatChangePreview \u2014 willApplyAtPeriodEnd=true requires proratedAmount=0 (got ${JSON.stringify(p.proratedAmount)})`
737
+ );
738
+ }
721
739
  if (!Array.isArray(p.lineItems)) {
722
740
  throw new FFIDSDKError(
723
741
  MALFORMED_SEAT_PREVIEW_CODE,
@@ -1358,7 +1376,7 @@ var FFID_ORGANIZATION_PROFILE_ERROR_CODES = {
1358
1376
  };
1359
1377
 
1360
1378
  // src/client/version-check.ts
1361
- var SDK_VERSION = "7.2.0";
1379
+ var SDK_VERSION = "8.0.0";
1362
1380
  var SDK_USER_AGENT = `FFID-SDK/${SDK_VERSION} (TypeScript)`;
1363
1381
  var SDK_VERSION_HEADER = "X-FFID-SDK-Version";
1364
1382
  function sdkHeaders() {
@@ -2612,6 +2630,129 @@ function cleanupStateStorage(logger) {
2612
2630
  }
2613
2631
  }
2614
2632
 
2633
+ // src/client/agency-invite-token.ts
2634
+ var AGENCY_INVITE_TOKEN_PARAM = "invite";
2635
+ var AGENCY_INVITE_COOKIE_NAME = "ffid_invite";
2636
+ var SECONDS_PER_MINUTE = 60;
2637
+ var MINUTES_PER_HOUR = 60;
2638
+ var HOURS_PER_DAY = 24;
2639
+ var RETENTION_DAYS = 30;
2640
+ var AGENCY_INVITE_COOKIE_MAX_AGE_SEC = SECONDS_PER_MINUTE * MINUTES_PER_HOUR * HOURS_PER_DAY * RETENTION_DAYS;
2641
+ var AGENCY_INVITE_TOKEN_REDACTION = "[REDACTED]";
2642
+ function hasDocument() {
2643
+ return typeof document !== "undefined";
2644
+ }
2645
+ function defaultSecure() {
2646
+ if (typeof process === "undefined") return true;
2647
+ return process.env?.NODE_ENV === "production";
2648
+ }
2649
+ function toQueryString(source) {
2650
+ const queryStart = source.indexOf("?");
2651
+ if (queryStart < 0) return "";
2652
+ const afterQuery = source.slice(queryStart);
2653
+ const fragmentStart = afterQuery.indexOf("#");
2654
+ return fragmentStart < 0 ? afterQuery : afterQuery.slice(0, fragmentStart);
2655
+ }
2656
+ function readTokenFromUrl(options) {
2657
+ const source = options.url ?? (typeof window !== "undefined" ? window.location.search : void 0);
2658
+ if (source === void 0) return void 0;
2659
+ const params = new URLSearchParams(toQueryString(source));
2660
+ return params.get(AGENCY_INVITE_TOKEN_PARAM)?.trim() || void 0;
2661
+ }
2662
+ function readAgencyInviteCookie() {
2663
+ if (!hasDocument()) return void 0;
2664
+ const prefix = `${AGENCY_INVITE_COOKIE_NAME}=`;
2665
+ const raw = document.cookie.split(";").map((entry) => entry.trim()).find((entry) => entry.startsWith(prefix));
2666
+ if (!raw) return void 0;
2667
+ try {
2668
+ return decodeURIComponent(raw.slice(prefix.length)).trim() || void 0;
2669
+ } catch {
2670
+ return void 0;
2671
+ }
2672
+ }
2673
+ function readAgencyInviteToken(options = {}) {
2674
+ return readTokenFromUrl(options) ?? readAgencyInviteCookie();
2675
+ }
2676
+ function buildAgencyInviteCookie(value, maxAgeSec, options) {
2677
+ const attrs = [
2678
+ `${AGENCY_INVITE_COOKIE_NAME}=${value}`,
2679
+ `Max-Age=${maxAgeSec}`,
2680
+ `Path=${options.path ?? "/"}`,
2681
+ "SameSite=Lax"
2682
+ ];
2683
+ if (options.domain) attrs.push(`Domain=${options.domain}`);
2684
+ if (options.secure ?? defaultSecure()) attrs.push("Secure");
2685
+ return attrs.join("; ");
2686
+ }
2687
+ function writeAgencyInviteCookie(token, options) {
2688
+ if (!hasDocument()) return void 0;
2689
+ document.cookie = buildAgencyInviteCookie(
2690
+ encodeURIComponent(token),
2691
+ AGENCY_INVITE_COOKIE_MAX_AGE_SEC,
2692
+ options
2693
+ );
2694
+ return readAgencyInviteCookie() === token;
2695
+ }
2696
+ function clearAgencyInviteToken(options = {}) {
2697
+ if (!hasDocument()) return void 0;
2698
+ const CLEAR_MAX_AGE = 0;
2699
+ document.cookie = buildAgencyInviteCookie("", CLEAR_MAX_AGE, options);
2700
+ return readAgencyInviteCookie() === void 0;
2701
+ }
2702
+ function captureAgencyInviteToken(options = {}) {
2703
+ const fromUrl = readTokenFromUrl(options);
2704
+ if (fromUrl !== void 0) {
2705
+ return { source: "url", token: fromUrl, persisted: writeAgencyInviteCookie(fromUrl, options) };
2706
+ }
2707
+ const fromCookie = readAgencyInviteCookie();
2708
+ return fromCookie !== void 0 ? { source: "cookie", token: fromCookie, persisted: void 0 } : { source: "none", token: void 0, persisted: void 0 };
2709
+ }
2710
+ var REDACT_BOUNDARIES = [
2711
+ "%2526",
2712
+ "%2523",
2713
+ "%253B",
2714
+ "%26",
2715
+ "%23",
2716
+ "%3B",
2717
+ "&",
2718
+ "#",
2719
+ ";"
2720
+ ];
2721
+ var REDACT_LEADS = ["%253F", "%3F", "?", ...REDACT_BOUNDARIES];
2722
+ var REDACT_SEPARATORS = ["%253D", "%3D", "="];
2723
+ function escapeForRegExp(literal) {
2724
+ return literal.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2725
+ }
2726
+ var REDACT_PATTERN = new RegExp(
2727
+ [
2728
+ // 先頭区切り。`\b` が使えないのは、入れ子形 (`%3Finvite%3D`) では `F` と `i` が
2729
+ // どちらも単語文字なので単語境界が存在しないため。
2730
+ `(^|${REDACT_LEADS.map(escapeForRegExp).join("|")})`,
2731
+ // パラメータ名と区切りはキャプチャして再出力する。`i` フラグがあるので、
2732
+ // 入力の `INVITE=` / `%3d` などの元の表記を保つ必要がある。
2733
+ `(${AGENCY_INVITE_TOKEN_PARAM})`,
2734
+ `(${REDACT_SEPARATORS.map(escapeForRegExp).join("|")})`,
2735
+ // 値。境界の直前まで貪欲に食う。`\s` は終端にしない — 生の空白や改行を含む
2736
+ // トークンでマスクが途中で切れ、後半が漏れる。
2737
+ `(?:(?!${REDACT_BOUNDARIES.map(escapeForRegExp).join("|")})[^&#;])*`
2738
+ ].join(""),
2739
+ "gi"
2740
+ );
2741
+ function redactAgencyInviteToken(url) {
2742
+ return url.replace(
2743
+ REDACT_PATTERN,
2744
+ // 文字列 replacement(`'$1$2$3[REDACTED]'`)にしないのは、
2745
+ // `AGENCY_INVITE_TOKEN_REDACTION` に `$` が入った瞬間に置換パターンとして
2746
+ // 解釈される罠を作らないため。
2747
+ (_match, lead, name, separator) => `${lead}${name}${separator}${AGENCY_INVITE_TOKEN_REDACTION}`
2748
+ );
2749
+ }
2750
+ function appendAgencyInviteToken(signupUrl, token) {
2751
+ if (token === void 0) return signupUrl;
2752
+ const separator = signupUrl.includes("?") ? "&" : "?";
2753
+ return `${signupUrl}${separator}${AGENCY_INVITE_TOKEN_PARAM}=${encodeURIComponent(token)}`;
2754
+ }
2755
+
2615
2756
  // src/client/redirect.ts
2616
2757
  var OAUTH_AUTHORIZE_ENDPOINT = "/api/v1/oauth/authorize";
2617
2758
  var AUTH_LOGOUT_ENDPOINT = "/api/v1/auth/logout";
@@ -2624,6 +2765,7 @@ var REDIRECT_LOOP_WINDOW_MS = 6e4;
2624
2765
  var REDIRECT_LOOP_THRESHOLD = 3;
2625
2766
  var storageReadFailureLogged = false;
2626
2767
  var storageWriteFailureLogged = false;
2768
+ var ssrInviteTokenUnavailableLogged = false;
2627
2769
  function isLoopDetectionDegraded() {
2628
2770
  return storageReadFailureLogged || storageWriteFailureLogged;
2629
2771
  }
@@ -2802,12 +2944,16 @@ function createRedirectMethods(deps) {
2802
2944
  }
2803
2945
  if (options?.screenHint === SCREEN_HINT_SIGNUP) {
2804
2946
  params.set(SCREEN_HINT_PARAM, SCREEN_HINT_SIGNUP);
2947
+ const inviteToken = readAgencyInviteToken();
2948
+ if (inviteToken !== void 0) {
2949
+ params.set(AGENCY_INVITE_TOKEN_PARAM, inviteToken);
2950
+ }
2805
2951
  }
2806
2952
  if (options?.prompt) {
2807
2953
  params.set("prompt", options.prompt);
2808
2954
  }
2809
2955
  const authorizeUrl = `${baseUrl}${OAUTH_AUTHORIZE_ENDPOINT}?${params.toString()}`;
2810
- logger.debug("Redirecting to authorize:", authorizeUrl);
2956
+ logger.debug("Redirecting to authorize:", redactAgencyInviteToken(authorizeUrl));
2811
2957
  recordRedirectAttempt(authorizeKey, now, logger);
2812
2958
  try {
2813
2959
  window.location.href = authorizeUrl;
@@ -2826,7 +2972,7 @@ function createRedirectMethods(deps) {
2826
2972
  return redirectToAuthorize(options);
2827
2973
  }
2828
2974
  const targetUrl = options?.screenHint === SCREEN_HINT_SIGNUP ? getSignupUrl() : getLoginUrl(void 0, options?.prompt ? { prompt: options.prompt } : void 0);
2829
- logger.debug("Redirecting to auth page:", targetUrl);
2975
+ logger.debug("Redirecting to auth page:", redactAgencyInviteToken(targetUrl));
2830
2976
  window.location.href = targetUrl;
2831
2977
  return { success: true };
2832
2978
  }
@@ -2843,7 +2989,7 @@ function createRedirectMethods(deps) {
2843
2989
  const promptSuffix = options?.prompt ? `&prompt=${encodeURIComponent(options.prompt)}` : "";
2844
2990
  return `${baseUrl}/login?redirect=${encodeURIComponent(redirect)}&service=${encodeURIComponent(serviceCode)}${promptSuffix}`;
2845
2991
  }
2846
- function getSignupUrl(redirectUrl) {
2992
+ function getSignupUrl(redirectUrl, options) {
2847
2993
  let redirect;
2848
2994
  if (redirectUrl != null) {
2849
2995
  redirect = redirectUrl;
@@ -2853,7 +2999,22 @@ function createRedirectMethods(deps) {
2853
2999
  logger.warn("getSignupUrl: SSR \u74B0\u5883\u3067 redirectUrl \u304C\u672A\u6307\u5B9A\u306E\u305F\u3081\u7A7A\u6587\u5B57\u306B\u30D5\u30A9\u30FC\u30EB\u30D0\u30C3\u30AF\u3057\u307E\u3059");
2854
3000
  redirect = "";
2855
3001
  }
2856
- return `${baseUrl}/signup?redirect=${encodeURIComponent(redirect)}&service=${encodeURIComponent(serviceCode)}`;
3002
+ let inviteToken = options?.inviteToken;
3003
+ if (inviteToken === void 0) {
3004
+ inviteToken = readAgencyInviteToken();
3005
+ if (inviteToken === void 0 && typeof window === "undefined") {
3006
+ if (!ssrInviteTokenUnavailableLogged) {
3007
+ ssrInviteTokenUnavailableLogged = true;
3008
+ logger.warn(
3009
+ "[FFID SDK] getSignupUrl: SSR \u3067\u306F\u62DB\u5F85\u30C8\u30FC\u30AF\u30F3\uFF08invite\uFF09\u3092\u8AAD\u3081\u307E\u305B\u3093\u3002\u4EE3\u7406\u5E97\u306E\u62DB\u5F85\u30EA\u30F3\u30AF\u3092\u4F7F\u3046\u5834\u5408\u306F options.inviteToken \u306B\u6E21\u3057\u3066\u304F\u3060\u3055\u3044\uFF08\u4F7F\u3063\u3066\u3044\u306A\u3044\u5834\u5408\u306F\u3053\u306E\u8B66\u544A\u3092\u7121\u8996\u3057\u3066\u3088\u3044\uFF09"
3010
+ );
3011
+ }
3012
+ }
3013
+ }
3014
+ return appendAgencyInviteToken(
3015
+ `${baseUrl}/signup?redirect=${encodeURIComponent(redirect)}&service=${encodeURIComponent(serviceCode)}`,
3016
+ inviteToken
3017
+ );
2857
3018
  }
2858
3019
  function getLogoutUrl(postLogoutRedirectUri) {
2859
3020
  const url = new URL(`${baseUrl}${AUTH_LOGOUT_ENDPOINT}`);
@@ -2869,7 +3030,7 @@ function createRedirectMethods(deps) {
2869
3030
  return { success: false, error: "SSR \u74B0\u5883\u3067\u306F\u30EA\u30C0\u30A4\u30EC\u30AF\u30C8\u3067\u304D\u307E\u305B\u3093" };
2870
3031
  }
2871
3032
  const logoutUrl = getLogoutUrl(postLogoutRedirectUri);
2872
- logger.debug("Redirecting to logout:", logoutUrl);
3033
+ logger.debug("Redirecting to logout:", redactAgencyInviteToken(logoutUrl));
2873
3034
  window.location.href = logoutUrl;
2874
3035
  return { success: true };
2875
3036
  }
@@ -3482,11 +3643,11 @@ var EFFECTIVE_SUBSCRIPTION_STATUSES = [
3482
3643
  "expired"
3483
3644
  ];
3484
3645
  var PAST_DUE_GRACE_PERIOD_DAYS = 7;
3485
- var HOURS_PER_DAY = 24;
3486
- var MINUTES_PER_HOUR = 60;
3487
- var SECONDS_PER_MINUTE = 60;
3646
+ var HOURS_PER_DAY2 = 24;
3647
+ var MINUTES_PER_HOUR2 = 60;
3648
+ var SECONDS_PER_MINUTE2 = 60;
3488
3649
  var MS_PER_SECOND2 = 1e3;
3489
- var MS_PER_DAY = HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE * MS_PER_SECOND2;
3650
+ var MS_PER_DAY = HOURS_PER_DAY2 * MINUTES_PER_HOUR2 * SECONDS_PER_MINUTE2 * MS_PER_SECOND2;
3490
3651
 
3491
3652
  // src/subscriptions/compute-effective-status.ts
3492
3653
  function isExpired(isoTimestamp, nowMs) {
@@ -3639,6 +3800,18 @@ var FFID_ERROR_CODES = {
3639
3800
  TOKEN_EXPIRED: "TOKEN_EXPIRED",
3640
3801
  STATE_MISMATCH_ERROR: "STATE_MISMATCH_ERROR"
3641
3802
  };
3803
+ var FFID_SUBSCRIPTION_ERROR_CODES = {
3804
+ CANCELLATION_SCHEDULED: "CANCELLATION_SCHEDULED",
3805
+ PENDING_CHANGE_EXISTS: "PENDING_CHANGE_EXISTS",
3806
+ CHANGE_DIRECTION_MIXED: "CHANGE_DIRECTION_MIXED",
3807
+ CHANGE_PAYMENT_FAILED: "CHANGE_PAYMENT_FAILED",
3808
+ SAME_PLAN_SELECTED: "SAME_PLAN_SELECTED",
3809
+ NO_PENDING_DOWNGRADE: "NO_PENDING_DOWNGRADE",
3810
+ SUBSCRIPTION_NOT_CANCELABLE: "SUBSCRIPTION_NOT_CANCELABLE",
3811
+ SUBSCRIPTION_ALREADY_CANCELED: "SUBSCRIPTION_ALREADY_CANCELED",
3812
+ FREE_DOWNGRADE_NOT_ALLOWED: "FREE_DOWNGRADE_NOT_ALLOWED",
3813
+ PAYMENT_METHOD_REQUIRED: "PAYMENT_METHOD_REQUIRED"
3814
+ };
3642
3815
 
3643
3816
  // src/client/ffid-client.ts
3644
3817
  var UNAUTHORIZED_STATUS2 = 401;
@@ -4322,6 +4495,15 @@ function FFIDProvider({
4322
4495
  }),
4323
4496
  [serviceCode, scope, apiBaseUrl, debug, logger, authMode, clientId, timeout, tokenStore]
4324
4497
  );
4498
+ useEffect(() => {
4499
+ const captured = captureAgencyInviteToken();
4500
+ if (captured.persisted === false) {
4501
+ client.logger.debug(
4502
+ "[FFID SDK] \u62DB\u5F85\u30C8\u30FC\u30AF\u30F3\u306E cookie \u4FDD\u6301\u306B\u5931\u6557\u3057\u307E\u3057\u305F\uFF08\u5225\u30DA\u30FC\u30B8\u3078\u56DE\u904A\u3059\u308B\u3068\u4EE3\u7406\u5E97\u306E\u7D10\u4ED8\u3051\u304C\u5931\u308F\u308C\u307E\u3059\uFF09",
4503
+ { source: captured.source }
4504
+ );
4505
+ }
4506
+ }, [client]);
4325
4507
  const refresh = useCallback(async () => {
4326
4508
  client.logger.debug("Refreshing session...");
4327
4509
  try {
@@ -6297,4 +6479,4 @@ function FFIDInquiryForm({
6297
6479
  );
6298
6480
  }
6299
6481
 
6300
- export { ACCESS_GRANTING_EFFECTIVE_STATUSES, BLOCKING_EFFECTIVE_STATUSES, CATALOG_HASH_PATTERN, DEFAULT_API_BASE_URL, DEFAULT_OAUTH_SCOPES, EFFECTIVE_SUBSCRIPTION_STATUSES, FFIDAnnouncementBadge, FFIDAnnouncementList, FFIDCatalogHashError, FFIDInquiryForm, FFIDLoginButton, FFIDOrganizationSwitcher, FFIDProvider, FFIDSDKError, FFIDSubscriptionBadge, FFIDUserMenu, FFID_ALLOWED_ORGANIZATION_LOGO_MIME_TYPES, FFID_ANNOUNCEMENTS_ERROR_CODES, FFID_CATALOG_ENVIRONMENTS, FFID_CATALOG_PUBLICATION_STATUSES, FFID_COMPANY_SIZE_CODES, FFID_ERROR_CODES, FFID_INDUSTRY_CODES, FFID_INQUIRY_ACQUISITION_SOURCES, FFID_INQUIRY_CATEGORIES, FFID_INQUIRY_CATEGORIES_SITE_2026, FFID_MAX_ORGANIZATION_LOGO_FILE_SIZE_BYTES, FFID_MAX_ORGANIZATION_LOGO_FILE_SIZE_MB, FFID_ORGANIZATION_PROFILE_ERROR_CODES, canonicalizeCatalogValue, cleanupStateStorage, computeEffectiveStatusFromSession, computeFFIDCheckoutCopyHash, computeLegalDisclosureHash, computePaymentConfigurationHash, computePraxisCopyHash, computeScopeHash, computeTaxConfigurationHash, createFFIDAnnouncementsClient, createFFIDClient, createTokenStore, generateCodeChallenge, generateCodeVerifier, handleRedirectCallback, hasAccessFromUserinfo, hashCanonicalJson, isBlockedFromUserinfo, isFFIDInquiryAcquisitionSource, isFFIDInquiryCategorySite2026, normalizeRedirectUri, retrieveCodeVerifier, retrieveState, sha256Hex, storeCodeVerifier, storeState, useFFID, useFFIDAnnouncements, useFFIDContext, useSubscription, validatePublishedCatalog, withSubscription };
6482
+ export { ACCESS_GRANTING_EFFECTIVE_STATUSES, AGENCY_INVITE_COOKIE_MAX_AGE_SEC, AGENCY_INVITE_COOKIE_NAME, AGENCY_INVITE_TOKEN_PARAM, AGENCY_INVITE_TOKEN_REDACTION, BLOCKING_EFFECTIVE_STATUSES, CATALOG_HASH_PATTERN, DEFAULT_API_BASE_URL, DEFAULT_OAUTH_SCOPES, EFFECTIVE_SUBSCRIPTION_STATUSES, FFIDAnnouncementBadge, FFIDAnnouncementList, FFIDCatalogHashError, FFIDInquiryForm, FFIDLoginButton, FFIDOrganizationSwitcher, FFIDProvider, FFIDSDKError, FFIDSubscriptionBadge, FFIDUserMenu, FFID_ALLOWED_ORGANIZATION_LOGO_MIME_TYPES, FFID_ANNOUNCEMENTS_ERROR_CODES, FFID_CATALOG_ENVIRONMENTS, FFID_CATALOG_PUBLICATION_STATUSES, FFID_COMPANY_SIZE_CODES, FFID_ERROR_CODES, FFID_INDUSTRY_CODES, FFID_INQUIRY_ACQUISITION_SOURCES, FFID_INQUIRY_CATEGORIES, FFID_INQUIRY_CATEGORIES_SITE_2026, FFID_MAX_ORGANIZATION_LOGO_FILE_SIZE_BYTES, FFID_MAX_ORGANIZATION_LOGO_FILE_SIZE_MB, FFID_ORGANIZATION_PROFILE_ERROR_CODES, FFID_SUBSCRIPTION_ERROR_CODES, canonicalizeCatalogValue, captureAgencyInviteToken, cleanupStateStorage, clearAgencyInviteToken, computeEffectiveStatusFromSession, computeFFIDCheckoutCopyHash, computeLegalDisclosureHash, computePaymentConfigurationHash, computePraxisCopyHash, computeScopeHash, computeTaxConfigurationHash, createFFIDAnnouncementsClient, createFFIDClient, createTokenStore, generateCodeChallenge, generateCodeVerifier, handleRedirectCallback, hasAccessFromUserinfo, hashCanonicalJson, isBlockedFromUserinfo, isFFIDInquiryAcquisitionSource, isFFIDInquiryCategorySite2026, normalizeRedirectUri, readAgencyInviteCookie, readAgencyInviteToken, redactAgencyInviteToken, retrieveCodeVerifier, retrieveState, sha256Hex, storeCodeVerifier, storeState, useFFID, useFFIDAnnouncements, useFFIDContext, useSubscription, validatePublishedCatalog, withSubscription };
@@ -1,34 +1,34 @@
1
1
  'use strict';
2
2
 
3
- var chunkSOZ2B2ZL_cjs = require('../chunk-SOZ2B2ZL.cjs');
3
+ var chunkRNCK5E3Y_cjs = require('../chunk-RNCK5E3Y.cjs');
4
4
 
5
5
 
6
6
 
7
7
  Object.defineProperty(exports, "FFIDAnnouncementBadge", {
8
8
  enumerable: true,
9
- get: function () { return chunkSOZ2B2ZL_cjs.FFIDAnnouncementBadge; }
9
+ get: function () { return chunkRNCK5E3Y_cjs.FFIDAnnouncementBadge; }
10
10
  });
11
11
  Object.defineProperty(exports, "FFIDAnnouncementList", {
12
12
  enumerable: true,
13
- get: function () { return chunkSOZ2B2ZL_cjs.FFIDAnnouncementList; }
13
+ get: function () { return chunkRNCK5E3Y_cjs.FFIDAnnouncementList; }
14
14
  });
15
15
  Object.defineProperty(exports, "FFIDInquiryForm", {
16
16
  enumerable: true,
17
- get: function () { return chunkSOZ2B2ZL_cjs.FFIDInquiryForm; }
17
+ get: function () { return chunkRNCK5E3Y_cjs.FFIDInquiryForm; }
18
18
  });
19
19
  Object.defineProperty(exports, "FFIDLoginButton", {
20
20
  enumerable: true,
21
- get: function () { return chunkSOZ2B2ZL_cjs.FFIDLoginButton; }
21
+ get: function () { return chunkRNCK5E3Y_cjs.FFIDLoginButton; }
22
22
  });
23
23
  Object.defineProperty(exports, "FFIDOrganizationSwitcher", {
24
24
  enumerable: true,
25
- get: function () { return chunkSOZ2B2ZL_cjs.FFIDOrganizationSwitcher; }
25
+ get: function () { return chunkRNCK5E3Y_cjs.FFIDOrganizationSwitcher; }
26
26
  });
27
27
  Object.defineProperty(exports, "FFIDSubscriptionBadge", {
28
28
  enumerable: true,
29
- get: function () { return chunkSOZ2B2ZL_cjs.FFIDSubscriptionBadge; }
29
+ get: function () { return chunkRNCK5E3Y_cjs.FFIDSubscriptionBadge; }
30
30
  });
31
31
  Object.defineProperty(exports, "FFIDUserMenu", {
32
32
  enumerable: true,
33
- get: function () { return chunkSOZ2B2ZL_cjs.FFIDUserMenu; }
33
+ get: function () { return chunkRNCK5E3Y_cjs.FFIDUserMenu; }
34
34
  });
@@ -1,3 +1,3 @@
1
- export { H as FFIDAnnouncementBadge, an as FFIDAnnouncementBadgeClassNames, ao as FFIDAnnouncementBadgeProps, I as FFIDAnnouncementList, ap as FFIDAnnouncementListClassNames, aq as FFIDAnnouncementListProps, U as FFIDInquiryForm, V as FFIDInquiryFormCategoryItem, W as FFIDInquiryFormClassNames, X as FFIDInquiryFormLegalLayout, Y as FFIDInquiryFormOrganization, Z as FFIDInquiryFormPlaceholderContext, _ as FFIDInquiryFormPrefill, $ as FFIDInquiryFormProps, a0 as FFIDInquiryFormSubmitData, a1 as FFIDInquiryFormSubmitResult, a3 as FFIDLoginButton, ar as FFIDLoginButtonProps, a5 as FFIDOrganizationSwitcher, as as FFIDOrganizationSwitcherClassNames, at as FFIDOrganizationSwitcherProps, ab as FFIDSubscriptionBadge, au as FFIDSubscriptionBadgeClassNames, av as FFIDSubscriptionBadgeProps, ac as FFIDUserMenu, aw as FFIDUserMenuClassNames, ax as FFIDUserMenuProps } from '../index-BMKhKhzF.cjs';
1
+ export { H as FFIDAnnouncementBadge, an as FFIDAnnouncementBadgeClassNames, ao as FFIDAnnouncementBadgeProps, I as FFIDAnnouncementList, ap as FFIDAnnouncementListClassNames, aq as FFIDAnnouncementListProps, U as FFIDInquiryForm, V as FFIDInquiryFormCategoryItem, W as FFIDInquiryFormClassNames, X as FFIDInquiryFormLegalLayout, Y as FFIDInquiryFormOrganization, Z as FFIDInquiryFormPlaceholderContext, _ as FFIDInquiryFormPrefill, $ as FFIDInquiryFormProps, a0 as FFIDInquiryFormSubmitData, a1 as FFIDInquiryFormSubmitResult, a3 as FFIDLoginButton, ar as FFIDLoginButtonProps, a5 as FFIDOrganizationSwitcher, as as FFIDOrganizationSwitcherClassNames, at as FFIDOrganizationSwitcherProps, ab as FFIDSubscriptionBadge, au as FFIDSubscriptionBadgeClassNames, av as FFIDSubscriptionBadgeProps, ac as FFIDUserMenu, aw as FFIDUserMenuClassNames, ax as FFIDUserMenuProps } from '../index-BG7g99pK.cjs';
2
2
  import 'react/jsx-runtime';
3
3
  import 'react';
@@ -1,3 +1,3 @@
1
- export { H as FFIDAnnouncementBadge, an as FFIDAnnouncementBadgeClassNames, ao as FFIDAnnouncementBadgeProps, I as FFIDAnnouncementList, ap as FFIDAnnouncementListClassNames, aq as FFIDAnnouncementListProps, U as FFIDInquiryForm, V as FFIDInquiryFormCategoryItem, W as FFIDInquiryFormClassNames, X as FFIDInquiryFormLegalLayout, Y as FFIDInquiryFormOrganization, Z as FFIDInquiryFormPlaceholderContext, _ as FFIDInquiryFormPrefill, $ as FFIDInquiryFormProps, a0 as FFIDInquiryFormSubmitData, a1 as FFIDInquiryFormSubmitResult, a3 as FFIDLoginButton, ar as FFIDLoginButtonProps, a5 as FFIDOrganizationSwitcher, as as FFIDOrganizationSwitcherClassNames, at as FFIDOrganizationSwitcherProps, ab as FFIDSubscriptionBadge, au as FFIDSubscriptionBadgeClassNames, av as FFIDSubscriptionBadgeProps, ac as FFIDUserMenu, aw as FFIDUserMenuClassNames, ax as FFIDUserMenuProps } from '../index-BMKhKhzF.js';
1
+ export { H as FFIDAnnouncementBadge, an as FFIDAnnouncementBadgeClassNames, ao as FFIDAnnouncementBadgeProps, I as FFIDAnnouncementList, ap as FFIDAnnouncementListClassNames, aq as FFIDAnnouncementListProps, U as FFIDInquiryForm, V as FFIDInquiryFormCategoryItem, W as FFIDInquiryFormClassNames, X as FFIDInquiryFormLegalLayout, Y as FFIDInquiryFormOrganization, Z as FFIDInquiryFormPlaceholderContext, _ as FFIDInquiryFormPrefill, $ as FFIDInquiryFormProps, a0 as FFIDInquiryFormSubmitData, a1 as FFIDInquiryFormSubmitResult, a3 as FFIDLoginButton, ar as FFIDLoginButtonProps, a5 as FFIDOrganizationSwitcher, as as FFIDOrganizationSwitcherClassNames, at as FFIDOrganizationSwitcherProps, ab as FFIDSubscriptionBadge, au as FFIDSubscriptionBadgeClassNames, av as FFIDSubscriptionBadgeProps, ac as FFIDUserMenu, aw as FFIDUserMenuClassNames, ax as FFIDUserMenuProps } from '../index-BG7g99pK.js';
2
2
  import 'react/jsx-runtime';
3
3
  import 'react';
@@ -1 +1 @@
1
- export { FFIDAnnouncementBadge, FFIDAnnouncementList, FFIDInquiryForm, FFIDLoginButton, FFIDOrganizationSwitcher, FFIDSubscriptionBadge, FFIDUserMenu } from '../chunk-E2AJAVX6.js';
1
+ export { FFIDAnnouncementBadge, FFIDAnnouncementList, FFIDInquiryForm, FFIDLoginButton, FFIDOrganizationSwitcher, FFIDSubscriptionBadge, FFIDUserMenu } from '../chunk-YKBAFPX5.js';
@@ -450,12 +450,31 @@ interface FFIDSubscriptionDetail {
450
450
  trialStart: string | null;
451
451
  trialEnd: string | null;
452
452
  canceledAt: string | null;
453
+ /**
454
+ * `true` while a cancellation is reserved until the period end. `status` stays
455
+ * `active` until then; the subscription becomes `canceled` at `currentPeriodEnd`.
456
+ * The reservation can only be withdrawn in the FFID portal (there is no SDK method).
457
+ */
453
458
  cancelAtPeriodEnd: boolean;
454
- /** Pending period-end downgrade info (null = no scheduled downgrade) */
459
+ /**
460
+ * Change reserved until the period end (null = nothing reserved).
461
+ *
462
+ * Every decrease — a lower plan, paid → free, fewer seats, yearly → monthly — is
463
+ * reserved instead of applied immediately (FFID #6327). The current plan, seats and
464
+ * interval stay in effect until `scheduledAt`. Withdraw it with `cancelPendingDowngrade()`.
465
+ */
455
466
  pendingDowngrade: {
467
+ /** Plan in effect after the period end. Equals `planCode` for a seat-only or interval-only reservation. */
456
468
  planCode: string;
457
469
  planName: string;
470
+ /** Billing interval in effect after the period end */
458
471
  billingInterval: FFIDBillingInterval;
472
+ /**
473
+ * Seat count in effect after the period end. `null` = the seat count does not change.
474
+ * Seat reductions are made in the FFID portal; this lets you show them (8.0.0).
475
+ * FFID servers released before #6327 omit it, so compare with `== null` at runtime.
476
+ */
477
+ quantity: number | null;
459
478
  scheduledAt: string;
460
479
  requestedAt: string;
461
480
  } | null;
@@ -522,7 +541,17 @@ interface FFIDChangePlanParams {
522
541
  /** New billing interval (default: keeps current) */
523
542
  billingInterval?: FFIDBillingInterval;
524
543
  }
525
- /** Response from change plan endpoint */
544
+ /**
545
+ * Response from change plan endpoint.
546
+ *
547
+ * - An increase (higher plan / monthly → yearly) applies immediately and the difference up to
548
+ * the current period end is charged now. `pendingDowngrade` is absent.
549
+ * - A decrease (lower plan / paid → free / yearly → monthly) is reserved until the period end:
550
+ * `pendingDowngrade` is present and `subscription.planCode` is still the current plan.
551
+ * Nothing is charged or refunded. A contract that cannot be reserved still changes
552
+ * immediately, without a refund: one with no period end, or a paid → paid decrease on a
553
+ * contract without a Stripe subscription.
554
+ */
526
555
  interface FFIDChangePlanResponse {
527
556
  message: string;
528
557
  subscription: {
@@ -535,7 +564,7 @@ interface FFIDChangePlanResponse {
535
564
  status: FFIDSubscriptionStatus;
536
565
  isUpgrade: boolean;
537
566
  };
538
- /** Present when downgrade is scheduled for period end instead of immediate */
567
+ /** Present when the change is a decrease reserved until the period end */
539
568
  pendingDowngrade?: {
540
569
  planCode: string;
541
570
  planName: string;
@@ -551,17 +580,41 @@ interface FFIDCancelSubscriptionParams {
551
580
  /** Additional cancellation details */
552
581
  reasonDetails?: string;
553
582
  }
554
- /** Response from cancel subscription endpoint */
583
+ /**
584
+ * Response from cancel subscription endpoint.
585
+ *
586
+ * Since 8.0.0 a contract billed through Stripe is **reserved** for cancellation at the period
587
+ * end (FFID #6327): `status` stays `active`, `cancelAtPeriodEnd` is `true` and `cancelAt` is the
588
+ * period end. Access continues until then and nothing is refunded. A contract that cannot be
589
+ * reserved (no Stripe subscription, no period end yet, or Stripe not configured on FFID) is
590
+ * canceled immediately (`status: 'canceled'`, `cancelAtPeriodEnd: false`, `cancelAt: null`).
591
+ * Branch on `cancelAtPeriodEnd`, not on whether the contract uses Stripe.
592
+ *
593
+ * Do not treat this response as "access revoked". The `subscription.canceled` webhook arrives
594
+ * at reservation (`source: 'user_initiated'`) and again at the period end
595
+ * (`source: 'stripe_confirmed'`); see `FFIDSubscriptionCanceledPayload`.
596
+ */
555
597
  interface FFIDCancelSubscriptionResponse {
556
598
  message: string;
557
599
  subscription: {
558
600
  id: string;
559
601
  organizationId: string;
602
+ /** `active` while the cancellation is reserved; `canceled` only for an immediate cancellation */
560
603
  status: FFIDSubscriptionStatus;
561
604
  canceledAt: string | null;
605
+ /** `true` when the cancellation is reserved until the period end */
606
+ cancelAtPeriodEnd: boolean;
607
+ /** When the reserved cancellation takes effect (the period end). `null` for an immediate cancellation */
608
+ cancelAt: string | null;
562
609
  };
563
610
  }
564
- /** Response from cancel pending downgrade endpoint */
611
+ /**
612
+ * Response from cancel pending downgrade endpoint.
613
+ *
614
+ * Withdraws the reserved change on the Stripe side as well, so the current plan, seats and
615
+ * interval simply continue after the period end. It does not withdraw a reserved
616
+ * cancellation — that is done in the FFID portal.
617
+ */
565
618
  interface FFIDCancelPendingDowngradeResponse {
566
619
  message: string;
567
620
  subscription: {
@@ -616,14 +669,18 @@ interface FFIDPlanChangePreviewBase {
616
669
  lineItems: FFIDPlanChangeLineItem[];
617
670
  }
618
671
  /**
619
- * Plan change proration preview.
672
+ * Plan change proration preview. It is computed with the same rules as `changePlan()`.
620
673
  *
621
674
  * `willApplyAtPeriodEnd` is a discriminant that constrains related fields at the type level:
622
- * - `true`: period-end-deferred downgrade. `proratedAmount` is always 0; `effectiveDate` mirrors
623
- * the subscription's `currentPeriodEnd` (may be `null` when the subscription has no active
624
- * billing cycle yet — e.g. a pre-Stripe trial or a subscription whose billing cycle is unset).
625
- * - `false`: immediate change. `effectiveDate` is always `null`; `proratedAmount` is the live
626
- * (possibly Stripe-refined) day-basis difference.
675
+ * - `true`: a decrease reserved until the period end. `proratedAmount` is always 0 (nothing is
676
+ * charged or refunded now); `effectiveDate` mirrors the subscription's `currentPeriodEnd`
677
+ * (may be `null` when the subscription has no active billing cycle yet).
678
+ * - `false`: immediate change. `effectiveDate` is always `null`. For an increase,
679
+ * `proratedAmount` is the difference up to the current period end that is charged now
680
+ * (possibly Stripe-refined). A decrease on a contract that cannot be reserved also lands here:
681
+ * `0` for a contract without a Stripe subscription (no refund), but Stripe's prorated amount —
682
+ * negative, credited on the next invoice — for a Stripe contract with no period end yet
683
+ * (e.g. during a trial).
627
684
  */
628
685
  type FFIDPlanChangePreview = FFIDPlanChangePreviewBase & ({
629
686
  willApplyAtPeriodEnd: true;
@@ -633,7 +690,7 @@ type FFIDPlanChangePreview = FFIDPlanChangePreviewBase & ({
633
690
  } | {
634
691
  willApplyAtPeriodEnd: false;
635
692
  effectiveDate: null;
636
- /** Prorated difference charged within the current billing period */
693
+ /** Difference up to the current period end charged now (see the type doc for the decrease cases) */
637
694
  proratedAmount: number;
638
695
  });
639
696
  /** Response from plan change preview endpoint */
@@ -658,14 +715,27 @@ interface FFIDSeatChangeLineItem {
658
715
  * Sister type to `FFIDPlanChangePreview`. The shared `type` discriminant lets
659
716
  * consumers narrow a preview payload without inspecting unrelated fields.
660
717
  *
718
+ * - `willApplyAtPeriodEnd=true`: a seat reduction reserved until the period end.
719
+ * `proratedAmount` is 0 and `effectiveDate` is when the new seat count applies.
720
+ * - Otherwise the change applies immediately. For an increase, `proratedAmount` is the amount
721
+ * charged now up to the current period end. A reduction that cannot be reserved is normally
722
+ * `0`, but can be negative (a credit on the next invoice) for a Stripe contract with no period
723
+ * end yet or when only the contract unit price is known.
661
724
  * - `isEstimate=true` (default when Stripe is not configured or data is unavailable):
662
- * `proratedAmount` is computed locally as `(newQuantity - currentQuantity) * unitPrice`.
663
- * - `isEstimate=false`: `proratedAmount` reflects Stripe's live proration calculation.
725
+ * `proratedAmount` is computed locally. `isEstimate=false`: it reflects Stripe's live
726
+ * proration calculation.
664
727
  *
665
728
  * `nextInvoiceAmount` is always a local estimate (`unitPrice * newQuantity`).
666
729
  *
667
- * `pricingUnavailable=true` indicates the plan uses custom pricing with a zero unit
668
- * price (Enterprise). Consumers should hide monetary amounts in this case.
730
+ * `pricingUnavailable=true` means custom pricing or an unverified contracted Stripe
731
+ * Price. Hide amounts; zero is not a quote. `custom_pricing` permits the existing
732
+ * unbilled seat-change flow. Require another preview before confirmation only when
733
+ * pricing is unavailable and the reason is not `custom_pricing`. `stripe_error` also
734
+ * covers invoice/address failures after the contract price is known; use
735
+ * `pricingUnavailable`, not the reason alone, to decide whether to hide amounts.
736
+ * `no_stripe_data` means invoice proration is unavailable even if the contract unit
737
+ * price is known. Portal seat PUT independently enforces subscription limits;
738
+ * external consumers should guide users to the FFID portal for seat changes.
669
739
  */
670
740
  interface FFIDSeatChangePreview {
671
741
  /** Discriminant for preview response variants (pairs with `FFIDPlanChangePreview.type`) */
@@ -676,20 +746,28 @@ interface FFIDSeatChangePreview {
676
746
  unitPrice: number;
677
747
  billingInterval: FFIDBillingInterval;
678
748
  /**
679
- * Prorated cost for the current billing period. Negative when seats are decreased
680
- * (credit). `0` when `pricingUnavailable === true` (Enterprise custom pricing).
749
+ * Amount charged now for the rest of the current billing period. A reserved seat reduction
750
+ * (`willApplyAtPeriodEnd: true`) is always `0`; see the type doc for reductions that cannot
751
+ * be reserved. Also `0` when `pricingUnavailable === true`; hide it instead of quoting zero.
681
752
  */
682
753
  proratedAmount: number;
683
754
  /** Next invoice full amount — always a local estimate (`unitPrice * newQuantity`) */
684
755
  nextInvoiceAmount: number;
685
756
  nextInvoiceDate: string | null;
686
757
  currency: FFIDSupportedCurrency;
687
- /** true when proratedAmount is estimated from local prices rather than Stripe proration data */
758
+ /** true when proratedAmount is a local estimate rather than Stripe invoice/proration data */
688
759
  isEstimate: boolean;
689
- /** true when unit price is 0 due to custom pricing (Enterprise) — hide monetary amounts */
760
+ /** Hide amounts: custom pricing or contracted Stripe Price could not be verified. */
690
761
  pricingUnavailable?: boolean;
691
762
  /** Reason why `isEstimate` is true. Only meaningful when `isEstimate === true`. */
692
763
  estimateReason?: 'no_stripe_data' | 'custom_pricing' | 'stripe_error';
764
+ /**
765
+ * `true` when the seat reduction would be reserved until the period end (8.0.0).
766
+ * Absent or `false` = the change applies immediately.
767
+ */
768
+ willApplyAtPeriodEnd?: boolean;
769
+ /** When the reserved seat count applies (present when `willApplyAtPeriodEnd` is `true`) */
770
+ effectiveDate?: string | null;
693
771
  lineItems: FFIDSeatChangeLineItem[];
694
772
  }
695
773
  /** Response from seat change preview endpoint */
@@ -774,6 +852,7 @@ interface FFIDProvisionUserProfileInput {
774
852
  /** Display name (1–255 chars). */
775
853
  displayName?: string;
776
854
  /** Company name (≤255 chars). */
855
+ /** @deprecated Accepted for compatibility but ignored by FFID. */
777
856
  companyName?: string;
778
857
  /** Department (≤255 chars). */
779
858
  department?: string;
@@ -1475,7 +1554,7 @@ interface FFIDUserProfile {
1475
1554
  avatarUrl: string | null;
1476
1555
  /** Phone number (nullable when not set) */
1477
1556
  phone: string | null;
1478
- /** Company name (nullable when not set) */
1557
+ /** @deprecated Individual employer name is retired; this field is always null. Use the organization entity's name. */
1479
1558
  companyName: string | null;
1480
1559
  /** Department (nullable when not set) */
1481
1560
  department: string | null;
@@ -1556,7 +1635,7 @@ interface FFIDUpdateUserProfileRequest {
1556
1635
  displayName?: string | null;
1557
1636
  /** Phone number (null clears the field) */
1558
1637
  phone?: string | null;
1559
- /** Company name (null clears the field) */
1638
+ /** @deprecated Accepted for compatibility but ignored. Use the organization entity's name. */
1560
1639
  companyName?: string | null;
1561
1640
  /** Department (null clears the field) */
1562
1641
  department?: string | null;
@@ -1922,6 +2001,7 @@ interface FFIDOAuthUserInfo {
1922
2001
  emailVerified?: boolean | undefined;
1923
2002
  name: string | null;
1924
2003
  picture: string | null;
2004
+ /** @deprecated Individual employer name is retired; always null. Use organizationName. */
1925
2005
  companyName?: string | null | undefined;
1926
2006
  department?: string | null | undefined;
1927
2007
  position?: string | null | undefined;
@@ -2356,7 +2436,9 @@ declare function createFFIDClient(config: FFIDConfig): {
2356
2436
  prompt?: FFIDPrompt;
2357
2437
  }) => string;
2358
2438
  getLogoutUrl: (postLogoutRedirectUri?: string) => string;
2359
- getSignupUrl: (redirectUrl?: string) => string;
2439
+ getSignupUrl: (redirectUrl?: string, options?: {
2440
+ inviteToken?: string;
2441
+ }) => string;
2360
2442
  createError: (code: string, message: string) => FFIDError;
2361
2443
  exchangeCodeForTokens: (code: string, codeVerifier?: string, opts?: ExchangeCodeForTokensOptions) => Promise<FFIDApiResponse<void>>;
2362
2444
  refreshAccessToken: () => Promise<FFIDApiResponse<void>>;