@mohasinac/appkit 4.63.4 → 4.63.5

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 (136) hide show
  1. package/dist/.build-stamp.json +3 -3
  2. package/dist/_internal/server/features/checkout/actions.d.ts +2 -0
  3. package/dist/_internal/server/features/checkout/actions.js +22 -5
  4. package/dist/_internal/server/features/orders/adapters.js +3 -1
  5. package/dist/_internal/server/features/site-settings/adapters.js +20 -2
  6. package/dist/_internal/server/features/stores/adapters.d.ts +1 -1
  7. package/dist/_internal/server/features/stores/adapters.js +1 -0
  8. package/dist/_internal/server/jobs/core/analyticsAlertEvaluate.js +5 -2
  9. package/dist/_internal/server/jobs/core/jobRunners.js +5 -0
  10. package/dist/_internal/server/jobs/core/onOrderCreate.js +104 -10
  11. package/dist/_internal/server/jobs/core/onOrderStatusChange.d.ts +2 -0
  12. package/dist/_internal/server/jobs/core/onOrderStatusChange.js +1 -0
  13. package/dist/_internal/server/jobs/core/onScamReportCreate.js +1 -0
  14. package/dist/_internal/server/jobs/core/onScamReportRejected.js +1 -0
  15. package/dist/_internal/server/jobs/core/onScamReportVerified.js +1 -0
  16. package/dist/_internal/server/jobs/core/paymentWindowTimeout.js +1 -0
  17. package/dist/_internal/server/jobs/core/pendingOrderTimeout.js +1 -0
  18. package/dist/_internal/server/jobs/core/whatsappNotify.d.ts +35 -0
  19. package/dist/_internal/server/jobs/core/whatsappNotify.js +140 -0
  20. package/dist/_internal/server/notifications/channel-health.d.ts +8 -6
  21. package/dist/_internal/server/notifications/channel-health.js +2 -2
  22. package/dist/_internal/server/notifications/send-guard.d.ts +1 -1
  23. package/dist/_internal/server/notifications/send-guard.js +4 -2
  24. package/dist/_internal/shared/actions/action-registry.js +28 -0
  25. package/dist/_internal/shared/checkout/order-math.d.ts +1 -1
  26. package/dist/_internal/shared/checkout/order-math.js +1 -1
  27. package/dist/_internal/shared/features/checkout/config.d.ts +2 -0
  28. package/dist/_internal/shared/features/checkout/config.js +2 -0
  29. package/dist/_internal/shared/features/messaging/config.d.ts +5 -7
  30. package/dist/_internal/shared/features/messaging/config.js +4 -1
  31. package/dist/_internal/shared/features/site-settings/fees.d.ts +2 -0
  32. package/dist/_internal/shared/features/site-settings/fees.js +2 -0
  33. package/dist/_internal/shared/features/site-settings/types.d.ts +27 -2
  34. package/dist/_internal/shared/fees/calculator.d.ts +8 -0
  35. package/dist/_internal/shared/fees/calculator.js +7 -0
  36. package/dist/cli/index.js +2 -0
  37. package/dist/client.d.ts +2 -2
  38. package/dist/client.js +1 -1
  39. package/dist/constants/api-endpoints.d.ts +15 -0
  40. package/dist/constants/api-endpoints.js +8 -0
  41. package/dist/constants/notification-types.d.ts +1 -1
  42. package/dist/constants/notification-types.js +1 -1
  43. package/dist/core/integration-keys.d.ts +5 -0
  44. package/dist/core/integration-keys.js +10 -0
  45. package/dist/features/about/components/FeesView.js +6 -0
  46. package/dist/features/account/components/BuyerShoppingGuideView.js +1 -1
  47. package/dist/features/account/components/NotificationPreferencesPanel.js +4 -3
  48. package/dist/features/account/types/index.d.ts +2 -0
  49. package/dist/features/admin/actions/notification-actions.d.ts +17 -2
  50. package/dist/features/admin/actions/notification-actions.js +154 -9
  51. package/dist/features/admin/components/AdminOrderEditorView.d.ts +1 -1
  52. package/dist/features/admin/components/AdminOrdersView.js +2 -0
  53. package/dist/features/admin/components/AdminSiteSettingsView.js +66 -6
  54. package/dist/features/admin/components/AdminStoreEditorView.js +1 -0
  55. package/dist/features/admin/constants/site-settings-tabs.d.ts +12 -7
  56. package/dist/features/admin/constants/site-settings-tabs.js +12 -6
  57. package/dist/features/admin/repository/site-settings.repository.js +1 -1
  58. package/dist/features/admin/schemas/admin-user-form.d.ts +61 -0
  59. package/dist/features/admin/schemas/admin-user-form.js +44 -3
  60. package/dist/features/admin/schemas/firestore.d.ts +73 -0
  61. package/dist/features/admin/schemas/firestore.js +8 -3
  62. package/dist/features/auth/checkout-value-otp.d.ts +1 -1
  63. package/dist/features/auth/checkout-value-otp.js +1 -1
  64. package/dist/features/auth/permissions/constants.d.ts +2 -2
  65. package/dist/features/auth/permissions/constants.js +2 -1
  66. package/dist/features/cart/components/CartPriceBreakdown.d.ts +2 -0
  67. package/dist/features/cart/components/CartPriceBreakdown.js +1 -1
  68. package/dist/features/cart/components/StoreAddonsPicker.d.ts +5 -2
  69. package/dist/features/cart/components/StoreAddonsPicker.js +5 -4
  70. package/dist/features/cart/repository/cart.repository.js +2 -1
  71. package/dist/features/cart/schemas/firestore.d.ts +2 -1
  72. package/dist/features/checkout/actions/checkout-value-otp-actions.d.ts +5 -10
  73. package/dist/features/checkout/actions/checkout-value-otp-actions.js +88 -2
  74. package/dist/features/homepage/lib/section-renderer.js +1 -1
  75. package/dist/features/orders/actions/refund-actions.js +1 -0
  76. package/dist/features/orders/components/OrderAddonBadges.d.ts +3 -1
  77. package/dist/features/orders/components/OrderAddonBadges.js +4 -3
  78. package/dist/features/orders/repository/orders.repository.d.ts +8 -0
  79. package/dist/features/orders/repository/orders.repository.js +2 -0
  80. package/dist/features/orders/schemas/firestore.d.ts +4 -0
  81. package/dist/features/orders/types/index.d.ts +5 -2
  82. package/dist/features/search/action-index/settings-entries.js +9 -0
  83. package/dist/features/store-extensions/repository/store-extensions.repositories.d.ts +6 -1
  84. package/dist/features/store-extensions/repository/store-extensions.repositories.js +10 -1
  85. package/dist/features/store-extensions/schemas/firestore.d.ts +17 -1
  86. package/dist/features/store-extensions/schemas/firestore.js +10 -0
  87. package/dist/features/stores/components/StoreCapabilitiesGuideView.js +1 -0
  88. package/dist/features/stores/components/StoreGuideHubView.js +1 -1
  89. package/dist/features/stores/components/StoreSettingsGuideView.js +6 -2
  90. package/dist/features/stores/repository/store.repository.d.ts +23 -1
  91. package/dist/features/stores/repository/store.repository.js +29 -1
  92. package/dist/features/stores/schemas/firestore.d.ts +16 -6
  93. package/dist/features/stores/schemas/firestore.js +3 -6
  94. package/dist/features/stores/schemas/index.d.ts +86 -0
  95. package/dist/features/stores/schemas/index.js +14 -0
  96. package/dist/features/whatsapp-bot/api/send-status/route.d.ts +12 -0
  97. package/dist/features/whatsapp-bot/api/send-status/route.js +38 -0
  98. package/dist/features/whatsapp-bot/api/webhook/route.d.ts +12 -0
  99. package/dist/features/whatsapp-bot/api/webhook/route.js +43 -0
  100. package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.d.ts +57 -0
  101. package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +76 -0
  102. package/dist/features/whatsapp-bot/components/WhatsAppChatButton.d.ts +9 -0
  103. package/dist/features/whatsapp-bot/components/WhatsAppChatButton.js +7 -0
  104. package/dist/features/whatsapp-bot/components/index.d.ts +3 -0
  105. package/dist/features/whatsapp-bot/components/index.js +2 -0
  106. package/dist/features/whatsapp-bot/helpers/whatsapp.d.ts +52 -0
  107. package/dist/features/whatsapp-bot/helpers/whatsapp.js +288 -0
  108. package/dist/features/whatsapp-bot/index.d.ts +3 -0
  109. package/dist/features/whatsapp-bot/index.js +3 -0
  110. package/dist/features/whatsapp-bot/manifest.d.ts +2 -0
  111. package/dist/features/whatsapp-bot/manifest.js +13 -0
  112. package/dist/features/whatsapp-bot/messages/en.json +7 -0
  113. package/dist/features/whatsapp-bot/server.d.ts +7 -0
  114. package/dist/features/whatsapp-bot/server.js +7 -0
  115. package/dist/features/whatsapp-bot/types/index.d.ts +126 -0
  116. package/dist/features/whatsapp-bot/types/index.js +1 -0
  117. package/dist/index.d.ts +23 -6
  118. package/dist/index.js +38 -12
  119. package/dist/next/routing/route-map.d.ts +3 -0
  120. package/dist/next/routing/route-map.js +1 -0
  121. package/dist/providers/db-firebase/base.repository.d.ts +1 -1
  122. package/dist/providers/db-firebase/base.repository.js +1 -1
  123. package/dist/repositories/index.d.ts +1 -1
  124. package/dist/repositories/index.js +1 -1
  125. package/dist/schemas/registry.d.ts +105 -0
  126. package/dist/schemas/registry.js +2 -1
  127. package/dist/security/pii-encrypt.js +1 -1
  128. package/dist/server.d.ts +10 -2
  129. package/dist/server.js +22 -2
  130. package/dist/styles.css +1 -1
  131. package/dist/tailwind-utilities.css +1 -1
  132. package/dist/ui/components/NavigationLoader.d.ts +14 -1
  133. package/dist/ui/components/NavigationLoader.js +79 -30
  134. package/package.json +1 -1
  135. package/dist/features/orders/utils/order-announcement-message.d.ts +0 -18
  136. package/dist/features/orders/utils/order-announcement-message.js +0 -16
@@ -822,6 +822,10 @@ export interface OrderDocument extends BaseDocument {
822
822
  codRemainingAmount?: number;
823
823
  /** COD handling fee charged to the buyer: min(subtotal × codHandlingFeePercent / 100, codHandlingFeeCap). Only set when paymentMethod === "cod". */
824
824
  codHandlingFee?: number;
825
+ /** Buyer opted into WhatsApp order-update messages at checkout (unchecked by default). */
826
+ whatsappNotifyAddon?: boolean;
827
+ /** Fee charged for the WhatsApp addon, decimal rupees — snapshot of siteSettings.commissions.whatsappNotifyFee at order time. */
828
+ whatsappNotifyFee?: number;
825
829
  /** Buyer opted into gift wrapping at checkout (unchecked by default). */
826
830
  giftWrapAddon?: boolean;
827
831
  /** Fee charged for gift wrap, decimal rupees — snapshot of siteSettings.commissions.giftWrapFee at order time. */
@@ -231,12 +231,15 @@ export interface Order {
231
231
  /**
232
232
  * Paid add-ons the buyer opted into for THIS order (= this store).
233
233
  *
234
- * These are operational flags, not just billing records:
235
- * `giftWrapAddon`/`shipmentProtectionAddon` are what
234
+ * These are operational flags, not just billing records: `whatsappNotifyAddon`
235
+ * is what the status-change notifier checks to decide whether to send a
236
+ * WhatsApp message, and `giftWrapAddon`/`shipmentProtectionAddon` are what
236
237
  * the packer needs to see before dispatch. They live on the order itself and
237
238
  * are filterable (`orders` SIEVE_FIELDS) precisely so nobody has to keep a
238
239
  * separate list of who asked for what.
239
240
  */
241
+ whatsappNotifyAddon?: boolean;
242
+ whatsappNotifyFee?: number;
240
243
  giftWrapAddon?: boolean;
241
244
  giftWrapFee?: number;
242
245
  /** The buyer's gift message — the packer has to physically include this. */
@@ -169,6 +169,15 @@ export const SITE_SETTINGS_ENTRIES = [
169
169
  tab: "notifications",
170
170
  kind: "toggle",
171
171
  },
172
+ {
173
+ slug: "whatsapp-notifications",
174
+ anchored: true,
175
+ label: "WhatsApp notifications",
176
+ description: "Send order updates over WhatsApp.",
177
+ keywords: ["whatsapp", "order updates", "meta"],
178
+ tab: "notifications",
179
+ kind: "toggle",
180
+ },
172
181
  {
173
182
  slug: "daily-digest",
174
183
  anchored: true,
@@ -14,7 +14,7 @@
14
14
  */
15
15
  import { BaseRepository } from "../../../providers/db-firebase";
16
16
  import type { DocumentSnapshot } from "firebase-admin/firestore";
17
- import { type AnalyticsAlertDocument, type AnalyticsCardDocument, type ItemRequestDocument, type ItemRequestReply, type ListingTemplateDocument, type ModerationQueueDocument, type PayoutMethodDocument, type ReportDocument, type ShippingConfigDocument, type StoreCategoryDocument, type StoreGoogleConfigDocument } from "../schemas/firestore";
17
+ import { type AnalyticsAlertDocument, type AnalyticsCardDocument, type ItemRequestDocument, type ItemRequestReply, type ListingTemplateDocument, type ModerationQueueDocument, type PayoutMethodDocument, type ReportDocument, type ShippingConfigDocument, type StoreCategoryDocument, type StoreGoogleConfigDocument, type StoreWhatsAppConfigDocument } from "../schemas/firestore";
18
18
  export declare class PayoutMethodsRepository extends BaseRepository<PayoutMethodDocument> {
19
19
  /**
20
20
  * Bank/UPI details, encrypted at rest by BaseRepository's write hooks.
@@ -103,6 +103,10 @@ export declare class ItemRequestsRepository extends BaseRepository<ItemRequestDo
103
103
  */
104
104
  appendReply(id: string, reply: ItemRequestReply): Promise<void>;
105
105
  }
106
+ export declare class StoreWhatsAppConfigRepository extends BaseRepository<StoreWhatsAppConfigDocument> {
107
+ constructor();
108
+ getByStore(storeId: string): Promise<StoreWhatsAppConfigDocument | null>;
109
+ }
106
110
  export declare class StoreGoogleConfigRepository extends BaseRepository<StoreGoogleConfigDocument> {
107
111
  constructor();
108
112
  getByStore(storeId: string): Promise<StoreGoogleConfigDocument | null>;
@@ -116,4 +120,5 @@ export declare const listingTemplatesRepository: ListingTemplatesRepository;
116
120
  export declare const moderationQueueRepository: ModerationQueueRepository;
117
121
  export declare const reportsRepository: ReportsRepository;
118
122
  export declare const itemRequestsRepository: ItemRequestsRepository;
123
+ export declare const storeWhatsAppConfigRepository: StoreWhatsAppConfigRepository;
119
124
  export declare const storeGoogleConfigRepository: StoreGoogleConfigRepository;
@@ -15,7 +15,7 @@
15
15
  import { BaseRepository } from "../../../providers/db-firebase/index.js";
16
16
  import { arrayUnion, increment } from "../../../contracts/field-ops.js";
17
17
  import { decryptPiiFields } from "../../../security/pii-encrypt.js";
18
- import { ANALYTICS_ALERTS_COLLECTION, ANALYTICS_CARDS_COLLECTION, ITEM_REQUESTS_COLLECTION, LISTING_TEMPLATES_COLLECTION, MODERATION_QUEUE_COLLECTION, PAYOUT_METHODS_COLLECTION, REPORTS_COLLECTION, SHIPPING_CONFIGS_COLLECTION, STORE_CATEGORIES_COLLECTION, STORE_GOOGLE_CONFIG_COLLECTION, } from "../schemas/firestore.js";
18
+ import { ANALYTICS_ALERTS_COLLECTION, ANALYTICS_CARDS_COLLECTION, ITEM_REQUESTS_COLLECTION, LISTING_TEMPLATES_COLLECTION, MODERATION_QUEUE_COLLECTION, PAYOUT_METHODS_COLLECTION, REPORTS_COLLECTION, SHIPPING_CONFIGS_COLLECTION, STORE_CATEGORIES_COLLECTION, STORE_GOOGLE_CONFIG_COLLECTION, STORE_WHATSAPP_CONFIG_COLLECTION, } from "../schemas/firestore.js";
19
19
  export class PayoutMethodsRepository extends BaseRepository {
20
20
  constructor() {
21
21
  super(PAYOUT_METHODS_COLLECTION);
@@ -149,6 +149,14 @@ export class ItemRequestsRepository extends BaseRepository {
149
149
  });
150
150
  }
151
151
  }
152
+ export class StoreWhatsAppConfigRepository extends BaseRepository {
153
+ constructor() {
154
+ super(STORE_WHATSAPP_CONFIG_COLLECTION);
155
+ }
156
+ async getByStore(storeId) {
157
+ return this.findOneBy("storeId", storeId);
158
+ }
159
+ }
152
160
  export class StoreGoogleConfigRepository extends BaseRepository {
153
161
  constructor() {
154
162
  super(STORE_GOOGLE_CONFIG_COLLECTION);
@@ -166,4 +174,5 @@ export const listingTemplatesRepository = new ListingTemplatesRepository();
166
174
  export const moderationQueueRepository = new ModerationQueueRepository();
167
175
  export const reportsRepository = new ReportsRepository();
168
176
  export const itemRequestsRepository = new ItemRequestsRepository();
177
+ export const storeWhatsAppConfigRepository = new StoreWhatsAppConfigRepository();
169
178
  export const storeGoogleConfigRepository = new StoreGoogleConfigRepository();
@@ -18,6 +18,7 @@ import type { BaseDocument } from "../../../_internal/shared/types/base-document
18
18
  * 7. moderationQueue — video / media moderation queue items
19
19
  * 8. reports — buyer-submitted reports against listings/stores
20
20
  * 9. itemRequests — community item request bulletin board
21
+ * 10. storeWhatsAppConfig — per-store WhatsApp business catalogue + auto-replies
21
22
  * 11. storeGoogleConfig — per-store Google Business reviews integration
22
23
  */
23
24
  export type PayoutMethodType = "upi" | "bank" | "card" | "other";
@@ -88,7 +89,7 @@ export interface AnalyticsAlertDocument extends BaseDocument {
88
89
  threshold: number;
89
90
  windowHours: number;
90
91
  isActive: boolean;
91
- notifyChannels: Array<"in-app" | "email">;
92
+ notifyChannels: Array<"in-app" | "email" | "whatsapp">;
92
93
  /**
93
94
  * When this alert last fired.
94
95
  *
@@ -220,6 +221,21 @@ export interface ItemRequestDocument extends BaseDocument {
220
221
  export declare const ITEM_REQUESTS_COLLECTION: "itemRequests";
221
222
  export declare const ITEM_REQUEST_INDEXED_FIELDS: readonly ["opUserId", "status", "category", "createdAt"];
222
223
  export declare const DEFAULT_ITEM_REQUEST_DATA: Partial<ItemRequestDocument>;
224
+ export interface StoreWhatsAppConfigDocument extends BaseDocument {
225
+ storeId: string;
226
+ isConnected: boolean;
227
+ isPaid: boolean;
228
+ phoneNumber?: string;
229
+ businessProfileName?: string;
230
+ catalogUrl?: string;
231
+ autoReply?: string;
232
+ welcomeMessage?: string;
233
+ /** Status of pending WhatsApp Business onboarding (Meta Cloud API). */
234
+ onboardingStatus?: "pending" | "approved" | "rejected";
235
+ }
236
+ export declare const STORE_WHATSAPP_CONFIG_COLLECTION: "storeWhatsAppConfig";
237
+ export declare const STORE_WHATSAPP_CONFIG_INDEXED_FIELDS: readonly ["storeId", "isConnected", "isPaid"];
238
+ export declare const DEFAULT_STORE_WHATSAPP_CONFIG_DATA: Partial<StoreWhatsAppConfigDocument>;
223
239
  export interface StoreGoogleConfigDocument extends BaseDocument {
224
240
  storeId: string;
225
241
  isConnected: boolean;
@@ -182,6 +182,16 @@ export const DEFAULT_ITEM_REQUEST_DATA = {
182
182
  replies: [],
183
183
  replyCount: 0,
184
184
  };
185
+ export const STORE_WHATSAPP_CONFIG_COLLECTION = "storeWhatsAppConfig";
186
+ export const STORE_WHATSAPP_CONFIG_INDEXED_FIELDS = [
187
+ "storeId",
188
+ "isConnected",
189
+ "isPaid",
190
+ ];
191
+ export const DEFAULT_STORE_WHATSAPP_CONFIG_DATA = {
192
+ isConnected: false,
193
+ isPaid: false,
194
+ };
185
195
  export const STORE_GOOGLE_CONFIG_COLLECTION = "storeGoogleConfig";
186
196
  export const STORE_GOOGLE_CONFIG_INDEXED_FIELDS = [
187
197
  "storeId",
@@ -45,6 +45,7 @@ const CAPABILITY_GROUPS = [
45
45
  { cap: "lower_commission_rate", unlocks: "Negotiated reduced platform fee. Requires a signed agreement and minimum volume.", defaultOn: false },
46
46
  { cap: "early_access_features", unlocks: "Access to beta features before they are publicly available.", defaultOn: false },
47
47
  { cap: "advanced_analytics", unlocks: "Cohort retention, basket analysis, and top-buyer breakdown (beyond summary stats).", defaultOn: false },
48
+ { cap: "whatsapp_catalog_sync", unlocks: "Sync your store products to your WhatsApp Business Catalog automatically.", defaultOn: false },
48
49
  ],
49
50
  },
50
51
  ];
@@ -27,7 +27,7 @@ const GUIDE_CARDS = [
27
27
  {
28
28
  Icon: Settings,
29
29
  title: "Settings",
30
- description: "Store profile, shipping config, return policy, and vacation mode.",
30
+ description: "Store profile, shipping config, return policy, vacation mode, and WhatsApp integration.",
31
31
  href: String(ROUTES.STORE.GUIDE_SETTINGS),
32
32
  },
33
33
  {
@@ -26,7 +26,11 @@ function ReturnPolicySection() {
26
26
  function VacationModeSection() {
27
27
  return (_jsxs(Div, { className: SECTION_BODY, children: [_jsxs(Div, { children: [_jsx(Text, { className: SUBHEADING, children: "What vacation mode does" }), _jsxs(Ul, { spacing: "tight", indent: "lg", children: [_jsx(Li, { className: LIST_DISC, children: "All your published listings are hidden from search results and browse pages." }), _jsx(Li, { className: LIST_DISC, children: "Your store profile page shows a \"On Vacation\" banner." }), _jsx(Li, { className: LIST_DISC, children: "Buyers cannot add your items to cart or checkout while on vacation." }), _jsx(Li, { className: LIST_DISC, children: "Existing orders that were placed before vacation mode must still be fulfilled." })] })] }), _jsxs(Div, { children: [_jsx(Text, { className: SUBHEADING, children: "How to enable" }), _jsx(Text, { className: "leading-relaxed", children: "Go to your Store Dashboard \u2192 Settings \u2192 Storefront tab. Toggle \"Vacation mode\" on. Your listings re-appear instantly when you toggle it off." })] }), _jsx(Alert, { variant: "warning", title: "Ongoing orders are not paused", children: "Enabling vacation mode does not pause or cancel any existing orders. You are still responsible for fulfilling orders placed before vacation mode was enabled." })] }));
28
28
  }
29
- // -- Section 5: Visibility states ---------------------------------------------
29
+ // -- Section 5: WhatsApp integration ------------------------------------------
30
+ function WhatsAppSection() {
31
+ return (_jsxs(Div, { className: SECTION_BODY, children: [_jsxs(Text, { className: "leading-relaxed", children: ["The WhatsApp integration allows your customers to contact you via WhatsApp from your store profile and lets you sync your product catalog to your WhatsApp Business Catalog (requires the", _jsx(Code, { className: "mx-1", size: "xs", padding: "xs", rounded: "default", surface: "subtle", children: "whatsapp_catalog_sync" }), " capability)."] }), _jsxs(Text, { className: "leading-relaxed", children: ["Full settings and the catalog sync toggle are at", " ", _jsx(TextLink, { variant: "bare", href: String(ROUTES.STORE.WHATSAPP), className: "underline text-[var(--appkit-color-primary)] hover:opacity-80", children: "Settings \u2192 WhatsApp" }), ". See the WhatsApp settings page for step-by-step setup instructions."] })] }));
32
+ }
33
+ // -- Section 6: Visibility states ---------------------------------------------
30
34
  function VisibilityStatesSection() {
31
35
  const STATES = [
32
36
  {
@@ -49,5 +53,5 @@ function VisibilityStatesSection() {
49
53
  return (_jsx(Stack, { gap: "3", children: STATES.map(({ state, note }) => (_jsxs(Row, { textSize: "sm", gap: "3", children: [_jsx(Text, { className: "shrink-0 w-32 text-[var(--appkit-color-text)]", weight: "semibold", children: state }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)] leading-relaxed", children: note })] }, state))) }));
50
54
  }
51
55
  export function StoreSettingsGuideView(_props) {
52
- return (_jsxs(Stack, { gap: "lg", padding: "b-2xl", children: [_jsxs(TextLink, { variant: "bare", href: String(ROUTES.STORE.GUIDE), size: "sm", layout: "inline-flex", align: "center", gap: "sm", className: "text-[var(--appkit-color-text-muted)] hover:text-[var(--appkit-color-text)] transition-colors", children: [_jsx(ArrowLeft, { className: "h-4 w-4" }), "Back to Seller Guide"] }), _jsxs(Div, { children: [_jsx(Heading, { level: 1, className: "text-[var(--appkit-color-text)]", size: "2xl", weight: "bold", children: "Settings Guide" }), _jsx(Text, { className: "mt-1 text-[var(--appkit-color-text-muted)]", size: "sm", children: "Store profile, shipping, returns, vacation mode, and visibility." })] }), _jsx(GuideSection, { title: "1. Store profile", children: _jsx(StoreProfileSection, {}) }), _jsx(GuideSection, { title: "2. Shipping configuration", children: _jsx(ShippingSection, {}) }), _jsx(GuideSection, { title: "3. Return policy", children: _jsx(ReturnPolicySection, {}) }), _jsx(GuideSection, { title: "4. Vacation mode", children: _jsx(VacationModeSection, {}) }), _jsx(GuideSection, { title: "5. Visibility states", children: _jsx(VisibilityStatesSection, {}) })] }));
56
+ return (_jsxs(Stack, { gap: "lg", padding: "b-2xl", children: [_jsxs(TextLink, { variant: "bare", href: String(ROUTES.STORE.GUIDE), size: "sm", layout: "inline-flex", align: "center", gap: "sm", className: "text-[var(--appkit-color-text-muted)] hover:text-[var(--appkit-color-text)] transition-colors", children: [_jsx(ArrowLeft, { className: "h-4 w-4" }), "Back to Seller Guide"] }), _jsxs(Div, { children: [_jsx(Heading, { level: 1, className: "text-[var(--appkit-color-text)]", size: "2xl", weight: "bold", children: "Settings Guide" }), _jsx(Text, { className: "mt-1 text-[var(--appkit-color-text-muted)]", size: "sm", children: "Store profile, shipping, returns, vacation mode, WhatsApp, and visibility." })] }), _jsx(GuideSection, { title: "1. Store profile", children: _jsx(StoreProfileSection, {}) }), _jsx(GuideSection, { title: "2. Shipping configuration", children: _jsx(ShippingSection, {}) }), _jsx(GuideSection, { title: "3. Return policy", children: _jsx(ReturnPolicySection, {}) }), _jsx(GuideSection, { title: "4. Vacation mode", children: _jsx(VacationModeSection, {}) }), _jsx(GuideSection, { title: "5. WhatsApp integration", children: _jsx(WhatsAppSection, {}) }), _jsx(GuideSection, { title: "6. Visibility states", children: _jsx(VisibilityStatesSection, {}) })] }));
53
57
  }
@@ -4,7 +4,7 @@
4
4
  * Data access layer for the `stores` Firestore collection.
5
5
  * One store per seller; identified by storeSlug (used as document ID).
6
6
  */
7
- import { BaseRepository, type SieveModel, type FirebaseSieveResult } from "../../../providers/db-firebase";
7
+ import { BaseRepository, type SieveModel, type FirebaseSieveResult, type DocumentSnapshot } from "../../../providers/db-firebase";
8
8
  import { StoreDocument } from "../schemas";
9
9
  import { type HistoryActor } from "../../../_internal/shared/history/index";
10
10
  import type { JsonValue } from "../../../schemas/types";
@@ -17,6 +17,28 @@ export interface StoreWriteContext {
17
17
  }
18
18
  export declare class StoreRepository extends BaseRepository<StoreDocument> {
19
19
  constructor();
20
+ protected mapDoc<D = StoreDocument>(snap: DocumentSnapshot): D;
21
+ /**
22
+ * LIST reads never carry the WhatsApp token — not even encrypted.
23
+ *
24
+ * `mapDoc` above decrypts it, and `listStores(model, activeOnly)` backs the
25
+ * PUBLIC `/stores` page. Once `sieveQuery` started routing list rows through
26
+ * `mapDoc` (it previously mapped documents itself and skipped every
27
+ * override), that decrypt would have put a plaintext Meta OAuth bearer token
28
+ * on a public listing — the exact shape of Root Cause #70.
29
+ *
30
+ * So the list path deliberately does NOT call `decryptSecrets`, and drops
31
+ * `accessToken` outright: a list has no legitimate use for it, and a value
32
+ * that is never in the payload cannot be leaked by a projection someone
33
+ * forgets to write.
34
+ *
35
+ * Detail reads are unchanged — `findBySlug`/`findById` still decrypt, so the
36
+ * WhatsApp settings and catalog-sync routes keep working exactly as before.
37
+ * Removing the decrypt from `mapDoc` itself is a separate, riskier change
38
+ * that CLAUDE.md explicitly defers; this closes the public-exposure half
39
+ * without touching the encryption round-trip at all.
40
+ */
41
+ update(id: string, data: Partial<StoreDocument>): Promise<StoreDocument>;
20
42
  /**
21
43
  * Create a new store.
22
44
  * The document ID is set to storeSlug for easy URL-based lookups.
@@ -16,6 +16,32 @@ export class StoreRepository extends BaseRepository {
16
16
  constructor() {
17
17
  super(STORE_COLLECTION);
18
18
  }
19
+ mapDoc(snap) {
20
+ return super.mapDoc(snap);
21
+ }
22
+ /**
23
+ * LIST reads never carry the WhatsApp token — not even encrypted.
24
+ *
25
+ * `mapDoc` above decrypts it, and `listStores(model, activeOnly)` backs the
26
+ * PUBLIC `/stores` page. Once `sieveQuery` started routing list rows through
27
+ * `mapDoc` (it previously mapped documents itself and skipped every
28
+ * override), that decrypt would have put a plaintext Meta OAuth bearer token
29
+ * on a public listing — the exact shape of Root Cause #70.
30
+ *
31
+ * So the list path deliberately does NOT call `decryptSecrets`, and drops
32
+ * `accessToken` outright: a list has no legitimate use for it, and a value
33
+ * that is never in the payload cannot be leaked by a projection someone
34
+ * forgets to write.
35
+ *
36
+ * Detail reads are unchanged — `findBySlug`/`findById` still decrypt, so the
37
+ * WhatsApp settings and catalog-sync routes keep working exactly as before.
38
+ * Removing the decrypt from `mapDoc` itself is a separate, riskier change
39
+ * that CLAUDE.md explicitly defers; this closes the public-exposure half
40
+ * without touching the encryption round-trip at all.
41
+ */
42
+ async update(id, data) {
43
+ return super.update(id, data);
44
+ }
19
45
  /**
20
46
  * Create a new store.
21
47
  * The document ID is set to storeSlug for easy URL-based lookups.
@@ -206,7 +232,9 @@ export class StoreRepository extends BaseRepository {
206
232
  options: { maxPageSize: win.maxPageSize(100) },
207
233
  fields: sieveFields,
208
234
  // Called DIRECTLY, not through `sieveQuery`, so the mapper has to be
209
- // passed by hand. With `activeOnly` this is the public /stores listing.
235
+ // passed by hand — and it is the list mapper, never `mapDoc`. With
236
+ // `activeOnly` this is the public /stores listing; `mapDoc` decrypts the
237
+ // WhatsApp bearer token and a list has no use for it.
210
238
  mapDoc: (snap) => this.mapDocForList(snap),
211
239
  });
212
240
  const searched = win.finish(result);
@@ -107,6 +107,19 @@ export interface StoreDocument extends BaseDocument {
107
107
  adminNotes?: string;
108
108
  /** Admin "verified store" badge — distinct from `status`/`isPublic`. */
109
109
  isVerified?: boolean;
110
+ /**
111
+ * Does this store subscribe to WhatsApp order notifications?
112
+ *
113
+ * A monthly add-on priced by the admin (`commissions.whatsappNotifyMonthlyFee`),
114
+ * not a per-order charge: the platform owns one WhatsApp number, so the cost
115
+ * is standing and borne by the seller who benefits, rather than a line item
116
+ * a buyer opts into per order.
117
+ *
118
+ * 🛑 Absent means NOT subscribed, and the gate fails closed on it. Defaulting
119
+ * a missing flag to `true` would message customers on behalf of every seller
120
+ * who never signed up, and bill the platform for each template.
121
+ */
122
+ whatsappNotifySubscribed?: boolean;
110
123
  /**
111
124
  * Paid at SHIPPING rather than at delivery.
112
125
  *
@@ -370,12 +383,9 @@ export interface StoreDocument extends BaseDocument {
370
383
  */
371
384
  export declare const STORE_TRACKED_FIELDS: readonly ["status", "isPublic", "isVerified", "isFeatured", "suspensionReason", "capabilities"];
372
385
  /**
373
- * PII on this document, for the `withHistory` scrub.
374
- *
375
- * `accessToken` stays listed although the WhatsApp integration that produced
376
- * one was removed 2026-10-20: a scrub list is cheap, and a future secret
377
- * reaching this document under that name should be scrubbed by default
378
- * rather than by someone remembering to add it.
386
+ * PII on this document, for `withHistory`'s scrub. `whatsappConfig.accessToken`
387
+ * is a live Meta credential; `mapDoc` decrypts it on EVERY read, so it is
388
+ * present in memory whenever a diff runs.
379
389
  */
380
390
  export declare const STORE_HISTORY_PII_FIELDS: readonly ["accessToken", "payoutDetails", "upiVpa", "accountNumber"];
381
391
  export type { StoreCapability } from "../../auth/permissions/constants";
@@ -33,12 +33,9 @@ export const STORE_TRACKED_FIELDS = [
33
33
  "capabilities",
34
34
  ];
35
35
  /**
36
- * PII on this document, for the `withHistory` scrub.
37
- *
38
- * `accessToken` stays listed although the WhatsApp integration that produced
39
- * one was removed 2026-10-20: a scrub list is cheap, and a future secret
40
- * reaching this document under that name should be scrubbed by default
41
- * rather than by someone remembering to add it.
36
+ * PII on this document, for `withHistory`'s scrub. `whatsappConfig.accessToken`
37
+ * is a live Meta credential; `mapDoc` decrypts it on EVERY read, so it is
38
+ * present in memory whenever a diff runs.
42
39
  */
43
40
  export const STORE_HISTORY_PII_FIELDS = [
44
41
  "accessToken",
@@ -347,6 +347,60 @@ export declare const storeFirestoreSchema: z.ZodObject<{
347
347
  defaultProviderId: string;
348
348
  }>>;
349
349
  emiEnabled: z.ZodOptional<z.ZodBoolean>;
350
+ whatsappConfig: z.ZodOptional<z.ZodObject<{
351
+ phoneNumber: z.ZodOptional<z.ZodString>;
352
+ wabaId: z.ZodOptional<z.ZodString>;
353
+ catalogId: z.ZodOptional<z.ZodString>;
354
+ accessToken: z.ZodOptional<z.ZodString>;
355
+ catalogSyncEnabled: z.ZodBoolean;
356
+ lastCatalogSyncAt: z.ZodOptional<z.ZodUnion<[z.ZodDate, z.ZodString, z.ZodObject<{
357
+ toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
358
+ }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
359
+ toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
360
+ }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
361
+ toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
362
+ }, z.ZodTypeAny, "passthrough">>]>>;
363
+ lastSyncCount: z.ZodOptional<z.ZodNumber>;
364
+ lastSyncStatus: z.ZodOptional<z.ZodEnum<["success", "partial", "failed"]>>;
365
+ connected: z.ZodBoolean;
366
+ connectedAt: z.ZodOptional<z.ZodUnion<[z.ZodDate, z.ZodString, z.ZodObject<{
367
+ toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
368
+ }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
369
+ toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
370
+ }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
371
+ toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
372
+ }, z.ZodTypeAny, "passthrough">>]>>;
373
+ }, "strip", z.ZodTypeAny, {
374
+ catalogSyncEnabled: boolean;
375
+ connected: boolean;
376
+ phoneNumber?: string | undefined;
377
+ accessToken?: string | undefined;
378
+ wabaId?: string | undefined;
379
+ catalogId?: string | undefined;
380
+ lastCatalogSyncAt?: string | Date | z.objectOutputType<{
381
+ toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
382
+ }, z.ZodTypeAny, "passthrough"> | undefined;
383
+ lastSyncCount?: number | undefined;
384
+ lastSyncStatus?: "success" | "failed" | "partial" | undefined;
385
+ connectedAt?: string | Date | z.objectOutputType<{
386
+ toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
387
+ }, z.ZodTypeAny, "passthrough"> | undefined;
388
+ }, {
389
+ catalogSyncEnabled: boolean;
390
+ connected: boolean;
391
+ phoneNumber?: string | undefined;
392
+ accessToken?: string | undefined;
393
+ wabaId?: string | undefined;
394
+ catalogId?: string | undefined;
395
+ lastCatalogSyncAt?: string | Date | z.objectInputType<{
396
+ toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
397
+ }, z.ZodTypeAny, "passthrough"> | undefined;
398
+ lastSyncCount?: number | undefined;
399
+ lastSyncStatus?: "success" | "failed" | "partial" | undefined;
400
+ connectedAt?: string | Date | z.objectInputType<{
401
+ toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
402
+ }, z.ZodTypeAny, "passthrough"> | undefined;
403
+ }>>;
350
404
  }, "strip", z.ZodTypeAny, {
351
405
  status: "pending" | "active" | "rejected" | "suspended";
352
406
  id: string;
@@ -416,6 +470,22 @@ export declare const storeFirestoreSchema: z.ZodObject<{
416
470
  maxReviews?: number | undefined;
417
471
  minRating?: number | undefined;
418
472
  } | undefined;
473
+ whatsappConfig?: {
474
+ catalogSyncEnabled: boolean;
475
+ connected: boolean;
476
+ phoneNumber?: string | undefined;
477
+ accessToken?: string | undefined;
478
+ wabaId?: string | undefined;
479
+ catalogId?: string | undefined;
480
+ lastCatalogSyncAt?: string | Date | z.objectOutputType<{
481
+ toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
482
+ }, z.ZodTypeAny, "passthrough"> | undefined;
483
+ lastSyncCount?: number | undefined;
484
+ lastSyncStatus?: "success" | "failed" | "partial" | undefined;
485
+ connectedAt?: string | Date | z.objectOutputType<{
486
+ toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
487
+ }, z.ZodTypeAny, "passthrough"> | undefined;
488
+ } | undefined;
419
489
  }, {
420
490
  status: "pending" | "active" | "rejected" | "suspended";
421
491
  id: string;
@@ -485,6 +555,22 @@ export declare const storeFirestoreSchema: z.ZodObject<{
485
555
  maxReviews?: number | undefined;
486
556
  minRating?: number | undefined;
487
557
  } | undefined;
558
+ whatsappConfig?: {
559
+ catalogSyncEnabled: boolean;
560
+ connected: boolean;
561
+ phoneNumber?: string | undefined;
562
+ accessToken?: string | undefined;
563
+ wabaId?: string | undefined;
564
+ catalogId?: string | undefined;
565
+ lastCatalogSyncAt?: string | Date | z.objectInputType<{
566
+ toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
567
+ }, z.ZodTypeAny, "passthrough"> | undefined;
568
+ lastSyncCount?: number | undefined;
569
+ lastSyncStatus?: "success" | "failed" | "partial" | undefined;
570
+ connectedAt?: string | Date | z.objectInputType<{
571
+ toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
572
+ }, z.ZodTypeAny, "passthrough"> | undefined;
573
+ } | undefined;
488
574
  }>;
489
575
  export type StoreFromSchema = z.infer<typeof storeFirestoreSchema>;
490
576
  /** Zod schema for store status — use instead of inline `z.enum(["pending",...])`. */
@@ -92,6 +92,20 @@ export const storeFirestoreSchema = z.object({
92
92
  .optional(),
93
93
  shippingConfig: storeShippingConfigSchema.optional(),
94
94
  emiEnabled: z.boolean().optional(),
95
+ whatsappConfig: z
96
+ .object({
97
+ phoneNumber: z.string().optional(),
98
+ wabaId: z.string().optional(),
99
+ catalogId: z.string().optional(),
100
+ accessToken: z.string().optional(),
101
+ catalogSyncEnabled: z.boolean(),
102
+ lastCatalogSyncAt: firestoreDateSchema.optional(),
103
+ lastSyncCount: z.number().int().optional(),
104
+ lastSyncStatus: z.enum(["success", "partial", "failed"]).optional(),
105
+ connected: z.boolean(),
106
+ connectedAt: firestoreDateSchema.optional(),
107
+ })
108
+ .optional(),
95
109
  ...auditTimestampsShape,
96
110
  });
97
111
  /** Zod schema for store status — use instead of inline `z.enum(["pending",...])`. */
@@ -0,0 +1,12 @@
1
+ /**
2
+ * feat-whatsapp-bot — POST /api/whatsapp/send-status
3
+ *
4
+ * WhatsApp message delivery status update.
5
+ *
6
+ * Consumer stub:
7
+ * ```ts
8
+ * export { whatsappSendStatusPOST as POST } from "@mohasinac/feat-whatsapp-bot";
9
+ * ```
10
+ */
11
+ import { NextResponse } from "next/server.js";
12
+ export declare function POST(request: Request): Promise<NextResponse>;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * feat-whatsapp-bot — POST /api/whatsapp/send-status
3
+ *
4
+ * WhatsApp message delivery status update.
5
+ *
6
+ * Consumer stub:
7
+ * ```ts
8
+ * export { whatsappSendStatusPOST as POST } from "@mohasinac/feat-whatsapp-bot";
9
+ * ```
10
+ */
11
+ import { NextResponse } from "next/server.js";
12
+ import { getProviders } from "../../../../contracts/index.js";
13
+ import { normalizeError } from "../../../../errors/normalize.js";
14
+ export async function POST(request) {
15
+ try {
16
+ const body = await request.json();
17
+ const { db } = getProviders();
18
+ if (!db)
19
+ return NextResponse.json({ success: false, error: "DB not configured" }, { status: 503 });
20
+ // Update message status in database
21
+ const repo = db.getRepository("whatsapp_message_status");
22
+ const updated = await repo.create({
23
+ ...body,
24
+ updatedAt: new Date(),
25
+ });
26
+ return NextResponse.json({
27
+ success: true,
28
+ data: updated,
29
+ });
30
+ }
31
+ catch (error) {
32
+ void normalizeError(error);
33
+ return NextResponse.json({
34
+ success: false,
35
+ error: error instanceof Error ? error.message : "Internal server error",
36
+ }, { status: 500 });
37
+ }
38
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * feat-whatsapp-bot — POST /api/whatsapp/webhook
3
+ *
4
+ * WhatsApp webhook receiver for incoming messages.
5
+ *
6
+ * Consumer stub:
7
+ * ```ts
8
+ * export { whatsappWebhookPOST as POST } from "@mohasinac/feat-whatsapp-bot";
9
+ * ```
10
+ */
11
+ import { NextResponse } from "next/server.js";
12
+ export declare function POST(request: Request): Promise<NextResponse>;
@@ -0,0 +1,43 @@
1
+ /**
2
+ * feat-whatsapp-bot — POST /api/whatsapp/webhook
3
+ *
4
+ * WhatsApp webhook receiver for incoming messages.
5
+ *
6
+ * Consumer stub:
7
+ * ```ts
8
+ * export { whatsappWebhookPOST as POST } from "@mohasinac/feat-whatsapp-bot";
9
+ * ```
10
+ */
11
+ import { NextResponse } from "next/server.js";
12
+ import { getProviders } from "../../../../contracts/index.js";
13
+ import { normalizeError } from "../../../../errors/normalize.js";
14
+ export async function POST(request) {
15
+ try {
16
+ const body = await request.json();
17
+ const { db, eventBus } = getProviders();
18
+ if (!db)
19
+ return NextResponse.json({ success: false, error: "DB not configured" }, { status: 503 });
20
+ // Store webhook payload for processing
21
+ const repo = db.getRepository("whatsapp_messages");
22
+ const created = await repo.create({
23
+ ...body,
24
+ receivedAt: new Date(),
25
+ processed: false,
26
+ });
27
+ // Emit event for async processing if eventBus available
28
+ if (eventBus) {
29
+ await eventBus.emit("whatsapp.message.received", created);
30
+ }
31
+ return NextResponse.json({
32
+ success: true,
33
+ data: created,
34
+ }, { status: 201 });
35
+ }
36
+ catch (error) {
37
+ void normalizeError(error);
38
+ return NextResponse.json({
39
+ success: false,
40
+ error: error instanceof Error ? error.message : "Internal server error",
41
+ }, { status: 500 });
42
+ }
43
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * WHY: a seller's WhatsApp settings are now ONE decision — do they pay the
3
+ * monthly fee to have order updates sent to their buyers on the
4
+ * platform's WhatsApp number.
5
+ *
6
+ * 🛑 This replaced a 574-line credential form that asked every seller for a
7
+ * WhatsApp Business Account ID, a Catalogue ID and a **System User Access
8
+ * Token**, then stored that token encrypted on their store document.
9
+ *
10
+ * Three things were wrong with it, and they are why none of it is coming back:
11
+ *
12
+ * 1. Almost no small seller will create a Meta app and mint a system user
13
+ * token. A setting nobody can complete is a feature nobody has.
14
+ * 2. It made LetItRip the custodian of a live Meta credential per seller.
15
+ * `StoreRepository.mapDoc` decrypted that token on EVERY read, so any
16
+ * code path that forgot to project was handling plaintext — a gap
17
+ * CLAUDE.md flags as deserving its own session.
18
+ * 3. The catalogue half it existed for is gone: `/whatsapp/{slug}/feed.csv`
19
+ * is a public feed the seller points their own Commerce Manager at, so
20
+ * no credential crosses the boundary at all.
21
+ *
22
+ * WHAT: subscription status, the price, and the toggle.
23
+ *
24
+ * ## Presentational on purpose
25
+ *
26
+ * Takes the price and the current state as PROPS rather than reading site
27
+ * settings itself. Site settings are server-read and passed down — the
28
+ * monthly fee lives beside the platform's Meta credentials in a document that
29
+ * must never reach a browser, and a client hook here would be a second reader
30
+ * of it (Root Cause #70).
31
+ *
32
+ * EXPORTS: SellerWhatsAppSettingsView, SellerWhatsAppSettingsViewProps
33
+ *
34
+ * @tag domain:whatsapp
35
+ * @tag layer:component
36
+ * @tag access:client
37
+ */
38
+ import React from "react";
39
+ export interface SellerWhatsAppSettingsViewProps {
40
+ /** Is this store currently subscribed? Server-resolved from the store document. */
41
+ subscribed: boolean;
42
+ /** Admin-set monthly price, in rupees. Server-resolved from site settings. */
43
+ monthlyFee: number;
44
+ /** Currency for the price label. */
45
+ currency?: string;
46
+ /**
47
+ * Is the channel switched on platform-wide? When false the toggle is
48
+ * disabled and says so — a seller who subscribes into a disabled channel
49
+ * pays for silence.
50
+ */
51
+ channelEnabled: boolean;
52
+ /** Persists the change. Resolves to the new state. */
53
+ onToggleSubscription: (next: boolean) => Promise<void>;
54
+ /** Gate from the store's capabilities; false renders a read-only explanation. */
55
+ hasCapability?: boolean;
56
+ }
57
+ export declare function SellerWhatsAppSettingsView({ subscribed, monthlyFee, currency, channelEnabled, onToggleSubscription, hasCapability, }: SellerWhatsAppSettingsViewProps): React.JSX.Element;