@monetize.software/sdk 3.0.0-alpha.0 → 3.0.0-alpha.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/README.md +62 -41
  2. package/dist/chunks/PaywallUI-CQG9HCwo.js +3245 -0
  3. package/dist/chunks/PaywallUI-CQG9HCwo.js.map +1 -0
  4. package/dist/chunks/PaywallUI-DQ1Jke8b.js +26 -0
  5. package/dist/chunks/PaywallUI-DQ1Jke8b.js.map +1 -0
  6. package/dist/chunks/ar-7cgIM-Vl.js +2 -0
  7. package/dist/chunks/ar-7cgIM-Vl.js.map +1 -0
  8. package/dist/chunks/ar-B2Wg_IrC.js +126 -0
  9. package/dist/chunks/ar-B2Wg_IrC.js.map +1 -0
  10. package/dist/chunks/cs-BNo9Dx0Q.js +122 -0
  11. package/dist/chunks/cs-BNo9Dx0Q.js.map +1 -0
  12. package/dist/chunks/cs-S05PC5AC.js +2 -0
  13. package/dist/chunks/cs-S05PC5AC.js.map +1 -0
  14. package/dist/chunks/da-Bi4zBG14.js +2 -0
  15. package/dist/chunks/da-Bi4zBG14.js.map +1 -0
  16. package/dist/chunks/da-Do9Lq6En.js +122 -0
  17. package/dist/chunks/da-Do9Lq6En.js.map +1 -0
  18. package/dist/chunks/de-C8pDZNvx.js +141 -0
  19. package/dist/chunks/de-C8pDZNvx.js.map +1 -0
  20. package/dist/chunks/de-nCDB6D2W.js +2 -0
  21. package/dist/chunks/de-nCDB6D2W.js.map +1 -0
  22. package/dist/chunks/el-BrKaa978.js +2 -0
  23. package/dist/chunks/el-BrKaa978.js.map +1 -0
  24. package/dist/chunks/el-DzMNX-_P.js +126 -0
  25. package/dist/chunks/el-DzMNX-_P.js.map +1 -0
  26. package/dist/chunks/es-B-Wtyzrl.js +2 -0
  27. package/dist/chunks/es-B-Wtyzrl.js.map +1 -0
  28. package/dist/chunks/es-YrKt-q4w.js +141 -0
  29. package/dist/chunks/es-YrKt-q4w.js.map +1 -0
  30. package/dist/chunks/fi-Bh44pwZ4.js +122 -0
  31. package/dist/chunks/fi-Bh44pwZ4.js.map +1 -0
  32. package/dist/chunks/fi-D1SGXjnO.js +2 -0
  33. package/dist/chunks/fi-D1SGXjnO.js.map +1 -0
  34. package/dist/chunks/fr-Bc0pw4ws.js +141 -0
  35. package/dist/chunks/fr-Bc0pw4ws.js.map +1 -0
  36. package/dist/chunks/fr-BhYf-iKk.js +2 -0
  37. package/dist/chunks/fr-BhYf-iKk.js.map +1 -0
  38. package/dist/chunks/he-BXAaFv6Y.js +2 -0
  39. package/dist/chunks/he-BXAaFv6Y.js.map +1 -0
  40. package/dist/chunks/he-Bfm-bhe3.js +126 -0
  41. package/dist/chunks/he-Bfm-bhe3.js.map +1 -0
  42. package/dist/chunks/hi-D-O-B9Dn.js +126 -0
  43. package/dist/chunks/hi-D-O-B9Dn.js.map +1 -0
  44. package/dist/chunks/hi-xblDO0O7.js +2 -0
  45. package/dist/chunks/hi-xblDO0O7.js.map +1 -0
  46. package/dist/chunks/hu-CmIuAbLL.js +122 -0
  47. package/dist/chunks/hu-CmIuAbLL.js.map +1 -0
  48. package/dist/chunks/hu-Wa46p0y4.js +2 -0
  49. package/dist/chunks/hu-Wa46p0y4.js.map +1 -0
  50. package/dist/chunks/id-CQEo5X94.js +2 -0
  51. package/dist/chunks/id-CQEo5X94.js.map +1 -0
  52. package/dist/chunks/id-DN7IES-A.js +122 -0
  53. package/dist/chunks/id-DN7IES-A.js.map +1 -0
  54. package/dist/chunks/it-8AYCm0xz.js +2 -0
  55. package/dist/chunks/it-8AYCm0xz.js.map +1 -0
  56. package/dist/chunks/it-Cz5Nmqx5.js +141 -0
  57. package/dist/chunks/it-Cz5Nmqx5.js.map +1 -0
  58. package/dist/chunks/ja-BH9BlBh2.js +145 -0
  59. package/dist/chunks/ja-BH9BlBh2.js.map +1 -0
  60. package/dist/chunks/ja-q-COVayn.js +2 -0
  61. package/dist/chunks/ja-q-COVayn.js.map +1 -0
  62. package/dist/chunks/ko-B6HRCscZ.js +2 -0
  63. package/dist/chunks/ko-B6HRCscZ.js.map +1 -0
  64. package/dist/chunks/ko-CYV9QuYs.js +145 -0
  65. package/dist/chunks/ko-CYV9QuYs.js.map +1 -0
  66. package/dist/chunks/nl-BvkB900D.js +141 -0
  67. package/dist/chunks/nl-BvkB900D.js.map +1 -0
  68. package/dist/chunks/nl-CAd6_xlm.js +2 -0
  69. package/dist/chunks/nl-CAd6_xlm.js.map +1 -0
  70. package/dist/chunks/no-3s9_ormb.js +122 -0
  71. package/dist/chunks/no-3s9_ormb.js.map +1 -0
  72. package/dist/chunks/no-CAmz6bz6.js +2 -0
  73. package/dist/chunks/no-CAmz6bz6.js.map +1 -0
  74. package/dist/chunks/pl-C9WTGQtb.js +122 -0
  75. package/dist/chunks/pl-C9WTGQtb.js.map +1 -0
  76. package/dist/chunks/pl-DqUSTCaF.js +2 -0
  77. package/dist/chunks/pl-DqUSTCaF.js.map +1 -0
  78. package/dist/chunks/pt-8ARZnH0_.js +2 -0
  79. package/dist/chunks/pt-8ARZnH0_.js.map +1 -0
  80. package/dist/chunks/pt-uFVUv_Op.js +141 -0
  81. package/dist/chunks/pt-uFVUv_Op.js.map +1 -0
  82. package/dist/chunks/ro-BrqQ8Au-.js +122 -0
  83. package/dist/chunks/ro-BrqQ8Au-.js.map +1 -0
  84. package/dist/chunks/ro-D-NMbp2F.js +2 -0
  85. package/dist/chunks/ro-D-NMbp2F.js.map +1 -0
  86. package/dist/chunks/ru-8gbHPh0g.js +2 -0
  87. package/dist/chunks/ru-8gbHPh0g.js.map +1 -0
  88. package/dist/chunks/ru-DK594dA8.js +144 -0
  89. package/dist/chunks/ru-DK594dA8.js.map +1 -0
  90. package/dist/chunks/sv-CHNH8-mq.js +122 -0
  91. package/dist/chunks/sv-CHNH8-mq.js.map +1 -0
  92. package/dist/chunks/sv-D8a8hmx9.js +2 -0
  93. package/dist/chunks/sv-D8a8hmx9.js.map +1 -0
  94. package/dist/chunks/th-DfjUK0Y7.js +2 -0
  95. package/dist/chunks/th-DfjUK0Y7.js.map +1 -0
  96. package/dist/chunks/th-l24Pm5q-.js +126 -0
  97. package/dist/chunks/th-l24Pm5q-.js.map +1 -0
  98. package/dist/chunks/tr-ADpigSY5.js +122 -0
  99. package/dist/chunks/tr-ADpigSY5.js.map +1 -0
  100. package/dist/chunks/tr-BdBpz4tL.js +2 -0
  101. package/dist/chunks/tr-BdBpz4tL.js.map +1 -0
  102. package/dist/chunks/uk-CGqo4jek.js +144 -0
  103. package/dist/chunks/uk-CGqo4jek.js.map +1 -0
  104. package/dist/chunks/uk-Cx1zv1ao.js +2 -0
  105. package/dist/chunks/uk-Cx1zv1ao.js.map +1 -0
  106. package/dist/chunks/vi-Dk9bTu6f.js +122 -0
  107. package/dist/chunks/vi-Dk9bTu6f.js.map +1 -0
  108. package/dist/chunks/vi-oe2dW21I.js +2 -0
  109. package/dist/chunks/vi-oe2dW21I.js.map +1 -0
  110. package/dist/chunks/zh-CwczPMPp.js +2 -0
  111. package/dist/chunks/zh-CwczPMPp.js.map +1 -0
  112. package/dist/chunks/zh-LDkEV2D9.js +145 -0
  113. package/dist/chunks/zh-LDkEV2D9.js.map +1 -0
  114. package/dist/core.cjs +1 -1
  115. package/dist/core.cjs.map +1 -1
  116. package/dist/core.d.ts +265 -24
  117. package/dist/core.js +519 -313
  118. package/dist/core.js.map +1 -1
  119. package/dist/index.cjs +1 -1
  120. package/dist/index.d.ts +355 -43
  121. package/dist/index.js +14 -10
  122. package/dist/ui.cjs +1 -1
  123. package/dist/ui.d.ts +322 -44
  124. package/dist/ui.js +1 -1
  125. package/package.json +32 -31
  126. package/dist/chunks/PaywallUI-BHp9afFC.js +0 -2209
  127. package/dist/chunks/PaywallUI-BHp9afFC.js.map +0 -1
  128. package/dist/chunks/PaywallUI-Dr-6q-HL.js +0 -26
  129. package/dist/chunks/PaywallUI-Dr-6q-HL.js.map +0 -1
package/dist/core.d.ts CHANGED
@@ -46,7 +46,9 @@ export declare class ApiGatewayClient {
46
46
 
47
47
  export declare interface ApiGatewayClientOptions {
48
48
  paywallId: string;
49
- apiOrigin?: string;
49
+ /** Origin серверного API SDK — обязательное поле, тот же `custom_domain`, что
50
+ * у BillingClient/AuthClient. См. {@link BillingClientOptions.apiOrigin}. */
51
+ apiOrigin: string;
50
52
  /** AuthClient — Bearer добавляется автоматически. На 401 от gateway клиент
51
53
  * не делает refresh: AuthClient уже сделал lazy-refresh в getAccessToken. */
52
54
  auth?: AuthClient;
@@ -66,7 +68,28 @@ export declare interface ApiGatewayClientOptions {
66
68
  onQuotaExceeded?: (err: QuotaExceededError) => void;
67
69
  }
68
70
 
69
- export declare type AuthChangeListener = (session: AuthSession | null) => void;
71
+ /** Дискриминатор для `onAuthChange`. Позволяет listener'у отличать первый
72
+ * callback (восстановление сессии из storage / синтетический snapshot для
73
+ * свежей подписки) от реальных переходов. Конвенция Supabase, минус события,
74
+ * которых у нас нет (MFA, EMAIL_VERIFIED).
75
+ *
76
+ * - INITIAL_SESSION — единственный гарантированный первый callback на каждую
77
+ * подписку. Дёргается через microtask после resolve hydrated-promise, даже
78
+ * если session=null. Listener'ы по этому event'у НЕ должны делать побочные
79
+ * эффекты типа force-refetch — это просто доставка стартового state'а.
80
+ * - SIGNED_IN — свежий вход: email/OAuth/anon, или появление session в этом
81
+ * инстансе из другого контекста (storage.watch), когда раньше был null.
82
+ * - SIGNED_OUT — signOut, revokeAllSessions, 401 на refresh, удаление session
83
+ * из другого контекста.
84
+ * - TOKEN_REFRESHED — тот же user, обновлённые токены: refresh(), либо
85
+ * storage.watch когда содержимое сменилось но user.id остался.
86
+ * - USER_UPDATED — изменился user.email / user.user_metadata (updatePassword,
87
+ * upgradeAnonymousToEmail) при том же user.id.
88
+ * - PASSWORD_RECOVERY — verifyOtp(type='recovery'). Listener знает, что надо
89
+ * показать «set new password» UI вместо обычного post-login flow'а. */
90
+ declare type AuthChangeEvent = 'INITIAL_SESSION' | 'SIGNED_IN' | 'SIGNED_OUT' | 'TOKEN_REFRESHED' | 'USER_UPDATED' | 'PASSWORD_RECOVERY';
91
+
92
+ export declare type AuthChangeListener = (event: AuthChangeEvent, session: AuthSession | null) => void;
70
93
 
71
94
  export declare class AuthClient {
72
95
  readonly paywallId: string;
@@ -225,10 +248,8 @@ export declare class AuthClient {
225
248
  * когда сервер начнёт возвращать challenge_required в риск-сценариях,
226
249
  * SDK сможет передать proof-of-something обратно без breaking change.
227
250
  *
228
- * `forceCaptcha: true` пропускает шаги 1-2 и сразу делает /signin (создаёт
229
- * нового anon-юзера). Используется в switch-account flow. Имя поля исторически
230
- * остаётся `forceCaptcha`, хотя капчи там больше нет — менять имя ломает
231
- * host-сигнатуру; смысл «принудительно новая anon-сессия» сохранён.
251
+ * `forceNewAnon: true` пропускает шаги 1-2 и сразу делает /signin (создаёт
252
+ * нового anon-юзера). Используется в switch-account flow.
232
253
  *
233
254
  * Параллельные вызовы дедуплицируются через `inflightAnonSignin` — два
234
255
  * click'а на «Войти как гость» не создадут двух anon-юзеров (два /signup =
@@ -237,7 +258,7 @@ export declare class AuthClient {
237
258
  signInAnonymously(input?: {
238
259
  captchaToken?: string;
239
260
  userMeta?: Record<string, string>;
240
- forceCaptcha?: boolean;
261
+ forceNewAnon?: boolean;
241
262
  }): Promise<AuthSession>;
242
263
  /**
243
264
  * Внутренний resume — пробует /auth/refresh с сохранённым anon refresh_token.
@@ -385,8 +406,19 @@ export declare class AuthClient {
385
406
  }): Promise<void>;
386
407
  /**
387
408
  * Подписка на изменения session: signin/signup/refresh/signOut/expired-401.
388
- * Колбек вызывается с текущим snapshot через microtask (если session есть)
389
- * + на каждое реальное изменение. Возвращает unsubscribe.
409
+ *
410
+ * Гарантированный контракт: ПЕРВЫЙ callback каждому subscriber'у — всегда
411
+ * `event = 'INITIAL_SESSION'`, дёргается асинхронно после resolve hydrate'а
412
+ * (даже если session=null — listener получает explicit «нет сессии», а не
413
+ * молчание). Все последующие callback'и — реальные переходы с конкретным
414
+ * event'ом (SIGNED_IN / SIGNED_OUT / TOKEN_REFRESHED / USER_UPDATED /
415
+ * PASSWORD_RECOVERY).
416
+ *
417
+ * Это позволяет listener'у безопасно делать «only on real signin» побочные
418
+ * эффекты (force refetch balances и т.п.) через `event === 'SIGNED_IN'`,
419
+ * не путая их с восстановлением из storage.
420
+ *
421
+ * Возвращает unsubscribe.
390
422
  */
391
423
  onAuthChange(cb: AuthChangeListener): () => void;
392
424
  private isFresh;
@@ -412,6 +444,18 @@ export declare class AuthClient {
412
444
  private readAnonRefreshToken;
413
445
  private writeAnonRefreshToken;
414
446
  private clearAnonRefreshToken;
447
+ /**
448
+ * Last-used auth method + email — для UI бейджа "Last used" и pre-fill'а
449
+ * email-инпута. Storage paywall-scoped, поэтому переключение между
450
+ * пейволами на одном host'е не пересекает данные. Чтение всегда возвращает
451
+ * объект — отсутствующие поля = null. */
452
+ getLastLogin(): Promise<LastLogin | null>;
453
+ /** Запись method и email атомарно (для email/password flows — оба известны
454
+ * на момент signin/signup'а). OAuth-flows используют раздельные
455
+ * recordLastLoginMethod (до popup) и recordLastLoginEmail (после exchange). */
456
+ private recordLastLogin;
457
+ private recordLastLoginMethod;
458
+ private recordLastLoginEmail;
415
459
  /**
416
460
  * Читает stable visitor_id из storage если он там уже есть. НЕ генерит:
417
461
  * AuthClient может быть инстанцирован раньше BillingClient, а синтетический
@@ -424,7 +468,9 @@ export declare class AuthClient {
424
468
 
425
469
  export declare interface AuthClientOptions {
426
470
  paywallId: string;
427
- apiOrigin?: string;
471
+ /** Origin серверного API SDK — обязательное поле, тот же `custom_domain`, что
472
+ * у BillingClient. См. {@link BillingClientOptions.apiOrigin}. */
473
+ apiOrigin: string;
428
474
  storage?: StorageAdapter;
429
475
  fetch?: typeof fetch;
430
476
  openPopup?: (url: string, name: string) => Window | null;
@@ -487,6 +533,8 @@ export declare class BillingClient {
487
533
  private balancesStorageUnwatch;
488
534
  private inflightBalances;
489
535
  private balanceListeners;
536
+ private readonly previewMode;
537
+ private previewVersionCounter;
490
538
  constructor(opts: BillingClientOptions);
491
539
  /**
492
540
  * Stable visitor_id (UUID v4). Первый вызов awaitит первичный резолв из
@@ -519,6 +567,24 @@ export declare class BillingClient {
519
567
  * unsubscribe.
520
568
  */
521
569
  onBootstrapChange(cb: (b: PaywallBootstrap) => void): () => void;
570
+ /**
571
+ * Заменить cachedBootstrap частичными или полными данными и эмитнуть всем
572
+ * подписчикам. Используется host'ом в preview-mode (редактор админки) для
573
+ * live-обновления открытой модалки без сетевого revalidate'а.
574
+ *
575
+ * Поведение:
576
+ * - Без `cachedBootstrap` ожидаются как минимум `settings` + `prices` —
577
+ * иначе PaywallRoot не сможет отрендерить тарифы и упадёт.
578
+ * - С существующим кешем партиал мёрджится поверх: `settings` глубокий мёрдж
579
+ * на 1 уровень (поля настроек), массивы `prices`/`offers` перезаписываются.
580
+ * - Каждый вызов бампит `version` ("preview:<n>"), чтобы applyBootstrap'овая
581
+ * проверка `versionChanged` всегда срабатывала и listener'ы дёргались.
582
+ * - Persist в storage НЕ делаем — preview не должен утекать в другие вкладки.
583
+ *
584
+ * В non-preview режиме метод доступен, но это редкий путь (например, для
585
+ * тестов host'а) — production-код должен полагаться на bootstrap() + revalidate.
586
+ */
587
+ setBootstrap(partial: Partial<PaywallBootstrap>): void;
522
588
  private fetchBootstrap;
523
589
  private revalidateBootstrap;
524
590
  private applyBootstrap;
@@ -544,6 +610,13 @@ export declare class BillingClient {
544
610
  }): Promise<PaywallPrice[]>;
545
611
  /** Sync-снимок цен из последнего bootstrap'а. null = ещё не загружали. */
546
612
  getCachedPrices(): PaywallPrice[] | null;
613
+ /** Sync-снимок офферов из последнего bootstrap'а. null = bootstrap ещё не
614
+ * загружали, пустой массив = бэк отдал пейвол без офферов. Бэк уже
615
+ * применил серверный таргетинг (target_countries / target_emails /
616
+ * targeting_mode из offer_settings) — наружу выезжает только то, что
617
+ * применимо к текущему юзеру. Клиентская сторона остаётся ответственной
618
+ * за price_id-matching и countdown (см. core/offer.ts → resolveOffer). */
619
+ getCachedOffers(): PaywallOffer[] | null;
547
620
  /**
548
621
  * Снимок того, какой язык SDK сейчас считает «языком юзера». Полезно для
549
622
  * синхронизации i18n хоста с тем, что фактически показывает пейвол — чтобы
@@ -718,21 +791,31 @@ export declare class BillingClient {
718
791
  * `/api/v1/paywall/[id]/user` без unstable_cache, потому что list для UI
719
792
  * должен быть свежим после cancel-а.
720
793
  *
721
- * Auth: Bearer обязателен (через AuthClient). Без Bearer — 401 от бэка,
722
- * пробрасываем как PaywallError('http_401'). Гость пустой список.
794
+ * Auth (два пути):
795
+ * - Bearer (через AuthClient) — user.id резолвится из сессии, identity
796
+ * в query игнорируется.
797
+ * - `apiKey` + `identity.email`/`identity.userId` — server-SDK путь для
798
+ * интеграций со своей авторизацией. Бэк проверяет, что identity линкована
799
+ * к этому пейволу (защита от cross-paywall lookup).
800
+ * Без auth и без apiKey+identity — `identity_required`.
723
801
  */
724
802
  listPurchases(opts?: {
725
803
  signal?: AbortSignal;
726
804
  }): Promise<PaywallPurchaseDetailed[]>;
727
805
  /**
728
- * Отменить подписку. Бэк проверит что subscription принадлежит auth-юзеру
729
- * и сделает cancel у acquiring'а (Stripe/Paddle/Chargebee). По умолчанию
730
- * cancel в конце текущего периода — юзер сохраняет access до renewal date'ы.
806
+ * Отменить подписку. Бэк проверит, что subscription принадлежит юзеру
807
+ * (Bearer-путь из сессии; apiKey-путь — из identity), и сделает cancel у
808
+ * acquiring'а (Stripe/Paddle/Chargebee/Overpay). По умолчанию cancel в
809
+ * конце текущего периода — юзер сохраняет access до renewal date'ы.
731
810
  *
732
- * `reason` обязательна (валидация на бэке). Удобно собрать через select
733
- * причин в host-UI, как в legacy customer portal'е.
811
+ * `reason` обязательна (валидация на бэке).
734
812
  *
735
- * Auth: Bearer обязателен.
813
+ * Auth (два пути):
814
+ * - Bearer (через AuthClient) — стандартный путь для UI customer-portal'a.
815
+ * - `apiKey` + `identity.email`/`identity.userId` — для self-service UI на
816
+ * бэке клиента со своей авторизацией. Бэк дополнительно фильтрует
817
+ * subscription по paywall_id, чтобы owner пейвола A не отменил подписку
818
+ * пейвола B.
736
819
  */
737
820
  cancelSubscription(params: {
738
821
  subscriptionId: string;
@@ -769,7 +852,15 @@ export declare class BillingClient {
769
852
 
770
853
  export declare interface BillingClientOptions {
771
854
  paywallId: string;
772
- apiOrigin?: string;
855
+ /**
856
+ * Origin серверного API SDK — обязательное поле. Должно совпадать с
857
+ * `custom_domain`, заданным для пейвола в платформе (модерация привязывает
858
+ * домен к paywall_id). SDK сверяет это значение с `bootstrap.settings.custom_domain`
859
+ * на первом ответе и кидает `invalid_config` при расхождении — защита от
860
+ * опечаток интегратора. Промежуточный `appbox.space` в новом SDK НЕ
861
+ * используется (это только для legacy v2).
862
+ */
863
+ apiOrigin: string;
773
864
  identity?: Identity;
774
865
  storage?: StorageAdapter;
775
866
  capabilities?: string[];
@@ -790,6 +881,16 @@ export declare interface BillingClientOptions {
790
881
  * через `setIdentity`, Bearer не отправляется.
791
882
  */
792
883
  auth?: AuthClient;
884
+ /**
885
+ * Preview/editor-mode. Когда true:
886
+ * - `bootstrap()` НЕ ходит в сеть — отдаёт только `cachedBootstrap`, заданный
887
+ * через `setBootstrap()`. Без seed'а throw'ает (caller обязан засидить до open).
888
+ * - Storage.watch / persist отключены (preview редактора локален для текущей вкладки).
889
+ * - `setBootstrap(partial)` доступен как публичный setter — host'у разрешено
890
+ * мутировать кеш для live-обновления модалки в редакторе админки.
891
+ * Дефолт false — обычный production-режим.
892
+ */
893
+ preview?: boolean;
793
894
  }
794
895
 
795
896
  export declare interface CheckoutResult {
@@ -845,6 +946,11 @@ export declare interface EventTrackerOptions {
845
946
  sendBeacon?: (url: string, data: BodyInit) => boolean;
846
947
  }
847
948
 
949
+ /** Pick the offer applicable to a price. Targeted (`price_id === id`) wins
950
+ * over the global default (`price_id === null`). Offers without a positive
951
+ * `discount_percent` are ignored. */
952
+ export declare function findApplicableOffer(offers: PaywallOffer[] | null | undefined, priceId: string): PaywallOffer | null;
953
+
848
954
  export declare function generateVisitorId(): string;
849
955
 
850
956
  export declare interface Identity {
@@ -853,6 +959,18 @@ export declare interface Identity {
853
959
  anonymousId?: string;
854
960
  }
855
961
 
962
+ export declare interface LastLogin {
963
+ method: LastLoginMethod;
964
+ email: string | null;
965
+ }
966
+
967
+ /** Метод, которым юзер залогинился в последний раз на этом пейволе.
968
+ * Хранится per-paywall в storage и используется UI чтобы:
969
+ * - предзаполнить email-инпут last-known email'ом;
970
+ * - подсветить ту же OAuth-кнопку / email-форму бейджем "Last used".
971
+ * `email` — email/password forms (signin или signup → confirm). */
972
+ export declare type LastLoginMethod = OAuthProvider | 'email';
973
+
856
974
  export declare interface Layout {
857
975
  type: 'modal';
858
976
  blocks: LayoutBlock[];
@@ -868,9 +986,13 @@ export declare type LayoutBlock = {
868
986
  } | {
869
987
  type: 'price_grid';
870
988
  priceIds?: string[];
871
- /** Раскладка карточек цен. `vertical` (default) — стек сверху вниз;
872
- * `horizontal` — ряд side-by-side. v2-аналог `view: 'default' | 'telegram'`. */
873
- view?: 'vertical' | 'horizontal';
989
+ /** Раскладка карточек цен:
990
+ * - `vertical` (default) стек карточек сверху вниз;
991
+ * - `compact` компактный список (одна строка на цену, без карточки,
992
+ * с разделителем); v2-аналог `view: 'telegram'`;
993
+ * - `horizontal` — несколько карточек рядом в ряд; v2-only с момента
994
+ * SDK 3.0 (legacy не показывает выбор этой опции в админке). */
995
+ view?: 'vertical' | 'compact' | 'horizontal';
874
996
  /** ID цены, которая помечается лейблом «популярный план». v2-аналог
875
997
  * пары `price_label_id` + `price_label`. */
876
998
  popular_price_id?: string;
@@ -879,7 +1001,10 @@ export declare type LayoutBlock = {
879
1001
  popular_label?: string;
880
1002
  } | {
881
1003
  type: 'cta_button';
882
- label: string;
1004
+ /** Текст на кнопке. Если не задан — рендерер сам подберёт по
1005
+ * selected price'у и `trial_days`: "Start N-Day Free Trial",
1006
+ * "Get Lifetime Access", "Get Monthly Plan" и т.п. */
1007
+ label?: string;
883
1008
  action: 'checkout' | 'close';
884
1009
  priceId?: string;
885
1010
  } | {
@@ -898,8 +1023,23 @@ export declare type LayoutBlock = {
898
1023
  /** Скрывать панель, если юзер уже залогинен. По умолчанию true.
899
1024
  * false — показываем "Signed in as ... [Sign out]" даже после логина. */
900
1025
  hide_when_authenticated?: boolean;
901
- /** Заголовок над формой (h2). Если опущензаголовок не рендерится. */
1026
+ /** Кастомный заголовок над формой. Если заданотображается вместо
1027
+ * дефолтного (определяется по mode'у — "Welcome back!" / "Welcome!" /
1028
+ * "Forgot password?" / ...). Без `submit_label` также используется как
1029
+ * submit-лейбл для signin (например `heading="Restore Purchases"` →
1030
+ * submit тоже "Restore Purchases"). Длинные heading (типа "Войдите,
1031
+ * чтобы продолжить покупку") в кнопку влезают плохо — задай
1032
+ * `submit_label` отдельно. */
902
1033
  heading?: string;
1034
+ /** Подпись под заголовком. Если опущен — подставляется default-текст
1035
+ * для текущего mode'а. Передай пустую строку чтобы скрыть подпись. */
1036
+ subheading?: string;
1037
+ /** Явный текст submit-кнопки. Имеет приоритет над heading-эхо. Нужен
1038
+ * для интентов с длинным descriptive heading'ом (preauth: "Войдите,
1039
+ * чтобы продолжить покупку" — кнопка только "Войти"). Для коротких
1040
+ * action-headings (restore: "Restore Purchases") опускай — эхо
1041
+ * даёт правильный UX. */
1042
+ submit_label?: string;
903
1043
  } | {
904
1044
  /** Список фич/преимуществ продукта. v2-аналог `features_list` + `features_view`.
905
1045
  * До 5 элементов — рендерим как чек-лист с заголовком и описанием. */
@@ -923,6 +1063,35 @@ export declare type LayoutBlock = {
923
1063
  desc: string;
924
1064
  count: number;
925
1065
  }>;
1066
+ } | {
1067
+ /** Money-back guarantee badge под cta_button: иконка + жирный заголовок +
1068
+ * пояснение мелким шрифтом + bottom divider, который визуально стыкуется
1069
+ * с current_session ниже. v2-аналог inline-блока в `PaywallPricing`. */
1070
+ type: 'guarantee_badge';
1071
+ /** Заголовок жирным. По умолчанию "100% Money-Back Guarantee". */
1072
+ title?: string;
1073
+ /** Подзаголовок мелким серым. По умолчанию
1074
+ * "Not satisfied? We'll refund you — no questions asked.". */
1075
+ subtitle?: string;
1076
+ /** Иконка слева от заголовка. По умолчанию `dollar_shield` —
1077
+ * зелёный shield с долларом (legacy-вид). `none` — без иконки. */
1078
+ icon?: 'dollar_shield' | 'none';
1079
+ } | {
1080
+ /** Urgency-баннер с countdown'ом до конца offer'а. Берёт первый offer
1081
+ * из `bootstrap.offers` с валидным `expires_at` или `duration_minutes`.
1082
+ * Авто-скрывается по истечении, чтобы не показывать "0d 0h 0m 0s".
1083
+ * Размещение — обычно первый блок в layout (над heading). */
1084
+ type: 'offer_banner';
1085
+ /** ID конкретного offer'а из bootstrap.offers. Если не задано — берётся
1086
+ * первый offer с активным таймером. */
1087
+ offer_id?: string;
1088
+ /** Текст слева от countdown'а. Если опущен — берётся `offer.label`,
1089
+ * иначе fallback "Limited-time offer". К нему дописывается процент:
1090
+ * "{title} {discount_percent}%" если discount задан. */
1091
+ title?: string;
1092
+ /** В превью админки — игнорировать expired-state, всё равно показывать
1093
+ * banner с нулями. Прод-режим — false (banner исчезает). */
1094
+ force?: boolean;
926
1095
  };
927
1096
 
928
1097
  /** Локализационные оверрайды для одного языка. Накатываются поверх дефолтного
@@ -942,6 +1111,11 @@ export declare interface LocaleOverrides {
942
1111
 
943
1112
  export declare type OAuthProvider = 'google' | 'apple' | 'github' | 'facebook';
944
1113
 
1114
+ /** Storage key under which a relative `duration_minutes` offer records its
1115
+ * first-view timestamp. Shared between the renderer (which writes the
1116
+ * start on first open) and the host SDK helpers (which read it). */
1117
+ export declare function offerStartStorageKey(offerId: string): string;
1118
+
945
1119
  export declare type OtpVerifyType = 'email' | 'recovery' | 'signup' | 'magiclink' | 'invite';
946
1120
 
947
1121
  export declare interface PaywallBootstrap {
@@ -977,7 +1151,15 @@ export declare class PaywallError extends Error {
977
1151
  export declare interface PaywallOffer {
978
1152
  id: string;
979
1153
  discount_percent: number | null;
1154
+ /** Абсолютная expiration date (ISO 8601). Если задана — countdown отсчитывает
1155
+ * до этого момента, по истечении offer считается истёкшим. */
980
1156
  expires_at: string | null;
1157
+ /** Относительный таймер: сколько минут offer живёт **от первого просмотра
1158
+ * пейвола** данным юзером. Старт хранится в clientStorage под ключом
1159
+ * `pw-offer-{id}-start`, по истечении — auto-cleanup. Используется, когда
1160
+ * бэк хочет показывать "remaining time" без жёсткой server-time, а считать
1161
+ * относительно сессии юзера. expires_at имеет приоритет если задано. */
1162
+ duration_minutes?: number | null;
981
1163
  price_id: string | null;
982
1164
  label?: string | null;
983
1165
  }
@@ -1027,6 +1209,14 @@ export declare interface PaywallSettings {
1027
1209
  brand_color?: string | null;
1028
1210
  custom_css?: string | null;
1029
1211
  locale_default?: string | null;
1212
+ /** Origin, на котором живёт бэк пейвола (тот же, что мерчант передаёт в
1213
+ * `BillingClientOptions.apiOrigin` при инициализации SDK). Бэк присылает
1214
+ * его на каждом bootstrap'е, SDK сверяет с init.apiOrigin — расхождение
1215
+ * даёт `invalid_config` (защита от опечатки интегратора). Без схемы:
1216
+ * "pay.your-domain.com" или "https://pay.your-domain.com" — оба валидны.
1217
+ * Для новых пейволов поле всегда заполнено (модерация требует custom_domain);
1218
+ * для legacy v2 может быть null/undefined. */
1219
+ custom_domain?: string | null;
1030
1220
  runtime_mode?: 'client' | 'hybrid' | 'server' | 'client-native' | 'hybrid-native';
1031
1221
  /** true, если эквайринг пейвола в test-mode — SDK рисует TEST MODE бейдж. */
1032
1222
  is_test_mode?: boolean;
@@ -1084,6 +1274,12 @@ export declare interface PaywallUser {
1084
1274
  started_at: string | null;
1085
1275
  expires_at: string | null;
1086
1276
  } | null;
1277
+ /** Был ли у юзера хотя бы один trial по этому пейволу когда-либо (включая
1278
+ * истёкшие и отменённые). Anti-abuse флаг для UI: CtaButton скрывает
1279
+ * "Start N-Day Free Trial" если true. Серверный enforcement в
1280
+ * `/start-checkout` дублирует — даже если UI обмануть, бэк не отдаст
1281
+ * trial_days в Stripe/Paddle. */
1282
+ had_previous_trial: boolean;
1087
1283
  }
1088
1284
 
1089
1285
  export declare interface PaywallUserPurchase {
@@ -1108,6 +1304,51 @@ export declare class QuotaExceededError extends PaywallError {
1108
1304
  });
1109
1305
  }
1110
1306
 
1307
+ /** Safe browser localStorage getter — returns null in SSR / private mode. */
1308
+ export declare function readBrowserOfferStart(offerId: string): string | null;
1309
+
1310
+ /**
1311
+ * Resolved view of a paywall offer — what host UI actually needs to render
1312
+ * a strike-through price + countdown without re-implementing the math.
1313
+ *
1314
+ * `remainingMs` ticks down with wall-clock time and reaches 0 on expiry.
1315
+ * `totalMs` stays constant — useful for progress bars / share-of-time UX.
1316
+ * `expiresAt` is the Date.now()-comparable epoch ms of expiry.
1317
+ *
1318
+ * For offers without an expiry mechanism (no `expires_at` and no
1319
+ * `duration_minutes`), `remainingMs`/`totalMs`/`expiresAt` are all `null`,
1320
+ * but the resolved view is still returned — discount badge / strike-through
1321
+ * still make sense for "perpetual sale" offers.
1322
+ */
1323
+ export declare interface ResolvedOffer {
1324
+ offer: PaywallOffer;
1325
+ discountPercent: number;
1326
+ remainingMs: number | null;
1327
+ totalMs: number | null;
1328
+ expiresAt: number | null;
1329
+ }
1330
+
1331
+ /** Compute the resolved view of an offer. Pure, no side-effects. */
1332
+ export declare function resolveOffer(offer: PaywallOffer, opts?: ResolveOfferOptions): ResolvedOffer | null;
1333
+
1334
+ export declare interface ResolveOfferOptions {
1335
+ /** Current epoch ms. Inject for deterministic tests; default `Date.now()`. */
1336
+ now?: number;
1337
+ /**
1338
+ * Synchronous reader for the `duration_minutes` start-timestamp ISO string.
1339
+ * Host passes a closure over its sync storage (browser → `localStorage`,
1340
+ * memory → in-process map). Return `null` if no start has been recorded.
1341
+ *
1342
+ * Intentionally synchronous, because consumers call this from UI render —
1343
+ * an async StorageAdapter would force every price card to suspend.
1344
+ *
1345
+ * If omitted, `duration_minutes`-only offers return `expiresAt = null`,
1346
+ * which makes the resolved view treat them as "not yet started" (the
1347
+ * renderer is responsible for writing the start on first paywall view).
1348
+ */
1349
+ readStart?: (offerId: string) => string | null;
1350
+ }
1351
+
1111
1352
  export declare const SDK_VERSION = "3.0.0-alpha.0";
1112
1353
 
1113
1354
  export declare type SignUpResult = {