@feelflow/ffid-sdk 12.0.0 → 12.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -22,6 +22,35 @@ pnpm add @feelflow/ffid-sdk
22
22
 
23
23
  ## 統合ガイド
24
24
 
25
+ ### ニュースレター登録・目的別停止(12.2.0)
26
+
27
+ 登録はサーバー上の `service-key` クライアントで行います。Service API Key を
28
+ ブラウザへ渡さず、公開フォームのCAPTCHAと配信同意をBFFで検証してください。
29
+
30
+ ```ts
31
+ // 一つの登録操作に一つのUUID。応答が失われた再操作では同じキーを保持する。
32
+ const idempotencyKey = crypto.randomUUID()
33
+ const result = await client.newsletter.subscribe({
34
+ email: 'reader@example.com',
35
+ types: ['general'],
36
+ source: 'website-newsletter',
37
+ locale: 'ja',
38
+ }, { idempotencyKey })
39
+
40
+ // tokenはメール内の不透明な値。ログへ出さない。
41
+ await client.newsletter.unsubscribe({ token, type: 'general' })
42
+ ```
43
+
44
+ `requiresConfirmation` は確認メール送信済み、`confirmationEmailPending` は未送信、
45
+ `requiresSignIn` は既存会員の設定案内です。後二つを「登録完了」にしないでください。
46
+ 未送信フラグは自動再送の予約を意味しません。登録内容を変えるときと、確定した結果の
47
+ 後で新しい登録を依頼するときは新しいキーを使います。SDKが自動再送することはありません。
48
+ 独立ニュースレター側の処理中・結果不明なreceiptの回復は、運用上の確認が必要です。
49
+ 配信停止の `type` を省略すると既存の全目的停止を維持します。
50
+ キー付き登録には、独立ownerへ転送する更新済みFFID互換APIが必要です。旧処理モードでは
51
+ 書込み・メール送信前に拒否し、SDKは返却された `requestId` の一致を確認します。
52
+ 互換APIの配備と独立化の切替を確認してから、BFFでキー付き登録を有効にしてください。
53
+
25
54
  サービスを FFID Platform に統合する詳細なガイドは **[Integration Guide](./docs/INTEGRATION_GUIDE.md)** を参照してください。OAuth フロー、フロントエンド/バックエンド実装パターン、セキュリティチェックリスト、アンチパターン集を網羅しています。
26
55
 
27
56
  ### Cookie 同意管理基盤 (v5.0.0+)
@@ -888,6 +917,9 @@ import { createFFIDAnnouncementsClient } from '@feelflow/ffid-sdk/announcements'
888
917
 
889
918
  // Webhook 署名検証・ハンドラー(※ Node.js crypto が必要)
890
919
  import { createFFIDWebhookHandler, verifyWebhookSignature } from '@feelflow/ffid-sdk/webhooks'
920
+
921
+ // 製品側 DB 用の短命トークン(ES256)
922
+ import { createDbTokenMinter } from '@feelflow/ffid-sdk/server'
891
923
  ```
892
924
 
893
925
  > **Note**: `webhooks` モジュールは Node.js の `crypto` モジュールと `Buffer` を使用します。Cloudflare Workers で利用する場合は [`nodejs_compat` 互換フラグ](https://developers.cloudflare.com/workers/runtime-apis/nodejs/)を有効にしてください。
@@ -940,6 +972,36 @@ module.exports = {
940
972
 
941
973
  > **Note**: サーバーサイドで bundler を使わずに実行する場合、**subpath exports(`@feelflow/ffid-sdk/legal` 等)を使用すれば** external 設定なしで React がインストールされていなくても動作します。メインエントリ(`@feelflow/ffid-sdk`)を ESM で import する場合は、モジュールグラフが静的に解決されるため React が必要です。
942
974
 
975
+ ### 製品側 DB 用トークン(createDbTokenMinter)
976
+
977
+ FFID でログインする製品が、自分の Supabase に supabase-js でアクセスするときの短命トークン(ES256)を作ります(12.1.0 以降)。サーバー専用で、React には依存しません。製品のサーバーが FFID のセッションを確認した後に呼び、RLS は `private.current_ffid_user_id()` から利用者を読みます。
978
+
979
+ ```typescript
980
+ import { createDbTokenMinter } from '@feelflow/ffid-sdk/server'
981
+
982
+ // プロセスで 1 回
983
+ const minter = createDbTokenMinter({
984
+ signingKey: process.env.SUPABASE_JWT_SIGNING_KEY?.trim() ?? '', // 私有 JWK(JSON 文字列)
985
+ })
986
+
987
+ // リクエストごと
988
+ const { token, expiresAt } = await minter.mint({
989
+ userId: session.user.id, // sub(UUID)
990
+ organizationId: session.organization?.id, // 任意。organization_id claim
991
+ })
992
+ // createClient(url, key, { global: { headers: { Authorization: `Bearer ${token}` } } })
993
+ ```
994
+
995
+ - 設定: `signingKey`(必須。私有 P-256 JWK で `kid` が必要)/ `kid`(任意の上書き)/ `ttlSeconds`(既定 300、上限 900)/ `issuer`(任意)
996
+ - 入力: `userId`、`organizationId`、`claims`(製品固有。`sub` / `role` / `aud` / `iss` / `iat` / `exp` / `nbf` / `jti` / `organization_id` は予約済み)
997
+ - エラー: `FFIDDbTokenError`。`code` は `INVALID_SIGNING_KEY` / `INVALID_TTL` / `INVALID_USER_ID` / `INVALID_ORGANIZATION_ID` / `RESERVED_CLAIM` / `INVALID_CLOCK`。取り込み失敗は message に原因、`cause` に元のエラー(鍵材料は含まれません)
998
+ - `supabase gen signing-key --algorithm ES256` の出力(`use` / `key_ops` / `ext` 付き)をそのまま渡せます。SDK が EC パラメータだけを取り込みます
999
+ - 環境変数は読みません。鍵は設定として渡します。追加の依存はありません
1000
+
1001
+ > **Warning**: 私有鍵とトークンは **ブラウザに渡さず、ログにも出さないでください**。鍵が無いときは 503 で止め、service role に黙って切り替えないでください。
1002
+
1003
+ 鍵の登録手順、RLS の SQL、確認項目は [DB_USER_IDENTITY_STANDARD.md](../../docs/06-reference/DB_USER_IDENTITY_STANDARD.md) を参照してください。
1004
+
943
1005
  ### `peerDependencies` は optional です
944
1006
 
945
1007
  SDK の `package.json` で `react` / `react-dom` は `optional: true` に設定済みです(利用者側での設定は不要)。React をインストールしなくても `npm install` 時に warning は発生しません。
@@ -28,6 +28,13 @@ interface FFIDAgencyLogger {
28
28
  error: (...args: unknown[]) => void;
29
29
  }
30
30
  type FFIDAgencyStatus = 'active' | 'suspended' | 'inactive';
31
+ /**
32
+ * 代理店の課金方式。
33
+ *
34
+ * `'markup'` は非推奨(FFID #6507)。既存データの値として残している。`updateBilling()` は
35
+ * 互換のため今も `'markup'` を受け付けるが、新しく設定しないこと。受け取った値を網羅的に
36
+ * 扱う箇所では引き続き `'markup'` を扱うこと。
37
+ */
31
38
  type FFIDAgencyBillingType = 'direct' | 'consolidated' | 'markup';
32
39
  type FFIDAgencyMemberRole = 'owner' | 'admin' | 'sales' | 'support';
33
40
  type FFIDAgencyMemberStatus = 'active' | 'invited' | 'suspended';
@@ -99,7 +106,9 @@ interface FFIDAgencyOrganization {
99
106
  organizationSlug: string;
100
107
  billingOverride: FFIDAgencyBillingOverride;
101
108
  commissionRate: number | null;
109
+ /** @deprecated markup 課金方式は非推奨(FFID #6507)。帳票用の既存値を読むだけで、更新する API は無い。 */
102
110
  markupRate: number | null;
111
+ /** @deprecated markup 課金方式は非推奨(FFID #6507)。帳票用の既存値を読むだけで、更新する API は無い。 */
103
112
  markupFixed: number | null;
104
113
  /** 組織単位の卸割引率 override(%、0〜100)。null = 代理店デフォルトに従う。 */
105
114
  wholesaleDiscountRate: number | null;
@@ -192,7 +201,9 @@ interface FFIDAgencyBillingConfig {
192
201
  stripeCustomerId: string | null;
193
202
  invoiceEmail: string | null;
194
203
  paymentTerms: number | null;
204
+ /** @deprecated markup 課金方式は非推奨(FFID #6507)。帳票用の既存値を読むだけで、更新する API は無い。 */
195
205
  defaultMarkupRate: number | null;
206
+ /** @deprecated markup 課金方式は非推奨(FFID #6507)。帳票用の既存値を読むだけで、更新する API は無い。 */
196
207
  defaultMarkupFixed: number | null;
197
208
  /** 代理店デフォルト卸割引率(%、0〜100)。null = 未設定(定価請求)。 */
198
209
  wholesaleDiscountRate: number | null;
@@ -201,7 +212,15 @@ interface FFIDUpdateAgencyBillingRequest {
201
212
  billingType?: FFIDAgencyBillingType;
202
213
  invoiceEmail?: string;
203
214
  paymentTerms?: number;
215
+ /**
216
+ * @deprecated FFID はこの項目を受け付けず、送っても黙って捨てる(FFID #6507)。
217
+ * markup 課金方式は非推奨で、値を更新する手段は無い。
218
+ */
204
219
  defaultMarkupRate?: number;
220
+ /**
221
+ * @deprecated FFID はこの項目を受け付けず、送っても黙って捨てる(FFID #6507)。
222
+ * markup 課金方式は非推奨で、値を更新する手段は無い。
223
+ */
205
224
  defaultMarkupFixed?: number;
206
225
  /** 代理店デフォルト卸割引率(%、0〜100)。null で未設定に戻す。 */
207
226
  wholesaleDiscountRate?: number | null;
@@ -28,6 +28,13 @@ interface FFIDAgencyLogger {
28
28
  error: (...args: unknown[]) => void;
29
29
  }
30
30
  type FFIDAgencyStatus = 'active' | 'suspended' | 'inactive';
31
+ /**
32
+ * 代理店の課金方式。
33
+ *
34
+ * `'markup'` は非推奨(FFID #6507)。既存データの値として残している。`updateBilling()` は
35
+ * 互換のため今も `'markup'` を受け付けるが、新しく設定しないこと。受け取った値を網羅的に
36
+ * 扱う箇所では引き続き `'markup'` を扱うこと。
37
+ */
31
38
  type FFIDAgencyBillingType = 'direct' | 'consolidated' | 'markup';
32
39
  type FFIDAgencyMemberRole = 'owner' | 'admin' | 'sales' | 'support';
33
40
  type FFIDAgencyMemberStatus = 'active' | 'invited' | 'suspended';
@@ -99,7 +106,9 @@ interface FFIDAgencyOrganization {
99
106
  organizationSlug: string;
100
107
  billingOverride: FFIDAgencyBillingOverride;
101
108
  commissionRate: number | null;
109
+ /** @deprecated markup 課金方式は非推奨(FFID #6507)。帳票用の既存値を読むだけで、更新する API は無い。 */
102
110
  markupRate: number | null;
111
+ /** @deprecated markup 課金方式は非推奨(FFID #6507)。帳票用の既存値を読むだけで、更新する API は無い。 */
103
112
  markupFixed: number | null;
104
113
  /** 組織単位の卸割引率 override(%、0〜100)。null = 代理店デフォルトに従う。 */
105
114
  wholesaleDiscountRate: number | null;
@@ -192,7 +201,9 @@ interface FFIDAgencyBillingConfig {
192
201
  stripeCustomerId: string | null;
193
202
  invoiceEmail: string | null;
194
203
  paymentTerms: number | null;
204
+ /** @deprecated markup 課金方式は非推奨(FFID #6507)。帳票用の既存値を読むだけで、更新する API は無い。 */
195
205
  defaultMarkupRate: number | null;
206
+ /** @deprecated markup 課金方式は非推奨(FFID #6507)。帳票用の既存値を読むだけで、更新する API は無い。 */
196
207
  defaultMarkupFixed: number | null;
197
208
  /** 代理店デフォルト卸割引率(%、0〜100)。null = 未設定(定価請求)。 */
198
209
  wholesaleDiscountRate: number | null;
@@ -201,7 +212,15 @@ interface FFIDUpdateAgencyBillingRequest {
201
212
  billingType?: FFIDAgencyBillingType;
202
213
  invoiceEmail?: string;
203
214
  paymentTerms?: number;
215
+ /**
216
+ * @deprecated FFID はこの項目を受け付けず、送っても黙って捨てる(FFID #6507)。
217
+ * markup 課金方式は非推奨で、値を更新する手段は無い。
218
+ */
204
219
  defaultMarkupRate?: number;
220
+ /**
221
+ * @deprecated FFID はこの項目を受け付けず、送っても黙って捨てる(FFID #6507)。
222
+ * markup 課金方式は非推奨で、値を更新する手段は無い。
223
+ */
205
224
  defaultMarkupFixed?: number;
206
225
  /** 代理店デフォルト卸割引率(%、0〜100)。null で未設定に戻す。 */
207
226
  wholesaleDiscountRate?: number | null;
@@ -1,5 +1,6 @@
1
1
  import { createContext, useState, useRef, useEffect, useMemo, useCallback, useContext } from 'react';
2
2
  import { createRemoteJWKSet, jwtVerify, errors } from 'jose';
3
+ import { z } from 'zod';
3
4
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
5
 
5
6
  // src/constants.ts
@@ -577,7 +578,8 @@ function createBillingMethods(deps) {
577
578
  subscriptionId: params.subscriptionId,
578
579
  successUrl: params.successUrl,
579
580
  cancelUrl: params.cancelUrl,
580
- ...params.planId ? { planId: params.planId } : {}
581
+ ...params.planId ? { planId: params.planId } : {},
582
+ ...params.quantity !== void 0 ? { quantity: params.quantity } : {}
581
583
  })
582
584
  }
583
585
  );
@@ -1390,7 +1392,7 @@ var FFID_ORGANIZATION_PROFILE_ERROR_CODES = {
1390
1392
  };
1391
1393
 
1392
1394
  // src/client/version-check.ts
1393
- var SDK_VERSION = "12.0.0";
1395
+ var SDK_VERSION = "12.2.0";
1394
1396
  var SDK_USER_AGENT = `FFID-SDK/${SDK_VERSION} (TypeScript)`;
1395
1397
  var SDK_VERSION_HEADER = "X-FFID-SDK-Version";
1396
1398
  function sdkHeaders() {
@@ -3477,24 +3479,45 @@ function createContractWizardMethods(deps) {
3477
3479
  };
3478
3480
  }
3479
3481
 
3482
+ // src/newsletter/types.ts
3483
+ var FFID_NEWSLETTER_TYPES = ["inquiry_followup", "general"];
3484
+ var FFID_NEWSLETTER_DISPATCH_MAX_RECIPIENTS = 1e3;
3485
+
3480
3486
  // src/newsletter/ffid-newsletter-client.ts
3481
3487
  var EXT_SUBSCRIBE_ENDPOINT2 = "/api/v1/ext/newsletter/subscribe";
3482
3488
  var CONFIRM_ENDPOINT = "/api/newsletter/confirm";
3483
3489
  var UNSUBSCRIBE_ENDPOINT = "/api/newsletter/unsubscribe";
3484
3490
  var DISPATCH_ENDPOINT = "/api/v1/newsletter/dispatch";
3491
+ var PUBLIC_TOKEN_TIMEOUT_MS = 3e4;
3492
+ var subscribeResponseSchema = z.object({
3493
+ ok: z.literal(true),
3494
+ requiresConfirmation: z.boolean(),
3495
+ requiresSignIn: z.boolean(),
3496
+ confirmationEmailPending: z.boolean(),
3497
+ requestId: z.uuid().optional()
3498
+ }).refine(
3499
+ (value) => [
3500
+ value.requiresConfirmation,
3501
+ value.requiresSignIn,
3502
+ value.confirmationEmailPending
3503
+ ].filter(Boolean).length <= 1
3504
+ );
3485
3505
  function trimOrEmpty(s) {
3486
3506
  return typeof s === "string" ? s.trim() : "";
3487
3507
  }
3488
3508
  async function postPublic(url, init, opts) {
3489
3509
  let response;
3490
3510
  try {
3491
- response = await fetch(url, init);
3492
- } catch (err) {
3511
+ response = await fetch(url, {
3512
+ ...init,
3513
+ credentials: "omit",
3514
+ redirect: "error",
3515
+ cache: "no-store",
3516
+ signal: AbortSignal.timeout(PUBLIC_TOKEN_TIMEOUT_MS)
3517
+ });
3518
+ } catch {
3493
3519
  return {
3494
- error: opts.createError(
3495
- "NETWORK_ERROR",
3496
- err instanceof Error ? err.message : "\u30CD\u30C3\u30C8\u30EF\u30FC\u30AF\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"
3497
- )
3520
+ error: opts.createError("NETWORK_ERROR", opts.fallbackMessage)
3498
3521
  };
3499
3522
  }
3500
3523
  if (!response.ok) {
@@ -3521,7 +3544,7 @@ async function postPublic(url, init, opts) {
3521
3544
  }
3522
3545
  function createNewsletterMethods(deps) {
3523
3546
  const { fetchWithAuth, baseUrl, createError } = deps;
3524
- async function subscribe(params) {
3547
+ async function subscribe(params, options = {}) {
3525
3548
  const email = trimOrEmpty(params.email);
3526
3549
  const source = trimOrEmpty(params.source);
3527
3550
  if (!email) {
@@ -3538,10 +3561,19 @@ function createNewsletterMethods(deps) {
3538
3561
  )
3539
3562
  };
3540
3563
  }
3541
- return fetchWithAuth(
3564
+ if (options.idempotencyKey !== void 0 && !z.uuid().safeParse(options.idempotencyKey).success) {
3565
+ return {
3566
+ error: createError(
3567
+ "INVALID_IDEMPOTENCY_KEY",
3568
+ "idempotencyKey \u306B\u306F UUID \u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044"
3569
+ )
3570
+ };
3571
+ }
3572
+ const response = await fetchWithAuth(
3542
3573
  EXT_SUBSCRIBE_ENDPOINT2,
3543
3574
  {
3544
3575
  method: "POST",
3576
+ ...options.idempotencyKey ? { headers: { "Idempotency-Key": options.idempotencyKey } } : {},
3545
3577
  body: JSON.stringify({
3546
3578
  email,
3547
3579
  types: params.types,
@@ -3550,6 +3582,23 @@ function createNewsletterMethods(deps) {
3550
3582
  })
3551
3583
  }
3552
3584
  );
3585
+ if (response.error) return response;
3586
+ const parsed = subscribeResponseSchema.safeParse(response.data);
3587
+ if (!parsed.success)
3588
+ return {
3589
+ error: createError(
3590
+ "PARSE_ERROR",
3591
+ "\u30CB\u30E5\u30FC\u30B9\u30EC\u30BF\u30FC\u767B\u9332\u306E\u5FDC\u7B54\u3092\u78BA\u8A8D\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F"
3592
+ )
3593
+ };
3594
+ if (options.idempotencyKey && parsed.data.requestId !== options.idempotencyKey)
3595
+ return {
3596
+ error: createError(
3597
+ "PARSE_ERROR",
3598
+ "\u30CB\u30E5\u30FC\u30B9\u30EC\u30BF\u30FC\u767B\u9332\u306E\u30EA\u30AF\u30A8\u30B9\u30C8\u8B58\u5225\u5B50\u3092\u78BA\u8A8D\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F"
3599
+ )
3600
+ };
3601
+ return { data: parsed.data };
3553
3602
  }
3554
3603
  async function confirm(params) {
3555
3604
  const token = trimOrEmpty(params.token);
@@ -3577,6 +3626,16 @@ function createNewsletterMethods(deps) {
3577
3626
  }
3578
3627
  const url = new URL(`${baseUrl}${UNSUBSCRIBE_ENDPOINT}`);
3579
3628
  url.searchParams.set("token", token);
3629
+ if (params.type !== void 0) {
3630
+ if (!FFID_NEWSLETTER_TYPES.includes(params.type))
3631
+ return {
3632
+ error: createError(
3633
+ "VALIDATION_ERROR",
3634
+ "\u30CB\u30E5\u30FC\u30B9\u30EC\u30BF\u30FC\u306E\u914D\u4FE1\u7A2E\u5225\u304C\u4E0D\u6B63\u3067\u3059"
3635
+ )
3636
+ };
3637
+ url.searchParams.set("type", params.type);
3638
+ }
3580
3639
  return postPublic(
3581
3640
  url.toString(),
3582
3641
  { method: "POST" },
@@ -4077,7 +4136,9 @@ var FFID_SUBSCRIPTION_ERROR_CODES = {
4077
4136
  SUBSCRIPTION_NOT_CANCELABLE: "SUBSCRIPTION_NOT_CANCELABLE",
4078
4137
  SUBSCRIPTION_ALREADY_CANCELED: "SUBSCRIPTION_ALREADY_CANCELED",
4079
4138
  FREE_DOWNGRADE_NOT_ALLOWED: "FREE_DOWNGRADE_NOT_ALLOWED",
4080
- PAYMENT_METHOD_REQUIRED: "PAYMENT_METHOD_REQUIRED"
4139
+ PAYMENT_METHOD_REQUIRED: "PAYMENT_METHOD_REQUIRED",
4140
+ SEAT_ABOVE_MAX: "SEAT_ABOVE_MAX",
4141
+ SEAT_BELOW_MIN: "SEAT_BELOW_MIN"
4081
4142
  };
4082
4143
  var FFID_CREDITS_ERROR_CODES = {
4083
4144
  CREDITS_INSUFFICIENT: "CREDITS_INSUFFICIENT",
@@ -6865,4 +6926,4 @@ function FFIDInquiryForm({
6865
6926
  );
6866
6927
  }
6867
6928
 
6868
- 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_CREDITS_CONSUME_MAX_AMOUNT, FFID_CREDITS_CONSUME_MIN_AMOUNT, FFID_CREDITS_ERROR_CODES, FFID_CREDITS_FEATURE_PATTERN, FFID_CREDITS_HISTORY_DEFAULT_LIMIT, FFID_CREDITS_HISTORY_MAX_LIMIT, FFID_CREDITS_HISTORY_MIN_LIMIT, FFID_CREDITS_IDEMPOTENCY_KEY_PATTERN, FFID_CREDITS_LOW_THRESHOLD_PERCENT, FFID_CREDITS_METADATA_MAX_BYTES, FFID_CREDIT_GRANT_KINDS, FFID_CREDIT_HISTORY_TYPES, FFID_CREDIT_PACK_MAX_QUANTITY, FFID_CREDIT_PACK_MIN_QUANTITY, 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_OAUTH_DENIAL_REASONS, 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, getFFIDAuthorizationError, handleRedirectCallback, hasAccessFromUserinfo, hashCanonicalJson, isBlockedFromUserinfo, isFFIDInquiryAcquisitionSource, isFFIDInquiryCategorySite2026, isFFIDOAuthDenialReason, normalizeRedirectUri, readAgencyInviteCookie, readAgencyInviteToken, redactAgencyInviteToken, retrieveCodeVerifier, retrieveState, sha256Hex, storeCodeVerifier, storeState, useFFID, useFFIDAnnouncements, useFFIDContext, useSubscription, validatePublishedCatalog, withSubscription };
6929
+ 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_CREDITS_CONSUME_MAX_AMOUNT, FFID_CREDITS_CONSUME_MIN_AMOUNT, FFID_CREDITS_ERROR_CODES, FFID_CREDITS_FEATURE_PATTERN, FFID_CREDITS_HISTORY_DEFAULT_LIMIT, FFID_CREDITS_HISTORY_MAX_LIMIT, FFID_CREDITS_HISTORY_MIN_LIMIT, FFID_CREDITS_IDEMPOTENCY_KEY_PATTERN, FFID_CREDITS_LOW_THRESHOLD_PERCENT, FFID_CREDITS_METADATA_MAX_BYTES, FFID_CREDIT_GRANT_KINDS, FFID_CREDIT_HISTORY_TYPES, FFID_CREDIT_PACK_MAX_QUANTITY, FFID_CREDIT_PACK_MIN_QUANTITY, 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_NEWSLETTER_DISPATCH_MAX_RECIPIENTS, FFID_NEWSLETTER_TYPES, FFID_OAUTH_DENIAL_REASONS, 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, getFFIDAuthorizationError, handleRedirectCallback, hasAccessFromUserinfo, hashCanonicalJson, isBlockedFromUserinfo, isFFIDInquiryAcquisitionSource, isFFIDInquiryCategorySite2026, isFFIDOAuthDenialReason, normalizeRedirectUri, readAgencyInviteCookie, readAgencyInviteToken, redactAgencyInviteToken, retrieveCodeVerifier, retrieveState, sha256Hex, storeCodeVerifier, storeState, useFFID, useFFIDAnnouncements, useFFIDContext, useSubscription, validatePublishedCatalog, withSubscription };
@@ -2,6 +2,7 @@
2
2
 
3
3
  var react = require('react');
4
4
  var jose = require('jose');
5
+ var zod = require('zod');
5
6
  var jsxRuntime = require('react/jsx-runtime');
6
7
 
7
8
  // src/constants.ts
@@ -579,7 +580,8 @@ function createBillingMethods(deps) {
579
580
  subscriptionId: params.subscriptionId,
580
581
  successUrl: params.successUrl,
581
582
  cancelUrl: params.cancelUrl,
582
- ...params.planId ? { planId: params.planId } : {}
583
+ ...params.planId ? { planId: params.planId } : {},
584
+ ...params.quantity !== void 0 ? { quantity: params.quantity } : {}
583
585
  })
584
586
  }
585
587
  );
@@ -1392,7 +1394,7 @@ var FFID_ORGANIZATION_PROFILE_ERROR_CODES = {
1392
1394
  };
1393
1395
 
1394
1396
  // src/client/version-check.ts
1395
- var SDK_VERSION = "12.0.0";
1397
+ var SDK_VERSION = "12.2.0";
1396
1398
  var SDK_USER_AGENT = `FFID-SDK/${SDK_VERSION} (TypeScript)`;
1397
1399
  var SDK_VERSION_HEADER = "X-FFID-SDK-Version";
1398
1400
  function sdkHeaders() {
@@ -3479,24 +3481,45 @@ function createContractWizardMethods(deps) {
3479
3481
  };
3480
3482
  }
3481
3483
 
3484
+ // src/newsletter/types.ts
3485
+ var FFID_NEWSLETTER_TYPES = ["inquiry_followup", "general"];
3486
+ var FFID_NEWSLETTER_DISPATCH_MAX_RECIPIENTS = 1e3;
3487
+
3482
3488
  // src/newsletter/ffid-newsletter-client.ts
3483
3489
  var EXT_SUBSCRIBE_ENDPOINT2 = "/api/v1/ext/newsletter/subscribe";
3484
3490
  var CONFIRM_ENDPOINT = "/api/newsletter/confirm";
3485
3491
  var UNSUBSCRIBE_ENDPOINT = "/api/newsletter/unsubscribe";
3486
3492
  var DISPATCH_ENDPOINT = "/api/v1/newsletter/dispatch";
3493
+ var PUBLIC_TOKEN_TIMEOUT_MS = 3e4;
3494
+ var subscribeResponseSchema = zod.z.object({
3495
+ ok: zod.z.literal(true),
3496
+ requiresConfirmation: zod.z.boolean(),
3497
+ requiresSignIn: zod.z.boolean(),
3498
+ confirmationEmailPending: zod.z.boolean(),
3499
+ requestId: zod.z.uuid().optional()
3500
+ }).refine(
3501
+ (value) => [
3502
+ value.requiresConfirmation,
3503
+ value.requiresSignIn,
3504
+ value.confirmationEmailPending
3505
+ ].filter(Boolean).length <= 1
3506
+ );
3487
3507
  function trimOrEmpty(s) {
3488
3508
  return typeof s === "string" ? s.trim() : "";
3489
3509
  }
3490
3510
  async function postPublic(url, init, opts) {
3491
3511
  let response;
3492
3512
  try {
3493
- response = await fetch(url, init);
3494
- } catch (err) {
3513
+ response = await fetch(url, {
3514
+ ...init,
3515
+ credentials: "omit",
3516
+ redirect: "error",
3517
+ cache: "no-store",
3518
+ signal: AbortSignal.timeout(PUBLIC_TOKEN_TIMEOUT_MS)
3519
+ });
3520
+ } catch {
3495
3521
  return {
3496
- error: opts.createError(
3497
- "NETWORK_ERROR",
3498
- err instanceof Error ? err.message : "\u30CD\u30C3\u30C8\u30EF\u30FC\u30AF\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"
3499
- )
3522
+ error: opts.createError("NETWORK_ERROR", opts.fallbackMessage)
3500
3523
  };
3501
3524
  }
3502
3525
  if (!response.ok) {
@@ -3523,7 +3546,7 @@ async function postPublic(url, init, opts) {
3523
3546
  }
3524
3547
  function createNewsletterMethods(deps) {
3525
3548
  const { fetchWithAuth, baseUrl, createError } = deps;
3526
- async function subscribe(params) {
3549
+ async function subscribe(params, options = {}) {
3527
3550
  const email = trimOrEmpty(params.email);
3528
3551
  const source = trimOrEmpty(params.source);
3529
3552
  if (!email) {
@@ -3540,10 +3563,19 @@ function createNewsletterMethods(deps) {
3540
3563
  )
3541
3564
  };
3542
3565
  }
3543
- return fetchWithAuth(
3566
+ if (options.idempotencyKey !== void 0 && !zod.z.uuid().safeParse(options.idempotencyKey).success) {
3567
+ return {
3568
+ error: createError(
3569
+ "INVALID_IDEMPOTENCY_KEY",
3570
+ "idempotencyKey \u306B\u306F UUID \u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044"
3571
+ )
3572
+ };
3573
+ }
3574
+ const response = await fetchWithAuth(
3544
3575
  EXT_SUBSCRIBE_ENDPOINT2,
3545
3576
  {
3546
3577
  method: "POST",
3578
+ ...options.idempotencyKey ? { headers: { "Idempotency-Key": options.idempotencyKey } } : {},
3547
3579
  body: JSON.stringify({
3548
3580
  email,
3549
3581
  types: params.types,
@@ -3552,6 +3584,23 @@ function createNewsletterMethods(deps) {
3552
3584
  })
3553
3585
  }
3554
3586
  );
3587
+ if (response.error) return response;
3588
+ const parsed = subscribeResponseSchema.safeParse(response.data);
3589
+ if (!parsed.success)
3590
+ return {
3591
+ error: createError(
3592
+ "PARSE_ERROR",
3593
+ "\u30CB\u30E5\u30FC\u30B9\u30EC\u30BF\u30FC\u767B\u9332\u306E\u5FDC\u7B54\u3092\u78BA\u8A8D\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F"
3594
+ )
3595
+ };
3596
+ if (options.idempotencyKey && parsed.data.requestId !== options.idempotencyKey)
3597
+ return {
3598
+ error: createError(
3599
+ "PARSE_ERROR",
3600
+ "\u30CB\u30E5\u30FC\u30B9\u30EC\u30BF\u30FC\u767B\u9332\u306E\u30EA\u30AF\u30A8\u30B9\u30C8\u8B58\u5225\u5B50\u3092\u78BA\u8A8D\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F"
3601
+ )
3602
+ };
3603
+ return { data: parsed.data };
3555
3604
  }
3556
3605
  async function confirm(params) {
3557
3606
  const token = trimOrEmpty(params.token);
@@ -3579,6 +3628,16 @@ function createNewsletterMethods(deps) {
3579
3628
  }
3580
3629
  const url = new URL(`${baseUrl}${UNSUBSCRIBE_ENDPOINT}`);
3581
3630
  url.searchParams.set("token", token);
3631
+ if (params.type !== void 0) {
3632
+ if (!FFID_NEWSLETTER_TYPES.includes(params.type))
3633
+ return {
3634
+ error: createError(
3635
+ "VALIDATION_ERROR",
3636
+ "\u30CB\u30E5\u30FC\u30B9\u30EC\u30BF\u30FC\u306E\u914D\u4FE1\u7A2E\u5225\u304C\u4E0D\u6B63\u3067\u3059"
3637
+ )
3638
+ };
3639
+ url.searchParams.set("type", params.type);
3640
+ }
3582
3641
  return postPublic(
3583
3642
  url.toString(),
3584
3643
  { method: "POST" },
@@ -4079,7 +4138,9 @@ var FFID_SUBSCRIPTION_ERROR_CODES = {
4079
4138
  SUBSCRIPTION_NOT_CANCELABLE: "SUBSCRIPTION_NOT_CANCELABLE",
4080
4139
  SUBSCRIPTION_ALREADY_CANCELED: "SUBSCRIPTION_ALREADY_CANCELED",
4081
4140
  FREE_DOWNGRADE_NOT_ALLOWED: "FREE_DOWNGRADE_NOT_ALLOWED",
4082
- PAYMENT_METHOD_REQUIRED: "PAYMENT_METHOD_REQUIRED"
4141
+ PAYMENT_METHOD_REQUIRED: "PAYMENT_METHOD_REQUIRED",
4142
+ SEAT_ABOVE_MAX: "SEAT_ABOVE_MAX",
4143
+ SEAT_BELOW_MIN: "SEAT_BELOW_MIN"
4083
4144
  };
4084
4145
  var FFID_CREDITS_ERROR_CODES = {
4085
4146
  CREDITS_INSUFFICIENT: "CREDITS_INSUFFICIENT",
@@ -6913,6 +6974,8 @@ exports.FFID_INQUIRY_CATEGORIES = FFID_INQUIRY_CATEGORIES;
6913
6974
  exports.FFID_INQUIRY_CATEGORIES_SITE_2026 = FFID_INQUIRY_CATEGORIES_SITE_2026;
6914
6975
  exports.FFID_MAX_ORGANIZATION_LOGO_FILE_SIZE_BYTES = FFID_MAX_ORGANIZATION_LOGO_FILE_SIZE_BYTES;
6915
6976
  exports.FFID_MAX_ORGANIZATION_LOGO_FILE_SIZE_MB = FFID_MAX_ORGANIZATION_LOGO_FILE_SIZE_MB;
6977
+ exports.FFID_NEWSLETTER_DISPATCH_MAX_RECIPIENTS = FFID_NEWSLETTER_DISPATCH_MAX_RECIPIENTS;
6978
+ exports.FFID_NEWSLETTER_TYPES = FFID_NEWSLETTER_TYPES;
6916
6979
  exports.FFID_OAUTH_DENIAL_REASONS = FFID_OAUTH_DENIAL_REASONS;
6917
6980
  exports.FFID_ORGANIZATION_PROFILE_ERROR_CODES = FFID_ORGANIZATION_PROFILE_ERROR_CODES;
6918
6981
  exports.FFID_SUBSCRIPTION_ERROR_CODES = FFID_SUBSCRIPTION_ERROR_CODES;
@@ -1,34 +1,34 @@
1
1
  'use strict';
2
2
 
3
- var chunkXRWDTYA7_cjs = require('../chunk-XRWDTYA7.cjs');
3
+ var chunkLT7FQ7BT_cjs = require('../chunk-LT7FQ7BT.cjs');
4
4
 
5
5
 
6
6
 
7
7
  Object.defineProperty(exports, "FFIDAnnouncementBadge", {
8
8
  enumerable: true,
9
- get: function () { return chunkXRWDTYA7_cjs.FFIDAnnouncementBadge; }
9
+ get: function () { return chunkLT7FQ7BT_cjs.FFIDAnnouncementBadge; }
10
10
  });
11
11
  Object.defineProperty(exports, "FFIDAnnouncementList", {
12
12
  enumerable: true,
13
- get: function () { return chunkXRWDTYA7_cjs.FFIDAnnouncementList; }
13
+ get: function () { return chunkLT7FQ7BT_cjs.FFIDAnnouncementList; }
14
14
  });
15
15
  Object.defineProperty(exports, "FFIDInquiryForm", {
16
16
  enumerable: true,
17
- get: function () { return chunkXRWDTYA7_cjs.FFIDInquiryForm; }
17
+ get: function () { return chunkLT7FQ7BT_cjs.FFIDInquiryForm; }
18
18
  });
19
19
  Object.defineProperty(exports, "FFIDLoginButton", {
20
20
  enumerable: true,
21
- get: function () { return chunkXRWDTYA7_cjs.FFIDLoginButton; }
21
+ get: function () { return chunkLT7FQ7BT_cjs.FFIDLoginButton; }
22
22
  });
23
23
  Object.defineProperty(exports, "FFIDOrganizationSwitcher", {
24
24
  enumerable: true,
25
- get: function () { return chunkXRWDTYA7_cjs.FFIDOrganizationSwitcher; }
25
+ get: function () { return chunkLT7FQ7BT_cjs.FFIDOrganizationSwitcher; }
26
26
  });
27
27
  Object.defineProperty(exports, "FFIDSubscriptionBadge", {
28
28
  enumerable: true,
29
- get: function () { return chunkXRWDTYA7_cjs.FFIDSubscriptionBadge; }
29
+ get: function () { return chunkLT7FQ7BT_cjs.FFIDSubscriptionBadge; }
30
30
  });
31
31
  Object.defineProperty(exports, "FFIDUserMenu", {
32
32
  enumerable: true,
33
- get: function () { return chunkXRWDTYA7_cjs.FFIDUserMenu; }
33
+ get: function () { return chunkLT7FQ7BT_cjs.FFIDUserMenu; }
34
34
  });
@@ -1 +1 @@
1
- export { FFIDAnnouncementBadge, FFIDAnnouncementList, FFIDInquiryForm, FFIDLoginButton, FFIDOrganizationSwitcher, FFIDSubscriptionBadge, FFIDUserMenu } from '../chunk-OL7KVPQ4.js';
1
+ export { FFIDAnnouncementBadge, FFIDAnnouncementList, FFIDInquiryForm, FFIDLoginButton, FFIDOrganizationSwitcher, FFIDSubscriptionBadge, FFIDUserMenu } from '../chunk-75ISD4I4.js';
@@ -114,7 +114,7 @@ interface FFIDInquiryCreateResponse {
114
114
  * Newsletter types exposed by the FFID SDK.
115
115
  *
116
116
  * 2-layer newsletter model (#2078):
117
- * - `inquiry_followup`: 問い合わせフォローアップ (Type A)
117
+ * - `inquiry_followup`: 資料請求・お問い合わせ後のご案内 (Type A)
118
118
  * - `general`: 定期ニュースレター (Type B)
119
119
  *
120
120
  * FFID account holders' preferences live in `user_marketing_preferences`;
@@ -133,6 +133,12 @@ interface FFIDNewsletterSubscribeParams {
133
133
  /** ISO 639-1 locale (e.g. 'ja', 'en') */
134
134
  locale?: string;
135
135
  }
136
+ interface FFIDNewsletterSubscribeOptions {
137
+ /** UUID for one intentional registration. Reuse unchanged across retries;
138
+ * create a new key when the registration changes or a new request is intended.
139
+ * Transport-only: sent as Idempotency-Key, never added to the consent body. */
140
+ idempotencyKey?: string | undefined;
141
+ }
136
142
  interface FFIDNewsletterSubscribeResponse {
137
143
  ok: true;
138
144
  /** True when a double opt-in confirmation email was sent */
@@ -140,6 +146,11 @@ interface FFIDNewsletterSubscribeResponse {
140
146
  /** True when the email matched an existing FFID account; the caller
141
147
  * must sign in to update preferences. Preferences are NOT modified. */
142
148
  requiresSignIn: boolean;
149
+ /** True when confirmation could not be sent; subscription is still pending.
150
+ * This flag does not promise an automatic resend. */
151
+ confirmationEmailPending: boolean;
152
+ /** Present only when the independent owner acknowledged a durable registration intent. */
153
+ requestId?: string | undefined;
143
154
  }
144
155
  interface FFIDNewsletterConfirmParams {
145
156
  /** Confirmation token received in the double opt-in email */
@@ -151,6 +162,8 @@ interface FFIDNewsletterConfirmResponse {
151
162
  interface FFIDNewsletterUnsubscribeParams {
152
163
  /** Unsubscribe token received in the newsletter footer / List-Unsubscribe header */
153
164
  token: string;
165
+ /** Stop only the selected purpose. Omission preserves legacy all-purpose links. */
166
+ type?: FFIDNewsletterType | undefined;
154
167
  }
155
168
  interface FFIDNewsletterUnsubscribeResponse {
156
169
  ok: true;
@@ -1164,6 +1177,15 @@ interface FFIDCreateCheckoutParams {
1164
1177
  cancelUrl: string;
1165
1178
  /** Optional plan ID for upgrade or resubscription */
1166
1179
  planId?: string;
1180
+ /**
1181
+ * Seat count after the plan change (#5755). Use together with `planId` (a
1182
+ * different plan); seat-only changes go through the seats API. Omit to carry
1183
+ * over the current seat count. Must fit the target plan's seat range
1184
+ * (fixed-seat plans need their fixed count) and, when decreasing, the seats in
1185
+ * use — otherwise 422 `SEAT_QUANTITY_OUT_OF_RANGE`. Raising the plan while
1186
+ * lowering seats (or vice versa) is 400 `INVALID_UPGRADE_PLAN`.
1187
+ */
1188
+ quantity?: number;
1167
1189
  }
1168
1190
  /**
1169
1191
  * Parameters for creating a billing portal session
@@ -2871,7 +2893,7 @@ declare function createFFIDClient(config: FFIDConfig): {
2871
2893
  }) => Promise<FFIDApiResponse<FFIDOtpVerifyResponse>>;
2872
2894
  /** Newsletter methods (subscribe / confirm / unsubscribe) */
2873
2895
  newsletter: {
2874
- subscribe: (params: FFIDNewsletterSubscribeParams) => Promise<FFIDApiResponse<FFIDNewsletterSubscribeResponse>>;
2896
+ subscribe: (params: FFIDNewsletterSubscribeParams, options?: FFIDNewsletterSubscribeOptions) => Promise<FFIDApiResponse<FFIDNewsletterSubscribeResponse>>;
2875
2897
  confirm: (params: FFIDNewsletterConfirmParams) => Promise<FFIDApiResponse<FFIDNewsletterConfirmResponse>>;
2876
2898
  unsubscribe: (params: FFIDNewsletterUnsubscribeParams) => Promise<FFIDApiResponse<FFIDNewsletterUnsubscribeResponse>>;
2877
2899
  dispatch: (params: FFIDNewsletterDispatchParams) => Promise<FFIDApiResponse<FFIDNewsletterDispatchResponse>>;