@feelflow/ffid-sdk 8.1.0 → 10.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.
Files changed (32) hide show
  1. package/README.md +1010 -960
  2. package/dist/{FFIDCookieLink-Bwr0FBrV.d.cts → FFIDCookieLink-yt5SUr-W.d.cts} +47 -3
  3. package/dist/{FFIDCookieLink-Bwr0FBrV.d.ts → FFIDCookieLink-yt5SUr-W.d.ts} +47 -3
  4. package/dist/{chunk-CPXTIRB6.cjs → chunk-2LU2F3F2.cjs} +283 -11
  5. package/dist/{chunk-P5A6C5SK.js → chunk-OXW6QSTO.js} +270 -12
  6. package/dist/{chunk-XELXXL2F.cjs → chunk-RZCQ22GH.cjs} +29 -9
  7. package/dist/{chunk-RHBZHSRD.js → chunk-S2AEBJRX.js} +29 -9
  8. package/dist/components/index.cjs +8 -8
  9. package/dist/components/index.d.cts +1 -1
  10. package/dist/components/index.d.ts +1 -1
  11. package/dist/components/index.js +1 -1
  12. package/dist/consent/index.cjs +63 -63
  13. package/dist/consent/index.d.cts +2 -2
  14. package/dist/consent/index.d.ts +2 -2
  15. package/dist/consent/index.js +1 -1
  16. package/dist/{ffid-client-B0cORHac.d.ts → ffid-client-5uxXRgIO.d.ts} +686 -376
  17. package/dist/{ffid-client-6aLs9Fqc.d.cts → ffid-client-CumygXXz.d.cts} +686 -376
  18. package/dist/{index-BG7g99pK.d.ts → index-KjewcNn1.d.cts} +813 -2
  19. package/dist/{index-BG7g99pK.d.cts → index-KjewcNn1.d.ts} +813 -2
  20. package/dist/index.cjs +151 -95
  21. package/dist/index.d.cts +116 -505
  22. package/dist/index.d.ts +116 -505
  23. package/dist/index.js +3 -3
  24. package/dist/server/index.cjs +253 -11
  25. package/dist/server/index.d.cts +2 -2
  26. package/dist/server/index.d.ts +2 -2
  27. package/dist/server/index.js +253 -11
  28. package/dist/server/test/index.d.cts +1 -1
  29. package/dist/server/test/index.d.ts +1 -1
  30. package/dist/webhooks/index.d.cts +112 -7
  31. package/dist/webhooks/index.d.ts +112 -7
  32. package/package.json +131 -131
@@ -987,10 +987,23 @@ interface FFIDCookieBannerProps {
987
987
  * `useFFIDConsent()` instead of overriding the label.
988
988
  */
989
989
  closeLabel?: ReactNode;
990
+ /**
991
+ * Accessible name of the banner dialog (`aria-label`). Default: "Cookie 同意".
992
+ *
993
+ * Screen readers announce this before any visible copy, so localize it together with
994
+ * `message` and the button labels. Pass a non-empty string: an empty `aria-label`
995
+ * leaves the dialog without an accessible name.
996
+ */
997
+ ariaLabel?: string;
998
+ /**
999
+ * Label of the preferences button while the preference center is loading.
1000
+ * Default: "読み込み中...".
1001
+ */
1002
+ preferencesLoadingLabel?: ReactNode;
990
1003
  /** Custom root style. */
991
1004
  style?: CSSProperties;
992
1005
  }
993
- declare function FFIDCookieBanner({ className, classNames, message, acceptAllLabel, necessaryOnlyLabel, preferencesLabel, closeLabel, style, }: FFIDCookieBannerProps): React.JSX.Element | null;
1006
+ declare function FFIDCookieBanner({ className, classNames, message, acceptAllLabel, necessaryOnlyLabel, preferencesLabel, closeLabel, ariaLabel, preferencesLoadingLabel, style, }: FFIDCookieBannerProps): React.JSX.Element | null;
994
1007
 
995
1008
  /**
996
1009
  * `<FFIDCookieSettings>` — preference center modal.
@@ -1001,6 +1014,7 @@ declare function FFIDCookieBanner({ className, classNames, message, acceptAllLab
1001
1014
  *
1002
1015
  * Refs: docs/02-design/COOKIE_CONSENT.md §6.2 + §6.3
1003
1016
  */
1017
+
1004
1018
  interface FFIDCookieSettingsClassNames {
1005
1019
  root?: string;
1006
1020
  backdrop?: string;
@@ -1014,10 +1028,40 @@ interface FFIDCookieSettingsProps {
1014
1028
  classNames?: FFIDCookieSettingsClassNames;
1015
1029
  /** Open/close override; defaults to the context's `isPreferencesOpen`. */
1016
1030
  open?: boolean;
1017
- /** When true, also expose a "すべて撤回" button. Default: true. */
1031
+ /** When true, also expose the withdraw-all button (label: `withdrawLabel`). Default: true. */
1018
1032
  showWithdrawButton?: boolean;
1033
+ /**
1034
+ * Heading of the modal. Default: "Cookie 設定". Also the default accessible name of
1035
+ * the dialog unless `ariaLabel` is given, so pass a non-empty string: `title=""`
1036
+ * without `ariaLabel` leaves the dialog without an accessible name.
1037
+ */
1038
+ title?: string;
1039
+ /**
1040
+ * Accessible name of the dialog (`aria-label`). Default: `title`, then "Cookie 設定".
1041
+ * Pass a non-empty string: an empty `aria-label` leaves the dialog without an
1042
+ * accessible name.
1043
+ */
1044
+ ariaLabel?: string;
1045
+ /** Save button label. Default: "変更を保存". */
1046
+ saveLabel?: ReactNode;
1047
+ /** Save button label while the save is in flight. Default: "保存中...". */
1048
+ savingLabel?: ReactNode;
1049
+ /** Withdraw button label. Default: "すべて撤回". */
1050
+ withdrawLabel?: ReactNode;
1051
+ /** Withdraw button label while the withdrawal is in flight. Default: "撤回中...". */
1052
+ withdrawingLabel?: ReactNode;
1053
+ /**
1054
+ * Category names keyed by category code. Each given entry wins over both the category
1055
+ * master fetched from FFID and the built-in fallback shown before the master loads.
1056
+ * Missing codes keep those. Under `FFIDAnalyticsProvider` the master is always Japanese
1057
+ * (its consent client keeps the default `defaultLang: 'ja'`), so non-Japanese pages
1058
+ * need this prop.
1059
+ */
1060
+ categoryLabels?: Partial<Record<FFIDConsentCategoryCode, string>>;
1061
+ /** Category descriptions keyed by category code. Same precedence as `categoryLabels`. */
1062
+ categoryDescriptions?: Partial<Record<FFIDConsentCategoryCode, string>>;
1019
1063
  }
1020
- declare function FFIDCookieSettings({ className, classNames, open, showWithdrawButton, }: FFIDCookieSettingsProps): React.JSX.Element | null;
1064
+ declare function FFIDCookieSettings({ className, classNames, open, showWithdrawButton, title, ariaLabel, saveLabel, savingLabel, withdrawLabel, withdrawingLabel, categoryLabels, categoryDescriptions, }: FFIDCookieSettingsProps): React.JSX.Element | null;
1021
1065
 
1022
1066
  /**
1023
1067
  * `<FFIDCookieLink>` — footer link that opens the preference center.
@@ -987,10 +987,23 @@ interface FFIDCookieBannerProps {
987
987
  * `useFFIDConsent()` instead of overriding the label.
988
988
  */
989
989
  closeLabel?: ReactNode;
990
+ /**
991
+ * Accessible name of the banner dialog (`aria-label`). Default: "Cookie 同意".
992
+ *
993
+ * Screen readers announce this before any visible copy, so localize it together with
994
+ * `message` and the button labels. Pass a non-empty string: an empty `aria-label`
995
+ * leaves the dialog without an accessible name.
996
+ */
997
+ ariaLabel?: string;
998
+ /**
999
+ * Label of the preferences button while the preference center is loading.
1000
+ * Default: "読み込み中...".
1001
+ */
1002
+ preferencesLoadingLabel?: ReactNode;
990
1003
  /** Custom root style. */
991
1004
  style?: CSSProperties;
992
1005
  }
993
- declare function FFIDCookieBanner({ className, classNames, message, acceptAllLabel, necessaryOnlyLabel, preferencesLabel, closeLabel, style, }: FFIDCookieBannerProps): React.JSX.Element | null;
1006
+ declare function FFIDCookieBanner({ className, classNames, message, acceptAllLabel, necessaryOnlyLabel, preferencesLabel, closeLabel, ariaLabel, preferencesLoadingLabel, style, }: FFIDCookieBannerProps): React.JSX.Element | null;
994
1007
 
995
1008
  /**
996
1009
  * `<FFIDCookieSettings>` — preference center modal.
@@ -1001,6 +1014,7 @@ declare function FFIDCookieBanner({ className, classNames, message, acceptAllLab
1001
1014
  *
1002
1015
  * Refs: docs/02-design/COOKIE_CONSENT.md §6.2 + §6.3
1003
1016
  */
1017
+
1004
1018
  interface FFIDCookieSettingsClassNames {
1005
1019
  root?: string;
1006
1020
  backdrop?: string;
@@ -1014,10 +1028,40 @@ interface FFIDCookieSettingsProps {
1014
1028
  classNames?: FFIDCookieSettingsClassNames;
1015
1029
  /** Open/close override; defaults to the context's `isPreferencesOpen`. */
1016
1030
  open?: boolean;
1017
- /** When true, also expose a "すべて撤回" button. Default: true. */
1031
+ /** When true, also expose the withdraw-all button (label: `withdrawLabel`). Default: true. */
1018
1032
  showWithdrawButton?: boolean;
1033
+ /**
1034
+ * Heading of the modal. Default: "Cookie 設定". Also the default accessible name of
1035
+ * the dialog unless `ariaLabel` is given, so pass a non-empty string: `title=""`
1036
+ * without `ariaLabel` leaves the dialog without an accessible name.
1037
+ */
1038
+ title?: string;
1039
+ /**
1040
+ * Accessible name of the dialog (`aria-label`). Default: `title`, then "Cookie 設定".
1041
+ * Pass a non-empty string: an empty `aria-label` leaves the dialog without an
1042
+ * accessible name.
1043
+ */
1044
+ ariaLabel?: string;
1045
+ /** Save button label. Default: "変更を保存". */
1046
+ saveLabel?: ReactNode;
1047
+ /** Save button label while the save is in flight. Default: "保存中...". */
1048
+ savingLabel?: ReactNode;
1049
+ /** Withdraw button label. Default: "すべて撤回". */
1050
+ withdrawLabel?: ReactNode;
1051
+ /** Withdraw button label while the withdrawal is in flight. Default: "撤回中...". */
1052
+ withdrawingLabel?: ReactNode;
1053
+ /**
1054
+ * Category names keyed by category code. Each given entry wins over both the category
1055
+ * master fetched from FFID and the built-in fallback shown before the master loads.
1056
+ * Missing codes keep those. Under `FFIDAnalyticsProvider` the master is always Japanese
1057
+ * (its consent client keeps the default `defaultLang: 'ja'`), so non-Japanese pages
1058
+ * need this prop.
1059
+ */
1060
+ categoryLabels?: Partial<Record<FFIDConsentCategoryCode, string>>;
1061
+ /** Category descriptions keyed by category code. Same precedence as `categoryLabels`. */
1062
+ categoryDescriptions?: Partial<Record<FFIDConsentCategoryCode, string>>;
1019
1063
  }
1020
- declare function FFIDCookieSettings({ className, classNames, open, showWithdrawButton, }: FFIDCookieSettingsProps): React.JSX.Element | null;
1064
+ declare function FFIDCookieSettings({ className, classNames, open, showWithdrawButton, title, ariaLabel, saveLabel, savingLabel, withdrawLabel, withdrawingLabel, categoryLabels, categoryDescriptions, }: FFIDCookieSettingsProps): React.JSX.Element | null;
1021
1065
 
1022
1066
  /**
1023
1067
  * `<FFIDCookieLink>` — footer link that opens the preference center.
@@ -228,18 +228,16 @@ function mapUserinfoSubscriptionToSession(userinfo, serviceCode) {
228
228
  ];
229
229
  }
230
230
  var JWKS_ENDPOINT = "/.well-known/jwks.json";
231
- var LEGACY_JWT_ISSUER = "https://id.feelflow.net";
232
231
  var JWT_ALGORITHM = "ES256";
233
232
  var JWT_CLOCK_TOLERANCE_SECONDS = 30;
234
- function resolveAcceptedIssuers(baseUrl) {
235
- const deployedOrigin = new URL(baseUrl).origin;
236
- return deployedOrigin === LEGACY_JWT_ISSUER ? [LEGACY_JWT_ISSUER] : [deployedOrigin, LEGACY_JWT_ISSUER];
233
+ function resolveAcceptedIssuer(baseUrl) {
234
+ return new URL(baseUrl).origin;
237
235
  }
238
236
  function createJwtVerifier(deps) {
239
237
  const { baseUrl, serviceCode, logger, createError, errorCodes } = deps;
240
238
  const jwksUrl = new URL(JWKS_ENDPOINT, baseUrl);
241
239
  const jwks = jose.createRemoteJWKSet(jwksUrl);
242
- const acceptedIssuers = resolveAcceptedIssuers(baseUrl);
240
+ const acceptedIssuer = resolveAcceptedIssuer(baseUrl);
243
241
  async function verifyJwt(accessToken) {
244
242
  if (!accessToken || !accessToken.trim()) {
245
243
  return {
@@ -252,7 +250,7 @@ function createJwtVerifier(deps) {
252
250
  try {
253
251
  const { payload } = await jose.jwtVerify(accessToken, jwks, {
254
252
  algorithms: [JWT_ALGORITHM],
255
- issuer: acceptedIssuers,
253
+ issuer: acceptedIssuer,
256
254
  audience: serviceCode,
257
255
  clockTolerance: JWT_CLOCK_TOLERANCE_SECONDS
258
256
  });
@@ -1378,7 +1376,7 @@ var FFID_ORGANIZATION_PROFILE_ERROR_CODES = {
1378
1376
  };
1379
1377
 
1380
1378
  // src/client/version-check.ts
1381
- var SDK_VERSION = "8.1.0";
1379
+ var SDK_VERSION = "10.0.0";
1382
1380
  var SDK_USER_AGENT = `FFID-SDK/${SDK_VERSION} (TypeScript)`;
1383
1381
  var SDK_VERSION_HEADER = "X-FFID-SDK-Version";
1384
1382
  function sdkHeaders() {
@@ -1734,8 +1732,11 @@ function createProvisioningMethods(deps) {
1734
1732
  }
1735
1733
 
1736
1734
  // src/client/seat-methods.ts
1735
+ function seatSummaryEndpoint(subscriptionId) {
1736
+ return `/api/v1/subscriptions/ext/${encodeURIComponent(subscriptionId)}/seats`;
1737
+ }
1737
1738
  function seatsEndpoint(subscriptionId) {
1738
- return `/api/v1/subscriptions/ext/${encodeURIComponent(subscriptionId)}/seats/assignments`;
1739
+ return `${seatSummaryEndpoint(subscriptionId)}/assignments`;
1739
1740
  }
1740
1741
  function createSeatMethods(deps) {
1741
1742
  const { fetchWithAuth, createError } = deps;
@@ -1745,6 +1746,12 @@ function createSeatMethods(deps) {
1745
1746
  }
1746
1747
  return fetchWithAuth(seatsEndpoint(params.subscriptionId));
1747
1748
  }
1749
+ async function getSeats(subscriptionId) {
1750
+ if (!subscriptionId || !subscriptionId.trim()) {
1751
+ return { error: createError("VALIDATION_ERROR", "subscriptionId \u306F\u5FC5\u9808\u3067\u3059") };
1752
+ }
1753
+ return fetchWithAuth(seatSummaryEndpoint(subscriptionId));
1754
+ }
1748
1755
  async function assignSeats(params) {
1749
1756
  if (!params.subscriptionId) {
1750
1757
  return { error: createError("VALIDATION_ERROR", "subscriptionId \u306F\u5FC5\u9808\u3067\u3059") };
@@ -1769,7 +1776,7 @@ function createSeatMethods(deps) {
1769
1776
  { method: "DELETE" }
1770
1777
  );
1771
1778
  }
1772
- return { listSeatAssignments, assignSeats, unassignSeat };
1779
+ return { listSeatAssignments, getSeats, assignSeats, unassignSeat };
1773
1780
  }
1774
1781
 
1775
1782
  // src/client/profile-methods.ts
@@ -3634,6 +3641,225 @@ function createInquiryMethods(deps) {
3634
3641
  return { create };
3635
3642
  }
3636
3643
 
3644
+ // src/types/credits.ts
3645
+ var FFID_CREDITS_LOW_THRESHOLD_PERCENT = 20;
3646
+ var FFID_CREDITS_CONSUME_MIN_AMOUNT = 1;
3647
+ var FFID_CREDITS_CONSUME_MAX_AMOUNT = 1e6;
3648
+ var FFID_CREDITS_METADATA_MAX_BYTES = 4096;
3649
+ var FFID_CREDITS_HISTORY_MIN_LIMIT = 1;
3650
+ var FFID_CREDITS_HISTORY_MAX_LIMIT = 100;
3651
+ var FFID_CREDITS_HISTORY_DEFAULT_LIMIT = 20;
3652
+ var FFID_CREDIT_PACK_MIN_QUANTITY = 1;
3653
+ var FFID_CREDIT_PACK_MAX_QUANTITY = 10;
3654
+ var FFID_CREDITS_IDEMPOTENCY_KEY_PATTERN = /^[A-Za-z0-9._:-]{8,128}$/;
3655
+ var FFID_CREDITS_FEATURE_PATTERN = /^[a-z0-9][a-z0-9._-]{0,63}$/;
3656
+ var FFID_CREDIT_HISTORY_TYPES = ["grant", "consumption", "revocation"];
3657
+ var FFID_CREDIT_GRANT_KINDS = ["monthly", "manual", "pack"];
3658
+
3659
+ // src/client/credits-methods.ts
3660
+ var EXT_SUBSCRIPTIONS_PATH = "/api/v1/subscriptions/ext";
3661
+ var CREDIT_PACKS_ENDPOINT = `${EXT_SUBSCRIPTIONS_PATH}/credits/packs`;
3662
+ var CREDIT_PACK_CHECKOUT_ENDPOINT = `${CREDIT_PACKS_ENDPOINT}/checkout`;
3663
+ var PORTAL_SUBSCRIPTION_PATH = "/subscriptions";
3664
+ var BUY_CREDITS_QUERY = { key: "purchase", value: "credits" };
3665
+ var ORG_QUERY_KEY = "org";
3666
+ var FFID_CONTRACT_ID_PREFIX = "ffid:";
3667
+ var STRIPE_SUBSCRIPTION_ID_PREFIX = "sub_";
3668
+ var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
3669
+ var VALIDATION_ERROR = "VALIDATION_ERROR";
3670
+ var FORBIDDEN = "FORBIDDEN";
3671
+ var utf8Encoder = new TextEncoder();
3672
+ function creditsEndpoint(subscriptionId, action) {
3673
+ return `${EXT_SUBSCRIPTIONS_PATH}/${encodeURIComponent(subscriptionId)}/credits/${action}`;
3674
+ }
3675
+ function isPlainObject2(value) {
3676
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3677
+ }
3678
+ function isIntegerInRange(value, min, max) {
3679
+ return typeof value === "number" && Number.isInteger(value) && value >= min && value <= max;
3680
+ }
3681
+ function createCreditsMethods(deps) {
3682
+ const { fetchWithAuth, createError, authMode, baseUrl, serviceCode, logger } = deps;
3683
+ function validationError(field, message) {
3684
+ return { error: { ...createError(VALIDATION_ERROR, message), details: { field } } };
3685
+ }
3686
+ function forbidden(reason, message) {
3687
+ return { error: { ...createError(FORBIDDEN, message), details: { reason } } };
3688
+ }
3689
+ function missingSubscriptionId(subscriptionId) {
3690
+ return subscriptionId && subscriptionId.trim() ? null : validationError("subscriptionId", "subscriptionId \u306F\u5FC5\u9808\u3067\u3059");
3691
+ }
3692
+ async function getBalance(subscriptionId) {
3693
+ const invalid = missingSubscriptionId(subscriptionId);
3694
+ if (invalid) return invalid;
3695
+ return fetchWithAuth(creditsEndpoint(subscriptionId, "balance"));
3696
+ }
3697
+ async function listHistory(subscriptionId, options = {}) {
3698
+ const invalid = missingSubscriptionId(subscriptionId);
3699
+ if (invalid) return invalid;
3700
+ const query = new URLSearchParams();
3701
+ if (options.cursor !== void 0) query.set("cursor", options.cursor);
3702
+ if (options.limit !== void 0) {
3703
+ if (!isIntegerInRange(options.limit, FFID_CREDITS_HISTORY_MIN_LIMIT, FFID_CREDITS_HISTORY_MAX_LIMIT)) {
3704
+ return validationError(
3705
+ "limit",
3706
+ `limit \u306F ${FFID_CREDITS_HISTORY_MIN_LIMIT}\u301C${FFID_CREDITS_HISTORY_MAX_LIMIT} \u306E\u6574\u6570\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044`
3707
+ );
3708
+ }
3709
+ query.set("limit", String(options.limit));
3710
+ }
3711
+ if (options.type !== void 0) {
3712
+ if (!FFID_CREDIT_HISTORY_TYPES.includes(options.type)) {
3713
+ return validationError("type", `type \u306F ${FFID_CREDIT_HISTORY_TYPES.join(" / ")} \u306E\u3044\u305A\u308C\u304B\u3067\u3059`);
3714
+ }
3715
+ query.set("type", options.type);
3716
+ }
3717
+ const qs = query.toString();
3718
+ return fetchWithAuth(
3719
+ `${creditsEndpoint(subscriptionId, "history")}${qs ? `?${qs}` : ""}`
3720
+ );
3721
+ }
3722
+ async function listPacks(packServiceCode) {
3723
+ const resolved = packServiceCode ?? (authMode === "service-key" ? void 0 : serviceCode);
3724
+ if (resolved !== void 0 && !resolved.trim()) {
3725
+ return validationError("serviceCode", "serviceCode \u3092\u6307\u5B9A\u3059\u308B\u5834\u5408\u3001\u7A7A\u6587\u5B57\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093");
3726
+ }
3727
+ const qs = resolved === void 0 ? "" : `?${new URLSearchParams({ serviceCode: resolved }).toString()}`;
3728
+ return fetchWithAuth(`${CREDIT_PACKS_ENDPOINT}${qs}`);
3729
+ }
3730
+ async function createPackCheckout(params) {
3731
+ if (authMode !== "token") {
3732
+ return forbidden(
3733
+ "bearer_required",
3734
+ "createPackCheckout \u306F token \u30E2\u30FC\u30C9\uFF08Bearer\uFF09\u3067\u306E\u307F\u5229\u7528\u3067\u304D\u307E\u3059\u3002\u8CFC\u5165\u8005\u306E\u8A18\u9332\u306B\u5B9F\u30E6\u30FC\u30B6\u30FC\u304C\u5FC5\u8981\u306A\u305F\u3081\u3067\u3059"
3735
+ );
3736
+ }
3737
+ const invalid = missingSubscriptionId(params.subscriptionId);
3738
+ if (invalid) return invalid;
3739
+ for (const field of ["packCode", "successUrl", "cancelUrl"]) {
3740
+ if (!params[field] || !params[field].trim()) {
3741
+ return validationError(field, `${field} \u306F\u5FC5\u9808\u3067\u3059`);
3742
+ }
3743
+ }
3744
+ if (params.quantity !== void 0 && !isIntegerInRange(params.quantity, FFID_CREDIT_PACK_MIN_QUANTITY, FFID_CREDIT_PACK_MAX_QUANTITY)) {
3745
+ return validationError(
3746
+ "quantity",
3747
+ `quantity \u306F ${FFID_CREDIT_PACK_MIN_QUANTITY}\u301C${FFID_CREDIT_PACK_MAX_QUANTITY} \u306E\u6574\u6570\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044`
3748
+ );
3749
+ }
3750
+ const body = {
3751
+ subscriptionId: params.subscriptionId,
3752
+ packCode: params.packCode,
3753
+ successUrl: params.successUrl,
3754
+ cancelUrl: params.cancelUrl
3755
+ };
3756
+ if (params.quantity !== void 0) body.quantity = params.quantity;
3757
+ return fetchWithAuth(CREDIT_PACK_CHECKOUT_ENDPOINT, {
3758
+ method: "POST",
3759
+ body: JSON.stringify(body)
3760
+ });
3761
+ }
3762
+ function validateConsume(params) {
3763
+ const invalid = missingSubscriptionId(params.subscriptionId);
3764
+ if (invalid) return invalid;
3765
+ if (!isIntegerInRange(params.amount, FFID_CREDITS_CONSUME_MIN_AMOUNT, FFID_CREDITS_CONSUME_MAX_AMOUNT)) {
3766
+ return validationError(
3767
+ "amount",
3768
+ `amount \u306F ${FFID_CREDITS_CONSUME_MIN_AMOUNT}\u301C${FFID_CREDITS_CONSUME_MAX_AMOUNT} \u306E\u6574\u6570\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044`
3769
+ );
3770
+ }
3771
+ if (typeof params.idempotencyKey !== "string" || !FFID_CREDITS_IDEMPOTENCY_KEY_PATTERN.test(params.idempotencyKey)) {
3772
+ return validationError(
3773
+ "idempotencyKey",
3774
+ "idempotencyKey \u306E\u5F62\u5F0F\u304C\u4E0D\u6B63\u3067\u3059\uFF08\u82F1\u6570\u5B57\u3068 . _ : - \u306E 8\u301C128 \u6587\u5B57\u3002\u63A8\u5968\u306F feature:requestId\uFF09"
3775
+ );
3776
+ }
3777
+ if (typeof params.feature !== "string" || !FFID_CREDITS_FEATURE_PATTERN.test(params.feature)) {
3778
+ return validationError("feature", "feature \u306E\u5F62\u5F0F\u304C\u4E0D\u6B63\u3067\u3059\uFF08\u5C0F\u6587\u5B57\u82F1\u6570\u5B57\u3067\u59CB\u307E\u308B\u3001\u5C0F\u6587\u5B57\u82F1\u6570\u5B57\u3068 . _ - \u306E 1\u301C64 \u6587\u5B57\uFF09");
3779
+ }
3780
+ if (params.metadata !== void 0) {
3781
+ if (!isPlainObject2(params.metadata)) {
3782
+ return validationError("metadata", "metadata \u306F JSON \u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059");
3783
+ }
3784
+ let serialized;
3785
+ try {
3786
+ serialized = JSON.stringify(params.metadata);
3787
+ } catch {
3788
+ return validationError("metadata", "metadata \u306F JSON \u306B\u5909\u63DB\u3067\u304D\u308B\u5024\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\uFF08BigInt\u30FB\u5FAA\u74B0\u53C2\u7167\u306F\u4E0D\u53EF\uFF09");
3789
+ }
3790
+ if (utf8Encoder.encode(serialized).byteLength > FFID_CREDITS_METADATA_MAX_BYTES) {
3791
+ return validationError("metadata", `metadata \u306F ${FFID_CREDITS_METADATA_MAX_BYTES} \u30D0\u30A4\u30C8\u4EE5\u4E0B\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059`);
3792
+ }
3793
+ }
3794
+ if (params.userId !== void 0 && !UUID_PATTERN.test(params.userId)) {
3795
+ return validationError("userId", "userId \u306F\u6709\u52B9\u306AUUID\u5F62\u5F0F\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059");
3796
+ }
3797
+ return null;
3798
+ }
3799
+ async function consume(params) {
3800
+ if (authMode !== "service-key") {
3801
+ return forbidden(
3802
+ "api_key_required",
3803
+ "credits.consume \u306F service-key \u8A8D\u8A3C\uFF08X-Service-Api-Key\uFF09\u3067\u306E\u307F\u5229\u7528\u3067\u304D\u307E\u3059\u3002\u6D88\u8CBB\u306E\u5831\u544A\u306F\u30B5\u30FC\u30D3\u30B9\u306E\u30D0\u30C3\u30AF\u30A8\u30F3\u30C9\u304B\u3089\u884C\u3063\u3066\u304F\u3060\u3055\u3044"
3804
+ );
3805
+ }
3806
+ const invalid = validateConsume(params);
3807
+ if (invalid) return invalid;
3808
+ const body = {
3809
+ amount: params.amount,
3810
+ idempotencyKey: params.idempotencyKey,
3811
+ feature: params.feature
3812
+ };
3813
+ if (params.metadata !== void 0) body.metadata = params.metadata;
3814
+ if (params.userId !== void 0) body.userId = params.userId;
3815
+ return fetchWithAuth(creditsEndpoint(params.subscriptionId, "consume"), {
3816
+ method: "POST",
3817
+ body: JSON.stringify(body)
3818
+ });
3819
+ }
3820
+ function getBuyCreditsUrl(params) {
3821
+ const raw = params.subscriptionId?.trim() ?? "";
3822
+ if (raw.startsWith(STRIPE_SUBSCRIPTION_ID_PREFIX)) {
3823
+ return validationError(
3824
+ "subscriptionId",
3825
+ "getBuyCreditsUrl \u306B\u306F\u5951\u7D04\u306E UUID\uFF08\u307E\u305F\u306F ffid:<uuid>\uFF09\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002Stripe \u306E sub_... \u306F\u4F7F\u3048\u307E\u305B\u3093"
3826
+ );
3827
+ }
3828
+ const uuid = raw.startsWith(FFID_CONTRACT_ID_PREFIX) ? raw.slice(FFID_CONTRACT_ID_PREFIX.length) : raw;
3829
+ if (!UUID_PATTERN.test(uuid)) {
3830
+ return validationError("subscriptionId", "subscriptionId \u306F\u5951\u7D04\u306E UUID\uFF08\u307E\u305F\u306F ffid:<uuid>\uFF09\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059");
3831
+ }
3832
+ const query = new URLSearchParams({ [BUY_CREDITS_QUERY.key]: BUY_CREDITS_QUERY.value });
3833
+ if (params.orgId !== void 0 && params.orgId.trim()) {
3834
+ query.set(ORG_QUERY_KEY, params.orgId);
3835
+ }
3836
+ return { data: `${baseUrl}${PORTAL_SUBSCRIPTION_PATH}/${encodeURIComponent(uuid)}?${query.toString()}` };
3837
+ }
3838
+ function redirectToBuyCredits(params) {
3839
+ const result = getBuyCreditsUrl(params);
3840
+ if (result.error) {
3841
+ logger.warn("redirectToBuyCredits:", result.error.message);
3842
+ return { success: false, error: result.error.message };
3843
+ }
3844
+ if (typeof window === "undefined") {
3845
+ logger.warn("SSR \u74B0\u5883\u3067\u306F\u30EA\u30C0\u30A4\u30EC\u30AF\u30C8\u3067\u304D\u307E\u305B\u3093");
3846
+ return { success: false, error: "SSR \u74B0\u5883\u3067\u306F\u30EA\u30C0\u30A4\u30EC\u30AF\u30C8\u3067\u304D\u307E\u305B\u3093" };
3847
+ }
3848
+ logger.debug("Redirecting to buy credits:", result.data);
3849
+ window.location.href = result.data;
3850
+ return { success: true };
3851
+ }
3852
+ return {
3853
+ getBalance,
3854
+ listHistory,
3855
+ listPacks,
3856
+ createPackCheckout,
3857
+ consume,
3858
+ getBuyCreditsUrl,
3859
+ redirectToBuyCredits
3860
+ };
3861
+ }
3862
+
3637
3863
  // src/subscriptions/types.ts
3638
3864
  var EFFECTIVE_SUBSCRIPTION_STATUSES = [
3639
3865
  "active",
@@ -3767,7 +3993,12 @@ function toServiceAccessDecision(response, allowGrace) {
3767
3993
  planCode: response.planCode,
3768
3994
  currentPeriodEnd: response.currentPeriodEnd,
3769
3995
  gracePeriodEndsAt: response.gracePeriodEndsAt ?? null,
3770
- reactivatable: response.reactivatable ?? false
3996
+ reactivatable: response.reactivatable ?? false,
3997
+ // Informational pass-through (10.0.0). Deliberately not part of hasAccess /
3998
+ // denialReason: credits and the member limit never gate access (CREDITS_API §1-5).
3999
+ ...response.pricingModel !== void 0 ? { pricingModel: response.pricingModel } : {},
4000
+ ...response.credits !== void 0 ? { credits: response.credits } : {},
4001
+ ...response.memberLimitReached !== void 0 ? { memberLimitReached: response.memberLimitReached } : {}
3771
4002
  };
3772
4003
  }
3773
4004
  function failClosedServiceAccessDecision(params, error) {
@@ -3814,6 +4045,19 @@ var FFID_SUBSCRIPTION_ERROR_CODES = {
3814
4045
  FREE_DOWNGRADE_NOT_ALLOWED: "FREE_DOWNGRADE_NOT_ALLOWED",
3815
4046
  PAYMENT_METHOD_REQUIRED: "PAYMENT_METHOD_REQUIRED"
3816
4047
  };
4048
+ var FFID_CREDITS_ERROR_CODES = {
4049
+ CREDITS_INSUFFICIENT: "CREDITS_INSUFFICIENT",
4050
+ CREDITS_IDEMPOTENCY_CONFLICT: "CREDITS_IDEMPOTENCY_CONFLICT",
4051
+ CREDITS_NOT_APPLICABLE: "CREDITS_NOT_APPLICABLE",
4052
+ CREDITS_BALANCE_CAP_EXCEEDED: "CREDITS_BALANCE_CAP_EXCEEDED",
4053
+ SEAT_BILLING_NOT_APPLICABLE: "SEAT_BILLING_NOT_APPLICABLE",
4054
+ MEMBER_LIMIT_REACHED: "MEMBER_LIMIT_REACHED",
4055
+ CREDIT_PACK_NOT_FOUND: "CREDIT_PACK_NOT_FOUND",
4056
+ CREDIT_PACK_REGION_NOT_SUPPORTED: "CREDIT_PACK_REGION_NOT_SUPPORTED",
4057
+ CREDIT_PACK_STRIPE_NOT_SYNCED: "CREDIT_PACK_STRIPE_NOT_SYNCED",
4058
+ CREDIT_PACK_STRIPE_OUT_OF_SYNC: "CREDIT_PACK_STRIPE_OUT_OF_SYNC",
4059
+ CREDIT_PACK_CHANGED: "CREDIT_PACK_CHANGED"
4060
+ };
3817
4061
 
3818
4062
  // src/client/ffid-client.ts
3819
4063
  var UNAUTHORIZED_STATUS2 = 401;
@@ -4141,7 +4385,7 @@ function createFFIDClient(config) {
4141
4385
  fetchWithAuth,
4142
4386
  createError
4143
4387
  });
4144
- const { listSeatAssignments, assignSeats, unassignSeat } = createSeatMethods({
4388
+ const { listSeatAssignments, getSeats, assignSeats, unassignSeat } = createSeatMethods({
4145
4389
  fetchWithAuth,
4146
4390
  createError
4147
4391
  });
@@ -4248,6 +4492,14 @@ function createFFIDClient(config) {
4248
4492
  fetchWithAuth,
4249
4493
  createError
4250
4494
  });
4495
+ const credits = createCreditsMethods({
4496
+ fetchWithAuth,
4497
+ createError,
4498
+ authMode,
4499
+ baseUrl,
4500
+ serviceCode: config.serviceCode,
4501
+ logger
4502
+ });
4251
4503
  const verifyAccessToken = createVerifyAccessToken({
4252
4504
  authMode,
4253
4505
  baseUrl,
@@ -4303,6 +4555,7 @@ function createFFIDClient(config) {
4303
4555
  getInvoice,
4304
4556
  retryPayment,
4305
4557
  listSeatAssignments,
4558
+ getSeats,
4306
4559
  assignSeats,
4307
4560
  unassignSeat,
4308
4561
  listPlans,
@@ -4313,13 +4566,16 @@ function createFFIDClient(config) {
4313
4566
  cancelSubscription,
4314
4567
  cancelPendingDowngrade,
4315
4568
  previewPlanChange,
4569
+ /** @deprecated 10.0.0: per_seat (praxis) only; removed in 11.0.0 */
4316
4570
  previewSeatChange,
4317
4571
  verifyAccessToken,
4318
4572
  getSubscribeUrl,
4319
4573
  redirectToSubscribe,
4320
4574
  getChangePlanUrl,
4321
4575
  redirectToChangePlan,
4576
+ /** @deprecated 10.0.0: per_seat (praxis) only; removed in 11.0.0 */
4322
4577
  getChangeSeatsUrl,
4578
+ /** @deprecated 10.0.0: per_seat (praxis) only; removed in 11.0.0 */
4323
4579
  redirectToChangeSeats,
4324
4580
  getRecoverPaymentUrl,
4325
4581
  redirectToRecoverPayment,
@@ -4335,6 +4591,8 @@ function createFFIDClient(config) {
4335
4591
  newsletter,
4336
4592
  /** Inquiry methods (create) */
4337
4593
  inquiry,
4594
+ /** Credits methods for flat plans (consume / balance / history / packs) */
4595
+ credits,
4338
4596
  /** Token store (token mode only) */
4339
4597
  tokenStore,
4340
4598
  /**
@@ -6506,6 +6764,20 @@ exports.FFID_ANNOUNCEMENTS_ERROR_CODES = FFID_ANNOUNCEMENTS_ERROR_CODES;
6506
6764
  exports.FFID_CATALOG_ENVIRONMENTS = FFID_CATALOG_ENVIRONMENTS;
6507
6765
  exports.FFID_CATALOG_PUBLICATION_STATUSES = FFID_CATALOG_PUBLICATION_STATUSES;
6508
6766
  exports.FFID_COMPANY_SIZE_CODES = FFID_COMPANY_SIZE_CODES;
6767
+ exports.FFID_CREDITS_CONSUME_MAX_AMOUNT = FFID_CREDITS_CONSUME_MAX_AMOUNT;
6768
+ exports.FFID_CREDITS_CONSUME_MIN_AMOUNT = FFID_CREDITS_CONSUME_MIN_AMOUNT;
6769
+ exports.FFID_CREDITS_ERROR_CODES = FFID_CREDITS_ERROR_CODES;
6770
+ exports.FFID_CREDITS_FEATURE_PATTERN = FFID_CREDITS_FEATURE_PATTERN;
6771
+ exports.FFID_CREDITS_HISTORY_DEFAULT_LIMIT = FFID_CREDITS_HISTORY_DEFAULT_LIMIT;
6772
+ exports.FFID_CREDITS_HISTORY_MAX_LIMIT = FFID_CREDITS_HISTORY_MAX_LIMIT;
6773
+ exports.FFID_CREDITS_HISTORY_MIN_LIMIT = FFID_CREDITS_HISTORY_MIN_LIMIT;
6774
+ exports.FFID_CREDITS_IDEMPOTENCY_KEY_PATTERN = FFID_CREDITS_IDEMPOTENCY_KEY_PATTERN;
6775
+ exports.FFID_CREDITS_LOW_THRESHOLD_PERCENT = FFID_CREDITS_LOW_THRESHOLD_PERCENT;
6776
+ exports.FFID_CREDITS_METADATA_MAX_BYTES = FFID_CREDITS_METADATA_MAX_BYTES;
6777
+ exports.FFID_CREDIT_GRANT_KINDS = FFID_CREDIT_GRANT_KINDS;
6778
+ exports.FFID_CREDIT_HISTORY_TYPES = FFID_CREDIT_HISTORY_TYPES;
6779
+ exports.FFID_CREDIT_PACK_MAX_QUANTITY = FFID_CREDIT_PACK_MAX_QUANTITY;
6780
+ exports.FFID_CREDIT_PACK_MIN_QUANTITY = FFID_CREDIT_PACK_MIN_QUANTITY;
6509
6781
  exports.FFID_ERROR_CODES = FFID_ERROR_CODES;
6510
6782
  exports.FFID_INDUSTRY_CODES = FFID_INDUSTRY_CODES;
6511
6783
  exports.FFID_INQUIRY_ACQUISITION_SOURCES = FFID_INQUIRY_ACQUISITION_SOURCES;