@feelflow/ffid-sdk 7.1.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.
- package/README.md +115 -5
- package/dist/{chunk-CI4N7KJK.cjs → chunk-RNCK5E3Y.cjs} +211 -14
- package/dist/{chunk-Y4TUICQZ.js → chunk-YKBAFPX5.js} +202 -15
- package/dist/components/index.cjs +8 -8
- package/dist/components/index.d.cts +1 -1
- package/dist/components/index.d.ts +1 -1
- package/dist/components/index.js +1 -1
- package/dist/{ffid-client-QNEFA3mv.d.ts → ffid-client-5GAPcRIG.d.ts} +105 -23
- package/dist/{ffid-client-BFgoVPOZ.d.cts → ffid-client-BaEmw7jP.d.cts} +105 -23
- package/dist/{index-BMKhKhzF.d.cts → index-BG7g99pK.d.cts} +1 -0
- package/dist/{index-BMKhKhzF.d.ts → index-BG7g99pK.d.ts} +1 -0
- package/dist/index.cjs +104 -64
- package/dist/index.d.cts +378 -26
- package/dist/index.d.ts +378 -26
- package/dist/index.js +2 -2
- package/dist/server/index.cjs +135 -12
- package/dist/server/index.d.cts +2 -2
- package/dist/server/index.d.ts +2 -2
- package/dist/server/index.js +135 -12
- package/dist/server/test/index.d.cts +1 -1
- package/dist/server/test/index.d.ts +1 -1
- package/dist/webhooks/index.d.cts +55 -6
- package/dist/webhooks/index.d.ts +55 -6
- package/package.json +1 -1
|
@@ -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:
|
|
161
|
+
companyName: null,
|
|
162
162
|
department: raw.department ?? null,
|
|
163
163
|
position: raw.position ?? null,
|
|
164
164
|
phoneNumber: raw.phone_number ?? null,
|
|
@@ -226,13 +226,18 @@ function mapUserinfoSubscriptionToSession(userinfo, serviceCode) {
|
|
|
226
226
|
];
|
|
227
227
|
}
|
|
228
228
|
var JWKS_ENDPOINT = "/.well-known/jwks.json";
|
|
229
|
-
var
|
|
229
|
+
var LEGACY_JWT_ISSUER = "https://id.feelflow.net";
|
|
230
230
|
var JWT_ALGORITHM = "ES256";
|
|
231
231
|
var JWT_CLOCK_TOLERANCE_SECONDS = 30;
|
|
232
|
+
function resolveAcceptedIssuers(baseUrl) {
|
|
233
|
+
const deployedOrigin = new URL(baseUrl).origin;
|
|
234
|
+
return deployedOrigin === LEGACY_JWT_ISSUER ? [LEGACY_JWT_ISSUER] : [deployedOrigin, LEGACY_JWT_ISSUER];
|
|
235
|
+
}
|
|
232
236
|
function createJwtVerifier(deps) {
|
|
233
237
|
const { baseUrl, serviceCode, logger, createError, errorCodes } = deps;
|
|
234
238
|
const jwksUrl = new URL(JWKS_ENDPOINT, baseUrl);
|
|
235
239
|
const jwks = createRemoteJWKSet(jwksUrl);
|
|
240
|
+
const acceptedIssuers = resolveAcceptedIssuers(baseUrl);
|
|
236
241
|
async function verifyJwt(accessToken) {
|
|
237
242
|
if (!accessToken || !accessToken.trim()) {
|
|
238
243
|
return {
|
|
@@ -245,7 +250,7 @@ function createJwtVerifier(deps) {
|
|
|
245
250
|
try {
|
|
246
251
|
const { payload } = await jwtVerify(accessToken, jwks, {
|
|
247
252
|
algorithms: [JWT_ALGORITHM],
|
|
248
|
-
issuer:
|
|
253
|
+
issuer: acceptedIssuers,
|
|
249
254
|
audience: serviceCode,
|
|
250
255
|
clockTolerance: JWT_CLOCK_TOLERANCE_SECONDS
|
|
251
256
|
});
|
|
@@ -462,7 +467,7 @@ function createVerifyAccessToken(deps) {
|
|
|
462
467
|
email_verified: introspectResponse.email_verified,
|
|
463
468
|
name: introspectResponse.name ?? null,
|
|
464
469
|
picture: introspectResponse.picture ?? null,
|
|
465
|
-
company_name:
|
|
470
|
+
company_name: null,
|
|
466
471
|
department: introspectResponse.department ?? null,
|
|
467
472
|
position: introspectResponse.position ?? null,
|
|
468
473
|
phone_number: introspectResponse.phone_number ?? null,
|
|
@@ -713,6 +718,24 @@ function validateSeatChangePreview(preview) {
|
|
|
713
718
|
`SDK: server returned malformed SeatChangePreview \u2014 estimateReason must be one of 'no_stripe_data' | 'custom_pricing' | 'stripe_error' (got ${JSON.stringify(p.estimateReason)})`
|
|
714
719
|
);
|
|
715
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
|
+
}
|
|
716
739
|
if (!Array.isArray(p.lineItems)) {
|
|
717
740
|
throw new FFIDSDKError(
|
|
718
741
|
MALFORMED_SEAT_PREVIEW_CODE,
|
|
@@ -1353,7 +1376,7 @@ var FFID_ORGANIZATION_PROFILE_ERROR_CODES = {
|
|
|
1353
1376
|
};
|
|
1354
1377
|
|
|
1355
1378
|
// src/client/version-check.ts
|
|
1356
|
-
var SDK_VERSION = "
|
|
1379
|
+
var SDK_VERSION = "8.0.0";
|
|
1357
1380
|
var SDK_USER_AGENT = `FFID-SDK/${SDK_VERSION} (TypeScript)`;
|
|
1358
1381
|
var SDK_VERSION_HEADER = "X-FFID-SDK-Version";
|
|
1359
1382
|
function sdkHeaders() {
|
|
@@ -2607,6 +2630,129 @@ function cleanupStateStorage(logger) {
|
|
|
2607
2630
|
}
|
|
2608
2631
|
}
|
|
2609
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
|
+
|
|
2610
2756
|
// src/client/redirect.ts
|
|
2611
2757
|
var OAUTH_AUTHORIZE_ENDPOINT = "/api/v1/oauth/authorize";
|
|
2612
2758
|
var AUTH_LOGOUT_ENDPOINT = "/api/v1/auth/logout";
|
|
@@ -2619,6 +2765,7 @@ var REDIRECT_LOOP_WINDOW_MS = 6e4;
|
|
|
2619
2765
|
var REDIRECT_LOOP_THRESHOLD = 3;
|
|
2620
2766
|
var storageReadFailureLogged = false;
|
|
2621
2767
|
var storageWriteFailureLogged = false;
|
|
2768
|
+
var ssrInviteTokenUnavailableLogged = false;
|
|
2622
2769
|
function isLoopDetectionDegraded() {
|
|
2623
2770
|
return storageReadFailureLogged || storageWriteFailureLogged;
|
|
2624
2771
|
}
|
|
@@ -2797,12 +2944,16 @@ function createRedirectMethods(deps) {
|
|
|
2797
2944
|
}
|
|
2798
2945
|
if (options?.screenHint === SCREEN_HINT_SIGNUP) {
|
|
2799
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
|
+
}
|
|
2800
2951
|
}
|
|
2801
2952
|
if (options?.prompt) {
|
|
2802
2953
|
params.set("prompt", options.prompt);
|
|
2803
2954
|
}
|
|
2804
2955
|
const authorizeUrl = `${baseUrl}${OAUTH_AUTHORIZE_ENDPOINT}?${params.toString()}`;
|
|
2805
|
-
logger.debug("Redirecting to authorize:", authorizeUrl);
|
|
2956
|
+
logger.debug("Redirecting to authorize:", redactAgencyInviteToken(authorizeUrl));
|
|
2806
2957
|
recordRedirectAttempt(authorizeKey, now, logger);
|
|
2807
2958
|
try {
|
|
2808
2959
|
window.location.href = authorizeUrl;
|
|
@@ -2821,7 +2972,7 @@ function createRedirectMethods(deps) {
|
|
|
2821
2972
|
return redirectToAuthorize(options);
|
|
2822
2973
|
}
|
|
2823
2974
|
const targetUrl = options?.screenHint === SCREEN_HINT_SIGNUP ? getSignupUrl() : getLoginUrl(void 0, options?.prompt ? { prompt: options.prompt } : void 0);
|
|
2824
|
-
logger.debug("Redirecting to auth page:", targetUrl);
|
|
2975
|
+
logger.debug("Redirecting to auth page:", redactAgencyInviteToken(targetUrl));
|
|
2825
2976
|
window.location.href = targetUrl;
|
|
2826
2977
|
return { success: true };
|
|
2827
2978
|
}
|
|
@@ -2838,7 +2989,7 @@ function createRedirectMethods(deps) {
|
|
|
2838
2989
|
const promptSuffix = options?.prompt ? `&prompt=${encodeURIComponent(options.prompt)}` : "";
|
|
2839
2990
|
return `${baseUrl}/login?redirect=${encodeURIComponent(redirect)}&service=${encodeURIComponent(serviceCode)}${promptSuffix}`;
|
|
2840
2991
|
}
|
|
2841
|
-
function getSignupUrl(redirectUrl) {
|
|
2992
|
+
function getSignupUrl(redirectUrl, options) {
|
|
2842
2993
|
let redirect;
|
|
2843
2994
|
if (redirectUrl != null) {
|
|
2844
2995
|
redirect = redirectUrl;
|
|
@@ -2848,7 +2999,22 @@ function createRedirectMethods(deps) {
|
|
|
2848
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");
|
|
2849
3000
|
redirect = "";
|
|
2850
3001
|
}
|
|
2851
|
-
|
|
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
|
+
);
|
|
2852
3018
|
}
|
|
2853
3019
|
function getLogoutUrl(postLogoutRedirectUri) {
|
|
2854
3020
|
const url = new URL(`${baseUrl}${AUTH_LOGOUT_ENDPOINT}`);
|
|
@@ -2864,7 +3030,7 @@ function createRedirectMethods(deps) {
|
|
|
2864
3030
|
return { success: false, error: "SSR \u74B0\u5883\u3067\u306F\u30EA\u30C0\u30A4\u30EC\u30AF\u30C8\u3067\u304D\u307E\u305B\u3093" };
|
|
2865
3031
|
}
|
|
2866
3032
|
const logoutUrl = getLogoutUrl(postLogoutRedirectUri);
|
|
2867
|
-
logger.debug("Redirecting to logout:", logoutUrl);
|
|
3033
|
+
logger.debug("Redirecting to logout:", redactAgencyInviteToken(logoutUrl));
|
|
2868
3034
|
window.location.href = logoutUrl;
|
|
2869
3035
|
return { success: true };
|
|
2870
3036
|
}
|
|
@@ -3477,11 +3643,11 @@ var EFFECTIVE_SUBSCRIPTION_STATUSES = [
|
|
|
3477
3643
|
"expired"
|
|
3478
3644
|
];
|
|
3479
3645
|
var PAST_DUE_GRACE_PERIOD_DAYS = 7;
|
|
3480
|
-
var
|
|
3481
|
-
var
|
|
3482
|
-
var
|
|
3646
|
+
var HOURS_PER_DAY2 = 24;
|
|
3647
|
+
var MINUTES_PER_HOUR2 = 60;
|
|
3648
|
+
var SECONDS_PER_MINUTE2 = 60;
|
|
3483
3649
|
var MS_PER_SECOND2 = 1e3;
|
|
3484
|
-
var MS_PER_DAY =
|
|
3650
|
+
var MS_PER_DAY = HOURS_PER_DAY2 * MINUTES_PER_HOUR2 * SECONDS_PER_MINUTE2 * MS_PER_SECOND2;
|
|
3485
3651
|
|
|
3486
3652
|
// src/subscriptions/compute-effective-status.ts
|
|
3487
3653
|
function isExpired(isoTimestamp, nowMs) {
|
|
@@ -3634,6 +3800,18 @@ var FFID_ERROR_CODES = {
|
|
|
3634
3800
|
TOKEN_EXPIRED: "TOKEN_EXPIRED",
|
|
3635
3801
|
STATE_MISMATCH_ERROR: "STATE_MISMATCH_ERROR"
|
|
3636
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
|
+
};
|
|
3637
3815
|
|
|
3638
3816
|
// src/client/ffid-client.ts
|
|
3639
3817
|
var UNAUTHORIZED_STATUS2 = 401;
|
|
@@ -4317,6 +4495,15 @@ function FFIDProvider({
|
|
|
4317
4495
|
}),
|
|
4318
4496
|
[serviceCode, scope, apiBaseUrl, debug, logger, authMode, clientId, timeout, tokenStore]
|
|
4319
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]);
|
|
4320
4507
|
const refresh = useCallback(async () => {
|
|
4321
4508
|
client.logger.debug("Refreshing session...");
|
|
4322
4509
|
try {
|
|
@@ -6292,4 +6479,4 @@ function FFIDInquiryForm({
|
|
|
6292
6479
|
);
|
|
6293
6480
|
}
|
|
6294
6481
|
|
|
6295
|
-
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
|
|
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
|
|
9
|
+
get: function () { return chunkRNCK5E3Y_cjs.FFIDAnnouncementBadge; }
|
|
10
10
|
});
|
|
11
11
|
Object.defineProperty(exports, "FFIDAnnouncementList", {
|
|
12
12
|
enumerable: true,
|
|
13
|
-
get: function () { return
|
|
13
|
+
get: function () { return chunkRNCK5E3Y_cjs.FFIDAnnouncementList; }
|
|
14
14
|
});
|
|
15
15
|
Object.defineProperty(exports, "FFIDInquiryForm", {
|
|
16
16
|
enumerable: true,
|
|
17
|
-
get: function () { return
|
|
17
|
+
get: function () { return chunkRNCK5E3Y_cjs.FFIDInquiryForm; }
|
|
18
18
|
});
|
|
19
19
|
Object.defineProperty(exports, "FFIDLoginButton", {
|
|
20
20
|
enumerable: true,
|
|
21
|
-
get: function () { return
|
|
21
|
+
get: function () { return chunkRNCK5E3Y_cjs.FFIDLoginButton; }
|
|
22
22
|
});
|
|
23
23
|
Object.defineProperty(exports, "FFIDOrganizationSwitcher", {
|
|
24
24
|
enumerable: true,
|
|
25
|
-
get: function () { return
|
|
25
|
+
get: function () { return chunkRNCK5E3Y_cjs.FFIDOrganizationSwitcher; }
|
|
26
26
|
});
|
|
27
27
|
Object.defineProperty(exports, "FFIDSubscriptionBadge", {
|
|
28
28
|
enumerable: true,
|
|
29
|
-
get: function () { return
|
|
29
|
+
get: function () { return chunkRNCK5E3Y_cjs.FFIDSubscriptionBadge; }
|
|
30
30
|
});
|
|
31
31
|
Object.defineProperty(exports, "FFIDUserMenu", {
|
|
32
32
|
enumerable: true,
|
|
33
|
-
get: function () { return
|
|
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-
|
|
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-
|
|
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';
|
package/dist/components/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { FFIDAnnouncementBadge, FFIDAnnouncementList, FFIDInquiryForm, FFIDLoginButton, FFIDOrganizationSwitcher, FFIDSubscriptionBadge, FFIDUserMenu } from '../chunk-
|
|
1
|
+
export { FFIDAnnouncementBadge, FFIDAnnouncementList, FFIDInquiryForm, FFIDLoginButton, FFIDOrganizationSwitcher, FFIDSubscriptionBadge, FFIDUserMenu } from '../chunk-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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
|
623
|
-
*
|
|
624
|
-
*
|
|
625
|
-
* - `false`: immediate change. `effectiveDate` is always `null
|
|
626
|
-
*
|
|
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
|
-
/**
|
|
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
|
|
663
|
-
*
|
|
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`
|
|
668
|
-
*
|
|
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
|
-
*
|
|
680
|
-
* (
|
|
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
|
|
758
|
+
/** true when proratedAmount is a local estimate rather than Stripe invoice/proration data */
|
|
688
759
|
isEstimate: boolean;
|
|
689
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
|
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>>;
|