@feelflow/ffid-sdk 7.2.0 → 8.1.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.
@@ -411,6 +411,8 @@ interface FFIDPlanInfo {
411
411
  maxSeats: number | null;
412
412
  seatSelectionEnabled: boolean;
413
413
  trialDays: number;
414
+ /** flat: period fee; per_seat: multiply by selected quantity. Older servers omit this. */
415
+ pricingModel?: 'per_seat' | 'flat';
414
416
  /**
415
417
  * Monthly price in the smallest currency unit.
416
418
  *
@@ -450,12 +452,31 @@ interface FFIDSubscriptionDetail {
450
452
  trialStart: string | null;
451
453
  trialEnd: string | null;
452
454
  canceledAt: string | null;
455
+ /**
456
+ * `true` while a cancellation is reserved until the period end. `status` stays
457
+ * `active` until then; the subscription becomes `canceled` at `currentPeriodEnd`.
458
+ * The reservation can only be withdrawn in the FFID portal (there is no SDK method).
459
+ */
453
460
  cancelAtPeriodEnd: boolean;
454
- /** Pending period-end downgrade info (null = no scheduled downgrade) */
461
+ /**
462
+ * Change reserved until the period end (null = nothing reserved).
463
+ *
464
+ * Every decrease — a lower plan, paid → free, fewer seats, yearly → monthly — is
465
+ * reserved instead of applied immediately (FFID #6327). The current plan, seats and
466
+ * interval stay in effect until `scheduledAt`. Withdraw it with `cancelPendingDowngrade()`.
467
+ */
455
468
  pendingDowngrade: {
469
+ /** Plan in effect after the period end. Equals `planCode` for a seat-only or interval-only reservation. */
456
470
  planCode: string;
457
471
  planName: string;
472
+ /** Billing interval in effect after the period end */
458
473
  billingInterval: FFIDBillingInterval;
474
+ /**
475
+ * Seat count in effect after the period end. `null` = the seat count does not change.
476
+ * Seat reductions are made in the FFID portal; this lets you show them (8.0.0).
477
+ * FFID servers released before #6327 omit it, so compare with `== null` at runtime.
478
+ */
479
+ quantity: number | null;
459
480
  scheduledAt: string;
460
481
  requestedAt: string;
461
482
  } | null;
@@ -522,7 +543,17 @@ interface FFIDChangePlanParams {
522
543
  /** New billing interval (default: keeps current) */
523
544
  billingInterval?: FFIDBillingInterval;
524
545
  }
525
- /** Response from change plan endpoint */
546
+ /**
547
+ * Response from change plan endpoint.
548
+ *
549
+ * - An increase (higher plan / monthly → yearly) applies immediately and the difference up to
550
+ * the current period end is charged now. `pendingDowngrade` is absent.
551
+ * - A decrease (lower plan / paid → free / yearly → monthly) is reserved until the period end:
552
+ * `pendingDowngrade` is present and `subscription.planCode` is still the current plan.
553
+ * Nothing is charged or refunded. A contract that cannot be reserved still changes
554
+ * immediately, without a refund: one with no period end, or a paid → paid decrease on a
555
+ * contract without a Stripe subscription.
556
+ */
526
557
  interface FFIDChangePlanResponse {
527
558
  message: string;
528
559
  subscription: {
@@ -535,7 +566,7 @@ interface FFIDChangePlanResponse {
535
566
  status: FFIDSubscriptionStatus;
536
567
  isUpgrade: boolean;
537
568
  };
538
- /** Present when downgrade is scheduled for period end instead of immediate */
569
+ /** Present when the change is a decrease reserved until the period end */
539
570
  pendingDowngrade?: {
540
571
  planCode: string;
541
572
  planName: string;
@@ -551,17 +582,41 @@ interface FFIDCancelSubscriptionParams {
551
582
  /** Additional cancellation details */
552
583
  reasonDetails?: string;
553
584
  }
554
- /** Response from cancel subscription endpoint */
585
+ /**
586
+ * Response from cancel subscription endpoint.
587
+ *
588
+ * Since 8.0.0 a contract billed through Stripe is **reserved** for cancellation at the period
589
+ * end (FFID #6327): `status` stays `active`, `cancelAtPeriodEnd` is `true` and `cancelAt` is the
590
+ * period end. Access continues until then and nothing is refunded. A contract that cannot be
591
+ * reserved (no Stripe subscription, no period end yet, or Stripe not configured on FFID) is
592
+ * canceled immediately (`status: 'canceled'`, `cancelAtPeriodEnd: false`, `cancelAt: null`).
593
+ * Branch on `cancelAtPeriodEnd`, not on whether the contract uses Stripe.
594
+ *
595
+ * Do not treat this response as "access revoked". The `subscription.canceled` webhook arrives
596
+ * at reservation (`source: 'user_initiated'`) and again at the period end
597
+ * (`source: 'stripe_confirmed'`); see `FFIDSubscriptionCanceledPayload`.
598
+ */
555
599
  interface FFIDCancelSubscriptionResponse {
556
600
  message: string;
557
601
  subscription: {
558
602
  id: string;
559
603
  organizationId: string;
604
+ /** `active` while the cancellation is reserved; `canceled` only for an immediate cancellation */
560
605
  status: FFIDSubscriptionStatus;
561
606
  canceledAt: string | null;
607
+ /** `true` when the cancellation is reserved until the period end */
608
+ cancelAtPeriodEnd: boolean;
609
+ /** When the reserved cancellation takes effect (the period end). `null` for an immediate cancellation */
610
+ cancelAt: string | null;
562
611
  };
563
612
  }
564
- /** Response from cancel pending downgrade endpoint */
613
+ /**
614
+ * Response from cancel pending downgrade endpoint.
615
+ *
616
+ * Withdraws the reserved change on the Stripe side as well, so the current plan, seats and
617
+ * interval simply continue after the period end. It does not withdraw a reserved
618
+ * cancellation — that is done in the FFID portal.
619
+ */
565
620
  interface FFIDCancelPendingDowngradeResponse {
566
621
  message: string;
567
622
  subscription: {
@@ -593,12 +648,12 @@ interface FFIDPlanChangeLineItem {
593
648
  interface FFIDPlanChangePreviewBase {
594
649
  /** Discriminant for preview response variants (reserved for future unions with e.g. seat-change previews) */
595
650
  type: 'plan-change';
596
- /** Current plan display info. `price` is the total for the current quantity (unitPrice × quantity), not per-seat. */
651
+ /** Current plan display info. `price` is the period total: flat fee, or per-seat price times quantity. */
597
652
  currentPlan: {
598
653
  name: string;
599
654
  price: number;
600
655
  };
601
- /** New plan display info. `price` is the total for the effective quantity (unitPrice × quantity), not per-seat. */
656
+ /** New plan display info. `price` is the period total: flat fee, or per-seat price times quantity. */
602
657
  newPlan: {
603
658
  name: string;
604
659
  price: number;
@@ -616,14 +671,18 @@ interface FFIDPlanChangePreviewBase {
616
671
  lineItems: FFIDPlanChangeLineItem[];
617
672
  }
618
673
  /**
619
- * Plan change proration preview.
674
+ * Plan change proration preview. It is computed with the same rules as `changePlan()`.
620
675
  *
621
676
  * `willApplyAtPeriodEnd` is a discriminant that constrains related fields at the type level:
622
- * - `true`: period-end-deferred downgrade. `proratedAmount` is always 0; `effectiveDate` mirrors
623
- * the subscription's `currentPeriodEnd` (may be `null` when the subscription has no active
624
- * billing cycle yet — e.g. a pre-Stripe trial or a subscription whose billing cycle is unset).
625
- * - `false`: immediate change. `effectiveDate` is always `null`; `proratedAmount` is the live
626
- * (possibly Stripe-refined) day-basis difference.
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).
627
686
  */
628
687
  type FFIDPlanChangePreview = FFIDPlanChangePreviewBase & ({
629
688
  willApplyAtPeriodEnd: true;
@@ -633,7 +692,7 @@ type FFIDPlanChangePreview = FFIDPlanChangePreviewBase & ({
633
692
  } | {
634
693
  willApplyAtPeriodEnd: false;
635
694
  effectiveDate: null;
636
- /** Prorated difference charged within the current billing period */
695
+ /** Difference up to the current period end charged now (see the type doc for the decrease cases) */
637
696
  proratedAmount: number;
638
697
  });
639
698
  /** Response from plan change preview endpoint */
@@ -658,16 +717,32 @@ interface FFIDSeatChangeLineItem {
658
717
  * Sister type to `FFIDPlanChangePreview`. The shared `type` discriminant lets
659
718
  * consumers narrow a preview payload without inspecting unrelated fields.
660
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.
661
726
  * - `isEstimate=true` (default when Stripe is not configured or data is unavailable):
662
- * `proratedAmount` is computed locally as `(newQuantity - currentQuantity) * unitPrice`.
663
- * - `isEstimate=false`: `proratedAmount` reflects Stripe's live proration calculation.
664
- *
665
- * `nextInvoiceAmount` is always a local estimate (`unitPrice * newQuantity`).
666
- *
667
- * `pricingUnavailable=true` indicates the plan uses custom pricing with a zero unit
668
- * price (Enterprise). Consumers should hide monetary amounts in this case.
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.
669
742
  */
670
743
  interface FFIDSeatChangePreview {
744
+ /** flat: unitPrice is the whole period fee; per_seat: multiply by capacity. */
745
+ pricingModel?: 'per_seat' | 'flat';
671
746
  /** Discriminant for preview response variants (pairs with `FFIDPlanChangePreview.type`) */
672
747
  type: 'seat-change';
673
748
  currentQuantity: number;
@@ -676,20 +751,28 @@ interface FFIDSeatChangePreview {
676
751
  unitPrice: number;
677
752
  billingInterval: FFIDBillingInterval;
678
753
  /**
679
- * Prorated cost for the current billing period. Negative when seats are decreased
680
- * (credit). `0` when `pricingUnavailable === true` (Enterprise custom pricing).
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.
681
757
  */
682
758
  proratedAmount: number;
683
- /** Next invoice full amount — always a local estimate (`unitPrice * newQuantity`) */
759
+ /** Next invoice full amount — flat period fee or per-seat price times newQuantity */
684
760
  nextInvoiceAmount: number;
685
761
  nextInvoiceDate: string | null;
686
762
  currency: FFIDSupportedCurrency;
687
- /** true when proratedAmount is estimated from local prices rather than Stripe proration data */
763
+ /** true when proratedAmount is a local estimate rather than Stripe invoice/proration data */
688
764
  isEstimate: boolean;
689
- /** true when unit price is 0 due to custom pricing (Enterprise) — hide monetary amounts */
765
+ /** Hide amounts: custom pricing or contracted Stripe Price could not be verified. */
690
766
  pricingUnavailable?: boolean;
691
767
  /** Reason why `isEstimate` is true. Only meaningful when `isEstimate === true`. */
692
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;
693
776
  lineItems: FFIDSeatChangeLineItem[];
694
777
  }
695
778
  /** Response from seat change preview endpoint */
@@ -774,6 +857,7 @@ interface FFIDProvisionUserProfileInput {
774
857
  /** Display name (1–255 chars). */
775
858
  displayName?: string;
776
859
  /** Company name (≤255 chars). */
860
+ /** @deprecated Accepted for compatibility but ignored by FFID. */
777
861
  companyName?: string;
778
862
  /** Department (≤255 chars). */
779
863
  department?: string;
@@ -1475,7 +1559,7 @@ interface FFIDUserProfile {
1475
1559
  avatarUrl: string | null;
1476
1560
  /** Phone number (nullable when not set) */
1477
1561
  phone: string | null;
1478
- /** Company name (nullable when not set) */
1562
+ /** @deprecated Individual employer name is retired; this field is always null. Use the organization entity's name. */
1479
1563
  companyName: string | null;
1480
1564
  /** Department (nullable when not set) */
1481
1565
  department: string | null;
@@ -1556,7 +1640,7 @@ interface FFIDUpdateUserProfileRequest {
1556
1640
  displayName?: string | null;
1557
1641
  /** Phone number (null clears the field) */
1558
1642
  phone?: string | null;
1559
- /** Company name (null clears the field) */
1643
+ /** @deprecated Accepted for compatibility but ignored. Use the organization entity's name. */
1560
1644
  companyName?: string | null;
1561
1645
  /** Department (null clears the field) */
1562
1646
  department?: string | null;
@@ -1922,6 +2006,7 @@ interface FFIDOAuthUserInfo {
1922
2006
  emailVerified?: boolean | undefined;
1923
2007
  name: string | null;
1924
2008
  picture: string | null;
2009
+ /** @deprecated Individual employer name is retired; always null. Use organizationName. */
1925
2010
  companyName?: string | null | undefined;
1926
2011
  department?: string | null | undefined;
1927
2012
  position?: string | null | undefined;
@@ -2356,7 +2441,9 @@ declare function createFFIDClient(config: FFIDConfig): {
2356
2441
  prompt?: FFIDPrompt;
2357
2442
  }) => string;
2358
2443
  getLogoutUrl: (postLogoutRedirectUri?: string) => string;
2359
- getSignupUrl: (redirectUrl?: string) => string;
2444
+ getSignupUrl: (redirectUrl?: string, options?: {
2445
+ inviteToken?: string;
2446
+ }) => string;
2360
2447
  createError: (code: string, message: string) => FFIDError;
2361
2448
  exchangeCodeForTokens: (code: string, codeVerifier?: string, opts?: ExchangeCodeForTokensOptions) => Promise<FFIDApiResponse<void>>;
2362
2449
  refreshAccessToken: () => Promise<FFIDApiResponse<void>>;
@@ -411,6 +411,7 @@ interface FFIDOAuthUserInfo {
411
411
  emailVerified?: boolean | undefined;
412
412
  name: string | null;
413
413
  picture: string | null;
414
+ /** @deprecated Individual employer name is retired; always null. Use organizationName. */
414
415
  companyName?: string | null | undefined;
415
416
  department?: string | null | undefined;
416
417
  position?: string | null | undefined;
@@ -411,6 +411,7 @@ interface FFIDOAuthUserInfo {
411
411
  emailVerified?: boolean | undefined;
412
412
  name: string | null;
413
413
  picture: string | null;
414
+ /** @deprecated Individual employer name is retired; always null. Use organizationName. */
414
415
  companyName?: string | null | undefined;
415
416
  department?: string | null | undefined;
416
417
  position?: string | null | undefined;