@aglyn/plugins-commerce 1.0.0-beta.143

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 (417) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +10 -0
  3. package/package.json +57 -0
  4. package/src/index.d.ts +38 -0
  5. package/src/index.js +38 -0
  6. package/src/index.js.map +1 -0
  7. package/src/lib/components/account.d.ts +31 -0
  8. package/src/lib/components/account.js +645 -0
  9. package/src/lib/components/account.js.map +1 -0
  10. package/src/lib/components/cart.d.ts +38 -0
  11. package/src/lib/components/cart.js +851 -0
  12. package/src/lib/components/cart.js.map +1 -0
  13. package/src/lib/components/commerce-console-page.d.ts +32 -0
  14. package/src/lib/components/commerce-console-page.js +296 -0
  15. package/src/lib/components/commerce-console-page.js.map +1 -0
  16. package/src/lib/components/commerce-console-sections.d.ts +34 -0
  17. package/src/lib/components/commerce-console-sections.js +62 -0
  18. package/src/lib/components/commerce-console-sections.js.map +1 -0
  19. package/src/lib/components/console/catalog-organization-card.component.d.ts +14 -0
  20. package/src/lib/components/console/catalog-organization-card.component.js +1087 -0
  21. package/src/lib/components/console/catalog-organization-card.component.js.map +1 -0
  22. package/src/lib/components/console/commerce-analytics-card.component.d.ts +27 -0
  23. package/src/lib/components/console/commerce-analytics-card.component.js +310 -0
  24. package/src/lib/components/console/commerce-analytics-card.component.js.map +1 -0
  25. package/src/lib/components/console/commerce-glance-card.component.d.ts +20 -0
  26. package/src/lib/components/console/commerce-glance-card.component.js +340 -0
  27. package/src/lib/components/console/commerce-glance-card.component.js.map +1 -0
  28. package/src/lib/components/console/commerce-stat-tile.component.d.ts +37 -0
  29. package/src/lib/components/console/commerce-stat-tile.component.js +72 -0
  30. package/src/lib/components/console/commerce-stat-tile.component.js.map +1 -0
  31. package/src/lib/components/console/discounts-card.component.d.ts +14 -0
  32. package/src/lib/components/console/discounts-card.component.js +443 -0
  33. package/src/lib/components/console/discounts-card.component.js.map +1 -0
  34. package/src/lib/components/console/entitlement-gate.component.d.ts +76 -0
  35. package/src/lib/components/console/entitlement-gate.component.js +115 -0
  36. package/src/lib/components/console/entitlement-gate.component.js.map +1 -0
  37. package/src/lib/components/console/gift-cards-card.component.d.ts +29 -0
  38. package/src/lib/components/console/gift-cards-card.component.js +379 -0
  39. package/src/lib/components/console/gift-cards-card.component.js.map +1 -0
  40. package/src/lib/components/console/host-coupons-card.component.d.ts +14 -0
  41. package/src/lib/components/console/host-coupons-card.component.js +252 -0
  42. package/src/lib/components/console/host-coupons-card.component.js.map +1 -0
  43. package/src/lib/components/console/host-orders-card.component.d.ts +13 -0
  44. package/src/lib/components/console/host-orders-card.component.js +861 -0
  45. package/src/lib/components/console/host-orders-card.component.js.map +1 -0
  46. package/src/lib/components/console/locations-card.component.d.ts +14 -0
  47. package/src/lib/components/console/locations-card.component.js +256 -0
  48. package/src/lib/components/console/locations-card.component.js.map +1 -0
  49. package/src/lib/components/console/member-posts-card.component.d.ts +8 -0
  50. package/src/lib/components/console/member-posts-card.component.js +283 -0
  51. package/src/lib/components/console/member-posts-card.component.js.map +1 -0
  52. package/src/lib/components/console/newest-site-users-card.component.d.ts +36 -0
  53. package/src/lib/components/console/newest-site-users-card.component.js +126 -0
  54. package/src/lib/components/console/newest-site-users-card.component.js.map +1 -0
  55. package/src/lib/components/console/order-detail-dialog.component.d.ts +28 -0
  56. package/src/lib/components/console/order-detail-dialog.component.js +976 -0
  57. package/src/lib/components/console/order-detail-dialog.component.js.map +1 -0
  58. package/src/lib/components/console/paid-media.d.ts +127 -0
  59. package/src/lib/components/console/paid-media.js +397 -0
  60. package/src/lib/components/console/paid-media.js.map +1 -0
  61. package/src/lib/components/console/payments-settings-card.component.d.ts +13 -0
  62. package/src/lib/components/console/payments-settings-card.component.js +295 -0
  63. package/src/lib/components/console/payments-settings-card.component.js.map +1 -0
  64. package/src/lib/components/console/pos-page.component.d.ts +13 -0
  65. package/src/lib/components/console/pos-page.component.js +916 -0
  66. package/src/lib/components/console/pos-page.component.js.map +1 -0
  67. package/src/lib/components/console/product-editor-dialog.component.d.ts +32 -0
  68. package/src/lib/components/console/product-editor-dialog.component.js +1374 -0
  69. package/src/lib/components/console/product-editor-dialog.component.js.map +1 -0
  70. package/src/lib/components/console/products-hub-card.component.d.ts +16 -0
  71. package/src/lib/components/console/products-hub-card.component.js +1280 -0
  72. package/src/lib/components/console/products-hub-card.component.js.map +1 -0
  73. package/src/lib/components/console/products-hub-zone.component.d.ts +50 -0
  74. package/src/lib/components/console/products-hub-zone.component.js +200 -0
  75. package/src/lib/components/console/products-hub-zone.component.js.map +1 -0
  76. package/src/lib/components/console/recovery-queue-card.component.d.ts +39 -0
  77. package/src/lib/components/console/recovery-queue-card.component.js +262 -0
  78. package/src/lib/components/console/recovery-queue-card.component.js.map +1 -0
  79. package/src/lib/components/console/registers-card.component.d.ts +21 -0
  80. package/src/lib/components/console/registers-card.component.js +296 -0
  81. package/src/lib/components/console/registers-card.component.js.map +1 -0
  82. package/src/lib/components/console/reservations-card.component.d.ts +14 -0
  83. package/src/lib/components/console/reservations-card.component.js +650 -0
  84. package/src/lib/components/console/reservations-card.component.js.map +1 -0
  85. package/src/lib/components/console/reviews-moderation-card.component.d.ts +8 -0
  86. package/src/lib/components/console/reviews-moderation-card.component.js +235 -0
  87. package/src/lib/components/console/reviews-moderation-card.component.js.map +1 -0
  88. package/src/lib/components/console/shipping-settings-card.component.d.ts +38 -0
  89. package/src/lib/components/console/shipping-settings-card.component.js +560 -0
  90. package/src/lib/components/console/shipping-settings-card.component.js.map +1 -0
  91. package/src/lib/components/console/stock-movements-card.component.d.ts +31 -0
  92. package/src/lib/components/console/stock-movements-card.component.js +333 -0
  93. package/src/lib/components/console/stock-movements-card.component.js.map +1 -0
  94. package/src/lib/components/console/store-settings-card.component.d.ts +14 -0
  95. package/src/lib/components/console/store-settings-card.component.js +300 -0
  96. package/src/lib/components/console/store-settings-card.component.js.map +1 -0
  97. package/src/lib/components/console/storefront-tax-summary-card.component.d.ts +73 -0
  98. package/src/lib/components/console/storefront-tax-summary-card.component.js +408 -0
  99. package/src/lib/components/console/storefront-tax-summary-card.component.js.map +1 -0
  100. package/src/lib/components/console/suppliers-card.component.d.ts +8 -0
  101. package/src/lib/components/console/suppliers-card.component.js +328 -0
  102. package/src/lib/components/console/suppliers-card.component.js.map +1 -0
  103. package/src/lib/components/console/tax-settings-card.component.d.ts +14 -0
  104. package/src/lib/components/console/tax-settings-card.component.js +473 -0
  105. package/src/lib/components/console/tax-settings-card.component.js.map +1 -0
  106. package/src/lib/components/gate.d.ts +39 -0
  107. package/src/lib/components/gate.js +202 -0
  108. package/src/lib/components/gate.js.map +1 -0
  109. package/src/lib/components/gated-video.d.ts +40 -0
  110. package/src/lib/components/gated-video.js +366 -0
  111. package/src/lib/components/gated-video.js.map +1 -0
  112. package/src/lib/components/member-feed.d.ts +32 -0
  113. package/src/lib/components/member-feed.js +198 -0
  114. package/src/lib/components/member-feed.js.map +1 -0
  115. package/src/lib/components/member-recovery.d.ts +35 -0
  116. package/src/lib/components/member-recovery.js +313 -0
  117. package/src/lib/components/member-recovery.js.map +1 -0
  118. package/src/lib/components/member-signin.d.ts +35 -0
  119. package/src/lib/components/member-signin.js +257 -0
  120. package/src/lib/components/member-signin.js.map +1 -0
  121. package/src/lib/components/member-signup.d.ts +34 -0
  122. package/src/lib/components/member-signup.js +287 -0
  123. package/src/lib/components/member-signup.js.map +1 -0
  124. package/src/lib/components/newsletter-signup.d.ts +39 -0
  125. package/src/lib/components/newsletter-signup.js +229 -0
  126. package/src/lib/components/newsletter-signup.js.map +1 -0
  127. package/src/lib/components/product-detail.d.ts +38 -0
  128. package/src/lib/components/product-detail.js +1022 -0
  129. package/src/lib/components/product-detail.js.map +1 -0
  130. package/src/lib/components/product-grid.d.ts +67 -0
  131. package/src/lib/components/product-grid.js +909 -0
  132. package/src/lib/components/product-grid.js.map +1 -0
  133. package/src/lib/components/product-reviews.d.ts +32 -0
  134. package/src/lib/components/product-reviews.js +349 -0
  135. package/src/lib/components/product-reviews.js.map +1 -0
  136. package/src/lib/components/related-products.d.ts +33 -0
  137. package/src/lib/components/related-products.js +232 -0
  138. package/src/lib/components/related-products.js.map +1 -0
  139. package/src/lib/components/reservation-widget.d.ts +33 -0
  140. package/src/lib/components/reservation-widget.js +418 -0
  141. package/src/lib/components/reservation-widget.js.map +1 -0
  142. package/src/lib/components/storefront-payment-element-fallback.d.ts +18 -0
  143. package/src/lib/components/storefront-payment-element-fallback.js +51 -0
  144. package/src/lib/components/storefront-payment-element-fallback.js.map +1 -0
  145. package/src/lib/components/storefront-payment-element.d.ts +78 -0
  146. package/src/lib/components/storefront-payment-element.js +156 -0
  147. package/src/lib/components/storefront-payment-element.js.map +1 -0
  148. package/src/lib/components/wishlist.d.ts +46 -0
  149. package/src/lib/components/wishlist.js +304 -0
  150. package/src/lib/components/wishlist.js.map +1 -0
  151. package/src/lib/constants/bundle-common.d.ts +18 -0
  152. package/src/lib/constants/bundle-common.js +18 -0
  153. package/src/lib/constants/bundle-common.js.map +1 -0
  154. package/src/lib/constants/product-filters.d.ts +35 -0
  155. package/src/lib/constants/product-filters.js +109 -0
  156. package/src/lib/constants/product-filters.js.map +1 -0
  157. package/src/lib/model/commerce-cart.d.ts +52 -0
  158. package/src/lib/model/commerce-cart.js +102 -0
  159. package/src/lib/model/commerce-cart.js.map +1 -0
  160. package/src/lib/model/commerce-connect-transfer.d.ts +243 -0
  161. package/src/lib/model/commerce-connect-transfer.js +231 -0
  162. package/src/lib/model/commerce-connect-transfer.js.map +1 -0
  163. package/src/lib/model/commerce-discounts.d.ts +164 -0
  164. package/src/lib/model/commerce-discounts.js +231 -0
  165. package/src/lib/model/commerce-discounts.js.map +1 -0
  166. package/src/lib/model/commerce-dispute.d.ts +124 -0
  167. package/src/lib/model/commerce-dispute.js +225 -0
  168. package/src/lib/model/commerce-dispute.js.map +1 -0
  169. package/src/lib/model/commerce-gift-cards.d.ts +107 -0
  170. package/src/lib/model/commerce-gift-cards.js +121 -0
  171. package/src/lib/model/commerce-gift-cards.js.map +1 -0
  172. package/src/lib/model/commerce-io.d.ts +36 -0
  173. package/src/lib/model/commerce-io.js +245 -0
  174. package/src/lib/model/commerce-io.js.map +1 -0
  175. package/src/lib/model/commerce-orders.d.ts +1040 -0
  176. package/src/lib/model/commerce-orders.js +1022 -0
  177. package/src/lib/model/commerce-orders.js.map +1 -0
  178. package/src/lib/model/commerce-promotions.d.ts +188 -0
  179. package/src/lib/model/commerce-promotions.js +199 -0
  180. package/src/lib/model/commerce-promotions.js.map +1 -0
  181. package/src/lib/model/commerce-reservations.d.ts +175 -0
  182. package/src/lib/model/commerce-reservations.js +157 -0
  183. package/src/lib/model/commerce-reservations.js.map +1 -0
  184. package/src/lib/model/commerce-shipping.d.ts +316 -0
  185. package/src/lib/model/commerce-shipping.js +484 -0
  186. package/src/lib/model/commerce-shipping.js.map +1 -0
  187. package/src/lib/model/commerce-stock-holds.d.ts +199 -0
  188. package/src/lib/model/commerce-stock-holds.js +190 -0
  189. package/src/lib/model/commerce-stock-holds.js.map +1 -0
  190. package/src/lib/model/commerce-subscription-status.d.ts +67 -0
  191. package/src/lib/model/commerce-subscription-status.js +76 -0
  192. package/src/lib/model/commerce-subscription-status.js.map +1 -0
  193. package/src/lib/model/commerce-tax-decision.d.ts +222 -0
  194. package/src/lib/model/commerce-tax-decision.js +165 -0
  195. package/src/lib/model/commerce-tax-decision.js.map +1 -0
  196. package/src/lib/model/commerce-tax.d.ts +176 -0
  197. package/src/lib/model/commerce-tax.js +92 -0
  198. package/src/lib/model/commerce-tax.js.map +1 -0
  199. package/src/lib/model/commerce.d.ts +551 -0
  200. package/src/lib/model/commerce.js +739 -0
  201. package/src/lib/model/commerce.js.map +1 -0
  202. package/src/lib/model/index.d.ts +40 -0
  203. package/src/lib/model/index.js +40 -0
  204. package/src/lib/model/index.js.map +1 -0
  205. package/src/lib/model/order-figures.d.ts +48 -0
  206. package/src/lib/model/order-figures.js +74 -0
  207. package/src/lib/model/order-figures.js.map +1 -0
  208. package/src/lib/model/plugin-permissions.d.ts +23 -0
  209. package/src/lib/model/plugin-permissions.js +34 -0
  210. package/src/lib/model/plugin-permissions.js.map +1 -0
  211. package/src/lib/model/purchase-analytics.d.ts +186 -0
  212. package/src/lib/model/purchase-analytics.js +216 -0
  213. package/src/lib/model/purchase-analytics.js.map +1 -0
  214. package/src/lib/plugin-config.d.ts +62 -0
  215. package/src/lib/plugin-config.js +78 -0
  216. package/src/lib/plugin-config.js.map +1 -0
  217. package/src/lib/plugin.d.ts +28 -0
  218. package/src/lib/plugin.js +147 -0
  219. package/src/lib/plugin.js.map +1 -0
  220. package/src/lib/server/billing-webhook.d.ts +26 -0
  221. package/src/lib/server/billing-webhook.js +4011 -0
  222. package/src/lib/server/billing-webhook.js.map +1 -0
  223. package/src/lib/server/cancel-order.d.ts +87 -0
  224. package/src/lib/server/cancel-order.js +368 -0
  225. package/src/lib/server/cancel-order.js.map +1 -0
  226. package/src/lib/server/cart-checkout.d.ts +25 -0
  227. package/src/lib/server/cart-checkout.js +976 -0
  228. package/src/lib/server/cart-checkout.js.map +1 -0
  229. package/src/lib/server/cart-cookie.d.ts +32 -0
  230. package/src/lib/server/cart-cookie.js +77 -0
  231. package/src/lib/server/cart-cookie.js.map +1 -0
  232. package/src/lib/server/cart.d.ts +34 -0
  233. package/src/lib/server/cart.js +181 -0
  234. package/src/lib/server/cart.js.map +1 -0
  235. package/src/lib/server/catalog.d.ts +127 -0
  236. package/src/lib/server/catalog.js +277 -0
  237. package/src/lib/server/catalog.js.map +1 -0
  238. package/src/lib/server/checkout.d.ts +36 -0
  239. package/src/lib/server/checkout.js +915 -0
  240. package/src/lib/server/checkout.js.map +1 -0
  241. package/src/lib/server/connect.d.ts +26 -0
  242. package/src/lib/server/connect.js +238 -0
  243. package/src/lib/server/connect.js.map +1 -0
  244. package/src/lib/server/contact-refund.d.ts +117 -0
  245. package/src/lib/server/contact-refund.js +196 -0
  246. package/src/lib/server/contact-refund.js.map +1 -0
  247. package/src/lib/server/download.d.ts +39 -0
  248. package/src/lib/server/download.js +206 -0
  249. package/src/lib/server/download.js.map +1 -0
  250. package/src/lib/server/draft-order.d.ts +26 -0
  251. package/src/lib/server/draft-order.js +706 -0
  252. package/src/lib/server/draft-order.js.map +1 -0
  253. package/src/lib/server/feed.d.ts +24 -0
  254. package/src/lib/server/feed.js +59 -0
  255. package/src/lib/server/feed.js.map +1 -0
  256. package/src/lib/server/fulfill-order.d.ts +97 -0
  257. package/src/lib/server/fulfill-order.js +235 -0
  258. package/src/lib/server/fulfill-order.js.map +1 -0
  259. package/src/lib/server/gate.d.ts +36 -0
  260. package/src/lib/server/gate.js +112 -0
  261. package/src/lib/server/gate.js.map +1 -0
  262. package/src/lib/server/gift-cards.d.ts +42 -0
  263. package/src/lib/server/gift-cards.js +219 -0
  264. package/src/lib/server/gift-cards.js.map +1 -0
  265. package/src/lib/server/low-stock.d.ts +49 -0
  266. package/src/lib/server/low-stock.js +60 -0
  267. package/src/lib/server/low-stock.js.map +1 -0
  268. package/src/lib/server/manual-tax-rate.d.ts +82 -0
  269. package/src/lib/server/manual-tax-rate.js +113 -0
  270. package/src/lib/server/manual-tax-rate.js.map +1 -0
  271. package/src/lib/server/member-feed.d.ts +24 -0
  272. package/src/lib/server/member-feed.js +72 -0
  273. package/src/lib/server/member-feed.js.map +1 -0
  274. package/src/lib/server/member-name-search.d.ts +37 -0
  275. package/src/lib/server/member-name-search.js +42 -0
  276. package/src/lib/server/member-name-search.js.map +1 -0
  277. package/src/lib/server/member-post.d.ts +23 -0
  278. package/src/lib/server/member-post.js +166 -0
  279. package/src/lib/server/member-post.js.map +1 -0
  280. package/src/lib/server/membership-account.d.ts +37 -0
  281. package/src/lib/server/membership-account.js +148 -0
  282. package/src/lib/server/membership-account.js.map +1 -0
  283. package/src/lib/server/membership-admin-password.d.ts +33 -0
  284. package/src/lib/server/membership-admin-password.js +202 -0
  285. package/src/lib/server/membership-admin-password.js.map +1 -0
  286. package/src/lib/server/membership-content.d.ts +23 -0
  287. package/src/lib/server/membership-content.js +105 -0
  288. package/src/lib/server/membership-content.js.map +1 -0
  289. package/src/lib/server/membership-login.d.ts +19 -0
  290. package/src/lib/server/membership-login.js +137 -0
  291. package/src/lib/server/membership-login.js.map +1 -0
  292. package/src/lib/server/membership-logout.d.ts +19 -0
  293. package/src/lib/server/membership-logout.js +35 -0
  294. package/src/lib/server/membership-logout.js.map +1 -0
  295. package/src/lib/server/membership-recover.d.ts +60 -0
  296. package/src/lib/server/membership-recover.js +244 -0
  297. package/src/lib/server/membership-recover.js.map +1 -0
  298. package/src/lib/server/membership-register.d.ts +22 -0
  299. package/src/lib/server/membership-register.js +241 -0
  300. package/src/lib/server/membership-register.js.map +1 -0
  301. package/src/lib/server/membership-reset.d.ts +26 -0
  302. package/src/lib/server/membership-reset.js +88 -0
  303. package/src/lib/server/membership-reset.js.map +1 -0
  304. package/src/lib/server/membership-wishlist.d.ts +23 -0
  305. package/src/lib/server/membership-wishlist.js +89 -0
  306. package/src/lib/server/membership-wishlist.js.map +1 -0
  307. package/src/lib/server/membership.d.ts +96 -0
  308. package/src/lib/server/membership.js +222 -0
  309. package/src/lib/server/membership.js.map +1 -0
  310. package/src/lib/server/native-checkout.d.ts +170 -0
  311. package/src/lib/server/native-checkout.js +193 -0
  312. package/src/lib/server/native-checkout.js.map +1 -0
  313. package/src/lib/server/newsletter.d.ts +23 -0
  314. package/src/lib/server/newsletter.js +323 -0
  315. package/src/lib/server/newsletter.js.map +1 -0
  316. package/src/lib/server/notify-restock.d.ts +22 -0
  317. package/src/lib/server/notify-restock.js +84 -0
  318. package/src/lib/server/notify-restock.js.map +1 -0
  319. package/src/lib/server/order-analytics.d.ts +134 -0
  320. package/src/lib/server/order-analytics.js +263 -0
  321. package/src/lib/server/order-analytics.js.map +1 -0
  322. package/src/lib/server/order-figures.d.ts +33 -0
  323. package/src/lib/server/order-figures.js +220 -0
  324. package/src/lib/server/order-figures.js.map +1 -0
  325. package/src/lib/server/paid-media-delivery.d.ts +173 -0
  326. package/src/lib/server/paid-media-delivery.js +212 -0
  327. package/src/lib/server/paid-media-delivery.js.map +1 -0
  328. package/src/lib/server/pos-order.d.ts +54 -0
  329. package/src/lib/server/pos-order.js +1137 -0
  330. package/src/lib/server/pos-order.js.map +1 -0
  331. package/src/lib/server/process-abandoned.d.ts +54 -0
  332. package/src/lib/server/process-abandoned.js +222 -0
  333. package/src/lib/server/process-abandoned.js.map +1 -0
  334. package/src/lib/server/process-restock.d.ts +42 -0
  335. package/src/lib/server/process-restock.js +219 -0
  336. package/src/lib/server/process-restock.js.map +1 -0
  337. package/src/lib/server/product-card.d.ts +29 -0
  338. package/src/lib/server/product-card.js +54 -0
  339. package/src/lib/server/product-card.js.map +1 -0
  340. package/src/lib/server/product.d.ts +70 -0
  341. package/src/lib/server/product.js +105 -0
  342. package/src/lib/server/product.js.map +1 -0
  343. package/src/lib/server/promotion-hold.d.ts +144 -0
  344. package/src/lib/server/promotion-hold.js +174 -0
  345. package/src/lib/server/promotion-hold.js.map +1 -0
  346. package/src/lib/server/reconcile-stock.d.ts +106 -0
  347. package/src/lib/server/reconcile-stock.js +349 -0
  348. package/src/lib/server/reconcile-stock.js.map +1 -0
  349. package/src/lib/server/refund.d.ts +45 -0
  350. package/src/lib/server/refund.js +607 -0
  351. package/src/lib/server/refund.js.map +1 -0
  352. package/src/lib/server/related.d.ts +24 -0
  353. package/src/lib/server/related.js +104 -0
  354. package/src/lib/server/related.js.map +1 -0
  355. package/src/lib/server/reservation-availability.d.ts +23 -0
  356. package/src/lib/server/reservation-availability.js +111 -0
  357. package/src/lib/server/reservation-availability.js.map +1 -0
  358. package/src/lib/server/reserve-stock.d.ts +156 -0
  359. package/src/lib/server/reserve-stock.js +146 -0
  360. package/src/lib/server/reserve-stock.js.map +1 -0
  361. package/src/lib/server/reserve.d.ts +25 -0
  362. package/src/lib/server/reserve.js +426 -0
  363. package/src/lib/server/reserve.js.map +1 -0
  364. package/src/lib/server/restock-flag.d.ts +202 -0
  365. package/src/lib/server/restock-flag.js +449 -0
  366. package/src/lib/server/restock-flag.js.map +1 -0
  367. package/src/lib/server/reviews.d.ts +51 -0
  368. package/src/lib/server/reviews.js +132 -0
  369. package/src/lib/server/reviews.js.map +1 -0
  370. package/src/lib/server/site-page-enricher.d.ts +42 -0
  371. package/src/lib/server/site-page-enricher.js +149 -0
  372. package/src/lib/server/site-page-enricher.js.map +1 -0
  373. package/src/lib/server/site-page-resolver.d.ts +26 -0
  374. package/src/lib/server/site-page-resolver.js +223 -0
  375. package/src/lib/server/site-page-resolver.js.map +1 -0
  376. package/src/lib/server/stock-hold.d.ts +127 -0
  377. package/src/lib/server/stock-hold.js +258 -0
  378. package/src/lib/server/stock-hold.js.map +1 -0
  379. package/src/lib/server/storefront-tax-record.d.ts +22 -0
  380. package/src/lib/server/storefront-tax-record.js +218 -0
  381. package/src/lib/server/storefront-tax-record.js.map +1 -0
  382. package/src/lib/server/storefront-tax.d.ts +197 -0
  383. package/src/lib/server/storefront-tax.js +245 -0
  384. package/src/lib/server/storefront-tax.js.map +1 -0
  385. package/src/lib/server/stream.d.ts +25 -0
  386. package/src/lib/server/stream.js +191 -0
  387. package/src/lib/server/stream.js.map +1 -0
  388. package/src/lib/server/subscription-portal.d.ts +24 -0
  389. package/src/lib/server/subscription-portal.js +91 -0
  390. package/src/lib/server/subscription-portal.js.map +1 -0
  391. package/src/lib/server/supplier-outbox.d.ts +190 -0
  392. package/src/lib/server/supplier-outbox.js +342 -0
  393. package/src/lib/server/supplier-outbox.js.map +1 -0
  394. package/src/lib/server/supplier-update.d.ts +23 -0
  395. package/src/lib/server/supplier-update.js +289 -0
  396. package/src/lib/server/supplier-update.js.map +1 -0
  397. package/src/lib/server/tax-profile.d.ts +29 -0
  398. package/src/lib/server/tax-profile.js +40 -0
  399. package/src/lib/server/tax-profile.js.map +1 -0
  400. package/src/lib/server.d.ts +32 -0
  401. package/src/lib/server.js +311 -0
  402. package/src/lib/server.js.map +1 -0
  403. package/src/lib/site.d.ts +27 -0
  404. package/src/lib/site.js +134 -0
  405. package/src/lib/site.js.map +1 -0
  406. package/src/lib/utils/escape-html.d.ts +41 -0
  407. package/src/lib/utils/escape-html.js +41 -0
  408. package/src/lib/utils/escape-html.js.map +1 -0
  409. package/src/lib/utils/generate-preset-id.d.ts +19 -0
  410. package/src/lib/utils/generate-preset-id.js +25 -0
  411. package/src/lib/utils/generate-preset-id.js.map +1 -0
  412. package/src/lib/utils/member-continue.d.ts +45 -0
  413. package/src/lib/utils/member-continue.js +49 -0
  414. package/src/lib/utils/member-continue.js.map +1 -0
  415. package/src/lib/utils/use-storefront-purchase-event.d.ts +60 -0
  416. package/src/lib/utils/use-storefront-purchase-event.js +142 -0
  417. package/src/lib/utils/use-storefront-purchase-event.js.map +1 -0
@@ -0,0 +1,976 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ import { _ as _extends } from "@swc/helpers/_/_extends";
17
+ import * as Aglyn from "@aglyn/aglyn/server";
18
+ import * as CommerceModel from "../model/index.js";
19
+ import { claimAttempt, deriveStripeObjectKey } from "@aglyn/aglyn/server";
20
+ import { firebaseAdmin, getOrgForHost } from "@aglyn/tenant-data-admin";
21
+ import { connectLinkageIsReady } from "@aglyn/tenant-data-admin/server/stripe-account-mode";
22
+ import { readCartId } from "./cart-cookie.js";
23
+ import { resolveManualTaxRateId } from "./manual-tax-rate.js";
24
+ import { holdPromotionSlot, promotionHoldKey } from "./promotion-hold.js";
25
+ import { holdStock, stockHoldKey } from "./stock-hold.js";
26
+ import { applyNativeCheckoutParams, nativeCheckoutStripeHeaders, readCheckoutSessionPayload, resolveNativeCheckoutMode } from "./native-checkout.js";
27
+ /**
28
+ * Cart checkout (AGL-293): the whole cart in one Stripe Checkout
29
+ * session on the merchant's account. Every line re-prices from its
30
+ * product doc, unavailable lines block with a visible error, the
31
+ * platform fee sums per line by product type (AGL-278 ladder), and the
32
+ * webhook creates one multi-line order and clears the cart.
33
+ */ export const cartCheckoutHandler = async (req, res)=>{
34
+ var _req_body, _body_hostId, _body_email, _body_giftCardCode, _body_couponCode, _ref, _req_headers_idempotencykey;
35
+ if (req.method !== 'POST') {
36
+ return res.status(405).json({
37
+ error: 'Method not allowed'
38
+ });
39
+ }
40
+ if (!process.env.STRIPE_SECRET_KEY) {
41
+ return res.status(501).json({
42
+ error: 'Purchases are not configured on this site.'
43
+ });
44
+ }
45
+ const body = typeof req.body === 'string' ? JSON.parse(req.body) : (_req_body = req.body) != null ? _req_body : {};
46
+ const hostId = String((_body_hostId = body.hostId) != null ? _body_hostId : '');
47
+ const email = String((_body_email = body.email) != null ? _body_email : '').trim().toLowerCase().slice(0, 120);
48
+ const marketingOptIn = Boolean(body.marketingOptIn);
49
+ const giftCardCode = String((_body_giftCardCode = body.giftCardCode) != null ? _body_giftCardCode : '').trim().toUpperCase().slice(0, 24);
50
+ const couponCode = String((_body_couponCode = body.couponCode) != null ? _body_couponCode : '').trim().toUpperCase().slice(0, 40);
51
+ if (!hostId) return res.status(400).json({
52
+ error: 'Missing hostId'
53
+ });
54
+ // AGL-1769: validated here even though this handler only READS the cart,
55
+ // because it is where the raw cookie left the request — `:342` stamps it
56
+ // into `metadata[cartId]`, and the billing webhook builds a document path
57
+ // from that copy. Closing it at the source is what lets the webhook keep
58
+ // taking its metadata at face value.
59
+ const cartId = readCartId(req.cookies, hostId);
60
+ if (!cartId) return res.status(400).json({
61
+ error: 'Your cart is empty'
62
+ });
63
+ // One checkout attempt, minted by the cart drawer (AGL-1697). Node
64
+ // lowercases incoming headers, but read both spellings — the plugin API
65
+ // request type makes no promise about casing.
66
+ const idempotencyKey = String((_ref = (_req_headers_idempotencykey = req.headers['idempotency-key']) != null ? _req_headers_idempotencykey : req.headers['Idempotency-Key']) != null ? _ref : '').trim().slice(0, 200);
67
+ let claim = null;
68
+ /**
69
+ * Promotion slots this attempt reserved (AGL-2453), hoisted so the catch
70
+ * below can hand them back too. A throw after a hold is placed is exactly the
71
+ * case where an invisible slot would sit against a merchant's cap for a day.
72
+ */ const heldPromotions = [];
73
+ const releasePromotionHolds = async ()=>{
74
+ for (const hold of heldPromotions.splice(0))await hold.release();
75
+ };
76
+ /**
77
+ * The units this attempt reserved (AGL-2356), hoisted for the same reason —
78
+ * a throw after the hold is placed is exactly the case where a merchant's
79
+ * last unit would sit invisibly spoken for until the TTL lapsed.
80
+ */ let stockHold = null;
81
+ let heldStockKey = '';
82
+ const releaseStock = async ()=>{
83
+ const held = stockHold;
84
+ stockHold = null;
85
+ heldStockKey = '';
86
+ if (held) await held.release();
87
+ };
88
+ try {
89
+ var _cartSnapshot_data, _ref1, _storeSettings_get, _req_headers_referer, _ref2;
90
+ var _ownerOrg_org, _ownerOrg_org1, _ownerOrg_org2;
91
+ const firestore = firebaseAdmin.app().firestore();
92
+ const hostRef = firestore.collection('hosts').doc(hostId);
93
+ const cartSnapshot = await hostRef.collection('carts').doc(cartId).get();
94
+ const cart = (_cartSnapshot_data = cartSnapshot.data()) != null ? _cartSnapshot_data : {
95
+ lines: []
96
+ };
97
+ if (cart.lines.length === 0) {
98
+ return res.status(400).json({
99
+ error: 'Your cart is empty'
100
+ });
101
+ }
102
+ const ownerOrg = await getOrgForHost(hostId);
103
+ const ownerId = ownerOrg == null ? void 0 : (_ownerOrg_org = ownerOrg.org) == null ? void 0 : _ownerOrg_org.ownerUid;
104
+ if (!ownerId) {
105
+ return res.status(409).json({
106
+ error: 'This site cannot sell yet'
107
+ });
108
+ }
109
+ // Storefront selling is the `commerce` entitlement (Starter+) — not
110
+ // `marketplaceSelling`, which gates the marketplace marketplace (AGL-470).
111
+ if (!Aglyn.checkEntitlement(ownerOrg.org, 'commerce')) {
112
+ return res.status(403).json({
113
+ error: 'Selling is not enabled'
114
+ });
115
+ }
116
+ const ownerProfile = await firestore.collection('profiles').doc(String(ownerId)).get();
117
+ const accountId = ownerProfile.get('stripeAccountId');
118
+ if (!connectLinkageIsReady({
119
+ accountId,
120
+ chargesEnabled: ownerProfile.get('stripeChargesEnabled'),
121
+ accountLivemode: ownerProfile.get('stripeAccountLivemode')
122
+ }, {
123
+ subject: `cart checkout host ${hostId}`
124
+ })) {
125
+ return res.status(409).json({
126
+ error: 'This site has not enabled payments yet'
127
+ });
128
+ }
129
+ // Re-price every line from its doc; any unavailable line blocks.
130
+ const productSnapshots = await Promise.all([
131
+ ...new Set(cart.lines.map((line)=>line.productId))
132
+ ].map((id)=>hostRef.collection('products').doc(id).get()));
133
+ const productsById = new Map(productSnapshots.map((snapshot)=>[
134
+ snapshot.id,
135
+ snapshot.exists ? CommerceModel.liftLegacyProduct(snapshot.data()) : null
136
+ ]));
137
+ let itemsCents = 0;
138
+ /** Per-line values for scoped discount pricing. */ const discountLines = [];
139
+ let feeCents = 0;
140
+ /** Whether any line carries a fee rate above zero (AGL-2232). */ let feeApplies = false;
141
+ /**
142
+ * The goods the shopper actually pays for, after any discount that reached
143
+ * the session (AGL-2232). Starts as the whole basket and is lowered only
144
+ * when a Stripe coupon is minted, which is the one reduction the fee is
145
+ * scaled by.
146
+ */ let chargedItemsCents = 0;
147
+ // Weight-tier shipping rates price off this (AGL-1707). Without it every
148
+ // `weight_tiers` rate would resolve at 0g and quote the lightest tier.
149
+ let totalGrams = 0;
150
+ // Whether anything in this cart is posted at all (AGL-1721). Buy-now has
151
+ // always resolved shipping for physical products only; the cart never
152
+ // asked, so a cart of downloads could be charged to ship. It matters more
153
+ // now that an unresolvable destination REFUSES: a shopper buying two PDFs
154
+ // must not be asked which country to ship them to.
155
+ let hasPhysicalLine = false;
156
+ /**
157
+ * What this cart will RESERVE (AGL-2356), collected as the loop below
158
+ * resolves each line's variant rather than re-derived afterwards: the
159
+ * reservation and the price must be taken against the same variant, and a
160
+ * second resolution is a second chance to pick a different one.
161
+ */ const reserveLines = [];
162
+ // Cart checkout never builds subscription sessions — every line bills
163
+ // one-time in `payment` mode (recurring products subscribe through the
164
+ // PDP's direct checkout, AGL-303) — so the buyer-chosen billing field
165
+ // (AGL-545) does not apply here.
166
+ const params = new URLSearchParams({
167
+ mode: 'payment'
168
+ });
169
+ cart.lines.forEach((line, index)=>{
170
+ var _variant_weightGrams, _product_type, _product_type1, _variant_options, _variant_options1;
171
+ const product = productsById.get(line.productId);
172
+ if (!product || product.deletedAt || product.status !== 'active') {
173
+ var _ref;
174
+ throw Object.assign(new Error('unavailable'), {
175
+ visible: `"${(_ref = product == null ? void 0 : product.name) != null ? _ref : 'A product'}" is no longer available`
176
+ });
177
+ }
178
+ const variant = line.variantId ? product.variants.find((item)=>item.id === line.variantId) : product.variants[0];
179
+ if (!variant || !CommerceModel.canPurchase(product, variant.id, line.quantity)) {
180
+ throw Object.assign(new Error('unavailable'), {
181
+ visible: `"${product.name}" is sold out`
182
+ });
183
+ }
184
+ // A variant with no price yet (AGL-2916) is not for sale: pricing it
185
+ // would charge nothing for it, or send the processor no amount.
186
+ if (!CommerceModel.variantHasPrice(variant)) {
187
+ throw Object.assign(new Error('unavailable'), {
188
+ visible: `"${product.name}" is not available`
189
+ });
190
+ }
191
+ // Gift cards are a Business+ entitlement, checked per sale (AGL-470).
192
+ if (product.giftCard && !Aglyn.checkEntitlement(ownerOrg.org, 'giftCards')) {
193
+ throw Object.assign(new Error('unavailable'), {
194
+ visible: `"${product.name}" is not available on this store's plan`
195
+ });
196
+ }
197
+ // Only the lines there is something to reserve ON. An untracked variant
198
+ // and a `backorder` product have no finite shelf, so a hold could never
199
+ // change what they answer — and collecting them would open a transaction
200
+ // over the products of a purely digital storefront to write nothing at
201
+ // all. The same decision `holdPromotionSlot` makes for an UNCAPPED
202
+ // promotion: the unlimited case stays off the write path entirely.
203
+ //
204
+ // A pre-filter, so the authority is still the in-transaction re-check in
205
+ // `holdStock`. The only thing it can get wrong is skipping a hold for a
206
+ // variant that became tracked between this read and that transaction,
207
+ // which is the behaviour that shipped before this issue.
208
+ if (CommerceModel.stockIsReservable(product, variant.id, Date.now())) {
209
+ reserveLines.push({
210
+ productId: line.productId,
211
+ variantId: variant.id,
212
+ quantity: line.quantity
213
+ });
214
+ }
215
+ const unitCents = Math.round(Number(variant.priceUsd) * 100);
216
+ itemsCents += unitCents * line.quantity;
217
+ // What THIS line is worth, so a product-scoped discount can be priced
218
+ // against the lines it actually covers rather than the whole basket
219
+ //.
220
+ discountLines.push({
221
+ productId: line.productId,
222
+ amountCents: unitCents * line.quantity
223
+ });
224
+ totalGrams += Math.max(0, Number((_variant_weightGrams = variant.weightGrams) != null ? _variant_weightGrams : 0)) * line.quantity;
225
+ // A missing `type` reads as physical, exactly as the fee ladder below
226
+ // reads it — `liftLegacyProduct` only defaults the field on docs it
227
+ // synthesizes variants for, so a part-migrated doc can reach here
228
+ // without one and the two reads must not disagree about what it is.
229
+ if (((_product_type = product.type) != null ? _product_type : 'physical') === 'physical') hasPhysicalLine = true;
230
+ // `?? 'physical'`, which the comment four lines up already promised and
231
+ // the code did not deliver (AGL-2251). `resolveTransactionFeePct` keys
232
+ // on `productType === 'physical'` and sends everything else — `undefined`
233
+ // included — to the DIGITAL rate, so a part-migrated product doc with no
234
+ // `type` was priced at 3%/2%/1% here while `checkout.ts` (`lifted.type ??
235
+ // 'physical'`) priced the identical product at the physical rate. The
236
+ // same basket cost the merchant a different fee depending on which
237
+ // button the shopper pressed.
238
+ const feePct = Aglyn.resolveTransactionFeePct(ownerOrg.org, (_product_type1 = product.type) != null ? _product_type1 : 'physical');
239
+ // WHETHER a rate applies is not the same fact as what it rounds to
240
+ // (AGL-2232), and only the sum was being kept. `checkout.ts`,
241
+ // `draft-order.ts` and `reserve.ts` all floor at `Math.max(1, …)` when
242
+ // `feePct > 0`; the cart floored nothing, so a Scale-plan cart of $0.30
243
+ // digital lines rounded every line to 0, the emission guard below read
244
+ // `feeCents > 0` as "this plan charges nothing", and the session went out
245
+ // with no application fee at all.
246
+ if (feePct > 0) feeApplies = true;
247
+ feeCents += Math.round(unitCents * line.quantity * feePct / 100);
248
+ params.set(`line_items[${index}][quantity]`, String(line.quantity));
249
+ params.set(`line_items[${index}][price_data][currency]`, 'usd');
250
+ params.set(`line_items[${index}][price_data][unit_amount]`, String(unitCents));
251
+ params.set(`line_items[${index}][price_data][product_data][name]`, `${product.name}${Object.keys((_variant_options = variant.options) != null ? _variant_options : {}).length ? ` (${Object.values((_variant_options1 = variant.options) != null ? _variant_options1 : {}).join(' / ')})` : ''}`.slice(0, 120));
252
+ });
253
+ // Shipping (AGL-1707), from the settings document tax is also read from.
254
+ // This is the ONLY thing that makes Stripe charge shipping: without
255
+ // `shipping_options` it presents no shipping choice,
256
+ // `total_details.amount_shipping` is 0, and the zones and rates the
257
+ // merchant configured in the console are silently worth nothing.
258
+ //
259
+ // RESOLVED BEFORE ANY STRIPE CALL, ahead of the discount block below,
260
+ // because the plan can refuse — and the discount block creates real coupon
261
+ // objects on the merchant's account. Refusing after them would leave one
262
+ // orphaned on every attempt.
263
+ //
264
+ // A merchant who has configured nothing plans to no options and gets a
265
+ // session byte-identical to today's: no shipping charged, exactly as
266
+ // before. Only a merchant who set rates up starts collecting them.
267
+ const storeSettings = await hostRef.collection('settings').doc('store').get();
268
+ const shippingPlan = hasPhysicalLine ? CommerceModel.planCheckoutShipping(storeSettings.get('shipping'), {
269
+ subtotalCents: itemsCents,
270
+ totalGrams
271
+ }, body.shippingCountry) : {
272
+ countries: CommerceModel.CHECKOUT_SHIPPING_COUNTRIES,
273
+ options: []
274
+ };
275
+ // The shopper is asked, then the answer is ENFORCED (AGL-1721): a declared
276
+ // destination narrows `allowed_countries` to itself, so the rate resolved
277
+ // for it is the only one on the session and no other address can be
278
+ // entered against it. Answering the 400 is not optional — a caller that
279
+ // skips the field is refused again rather than served the union.
280
+ if (shippingPlan.refusal === 'destination-required') {
281
+ return res.status(400).json({
282
+ error: 'Choose where you’re shipping to.',
283
+ needsShippingCountry: true,
284
+ shippingCountries: [
285
+ ...CommerceModel.CHECKOUT_SHIPPING_COUNTRIES
286
+ ]
287
+ });
288
+ }
289
+ if (shippingPlan.refusal === 'destination-unserved') {
290
+ var _CommerceModel_CHECKOUT_SHIPPING_COUNTRY_NAMES_declared;
291
+ const declared = CommerceModel.normalizeCheckoutShippingCountry(body.shippingCountry);
292
+ return res.status(409).json({
293
+ error: `This store does not ship to ${(_CommerceModel_CHECKOUT_SHIPPING_COUNTRY_NAMES_declared = CommerceModel.CHECKOUT_SHIPPING_COUNTRY_NAMES[declared]) != null ? _CommerceModel_CHECKOUT_SHIPPING_COUNTRY_NAMES_declared : 'that country'}.`,
294
+ needsShippingCountry: true,
295
+ shippingCountries: [
296
+ ...CommerceModel.CHECKOUT_SHIPPING_COUNTRIES
297
+ ]
298
+ });
299
+ }
300
+ // The merchant priced shipping and their table cannot reach this cart
301
+ // (AGL-2232) — a 3 kg basket at a store whose weight tiers stop at 2 kg.
302
+ // No `needsShippingCountry`: asking where it goes cannot price it, and a
303
+ // shopper handed the country select would answer it and be refused again.
304
+ if (shippingPlan.refusal === 'cart-unpriceable') {
305
+ return res.status(409).json({
306
+ error: CommerceModel.CART_UNPRICEABLE_SHIPPING_MESSAGE
307
+ });
308
+ }
309
+ chargedItemsCents = itemsCents;
310
+ // Discounts engine (AGL-305): entered codes and automatic
311
+ // promotions from hosts/{id}/discounts; the AGL-96 coupons remain a
312
+ // fallback for unknown codes.
313
+ const discountsSnapshot = await hostRef.collection('discounts').limit(100).get();
314
+ const resolvedDiscount = CommerceModel.resolveDiscount(discountsSnapshot.docs.map((docSnapshot)=>_extends({}, docSnapshot.data(), {
315
+ $id: docSnapshot.id
316
+ })), _extends({}, couponCode ? {
317
+ code: couponCode
318
+ } : {}, {
319
+ subtotalCents: itemsCents,
320
+ productIds: cart.lines.map((line)=>line.productId),
321
+ lines: discountLines
322
+ }));
323
+ if (resolvedDiscount == null ? void 0 : resolvedDiscount.codeProblem) {
324
+ return res.status(400).json({
325
+ error: resolvedDiscount.codeProblem
326
+ });
327
+ }
328
+ // Point of no return (AGL-1697): everything past here creates Stripe
329
+ // objects — up to two coupons and the session — on the merchant's live
330
+ // account. The cart is not consumed until the webhook clears it, so
331
+ // before the claim one cart could spawn unlimited sessions, each retry
332
+ // leaving an orphan `checkouts/{sessionId}` doc (which feeds the AGL-323
333
+ // abandoned-cart emails) plus a stray coupon.
334
+ //
335
+ // The two refusals that remain BELOW this line (invalid legacy coupon,
336
+ // empty gift card) each release the claim: they are deterministic, the
337
+ // shopper fixes the code and presses the same button, and because the
338
+ // retry re-derives the same digest Stripe replays any coupon the first
339
+ // run already minted rather than creating a twin (AGL-1714's rule).
340
+ const claimed = await claimAttempt(firestore, {
341
+ kind: 'commerce-cart',
342
+ scopeId: `${hostId}:${cartId}`,
343
+ orgId: String((_ref1 = ownerOrg == null ? void 0 : (_ownerOrg_org1 = ownerOrg.org) == null ? void 0 : _ownerOrg_org1.id) != null ? _ref1 : ''),
344
+ key: idempotencyKey,
345
+ busyMessage: 'This checkout is already being processed'
346
+ });
347
+ if ('replay' in claimed) {
348
+ return res.status(claimed.replay.status).json(claimed.replay.body);
349
+ }
350
+ claim = claimed.claim;
351
+ // Derived per OBJECT, never the raw digest (AGL-1714): Stripe
352
+ // parameter-compares a repeated key account-wide, so one digest sent to
353
+ // `/v1/coupons` and then `checkout/sessions` would error the second call.
354
+ const stripeKeyHeader = (object)=>{
355
+ const derived = deriveStripeObjectKey(claimed.claim.stripeKey, object);
356
+ return derived ? {
357
+ 'Idempotency-Key': derived
358
+ } : {};
359
+ };
360
+ // THE UNITS ARE RESERVED HERE (AGL-2356), not merely read in the loop
361
+ // above.
362
+ //
363
+ // `canPurchase` up there is a plain `.get()` compared against a number, and
364
+ // the webhook decremented minutes later without ever re-asking. Nothing was
365
+ // written, so there was no document to contend on: every shopper who
366
+ // reached this line while the shelf held one unit passed it, and they all
367
+ // paid. This is the write that makes them serialise.
368
+ //
369
+ // ONE transaction across every product in the cart, so a two-product basket
370
+ // reserves both or neither — a partial reservation would charge a shopper
371
+ // for goods the store cannot fill, which is this issue's own defect
372
+ // arriving through its fix. See `stock-hold.ts`.
373
+ //
374
+ // FIRST among the three holds this handler takes, before the promotion
375
+ // slots and the gift card below: stock is the likeliest refusal, and
376
+ // reserving a merchant's capped discount for a basket the store cannot fill
377
+ // is a slot held for nothing.
378
+ {
379
+ const held = await holdStock({
380
+ firestore,
381
+ hostRef,
382
+ holdKey: stockHoldKey(claimed.claim.stripeKey),
383
+ lines: reserveLines,
384
+ label: `cart ${hostId}/${cartId}`
385
+ });
386
+ if (!held.ok) {
387
+ // Nothing has been minted yet, so the key goes back and the same
388
+ // button works when the units return (AGL-1697). `sold-out` names the
389
+ // shelf being SPOKEN FOR rather than empty — the merchant has stock and
390
+ // another shopper is in checkout with it, and telling this one to come
391
+ // back in a few minutes is the difference between a wait and a loss.
392
+ await releaseStock();
393
+ await claim.release();
394
+ return res.status(409).json({
395
+ error: held.reason === 'sold-out' ? held.productName ? `"${held.productName}" — ${CommerceModel.STOCK_HELD_MESSAGE}` : CommerceModel.STOCK_HELD_MESSAGE : 'Your cart is no longer available'
396
+ });
397
+ }
398
+ if (held.holdKey) {
399
+ stockHold = held;
400
+ heldStockKey = held.holdKey;
401
+ }
402
+ }
403
+ // ONE Stripe discount, and that is the whole shape of this block
404
+ // (AGL-2112).
405
+ //
406
+ // A Checkout Session takes exactly one entry in `discounts`. This used to
407
+ // be three independent blocks — an AGL-305 discount, a legacy AGL-96
408
+ // coupon code, and an AGL-322 gift card — each minting its own Stripe
409
+ // coupon and each doing `params.set('discounts[0][coupon]', …)`.
410
+ // `URLSearchParams.set` REPLACES, so a cart carrying a discount AND a gift
411
+ // card minted two real coupon objects on the merchant's account and sent
412
+ // only the LAST one. The shopper paid full price minus the gift card; the
413
+ // discount silently evaporated — and the webhook still burned its
414
+ // redemption against `maxRedemptions`, so the code was spent on an order
415
+ // that never got it.
416
+ //
417
+ // The fee was wrong in the same combination and in Aglyn's favour by
418
+ // accident, then against it: `feeCents` was scaled DOWN once per block, so
419
+ // two reductions compounded for one discount that was actually applied.
420
+ //
421
+ // So: resolve every reduction FIRST, add them up, and mint a single
422
+ // `amount_off` coupon for the sum. A percentage coupon becomes its cash
423
+ // equivalent against `itemsCents` — which is what the fee arithmetic here
424
+ // has always assumed it was, and what `checkout.ts` already does for
425
+ // buy-now (`amountCents * (100 - percentOff) / 100`).
426
+ //
427
+ // Validation still happens in the same order and still 400s the same way,
428
+ // BEFORE anything is minted — the shopper fixes the code and presses the
429
+ // same button, so the claim is released and the key survives (AGL-1697).
430
+ // REDEMPTION SLOTS ARE HELD, NOT MERELY READ (AGL-2453).
431
+ //
432
+ // `resolveDiscount` above and the coupon `.get()` below both answered "is
433
+ // there a slot left?" from a plain read, and the webhook incremented the
434
+ // counter minutes later without ever re-asking. The gap is the whole
435
+ // Checkout Session lifetime — up to 24 hours — so every shopper who reached
436
+ // this line while a cap of 100 sat at 99 passed it, and the counter
437
+ // finished at 99+N. Nothing was lost; nothing was ever CHECKED.
438
+ //
439
+ // The AGL-2449 shape, reused: the slot is claimed in a transaction here and
440
+ // settled in one at the webhook. Both redemption paths hold — the typed
441
+ // coupon and the automatic discount — because holding one of two doors onto
442
+ // the same counter fixes neither, and the automatic path is the one a
443
+ // shopper does not even have to opt into.
444
+ //
445
+ // A held slot COUNTS while its session is live and is released when the
446
+ // session expires, is cancelled, or this handler refuses below. See
447
+ // `model/commerce-promotions.ts` for why that is the right way round.
448
+ const slotKey = promotionHoldKey(claim.stripeKey);
449
+ let totalOffCents = 0;
450
+ if (resolvedDiscount && resolvedDiscount.benefit.kind !== 'none') {
451
+ const discountRef = hostRef.collection('discounts').doc(resolvedDiscount.discountId);
452
+ const slot = await holdPromotionSlot({
453
+ firestore,
454
+ ref: discountRef,
455
+ holdKey: slotKey,
456
+ label: `discount ${resolvedDiscount.discountId}`
457
+ });
458
+ if (!slot.ok) {
459
+ // Refused rather than silently dropped, and that is a deliberate
460
+ // choice for the AUTOMATIC case: this discount was in the price the
461
+ // storefront showed, so continuing without it would charge more than
462
+ // the shopper agreed to and tell them nothing. Nothing has been minted
463
+ // yet, so this is retryable — and the retry prices correctly, because
464
+ // `resolveDiscount` counts holds too and simply stops offering it.
465
+ await claim.release();
466
+ return res.status(409).json({
467
+ error: couponCode && resolvedDiscount.discount.code ? CommerceModel.PROMOTION_EXHAUSTED_MESSAGE : CommerceModel.PROMOTION_UNAVAILABLE_MESSAGE
468
+ });
469
+ }
470
+ if (slot.holdKey) heldPromotions.push(slot);
471
+ totalOffCents += resolvedDiscount.discountCents;
472
+ params.set('metadata[discountId]', resolvedDiscount.discountId);
473
+ // Only when a slot was actually reserved. Its ABSENCE is what tells the
474
+ // webhook to fall back to the unconditional increment, which is right for
475
+ // an uncapped promotion and for a session minted before this deploy.
476
+ if (slot.holdKey) {
477
+ params.set('metadata[discountHoldKey]', slot.holdKey);
478
+ }
479
+ }
480
+ // FREE SHIPPING IS A ZEROED RATE, NOT A COUPON.
481
+ //
482
+ // A session-level Stripe coupon discounts LINE ITEMS; shipping is a
483
+ // separate `shipping_options` concept and a coupon never touches it. There
484
+ // is also no amount to discount at this point — the shopper picks their
485
+ // rate after the session is created — so the only construction that can be
486
+ // exact is to offer every rate at zero.
487
+ //
488
+ // Every rate, not just the cheapest: `free_shipping` carries no field
489
+ // scoping it to one, so the merchant asked for shipping to be free rather
490
+ // than for one particular rate to be. Zeroing the dearest too also keeps
491
+ // the fee and transfer arithmetic below honest, since both read these same
492
+ // options and the shopper is paying nothing for carriage either way.
493
+ const shippingOptions = (resolvedDiscount == null ? void 0 : resolvedDiscount.freeShipping) === true ? shippingPlan.options.map((option)=>_extends({}, option, {
494
+ amountCents: 0
495
+ })) : shippingPlan.options;
496
+ // Coupons (AGL-96 semantics): percent off the items total.
497
+ if (couponCode && !resolvedDiscount) {
498
+ var _ref3;
499
+ const couponRef = hostRef.collection('coupons').doc(couponCode);
500
+ const couponSnapshot = await couponRef.get();
501
+ const coupon = couponSnapshot.data();
502
+ const percentOff = Number((_ref3 = coupon == null ? void 0 : coupon.percentOff) != null ? _ref3 : 0);
503
+ const expired = (coupon == null ? void 0 : coupon.expiresAtMs) != null && Number(coupon.expiresAtMs) < Date.now();
504
+ if (!coupon || coupon.enabled === false || expired || !(percentOff > 0 && percentOff <= 100)) {
505
+ // Deterministic and retryable — the shopper fixes the code and
506
+ // presses the same button, so the key survives (AGL-1697).
507
+ await claim.release();
508
+ return res.status(400).json({
509
+ error: 'Invalid or expired coupon'
510
+ });
511
+ }
512
+ // The exhaustion test moved INTO a transaction that also writes
513
+ // (AGL-2453). It used to be a comparison against a plain `.get()` here,
514
+ // and the webhook incremented the counter minutes later without
515
+ // re-asking — so the check bounded nothing once two shoppers held
516
+ // sessions at the same time.
517
+ const slot = await holdPromotionSlot({
518
+ firestore,
519
+ ref: couponRef,
520
+ holdKey: slotKey,
521
+ label: `coupon ${couponCode}`
522
+ });
523
+ if (!slot.ok) {
524
+ await claim.release();
525
+ return res.status(400).json({
526
+ error: slot.reason === 'exhausted' ? CommerceModel.PROMOTION_EXHAUSTED_MESSAGE : 'Invalid or expired coupon'
527
+ });
528
+ }
529
+ if (slot.holdKey) {
530
+ heldPromotions.push(slot);
531
+ params.set('metadata[couponHoldKey]', slot.holdKey);
532
+ }
533
+ totalOffCents += Math.round(itemsCents * percentOff / 100);
534
+ params.set('metadata[couponCode]', couponCode);
535
+ }
536
+ // Gift cards (AGL-322): balance applies as amount-off; the webhook settles
537
+ // the hold this places on completion.
538
+ //
539
+ // The read is a TRANSACTION and it WRITES (AGL-2449). A gift card is cash,
540
+ // and a plain `.get()` here was the last unreserved limited resource in the
541
+ // order path: two checkouts entering the same code both read $50, both got
542
+ // $50 off, and the webhook's bare `increment(-N)` settled the card at
543
+ // -$50 — $100 of goods against a $50 card, and a negative row in the
544
+ // outstanding-liability total. The increment was atomic the whole time;
545
+ // nothing ever CHECKED.
546
+ //
547
+ // Unlike stock (AGL-2320), this door can still refuse: the card is applied
548
+ // BEFORE Stripe is contacted, so no money has moved yet and a shortfall is
549
+ // a 400 rather than a shipped parcel and an apology. That is why this
550
+ // reserves instead of merely reporting.
551
+ //
552
+ // `giftCardCents` stays in the session metadata because the webhook still
553
+ // needs to know what to settle, but it is no longer the AUTHORITY for the
554
+ // decrement — the hold is, and it is keyed by the session id below.
555
+ // Set only once a hold actually stands, so the refusals below can call it
556
+ // unconditionally without knowing whether there was a gift card at all.
557
+ let releaseGiftCardHold = async ()=>undefined;
558
+ if (giftCardCode) {
559
+ const cardRef = hostRef.collection('giftCards').doc(giftCardCode);
560
+ // Placed before the Stripe session exists, so it is keyed by the
561
+ // idempotency claim's derived key rather than by a session id we do not
562
+ // have yet — the same string the session is minted under below, so a
563
+ // retry of this attempt re-places the SAME hold rather than a second one.
564
+ const holdKey = claim.stripeKey;
565
+ const nowMs = Date.now();
566
+ const held = await firestore.runTransaction(async (transaction)=>{
567
+ var _fresh_data, _ref, _card_holds;
568
+ var _holds_holdKey;
569
+ const fresh = await transaction.get(cardRef);
570
+ if (!fresh.exists) return null;
571
+ const card = (_fresh_data = fresh.data()) != null ? _fresh_data : {};
572
+ const holds = CommerceModel.pruneGiftCardHolds(card.holds, nowMs);
573
+ // This attempt's own prior hold does not stand in its way — a retry
574
+ // must be able to re-claim what it already reserved, or the second
575
+ // press of the same button refuses the shopper their own money.
576
+ const mine = Number((_ref = (_holds_holdKey = holds[holdKey]) == null ? void 0 : _holds_holdKey.cents) != null ? _ref : 0);
577
+ delete holds[holdKey];
578
+ const available = CommerceModel.giftCardAvailableCents(_extends({}, card, {
579
+ holds
580
+ }), nowMs);
581
+ if (available <= 0 && mine <= 0) return null;
582
+ // Against what is LEFT after the discount, not against the whole
583
+ // basket (AGL-2112). The old cap was `min(balance, itemsCents)`, so a
584
+ // $50 card on a $100 basket already 30% off consumed $50 of card
585
+ // against $70 of goods — the webhook decrements by exactly this
586
+ // number, so the extra was burned off the customer's card for nothing.
587
+ const applyCents = Math.min(available + mine, Math.max(0, itemsCents - totalOffCents));
588
+ if (applyCents <= 0) return 0;
589
+ // Lapsed holds are deleted by SENTINEL, not by writing back a pruned
590
+ // copy of the map: `set(…, { merge: true })` merges nested maps
591
+ // rather than replacing them, so a locally-pruned object leaves every
592
+ // stale key exactly where it was. Correctness does not depend on this
593
+ // — every read prunes again — but the document would otherwise grow
594
+ // without bound, one dead key per abandoned checkout, forever.
595
+ const swept = {};
596
+ for (const stale of Object.keys((_card_holds = card.holds) != null ? _card_holds : {})){
597
+ if (stale !== holdKey && !holds[stale]) {
598
+ swept[stale] = firebaseAdmin.firestore.FieldValue.delete();
599
+ }
600
+ }
601
+ transaction.set(cardRef, {
602
+ holds: _extends({}, swept, {
603
+ [holdKey]: {
604
+ cents: applyCents,
605
+ expiresAtMs: nowMs + CommerceModel.GIFT_CARD_HOLD_TTL_MS
606
+ }
607
+ })
608
+ }, {
609
+ merge: true
610
+ });
611
+ return applyCents;
612
+ }).catch((error)=>{
613
+ console.error('Gift card hold failed', giftCardCode, error);
614
+ return null;
615
+ });
616
+ if (held == null) {
617
+ // Deterministic and retryable; nothing has been minted yet. Covers the
618
+ // unknown card, the empty card, and the card whose whole balance is
619
+ // already held by someone else's live checkout — the shopper is told
620
+ // the same thing in each case, because "someone is spending it right
621
+ // now" is not a distinction a storefront should draw.
622
+ await claim.release();
623
+ return res.status(400).json({
624
+ error: 'Gift card is empty or invalid'
625
+ });
626
+ }
627
+ if (held > 0) {
628
+ totalOffCents += held;
629
+ params.set('metadata[giftCardCode]', giftCardCode);
630
+ params.set('metadata[giftCardCents]', String(held));
631
+ params.set('metadata[giftCardHoldKey]', holdKey);
632
+ // Every refusal BELOW this point releases the claim so the shopper can
633
+ // retry; the hold has to come back with it or the retry finds the card
634
+ // empty and the shopper is locked out of their own balance until the
635
+ // TTL lapses. The expiry is the backstop for a crash, not the
636
+ // mechanism for a refusal the code can see happening.
637
+ releaseGiftCardHold = async ()=>{
638
+ await cardRef.set({
639
+ holds: {
640
+ [holdKey]: firebaseAdmin.firestore.FieldValue.delete()
641
+ }
642
+ }, {
643
+ merge: true
644
+ }).catch((error)=>{
645
+ // Best effort: the TTL still releases it. Never let tidying up a
646
+ // hold turn a clean 409 into a 500.
647
+ console.error('Gift card hold release failed', giftCardCode, error);
648
+ });
649
+ };
650
+ }
651
+ }
652
+ if (totalOffCents > 0) {
653
+ const stripeCoupon = await fetch('https://api.stripe.com/v1/coupons', {
654
+ method: 'POST',
655
+ headers: _extends({
656
+ Authorization: `Bearer ${process.env.STRIPE_SECRET_KEY}`,
657
+ 'Content-Type': 'application/x-www-form-urlencoded'
658
+ }, stripeKeyHeader('coupon')),
659
+ body: new URLSearchParams({
660
+ amount_off: String(Math.min(totalOffCents, itemsCents)),
661
+ currency: 'usd',
662
+ duration: 'once'
663
+ }).toString()
664
+ }).then((response)=>response.json());
665
+ if (stripeCoupon == null ? void 0 : stripeCoupon.id) {
666
+ params.set('discounts[0][coupon]', stripeCoupon.id);
667
+ // Scaled ONCE, by the reduction that actually reached the session.
668
+ feeCents = Math.round(feeCents * Math.max(0, itemsCents - totalOffCents) / Math.max(1, itemsCents));
669
+ chargedItemsCents = Math.max(0, itemsCents - totalOffCents);
670
+ }
671
+ }
672
+ // THE FLOOR (AGL-2232). A rate above zero on goods the shopper actually
673
+ // pays for is a fee, however small the arithmetic makes it — the same rule
674
+ // the other three doors already hold. Gated on `chargedItemsCents` so a
675
+ // basket a gift card covered entirely still takes nothing: the fee is a cut
676
+ // of the goods, and there are no goods left to cut.
677
+ if (feeApplies && chargedItemsCents > 0 && feeCents < 1) feeCents = 1;
678
+ // STRIPE'S CARD COST, PASSED THROUGH AT COST (AGL-2152). The loop above
679
+ // accumulates the platform's advertised TAKE per line and nothing else; on
680
+ // a destination charge Stripe debits 2.9% + 30¢ of the whole card total
681
+ // from the PLATFORM's balance, so a cart on any tier carrying a 0% rate
682
+ // cost Aglyn money on every single order — and Starter's 2% is below 2.9%,
683
+ // so it lost money too. Added ONCE per charge, not per line: the 30¢ is a
684
+ // per-charge cost.
685
+ //
686
+ // The base is everything the card will be run for that can be seen from
687
+ // here: the goods the shopper actually pays for, the DEAREST shipping
688
+ // option on the session (the shopper picks after this and
689
+ // `application_fee_amount` is fixed at creation), and the manual origin tax
690
+ // rate the lines carry. A store on Stripe Tax is the one residual — its tax
691
+ // is computed inside Stripe after the session is made.
692
+ if (chargedItemsCents > 0) {
693
+ var _storeSettings_get1, _cartTaxSettings_origin;
694
+ const shippingCeilingCents = shippingOptions.reduce((most, option)=>{
695
+ var _option_amountCents;
696
+ return Math.max(most, Math.max(0, Number((_option_amountCents = option.amountCents) != null ? _option_amountCents : 0)));
697
+ }, 0);
698
+ const cartTaxSettings = (_storeSettings_get1 = storeSettings.get('tax')) != null ? _storeSettings_get1 : {};
699
+ const manualRate = cartTaxSettings.mode === 'manual' && !cartTaxSettings.pricesIncludeTax ? CommerceModel.resolveTaxRate(cartTaxSettings, (_cartTaxSettings_origin = cartTaxSettings.origin) != null ? _cartTaxSettings_origin : {}) : null;
700
+ const manualTaxPct = manualRate && manualRate.pct > 0 ? manualRate.pct : 0;
701
+ const chargeCents = chargedItemsCents + Math.round(chargedItemsCents * manualTaxPct / 100) + shippingCeilingCents;
702
+ feeCents = Math.min(chargeCents, feeCents + Aglyn.storefrontProcessingCostCents(chargeCents));
703
+ }
704
+ // THE CONNECT SPLIT IS EMITTED BELOW, NOT HERE (AGL-1956). It used to be
705
+ // written at this point, which is ABOVE the tax decision — and the shape
706
+ // of the split now depends on that decision: a Stripe Tax sale must send a
707
+ // fixed `transfer_data[amount]` instead of `application_fee_amount`,
708
+ // because the fee form transfers `amount_total − fee` and `amount_total`
709
+ // carries tax that AGLYN owes. `feeCents` is final as of this line; only
710
+ // its emission moved. See `commerce-connect-transfer.ts`.
711
+ //
712
+ // Address collection feeds order shipping + destination tax later, and is
713
+ // the enforcing half of the plan above (AGL-1721) — emitted from the
714
+ // plan's own country list, never a default, so it cannot widen past the
715
+ // destinations the declared rates were resolved for. The emission is
716
+ // shared with buy-now (AGL-1720).
717
+ CommerceModel.appendShippingAddressCollectionParams(params, shippingPlan.countries);
718
+ CommerceModel.appendCheckoutShippingParams(params, shippingOptions);
719
+ // Taxes (AGL-1953). This block used to be the `stripe` line alone, and a
720
+ // `manual`-mode store — the mode the AGL-285 zone editor leaves a merchant
721
+ // in by default — collected tax on a buy-now sale and NOTHING on a cart
722
+ // sale of the same goods. Same store, same shopper, same items, two totals
723
+ // depending on which button was pressed, with the merchant left owing the
724
+ // destination tax they never charged.
725
+ //
726
+ // The manual tax rides a real Stripe Tax Rate on each taxable line rather
727
+ // than buy-now's `line_items[1]` product line (AGL-1711). That is not a
728
+ // gratuitous difference: THIS path applies its discounts, coupons and gift
729
+ // cards as session-level Stripe coupons, which spread across every line,
730
+ // so a fake tax line would be discounted along with the goods. Stripe
731
+ // applies a tax rate AFTER the discount — measured, see
732
+ // `manual-tax-rate.ts` — so the arithmetic stays right and
733
+ // `total_details.amount_tax` becomes real, which is what lets the webhook
734
+ // record the tax with no metadata snapshot to keep in step.
735
+ //
736
+ // `automatic_tax.enabled` stays false on a manual sale, so
737
+ // `storefront-tax.ts` still classifies it `manual` and it is never summed
738
+ // into the Aglyn-liable figure (AGL-1904).
739
+ const taxSettings = (_storeSettings_get = storeSettings.get('tax')) != null ? _storeSettings_get : {};
740
+ // AGL-1999: an unset tax mode is NOT a decision, and it must not sell.
741
+ // `mode` was `undefined` for every store whose owner never opened the
742
+ // Taxes card — the default state of every new storefront — and each
743
+ // branch below tested for a specific string, so no branch was taken, no
744
+ // tax was computed, and the shopper was charged an untaxed total. See
745
+ // `commerce-tax-decision.ts` for why refusing beats defaulting to either
746
+ // mode. `mode: 'none'` is the explicit opt-out and passes straight
747
+ // through.
748
+ const taxDecision = CommerceModel.storefrontTaxDecision({
749
+ settings: taxSettings
750
+ });
751
+ if (taxDecision.kind === 'undecided') {
752
+ // BELOW the claim on this path, so release it: the merchant fixes the
753
+ // setting and the shopper retries under the same key (AGL-1697).
754
+ await releaseGiftCardHold();
755
+ await releasePromotionHolds();
756
+ await releaseStock();
757
+ await claim.release();
758
+ return res.status(409).json({
759
+ error: CommerceModel.STOREFRONT_TAX_UNDECIDED_MESSAGE
760
+ });
761
+ }
762
+ const taxMisconfigured = CommerceModel.storefrontTaxMisconfiguration(taxSettings);
763
+ if (taxMisconfigured) {
764
+ // AGL-2145: the merchant DECIDED to collect and the sale would have
765
+ // charged zero anyway — an unfinished settings card, not a
766
+ // jurisdiction miss. Refused in the same words and the same place as
767
+ // the undecided case above.
768
+ // Below the claim here too, so release it for the same reason.
769
+ await releaseGiftCardHold();
770
+ await releasePromotionHolds();
771
+ await releaseStock();
772
+ await claim.release();
773
+ return res.status(409).json({
774
+ error: taxMisconfigured
775
+ });
776
+ }
777
+ if (taxDecision.kind === 'stripe-automatic') {
778
+ params.set('automatic_tax[enabled]', 'true');
779
+ } else if (taxSettings.mode === 'manual' && !taxSettings.pricesIncludeTax) {
780
+ var _taxSettings_origin;
781
+ // Origin-based, exactly as buy-now resolves it: the cart collects the
782
+ // shopper's address inside Stripe Checkout, so there is no destination
783
+ // to resolve against before the session exists. Destination-based manual
784
+ // tax is AGL-296's checkout, and taxing by origin here is what makes the
785
+ // two paths agree — which is the whole point of this issue.
786
+ //
787
+ // `pricesIncludeTax` stores charge no separate tax on ANY path (buy-now
788
+ // skips it the same way); the tax is already inside the displayed price.
789
+ const rate = CommerceModel.resolveTaxRate(taxSettings, (_taxSettings_origin = taxSettings.origin) != null ? _taxSettings_origin : {});
790
+ // Tax-exempt products are excluded per LINE, which a single session-wide
791
+ // figure could not express — a cart mixing a taxable chair with an exempt
792
+ // download must tax only the chair.
793
+ const taxableIndexes = cart.lines.map((line, index)=>({
794
+ line,
795
+ index
796
+ })).filter(({ line })=>{
797
+ var _productsById_get;
798
+ return !((_productsById_get = productsById.get(line.productId)) == null ? void 0 : _productsById_get.taxExempt);
799
+ }).map(({ index })=>index);
800
+ if (rate && rate.pct > 0 && taxableIndexes.length > 0) {
801
+ const taxRateId = await resolveManualTaxRateId({
802
+ hostRef,
803
+ taxPct: rate.pct,
804
+ taxLabel: rate.label || `Tax (${rate.pct}%)`,
805
+ headers: stripeKeyHeader('tax-rate')
806
+ });
807
+ if (!taxRateId) {
808
+ // A VISIBLE refusal, never a fallback to an untaxed session — that
809
+ // silent under-collection is this issue. Nothing was sold, so the
810
+ // key goes back and the same button works once Stripe does
811
+ // (AGL-1697); the retry re-derives the same digest, so Stripe
812
+ // replays any rate the first run did mint.
813
+ await releaseGiftCardHold();
814
+ await releasePromotionHolds();
815
+ await releaseStock();
816
+ await claim.release();
817
+ return res.status(502).json({
818
+ error: 'Checkout failed'
819
+ });
820
+ }
821
+ for (const index of taxableIndexes){
822
+ params.set(`line_items[${index}][tax_rates][0]`, taxRateId);
823
+ }
824
+ }
825
+ }
826
+ const referer = String((_req_headers_referer = req.headers.referer) != null ? _req_headers_referer : '');
827
+ const origin = `https://${req.headers.host}`;
828
+ const backUrl = referer.startsWith('http') ? referer : origin;
829
+ const separator = backUrl.includes('?') ? '&' : '?';
830
+ // `{CHECKOUT_SESSION_ID}` is substituted by Stripe on redirect (AGL-1641).
831
+ // Without it the return URL said only that SOMETHING succeeded, so the
832
+ // storefront could not name the order it had just completed — which is why
833
+ // `purchase` could not be reported at all. It is also the order doc id and
834
+ // becomes the GA `transaction_id`, so GA4's own de-duplication makes a
835
+ // refresh of this page harmless.
836
+ params.set('success_url', `${backUrl}${separator}order=success&session_id={CHECKOUT_SESSION_ID}`);
837
+ params.set('cancel_url', `${backUrl}${separator}order=canceled`);
838
+ if (email) params.set('customer_email', email);
839
+ // THE CONNECT SPLIT (AGL-1956), emitted here because it needs the tax
840
+ // decision taken above. On a Stripe Tax cart the tax is computed against
841
+ // AGLYN's registrations and is Aglyn's to remit (AGL-1904), so the
842
+ // merchant is paid a FIXED `transfer_data[amount]` and no
843
+ // `application_fee_amount` is sent at all — the fee form would have Stripe
844
+ // transfer `amount_total − fee`, handing the state's money to the
845
+ // merchant. A manual-rate or untaxed cart keeps the fee form byte for
846
+ // byte. See `commerce-connect-transfer.ts` for why only one of the two
847
+ // knobs may ever be present.
848
+ const cartTaxOwner = taxDecision.kind === 'stripe-automatic' ? 'platform' : 'merchant';
849
+ const cartShippingFloorCents = CommerceModel.shippingFloorCents(shippingOptions);
850
+ Object.entries(CommerceModel.destinationChargeParams({
851
+ accountId: String(accountId),
852
+ feeCents,
853
+ taxOwner: cartTaxOwner,
854
+ merchantGoodsCents: chargedItemsCents,
855
+ shippingFloorCents: cartShippingFloorCents
856
+ })).forEach(([key, value])=>params.set(key, value));
857
+ params.set('metadata[type]', 'commerce-cart');
858
+ params.set('metadata[hostId]', hostId);
859
+ params.set('metadata[cartId]', cartId);
860
+ params.set('metadata[feeCents]', String(Math.max(0, feeCents)));
861
+ // Only on the shape where `transfer.amount` is no longer `charge.amount`,
862
+ // so the merchant's actual payout stops being derivable from the charge
863
+ // (AGL-1956). The shipping baseline is the other half: the difference
864
+ // between it and the option the shopper chose is owed back to the merchant.
865
+ if (cartTaxOwner === 'platform') {
866
+ params.set('metadata[transferCents]', String(CommerceModel.platformLiableTransferCents({
867
+ feeCents,
868
+ merchantGoodsCents: chargedItemsCents,
869
+ shippingFloorCents: cartShippingFloorCents
870
+ })));
871
+ params.set('metadata[transferShippingCents]', String(cartShippingFloorCents));
872
+ }
873
+ // Set only when units were actually reserved (AGL-2356). Its ABSENCE is
874
+ // what tells the webhook there is nothing to release — right for a cart of
875
+ // untracked or backorder lines, and right for a session minted before this
876
+ // deploy, neither of which reserved anything.
877
+ if (heldStockKey) params.set('metadata[stockHoldKey]', heldStockKey);
878
+ // A HALF-HOUR SESSION, AND IT IS THE HOLD'S WHOLE SAFETY ARGUMENT
879
+ // (AGL-2356). Stripe's default is 24 hours, and a hold must outlive any
880
+ // session that can still be paid — so the default would mean one abandoned
881
+ // cart standing on a merchant's last unit for a day, which is exactly the
882
+ // unbounded, invisible UNDER-sell this issue was held back for. Shortening
883
+ // the SESSION is what bounds it; see `CHECKOUT_SESSION_TTL_MS` for why the
884
+ // figure is 31 minutes and not Stripe's advertised 30-minute floor.
885
+ params.set('expires_at', String(Math.floor((Date.now() + CommerceModel.CHECKOUT_SESSION_TTL_MS) / 1000)));
886
+ // The Payment Element (AGL-1944), LAST and touching nothing above it. Every
887
+ // figure the shopper is charged — line prices, the discount coupon, the tax
888
+ // construction, the shipping rates, the fee — was decided before this line
889
+ // and is identical on both checkout paths. This swaps the redirect's URL
890
+ // pair for `ui_mode` + `return_url` and stops. Off unless the flag is on for
891
+ // this org AND a publishable key exists; see `resolveNativeCheckoutMode`.
892
+ const nativeMode = await resolveNativeCheckoutMode(String((_ref2 = ownerOrg == null ? void 0 : (_ownerOrg_org2 = ownerOrg.org) == null ? void 0 : _ownerOrg_org2.id) != null ? _ref2 : ''));
893
+ if (nativeMode.native) {
894
+ applyNativeCheckoutParams(params, `${backUrl}${separator}order=success&session_id={CHECKOUT_SESSION_ID}`);
895
+ }
896
+ const response = await fetch('https://api.stripe.com/v1/checkout/sessions', {
897
+ method: 'POST',
898
+ headers: _extends({
899
+ Authorization: `Bearer ${process.env.STRIPE_SECRET_KEY}`,
900
+ 'Content-Type': 'application/x-www-form-urlencoded'
901
+ }, stripeKeyHeader('session'), nativeCheckoutStripeHeaders(nativeMode)),
902
+ body: params.toString()
903
+ });
904
+ const session = await response.json();
905
+ // `!session.url` was the liveness check and a `ui_mode` session has no url,
906
+ // so it moves with the mode (AGL-1944) — otherwise every successful native
907
+ // session reads as a Stripe failure: a 502 at the shopper, a released claim,
908
+ // and a real Checkout Session left open on the merchant's account.
909
+ const payload = response.ok ? readCheckoutSessionPayload(session, nativeMode) : null;
910
+ if (!payload) {
911
+ console.error('Stripe cart checkout error', session.error);
912
+ // The checkout did not happen — hand the key back so one flaky moment
913
+ // does not lock this cart out. The retry re-derives the same derived
914
+ // keys, so Stripe replays whatever objects the first run did create.
915
+ await releaseGiftCardHold();
916
+ await releasePromotionHolds();
917
+ await releaseStock();
918
+ await claim.release();
919
+ return res.status(502).json({
920
+ error: 'Checkout failed'
921
+ });
922
+ }
923
+ // THE SESSION NOW EXISTS, SO THE SLOTS BELONG TO IT (AGL-2453). Past this
924
+ // line a hold must survive: the shopper can still pay, and the webhook
925
+ // settles against the reservation. A catch that released one here would
926
+ // leave a paid order whose redemption is never counted — the same
927
+ // over-redemption this fix closes, arriving through the release path.
928
+ heldPromotions.length = 0;
929
+ // And so do the units (AGL-2356), for the stronger version of the same
930
+ // reason: releasing here would hand the last unit to the next shopper while
931
+ // this one still holds a payable session, which is the oversell itself.
932
+ stockHold = null;
933
+ // Recoverable checkout (AGL-296): email captured before the redirect
934
+ // makes abandonment actionable (AGL-323 sends the recovery emails). Written
935
+ // for the native path too, and it matters MORE there: a shopper who
936
+ // abandons an in-page form never leaves the store, so this row is the only
937
+ // trace that a cart reached checkout at all.
938
+ await hostRef.collection('checkouts').doc(String(session.id)).set(_extends({
939
+ cartId
940
+ }, email ? {
941
+ email
942
+ } : {}, marketingOptIn ? {
943
+ marketingOptIn: true
944
+ } : {}, {
945
+ itemsCents,
946
+ resumeUrl: backUrl,
947
+ status: 'open',
948
+ createdAtMs: Date.now()
949
+ })).catch(()=>undefined);
950
+ await claim.record(200, payload);
951
+ return res.status(200).json(payload);
952
+ } catch (error) {
953
+ if (error == null ? void 0 : error.visible) {
954
+ // Line-availability refusals throw from the pricing loop, which sits
955
+ // ABOVE the claim — so there is nothing to release and the key
956
+ // survives, as a deterministic refusal should (AGL-1697).
957
+ await releasePromotionHolds();
958
+ await releaseStock();
959
+ await (claim == null ? void 0 : claim.release());
960
+ return res.status(409).json({
961
+ error: error.visible
962
+ });
963
+ }
964
+ console.error(error);
965
+ // Release on the way out so a transient failure does not strand the key
966
+ // (AGL-1691's rule) — nor a merchant's promotion slot (AGL-2453).
967
+ await releasePromotionHolds();
968
+ await releaseStock();
969
+ await (claim == null ? void 0 : claim.release());
970
+ return res.status(500).json({
971
+ error: 'Checkout failed'
972
+ });
973
+ }
974
+ };
975
+
976
+ //# sourceMappingURL=cart-checkout.js.map