@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,915 @@
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 { readActiveMemberSession } from "./membership.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
+ * Commerce Starter checkout (AGL-90): a site visitor buys a product. The
29
+ * price ALWAYS comes from the host's product doc (never the request), the
30
+ * money goes to the host owner's Connect account (AGL-46 onboarding) as a
31
+ * DESTINATION charge, and the webhook records the order under the host.
32
+ * 501 without Stripe env.
33
+ *
34
+ * Two claims in this comment were stale and are corrected here (AGL-2295),
35
+ * both in the direction that matters:
36
+ *
37
+ * - The fee is NOT "2%". It is `resolveTransactionFeePct(org, productType)`
38
+ * — a per-plan, per-product-type ladder with per-org staff overrides,
39
+ * resolved per request. 2% is one cell of sixteen (Starter, physical).
40
+ * - Selling is gated on `commerce`, not on `marketplaceSelling` (AGL-470).
41
+ * They are different entitlements on different plans: `marketplaceSelling`
42
+ * gates the marketplace, and reading this comment as current would gate a
43
+ * storefront on the wrong one.
44
+ */ export const checkoutHandler = async (req, res)=>{
45
+ var _req_body, _body_hostId, _body_productId, _body_variantId, _body_quantity, _body_couponCode, _body_billing, _ref, _req_headers_idempotencykey;
46
+ if (req.method !== 'POST') {
47
+ return res.status(405).json({
48
+ error: 'Method not allowed'
49
+ });
50
+ }
51
+ if (!process.env.STRIPE_SECRET_KEY) {
52
+ return res.status(501).json({
53
+ error: 'Purchases are not configured on this site.'
54
+ });
55
+ }
56
+ const body = typeof req.body === 'string' ? JSON.parse(req.body) : (_req_body = req.body) != null ? _req_body : {};
57
+ const hostId = String((_body_hostId = body.hostId) != null ? _body_hostId : '');
58
+ const productId = String((_body_productId = body.productId) != null ? _body_productId : '');
59
+ const variantId = String((_body_variantId = body.variantId) != null ? _body_variantId : '');
60
+ const quantity = Math.max(1, Math.min(99, Math.round(Number((_body_quantity = body.quantity) != null ? _body_quantity : 1))));
61
+ const couponCode = String((_body_couponCode = body.couponCode) != null ? _body_couponCode : '').trim().toUpperCase().slice(0, 40);
62
+ // Buyer-chosen billing (AGL-545): a request, never an instruction —
63
+ // the session mode re-resolves from the product doc below.
64
+ const billingRaw = String((_body_billing = body.billing) != null ? _body_billing : '');
65
+ const billing = billingRaw === 'once' || billingRaw === 'subscribe' ? billingRaw : undefined;
66
+ // One buy-now attempt, minted by the product page (AGL-1697). Node
67
+ // lowercases incoming headers, but read both spellings — the plugin API
68
+ // request type makes no promise about casing.
69
+ 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);
70
+ if (!hostId || !productId) {
71
+ return res.status(400).json({
72
+ error: 'Missing hostId or productId'
73
+ });
74
+ }
75
+ let claim = null;
76
+ /**
77
+ * The coupon redemption slot this attempt reserved (AGL-2453), hoisted so
78
+ * every refusal below the claim — and the catch — can hand it back. A throw
79
+ * after the hold is placed is exactly the case where a slot would otherwise
80
+ * sit invisibly against the merchant's cap until the TTL lapsed.
81
+ */ let couponSlot = null;
82
+ let couponHoldKey = '';
83
+ const releaseCouponSlot = async ()=>{
84
+ const slot = couponSlot;
85
+ couponSlot = null;
86
+ couponHoldKey = '';
87
+ if (slot) await slot.release();
88
+ };
89
+ /**
90
+ * The units this attempt reserved (AGL-2356), hoisted for the same reason the
91
+ * coupon slot is: every refusal below the claim — and the catch — has to hand
92
+ * them back, or an abandoned request stands on a merchant's last unit until
93
+ * the TTL lapses.
94
+ */ let stockHold = null;
95
+ let heldStockKey = '';
96
+ const releaseStock = async ()=>{
97
+ const held = stockHold;
98
+ stockHold = null;
99
+ heldStockKey = '';
100
+ if (held) await held.release();
101
+ };
102
+ try {
103
+ var _variant_priceUsd, _req_headers_referer, _storeSettings_get, _ref1, _lifted_type, _variant_weightGrams, _lifted_type1, _lifted_type2, _product_name, _ref2;
104
+ var _ownerOrg_org, _ownerOrg_org1, _ownerOrg_org2;
105
+ const firestore = firebaseAdmin.app().firestore();
106
+ const hostRef = firestore.collection('hosts').doc(hostId);
107
+ const [hostSnapshot, productSnapshot] = await Promise.all([
108
+ hostRef.get(),
109
+ hostRef.collection('products').doc(productId).get()
110
+ ]);
111
+ if (!hostSnapshot.exists) {
112
+ return res.status(404).json({
113
+ error: 'Unknown site'
114
+ });
115
+ }
116
+ const product = productSnapshot.data();
117
+ if (!product || product.deletedAt) {
118
+ return res.status(404).json({
119
+ error: 'Unknown product'
120
+ });
121
+ }
122
+ // Variant pricing (AGL-292): the buyer's selection maps to a variant;
123
+ // absent variantId keeps the legacy default-variant behavior.
124
+ const lifted = CommerceModel.liftLegacyProduct(product);
125
+ const variant = variantId ? lifted.variants.find((item)=>item.id === variantId) : lifted.variants[0];
126
+ if (!variant) return res.status(404).json({
127
+ error: 'Unknown variant'
128
+ });
129
+ const priceUsd = Number((_variant_priceUsd = variant.priceUsd) != null ? _variant_priceUsd : 0);
130
+ if (!(priceUsd > 0) || priceUsd > CommerceModel.COMMERCE_MAX_PRICE_USD) {
131
+ return res.status(400).json({
132
+ error: 'Product is not purchasable'
133
+ });
134
+ }
135
+ // Inventory (AGL-281): variant-aware, honoring the product's oversell
136
+ // policy (backorder products keep selling at zero). Enforced here (the
137
+ // block's display is cosmetic) and decremented by the webhook.
138
+ //
139
+ // DELIBERATELY UNCHANGED for subscription sessions. For a PHYSICAL
140
+ // subscription the gate is honest again since AGL-1750: every paid cycle
141
+ // mints an order and decrements the variant (the webhook's invoice
142
+ // branch), so the number this checks now moves as boxes ship. For a
143
+ // digital or service subscription nothing decrements, and the console
144
+ // withdraws the field there (`stockTrackingApplies`, AGL-1744) rather
145
+ // than skipping this gate — it is the one shared by every purchase path,
146
+ // and skipping it for subscription mode would silently RESUME selling
147
+ // for any merchant who set 0 to stop new subscribers.
148
+ if (!CommerceModel.canPurchase(lifted, variant.id, quantity)) {
149
+ return res.status(409).json({
150
+ error: 'Sold out'
151
+ });
152
+ }
153
+ // Seller resolution rides the owning org (AGL-238): plan gate from
154
+ // the org doc, Stripe account from the owner's marketplace profile.
155
+ const ownerOrg = await getOrgForHost(hostId);
156
+ const ownerId = ownerOrg == null ? void 0 : (_ownerOrg_org = ownerOrg.org) == null ? void 0 : _ownerOrg_org.ownerUid;
157
+ if (!ownerId) {
158
+ return res.status(409).json({
159
+ error: 'This site cannot sell yet'
160
+ });
161
+ }
162
+ const ownerProfile = await firestore.collection('profiles').doc(String(ownerId)).get();
163
+ // Storefront selling is the `commerce` entitlement (Starter+) — not
164
+ // `marketplaceSelling`, which gates the marketplace marketplace (AGL-470).
165
+ if (!Aglyn.checkEntitlement(ownerOrg.org, 'commerce')) {
166
+ return res.status(403).json({
167
+ error: 'Selling is not enabled'
168
+ });
169
+ }
170
+ // Session mode (AGL-545): 'subscribe' only takes effect on
171
+ // subscriptionOptional products, 'once' never applies to
172
+ // subscription-only products, and price/interval always come from
173
+ // the product doc — never the request.
174
+ const isSubscription = CommerceModel.resolveCheckoutBillingMode(lifted, billing) === 'subscription';
175
+ // Tiered product types (AGL-470): recurring subscriptions and gift
176
+ // cards are Business+ entitlements, checked per sale — the product doc
177
+ // alone must not unlock them. Gated whenever the session ends up in
178
+ // subscription mode (a subscriptionOptional one-time sale is a plain
179
+ // order, so it rides the base `commerce` entitlement).
180
+ if (isSubscription && !Aglyn.checkEntitlement(ownerOrg.org, 'storefrontSubscriptions')) {
181
+ return res.status(403).json({
182
+ error: 'Subscription products require a Business plan'
183
+ });
184
+ }
185
+ // ALREADY SUBSCRIBED? (AGL-1849)
186
+ //
187
+ // The idempotency key closes the RETRY shape: one attempt, one key, one
188
+ // subscription. What it cannot close is a deliberate second submit under a
189
+ // fresh key — `product-detail.tsx` mints a new UUID on every variant /
190
+ // quantity / billing change — which is two RECURRING charges on one card
191
+ // for one product, billed forever until someone notices.
192
+ //
193
+ // The issue filed this as impossible: the storefront buy-now path is
194
+ // anonymous, so there is no buyer to ask about. That was half right. The
195
+ // path admits anonymous buyers, but it is not ONLY anonymous — a signed-in
196
+ // site member's `aglyn_member_{hostId}` cookie is already on this request
197
+ // (same-origin `fetch` sends it, and `PluginApiRequest` exposes
198
+ // `cookies`), and the handler simply never read it. So the guard covers
199
+ // the case it can actually identify and, deliberately, no more.
200
+ //
201
+ // WHAT THIS DOES NOT COVER, stated rather than implied: a logged-out buyer
202
+ // who types any email at Stripe. There is no identity for them before the
203
+ // hosted page, and inventing one — forcing sign-in to buy — is a UX change
204
+ // that belongs to a product decision, not to a duplicate guard. The
205
+ // honest scope is "a member cannot silently double-subscribe", not "nobody
206
+ // can".
207
+ //
208
+ // Live-status, NOT existence. A member who cancelled must be able to
209
+ // re-subscribe; refusing on "a subscription row exists" would lock a
210
+ // returning customer out of paying us, which is the AGL-1715 lesson
211
+ // restated on the tenant side. `isTenantSubscriptionLive` is the shared
212
+ // tenant predicate, the same one `gate.ts` grants content access with.
213
+ if (isSubscription) {
214
+ var _session_member_get;
215
+ const session = await readActiveMemberSession(req, hostId);
216
+ const memberEmail = session.status === 'active' ? String((_session_member_get = session.member.get('email')) != null ? _session_member_get : '') : '';
217
+ if (memberEmail) {
218
+ // Scoped by email because that is the join `billing-webhook.ts` writes
219
+ // — `customerEmail` comes off Stripe's `customer_details` — and it is
220
+ // the same key `gate.ts` reads. The 10-doc cap matches gate.ts too: a
221
+ // member with more than ten subscriptions to ONE host is not the
222
+ // shape this refuses, and an unbounded scan on a checkout path is a
223
+ // cost with no matching benefit.
224
+ const existing = await hostRef.collection('subscriptions').where('customerEmail', '==', memberEmail).limit(10).get();
225
+ const alreadySubscribed = existing.docs.some((docSnapshot)=>docSnapshot.get('productId') === productId && CommerceModel.isTenantSubscriptionLive(docSnapshot.get('status')));
226
+ if (alreadySubscribed) {
227
+ // Above the claim, so the key is never burned: this is a
228
+ // deterministic refusal the buyer resolves by cancelling first, and
229
+ // a retry must get the same answer rather than a stale replay.
230
+ return res.status(409).json({
231
+ error: 'You already have an active subscription to this product — ' + 'manage it from your account.',
232
+ code: 'already_subscribed'
233
+ });
234
+ }
235
+ }
236
+ }
237
+ if (product.giftCard && !Aglyn.checkEntitlement(ownerOrg.org, 'giftCards')) {
238
+ return res.status(403).json({
239
+ error: 'Gift cards require a Business plan'
240
+ });
241
+ }
242
+ const accountId = ownerProfile.get('stripeAccountId');
243
+ if (!connectLinkageIsReady({
244
+ accountId,
245
+ chargesEnabled: ownerProfile.get('stripeChargesEnabled'),
246
+ accountLivemode: ownerProfile.get('stripeAccountLivemode')
247
+ }, {
248
+ subject: `checkout host ${hostId}`
249
+ })) {
250
+ return res.status(409).json({
251
+ error: 'This site has not enabled payments yet'
252
+ });
253
+ }
254
+ // The list price per unit, before any coupon (AGL-1711). This is what the
255
+ // order's line-item snapshot records, and it is carried in the session
256
+ // metadata so the webhook never has to re-price from the product doc.
257
+ const listUnitAmountCents = Math.round(priceUsd * 100);
258
+ let amountCents = listUnitAmountCents * quantity;
259
+ // THE DISCOUNTS HUB, ON THIS PATH TOO.
260
+ //
261
+ // `hosts/{hostId}/discounts` had exactly one reader — `cart-checkout.ts` —
262
+ // so a code from the Discounts card worked in the cart and was rejected as
263
+ // invalid on Buy now. Same goods, same code, two prices depending on which
264
+ // button the shopper pressed, which is the disagreement AGL-1953 already
265
+ // closed for TAX on these same two paths.
266
+ //
267
+ // Resolved through the SAME `resolveDiscount` the cart uses, so one code
268
+ // cannot mean two amounts. The legacy AGL-96 coupons below stay as the
269
+ // fallback for a code the hub does not know, exactly as the cart orders
270
+ // them.
271
+ //
272
+ // Applied by reducing the AMOUNT rather than by minting a session-level
273
+ // Stripe coupon, which is how this path already applies its legacy coupon.
274
+ // That difference is load-bearing: buy-now carries its manual tax as a
275
+ // real `line_items[1]` product line, and a session coupon spreads across
276
+ // every line — so minting one here would discount the tax line too.
277
+ const buyNowDiscountLines = [
278
+ {
279
+ productId,
280
+ amountCents: listUnitAmountCents * quantity
281
+ }
282
+ ];
283
+ const hubDiscounts = await hostRef.collection('discounts').limit(100).get();
284
+ const resolvedDiscount = CommerceModel.resolveDiscount(hubDiscounts.docs.map((docSnapshot)=>_extends({}, docSnapshot.data(), {
285
+ $id: docSnapshot.id
286
+ })), _extends({}, couponCode ? {
287
+ code: couponCode
288
+ } : {}, {
289
+ subtotalCents: listUnitAmountCents * quantity,
290
+ productIds: [
291
+ productId
292
+ ],
293
+ lines: buyNowDiscountLines
294
+ }));
295
+ if (resolvedDiscount == null ? void 0 : resolvedDiscount.codeProblem) {
296
+ return res.status(400).json({
297
+ error: resolvedDiscount.codeProblem
298
+ });
299
+ }
300
+ let appliedDiscountId = '';
301
+ if (resolvedDiscount && resolvedDiscount.benefit.kind !== 'none') {
302
+ // Stripe's charge minimum is 50c — never discount below it, the same
303
+ // floor the legacy coupon path applies.
304
+ amountCents = Math.max(50, amountCents - resolvedDiscount.discountCents);
305
+ appliedDiscountId = resolvedDiscount.discountId;
306
+ }
307
+ // Coupons (AGL-96): host-defined percent-off codes; invalid codes are
308
+ // a visible 400, never a silent full-price charge. Only consulted when the
309
+ // hub did not already answer for this code.
310
+ let appliedCoupon = '';
311
+ if (couponCode && !resolvedDiscount) {
312
+ var _ref3;
313
+ const couponSnapshot = await hostRef.collection('coupons').doc(couponCode).get();
314
+ const coupon = couponSnapshot.data();
315
+ const percentOff = Number((_ref3 = coupon == null ? void 0 : coupon.percentOff) != null ? _ref3 : 0);
316
+ const expired = (coupon == null ? void 0 : coupon.expiresAtMs) != null && Number(coupon.expiresAtMs) < Date.now();
317
+ // A PRE-FILTER, no longer the enforcement (AGL-2453). It answers early so
318
+ // an obviously spent code 400s before a claim is minted, and it counts
319
+ // live holds so it agrees with the transaction below — the authority is
320
+ // `holdPromotionSlot`, which re-asks this same question inside the write.
321
+ const exhausted = CommerceModel.promotionExhausted(coupon, Date.now());
322
+ if (!coupon || coupon.enabled === false || expired || !(percentOff > 0 && percentOff <= 100)) {
323
+ return res.status(400).json({
324
+ error: 'Invalid or expired coupon'
325
+ });
326
+ }
327
+ // Named separately from the invalid-code refusal (AGL-2453), and in the
328
+ // same words the transaction below uses. "Invalid or expired" is wrong
329
+ // about a code that is perfectly valid and simply spent — and once a live
330
+ // hold can produce this answer, a shopper told their code is invalid
331
+ // would stop trying rather than retry a moment later.
332
+ if (exhausted) {
333
+ return res.status(400).json({
334
+ error: CommerceModel.PROMOTION_EXHAUSTED_MESSAGE
335
+ });
336
+ }
337
+ // Stripe's charge minimum is 50¢ — never discount below it. The
338
+ // percentage comes off the ORDER total, matching `cart-checkout.ts`'s
339
+ // `Math.round(itemsCents * percentOff / 100)`, so the same code takes
340
+ // the same money whichever button the shopper pressed. What the SESSION
341
+ // is built from this figure is where the two used to diverge — see the
342
+ // line-item construction below (AGL-2159).
343
+ amountCents = Math.max(50, Math.round(amountCents * (100 - percentOff) / 100));
344
+ appliedCoupon = couponCode;
345
+ }
346
+ // The platform fee is NOT computed here (AGL-2152): Stripe's processing
347
+ // cost is charged on the whole card total, so the fee cannot be known
348
+ // until the manual tax and the shipping options are resolved below. The
349
+ // per-plan ladder (AGL-284, AGL-278) is read inside the two resolvers
350
+ // called after the shipping plan — `resolveTransactionFeeCents` for a
351
+ // one-time sale and `resolveSubscriptionFeePercent` for a subscription.
352
+ const referer = String((_req_headers_referer = req.headers.referer) != null ? _req_headers_referer : '');
353
+ const origin = `https://${req.headers.host}`;
354
+ const backUrl = referer.startsWith('http') ? referer : origin;
355
+ const separator = backUrl.includes('?') ? '&' : '?';
356
+ // Taxes (AGL-285): Stripe Tax when the host opted in; manual mode
357
+ // taxes by store origin here (destination-based arrives with our own
358
+ // checkout, AGL-296). Exempt products skip both.
359
+ const storeSettings = await hostRef.collection('settings').doc('store').get();
360
+ const taxSettings = (_storeSettings_get = storeSettings.get('tax')) != null ? _storeSettings_get : {};
361
+ // AGL-1999: an unset tax mode is NOT a decision, and it must not sell.
362
+ // `mode` was `undefined` for every store whose owner never opened the
363
+ // Taxes card — the default state of every new storefront — and each
364
+ // branch below tested for a specific string, so no branch was taken, no
365
+ // tax was computed, and the shopper was charged an untaxed total. See
366
+ // `commerce-tax-decision.ts` for why refusing beats defaulting to either
367
+ // mode. `mode: 'none'` is the explicit opt-out and passes straight
368
+ // through.
369
+ const taxDecision = CommerceModel.storefrontTaxDecision({
370
+ settings: taxSettings,
371
+ taxExempt: lifted.taxExempt
372
+ });
373
+ if (taxDecision.kind === 'undecided') {
374
+ // Above the attempt claim, like every other deterministic refusal on
375
+ // this path — there is no claim to release yet.
376
+ return res.status(409).json({
377
+ error: CommerceModel.STOREFRONT_TAX_UNDECIDED_MESSAGE
378
+ });
379
+ }
380
+ const taxMisconfigured = CommerceModel.storefrontTaxMisconfiguration(taxSettings);
381
+ if (taxMisconfigured) {
382
+ // AGL-2145: the merchant DECIDED to collect and the sale would have
383
+ // charged zero anyway — an unfinished settings card, not a
384
+ // jurisdiction miss. Refused in the same words and the same place as
385
+ // the undecided case above.
386
+ return res.status(409).json({
387
+ error: taxMisconfigured
388
+ });
389
+ }
390
+ const useStripeTax = taxDecision.kind === 'stripe-automatic';
391
+ let taxCents = 0;
392
+ let taxLabel = '';
393
+ let taxPct = 0;
394
+ if (taxSettings.mode === 'manual' && !lifted.taxExempt) {
395
+ var _taxSettings_origin;
396
+ const rate = CommerceModel.resolveTaxRate(taxSettings, (_taxSettings_origin = taxSettings.origin) != null ? _taxSettings_origin : {});
397
+ if (rate && !taxSettings.pricesIncludeTax) {
398
+ taxCents = CommerceModel.computeTaxCents(amountCents, rate.pct);
399
+ taxLabel = rate.label || `Tax (${rate.pct}%)`;
400
+ taxPct = rate.pct;
401
+ }
402
+ }
403
+ // Recurring manual tax (AGL-1751). The manual tax cannot ride the
404
+ // `line_items[1]` product line on a SUBSCRIPTION session: a non-recurring
405
+ // price in `mode: subscription` is a one-time item, and Stripe bills
406
+ // one-time items on the FIRST invoice only — so the merchant's tax was
407
+ // collected once and then, on every renewal the buyer had been shown a
408
+ // taxed price for, never again. A Stripe Tax Rate attached to the
409
+ // subscription line recurs: Checkout carries `line_items[][tax_rates]`
410
+ // onto the subscription item, every invoice applies it, and the figure
411
+ // lands where the records already look — the session's
412
+ // `total_details.amount_tax` for the sale (AGL-1732) and the invoice's
413
+ // own tax fields for each renewal (AGL-1743). No webhook change.
414
+ //
415
+ // The rate is created on the PLATFORM account — no Stripe-Account header,
416
+ // matching the session call below — because that is where the
417
+ // subscription lives (destination transfer); a connected-account rate id
418
+ // would not resolve. Tax rates are immutable, so one is minted per
419
+ // (percentage, label) and cached under the host; a merchant editing their
420
+ // zone rate misses the cache and mints a fresh object, while the old one
421
+ // stays behind on the subscriptions that already carry it, exactly as it
422
+ // should.
423
+ //
424
+ // A creation failure is a VISIBLE 502, never a fallback: the one-time
425
+ // line is this very bug, and a session with no tax at all would
426
+ // under-collect every invoice silently.
427
+ // Point of no return (AGL-1697): everything past here creates Stripe
428
+ // objects — possibly a Tax Rate, then the session. Server-side a retry
429
+ // looked free, but for `mode: subscription` two completed sessions are
430
+ // two RECURRING subscriptions for the same member and product. The
431
+ // shipping refusals BELOW this line release the claim: they are
432
+ // deterministic asks the shopper answers and retries under the same key,
433
+ // and the released key re-derives the same digests so Stripe replays the
434
+ // tax rate rather than minting a twin (AGL-1714's rule).
435
+ const claimed = await claimAttempt(firestore, {
436
+ kind: 'commerce-checkout',
437
+ scopeId: hostId,
438
+ orgId: String((_ref1 = ownerOrg == null ? void 0 : (_ownerOrg_org1 = ownerOrg.org) == null ? void 0 : _ownerOrg_org1.id) != null ? _ref1 : ''),
439
+ key: idempotencyKey,
440
+ busyMessage: 'This checkout is already being processed'
441
+ });
442
+ if ('replay' in claimed) {
443
+ return res.status(claimed.replay.status).json(claimed.replay.body);
444
+ }
445
+ claim = claimed.claim;
446
+ // Derived per OBJECT, never the raw digest (AGL-1714): Stripe
447
+ // parameter-compares a repeated key account-wide, so one digest sent to
448
+ // `/v1/tax_rates` and then `checkout/sessions` would error the second.
449
+ const stripeKeyHeader = (object)=>{
450
+ const derived = deriveStripeObjectKey(claimed.claim.stripeKey, object);
451
+ return derived ? {
452
+ 'Idempotency-Key': derived
453
+ } : {};
454
+ };
455
+ // THE UNITS ARE RESERVED HERE (AGL-2356), not merely read above.
456
+ //
457
+ // The `canPurchase` gate at the top of this handler is a plain `.get()`
458
+ // compared against a number, and the webhook decremented minutes later
459
+ // without ever re-asking. Nothing was written, so there was no document to
460
+ // contend on: every shopper who reached this line while the shelf held one
461
+ // unit passed it, and they all paid. This is the write that makes them
462
+ // serialise — see `model/commerce-stock-holds.ts` for why the shelf count
463
+ // itself deliberately does not move.
464
+ //
465
+ // FIRST among the holds, before the coupon slot below: stock is the
466
+ // likeliest refusal on this path, and reserving a promotion slot for a
467
+ // basket the store cannot fill is a slot held against the merchant's cap
468
+ // for nothing.
469
+ {
470
+ const held = await holdStock({
471
+ firestore,
472
+ hostRef,
473
+ holdKey: stockHoldKey(claimed.claim.stripeKey),
474
+ // Empty when there is nothing to reserve ON — an untracked variant or
475
+ // a `backorder` product, whose merchant chose to sell past zero. See
476
+ // the note in `cart-checkout.ts`: the unlimited case stays off the
477
+ // write path entirely, and `holdStock` opens no transaction for it.
478
+ lines: CommerceModel.stockIsReservable(lifted, variant.id, Date.now()) ? [
479
+ {
480
+ productId,
481
+ variantId: variant.id,
482
+ quantity
483
+ }
484
+ ] : [],
485
+ label: `buy-now ${hostId}/${productId}`
486
+ });
487
+ if (!held.ok) {
488
+ // Nothing has been minted yet, so the key goes back and the same
489
+ // button works when the units return (AGL-1697). `sold-out` is named
490
+ // separately from an empty shelf: the merchant HAS stock and someone
491
+ // else is in checkout with it, and a shopper told "sold out" leaves
492
+ // while a shopper told to wait a few minutes comes back.
493
+ await releaseStock();
494
+ await claim.release();
495
+ return res.status(409).json({
496
+ error: held.reason === 'sold-out' ? CommerceModel.STOCK_HELD_MESSAGE : 'Sold out'
497
+ });
498
+ }
499
+ if (held.holdKey) {
500
+ stockHold = held;
501
+ heldStockKey = held.holdKey;
502
+ }
503
+ }
504
+ // THE COUPON SLOT IS RESERVED HERE (AGL-2453), not merely read above.
505
+ //
506
+ // The check at the top of this handler was a comparison against a plain
507
+ // `.get()`, and the webhook incremented `redemptions` minutes later without
508
+ // re-asking — so a cap of one admitted every shopper who reached checkout
509
+ // before the first of them paid. Placed BELOW the claim because the hold is
510
+ // keyed by the attempt: a retry of one attempt must re-claim its own slot
511
+ // rather than be refused by it.
512
+ //
513
+ // Buy-now runs no automatic-discount path — `resolveDiscount` is the cart's
514
+ // — so the typed coupon is the only counter this handler can burn.
515
+ if (appliedCoupon || appliedDiscountId) {
516
+ const slot = await holdPromotionSlot({
517
+ firestore,
518
+ // The hub discount and the legacy coupon are two counters and only one
519
+ // of them can be in play — the hub answers first and the legacy block
520
+ // is skipped when it did.
521
+ ref: appliedDiscountId ? hostRef.collection('discounts').doc(appliedDiscountId) : hostRef.collection('coupons').doc(appliedCoupon),
522
+ holdKey: promotionHoldKey(claimed.claim.stripeKey),
523
+ label: appliedDiscountId ? `discount ${appliedDiscountId}` : `coupon ${appliedCoupon}`
524
+ });
525
+ if (!slot.ok) {
526
+ // Nothing has been minted yet, so this is retryable and the key goes
527
+ // back (AGL-1697). Exhaustion is named; anything else reads as the
528
+ // same invalid-code refusal the pre-filter above would have given.
529
+ await releaseCouponSlot();
530
+ await releaseStock();
531
+ await claim.release();
532
+ return res.status(400).json({
533
+ error: slot.reason === 'exhausted' ? CommerceModel.PROMOTION_EXHAUSTED_MESSAGE : 'Invalid or expired coupon'
534
+ });
535
+ }
536
+ if (slot.holdKey) {
537
+ couponSlot = slot;
538
+ couponHoldKey = slot.holdKey;
539
+ }
540
+ }
541
+ // Minting and caching now live in `manual-tax-rate.ts` (AGL-1953), which
542
+ // the cart path needs too — two copies of a money-shaped cache is how two
543
+ // paths quietly diverge. Behaviour here is unchanged.
544
+ let recurringTaxRateId = '';
545
+ if (isSubscription && taxCents > 0) {
546
+ var _ref4;
547
+ recurringTaxRateId = (_ref4 = await resolveManualTaxRateId({
548
+ hostRef,
549
+ taxPct,
550
+ taxLabel,
551
+ headers: stripeKeyHeader('tax-rate')
552
+ })) != null ? _ref4 : '';
553
+ if (!recurringTaxRateId) {
554
+ // Nothing was sold — hand the key back so the same button works
555
+ // once Stripe does (AGL-1697).
556
+ await releaseCouponSlot();
557
+ await releaseStock();
558
+ await claim.release();
559
+ return res.status(502).json({
560
+ error: 'Checkout failed'
561
+ });
562
+ }
563
+ }
564
+ // Shipping (AGL-1720), from the same settings document tax was just read
565
+ // from. Buy-now declared neither `shipping_address_collection` nor
566
+ // `shipping_options`, so a merchant who configured zones and rates in the
567
+ // console collected them on cart orders (AGL-1707) and nothing at all on
568
+ // buy-now — same merchant, same product, two totals depending on which
569
+ // button the shopper pressed.
570
+ //
571
+ // CONDITIONAL, unlike the cart's, on two things the cart cannot know:
572
+ //
573
+ // - PHYSICAL PRODUCTS ONLY. This is the PDP path for one product, and it
574
+ // sells digital downloads and services too. Asking a shopper buying a
575
+ // PDF for a shipping address, then charging them to ship it, would be
576
+ // a worse bug than the one being fixed. A missing `type` reads as
577
+ // physical, exactly as the fee ladder above already reads it —
578
+ // `liftLegacyProduct` only defaults the field on docs it synthesizes
579
+ // variants for, so a part-migrated doc can reach here without one, and
580
+ // the two reads must not disagree about what that doc is.
581
+ // - ONE-TIME SALES ONLY. Originally because the webhook's
582
+ // `commerce-subscription` branch never touched `total_details`, so
583
+ // shipping charged there would have been real money recorded NOWHERE.
584
+ // Both halves of that are now closed: AGL-1732 decomposed the initial
585
+ // charge onto the subscription document, `amount_shipping` included,
586
+ // and AGL-1743 records every renewal invoice — `shipping_cost` read
587
+ // the same way — so a recurring shipping charge would now land.
588
+ //
589
+ // What remains is the PRODUCT question, and only it: a rate editor
590
+ // written for one-time orders never asks the merchant whether a rate
591
+ // should be re-charged every cycle, and Stripe bills a subscription's
592
+ // one-time line items on the FIRST invoice only, so a shipping rate
593
+ // added as a session line item would be charged once and then silently
594
+ // stop. Answering that is AGL-1720's revisit, not wiring.
595
+ //
596
+ // Weight is per unit TIMES the quantity bought. `weightGrams` alone would
597
+ // quote the lightest tier on a multi-unit order — the same under-collection
598
+ // this issue is about, and adjacent to the hardcoded `quantity: 1` that
599
+ // AGL-1711 just removed from this function.
600
+ //
601
+ // The subtotal fed to the resolver is the PRE-discount list total, matching
602
+ // the cart's, so "free over $50" means what the shopper saw it mean.
603
+ //
604
+ // WHICH DESTINATIONS the session may serve is decided with the rates and
605
+ // not after them (AGL-1721): a session that declared every zone's rates
606
+ // while accepting an address in any country let a shopper pick a domestic
607
+ // rate for an international parcel, and Stripe re-checks nothing — a
608
+ // `shipping_rate_data` carries no country. `planCheckoutShipping` pairs
609
+ // the two lists, and refuses rather than guessing.
610
+ const shippingSettings = storeSettings.get('shipping');
611
+ const shipsPhysically = ((_lifted_type = lifted.type) != null ? _lifted_type : 'physical') === 'physical' && !isSubscription;
612
+ const shippingPlan = shipsPhysically ? CommerceModel.planCheckoutShipping(shippingSettings, {
613
+ subtotalCents: listUnitAmountCents * quantity,
614
+ totalGrams: Math.max(0, Number((_variant_weightGrams = variant.weightGrams) != null ? _variant_weightGrams : 0)) * quantity
615
+ }, body.shippingCountry) : {
616
+ countries: CommerceModel.CHECKOUT_SHIPPING_COUNTRIES,
617
+ options: []
618
+ };
619
+ if (shippingPlan.refusal === 'destination-required') {
620
+ // A deterministic ask the shopper answers and retries under the same
621
+ // key — released, not burned (AGL-1697). The tax rate possibly minted
622
+ // above replays from its cache (or its derived key) on the retry.
623
+ await releaseCouponSlot();
624
+ await releaseStock();
625
+ await claim.release();
626
+ return res.status(400).json({
627
+ error: 'Choose where you’re shipping to.',
628
+ needsShippingCountry: true,
629
+ shippingCountries: [
630
+ ...CommerceModel.CHECKOUT_SHIPPING_COUNTRIES
631
+ ]
632
+ });
633
+ }
634
+ if (shippingPlan.refusal === 'destination-unserved') {
635
+ var _CommerceModel_CHECKOUT_SHIPPING_COUNTRY_NAMES_declared;
636
+ const declared = CommerceModel.normalizeCheckoutShippingCountry(body.shippingCountry);
637
+ await releaseCouponSlot();
638
+ await releaseStock();
639
+ await claim.release();
640
+ return res.status(409).json({
641
+ 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'}.`,
642
+ needsShippingCountry: true,
643
+ shippingCountries: [
644
+ ...CommerceModel.CHECKOUT_SHIPPING_COUNTRIES
645
+ ]
646
+ });
647
+ }
648
+ // The merchant priced shipping and no rate of theirs reaches this cart
649
+ // (AGL-2232). Released like every other deterministic refusal on this
650
+ // path — the shopper can change the basket and retry under the same key.
651
+ if (shippingPlan.refusal === 'cart-unpriceable') {
652
+ await releaseCouponSlot();
653
+ await releaseStock();
654
+ await claim.release();
655
+ return res.status(409).json({
656
+ error: CommerceModel.CART_UNPRICEABLE_SHIPPING_MESSAGE
657
+ });
658
+ }
659
+ // THE PLATFORM FEE (AGL-2152). Formerly `feePct` of the goods and nothing
660
+ // else, which on a DESTINATION charge meant Aglyn paid Stripe 2.9% + 30¢
661
+ // out of its own balance and — on every tier carrying
662
+ // `transactionFeePhysicalPct: 0` — collected nothing back. The advertised
663
+ // platform take is unchanged; the card cost is now passed through at cost
664
+ // so the net per order is the take rather than the take minus Stripe's fee.
665
+ //
666
+ // The charge base is everything Stripe will run the card for: the goods
667
+ // after the coupon, the manual tax line, and the DEAREST shipping option on
668
+ // the session. The dearest, not the cheapest, because the shopper picks
669
+ // after the session is created and `application_fee_amount` is a fixed
670
+ // number Stripe will not let us revise — under-reaching would put the
671
+ // difference back on Aglyn. A store on Stripe Tax is the one residual: its
672
+ // tax is computed by Stripe after this point and cannot be seen from here.
673
+ const shippingCeilingCents = shippingPlan.options.reduce((most, option)=>{
674
+ var _option_amountCents;
675
+ return Math.max(most, Math.max(0, Number((_option_amountCents = option.amountCents) != null ? _option_amountCents : 0)));
676
+ }, 0);
677
+ // A SUBSCRIPTION carries the same pass-through AS A PERCENT (AGL-2655).
678
+ // Stripe offers a Subscription no `application_fee_amount`, only
679
+ // `application_fee_percent`, which cannot carry a fixed 30¢ as cents — so
680
+ // the 30¢ is folded into the rate: `(rate × amount + fixed) ÷ amount`,
681
+ // rounded UP to the two decimals the parameter allows, on top of the
682
+ // plan's own percent. `resolveSubscriptionFeePercent` owns that
683
+ // arithmetic and its rounding. Sized on the recurring goods alone: Stripe
684
+ // applies the percent to the whole invoice, and the paid-invoice
685
+ // correction (`chargeSubscriptionFeeOnItemsOnly`, AGL-2317) scales it
686
+ // back to the items, so the goods are the base the recovery has to come
687
+ // out at cost on.
688
+ //
689
+ // This branch never reaches `payment_intent_data[application_fee_amount]`
690
+ // (the emission below is gated on `!isSubscription`); the percent goes
691
+ // out on `subscription_data` further down, and `feeCents` here is what
692
+ // that percent takes of the opening cycle's goods — the figure
693
+ // `metadata[feeCents]` records as what Aglyn keeps.
694
+ const subscriptionFeePercent = isSubscription ? Aglyn.resolveSubscriptionFeePercent(ownerOrg.org, (_lifted_type1 = lifted.type) != null ? _lifted_type1 : 'physical', amountCents) : 0;
695
+ const feeCents = isSubscription ? subscriptionFeePercent > 0 ? Math.max(1, Math.round(amountCents * subscriptionFeePercent / 100)) : 0 : Aglyn.resolveTransactionFeeCents(ownerOrg.org, (_lifted_type2 = lifted.type) != null ? _lifted_type2 : 'physical', amountCents, amountCents + taxCents + shippingCeilingCents);
696
+ // The unit price Stripe actually charges — the coupon is priced INTO it
697
+ // rather than sent as a Stripe discount, which is why `amount_discount` is
698
+ // 0 on every buy-now session and the discount has to ride the metadata
699
+ // (AGL-1711). Hoisted so the Stripe param and the metadata cannot drift.
700
+ // THE SESSION CHARGES `amountCents`, EXACTLY (AGL-2159).
701
+ //
702
+ // A Stripe line is `quantity` units at ONE integer `unit_amount`, so it can
703
+ // only express totals divisible by `quantity` — and a percentage coupon
704
+ // does not respect that. This used to be
705
+ // `Math.round(amountCents / quantity)` multiplied back by `quantity`, which
706
+ // missed `amountCents` by up to `quantity / 2` cents in either direction:
707
+ // the shopper was charged something the storefront never quoted, and the
708
+ // fee and the manual tax were computed on a base nothing collected.
709
+ // Rounding UP was worse still — `discountCents` below is
710
+ // `list - charged` under a `Math.max(0, …)`, so a charge that met or passed
711
+ // the list price clamped it to ZERO and the order recorded no discount on a
712
+ // sale the shopper had applied a coupon to. On a 99-unit order of a 50¢
713
+ // item with 1% off that is not a cent of drift: the shopper paid the full
714
+ // $49.50 and the promotion vanished from the merchant's own record.
715
+ //
716
+ // So when the total does not divide evenly, the line becomes ONE unit at
717
+ // the exact total, with the count moved into the display name. The
718
+ // shopper's real quantity was never carried by this field anyway —
719
+ // `metadata[quantity]` is what `computeBuyNowOrder` builds the order's line
720
+ // item from, and it is untouched — so nothing downstream can tell the
721
+ // difference except that the arithmetic now closes.
722
+ //
723
+ // The even case (every sale with no coupon, and most with one) keeps the
724
+ // per-unit shape byte for byte, so the ordinary Stripe checkout page is
725
+ // unchanged.
726
+ const evenUnitAmountCents = Math.floor(amountCents / quantity);
727
+ const remainderCents = amountCents - evenUnitAmountCents * quantity;
728
+ const chargedQuantity = remainderCents === 0 ? quantity : 1;
729
+ const chargedUnitAmountCents = remainderCents === 0 ? evenUnitAmountCents : amountCents;
730
+ const chargedLineName = `${String((_product_name = product.name) != null ? _product_name : 'Product')}${remainderCents === 0 ? '' : ` × ${quantity}`}`.slice(0, 120);
731
+ // What was ACTUALLY discounted, now that the charge and `amountCents` are
732
+ // the same number. `Math.max(0, …)` stays as a guard against a future floor
733
+ // lifting the charge above list; it no longer erases a real discount,
734
+ // because there is no longer a rounding that can push the charge past it.
735
+ const discountCents = Math.max(0, listUnitAmountCents * quantity - amountCents);
736
+ // Subscriptions (AGL-303): recurring prices bill on the platform with
737
+ // a destination transfer + percent fee; the webhook records the sub.
738
+ // `isSubscription` resolved above (AGL-545).
739
+ const params = new URLSearchParams(_extends({
740
+ mode: isSubscription ? 'subscription' : 'payment',
741
+ 'line_items[0][quantity]': String(chargedQuantity),
742
+ 'line_items[0][price_data][currency]': 'usd',
743
+ 'line_items[0][price_data][unit_amount]': String(chargedUnitAmountCents),
744
+ 'line_items[0][price_data][product_data][name]': chargedLineName
745
+ }, isSubscription ? _extends({
746
+ 'line_items[0][price_data][recurring][interval]': lifted.subscription.interval
747
+ }, lifted.subscription.trialDays ? {
748
+ 'subscription_data[trial_period_days]': String(lifted.subscription.trialDays)
749
+ } : {}, {
750
+ 'subscription_data[transfer_data][destination]': String(accountId)
751
+ }, subscriptionFeePercent > 0 ? {
752
+ 'subscription_data[application_fee_percent]': String(subscriptionFeePercent)
753
+ } : {}, {
754
+ 'subscription_data[metadata][type]': 'commerce-subscription',
755
+ 'subscription_data[metadata][hostId]': hostId,
756
+ 'subscription_data[metadata][productId]': productId
757
+ }, recurringTaxRateId ? {
758
+ 'subscription_data[metadata][taxRateId]': recurringTaxRateId,
759
+ 'subscription_data[metadata][taxPct]': String(taxPct)
760
+ } : {}) : {}, taxCents > 0 && !isSubscription ? {
761
+ 'line_items[1][quantity]': '1',
762
+ 'line_items[1][price_data][currency]': 'usd',
763
+ 'line_items[1][price_data][unit_amount]': String(taxCents),
764
+ 'line_items[1][price_data][product_data][name]': taxLabel.slice(0, 120)
765
+ } : {}, recurringTaxRateId ? {
766
+ 'line_items[0][tax_rates][0]': recurringTaxRateId
767
+ } : {}, useStripeTax ? {
768
+ 'automatic_tax[enabled]': 'true'
769
+ } : {}, !isSubscription ? CommerceModel.destinationChargeParams({
770
+ accountId: String(accountId),
771
+ feeCents,
772
+ taxOwner: useStripeTax ? 'platform' : 'merchant',
773
+ merchantGoodsCents: amountCents,
774
+ shippingFloorCents: CommerceModel.shippingFloorCents(shippingPlan.options)
775
+ }) : {}, {
776
+ // Substituted by Stripe on redirect — see the note in `cart-checkout.ts`
777
+ // (AGL-1641). The single-product path returns the shopper to the product
778
+ // page, so the `purchase` is reported by the PDP rather than the cart.
779
+ success_url: `${backUrl}${separator}order=success&session_id={CHECKOUT_SESSION_ID}`,
780
+ cancel_url: `${backUrl}${separator}order=canceled`,
781
+ 'metadata[type]': isSubscription ? 'commerce-subscription' : 'commerce-order',
782
+ 'metadata[hostId]': hostId,
783
+ 'metadata[productId]': productId
784
+ }, variantId ? {
785
+ 'metadata[variantId]': variantId
786
+ } : {}, {
787
+ 'metadata[quantity]': String(quantity),
788
+ 'metadata[feeCents]': String(feeCents)
789
+ }, useStripeTax && !isSubscription ? {
790
+ 'metadata[transferCents]': String(CommerceModel.platformLiableTransferCents({
791
+ feeCents,
792
+ merchantGoodsCents: amountCents,
793
+ shippingFloorCents: CommerceModel.shippingFloorCents(shippingPlan.options)
794
+ })),
795
+ 'metadata[transferShippingCents]': String(CommerceModel.shippingFloorCents(shippingPlan.options))
796
+ } : {}, recurringTaxRateId ? {
797
+ 'metadata[taxRateId]': recurringTaxRateId,
798
+ 'metadata[taxPct]': String(taxPct)
799
+ } : {}, {
800
+ // The order's decomposition, snapshotted as we compute it (AGL-1711).
801
+ // Two of these three are invisible to Stripe's own `total_details` BY
802
+ // OUR OWN CONSTRUCTION — the manual tax goes over as an ordinary
803
+ // `line_items[1]` product line, and the coupon is priced into
804
+ // `line_items[0]`'s unit amount — so the webhook cannot recover them
805
+ // from the session. Frozen here rather than re-derived from the product
806
+ // doc at webhook time, which a price edit in between would falsify.
807
+ //
808
+ // EXCEPT the tax on a subscription (AGL-1751): there it is a real tax
809
+ // rate, `amount_tax` carries it, and this snapshot must say 0 —
810
+ // `computeBuyNowOrder` SUMS the metadata figure with Stripe's, on the
811
+ // documented ground that the two are mutually exclusive, so carrying
812
+ // both would double-count the tax on the recorded sale.
813
+ 'metadata[unitAmountCents]': String(listUnitAmountCents),
814
+ 'metadata[taxCents]': String(isSubscription ? 0 : taxCents),
815
+ 'metadata[discountCents]': String(discountCents)
816
+ }, appliedCoupon ? {
817
+ 'metadata[couponCode]': appliedCoupon
818
+ } : {}, appliedDiscountId ? {
819
+ 'metadata[discountId]': appliedDiscountId
820
+ } : {}, couponHoldKey ? {
821
+ 'metadata[couponHoldKey]': couponHoldKey
822
+ } : {}, heldStockKey ? {
823
+ 'metadata[stockHoldKey]': heldStockKey
824
+ } : {}, {
825
+ // A HALF-HOUR SESSION, AND IT IS THE HOLD'S WHOLE SAFETY ARGUMENT
826
+ // (AGL-2356). Stripe's default is 24 hours, and a hold must outlive any
827
+ // session that can still be paid — so the default would mean one
828
+ // abandoned cart standing on a merchant's last unit for a day, which is
829
+ // exactly the unbounded, invisible UNDER-sell this issue was held back
830
+ // for. Shortening the SESSION is what bounds it; 30 minutes is Stripe's
831
+ // own minimum and the same figure the bookings plugin gives a held room.
832
+ // Sent on subscription sessions too, deliberately: a shopper who has been
833
+ // sitting on a payment page for half an hour is starting again either
834
+ // way, and one rule is one thing to reason about.
835
+ expires_at: String(Math.floor((Date.now() + CommerceModel.CHECKOUT_SESSION_TTL_MS) / 1000))
836
+ }));
837
+ // Both keys or neither (AGL-1720). Stripe will not apply a shipping rate
838
+ // without an address to ship to, and a merchant who configured nothing
839
+ // resolves to no options and so gets neither key — a session byte-identical
840
+ // to the one this handler built before shipping existed on it. Buy-now has
841
+ // never collected an address, so declaring one unconditionally would put a
842
+ // shipping form in front of every shopper on a store that charges none.
843
+ if (shippingPlan.options.length > 0) {
844
+ CommerceModel.appendShippingAddressCollectionParams(params, shippingPlan.countries);
845
+ CommerceModel.appendCheckoutShippingParams(params, // Free shipping is a zeroed rate, never a coupon — see the cart's note
846
+ // for why a session coupon cannot reach `shipping_options` at all.
847
+ (resolvedDiscount == null ? void 0 : resolvedDiscount.freeShipping) === true ? shippingPlan.options.map((option)=>_extends({}, option, {
848
+ amountCents: 0
849
+ })) : shippingPlan.options);
850
+ }
851
+ // The Payment Element (AGL-1944), and the LAST thing done to the params on
852
+ // purpose. Everything above — price, coupon, tax, shipping, the Connect
853
+ // destination, the fee, every metadata key the webhook reads — is computed
854
+ // identically for both checkout paths, and this rewrite touches none of it:
855
+ // it swaps the redirect's URL pair for `ui_mode` + `return_url` and stops.
856
+ // A native session that priced differently from the hosted one would be the
857
+ // under-collection AGL-1944 warns about, and the only defence that actually
858
+ // holds is that there is nowhere for the two to diverge.
859
+ //
860
+ // Off unless the flag is on for this org AND a publishable key exists, so
861
+ // the default remains the redirect — see `resolveNativeCheckoutMode`.
862
+ const nativeMode = await resolveNativeCheckoutMode(String((_ref2 = ownerOrg == null ? void 0 : (_ownerOrg_org2 = ownerOrg.org) == null ? void 0 : _ownerOrg_org2.id) != null ? _ref2 : ''));
863
+ if (nativeMode.native) {
864
+ applyNativeCheckoutParams(params, `${backUrl}${separator}order=success&session_id={CHECKOUT_SESSION_ID}`);
865
+ }
866
+ const response = await fetch('https://api.stripe.com/v1/checkout/sessions', {
867
+ method: 'POST',
868
+ headers: _extends({
869
+ Authorization: `Bearer ${process.env.STRIPE_SECRET_KEY}`,
870
+ 'Content-Type': 'application/x-www-form-urlencoded'
871
+ }, stripeKeyHeader('session'), nativeCheckoutStripeHeaders(nativeMode)),
872
+ body: params.toString()
873
+ });
874
+ const session = await response.json();
875
+ // `!session.url` was the liveness check, and a `ui_mode` session HAS no
876
+ // url — so the check moves with the mode rather than staying behind and
877
+ // reading every successful native session as a Stripe failure (AGL-1944).
878
+ const payload = response.ok ? readCheckoutSessionPayload(session, nativeMode) : null;
879
+ if (!payload) {
880
+ console.error('Stripe checkout error', session.error);
881
+ // Nothing was sold — hand the key back so one flaky moment does not
882
+ // lock this product out for the shopper.
883
+ await releaseCouponSlot();
884
+ await releaseStock();
885
+ await claim.release();
886
+ return res.status(502).json({
887
+ error: 'Checkout failed'
888
+ });
889
+ }
890
+ // THE SESSION NOW EXISTS, SO THE SLOT BELONGS TO IT (AGL-2453). Past this
891
+ // line the hold must survive: the shopper can still pay, and the webhook
892
+ // settles against the reservation. A catch that released it here would
893
+ // leave a paid order whose redemption is never counted — the same
894
+ // over-redemption this fix closes, arriving through the release path.
895
+ couponSlot = null;
896
+ // And so do the units (AGL-2356), for the stronger version of the same
897
+ // reason: releasing here would hand the last unit to the next shopper while
898
+ // this one still holds a payable session, which is the oversell itself.
899
+ stockHold = null;
900
+ await claim.record(200, payload);
901
+ return res.status(200).json(payload);
902
+ } catch (error) {
903
+ console.error(error);
904
+ // Release on the way out so a transient failure does not strand the key
905
+ // (AGL-1691's rule).
906
+ await releaseCouponSlot();
907
+ await releaseStock();
908
+ await (claim == null ? void 0 : claim.release());
909
+ return res.status(500).json({
910
+ error: 'Checkout failed'
911
+ });
912
+ }
913
+ };
914
+
915
+ //# sourceMappingURL=checkout.js.map