@mohasinac/appkit 4.1.1 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (225) hide show
  1. package/dist/_internal/client/features/maintenance/index.d.ts +2 -0
  2. package/dist/_internal/client/features/maintenance/index.js +1 -0
  3. package/dist/_internal/client/features/maintenance/views/CloudLogsListView.d.ts +9 -0
  4. package/dist/_internal/client/features/maintenance/views/CloudLogsListView.js +55 -0
  5. package/dist/_internal/server/features/auctions/index.d.ts +1 -1
  6. package/dist/_internal/server/features/auctions/index.js +1 -1
  7. package/dist/_internal/server/features/auctions/service.js +2 -2
  8. package/dist/_internal/server/features/checkout/actions.d.ts +15 -0
  9. package/dist/_internal/server/features/checkout/actions.js +245 -182
  10. package/dist/_internal/server/features/maintenance/cloud-logs-data.d.ts +50 -0
  11. package/dist/_internal/server/features/maintenance/cloud-logs-data.js +152 -0
  12. package/dist/_internal/server/functions/scheduled.d.ts +2 -1
  13. package/dist/_internal/server/functions/scheduled.js +9 -1
  14. package/dist/_internal/server/jobs/core/jobRunners.js +10 -0
  15. package/dist/_internal/server/jobs/core/newsletterExport.d.ts +15 -0
  16. package/dist/_internal/server/jobs/core/newsletterExport.js +43 -0
  17. package/dist/_internal/server/jobs/core/onOrderCreate.d.ts +4 -12
  18. package/dist/_internal/server/jobs/core/onOrderCreate.js +3 -3
  19. package/dist/_internal/server/jobs/core/onOrderStatusChange.d.ts +2 -0
  20. package/dist/_internal/server/jobs/core/onOrderStatusChange.js +1 -0
  21. package/dist/_internal/server/jobs/core/onProductWrite.js +14 -2
  22. package/dist/_internal/server/jobs/core/paymentWindowTimeout.js +1 -0
  23. package/dist/_internal/server/jobs/core/pendingOrderTimeout.js +1 -0
  24. package/dist/_internal/server/jobs/core/revenueRollup.d.ts +13 -0
  25. package/dist/_internal/server/jobs/core/revenueRollup.js +22 -0
  26. package/dist/_internal/server/jobs/core/whatsappNotify.d.ts +35 -0
  27. package/dist/_internal/server/jobs/core/whatsappNotify.js +82 -0
  28. package/dist/_internal/server/jobs/handlers/index.d.ts +1 -0
  29. package/dist/_internal/server/jobs/handlers/index.js +1 -0
  30. package/dist/_internal/server/jobs/handlers/revenueRollup.d.ts +2 -0
  31. package/dist/_internal/server/jobs/handlers/revenueRollup.js +2 -0
  32. package/dist/_internal/shared/features/auctions/config.d.ts +1 -1
  33. package/dist/_internal/shared/features/auctions/config.js +1 -1
  34. package/dist/_internal/shared/features/auctions/schema.js +1 -1
  35. package/dist/_internal/shared/features/cart/schema.js +2 -2
  36. package/dist/_internal/shared/features/checkout/config.d.ts +7 -0
  37. package/dist/_internal/shared/features/checkout/config.js +7 -0
  38. package/dist/_internal/shared/features/events/schema.js +1 -1
  39. package/dist/_internal/shared/features/orders/schema.js +1 -1
  40. package/dist/_internal/shared/features/products/schema.js +3 -3
  41. package/dist/_internal/shared/features/promotions/schema.js +3 -3
  42. package/dist/_internal/shared/fees/calculator.d.ts +32 -7
  43. package/dist/_internal/shared/fees/calculator.js +25 -0
  44. package/dist/client.d.ts +4 -4
  45. package/dist/client.js +4 -2
  46. package/dist/constants/api-endpoints.d.ts +9 -6
  47. package/dist/constants/api-endpoints.js +3 -2
  48. package/dist/contracts/client-payment-gateway.d.ts +1 -2
  49. package/dist/features/about/components/AboutView.d.ts +6 -16
  50. package/dist/features/about/components/AboutView.js +3 -3
  51. package/dist/features/about/components/FeesView.js +18 -0
  52. package/dist/features/about/index.d.ts +2 -1
  53. package/dist/features/about/schemas/firestore.d.ts +47 -0
  54. package/dist/features/about/schemas/firestore.js +6 -0
  55. package/dist/features/about/schemas/index.d.ts +210 -0
  56. package/dist/features/about/schemas/index.js +45 -0
  57. package/dist/features/account/hooks/useProfile.js +3 -0
  58. package/dist/features/admin/actions/notification-actions.d.ts +4 -1
  59. package/dist/features/admin/actions/notification-actions.js +56 -10
  60. package/dist/features/admin/components/AdminAdsView.js +1 -1
  61. package/dist/features/admin/components/AdminCarouselGroupEditorView.d.ts +6 -0
  62. package/dist/features/admin/components/AdminCarouselGroupEditorView.js +50 -0
  63. package/dist/features/admin/components/AdminCartsView.js +16 -3
  64. package/dist/features/admin/components/AdminCouponEditorView.js +2 -2
  65. package/dist/features/admin/components/AdminCouponsView.js +14 -4
  66. package/dist/features/admin/components/AdminEmployeeEditorView.js +1 -42
  67. package/dist/features/admin/components/AdminMediaView.js +1 -1
  68. package/dist/features/admin/components/AdminNewsletterView.js +42 -11
  69. package/dist/features/admin/components/AdminNotificationsView.js +31 -19
  70. package/dist/features/admin/components/AdminOrdersView.js +7 -0
  71. package/dist/features/admin/components/AdminPayoutsView.js +18 -3
  72. package/dist/features/admin/components/AdminProductsView.js +17 -2
  73. package/dist/features/admin/components/AdminReviewsView.js +16 -2
  74. package/dist/features/admin/components/AdminScammersView.js +7 -0
  75. package/dist/features/admin/components/AdminSectionsView.js +4 -0
  76. package/dist/features/admin/components/AdminSiteConfigGuideView.js +1 -1
  77. package/dist/features/admin/components/AdminSiteSettingsView.js +127 -8
  78. package/dist/features/admin/components/AdminStoreEditorView.d.ts +2 -1
  79. package/dist/features/admin/components/AdminStoreEditorView.js +4 -4
  80. package/dist/features/admin/components/AdminStoresView.js +1 -1
  81. package/dist/features/admin/components/AdminSupportTicketDetailView.js +23 -5
  82. package/dist/features/admin/components/AdminSupportTicketsView.js +20 -2
  83. package/dist/features/admin/components/AdminTesterChecklistItemEditorView.js +4 -1
  84. package/dist/features/admin/components/AdminTesterChecklistView.js +8 -0
  85. package/dist/features/admin/components/index.d.ts +2 -0
  86. package/dist/features/admin/components/index.js +1 -0
  87. package/dist/features/admin/constants/filter-tabs.d.ts +55 -41
  88. package/dist/features/admin/constants/filter-tabs.js +55 -27
  89. package/dist/features/admin/repository/analytics-rollup.repository.d.ts +25 -0
  90. package/dist/features/admin/repository/analytics-rollup.repository.js +31 -0
  91. package/dist/features/admin/repository/site-settings.repository.js +8 -1
  92. package/dist/features/admin/schemas/firestore.d.ts +46 -0
  93. package/dist/features/admin/schemas/firestore.js +62 -24
  94. package/dist/features/auctions/actions/bid-actions.js +1 -1
  95. package/dist/features/auctions/components/AuctionDetailPageView.js +2 -2
  96. package/dist/features/auctions/components/AuctionsListView.js +8 -1
  97. package/dist/features/auth/components/LoginForm.js +2 -2
  98. package/dist/features/auth/components/RegisterForm.js +2 -2
  99. package/dist/features/auth/permissions/constants.d.ts +16 -0
  100. package/dist/features/auth/permissions/constants.js +53 -0
  101. package/dist/features/auth/schemas/firestore.js +1 -0
  102. package/dist/features/blog/components/BlogFilters.js +1 -1
  103. package/dist/features/cart/schemas/firestore.d.ts +1 -1
  104. package/dist/features/categories/schemas/firestore.d.ts +1 -1
  105. package/dist/features/consultation/components/ConsultationForm.js +1 -1
  106. package/dist/features/events/components/EventFilters.js +2 -0
  107. package/dist/features/homepage/components/WelcomeSection.js +2 -2
  108. package/dist/features/layout/BackToTop.d.ts +9 -5
  109. package/dist/features/layout/BackToTop.js +17 -21
  110. package/dist/features/layout/TitleBarLayout.d.ts +2 -2
  111. package/dist/features/layout/TitleBarLayout.js +4 -4
  112. package/dist/features/media/AvatarUpload.js +7 -3
  113. package/dist/features/media/MediaVideo.js +1 -1
  114. package/dist/features/media/finalize.d.ts +9 -13
  115. package/dist/features/media/finalize.js +23 -74
  116. package/dist/features/media/server.d.ts +1 -1
  117. package/dist/features/media/server.js +1 -1
  118. package/dist/features/orders/actions/refund-actions.js +1 -0
  119. package/dist/features/orders/hooks/useOrders.js +16 -4
  120. package/dist/features/orders/schemas/firestore.d.ts +19 -5
  121. package/dist/features/payments/schemas/firestore.d.ts +2 -2
  122. package/dist/features/pre-orders/components/PreOrdersListView.js +7 -0
  123. package/dist/features/products/components/ArtStickersListView.js +5 -0
  124. package/dist/features/products/components/ProductDetailPageView.d.ts +7 -1
  125. package/dist/features/products/components/ProductForm.js +14 -1
  126. package/dist/features/products/components/ProductsIndexPageView.js +7 -0
  127. package/dist/features/products/constants/action-defs.d.ts +29 -3
  128. package/dist/features/products/constants/action-defs.js +77 -1
  129. package/dist/features/products/schemas/firestore.d.ts +1 -1
  130. package/dist/features/products/schemas/product-templates.d.ts +1 -1
  131. package/dist/features/products/types/index.d.ts +4 -0
  132. package/dist/features/scams/schemas/index.js +2 -2
  133. package/dist/features/search/components/Search.js +3 -3
  134. package/dist/features/seller/components/SellerAuctionsView.js +27 -2
  135. package/dist/features/seller/components/SellerOrdersView.js +2 -2
  136. package/dist/features/seller/components/SellerPayoutsView.js +24 -10
  137. package/dist/features/seller/components/SellerPreOrdersView.js +12 -4
  138. package/dist/features/seller/components/SellerPrizeDrawsView.js +23 -5
  139. package/dist/features/seller/components/SellerProductShell.d.ts +2 -0
  140. package/dist/features/seller/components/SellerProductShell.js +1 -1
  141. package/dist/features/seller/components/SellerProductsView.js +8 -6
  142. package/dist/features/seller/components/SellerReviewsView.js +27 -6
  143. package/dist/features/seller/components/index.d.ts +0 -2
  144. package/dist/features/seller/components/index.js +0 -1
  145. package/dist/features/stores/components/StoreListingsGuideView.js +2 -1
  146. package/dist/features/stores/components/StoreProductsView.js +1 -1
  147. package/dist/features/stores/schemas/firestore.d.ts +11 -0
  148. package/dist/features/stores/schemas/firestore.js +5 -0
  149. package/dist/features/stores/schemas/index.d.ts +2 -2
  150. package/dist/features/tester/actions/checklist-item-actions.d.ts +6 -0
  151. package/dist/features/tester/actions/checklist-item-actions.js +2 -0
  152. package/dist/features/tester/components/AdminTesterFeedbackIssuesView.js +13 -2
  153. package/dist/features/tester/components/AdminTesterFeedbackListView.js +2 -1
  154. package/dist/features/tester/components/AdminTesterFeedbackReportView.js +9 -5
  155. package/dist/features/tester/components/TesterHubView.js +34 -21
  156. package/dist/features/tester/repository/tester-checklist-item.repository.js +1 -0
  157. package/dist/features/tester/repository/tester-checklist-response.repository.d.ts +18 -4
  158. package/dist/features/tester/repository/tester-checklist-response.repository.js +43 -8
  159. package/dist/features/tester/schemas/firestore.d.ts +8 -3
  160. package/dist/features/tester/schemas/firestore.js +6 -0
  161. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +21 -9
  162. package/dist/features/tester/utils/phases.d.ts +31 -0
  163. package/dist/features/tester/utils/phases.js +46 -0
  164. package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +1 -1
  165. package/dist/features/whatsapp-bot/helpers/whatsapp.d.ts +10 -1
  166. package/dist/features/whatsapp-bot/helpers/whatsapp.js +47 -0
  167. package/dist/features/whatsapp-bot/types/index.d.ts +20 -1
  168. package/dist/features/wishlist/types/index.d.ts +3 -0
  169. package/dist/index.d.ts +15 -6
  170. package/dist/index.js +19 -9
  171. package/dist/next/components/NotFoundView.js +2 -1
  172. package/dist/next/routing/route-map.d.ts +6 -12
  173. package/dist/next/routing/route-map.js +3 -4
  174. package/dist/providers/db-firebase/admin.d.ts +1 -0
  175. package/dist/providers/db-firebase/admin.js +1 -1
  176. package/dist/repositories/index.d.ts +2 -0
  177. package/dist/repositories/index.js +1 -0
  178. package/dist/schemas/registry.d.ts +2 -2
  179. package/dist/seed/addresses-seed-data.js +18 -18
  180. package/dist/seed/bids-seed-data.js +4 -4
  181. package/dist/seed/blog-posts-seed-data.js +82 -82
  182. package/dist/seed/carousel-slides-seed-data.js +14 -10
  183. package/dist/seed/carousels-seed-data.js +3 -3
  184. package/dist/seed/cart-seed-data.js +92 -92
  185. package/dist/seed/claimed-coupons-seed-data.js +10 -10
  186. package/dist/seed/conversations-seed-data.js +75 -75
  187. package/dist/seed/coupon-usage-seed-data.js +12 -12
  188. package/dist/seed/coupons-seed-data.js +41 -41
  189. package/dist/seed/events-seed-data.js +105 -105
  190. package/dist/seed/grouped-listings-seed-data.d.ts +2 -137
  191. package/dist/seed/grouped-listings-seed-data.js +79 -143
  192. package/dist/seed/history-seed-data.js +38 -38
  193. package/dist/seed/homepage-sections-seed-data.js +9 -9
  194. package/dist/seed/notifications-seed-data.js +56 -56
  195. package/dist/seed/offers-seed-data.js +91 -91
  196. package/dist/seed/orders-seed-data.js +121 -106
  197. package/dist/seed/payouts-seed-data.js +42 -42
  198. package/dist/seed/products-auctions-seed-data.js +10 -2
  199. package/dist/seed/products-standard-seed-data.js +40 -8
  200. package/dist/seed/reviews-seed-data.js +42 -23
  201. package/dist/seed/scammers-seed-data.js +22 -22
  202. package/dist/seed/sessions-seed-data.js +7 -7
  203. package/dist/seed/shipments-seed-data.js +10 -10
  204. package/dist/seed/site-settings-seed-data.js +89 -0
  205. package/dist/seed/store-addresses-seed-data.js +2 -2
  206. package/dist/seed/store-extensions-seed-data.js +93 -93
  207. package/dist/seed/stores-seed-data.js +2 -0
  208. package/dist/seed/support-tickets-seed-data.js +47 -47
  209. package/dist/seed/users-seed-data.js +55 -89
  210. package/dist/seed/wishlists-seed-data.js +22 -22
  211. package/dist/server.d.ts +4 -3
  212. package/dist/server.js +7 -7
  213. package/dist/styles.css +1 -1
  214. package/dist/tailwind-utilities.css +1 -1
  215. package/dist/ui/components/PageLoader.js +8 -3
  216. package/dist/ui/components/Select.d.ts +8 -1
  217. package/dist/ui/components/Select.js +2 -2
  218. package/dist/ui/components/SiteMark.d.ts +23 -0
  219. package/dist/ui/components/SiteMark.js +76 -0
  220. package/dist/ui/index.d.ts +2 -0
  221. package/dist/ui/index.js +1 -0
  222. package/dist/validation/schemas.d.ts +0 -2
  223. package/dist/validation/schemas.js +0 -2
  224. package/package.json +1 -1
  225. package/scripts/seed-cli.mjs +17 -15
@@ -12,14 +12,14 @@ import { roundRupees } from "../../../../utils/number.formatter";
12
12
  import { ApiError, ValidationError, NotFoundError, ERROR_MESSAGES } from "../../../../errors";
13
13
  import { ORDER_FIELDS } from "../../../../constants/field-names";
14
14
  import { serverLogger } from "../../../../monitoring";
15
- import { unitOfWork, siteSettingsRepository, userRepository, storeRepository, couponsRepository, notificationRepository, claimedCouponsRepository, addressesRepository, } from "../../../../repositories";
15
+ import { unitOfWork, siteSettingsRepository, userRepository, storeRepository, couponsRepository, claimedCouponsRepository, addressesRepository, } from "../../../../repositories";
16
16
  import { calculateGst } from "../../../shared/fees/calculator";
17
17
  import { computePreOrderDepositAmount } from "../../../shared/checkout/order-math";
18
18
  import { failedCheckoutRepository } from "../../../../features/checkout/repository/failed-checkout.repository";
19
19
  import { sendOrderConfirmationEmail } from "../../../../features/contact/server";
20
20
  import { splitCartIntoOrderGroups } from "../../../../features/orders/index";
21
21
  import { resolveDate } from "../../../../utils";
22
- import { computeCodHandlingFee } from "../../../shared/fees/calculator";
22
+ import { computeCodHandlingFee, computeWhatsAppNotifyFee, computeGiftWrapFee, computeShipmentProtectionFee, } from "../../../shared/fees/calculator";
23
23
  import { getAdminDb, getAdminRealtimeDb, RTDB_PATHS, } from "../../../../providers/db-firebase";
24
24
  import { PRODUCT_COLLECTION, PRODUCT_CODES_SUBCOLLECTION } from "../../../../features/products/schemas/firestore";
25
25
  import { CART_COLLECTION } from "../../../../features/cart/schemas/index";
@@ -112,9 +112,8 @@ function assertLiveJurisdiction(cartItemsArg, productByIdArg, buyerState) {
112
112
  * either party. We emit the buyer + seller rows here at the create boundary.
113
113
  */
114
114
  function emitOrderPlacedNotifications(args) {
115
- const { orderId, buyerUid, buyerName, storeOwnerId, productLabel, paid } = args;
116
- const buyerNotif = notificationRepository
117
- .create({
115
+ const { orderId, buyerUid, buyerName, storeOwnerId, productLabel, paid, whatsappNotifyAddon } = args;
116
+ const buyerNotif = sendNotification({
118
117
  userId: buyerUid,
119
118
  type: "order_placed",
120
119
  priority: "normal",
@@ -123,14 +122,13 @@ function emitOrderPlacedNotifications(args) {
123
122
  relatedId: orderId,
124
123
  relatedType: "order",
125
124
  actionUrl: `/user/orders/view/${orderId}`,
126
- })
127
- .catch((err) => serverLogger.warn("Failed to create buyer order_placed notification", {
125
+ orderWhatsappAddonPaid: whatsappNotifyAddon,
126
+ }).catch((err) => serverLogger.warn("Failed to send buyer order_placed notification", {
128
127
  err: err instanceof Error ? err.message : String(err),
129
128
  orderId,
130
129
  }));
131
130
  const sellerNotif = storeOwnerId
132
- ? notificationRepository
133
- .create({
131
+ ? sendNotification({
134
132
  userId: storeOwnerId,
135
133
  type: "order_placed",
136
134
  priority: "high",
@@ -139,8 +137,7 @@ function emitOrderPlacedNotifications(args) {
139
137
  relatedId: orderId,
140
138
  relatedType: "order",
141
139
  actionUrl: `/store/orders/${orderId}/view`,
142
- })
143
- .catch((err) => serverLogger.warn("Failed to create seller order_placed notification", {
140
+ }).catch((err) => serverLogger.warn("Failed to send seller order_placed notification", {
144
141
  err: err instanceof Error ? err.message : String(err),
145
142
  orderId,
146
143
  }))
@@ -251,7 +248,7 @@ function unitPriceFor(item, product) {
251
248
  * so the caller can accumulate the checkout-wide `total`.
252
249
  */
253
250
  async function createOrderForGroup(group, orderType, ctx) {
254
- const { paymentMethod, emiTenureMonths, emiSettings, commissions, appliedCoupons, cartSubtotal, couponUsageAccumulator, uid, userName, userEmail, shippingAddress, notes, adminBypass, adminBypassBy, adminBatchId, orderIds, emailsToSend, outOfStockPolicy, droppedItems, buyerState, gstSettings, siteContactUpiVpa, } = ctx;
251
+ const { paymentMethod, emiTenureMonths, emiSettings, commissions, appliedCoupons, cartSubtotal, couponUsageAccumulator, uid, userName, userEmail, shippingAddress, notes, adminBypass, adminBypassBy, adminBatchId, orderIds, emailsToSend, outOfStockPolicy, droppedItems, buyerState, gstSettings, siteContactUpiVpa, whatsappNotifyAddon, giftWrapAddon, giftWrapMessage, shipmentProtectionAddon, } = ctx;
255
252
  const firstItem = group[0].item;
256
253
  const firstProduct = group[0].product;
257
254
  const groupTotal = group.reduce((sum, { item, product }) => sum + unitPriceFor(item, product) * item.quantity, 0);
@@ -327,6 +324,9 @@ async function createOrderForGroup(group, orderType, ctx) {
327
324
  // Handling fee charged to the buyer for choosing COD, on top of the order
328
325
  // total — not deducted from the deposit/remaining split above.
329
326
  const codHandlingFee = paymentMethod === "cod" ? computeCodHandlingFee(groupTotal, commissions) : 0;
327
+ const whatsappNotifyFee = computeWhatsAppNotifyFee(whatsappNotifyAddon, commissions);
328
+ const giftWrapFee = computeGiftWrapFee(giftWrapAddon, commissions);
329
+ const shipmentProtectionFee = computeShipmentProtectionFee(groupTotal, shipmentProtectionAddon, commissions);
330
330
  let couponDiscount = 0;
331
331
  const appliedDiscounts = [];
332
332
  const groupCouponCodes = new Set();
@@ -371,7 +371,7 @@ async function createOrderForGroup(group, orderType, ctx) {
371
371
  }
372
372
  }
373
373
  couponDiscount = Math.min(couponDiscount, groupTotal);
374
- const orderTotal = Math.max(0, groupTotal - couponDiscount) + shippingFee + codHandlingFee + (emiSchedule?.surchargeAmount ?? 0) + (gstBreakdown?.gstAmount ?? 0);
374
+ const orderTotal = Math.max(0, groupTotal - couponDiscount) + shippingFee + codHandlingFee + whatsappNotifyFee + giftWrapFee + shipmentProtectionFee + (emiSchedule?.surchargeAmount ?? 0) + (gstBreakdown?.gstAmount ?? 0);
375
375
  const imageUrls = [
376
376
  ...new Set(group
377
377
  .map(({ product }) => product.mainImage)
@@ -428,6 +428,13 @@ async function createOrderForGroup(group, orderType, ctx) {
428
428
  depositAmount: adminBypass ? undefined : depositAmount,
429
429
  codRemainingAmount: adminBypass ? undefined : codRemainingAmount,
430
430
  codHandlingFee: !adminBypass && codHandlingFee > 0 ? codHandlingFee : undefined,
431
+ whatsappNotifyAddon: !adminBypass && whatsappNotifyFee > 0 ? true : undefined,
432
+ whatsappNotifyFee: !adminBypass && whatsappNotifyFee > 0 ? whatsappNotifyFee : undefined,
433
+ giftWrapAddon: !adminBypass && giftWrapFee > 0 ? true : undefined,
434
+ giftWrapFee: !adminBypass && giftWrapFee > 0 ? giftWrapFee : undefined,
435
+ giftWrapMessage: !adminBypass && giftWrapFee > 0 ? giftWrapMessage?.slice(0, 500) : undefined,
436
+ shipmentProtectionAddon: !adminBypass && shipmentProtectionFee > 0 ? true : undefined,
437
+ shipmentProtectionFee: !adminBypass && shipmentProtectionFee > 0 ? shipmentProtectionFee : undefined,
431
438
  taxableAmount: gstBreakdown?.taxableAmount,
432
439
  gstAmount: gstBreakdown?.gstAmount,
433
440
  cgst: gstBreakdown?.cgst || undefined,
@@ -486,6 +493,7 @@ async function createOrderForGroup(group, orderType, ctx) {
486
493
  storeOwnerId,
487
494
  productLabel: orderItems.length > 1 ? `${orderItems.length} items` : firstItem.productTitle,
488
495
  paid: adminBypass,
496
+ whatsappNotifyAddon: !adminBypass && whatsappNotifyFee > 0,
489
497
  });
490
498
  return groupTotal;
491
499
  }
@@ -496,7 +504,7 @@ async function createOrderForGroup(group, orderType, ctx) {
496
504
  * auth + rate-limiting before calling this and supply the resolved user fields.
497
505
  */
498
506
  export async function createCheckoutOrderAction(input) {
499
- const { userId: uid, userName, userEmail, addressId, paymentMethod, emiTenureMonths, notes, excludedProductIds = [], adminBypass = false, adminBypassBy, outOfStockPolicy = OutOfStockPolicyValues.SKIP_ITEMS, } = input;
507
+ const { userId: uid, userName, userEmail, addressId, paymentMethod, emiTenureMonths, notes, excludedProductIds = [], adminBypass = false, adminBypassBy, outOfStockPolicy = OutOfStockPolicyValues.SKIP_ITEMS, whatsappNotifyAddon = false, giftWrapAddon = false, giftWrapMessage, shipmentProtectionAddon = false, } = input;
500
508
  const siteSettings = await siteSettingsRepository.getSingleton();
501
509
  const commissions = siteSettings?.commissions ?? CHECKOUT_DEFAULT_COMMISSIONS;
502
510
  const emiSettings = siteSettings?.emi ?? CHECKOUT_DEFAULT_EMI_SETTINGS;
@@ -747,6 +755,10 @@ export async function createCheckoutOrderAction(input) {
747
755
  buyerState: resolvedAddress?.state,
748
756
  gstSettings: siteSettings?.gst,
749
757
  siteContactUpiVpa: siteSettings?.contact?.upiVpa,
758
+ whatsappNotifyAddon,
759
+ giftWrapAddon,
760
+ giftWrapMessage,
761
+ shipmentProtectionAddon,
750
762
  };
751
763
  for (const { items: group, orderType } of orderGroups) {
752
764
  total += await createOrderForGroup(group, orderType, groupCtx);
@@ -872,11 +884,212 @@ async function refundDroppedItemsForRazorpayCheckout(input) {
872
884
  }
873
885
  }
874
886
  }
887
+ /**
888
+ * Places one order for one seller-group within `verifyAndPlaceRazorpayOrderAction`
889
+ * — mirrors `createOrderForGroup`'s role on the COD/cash/EMI path (same
890
+ * per-group fee/coupon/order-doc/notification shape), extracted into its own
891
+ * function purely to keep the parent function under the LARGE_COMPONENT
892
+ * audit threshold (`scripts/audit-code-quality.mjs`). No behavior change
893
+ * from the inline version this replaced.
894
+ */
895
+ async function createRazorpayGroupOrder(group, orderType, ctx) {
896
+ const { appliedCoupons, cartSubtotal, platformFeePercent, gstPercent, whatsappNotifyFee, giftWrapFee, giftWrapMessage, shipmentProtectionAddon, siteSettings, uid, userName, userEmail, razorpay_order_id, razorpay_payment_id, razorpay_signature, shippingAddress, notes, outOfStockPolicy, unavailablePaid, orderIds, emailsToSend, } = ctx;
897
+ // SB-UNI-5 2026-05-13 — bundle cart-lines use item.price (locked bundle
898
+ // price); regular lines use product.price. The helper keeps the math
899
+ // local to this block.
900
+ const unitPriceFor = (item, product) => item.bundleCategorySlug && item.bundleProductIds?.length
901
+ ? item.price
902
+ : product.price;
903
+ const firstItem = group[0].item;
904
+ const groupTotal = group.reduce((sum, { item, product }) => sum + unitPriceFor(item, product) * item.quantity, 0);
905
+ // Reuses the same store/seller lookup as the COD/UPI path above instead
906
+ // of re-implementing it inline — was two sequential findById calls per
907
+ // seller group here, duplicated from resolveShippingCost.
908
+ const { shippingFee, storeOwnerId } = await resolveShippingCost(firstItem.storeId);
909
+ let couponDiscount = 0;
910
+ const appliedDiscounts = [];
911
+ for (const coupon of appliedCoupons) {
912
+ let couponGroupDiscount = 0;
913
+ const isSellerScoped = coupon.scope === "seller" && coupon.storeId;
914
+ if (isSellerScoped) {
915
+ if (coupon.storeId !== firstItem.storeId)
916
+ continue;
917
+ if (coupon.applicableItemIds?.length) {
918
+ const eligibleTotal = group
919
+ .filter(({ item }) => coupon.applicableItemIds.includes(item.itemId))
920
+ .reduce((s, { item, product }) => s + unitPriceFor(item, product) * item.quantity, 0);
921
+ couponGroupDiscount =
922
+ eligibleTotal > 0
923
+ ? Math.min(roundRupees((eligibleTotal / groupTotal) * coupon.discountAmount), eligibleTotal)
924
+ : 0;
925
+ }
926
+ else {
927
+ couponGroupDiscount = Math.min(coupon.discountAmount, groupTotal);
928
+ }
929
+ }
930
+ else if (cartSubtotal > 0) {
931
+ couponGroupDiscount = Math.min(roundRupees((groupTotal / cartSubtotal) * coupon.discountAmount), groupTotal);
932
+ }
933
+ if (couponGroupDiscount > 0) {
934
+ couponDiscount += couponGroupDiscount;
935
+ appliedDiscounts.push({
936
+ code: coupon.code,
937
+ couponId: coupon.couponId,
938
+ type: "coupon",
939
+ discountAmount: couponGroupDiscount,
940
+ scope: coupon.scope,
941
+ storeId: coupon.storeId,
942
+ });
943
+ }
944
+ }
945
+ couponDiscount = Math.min(couponDiscount, groupTotal);
946
+ const rawPlatformFee = roundRupees(groupTotal * (platformFeePercent / 100));
947
+ const gstOnFee = roundRupees(rawPlatformFee * (gstPercent / 100));
948
+ const platformFee = rawPlatformFee + gstOnFee;
949
+ const shipmentProtectionFee = computeShipmentProtectionFee(groupTotal, shipmentProtectionAddon, siteSettings?.commissions ?? CHECKOUT_DEFAULT_COMMISSIONS);
950
+ const orderTotal = Math.max(0, groupTotal - couponDiscount) + shippingFee + whatsappNotifyFee + giftWrapFee + shipmentProtectionFee;
951
+ // P-8 GST — deliberately NOT wired into this Razorpay-verify path. The
952
+ // amount-mismatch check above (expectedPaymentAmountRs) compares against
953
+ // what the buyer already paid via the Razorpay order created earlier in
954
+ // the flow; adding product GST here without also adding it to that
955
+ // upstream pre-payment amount calculation would either fail the mismatch
956
+ // check or silently under/over-charge. Wiring GST through the full
957
+ // Razorpay create→verify round-trip is separate follow-up work, tracked
958
+ // alongside P-13 (Razorpay is disabled by default today, so this order
959
+ // type doesn't currently carry a GST breakdown).
960
+ // S-SBUNI-RULES 2026-05-13 — order-item decoration via rule registry.
961
+ const orderItems = group.map(({ item, product }) => {
962
+ const lt = (product?.listingType ?? "standard");
963
+ const itemRule = getListingRule(lt);
964
+ // SB-UNI-5 2026-05-13 — bundle cart-lines surface the locked
965
+ // bundlePrice (item.price), not the representative member's
966
+ // product.price. Stock decrement still runs per member elsewhere.
967
+ const isBundle = Boolean(item.bundleCategorySlug && item.bundleProductIds?.length);
968
+ const unitPrice = isBundle ? item.price : product.price;
969
+ const bundleFields = isBundle
970
+ ? {
971
+ bundleCategorySlug: item.bundleCategorySlug,
972
+ bundleProductIds: item.bundleProductIds,
973
+ }
974
+ : {};
975
+ const baseLine = {
976
+ productId: item.productId,
977
+ productTitle: item.productTitle,
978
+ quantity: item.quantity,
979
+ unitPrice,
980
+ totalPrice: unitPrice * item.quantity,
981
+ ...bundleFields,
982
+ };
983
+ return itemRule.decorateOrderItem(baseLine, product);
984
+ });
985
+ const totalQuantity = group.reduce((sum, { item }) => sum + item.quantity, 0);
986
+ const imageUrls = [
987
+ ...new Set(group
988
+ .map(({ product }) => product?.mainImage)
989
+ .filter((url) => typeof url === "string" && url.length > 0)),
990
+ ];
991
+ // S-SBUNI-RULES 2026-05-13 — order-doc decoration via rule registry.
992
+ const lt0Rzp = (group[0].product?.listingType ?? "standard");
993
+ const groupRuleRzp = getListingRule(lt0Rzp);
994
+ const extraOrderFields = {
995
+ ...groupRuleRzp.decorateOrderDoc(group[0].item, group[0].product),
996
+ ...(orderType === "prize-draw" && group[0].product
997
+ ? { prizeRevealDeadline: computePrizeRevealDeadline(group[0].product) }
998
+ : {}),
999
+ };
1000
+ const order = await unitOfWork.orders.create({
1001
+ productId: firstItem.productId,
1002
+ productTitle: firstItem.productTitle,
1003
+ userId: uid,
1004
+ userName,
1005
+ userEmail,
1006
+ quantity: totalQuantity,
1007
+ unitPrice: group[0].product.price,
1008
+ totalPrice: orderTotal,
1009
+ currency: firstItem.currency ?? getDefaultCurrency(),
1010
+ storeId: firstItem.storeId || undefined,
1011
+ storeName: firstItem.storeName || undefined,
1012
+ items: orderItems,
1013
+ orderType,
1014
+ offerId: firstItem.offerId ?? undefined,
1015
+ status: OrderStatusValues.CONFIRMED,
1016
+ paymentStatus: PaymentStatusValues.PAID,
1017
+ paymentMethod: PaymentMethodValues.ONLINE,
1018
+ paymentId: razorpay_payment_id,
1019
+ paymentRecord: {
1020
+ method: "razorpay",
1021
+ transactionId: razorpay_payment_id,
1022
+ amount: orderTotal,
1023
+ paidAt: new Date(),
1024
+ verifiedBy: "razorpay-webhook",
1025
+ verificationMethod: "webhook",
1026
+ gatewayRef: {
1027
+ orderId: razorpay_order_id,
1028
+ paymentId: razorpay_payment_id,
1029
+ signature: razorpay_signature,
1030
+ },
1031
+ },
1032
+ shippingAddress,
1033
+ notes,
1034
+ platformFee,
1035
+ shippingFee: shippingFee > 0 ? shippingFee : undefined,
1036
+ whatsappNotifyAddon: whatsappNotifyFee > 0 ? true : undefined,
1037
+ whatsappNotifyFee: whatsappNotifyFee > 0 ? whatsappNotifyFee : undefined,
1038
+ giftWrapAddon: giftWrapFee > 0 ? true : undefined,
1039
+ giftWrapFee: giftWrapFee > 0 ? giftWrapFee : undefined,
1040
+ giftWrapMessage: giftWrapFee > 0 ? giftWrapMessage?.slice(0, 500) : undefined,
1041
+ shipmentProtectionAddon: shipmentProtectionFee > 0 ? true : undefined,
1042
+ shipmentProtectionFee: shipmentProtectionFee > 0 ? shipmentProtectionFee : undefined,
1043
+ couponCode: appliedDiscounts[0]?.code,
1044
+ couponDiscount: couponDiscount > 0 ? couponDiscount : undefined,
1045
+ appliedDiscounts: appliedDiscounts.length > 0 ? appliedDiscounts : undefined,
1046
+ imageUrls: imageUrls.length > 0 ? imageUrls : undefined,
1047
+ outOfStockPolicy,
1048
+ droppedItems: unavailablePaid.length > 0 ? unavailablePaid : undefined,
1049
+ ...extraOrderFields,
1050
+ });
1051
+ orderIds.push(order.id);
1052
+ // SB-UNI-N — claim a digital code for digital-code orders (fire-and-forget,
1053
+ // order is already persisted so a claim failure is logged, not thrown).
1054
+ if ((group[0]?.product?.listingType ?? "standard") === "digital-code") {
1055
+ claimDigitalCodeForOrder(getAdminDb(), firstItem.productId, order.id, uid, {
1056
+ userEmail: userEmail || undefined,
1057
+ userName,
1058
+ productTitle: firstItem.productTitle,
1059
+ }).catch((e) => serverLogger.error("claimDigitalCode", e));
1060
+ }
1061
+ if (userEmail) {
1062
+ emailsToSend.push({
1063
+ to: userEmail,
1064
+ userName,
1065
+ orderId: order.id,
1066
+ productTitle: orderItems.length > 1 ? `${orderItems.length} items` : firstItem.productTitle,
1067
+ quantity: totalQuantity,
1068
+ totalPrice: orderTotal,
1069
+ currency: firstItem.currency ?? getDefaultCurrency(),
1070
+ shippingAddress,
1071
+ paymentMethod: PaymentMethodValues.ONLINE,
1072
+ items: orderItems,
1073
+ });
1074
+ }
1075
+ emitOrderPlacedNotifications({
1076
+ orderId: order.id,
1077
+ buyerUid: uid,
1078
+ buyerName: userName,
1079
+ storeOwnerId,
1080
+ productLabel: orderItems.length > 1 ? `${orderItems.length} items` : firstItem.productTitle,
1081
+ paid: true,
1082
+ whatsappNotifyAddon: whatsappNotifyFee > 0,
1083
+ });
1084
+ return orderTotal;
1085
+ }
875
1086
  export async function verifyAndPlaceRazorpayOrderAction(input) {
876
- const { userId: uid, userName, userEmail, razorpay_order_id, razorpay_payment_id, razorpay_signature, addressId, notes, outOfStockPolicy = OutOfStockPolicyValues.CANCEL_ORDER, } = input;
1087
+ const { userId: uid, userName, userEmail, razorpay_order_id, razorpay_payment_id, razorpay_signature, addressId, notes, outOfStockPolicy = OutOfStockPolicyValues.CANCEL_ORDER, whatsappNotifyAddon = false, giftWrapAddon = false, giftWrapMessage, shipmentProtectionAddon = false, } = input;
877
1088
  const siteSettings = await siteSettingsRepository.getSingleton();
878
1089
  const platformFeePercent = siteSettings?.commissions?.platformFeePercent ?? 5;
879
1090
  const gstPercent = siteSettings?.commissions?.gstPercent ?? 18;
1091
+ const whatsappNotifyFee = computeWhatsAppNotifyFee(whatsappNotifyAddon, siteSettings?.commissions ?? CHECKOUT_DEFAULT_COMMISSIONS);
1092
+ const giftWrapFee = computeGiftWrapFee(giftWrapAddon, siteSettings?.commissions ?? CHECKOUT_DEFAULT_COMMISSIONS);
880
1093
  const isValid = await verifyPaymentSignatureWithKeys({
881
1094
  razorpay_order_id,
882
1095
  razorpay_payment_id,
@@ -1051,178 +1264,28 @@ export async function verifyAndPlaceRazorpayOrderAction(input) {
1051
1264
  const cartSubtotal = orderGroups.reduce((s, { items: g }) => s +
1052
1265
  g.reduce((gs, { item, product }) => gs + unitPriceFor(item, product) * item.quantity, 0), 0);
1053
1266
  for (const { items: group, orderType } of orderGroups) {
1054
- const firstItem = group[0].item;
1055
- const groupTotal = group.reduce((sum, { item, product }) => sum + unitPriceFor(item, product) * item.quantity, 0);
1056
- // Reuses the same store/seller lookup as the COD/UPI path above instead
1057
- // of re-implementing it inline — was two sequential findById calls per
1058
- // seller group here, duplicated from resolveShippingCost.
1059
- const { shippingFee, storeOwnerId } = await resolveShippingCost(firstItem.storeId);
1060
- let couponDiscount = 0;
1061
- const appliedDiscounts = [];
1062
- for (const coupon of appliedCoupons) {
1063
- let couponGroupDiscount = 0;
1064
- const isSellerScoped = coupon.scope === "seller" && coupon.storeId;
1065
- if (isSellerScoped) {
1066
- if (coupon.storeId !== firstItem.storeId)
1067
- continue;
1068
- if (coupon.applicableItemIds?.length) {
1069
- const eligibleTotal = group
1070
- .filter(({ item }) => coupon.applicableItemIds.includes(item.itemId))
1071
- .reduce((s, { item, product }) => s + unitPriceFor(item, product) * item.quantity, 0);
1072
- couponGroupDiscount =
1073
- eligibleTotal > 0
1074
- ? Math.min(roundRupees((eligibleTotal / groupTotal) * coupon.discountAmount), eligibleTotal)
1075
- : 0;
1076
- }
1077
- else {
1078
- couponGroupDiscount = Math.min(coupon.discountAmount, groupTotal);
1079
- }
1080
- }
1081
- else if (cartSubtotal > 0) {
1082
- couponGroupDiscount = Math.min(roundRupees((groupTotal / cartSubtotal) * coupon.discountAmount), groupTotal);
1083
- }
1084
- if (couponGroupDiscount > 0) {
1085
- couponDiscount += couponGroupDiscount;
1086
- appliedDiscounts.push({
1087
- code: coupon.code,
1088
- couponId: coupon.couponId,
1089
- type: "coupon",
1090
- discountAmount: couponGroupDiscount,
1091
- scope: coupon.scope,
1092
- storeId: coupon.storeId,
1093
- });
1094
- }
1095
- }
1096
- couponDiscount = Math.min(couponDiscount, groupTotal);
1097
- const rawPlatformFee = roundRupees(groupTotal * (platformFeePercent / 100));
1098
- const gstOnFee = roundRupees(rawPlatformFee * (gstPercent / 100));
1099
- const platformFee = rawPlatformFee + gstOnFee;
1100
- const orderTotal = Math.max(0, groupTotal - couponDiscount) + shippingFee;
1101
- total += orderTotal;
1102
- // P-8 GST — deliberately NOT wired into this Razorpay-verify path. The
1103
- // amount-mismatch check above (expectedPaymentAmountRs) compares against
1104
- // what the buyer already paid via the Razorpay order created earlier in
1105
- // the flow; adding product GST here without also adding it to that
1106
- // upstream pre-payment amount calculation would either fail the mismatch
1107
- // check or silently under/over-charge. Wiring GST through the full
1108
- // Razorpay create→verify round-trip is separate follow-up work, tracked
1109
- // alongside P-13 (Razorpay is disabled by default today, so this order
1110
- // type doesn't currently carry a GST breakdown).
1111
- // S-SBUNI-RULES 2026-05-13 — order-item decoration via rule registry.
1112
- const orderItems = group.map(({ item, product }) => {
1113
- const lt = (product?.listingType ?? "standard");
1114
- const itemRule = getListingRule(lt);
1115
- // SB-UNI-5 2026-05-13 — bundle cart-lines surface the locked
1116
- // bundlePrice (item.price), not the representative member's
1117
- // product.price. Stock decrement still runs per member elsewhere.
1118
- const isBundle = Boolean(item.bundleCategorySlug && item.bundleProductIds?.length);
1119
- const unitPrice = isBundle ? item.price : product.price;
1120
- const bundleFields = isBundle
1121
- ? {
1122
- bundleCategorySlug: item.bundleCategorySlug,
1123
- bundleProductIds: item.bundleProductIds,
1124
- }
1125
- : {};
1126
- const baseLine = {
1127
- productId: item.productId,
1128
- productTitle: item.productTitle,
1129
- quantity: item.quantity,
1130
- unitPrice,
1131
- totalPrice: unitPrice * item.quantity,
1132
- ...bundleFields,
1133
- };
1134
- return itemRule.decorateOrderItem(baseLine, product);
1135
- });
1136
- const totalQuantity = group.reduce((sum, { item }) => sum + item.quantity, 0);
1137
- const imageUrls = [
1138
- ...new Set(group
1139
- .map(({ product }) => product?.mainImage)
1140
- .filter((url) => typeof url === "string" && url.length > 0)),
1141
- ];
1142
- // S-SBUNI-RULES 2026-05-13 — order-doc decoration via rule registry.
1143
- const lt0Rzp = (group[0].product?.listingType ?? "standard");
1144
- const groupRuleRzp = getListingRule(lt0Rzp);
1145
- const extraOrderFields = {
1146
- ...groupRuleRzp.decorateOrderDoc(group[0].item, group[0].product),
1147
- ...(orderType === "prize-draw" && group[0].product
1148
- ? { prizeRevealDeadline: computePrizeRevealDeadline(group[0].product) }
1149
- : {}),
1150
- };
1151
- const order = await unitOfWork.orders.create({
1152
- productId: firstItem.productId,
1153
- productTitle: firstItem.productTitle,
1154
- userId: uid,
1267
+ total += await createRazorpayGroupOrder(group, orderType, {
1268
+ appliedCoupons,
1269
+ cartSubtotal,
1270
+ platformFeePercent,
1271
+ gstPercent,
1272
+ whatsappNotifyFee,
1273
+ giftWrapFee,
1274
+ giftWrapMessage,
1275
+ shipmentProtectionAddon,
1276
+ siteSettings,
1277
+ uid,
1155
1278
  userName,
1156
1279
  userEmail,
1157
- quantity: totalQuantity,
1158
- unitPrice: group[0].product.price,
1159
- totalPrice: orderTotal,
1160
- currency: firstItem.currency ?? getDefaultCurrency(),
1161
- storeId: firstItem.storeId || undefined,
1162
- storeName: firstItem.storeName || undefined,
1163
- items: orderItems,
1164
- orderType,
1165
- offerId: firstItem.offerId ?? undefined,
1166
- status: OrderStatusValues.CONFIRMED,
1167
- paymentStatus: PaymentStatusValues.PAID,
1168
- paymentMethod: PaymentMethodValues.ONLINE,
1169
- paymentId: razorpay_payment_id,
1170
- paymentRecord: {
1171
- method: "razorpay",
1172
- transactionId: razorpay_payment_id,
1173
- amount: orderTotal,
1174
- paidAt: new Date(),
1175
- verifiedBy: "razorpay-webhook",
1176
- verificationMethod: "webhook",
1177
- gatewayRef: {
1178
- orderId: razorpay_order_id,
1179
- paymentId: razorpay_payment_id,
1180
- signature: razorpay_signature,
1181
- },
1182
- },
1280
+ razorpay_order_id,
1281
+ razorpay_payment_id,
1282
+ razorpay_signature,
1183
1283
  shippingAddress,
1184
1284
  notes,
1185
- platformFee,
1186
- shippingFee: shippingFee > 0 ? shippingFee : undefined,
1187
- couponCode: appliedDiscounts[0]?.code,
1188
- couponDiscount: couponDiscount > 0 ? couponDiscount : undefined,
1189
- appliedDiscounts: appliedDiscounts.length > 0 ? appliedDiscounts : undefined,
1190
- imageUrls: imageUrls.length > 0 ? imageUrls : undefined,
1191
1285
  outOfStockPolicy,
1192
- droppedItems: unavailablePaid.length > 0 ? unavailablePaid : undefined,
1193
- ...extraOrderFields,
1194
- });
1195
- orderIds.push(order.id);
1196
- // SB-UNI-N — claim a digital code for digital-code orders (fire-and-forget,
1197
- // order is already persisted so a claim failure is logged, not thrown).
1198
- if ((group[0]?.product?.listingType ?? "standard") === "digital-code") {
1199
- claimDigitalCodeForOrder(getAdminDb(), firstItem.productId, order.id, uid, {
1200
- userEmail: userEmail || undefined,
1201
- userName,
1202
- productTitle: firstItem.productTitle,
1203
- }).catch((e) => serverLogger.error("claimDigitalCode", e));
1204
- }
1205
- if (userEmail) {
1206
- emailsToSend.push({
1207
- to: userEmail,
1208
- userName,
1209
- orderId: order.id,
1210
- productTitle: orderItems.length > 1 ? `${orderItems.length} items` : firstItem.productTitle,
1211
- quantity: totalQuantity,
1212
- totalPrice: orderTotal,
1213
- currency: firstItem.currency ?? getDefaultCurrency(),
1214
- shippingAddress,
1215
- paymentMethod: PaymentMethodValues.ONLINE,
1216
- items: orderItems,
1217
- });
1218
- }
1219
- emitOrderPlacedNotifications({
1220
- orderId: order.id,
1221
- buyerUid: uid,
1222
- buyerName: userName,
1223
- storeOwnerId,
1224
- productLabel: orderItems.length > 1 ? `${orderItems.length} items` : firstItem.productTitle,
1225
- paid: true,
1286
+ unavailablePaid,
1287
+ orderIds,
1288
+ emailsToSend,
1226
1289
  });
1227
1290
  }
1228
1291
  // SB-UNI-5 2026-05-13 — bundle-aware batch decrement. We iterate the
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Google Cloud Logging integration for the `/admin/maintenance/cloud-logs`
3
+ * page. Reuses the same 3-tier credential resolution as
4
+ * `providers/db-firebase/admin.ts` (local `firebase-admin-key.json` → Vercel
5
+ * `FIREBASE_ADMIN_*` env vars → Application Default Credentials), so no new
6
+ * env vars are required.
7
+ *
8
+ * Bounded to ONE `Logging#getEntries()` call per invocation — never
9
+ * `getEntriesStream()` in an unbounded loop — to respect the Vercel Hobby
10
+ * 10s sync-function ceiling (CLAUDE.md Rule #6). Pagination is client-driven
11
+ * via `nextPageToken`, not exhausted server-side.
12
+ *
13
+ * `@google-cloud/logging` is a consumer-side (root `package.json`) dependency
14
+ * — it is never imported at module scope here. The `Logging` client is
15
+ * lazily `require()`d inside a function body via `(module as any).require`,
16
+ * exactly like `admin.ts` lazily loads `firebase-admin/*`, so Turbopack never
17
+ * sees a static import of a Node-only client reachable from a client bundle
18
+ * (CLAUDE.md Root Cause #6 / #24). A minimal local interface describes the
19
+ * only surface we call — this file never imports `@google-cloud/logging`'s
20
+ * own types, so it type-checks even though the package is deliberately not
21
+ * listed as an appkit dependency (it lives in the consumer's root
22
+ * `package.json` only, since this integration is Next.js-route-only).
23
+ */
24
+ export interface CloudLogEntry {
25
+ id: string;
26
+ timestamp: string;
27
+ severity: string;
28
+ resourceType: string;
29
+ resourceLabels: Record<string, string>;
30
+ logName: string;
31
+ message: string;
32
+ }
33
+ export interface ListCloudLogEntriesOpts {
34
+ /** Full Cloud Logging filter string. Defaults to a 24h `resource.type` window (see below). */
35
+ filter?: string;
36
+ /** Capped at 100. Defaults to 50. */
37
+ pageSize?: number;
38
+ /** Opaque continuation token from a previous call's `nextPageToken`. */
39
+ pageToken?: string;
40
+ }
41
+ export interface ListCloudLogEntriesResult {
42
+ entries: CloudLogEntry[];
43
+ nextPageToken: string | null;
44
+ }
45
+ /**
46
+ * Bounded, single-page Cloud Logging read. One `getEntries()` call per
47
+ * invocation — the caller drives further pages via `opts.pageToken`
48
+ * (client-side "Load more"), never a server-side exhaust loop.
49
+ */
50
+ export declare const listCloudLogEntries: (opts?: ListCloudLogEntriesOpts) => Promise<ListCloudLogEntriesResult>;