@feelflow/ffid-sdk 9.0.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.
@@ -211,178 +211,6 @@ interface FFIDCacheConfig {
211
211
  ttl: number;
212
212
  }
213
213
 
214
- /**
215
- * Canonical service-access types for subscription lifecycle decisions.
216
- */
217
-
218
- /** Subscription status values matching the FFID platform's SubscriptionStatus type */
219
- type FFIDSubscriptionStatus = 'trialing' | 'active' | 'past_due' | 'canceled' | 'pending_invoice' | 'paused' | 'incomplete' | 'incomplete_expired' | 'unpaid';
220
- interface FFIDSubscriptionCheckResponse {
221
- hasActiveSubscription: boolean;
222
- /**
223
- * Canonical access decision returned by FFID's `/subscriptions/ext/check`.
224
- *
225
- * This is the server-side source of truth for service gates. Consumers
226
- * should not recompute access from `currentPeriodEnd`, `past_due_since`, or
227
- * local payment timestamps.
228
- */
229
- hasAccess?: boolean;
230
- /** True when `effectiveStatus === 'past_due_grace'`. */
231
- isGrace?: boolean;
232
- /** True when FFID's canonical effective status denies service access. */
233
- isBlocked?: boolean;
234
- organizationId: string | null;
235
- subscriptionId: string | null;
236
- status: FFIDSubscriptionStatus | null;
237
- planCode: string | null;
238
- currentPeriodEnd: string | null;
239
- /**
240
- * Semantic FFID access-control status. `null` means the organization has no
241
- * subscription row for this service.
242
- */
243
- effectiveStatus?: EffectiveSubscriptionStatus | null;
244
- /**
245
- * ISO timestamp at which `past_due_grace` flips to `blocked`; null outside
246
- * the grace window.
247
- */
248
- gracePeriodEndsAt?: string | null;
249
- /** Whether a canceled subscription can be resumed via a re-subscription flow. */
250
- reactivatable?: boolean;
251
- }
252
- type FFIDServiceAccessFailPolicy = 'failClosed';
253
- type FFIDServiceAccessDenialReason = 'no_subscription' | 'grace_disallowed' | 'blocked' | 'canceled' | 'expired' | 'trial_expired' | 'ffid_unreachable';
254
- interface FFIDCheckServiceAccessParams {
255
- userId?: string;
256
- organizationId: string;
257
- /**
258
- * Whether `past_due_grace` should keep access open.
259
- *
260
- * @default true
261
- */
262
- allowGrace?: boolean;
263
- /**
264
- * Error policy when FFID cannot return a canonical decision.
265
- *
266
- * Currently only `failClosed` is supported: network/server/parse failures
267
- * become `hasAccess=false` decisions with `denialReason='ffid_unreachable'`
268
- * and the root cause in `decision.error`. Treat `hasAccess` as the gate.
269
- */
270
- failPolicy?: FFIDServiceAccessFailPolicy;
271
- }
272
- interface FFIDServiceAccessError {
273
- code: string;
274
- message: string;
275
- details?: unknown;
276
- }
277
- interface FFIDServiceAccessDecision {
278
- hasAccess: boolean;
279
- effectiveStatus: EffectiveSubscriptionStatus | null;
280
- isGrace: boolean;
281
- isBlocked: boolean;
282
- allowGrace: boolean;
283
- failPolicy: FFIDServiceAccessFailPolicy;
284
- denialReason: FFIDServiceAccessDenialReason | null;
285
- organizationId: string | null;
286
- subscriptionId: string | null;
287
- status: FFIDSubscriptionStatus | null;
288
- planCode: string | null;
289
- currentPeriodEnd: string | null;
290
- gracePeriodEndsAt: string | null;
291
- reactivatable: boolean;
292
- /**
293
- * Present when the decision was produced by the SDK fail-closed policy
294
- * rather than by a successful FFID response.
295
- */
296
- error?: FFIDServiceAccessError;
297
- }
298
-
299
- /**
300
- * Token Store
301
- *
302
- * Manages OAuth 2.0 tokens (access + refresh) with dual-storage support.
303
- * Falls back to in-memory storage when localStorage is unavailable
304
- * (e.g., Safari private browsing mode).
305
- */
306
- /**
307
- * Token data stored by the token store
308
- */
309
- interface TokenData {
310
- /** OAuth 2.0 access token */
311
- accessToken: string;
312
- /** OAuth 2.0 refresh token */
313
- refreshToken: string;
314
- /** Expiration timestamp in milliseconds (Unix epoch) */
315
- expiresAt: number;
316
- }
317
- /**
318
- * Token store interface for managing OAuth tokens
319
- */
320
- interface TokenStore {
321
- /** Get stored tokens (null if not stored) */
322
- getTokens(): TokenData | null;
323
- /** Store new tokens */
324
- setTokens(tokens: TokenData): void;
325
- /** Clear all stored tokens */
326
- clearTokens(): void;
327
- /** Check if access token is expired (with 30s buffer) */
328
- isAccessTokenExpired(): boolean;
329
- }
330
- /**
331
- * Create a token store with the specified storage type.
332
- *
333
- * When storageType is 'localStorage' (default in browser), falls back
334
- * to memory if localStorage is not available (e.g., Safari private mode).
335
- *
336
- * @param storageType - 'localStorage' (default) or 'memory'
337
- */
338
- declare function createTokenStore(storageType?: 'localStorage' | 'memory'): TokenStore;
339
-
340
- /**
341
- * Billing checkout / portal session types.
342
- *
343
- * types/index.ts のサイズ上限対応で切り出し(中身は逐語移設、#3787 Phase A)。
344
- */
345
- /**
346
- * Checkout session response from billing checkout endpoint
347
- */
348
- interface FFIDCheckoutSessionResponse {
349
- /** Stripe Checkout session ID */
350
- sessionId: string;
351
- /** Stripe Checkout session URL (null if session creation had issues) */
352
- url: string | null;
353
- }
354
- /**
355
- * Portal session response from billing portal endpoint
356
- */
357
- interface FFIDPortalSessionResponse {
358
- /** Stripe Billing Portal URL */
359
- url: string;
360
- }
361
- /**
362
- * Parameters for creating a checkout session
363
- */
364
- interface FFIDCreateCheckoutParams {
365
- /** Organization ID (UUID) */
366
- organizationId: string;
367
- /** Subscription ID (UUID) */
368
- subscriptionId: string;
369
- /** URL to redirect after successful checkout */
370
- successUrl: string;
371
- /** URL to redirect after cancelled checkout */
372
- cancelUrl: string;
373
- /** Optional plan ID for upgrade or resubscription */
374
- planId?: string;
375
- }
376
- /**
377
- * Parameters for creating a billing portal session
378
- */
379
- interface FFIDCreatePortalParams {
380
- /** Organization ID (UUID) */
381
- organizationId: string;
382
- /** URL to redirect when user exits the portal */
383
- returnUrl: string;
384
- }
385
-
386
214
  /** Billing interval for subscriptions */
387
215
  type FFIDBillingInterval = 'monthly' | 'yearly';
388
216
  /**
@@ -413,6 +241,15 @@ interface FFIDPlanInfo {
413
241
  trialDays: number;
414
242
  /** flat: period fee; per_seat: multiply by selected quantity. Older servers omit this. */
415
243
  pricingModel?: 'per_seat' | 'flat';
244
+ /**
245
+ * flat: member limit of the plan (`null` = unlimited). Absent on per_seat plans
246
+ * and on FFID servers released before #6392 — do not read absence as unlimited.
247
+ */
248
+ maxMembers?: number | null;
249
+ /** flat: monthly AI credit allowance (0 = none). Absent on older servers. */
250
+ monthlyCredits?: number;
251
+ /** Plan tier in the personal / team / enterprise ladder. Absent on older servers. */
252
+ tier?: 'personal' | 'team' | 'enterprise';
416
253
  /**
417
254
  * Monthly price in the smallest currency unit.
418
255
  *
@@ -490,7 +327,10 @@ interface FFIDSubscribeParams {
490
327
  planCode: string;
491
328
  /** Billing interval (default: 'monthly') */
492
329
  billingInterval?: FFIDBillingInterval;
493
- /** Number of seats (default: plan's minSeats) */
330
+ /**
331
+ * Number of seats (default: plan's minSeats). per_seat plans only — a flat plan
332
+ * answers 400 `VALIDATION_ERROR` (`details.field: 'quantity'`), so omit it there.
333
+ */
494
334
  quantity?: number;
495
335
  /** User ID for seat auto-assignment (only needed for service-key mode) */
496
336
  userId?: string;
@@ -671,113 +511,654 @@ interface FFIDPlanChangePreviewBase {
671
511
  lineItems: FFIDPlanChangeLineItem[];
672
512
  }
673
513
  /**
674
- * Plan change proration preview. It is computed with the same rules as `changePlan()`.
514
+ * Plan change proration preview. It is computed with the same rules as `changePlan()`.
515
+ *
516
+ * `willApplyAtPeriodEnd` is a discriminant that constrains related fields at the type level:
517
+ * - `true`: a decrease reserved until the period end. `proratedAmount` is always 0 (nothing is
518
+ * charged or refunded now); `effectiveDate` mirrors the subscription's `currentPeriodEnd`
519
+ * (may be `null` when the subscription has no active billing cycle yet).
520
+ * - `false`: immediate change. `effectiveDate` is always `null`. For an increase,
521
+ * `proratedAmount` is the difference up to the current period end that is charged now
522
+ * (possibly Stripe-refined). A decrease on a contract that cannot be reserved also lands here:
523
+ * `0` for a contract without a Stripe subscription (no refund), but Stripe's prorated amount —
524
+ * negative, credited on the next invoice — for a Stripe contract with no period end yet
525
+ * (e.g. during a trial).
526
+ */
527
+ type FFIDPlanChangePreview = FFIDPlanChangePreviewBase & ({
528
+ willApplyAtPeriodEnd: true;
529
+ effectiveDate: string | null;
530
+ /** 0 on period-end-deferred changes; charge happens at the next invoice */
531
+ proratedAmount: 0;
532
+ } | {
533
+ willApplyAtPeriodEnd: false;
534
+ effectiveDate: null;
535
+ /** Difference up to the current period end charged now (see the type doc for the decrease cases) */
536
+ proratedAmount: number;
537
+ });
538
+ /** Response from plan change preview endpoint */
539
+ interface FFIDPlanChangePreviewResponse {
540
+ preview: FFIDPlanChangePreview;
541
+ }
542
+ /** Parameters for previewing a seat count change */
543
+ interface FFIDPreviewSeatChangeParams {
544
+ /** Subscription ID (UUID) */
545
+ subscriptionId: string;
546
+ /** New seat quantity. Must be an integer within the plan's allowed range. */
547
+ quantity: number;
548
+ }
549
+ /** Seat change preview line item */
550
+ interface FFIDSeatChangeLineItem {
551
+ description: string;
552
+ amount: number;
553
+ }
554
+ /**
555
+ * Seat change proration preview.
556
+ *
557
+ * Sister type to `FFIDPlanChangePreview`. The shared `type` discriminant lets
558
+ * consumers narrow a preview payload without inspecting unrelated fields.
559
+ *
560
+ * - `willApplyAtPeriodEnd=true`: a seat reduction reserved until the period end.
561
+ * `proratedAmount` is 0 and `effectiveDate` is when the new seat count applies.
562
+ * - Otherwise the change applies immediately. For an increase, `proratedAmount` is the amount
563
+ * charged now up to the current period end. A reduction that cannot be reserved is normally
564
+ * `0`, but can be negative (a credit on the next invoice) for a Stripe contract with no period
565
+ * end yet or when only the contract unit price is known.
566
+ * - `isEstimate=true` (default when Stripe is not configured or data is unavailable):
567
+ * `proratedAmount` is computed locally. `isEstimate=false`: it reflects Stripe's live
568
+ * proration calculation.
569
+ *
570
+ * `nextInvoiceAmount` is a local estimate: flat period fee, or `unitPrice * newQuantity`.
571
+ * Flat capacity changes have zero prorated cost.
572
+ *
573
+ * `pricingUnavailable=true` means custom pricing or an unverified contracted Stripe
574
+ * Price. Hide amounts; zero is not a quote. `custom_pricing` permits the existing
575
+ * unbilled seat-change flow. Require another preview before confirmation only when
576
+ * pricing is unavailable and the reason is not `custom_pricing`. `stripe_error` also
577
+ * covers invoice/address failures after the contract price is known; use
578
+ * `pricingUnavailable`, not the reason alone, to decide whether to hide amounts.
579
+ * `no_stripe_data` means invoice proration is unavailable even if the contract unit
580
+ * price is known. Portal seat PUT independently enforces subscription limits;
581
+ * external consumers should guide users to the FFID portal for seat changes.
582
+ */
583
+ interface FFIDSeatChangePreview {
584
+ /** flat: unitPrice is the whole period fee; per_seat: multiply by capacity. */
585
+ pricingModel?: 'per_seat' | 'flat';
586
+ /** Discriminant for preview response variants (pairs with `FFIDPlanChangePreview.type`) */
587
+ type: 'seat-change';
588
+ currentQuantity: number;
589
+ newQuantity: number;
590
+ /** Per-seat price for the current billing interval */
591
+ unitPrice: number;
592
+ billingInterval: FFIDBillingInterval;
593
+ /**
594
+ * Amount charged now for the rest of the current billing period. A reserved seat reduction
595
+ * (`willApplyAtPeriodEnd: true`) is always `0`; see the type doc for reductions that cannot
596
+ * be reserved. Also `0` when `pricingUnavailable === true`; hide it instead of quoting zero.
597
+ */
598
+ proratedAmount: number;
599
+ /** Next invoice full amount — flat period fee or per-seat price times newQuantity */
600
+ nextInvoiceAmount: number;
601
+ nextInvoiceDate: string | null;
602
+ currency: FFIDSupportedCurrency;
603
+ /** true when proratedAmount is a local estimate rather than Stripe invoice/proration data */
604
+ isEstimate: boolean;
605
+ /** Hide amounts: custom pricing or contracted Stripe Price could not be verified. */
606
+ pricingUnavailable?: boolean;
607
+ /** Reason why `isEstimate` is true. Only meaningful when `isEstimate === true`. */
608
+ estimateReason?: 'no_stripe_data' | 'custom_pricing' | 'stripe_error';
609
+ /**
610
+ * `true` when the seat reduction would be reserved until the period end (8.0.0).
611
+ * Absent or `false` = the change applies immediately.
612
+ */
613
+ willApplyAtPeriodEnd?: boolean;
614
+ /** When the reserved seat count applies (present when `willApplyAtPeriodEnd` is `true`) */
615
+ effectiveDate?: string | null;
616
+ lineItems: FFIDSeatChangeLineItem[];
617
+ }
618
+ /** Response from seat change preview endpoint */
619
+ interface FFIDSeatChangePreviewResponse {
620
+ preview: FFIDSeatChangePreview;
621
+ }
622
+
623
+ /** Deployment environments a catalog publication can apply to (single source) */
624
+ declare const FFID_CATALOG_ENVIRONMENTS: readonly ["staging", "production"];
625
+ /** Deployment environment a catalog publication applies to */
626
+ type FFIDCatalogEnvironment = (typeof FFID_CATALOG_ENVIRONMENTS)[number];
627
+ /**
628
+ * Tax behavior of a published price.
629
+ * `inclusive`: displayed price contains Stripe-calculated tax.
630
+ * `exclusive`: tax is added on top of the base price.
631
+ */
632
+ type FFIDTaxBehavior = 'inclusive' | 'exclusive';
633
+ /**
634
+ * Publication lifecycle statuses (single source — the type, the SDK runtime
635
+ * validator, and consumer sets all derive from this tuple so a new literal
636
+ * cannot be added to one side only).
637
+ */
638
+ declare const FFID_CATALOG_PUBLICATION_STATUSES: readonly ["approved", "superseded", "revoking", "revoked"];
639
+ /**
640
+ * Publication lifecycle status.
641
+ * - `approved`: sellable — the only status that permits new paid checkout
642
+ * - `superseded`: replaced by a newer revision (old sessions may sell through
643
+ * until `sellThroughUntil`)
644
+ * - `revoking`: emergency stop in progress — never sellable
645
+ * - `revoked`: emergency stop completed — never sellable
646
+ */
647
+ type FFIDCatalogPublicationStatus = (typeof FFID_CATALOG_PUBLICATION_STATUSES)[number];
648
+ /**
649
+ * Human-approved publication record binding a catalog revision to the
650
+ * content/config hashes reviewed at approval time.
651
+ *
652
+ * All `*Hash` fields are lowercase SHA-256 hex computed with the shared
653
+ * canonicalization helpers in `shared/catalog-hash` — consumers re-hash the
654
+ * content they actually render and must refuse new sales on any mismatch.
655
+ */
656
+ interface FFIDCatalogPublication {
657
+ serviceCode: string;
658
+ environment: FFIDCatalogEnvironment;
659
+ catalogVersion: string;
660
+ praxisCopyHash: string;
661
+ ffidCheckoutCopyHash: string;
662
+ legalDisclosureHash: string;
663
+ taxConfigurationHash: string;
664
+ paymentConfigurationHash: string;
665
+ scopeHash: string;
666
+ /** Monotonically increasing per (service, environment) publication counter */
667
+ salesEpoch: number;
668
+ /** FFID-generated immutable human-review approval ID */
669
+ approvalId: string;
670
+ /** ISO 8601 timestamp of the recorded human review */
671
+ reviewedAt: string;
672
+ /** ISO 8601 timestamp the publication became effective */
673
+ effectiveAt: string;
674
+ /** ISO 8601 timestamp the publication was superseded (null while current) */
675
+ supersededAt: string | null;
676
+ /** ISO 8601 upper bound for old-session sell-through (null unless superseded) */
677
+ sellThroughUntil: string | null;
678
+ /** ISO 8601 timestamp an emergency revoke took effect (null unless revoking/revoked) */
679
+ revocationEffectiveAt: string | null;
680
+ status: FFIDCatalogPublicationStatus;
681
+ }
682
+ /**
683
+ * A plan inside a published catalog revision (public-safe snapshot).
684
+ * Never contains Stripe Product/Price identifiers or other secrets.
685
+ */
686
+ interface FFIDPublishedPlan {
687
+ /**
688
+ * Plan code — always a lowercase slug (`^[a-z0-9][a-z0-9_-]*$`).
689
+ * Per-service open set; see the FFID repo's
690
+ * `docs/04-api/PLAN_CODE_CONTRACT.md` for the full guarantees.
691
+ */
692
+ code: string;
693
+ name: string;
694
+ description: string | null;
695
+ /** Monthly price in the catalog currency; null when monthly is not offered */
696
+ priceMonthly: number | null;
697
+ /** Yearly price in the catalog currency; null when yearly is not offered */
698
+ priceYearly: number | null;
699
+ currency: string;
700
+ /** Billing intervals actually purchasable for this plan */
701
+ billingIntervals: FFIDBillingInterval[];
702
+ taxBehavior: FFIDTaxBehavior;
703
+ minSeats: number;
704
+ maxSeats: number | null;
705
+ displayOrder: number;
706
+ /** Billing model (10.0.0). Absent on snapshots published before ADR-008 (e.g. praxis) */
707
+ pricingModel?: 'per_seat' | 'flat';
708
+ /** flat: member limit (`null` = unlimited). Absent on older snapshots */
709
+ maxMembers?: number | null;
710
+ /** flat: monthly AI credit allowance (0 = none). Absent on older snapshots */
711
+ monthlyCredits?: number;
712
+ /** Plan tier. Absent on older snapshots */
713
+ tier?: 'personal' | 'team' | 'enterprise';
714
+ }
715
+ /**
716
+ * Response of GET /api/v1/subscriptions/ext/catalog/{serviceCode}.
717
+ *
718
+ * `catalogVersion` always equals `publication.catalogVersion`; it is hoisted
719
+ * to the top level so consumers can pin the version (e.g. as
720
+ * `expectedCatalogVersion` for checkout) without digging into the publication.
721
+ */
722
+ interface FFIDPublishedCatalog {
723
+ service: {
724
+ code: string;
725
+ name: string;
726
+ };
727
+ catalogVersion: string;
728
+ publication: FFIDCatalogPublication;
729
+ plans: FFIDPublishedPlan[];
730
+ }
731
+
732
+ /**
733
+ * Credits API types (SDK 10.0.0 / FFID #6395, CREDITS_API.md v1.3.1).
734
+ *
735
+ * `pricing_model='flat'` plans (fixed fee + member limit + monthly AI credits)
736
+ * keep a credit ledger in FFID. These types mirror the FFID server's response
737
+ * shapes exactly; the FFID repository type-checks them against the server types
738
+ * (`src/lib/credits/__tests__/sdk-credits-types.sync.test.ts`), so a field changed
739
+ * on one side only fails FFID's gate.
740
+ *
741
+ * Kept out of `types/index.ts` (file-size limit). Exported directly from the
742
+ * package entry.
743
+ */
744
+
745
+ /** Values of the history `type` filter / item `type` */
746
+ declare const FFID_CREDIT_HISTORY_TYPES: readonly ["grant", "consumption", "revocation"];
747
+ /** Kinds of credit grant */
748
+ declare const FFID_CREDIT_GRANT_KINDS: readonly ["monthly", "manual", "pack"];
749
+ /**
750
+ * Billing model of a plan. `flat` = fixed fee + member limit + monthly credits;
751
+ * `per_seat` = seat billing (praxis). Do not infer it from `planCode`.
752
+ */
753
+ type FFIDPricingModel = 'per_seat' | 'flat';
754
+ /** History item / filter type */
755
+ type FFIDCreditHistoryType = (typeof FFID_CREDIT_HISTORY_TYPES)[number];
756
+ /** Grant kind: monthly allowance, operator grant, or purchased pack */
757
+ type FFIDCreditGrantKind = (typeof FFID_CREDIT_GRANT_KINDS)[number];
758
+ /** Remaining credits per grant kind */
759
+ interface FFIDCreditBreakdown {
760
+ monthly: number;
761
+ manual: number;
762
+ pack: number;
763
+ }
764
+ /** `GET /ext/{id}/credits/balance` (CREDITS_API §4.2) */
765
+ interface FFIDCreditBalance {
766
+ subscriptionId: string;
767
+ organizationId: string;
768
+ serviceCode: string;
769
+ pricingModel: 'flat';
770
+ /** Credits usable now (sum of active grants' remaining) */
771
+ available: number;
772
+ /** This period's monthly allowance */
773
+ monthlyCredits: number;
774
+ /** Current monthly window; null when there is no monthly grant this period */
775
+ period: {
776
+ start: string;
777
+ end: string;
778
+ } | null;
779
+ breakdown: FFIDCreditBreakdown;
780
+ /** Next manual / pack expiry; null when none is active */
781
+ nextExpiry: {
782
+ at: string;
783
+ amount: number;
784
+ } | null;
785
+ lowThresholdPercent: number;
786
+ /** `available < monthlyCredits * lowThresholdPercent / 100` (always false when monthlyCredits = 0) */
787
+ isLow: boolean;
788
+ /** `available === 0` */
789
+ isExhausted: boolean;
790
+ /** ISO 8601 time the balance was read */
791
+ asOf: string;
792
+ }
793
+ /** `POST /ext/{id}/credits/consume` 200 response (CREDITS_API §4.1) */
794
+ interface FFIDCreditConsumeResult {
795
+ /**
796
+ * `duplicate` = same idempotency key and amount was already consumed. No side
797
+ * effect; `consumptionId` / `amount` are the first call's, `balance` is current,
798
+ * and `crossedLow` / `crossedExhausted` are always false.
799
+ */
800
+ status: 'consumed' | 'duplicate';
801
+ consumptionId: string;
802
+ amount: number;
803
+ balance: {
804
+ available: number;
805
+ monthlyCredits: number;
806
+ breakdown: FFIDCreditBreakdown;
807
+ };
808
+ /** This consumption crossed the low-balance threshold (once per period) */
809
+ crossedLow: boolean;
810
+ /** This consumption brought the balance to 0 (once per period) */
811
+ crossedExhausted: boolean;
812
+ }
813
+ /** One row of the credit history (CREDITS_API §4.3) */
814
+ interface FFIDCreditHistoryItem {
815
+ /** Grant / consumption id. For `revocation` this is the revoked grant's id */
816
+ id: string;
817
+ type: FFIDCreditHistoryType;
818
+ /** Always positive; the direction is given by `type` */
819
+ amount: number;
820
+ occurredAt: string;
821
+ /** Set for `grant` / `revocation` */
822
+ kind: FFIDCreditGrantKind | null;
823
+ expiresAt: string | null;
824
+ /** Set for `consumption` */
825
+ feature: string | null;
826
+ /** Set for `consumption` */
827
+ idempotencyKey: string | null;
828
+ actorUserId: string | null;
829
+ reason: string | null;
830
+ metadata: Record<string, unknown> | null;
831
+ }
832
+ /** `GET /ext/{id}/credits/history` (newest first) */
833
+ interface FFIDCreditHistoryPage {
834
+ items: FFIDCreditHistoryItem[];
835
+ /** Opaque cursor for the next page; null on the last page */
836
+ nextCursor: string | null;
837
+ }
838
+ /** One purchasable credit pack (CREDITS_API §4.4) */
839
+ interface FFIDCreditPack {
840
+ code: string;
841
+ name: string;
842
+ credits: number;
843
+ /** Tax-inclusive price in the smallest currency unit */
844
+ price: number;
845
+ currency: string;
846
+ taxBehavior: FFIDTaxBehavior;
847
+ validityMonths: number;
848
+ displayOrder: number;
849
+ }
850
+ /** `GET /ext/credits/packs` */
851
+ interface FFIDCreditPackList {
852
+ serviceCode: string;
853
+ packs: FFIDCreditPack[];
854
+ }
855
+ /**
856
+ * `POST /ext/credits/packs/checkout` (CREDITS_API §4.5). Credits are **not**
857
+ * granted yet when this returns — confirm with `credits.pack_purchased` /
858
+ * `credits.granted` or the balance.
859
+ */
860
+ interface FFIDCreditPackCheckout {
861
+ checkoutUrl: string;
862
+ sessionId: string;
863
+ operationId: string;
864
+ /** ISO 8601 */
865
+ expiresAt: string;
866
+ }
867
+ /**
868
+ * `credits` field added to `/ext/check` (CREDITS_API §5.1). Null for per_seat,
869
+ * for no subscription, **and** when FFID could not read the ledger — so null
870
+ * does not mean "not flat"; use `pricingModel`.
871
+ */
872
+ interface FFIDSubscriptionCheckCredits {
873
+ available: number;
874
+ monthlyCredits: number;
875
+ isLow: boolean;
876
+ isExhausted: boolean;
877
+ periodEnd: string | null;
878
+ }
879
+ /**
880
+ * `GET /ext/{id}/seats` (CREDITS_API §5.2).
881
+ *
882
+ * flat contracts report the member limit: `maxMembers` (null = unlimited),
883
+ * `assignedMembers`, `availableMembers` (null = unlimited); `quantity` /
884
+ * `minSeats` / `maxSeats` are always 1 and `availableSeats === availableMembers`.
885
+ * per_seat contracts keep the seat shape and only add `pricingModel`.
886
+ * FFID servers released before #6392 omit `pricingModel` and the member fields.
887
+ */
888
+ interface FFIDSeatsSummary {
889
+ subscriptionId: string;
890
+ organizationId: string;
891
+ pricingModel?: FFIDPricingModel;
892
+ quantity: number;
893
+ assignedSeats: number;
894
+ availableSeats: number | null;
895
+ minSeats: number;
896
+ maxSeats: number | null;
897
+ maxMembers?: number | null;
898
+ assignedMembers?: number;
899
+ availableMembers?: number | null;
900
+ }
901
+ /** Input of `client.credits.consume()` (service-key mode only) */
902
+ interface FFIDCreditConsumeParams {
903
+ /** Contract id: UUID, `ffid:<uuid>` or Stripe `sub_...` */
904
+ subscriptionId: string;
905
+ /** Integer, `FFID_CREDITS_CONSUME_MIN_AMOUNT`..`FFID_CREDITS_CONSUME_MAX_AMOUNT` */
906
+ amount: number;
907
+ /**
908
+ * One key per AI execution, reused on retry (`FFID_CREDITS_IDEMPOTENCY_KEY_PATTERN`).
909
+ * Recommended: `${feature}:${requestId}`.
910
+ */
911
+ idempotencyKey: string;
912
+ /** Service-side feature identifier (`FFID_CREDITS_FEATURE_PATTERN`) */
913
+ feature: string;
914
+ /**
915
+ * Aggregation values only (model name, token counts). **No personal data** —
916
+ * consumption rows are accounting records and survive user deletion.
917
+ * JSON object, at most `FFID_CREDITS_METADATA_MAX_BYTES` bytes.
918
+ */
919
+ metadata?: Record<string, unknown>;
920
+ /** End user who ran the AI (UUID). Recorded for audit only */
921
+ userId?: string;
922
+ }
923
+ /** Options of `client.credits.listHistory()` */
924
+ interface FFIDListCreditHistoryOptions {
925
+ /** `nextCursor` of the previous page */
926
+ cursor?: string;
927
+ /** `FFID_CREDITS_HISTORY_MIN_LIMIT`..`FFID_CREDITS_HISTORY_MAX_LIMIT` (server default 20) */
928
+ limit?: number;
929
+ /** Filter by type; omitted = all */
930
+ type?: FFIDCreditHistoryType;
931
+ }
932
+ /** Input of `client.credits.createPackCheckout()` (token mode only) */
933
+ interface FFIDCreatePackCheckoutParams {
934
+ /** Contract id: UUID, `ffid:<uuid>` or Stripe `sub_...` */
935
+ subscriptionId: string;
936
+ /** `code` from `listPacks()` */
937
+ packCode: string;
938
+ /** https URL to return to after payment */
939
+ successUrl: string;
940
+ /** https URL to return to on cancel */
941
+ cancelUrl: string;
942
+ /** `FFID_CREDIT_PACK_MIN_QUANTITY`..`FFID_CREDIT_PACK_MAX_QUANTITY` (server default 1) */
943
+ quantity?: number;
944
+ }
945
+ /** Input of `client.credits.getBuyCreditsUrl()` / `redirectToBuyCredits()` */
946
+ interface FFIDBuyCreditsUrlParams {
947
+ /**
948
+ * Contract id as a UUID or `ffid:<uuid>`. The FFID portal page takes the UUID
949
+ * form only, so a Stripe `sub_...` id is rejected with `VALIDATION_ERROR`.
950
+ */
951
+ subscriptionId: string;
952
+ /** Organization ID — passed as `?org=` so FFID selects that organization */
953
+ orgId?: string;
954
+ }
955
+
956
+ /**
957
+ * Canonical service-access types for subscription lifecycle decisions.
958
+ */
959
+
960
+ /** Subscription status values matching the FFID platform's SubscriptionStatus type */
961
+ type FFIDSubscriptionStatus = 'trialing' | 'active' | 'past_due' | 'canceled' | 'pending_invoice' | 'paused' | 'incomplete' | 'incomplete_expired' | 'unpaid';
962
+ interface FFIDSubscriptionCheckResponse {
963
+ hasActiveSubscription: boolean;
964
+ /**
965
+ * Canonical access decision returned by FFID's `/subscriptions/ext/check`.
966
+ *
967
+ * This is the server-side source of truth for service gates. Consumers
968
+ * should not recompute access from `currentPeriodEnd`, `past_due_since`, or
969
+ * local payment timestamps.
970
+ */
971
+ hasAccess?: boolean;
972
+ /** True when `effectiveStatus === 'past_due_grace'`. */
973
+ isGrace?: boolean;
974
+ /** True when FFID's canonical effective status denies service access. */
975
+ isBlocked?: boolean;
976
+ organizationId: string | null;
977
+ subscriptionId: string | null;
978
+ status: FFIDSubscriptionStatus | null;
979
+ planCode: string | null;
980
+ currentPeriodEnd: string | null;
981
+ /**
982
+ * Semantic FFID access-control status. `null` means the organization has no
983
+ * subscription row for this service.
984
+ */
985
+ effectiveStatus?: EffectiveSubscriptionStatus | null;
986
+ /**
987
+ * ISO timestamp at which `past_due_grace` flips to `blocked`; null outside
988
+ * the grace window.
989
+ */
990
+ gracePeriodEndsAt?: string | null;
991
+ /** Whether a canceled subscription can be resumed via a re-subscription flow. */
992
+ reactivatable?: boolean;
993
+ /**
994
+ * Billing model of the subscription's plan (10.0.0; CREDITS_API §5.1). `null` when
995
+ * there is no subscription. FFID servers released before #6390 omit it.
996
+ */
997
+ pricingModel?: FFIDPricingModel | null;
998
+ /**
999
+ * Credit balance summary — flat plans only (10.0.0). `null` for per_seat, for no
1000
+ * subscription, **and** when FFID could not read the ledger, so `null` does not
1001
+ * mean "not flat" (use `pricingModel`). Never affects `hasAccess`.
1002
+ */
1003
+ credits?: FFIDSubscriptionCheckCredits | null;
1004
+ /**
1005
+ * The plan's member limit is full (10.0.0). Informational: `hasAccess` is not
1006
+ * affected. A user who cannot get a seat because of it is refused at login with
1007
+ * OAuth `denial_reason: 'member_limit_reached'`.
1008
+ */
1009
+ memberLimitReached?: boolean;
1010
+ }
1011
+ type FFIDServiceAccessFailPolicy = 'failClosed';
1012
+ /**
1013
+ * Why access was denied.
1014
+ *
1015
+ * `member_limit_reached` (10.0.0) is FFID's denial reason when a flat plan's member
1016
+ * limit is full and the user has no seat. It reaches you through OAuth
1017
+ * (`denial_reason` on authorize / userinfo / introspect); `checkServiceAccess()`
1018
+ * derives its decision from `effectiveStatus` and never returns it, because a full
1019
+ * member limit does not change `hasAccess` (CREDITS_API §1-5). It is in this union so
1020
+ * one exhaustive `switch` over denial reasons covers both paths.
1021
+ */
1022
+ type FFIDServiceAccessDenialReason = 'no_subscription' | 'grace_disallowed' | 'blocked' | 'canceled' | 'expired' | 'trial_expired' | 'ffid_unreachable' | 'member_limit_reached';
1023
+ interface FFIDCheckServiceAccessParams {
1024
+ userId?: string;
1025
+ organizationId: string;
1026
+ /**
1027
+ * Whether `past_due_grace` should keep access open.
1028
+ *
1029
+ * @default true
1030
+ */
1031
+ allowGrace?: boolean;
1032
+ /**
1033
+ * Error policy when FFID cannot return a canonical decision.
1034
+ *
1035
+ * Currently only `failClosed` is supported: network/server/parse failures
1036
+ * become `hasAccess=false` decisions with `denialReason='ffid_unreachable'`
1037
+ * and the root cause in `decision.error`. Treat `hasAccess` as the gate.
1038
+ */
1039
+ failPolicy?: FFIDServiceAccessFailPolicy;
1040
+ }
1041
+ interface FFIDServiceAccessError {
1042
+ code: string;
1043
+ message: string;
1044
+ details?: unknown;
1045
+ }
1046
+ interface FFIDServiceAccessDecision {
1047
+ hasAccess: boolean;
1048
+ effectiveStatus: EffectiveSubscriptionStatus | null;
1049
+ isGrace: boolean;
1050
+ isBlocked: boolean;
1051
+ allowGrace: boolean;
1052
+ failPolicy: FFIDServiceAccessFailPolicy;
1053
+ denialReason: FFIDServiceAccessDenialReason | null;
1054
+ organizationId: string | null;
1055
+ subscriptionId: string | null;
1056
+ status: FFIDSubscriptionStatus | null;
1057
+ planCode: string | null;
1058
+ currentPeriodEnd: string | null;
1059
+ gracePeriodEndsAt: string | null;
1060
+ reactivatable: boolean;
1061
+ /** Passed through from `/ext/check` when the server sends it (10.0.0). Informational. */
1062
+ pricingModel?: FFIDPricingModel | null;
1063
+ /**
1064
+ * Passed through from `/ext/check` (10.0.0). Gate AI features on this or on
1065
+ * `credits.consume`'s result — not on `hasAccess`.
1066
+ */
1067
+ credits?: FFIDSubscriptionCheckCredits | null;
1068
+ /** Passed through from `/ext/check` (10.0.0). Does not affect `hasAccess`. */
1069
+ memberLimitReached?: boolean;
1070
+ /**
1071
+ * Present when the decision was produced by the SDK fail-closed policy
1072
+ * rather than by a successful FFID response.
1073
+ */
1074
+ error?: FFIDServiceAccessError;
1075
+ }
1076
+
1077
+ /**
1078
+ * Token Store
1079
+ *
1080
+ * Manages OAuth 2.0 tokens (access + refresh) with dual-storage support.
1081
+ * Falls back to in-memory storage when localStorage is unavailable
1082
+ * (e.g., Safari private browsing mode).
1083
+ */
1084
+ /**
1085
+ * Token data stored by the token store
1086
+ */
1087
+ interface TokenData {
1088
+ /** OAuth 2.0 access token */
1089
+ accessToken: string;
1090
+ /** OAuth 2.0 refresh token */
1091
+ refreshToken: string;
1092
+ /** Expiration timestamp in milliseconds (Unix epoch) */
1093
+ expiresAt: number;
1094
+ }
1095
+ /**
1096
+ * Token store interface for managing OAuth tokens
1097
+ */
1098
+ interface TokenStore {
1099
+ /** Get stored tokens (null if not stored) */
1100
+ getTokens(): TokenData | null;
1101
+ /** Store new tokens */
1102
+ setTokens(tokens: TokenData): void;
1103
+ /** Clear all stored tokens */
1104
+ clearTokens(): void;
1105
+ /** Check if access token is expired (with 30s buffer) */
1106
+ isAccessTokenExpired(): boolean;
1107
+ }
1108
+ /**
1109
+ * Create a token store with the specified storage type.
675
1110
  *
676
- * `willApplyAtPeriodEnd` is a discriminant that constrains related fields at the type level:
677
- * - `true`: a decrease reserved until the period end. `proratedAmount` is always 0 (nothing is
678
- * charged or refunded now); `effectiveDate` mirrors the subscription's `currentPeriodEnd`
679
- * (may be `null` when the subscription has no active billing cycle yet).
680
- * - `false`: immediate change. `effectiveDate` is always `null`. For an increase,
681
- * `proratedAmount` is the difference up to the current period end that is charged now
682
- * (possibly Stripe-refined). A decrease on a contract that cannot be reserved also lands here:
683
- * `0` for a contract without a Stripe subscription (no refund), but Stripe's prorated amount —
684
- * negative, credited on the next invoice — for a Stripe contract with no period end yet
685
- * (e.g. during a trial).
1111
+ * When storageType is 'localStorage' (default in browser), falls back
1112
+ * to memory if localStorage is not available (e.g., Safari private mode).
1113
+ *
1114
+ * @param storageType - 'localStorage' (default) or 'memory'
686
1115
  */
687
- type FFIDPlanChangePreview = FFIDPlanChangePreviewBase & ({
688
- willApplyAtPeriodEnd: true;
689
- effectiveDate: string | null;
690
- /** 0 on period-end-deferred changes; charge happens at the next invoice */
691
- proratedAmount: 0;
692
- } | {
693
- willApplyAtPeriodEnd: false;
694
- effectiveDate: null;
695
- /** Difference up to the current period end charged now (see the type doc for the decrease cases) */
696
- proratedAmount: number;
697
- });
698
- /** Response from plan change preview endpoint */
699
- interface FFIDPlanChangePreviewResponse {
700
- preview: FFIDPlanChangePreview;
1116
+ declare function createTokenStore(storageType?: 'localStorage' | 'memory'): TokenStore;
1117
+
1118
+ /**
1119
+ * Billing checkout / portal session types.
1120
+ *
1121
+ * types/index.ts のサイズ上限対応で切り出し(中身は逐語移設、#3787 Phase A)。
1122
+ */
1123
+ /**
1124
+ * Checkout session response from billing checkout endpoint
1125
+ */
1126
+ interface FFIDCheckoutSessionResponse {
1127
+ /** Stripe Checkout session ID */
1128
+ sessionId: string;
1129
+ /** Stripe Checkout session URL (null if session creation had issues) */
1130
+ url: string | null;
701
1131
  }
702
- /** Parameters for previewing a seat count change */
703
- interface FFIDPreviewSeatChangeParams {
1132
+ /**
1133
+ * Portal session response from billing portal endpoint
1134
+ */
1135
+ interface FFIDPortalSessionResponse {
1136
+ /** Stripe Billing Portal URL */
1137
+ url: string;
1138
+ }
1139
+ /**
1140
+ * Parameters for creating a checkout session
1141
+ */
1142
+ interface FFIDCreateCheckoutParams {
1143
+ /** Organization ID (UUID) */
1144
+ organizationId: string;
704
1145
  /** Subscription ID (UUID) */
705
1146
  subscriptionId: string;
706
- /** New seat quantity. Must be an integer within the plan's allowed range. */
707
- quantity: number;
708
- }
709
- /** Seat change preview line item */
710
- interface FFIDSeatChangeLineItem {
711
- description: string;
712
- amount: number;
1147
+ /** URL to redirect after successful checkout */
1148
+ successUrl: string;
1149
+ /** URL to redirect after cancelled checkout */
1150
+ cancelUrl: string;
1151
+ /** Optional plan ID for upgrade or resubscription */
1152
+ planId?: string;
713
1153
  }
714
1154
  /**
715
- * Seat change proration preview.
716
- *
717
- * Sister type to `FFIDPlanChangePreview`. The shared `type` discriminant lets
718
- * consumers narrow a preview payload without inspecting unrelated fields.
719
- *
720
- * - `willApplyAtPeriodEnd=true`: a seat reduction reserved until the period end.
721
- * `proratedAmount` is 0 and `effectiveDate` is when the new seat count applies.
722
- * - Otherwise the change applies immediately. For an increase, `proratedAmount` is the amount
723
- * charged now up to the current period end. A reduction that cannot be reserved is normally
724
- * `0`, but can be negative (a credit on the next invoice) for a Stripe contract with no period
725
- * end yet or when only the contract unit price is known.
726
- * - `isEstimate=true` (default when Stripe is not configured or data is unavailable):
727
- * `proratedAmount` is computed locally. `isEstimate=false`: it reflects Stripe's live
728
- * proration calculation.
729
- *
730
- * `nextInvoiceAmount` is a local estimate: flat period fee, or `unitPrice * newQuantity`.
731
- * Flat capacity changes have zero prorated cost.
732
- *
733
- * `pricingUnavailable=true` means custom pricing or an unverified contracted Stripe
734
- * Price. Hide amounts; zero is not a quote. `custom_pricing` permits the existing
735
- * unbilled seat-change flow. Require another preview before confirmation only when
736
- * pricing is unavailable and the reason is not `custom_pricing`. `stripe_error` also
737
- * covers invoice/address failures after the contract price is known; use
738
- * `pricingUnavailable`, not the reason alone, to decide whether to hide amounts.
739
- * `no_stripe_data` means invoice proration is unavailable even if the contract unit
740
- * price is known. Portal seat PUT independently enforces subscription limits;
741
- * external consumers should guide users to the FFID portal for seat changes.
1155
+ * Parameters for creating a billing portal session
742
1156
  */
743
- interface FFIDSeatChangePreview {
744
- /** flat: unitPrice is the whole period fee; per_seat: multiply by capacity. */
745
- pricingModel?: 'per_seat' | 'flat';
746
- /** Discriminant for preview response variants (pairs with `FFIDPlanChangePreview.type`) */
747
- type: 'seat-change';
748
- currentQuantity: number;
749
- newQuantity: number;
750
- /** Per-seat price for the current billing interval */
751
- unitPrice: number;
752
- billingInterval: FFIDBillingInterval;
753
- /**
754
- * Amount charged now for the rest of the current billing period. A reserved seat reduction
755
- * (`willApplyAtPeriodEnd: true`) is always `0`; see the type doc for reductions that cannot
756
- * be reserved. Also `0` when `pricingUnavailable === true`; hide it instead of quoting zero.
757
- */
758
- proratedAmount: number;
759
- /** Next invoice full amount — flat period fee or per-seat price times newQuantity */
760
- nextInvoiceAmount: number;
761
- nextInvoiceDate: string | null;
762
- currency: FFIDSupportedCurrency;
763
- /** true when proratedAmount is a local estimate rather than Stripe invoice/proration data */
764
- isEstimate: boolean;
765
- /** Hide amounts: custom pricing or contracted Stripe Price could not be verified. */
766
- pricingUnavailable?: boolean;
767
- /** Reason why `isEstimate` is true. Only meaningful when `isEstimate === true`. */
768
- estimateReason?: 'no_stripe_data' | 'custom_pricing' | 'stripe_error';
769
- /**
770
- * `true` when the seat reduction would be reserved until the period end (8.0.0).
771
- * Absent or `false` = the change applies immediately.
772
- */
773
- willApplyAtPeriodEnd?: boolean;
774
- /** When the reserved seat count applies (present when `willApplyAtPeriodEnd` is `true`) */
775
- effectiveDate?: string | null;
776
- lineItems: FFIDSeatChangeLineItem[];
777
- }
778
- /** Response from seat change preview endpoint */
779
- interface FFIDSeatChangePreviewResponse {
780
- preview: FFIDSeatChangePreview;
1157
+ interface FFIDCreatePortalParams {
1158
+ /** Organization ID (UUID) */
1159
+ organizationId: string;
1160
+ /** URL to redirect when user exits the portal */
1161
+ returnUrl: string;
781
1162
  }
782
1163
 
783
1164
  /** Member role in an organization */
@@ -1124,12 +1505,28 @@ interface FFIDSeatAssignmentRecord {
1124
1505
  createdAt: string;
1125
1506
  updatedAt: string;
1126
1507
  }
1127
- /** GET /api/v1/subscriptions/ext/{id}/seats/assignments のレスポンス */
1508
+ /**
1509
+ * GET /api/v1/subscriptions/ext/{id}/seats/assignments のレスポンス
1510
+ *
1511
+ * 10.0.0(破壊的変更): flat 契約では `totalSeats` = 人数枠(`maxMembers`)で、
1512
+ * 無制限のプランでは `totalSeats` / `availableSeats` が `null` になる。
1513
+ * `pricingModel` で分岐すること(#6395(10.0.0)で assignments にも追加。それより前の FFID は返さない)。
1514
+ */
1128
1515
  interface FFIDListSeatAssignmentsResponse {
1129
1516
  assignments: FFIDSeatAssignmentWithUser[];
1130
- totalSeats: number;
1517
+ /** 課金モデル。#6395(10.0.0)で assignments にも追加(それより前の FFID サーバーは返さない) */
1518
+ pricingModel?: 'per_seat' | 'flat';
1519
+ /** per_seat: 契約の席数。flat: 人数枠(`null` = 無制限) */
1520
+ totalSeats: number | null;
1131
1521
  assignedSeats: number;
1132
- availableSeats: number;
1522
+ /** 残り。flat で無制限のときは `null` */
1523
+ availableSeats: number | null;
1524
+ /** flat のみ: 人数枠(`null` = 無制限) */
1525
+ maxMembers?: number | null;
1526
+ /** flat のみ: 割り当て済みの人数 */
1527
+ assignedMembers?: number;
1528
+ /** flat のみ: 残りの人数枠(`null` = 無制限) */
1529
+ availableMembers?: number | null;
1133
1530
  }
1134
1531
  /** listSeatAssignments のパラメータ */
1135
1532
  interface FFIDListSeatAssignmentsParams {
@@ -1705,107 +2102,6 @@ type FFIDRedirectResult = {
1705
2102
  code?: FFIDRedirectErrorCode;
1706
2103
  };
1707
2104
 
1708
- /** Deployment environments a catalog publication can apply to (single source) */
1709
- declare const FFID_CATALOG_ENVIRONMENTS: readonly ["staging", "production"];
1710
- /** Deployment environment a catalog publication applies to */
1711
- type FFIDCatalogEnvironment = (typeof FFID_CATALOG_ENVIRONMENTS)[number];
1712
- /**
1713
- * Tax behavior of a published price.
1714
- * `inclusive`: displayed price contains Stripe-calculated tax.
1715
- * `exclusive`: tax is added on top of the base price.
1716
- */
1717
- type FFIDTaxBehavior = 'inclusive' | 'exclusive';
1718
- /**
1719
- * Publication lifecycle statuses (single source — the type, the SDK runtime
1720
- * validator, and consumer sets all derive from this tuple so a new literal
1721
- * cannot be added to one side only).
1722
- */
1723
- declare const FFID_CATALOG_PUBLICATION_STATUSES: readonly ["approved", "superseded", "revoking", "revoked"];
1724
- /**
1725
- * Publication lifecycle status.
1726
- * - `approved`: sellable — the only status that permits new paid checkout
1727
- * - `superseded`: replaced by a newer revision (old sessions may sell through
1728
- * until `sellThroughUntil`)
1729
- * - `revoking`: emergency stop in progress — never sellable
1730
- * - `revoked`: emergency stop completed — never sellable
1731
- */
1732
- type FFIDCatalogPublicationStatus = (typeof FFID_CATALOG_PUBLICATION_STATUSES)[number];
1733
- /**
1734
- * Human-approved publication record binding a catalog revision to the
1735
- * content/config hashes reviewed at approval time.
1736
- *
1737
- * All `*Hash` fields are lowercase SHA-256 hex computed with the shared
1738
- * canonicalization helpers in `shared/catalog-hash` — consumers re-hash the
1739
- * content they actually render and must refuse new sales on any mismatch.
1740
- */
1741
- interface FFIDCatalogPublication {
1742
- serviceCode: string;
1743
- environment: FFIDCatalogEnvironment;
1744
- catalogVersion: string;
1745
- praxisCopyHash: string;
1746
- ffidCheckoutCopyHash: string;
1747
- legalDisclosureHash: string;
1748
- taxConfigurationHash: string;
1749
- paymentConfigurationHash: string;
1750
- scopeHash: string;
1751
- /** Monotonically increasing per (service, environment) publication counter */
1752
- salesEpoch: number;
1753
- /** FFID-generated immutable human-review approval ID */
1754
- approvalId: string;
1755
- /** ISO 8601 timestamp of the recorded human review */
1756
- reviewedAt: string;
1757
- /** ISO 8601 timestamp the publication became effective */
1758
- effectiveAt: string;
1759
- /** ISO 8601 timestamp the publication was superseded (null while current) */
1760
- supersededAt: string | null;
1761
- /** ISO 8601 upper bound for old-session sell-through (null unless superseded) */
1762
- sellThroughUntil: string | null;
1763
- /** ISO 8601 timestamp an emergency revoke took effect (null unless revoking/revoked) */
1764
- revocationEffectiveAt: string | null;
1765
- status: FFIDCatalogPublicationStatus;
1766
- }
1767
- /**
1768
- * A plan inside a published catalog revision (public-safe snapshot).
1769
- * Never contains Stripe Product/Price identifiers or other secrets.
1770
- */
1771
- interface FFIDPublishedPlan {
1772
- /**
1773
- * Plan code — always a lowercase slug (`^[a-z0-9][a-z0-9_-]*$`).
1774
- * Per-service open set; see the FFID repo's
1775
- * `docs/04-api/PLAN_CODE_CONTRACT.md` for the full guarantees.
1776
- */
1777
- code: string;
1778
- name: string;
1779
- description: string | null;
1780
- /** Monthly price in the catalog currency; null when monthly is not offered */
1781
- priceMonthly: number | null;
1782
- /** Yearly price in the catalog currency; null when yearly is not offered */
1783
- priceYearly: number | null;
1784
- currency: string;
1785
- /** Billing intervals actually purchasable for this plan */
1786
- billingIntervals: FFIDBillingInterval[];
1787
- taxBehavior: FFIDTaxBehavior;
1788
- minSeats: number;
1789
- maxSeats: number | null;
1790
- displayOrder: number;
1791
- }
1792
- /**
1793
- * Response of GET /api/v1/subscriptions/ext/catalog/{serviceCode}.
1794
- *
1795
- * `catalogVersion` always equals `publication.catalogVersion`; it is hoisted
1796
- * to the top level so consumers can pin the version (e.g. as
1797
- * `expectedCatalogVersion` for checkout) without digging into the publication.
1798
- */
1799
- interface FFIDPublishedCatalog {
1800
- service: {
1801
- code: string;
1802
- name: string;
1803
- };
1804
- catalogVersion: string;
1805
- publication: FFIDCatalogPublication;
1806
- plans: FFIDPublishedPlan[];
1807
- }
1808
-
1809
2105
  /**
1810
2106
  * FFID SDK Type Definitions
1811
2107
  *
@@ -2513,6 +2809,7 @@ declare function createFFIDClient(config: FFIDConfig): {
2513
2809
  getInvoice: (params: FFIDGetInvoiceParams) => Promise<FFIDApiResponse<FFIDGetInvoiceResponse>>;
2514
2810
  retryPayment: (params: FFIDRetryPaymentParams) => Promise<FFIDApiResponse<FFIDRetryPaymentSummary>>;
2515
2811
  listSeatAssignments: (params: FFIDListSeatAssignmentsParams) => Promise<FFIDApiResponse<FFIDListSeatAssignmentsResponse>>;
2812
+ getSeats: (subscriptionId: string) => Promise<FFIDApiResponse<FFIDSeatsSummary>>;
2516
2813
  assignSeats: (params: FFIDAssignSeatsParams) => Promise<FFIDApiResponse<FFIDAssignSeatsResponse>>;
2517
2814
  unassignSeat: (params: FFIDUnassignSeatParams) => Promise<FFIDApiResponse<FFIDUnassignSeatResponse>>;
2518
2815
  listPlans: () => Promise<FFIDApiResponse<FFIDListPlansResponse>>;
@@ -2523,13 +2820,16 @@ declare function createFFIDClient(config: FFIDConfig): {
2523
2820
  cancelSubscription: (params: FFIDCancelSubscriptionParams) => Promise<FFIDApiResponse<FFIDCancelSubscriptionResponse>>;
2524
2821
  cancelPendingDowngrade: (subscriptionId: string) => Promise<FFIDApiResponse<FFIDCancelPendingDowngradeResponse>>;
2525
2822
  previewPlanChange: (params: FFIDPreviewPlanChangeParams) => Promise<FFIDApiResponse<FFIDPlanChangePreviewResponse>>;
2823
+ /** @deprecated 10.0.0: per_seat (praxis) only; removed in 11.0.0 */
2526
2824
  previewSeatChange: (params: FFIDPreviewSeatChangeParams) => Promise<FFIDApiResponse<FFIDSeatChangePreviewResponse>>;
2527
2825
  verifyAccessToken: (accessToken: string, options?: FFIDVerifyAccessTokenOptions) => Promise<FFIDApiResponse<FFIDOAuthUserInfo>>;
2528
2826
  getSubscribeUrl: (options?: ContractWizardSubscribeOptions) => string;
2529
2827
  redirectToSubscribe: (options?: ContractWizardSubscribeOptions) => FFIDRedirectResult;
2530
2828
  getChangePlanUrl: (subscriptionId: string, options?: ContractWizardSubscriptionOptions) => string;
2531
2829
  redirectToChangePlan: (subscriptionId: string, options?: ContractWizardSubscriptionOptions) => FFIDRedirectResult;
2830
+ /** @deprecated 10.0.0: per_seat (praxis) only; removed in 11.0.0 */
2532
2831
  getChangeSeatsUrl: (subscriptionId: string, options?: ContractWizardSubscriptionOptions) => string;
2832
+ /** @deprecated 10.0.0: per_seat (praxis) only; removed in 11.0.0 */
2533
2833
  redirectToChangeSeats: (subscriptionId: string, options?: ContractWizardSubscriptionOptions) => FFIDRedirectResult;
2534
2834
  getRecoverPaymentUrl: (subscriptionId: string, options?: ContractWizardSubscriptionOptions) => string;
2535
2835
  redirectToRecoverPayment: (subscriptionId: string, options?: ContractWizardSubscriptionOptions) => FFIDRedirectResult;
@@ -2557,6 +2857,16 @@ declare function createFFIDClient(config: FFIDConfig): {
2557
2857
  inquiry: {
2558
2858
  create: (params: FFIDInquiryCreateParams) => Promise<FFIDApiResponse<FFIDInquiryCreateResponse>>;
2559
2859
  };
2860
+ /** Credits methods for flat plans (consume / balance / history / packs) */
2861
+ credits: {
2862
+ getBalance: (subscriptionId: string) => Promise<FFIDApiResponse<FFIDCreditBalance>>;
2863
+ listHistory: (subscriptionId: string, options?: FFIDListCreditHistoryOptions) => Promise<FFIDApiResponse<FFIDCreditHistoryPage>>;
2864
+ listPacks: (packServiceCode?: string) => Promise<FFIDApiResponse<FFIDCreditPackList>>;
2865
+ createPackCheckout: (params: FFIDCreatePackCheckoutParams) => Promise<FFIDApiResponse<FFIDCreditPackCheckout>>;
2866
+ consume: (params: FFIDCreditConsumeParams) => Promise<FFIDApiResponse<FFIDCreditConsumeResult>>;
2867
+ getBuyCreditsUrl: (params: FFIDBuyCreditsUrlParams) => FFIDApiResponse<string>;
2868
+ redirectToBuyCredits: (params: FFIDBuyCreditsUrlParams) => FFIDRedirectResult;
2869
+ };
2560
2870
  /** Token store (token mode only) */
2561
2871
  tokenStore: TokenStore;
2562
2872
  /**