@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,196 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ /**
3
+ * @license
4
+ * Copyright 2026 Aglyn LLC
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this file except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ */ import { mergeContactInteraction, normalizeContactEmail } from "@aglyn/aglyn/server";
18
+ import { firebaseAdmin, orgDataCollectionForHost } from "@aglyn/tenant-data-admin";
19
+ // Leaf imports, not the barrel: `refund.spec.ts` and most specs in this
20
+ // library mock `@aglyn/tenant-data-admin` wholesale, and a permissive stub of
21
+ // either function would turn every case below green without a document ever
22
+ // having to exist.
23
+ import { findContactByEmail } from "@aglyn/tenant-data-admin/server/contact-email-index";
24
+ import { updateExisting } from "@aglyn/tenant-data-admin/server/update-existing";
25
+ /**
26
+ * Money going BACK to a customer, recorded on their contact (AGL-1754).
27
+ *
28
+ * `upsertHostContact` accumulates `ltvCents` and nothing anywhere decremented
29
+ * it, so a customer who bought $500 and returned all of it read identically to
30
+ * one who bought $500 and kept it — and the first surface built on the field
31
+ * (RFM segmentation, a lifetime-value column, a best-customers list) would have
32
+ * ranked a serial returner among the merchant's best customers.
33
+ *
34
+ * WHAT THIS WRITES, AND WHY IT IS NOT A DECREMENT
35
+ *
36
+ * `ltvCents` is left exactly as it is and the reversal is recorded BESIDE it:
37
+ *
38
+ * | field | meaning |
39
+ * | -- | -- |
40
+ * | `ltvCents` | gross money handed over, unchanged (AGL-1748) |
41
+ * | `refundedCents` | gross money handed back |
42
+ * | `ordersCount` | orders placed, unchanged |
43
+ * | `refundedOrdersCount` | of those, how many ended fully reversed |
44
+ * | `lastRefundAtMs` | when money last went back |
45
+ *
46
+ * This is the shape AGL-1747 chose two days earlier for the same question on
47
+ * the orders CSV — `amountUsd` stayed gross under its existing name and
48
+ * `refundedUsd`/`netUsd` were appended — and it is the shape AGL-1748 named
49
+ * when it recorded the gross semantics in `upsert-contact.ts`. Netting in
50
+ * place would make a stored number mean one thing for rows written before this
51
+ * commit and another for rows written after, with nothing on the document to
52
+ * tell the two apart. Two fields cost one number's worth of storage and keep
53
+ * both facts, and a reader that wants the net has `ltvCents - refundedCents`.
54
+ *
55
+ * It also answers "can it go negative?" by moving the question off storage.
56
+ * Both figures are monotonic counters of money that actually moved in one
57
+ * direction, so neither can go negative. The DERIVED net can, for exactly one
58
+ * reason: an order placed before AGL-1748 taught POS, drafts, reservations and
59
+ * bookings to pass `purchaseCents` at all, whose sale was never added and whose
60
+ * refund is subtracted here. That is a true statement about the data, and
61
+ * clamping it away at write time would erase the only evidence that a purchase
62
+ * is missing. AGL-1753 is the backfill that reconciles it, by SETTING both
63
+ * fields from the orders — which already carry `refundedCents`, so whatever a
64
+ * reader decides here costs the rebuild nothing. A merchant-facing reader
65
+ * should show gross and refunded, and clamp only the net it ranks on.
66
+ *
67
+ * `ordersCount` is deliberately NOT decremented. A fully refunded order still
68
+ * happened, and a decrement cannot be undone by a reader that would rather
69
+ * count it — while `refundedOrdersCount` beside it lets RFM's frequency term
70
+ * net the two, or not, at the point of display. Same argument as the amount,
71
+ * one field along.
72
+ *
73
+ * WHEN THE CONTACT IS MISSING, THIS REFUSES — and records the refusal.
74
+ *
75
+ * Refusing is right on AGL-1732's test, "does refusing discard money or work
76
+ * that already happened?", because it does not: the refund is durable on the
77
+ * order (`refundedCents`, the timeline entry, Stripe), and the contact is a
78
+ * derived aggregate that AGL-1753's rebuild reads back out of those same
79
+ * orders. Creating one here would be worse three ways — creation is audience
80
+ * BAND gated, so it hard-bands a Free org (raising the AGL-891 dropped-contacts
81
+ * alert for a record the merchant never had) or meters onto a paid invoice; the
82
+ * contact it created would hold a refund and no purchase, which is the negative
83
+ * lifetime value above with nothing to reconcile it against; and it is the
84
+ * phantom-document shape the `updateExisting` sweep spent yesterday closing.
85
+ *
86
+ * So the write is an `update()`, never `set(…, { merge: true })` — the contact
87
+ * may have been deleted between the query and the write, and a merge-set would
88
+ * resurrect it as a document holding nothing but a refund. Every refusal
89
+ * increments `hosts/{hostId}/counters/contactRefundsUnmatched`, the counter
90
+ * shape `contactsDropped` already uses, so a refund that reached no contact is
91
+ * countable rather than silent.
92
+ *
93
+ * CHARGEBACKS (AGL-1554) are a separate decision and are NOT handled here.
94
+ * This takes `kind` so the handler that eventually answers
95
+ * `charge.dispute.closed` can record a lost dispute through the same writer
96
+ * with the same fields: money reversed is money reversed, whichever door it
97
+ * left by, and only the wording of the timeline entry differs.
98
+ */ export async function recordContactRefund(options) {
99
+ // Swallow-all, like `upsertHostContact`: the money has already moved and the
100
+ // order already records it, so nothing here may fail the refund.
101
+ try {
102
+ var _snapshot_get, _snapshot_get1;
103
+ const amountCents = Math.round(Number(options.amountCents));
104
+ if (!(amountCents > 0)) return;
105
+ const email = normalizeContactEmail(options.email);
106
+ if (!email) {
107
+ await recordUnmatchedRefund(options.hostId, options.orderId, 'no-email');
108
+ return;
109
+ }
110
+ // Contacts are ORG-scoped (AGL-237), not `hosts/{h}/contacts`, and reads
111
+ // narrow to what this host may see (AGL-1039) — the same lookup
112
+ // `upsertHostContact` makes, through the org's address index
113
+ // (AGL-2633), so both writers resolve the same document even when the
114
+ // order's address is one a merge folded into the buyer's record.
115
+ const contactsRef = await orgDataCollectionForHost(options.hostId, 'contacts');
116
+ const snapshot = await findContactByEmail(contactsRef, email, {
117
+ hostId: options.hostId
118
+ });
119
+ if (!snapshot) {
120
+ await recordUnmatchedRefund(options.hostId, options.orderId, 'no-contact');
121
+ return;
122
+ }
123
+ const atMs = Date.now();
124
+ const interaction = {
125
+ // `refId` names an order, so the interaction is an order interaction.
126
+ // There is no `'refund'` ContactSource and this deliberately does not
127
+ // add one: `sources` records which capture silo produced the contact,
128
+ // and a refund captures nobody.
129
+ type: 'order',
130
+ atMs,
131
+ refId: options.orderId,
132
+ summary: `$${(amountCents / 100).toFixed(2)} ` + (options.kind === 'chargeback' ? 'charged back' : 'refunded') + (options.closedTheOrder ? ' (full)' : '')
133
+ };
134
+ // Reused for the newest-first ordering and the interactions cap; only the
135
+ // timeline is taken. The `sources` half of the merge is discarded on
136
+ // purpose, per the note above.
137
+ const { interactions } = mergeContactInteraction({
138
+ sources: (_snapshot_get = snapshot.get('sources')) != null ? _snapshot_get : {},
139
+ interactions: (_snapshot_get1 = snapshot.get('interactions')) != null ? _snapshot_get1 : []
140
+ }, {
141
+ source: 'order',
142
+ interaction
143
+ });
144
+ const FieldValue = firebaseAdmin.firestore.FieldValue;
145
+ // Every field is top-level. `update()` replaces a nested map wholesale
146
+ // rather than merging into it, so anything under a map would have to be
147
+ // written as a dotted path; there is nothing here that is.
148
+ //
149
+ // The amounts are increments, so two refunds settling at once both land.
150
+ // `interactions` is a read-modify-write and therefore CAN lose one of two
151
+ // concurrent entries — the same exposure `upsertHostContact` has on the
152
+ // same array, and it costs a timeline line rather than a number.
153
+ const landed = await updateExisting(snapshot.ref, _extends({
154
+ refundedCents: FieldValue.increment(amountCents)
155
+ }, options.closedTheOrder ? {
156
+ refundedOrdersCount: FieldValue.increment(1)
157
+ } : {}, {
158
+ lastRefundAtMs: atMs,
159
+ interactions,
160
+ updatedAt: FieldValue.serverTimestamp()
161
+ }));
162
+ if (!landed) {
163
+ await recordUnmatchedRefund(options.hostId, options.orderId, 'contact-deleted');
164
+ }
165
+ } catch (error) {
166
+ console.error('recordContactRefund failed', error);
167
+ }
168
+ }
169
+ /**
170
+ * A refund that reached no contact, recorded rather than dropped (AGL-1754).
171
+ *
172
+ * The `set(…, { merge: true })` here IS meant to conjure the document — it is
173
+ * a counter, and the shape is `counters/contactsDropped`'s, so an operator
174
+ * reading a host's counters finds both kinds of missed contact record in one
175
+ * place. The reason is stamped because the three cases want different answers:
176
+ * `no-email` is an order that never identified its buyer, `no-contact` is the
177
+ * AGL-1753 backfill's work, and `contact-deleted` is a contact removed between
178
+ * the sale and the refund, which nothing should recreate.
179
+ */ async function recordUnmatchedRefund(hostId, orderId, reason) {
180
+ console.warn('refund reached no contact', {
181
+ hostId,
182
+ orderId,
183
+ reason
184
+ });
185
+ const FieldValue = firebaseAdmin.firestore.FieldValue;
186
+ await firebaseAdmin.app().firestore().collection('hosts').doc(hostId).collection('counters').doc('contactRefundsUnmatched').set({
187
+ total: FieldValue.increment(1),
188
+ lastOrderId: orderId,
189
+ lastReason: reason,
190
+ lastAtMs: Date.now()
191
+ }, {
192
+ merge: true
193
+ }).catch(()=>undefined);
194
+ }
195
+
196
+ //# sourceMappingURL=contact-refund.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/contact-refund.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n mergeContactInteraction,\n normalizeContactEmail,\n type ContactInteraction,\n} from '@aglyn/aglyn/server'\nimport {\n firebaseAdmin,\n orgDataCollectionForHost,\n} from '@aglyn/tenant-data-admin'\n// Leaf imports, not the barrel: `refund.spec.ts` and most specs in this\n// library mock `@aglyn/tenant-data-admin` wholesale, and a permissive stub of\n// either function would turn every case below green without a document ever\n// having to exist.\nimport { findContactByEmail } from '@aglyn/tenant-data-admin/server/contact-email-index'\nimport { updateExisting } from '@aglyn/tenant-data-admin/server/update-existing'\n\n/**\n * Money going BACK to a customer, recorded on their contact (AGL-1754).\n *\n * `upsertHostContact` accumulates `ltvCents` and nothing anywhere decremented\n * it, so a customer who bought $500 and returned all of it read identically to\n * one who bought $500 and kept it — and the first surface built on the field\n * (RFM segmentation, a lifetime-value column, a best-customers list) would have\n * ranked a serial returner among the merchant's best customers.\n *\n * WHAT THIS WRITES, AND WHY IT IS NOT A DECREMENT\n *\n * `ltvCents` is left exactly as it is and the reversal is recorded BESIDE it:\n *\n * | field | meaning |\n * | -- | -- |\n * | `ltvCents` | gross money handed over, unchanged (AGL-1748) |\n * | `refundedCents` | gross money handed back |\n * | `ordersCount` | orders placed, unchanged |\n * | `refundedOrdersCount` | of those, how many ended fully reversed |\n * | `lastRefundAtMs` | when money last went back |\n *\n * This is the shape AGL-1747 chose two days earlier for the same question on\n * the orders CSV — `amountUsd` stayed gross under its existing name and\n * `refundedUsd`/`netUsd` were appended — and it is the shape AGL-1748 named\n * when it recorded the gross semantics in `upsert-contact.ts`. Netting in\n * place would make a stored number mean one thing for rows written before this\n * commit and another for rows written after, with nothing on the document to\n * tell the two apart. Two fields cost one number's worth of storage and keep\n * both facts, and a reader that wants the net has `ltvCents - refundedCents`.\n *\n * It also answers \"can it go negative?\" by moving the question off storage.\n * Both figures are monotonic counters of money that actually moved in one\n * direction, so neither can go negative. The DERIVED net can, for exactly one\n * reason: an order placed before AGL-1748 taught POS, drafts, reservations and\n * bookings to pass `purchaseCents` at all, whose sale was never added and whose\n * refund is subtracted here. That is a true statement about the data, and\n * clamping it away at write time would erase the only evidence that a purchase\n * is missing. AGL-1753 is the backfill that reconciles it, by SETTING both\n * fields from the orders — which already carry `refundedCents`, so whatever a\n * reader decides here costs the rebuild nothing. A merchant-facing reader\n * should show gross and refunded, and clamp only the net it ranks on.\n *\n * `ordersCount` is deliberately NOT decremented. A fully refunded order still\n * happened, and a decrement cannot be undone by a reader that would rather\n * count it — while `refundedOrdersCount` beside it lets RFM's frequency term\n * net the two, or not, at the point of display. Same argument as the amount,\n * one field along.\n *\n * WHEN THE CONTACT IS MISSING, THIS REFUSES — and records the refusal.\n *\n * Refusing is right on AGL-1732's test, \"does refusing discard money or work\n * that already happened?\", because it does not: the refund is durable on the\n * order (`refundedCents`, the timeline entry, Stripe), and the contact is a\n * derived aggregate that AGL-1753's rebuild reads back out of those same\n * orders. Creating one here would be worse three ways — creation is audience\n * BAND gated, so it hard-bands a Free org (raising the AGL-891 dropped-contacts\n * alert for a record the merchant never had) or meters onto a paid invoice; the\n * contact it created would hold a refund and no purchase, which is the negative\n * lifetime value above with nothing to reconcile it against; and it is the\n * phantom-document shape the `updateExisting` sweep spent yesterday closing.\n *\n * So the write is an `update()`, never `set(…, { merge: true })` — the contact\n * may have been deleted between the query and the write, and a merge-set would\n * resurrect it as a document holding nothing but a refund. Every refusal\n * increments `hosts/{hostId}/counters/contactRefundsUnmatched`, the counter\n * shape `contactsDropped` already uses, so a refund that reached no contact is\n * countable rather than silent.\n *\n * CHARGEBACKS (AGL-1554) are a separate decision and are NOT handled here.\n * This takes `kind` so the handler that eventually answers\n * `charge.dispute.closed` can record a lost dispute through the same writer\n * with the same fields: money reversed is money reversed, whichever door it\n * left by, and only the wording of the timeline entry differs.\n */\nexport async function recordContactRefund(options: {\n hostId: string\n /** The order the money went back on — the contact timeline's `refId`. */\n orderId: string\n /**\n * The buyer as the ORDER recorded them, raw. Normalized here, because that\n * is the key the contacts collection is stored under and orders are not\n * consistent about it (AGL-1753 item 3). No `name` rides along on purpose: a\n * refund is not a capture point and must not rewrite who the contact is.\n */\n email: unknown\n /**\n * Cents reversed by THIS attempt, never the order total. `refund.ts` caps\n * each attempt against what is left, so several partials on one order sum to\n * at most the order total; this follows that number rather than recomputing\n * it.\n */\n amountCents: number\n /**\n * True only for the write that moved the order INTO `refunded`. Two partials\n * settling concurrently can both observe a fully refunded total, so the\n * caller decides this from the status transition it made, not from the\n * total it read.\n */\n closedTheOrder: boolean\n kind?: 'refund' | 'chargeback'\n}): Promise<void> {\n // Swallow-all, like `upsertHostContact`: the money has already moved and the\n // order already records it, so nothing here may fail the refund.\n try {\n const amountCents = Math.round(Number(options.amountCents))\n if (!(amountCents > 0)) return\n const email = normalizeContactEmail(options.email)\n if (!email) {\n await recordUnmatchedRefund(options.hostId, options.orderId, 'no-email')\n return\n }\n // Contacts are ORG-scoped (AGL-237), not `hosts/{h}/contacts`, and reads\n // narrow to what this host may see (AGL-1039) — the same lookup\n // `upsertHostContact` makes, through the org's address index\n // (AGL-2633), so both writers resolve the same document even when the\n // order's address is one a merge folded into the buyer's record.\n const contactsRef = await orgDataCollectionForHost(\n options.hostId,\n 'contacts',\n )\n const snapshot = await findContactByEmail(contactsRef, email, {\n hostId: options.hostId,\n })\n if (!snapshot) {\n await recordUnmatchedRefund(options.hostId, options.orderId, 'no-contact')\n return\n }\n const atMs = Date.now()\n const interaction: ContactInteraction = {\n // `refId` names an order, so the interaction is an order interaction.\n // There is no `'refund'` ContactSource and this deliberately does not\n // add one: `sources` records which capture silo produced the contact,\n // and a refund captures nobody.\n type: 'order',\n atMs,\n refId: options.orderId,\n summary:\n `$${(amountCents / 100).toFixed(2)} ` +\n (options.kind === 'chargeback' ? 'charged back' : 'refunded') +\n (options.closedTheOrder ? ' (full)' : ''),\n }\n // Reused for the newest-first ordering and the interactions cap; only the\n // timeline is taken. The `sources` half of the merge is discarded on\n // purpose, per the note above.\n const { interactions } = mergeContactInteraction(\n {\n sources: snapshot.get('sources') ?? {},\n interactions: snapshot.get('interactions') ?? [],\n },\n { source: 'order', interaction },\n )\n\n const FieldValue = firebaseAdmin.firestore.FieldValue\n // Every field is top-level. `update()` replaces a nested map wholesale\n // rather than merging into it, so anything under a map would have to be\n // written as a dotted path; there is nothing here that is.\n //\n // The amounts are increments, so two refunds settling at once both land.\n // `interactions` is a read-modify-write and therefore CAN lose one of two\n // concurrent entries — the same exposure `upsertHostContact` has on the\n // same array, and it costs a timeline line rather than a number.\n const landed = await updateExisting(snapshot.ref, {\n refundedCents: FieldValue.increment(amountCents),\n ...(options.closedTheOrder\n ? { refundedOrdersCount: FieldValue.increment(1) }\n : {}),\n lastRefundAtMs: atMs,\n interactions,\n updatedAt: FieldValue.serverTimestamp(),\n })\n if (!landed) {\n await recordUnmatchedRefund(\n options.hostId,\n options.orderId,\n 'contact-deleted',\n )\n }\n } catch (error) {\n console.error('recordContactRefund failed', error)\n }\n}\n\n/**\n * A refund that reached no contact, recorded rather than dropped (AGL-1754).\n *\n * The `set(…, { merge: true })` here IS meant to conjure the document — it is\n * a counter, and the shape is `counters/contactsDropped`'s, so an operator\n * reading a host's counters finds both kinds of missed contact record in one\n * place. The reason is stamped because the three cases want different answers:\n * `no-email` is an order that never identified its buyer, `no-contact` is the\n * AGL-1753 backfill's work, and `contact-deleted` is a contact removed between\n * the sale and the refund, which nothing should recreate.\n */\nasync function recordUnmatchedRefund(\n hostId: string,\n orderId: string,\n reason: 'no-email' | 'no-contact' | 'contact-deleted',\n): Promise<void> {\n console.warn('refund reached no contact', { hostId, orderId, reason })\n const FieldValue = firebaseAdmin.firestore.FieldValue\n await firebaseAdmin\n .app()\n .firestore()\n .collection('hosts')\n .doc(hostId)\n .collection('counters')\n .doc('contactRefundsUnmatched')\n .set(\n {\n total: FieldValue.increment(1),\n lastOrderId: orderId,\n lastReason: reason,\n lastAtMs: Date.now(),\n },\n { merge: true },\n )\n .catch(() => undefined)\n}\n"],"names":["mergeContactInteraction","normalizeContactEmail","firebaseAdmin","orgDataCollectionForHost","findContactByEmail","updateExisting","recordContactRefund","options","snapshot","amountCents","Math","round","Number","email","recordUnmatchedRefund","hostId","orderId","contactsRef","atMs","Date","now","interaction","type","refId","summary","toFixed","kind","closedTheOrder","interactions","sources","get","source","FieldValue","firestore","landed","ref","refundedCents","increment","refundedOrdersCount","lastRefundAtMs","updatedAt","serverTimestamp","error","console","reason","warn","app","collection","doc","set","total","lastOrderId","lastReason","lastAtMs","merge","catch","undefined"],"mappings":"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"}
@@ -0,0 +1,39 @@
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
+ */
17
+ import type { PluginApiHandler } from '@aglyn/aglyn/server';
18
+ /**
19
+ * Signing secret for commerce tokens (AGL-509). A dedicated env var with NO
20
+ * fallback: the previous `STRIPE_SECRET_KEY ?? 'aglyn'` recipe made download
21
+ * and supplier tokens forgeable on any deploy that had not set the Stripe key
22
+ * (payloads like `download:${hostId}:${orderId}` are guessable). Fails closed.
23
+ */
24
+ export declare function tokenSigningSecret(): string;
25
+ /**
26
+ * Order-scoped download token (AGL-302). Carries an embedded expiry (AGL-514)
27
+ * so a leaked receipt link doesn't grant perpetual re-download; the per-order
28
+ * download limit still bounds use within the window. Format: `${expMs}.${sig}`.
29
+ */
30
+ export declare function mintDownloadToken(hostId: string, orderId: string): string;
31
+ /** Constant-time verify of a download token, including its expiry. */
32
+ export declare function verifyDownloadToken(hostId: string, orderId: string, token: string): boolean;
33
+ /**
34
+ * Digital delivery (AGL-302): token-gated download for a paid order's
35
+ * digital product. Buyers always get the product's CURRENT files (new
36
+ * versions re-deliver automatically); attempts count against the
37
+ * product's download limit per order.
38
+ */
39
+ export declare const downloadHandler: PluginApiHandler;
@@ -0,0 +1,206 @@
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 CommerceModel from "../model/index.js";
17
+ import { firebaseAdmin, PAID_DOWNLOAD_LINK_TTL_MS } from "@aglyn/tenant-data-admin";
18
+ import { createPaidMediaDeliveryIo, resolvePaidMediaDelivery } from "./paid-media-delivery.js";
19
+ import { createHmac, timingSafeEqual } from "crypto";
20
+ /**
21
+ * Signing secret for commerce tokens (AGL-509). A dedicated env var with NO
22
+ * fallback: the previous `STRIPE_SECRET_KEY ?? 'aglyn'` recipe made download
23
+ * and supplier tokens forgeable on any deploy that had not set the Stripe key
24
+ * (payloads like `download:${hostId}:${orderId}` are guessable). Fails closed.
25
+ */ export function tokenSigningSecret() {
26
+ const secret = process.env.TOKEN_SIGNING_SECRET;
27
+ if (!secret) {
28
+ throw new Error('TOKEN_SIGNING_SECRET is not configured');
29
+ }
30
+ return secret;
31
+ }
32
+ /** Download links expire 90 days after minting (AGL-514). */ const DOWNLOAD_TOKEN_TTL_MS = 90 * 24 * 60 * 60 * 1000;
33
+ function signDownload(hostId, orderId, exp) {
34
+ return createHmac('sha256', tokenSigningSecret()).update(`download:${hostId}:${orderId}:${exp}`).digest('hex').slice(0, 32);
35
+ }
36
+ /**
37
+ * Order-scoped download token (AGL-302). Carries an embedded expiry (AGL-514)
38
+ * so a leaked receipt link doesn't grant perpetual re-download; the per-order
39
+ * download limit still bounds use within the window. Format: `${expMs}.${sig}`.
40
+ */ export function mintDownloadToken(hostId, orderId) {
41
+ const exp = Date.now() + DOWNLOAD_TOKEN_TTL_MS;
42
+ return `${exp}.${signDownload(hostId, orderId, exp)}`;
43
+ }
44
+ /** Constant-time verify of a download token, including its expiry. */ export function verifyDownloadToken(hostId, orderId, token) {
45
+ const dot = token.indexOf('.');
46
+ if (dot <= 0) return false;
47
+ const exp = Number(token.slice(0, dot));
48
+ const sig = token.slice(dot + 1);
49
+ if (!Number.isFinite(exp) || Date.now() > exp) return false;
50
+ const expected = signDownload(hostId, orderId, exp);
51
+ const a = Buffer.from(sig);
52
+ const b = Buffer.from(expected);
53
+ if (a.length !== b.length) return false;
54
+ return timingSafeEqual(new Uint8Array(a), new Uint8Array(b));
55
+ }
56
+ /**
57
+ * Digital delivery (AGL-302): token-gated download for a paid order's
58
+ * digital product. Buyers always get the product's CURRENT files (new
59
+ * versions re-deliver automatically); attempts count against the
60
+ * product's download limit per order.
61
+ */ export const downloadHandler = async (req, res)=>{
62
+ var _req_query_hostId, _req_query_orderId, _req_query_token, _req_query_productId, _req_query_file;
63
+ const hostId = String((_req_query_hostId = req.query.hostId) != null ? _req_query_hostId : '');
64
+ const orderId = String((_req_query_orderId = req.query.orderId) != null ? _req_query_orderId : '');
65
+ const token = String((_req_query_token = req.query.token) != null ? _req_query_token : '');
66
+ const productId = String((_req_query_productId = req.query.productId) != null ? _req_query_productId : '');
67
+ const fileIndex = Math.max(0, Number((_req_query_file = req.query.file) != null ? _req_query_file : 0));
68
+ if (!hostId || !orderId || !token || !productId) {
69
+ return res.status(400).send('Missing parameters');
70
+ }
71
+ if (!verifyDownloadToken(hostId, orderId, token)) {
72
+ return res.status(403).send('Invalid or expired download link');
73
+ }
74
+ try {
75
+ var _productSnapshot_data;
76
+ var _product_digitalFiles;
77
+ const firestore = firebaseAdmin.app().firestore();
78
+ const hostRef = firestore.collection('hosts').doc(hostId);
79
+ const orderRef = hostRef.collection('orders').doc(orderId);
80
+ const [orderSnapshot, productSnapshot] = await Promise.all([
81
+ orderRef.get(),
82
+ hostRef.collection('products').doc(productId).get()
83
+ ]);
84
+ if (!orderSnapshot.exists) return res.status(404).send('Unknown order');
85
+ const order = CommerceModel.liftLegacyOrder(orderSnapshot.data());
86
+ // ONE ENTITLEMENT TEST, AND IT KNOWS ABOUT PARTIAL REFUNDS (AGL-2454).
87
+ //
88
+ // This used to be a status test against the literal `'refunded'` followed
89
+ // by an ownership test, and `refund.ts` writes that literal only when the
90
+ // order is FULLY refunded — so a 99%-refunded order stayed `paid` and this
91
+ // gate went on serving the files. The two questions are now one call, and
92
+ // it also answers "was THIS line refunded by name", which is the case a
93
+ // line-scoped refund creates.
94
+ //
95
+ // The order-level refusal is kept separate from the ownership one so the
96
+ // shopper is told which of the two happened; collapsing them would answer
97
+ // "not part of this order" to a buyer whose own order was cancelled.
98
+ if (!CommerceModel.orderEntitlesProduct(order, 'any')) {
99
+ return res.status(403).send('This order cannot download files');
100
+ }
101
+ if (!CommerceModel.orderContainsProduct(order, productId)) {
102
+ return res.status(403).send('Not part of this order');
103
+ }
104
+ if (!CommerceModel.orderEntitlesProduct(order, productId)) {
105
+ return res.status(403).send('This purchase was refunded');
106
+ }
107
+ const product = CommerceModel.liftLegacyProduct((_productSnapshot_data = productSnapshot.data()) != null ? _productSnapshot_data : {});
108
+ const file = (_product_digitalFiles = product.digitalFiles) == null ? void 0 : _product_digitalFiles[fileIndex];
109
+ if (!(file == null ? void 0 : file.url)) return res.status(404).send('No file available');
110
+ /**
111
+ * Where the redirect lands, decided BEFORE an attempt is counted
112
+ * (AGL-2847).
113
+ *
114
+ * Everything above only guards the 302, so what the 302 points at is what
115
+ * a buyer, and anyone they forward the `Location` to, keeps. The resolver
116
+ * hands back a link that expires, or nothing: a CDN URL signed for
117
+ * {@link PAID_DOWNLOAD_LINK_TTL_MS} for a PRIVATE file in this site's
118
+ * library or its org's, a V4 signed Storage read for an object no media
119
+ * document owns, or an author-typed hotlink unchanged. A raw Storage
120
+ * download URL is never the answer, and a file that is still public is
121
+ * refused, because its URL already works for anyone forever.
122
+ *
123
+ * Resolving first means a refusal costs the buyer no attempt: the limit
124
+ * meters files that were handed out, not the seller's setup.
125
+ *
126
+ * `?download=1` rides inside the signed URL and makes the CDN answer with
127
+ * `attachment`. The signature covers `(scope, mediaId, exp)` and not the
128
+ * parameter, so it chooses a disposition for the one file signed and can
129
+ * reach no other.
130
+ */ const delivery = await resolvePaidMediaDelivery({
131
+ stored: file.url,
132
+ hostId,
133
+ ttlMs: PAID_DOWNLOAD_LINK_TTL_MS,
134
+ cdnParams: [
135
+ [
136
+ 'download',
137
+ '1'
138
+ ]
139
+ ],
140
+ io: createPaidMediaDeliveryIo({
141
+ firestore,
142
+ bucket: firebaseAdmin.app().storage().bucket(process.env['NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET'])
143
+ })
144
+ });
145
+ if (delivery.ok === false) {
146
+ res.setHeader('Cache-Control', 'no-store');
147
+ console.error('[commerce/download] paid file not delivered', JSON.stringify({
148
+ hostId,
149
+ orderId,
150
+ productId,
151
+ file: fileIndex,
152
+ refusal: delivery.refusal
153
+ }));
154
+ return delivery.refusal === 'not-private' ? res.status(409).send('This file is not ready to download yet — contact the seller') : res.status(404).send('No file available');
155
+ }
156
+ // ATTEMPT ACCOUNTING, IN ONE TRANSACTION (AGL-2275).
157
+ //
158
+ // This was a read-then-write across an await, from a snapshot fetched
159
+ // before the check, with the failure swallowed — so N parallel requests
160
+ // all read the same count, all passed the limit, and all wrote
161
+ // `attempts + 1`. The counter advanced by one for N downloads, which
162
+ // makes `downloadLimit` unenforceable rather than merely approximate: a
163
+ // buyer who fans out ten requests takes ten copies against a limit of one.
164
+ // For a paid digital product that is the merchant's goods given away.
165
+ //
166
+ // It also wrote the WHOLE `downloadAttempts` map back from that stale
167
+ // snapshot, so a concurrent download of a DIFFERENT product in the same
168
+ // order had its increment erased.
169
+ //
170
+ // The half-finished conversion was visible in the file: `attemptsKey`
171
+ // built the dotted path `downloadAttempts.{productId}` and the next line
172
+ // threw it away with `void attemptsKey`. The dotted path is the right
173
+ // instrument and needs `update()` — which merges only that nested key, and
174
+ // whose "document must exist" precondition the transaction's own read has
175
+ // just satisfied. `set(..., { merge: true })` cannot express it: a dotted
176
+ // key there is a literal field name with a dot in it.
177
+ const attemptsKey = `downloadAttempts.${productId}`;
178
+ const limit = product.downloadLimit != null ? Math.max(1, product.downloadLimit) : null;
179
+ const withinLimit = await firestore.runTransaction(async (transaction)=>{
180
+ var _productId, _fresh_get;
181
+ const fresh = await transaction.get(orderRef);
182
+ if (!fresh.exists) return false;
183
+ const attempts = Number((_productId = ((_fresh_get = fresh.get('downloadAttempts')) != null ? _fresh_get : {})[productId]) != null ? _productId : 0);
184
+ if (limit != null && attempts >= limit) return false;
185
+ transaction.update(orderRef, {
186
+ [attemptsKey]: attempts + 1
187
+ });
188
+ return true;
189
+ });
190
+ if (!withinLimit) {
191
+ return res.status(429).send('Download limit reached — contact the seller for help');
192
+ }
193
+ // Per-order and time-boxed: no cache may keep a redirect that carries a
194
+ // signature past the link it opens.
195
+ res.setHeader('Cache-Control', 'no-store');
196
+ // The link dies within the hour, so a refund, an expired receipt token or
197
+ // a spent limit also ends what a forwarded `Location` can fetch: the next
198
+ // download has to come back through this route and its checks.
199
+ return res.redirect(302, delivery.location);
200
+ } catch (error) {
201
+ console.error(error);
202
+ return res.status(500).send('Download unavailable');
203
+ }
204
+ };
205
+
206
+ //# sourceMappingURL=download.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/download.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { PluginApiHandler } from '@aglyn/aglyn/server'\nimport * as Aglyn from '@aglyn/aglyn/server'\nimport * as CommerceModel from '../model'\nimport {\n firebaseAdmin,\n PAID_DOWNLOAD_LINK_TTL_MS,\n} from '@aglyn/tenant-data-admin'\nimport {\n createPaidMediaDeliveryIo,\n resolvePaidMediaDelivery,\n} from './paid-media-delivery'\nimport { createHmac, timingSafeEqual } from 'crypto'\n\n/**\n * Signing secret for commerce tokens (AGL-509). A dedicated env var with NO\n * fallback: the previous `STRIPE_SECRET_KEY ?? 'aglyn'` recipe made download\n * and supplier tokens forgeable on any deploy that had not set the Stripe key\n * (payloads like `download:${hostId}:${orderId}` are guessable). Fails closed.\n */\nexport function tokenSigningSecret(): string {\n const secret = process.env.TOKEN_SIGNING_SECRET\n if (!secret) {\n throw new Error('TOKEN_SIGNING_SECRET is not configured')\n }\n return secret\n}\n\n/** Download links expire 90 days after minting (AGL-514). */\nconst DOWNLOAD_TOKEN_TTL_MS = 90 * 24 * 60 * 60 * 1000\n\nfunction signDownload(hostId: string, orderId: string, exp: number): string {\n return createHmac('sha256', tokenSigningSecret())\n .update(`download:${hostId}:${orderId}:${exp}`)\n .digest('hex')\n .slice(0, 32)\n}\n\n/**\n * Order-scoped download token (AGL-302). Carries an embedded expiry (AGL-514)\n * so a leaked receipt link doesn't grant perpetual re-download; the per-order\n * download limit still bounds use within the window. Format: `${expMs}.${sig}`.\n */\nexport function mintDownloadToken(hostId: string, orderId: string): string {\n const exp = Date.now() + DOWNLOAD_TOKEN_TTL_MS\n return `${exp}.${signDownload(hostId, orderId, exp)}`\n}\n\n/** Constant-time verify of a download token, including its expiry. */\nexport function verifyDownloadToken(\n hostId: string,\n orderId: string,\n token: string,\n): boolean {\n const dot = token.indexOf('.')\n if (dot <= 0) return false\n const exp = Number(token.slice(0, dot))\n const sig = token.slice(dot + 1)\n if (!Number.isFinite(exp) || Date.now() > exp) return false\n const expected = signDownload(hostId, orderId, exp)\n const a = Buffer.from(sig)\n const b = Buffer.from(expected)\n if (a.length !== b.length) return false\n return timingSafeEqual(new Uint8Array(a), new Uint8Array(b))\n}\n\n/**\n * Digital delivery (AGL-302): token-gated download for a paid order's\n * digital product. Buyers always get the product's CURRENT files (new\n * versions re-deliver automatically); attempts count against the\n * product's download limit per order.\n */\nexport const downloadHandler: PluginApiHandler = async (req, res) => {\n const hostId = String(req.query.hostId ?? '')\n const orderId = String(req.query.orderId ?? '')\n const token = String(req.query.token ?? '')\n const productId = String(req.query.productId ?? '')\n const fileIndex = Math.max(0, Number(req.query.file ?? 0))\n if (!hostId || !orderId || !token || !productId) {\n return res.status(400).send('Missing parameters')\n }\n if (!verifyDownloadToken(hostId, orderId, token)) {\n return res.status(403).send('Invalid or expired download link')\n }\n try {\n const firestore = firebaseAdmin.app().firestore()\n const hostRef = firestore.collection('hosts').doc(hostId)\n const orderRef = hostRef.collection('orders').doc(orderId)\n const [orderSnapshot, productSnapshot] = await Promise.all([\n orderRef.get(),\n hostRef.collection('products').doc(productId).get(),\n ])\n if (!orderSnapshot.exists) return res.status(404).send('Unknown order')\n const order = CommerceModel.liftLegacyOrder(orderSnapshot.data() as any)\n // ONE ENTITLEMENT TEST, AND IT KNOWS ABOUT PARTIAL REFUNDS (AGL-2454).\n //\n // This used to be a status test against the literal `'refunded'` followed\n // by an ownership test, and `refund.ts` writes that literal only when the\n // order is FULLY refunded — so a 99%-refunded order stayed `paid` and this\n // gate went on serving the files. The two questions are now one call, and\n // it also answers \"was THIS line refunded by name\", which is the case a\n // line-scoped refund creates.\n //\n // The order-level refusal is kept separate from the ownership one so the\n // shopper is told which of the two happened; collapsing them would answer\n // \"not part of this order\" to a buyer whose own order was cancelled.\n if (!CommerceModel.orderEntitlesProduct(order, 'any')) {\n return res.status(403).send('This order cannot download files')\n }\n if (!CommerceModel.orderContainsProduct(order, productId)) {\n return res.status(403).send('Not part of this order')\n }\n if (!CommerceModel.orderEntitlesProduct(order, productId)) {\n return res.status(403).send('This purchase was refunded')\n }\n const product = CommerceModel.liftLegacyProduct(\n (productSnapshot.data() as any) ?? {},\n )\n const file = product.digitalFiles?.[fileIndex]\n if (!file?.url) return res.status(404).send('No file available')\n\n /**\n * Where the redirect lands, decided BEFORE an attempt is counted\n * (AGL-2847).\n *\n * Everything above only guards the 302, so what the 302 points at is what\n * a buyer, and anyone they forward the `Location` to, keeps. The resolver\n * hands back a link that expires, or nothing: a CDN URL signed for\n * {@link PAID_DOWNLOAD_LINK_TTL_MS} for a PRIVATE file in this site's\n * library or its org's, a V4 signed Storage read for an object no media\n * document owns, or an author-typed hotlink unchanged. A raw Storage\n * download URL is never the answer, and a file that is still public is\n * refused, because its URL already works for anyone forever.\n *\n * Resolving first means a refusal costs the buyer no attempt: the limit\n * meters files that were handed out, not the seller's setup.\n *\n * `?download=1` rides inside the signed URL and makes the CDN answer with\n * `attachment`. The signature covers `(scope, mediaId, exp)` and not the\n * parameter, so it chooses a disposition for the one file signed and can\n * reach no other.\n */\n const delivery = await resolvePaidMediaDelivery({\n stored: file.url,\n hostId,\n ttlMs: PAID_DOWNLOAD_LINK_TTL_MS,\n cdnParams: [['download', '1']],\n io: createPaidMediaDeliveryIo({\n firestore,\n bucket: firebaseAdmin\n .app()\n .storage()\n .bucket(process.env['NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET']),\n }),\n })\n if (delivery.ok === false) {\n res.setHeader('Cache-Control', 'no-store')\n console.error(\n '[commerce/download] paid file not delivered',\n JSON.stringify({\n hostId,\n orderId,\n productId,\n file: fileIndex,\n refusal: delivery.refusal,\n }),\n )\n return delivery.refusal === 'not-private'\n ? res\n .status(409)\n .send('This file is not ready to download yet — contact the seller')\n : res.status(404).send('No file available')\n }\n\n // ATTEMPT ACCOUNTING, IN ONE TRANSACTION (AGL-2275).\n //\n // This was a read-then-write across an await, from a snapshot fetched\n // before the check, with the failure swallowed — so N parallel requests\n // all read the same count, all passed the limit, and all wrote\n // `attempts + 1`. The counter advanced by one for N downloads, which\n // makes `downloadLimit` unenforceable rather than merely approximate: a\n // buyer who fans out ten requests takes ten copies against a limit of one.\n // For a paid digital product that is the merchant's goods given away.\n //\n // It also wrote the WHOLE `downloadAttempts` map back from that stale\n // snapshot, so a concurrent download of a DIFFERENT product in the same\n // order had its increment erased.\n //\n // The half-finished conversion was visible in the file: `attemptsKey`\n // built the dotted path `downloadAttempts.{productId}` and the next line\n // threw it away with `void attemptsKey`. The dotted path is the right\n // instrument and needs `update()` — which merges only that nested key, and\n // whose \"document must exist\" precondition the transaction's own read has\n // just satisfied. `set(..., { merge: true })` cannot express it: a dotted\n // key there is a literal field name with a dot in it.\n const attemptsKey = `downloadAttempts.${productId}`\n const limit =\n product.downloadLimit != null\n ? Math.max(1, product.downloadLimit)\n : null\n const withinLimit = await firestore.runTransaction(async (transaction) => {\n const fresh = await transaction.get(orderRef)\n if (!fresh.exists) return false\n const attempts = Number(\n (fresh.get('downloadAttempts') ?? {})[productId] ?? 0,\n )\n if (limit != null && attempts >= limit) return false\n transaction.update(orderRef, { [attemptsKey]: attempts + 1 })\n return true\n })\n if (!withinLimit) {\n return res\n .status(429)\n .send('Download limit reached — contact the seller for help')\n }\n // Per-order and time-boxed: no cache may keep a redirect that carries a\n // signature past the link it opens.\n res.setHeader('Cache-Control', 'no-store')\n // The link dies within the hour, so a refund, an expired receipt token or\n // a spent limit also ends what a forwarded `Location` can fetch: the next\n // download has to come back through this route and its checks.\n return res.redirect(302, delivery.location)\n } catch (error) {\n console.error(error)\n return res.status(500).send('Download unavailable')\n }\n}\n"],"names":["CommerceModel","firebaseAdmin","PAID_DOWNLOAD_LINK_TTL_MS","createPaidMediaDeliveryIo","resolvePaidMediaDelivery","createHmac","timingSafeEqual","tokenSigningSecret","secret","process","env","TOKEN_SIGNING_SECRET","Error","DOWNLOAD_TOKEN_TTL_MS","signDownload","hostId","orderId","exp","update","digest","slice","mintDownloadToken","Date","now","verifyDownloadToken","token","dot","indexOf","Number","sig","isFinite","expected","a","Buffer","from","b","length","Uint8Array","downloadHandler","req","res","String","query","productId","fileIndex","Math","max","file","status","send","productSnapshot","product","firestore","app","hostRef","collection","doc","orderRef","orderSnapshot","Promise","all","get","exists","order","liftLegacyOrder","data","orderEntitlesProduct","orderContainsProduct","liftLegacyProduct","digitalFiles","url","delivery","stored","ttlMs","cdnParams","io","bucket","storage","ok","setHeader","console","error","JSON","stringify","refusal","attemptsKey","limit","downloadLimit","withinLimit","runTransaction","transaction","fresh","attempts","redirect","location"],"mappings":"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"}
@@ -0,0 +1,26 @@
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
+ */
17
+ import { type PluginApiHandler } from '@aglyn/aglyn/server';
18
+ /**
19
+ * Draft orders (AGL-287, Shopify parity): a manager builds an order in
20
+ * the console and sends the buyer a payment link. Creates the order doc
21
+ * (status `pending`, channel `draft`) with a sequential number, then a
22
+ * Stripe Checkout session on the merchant's connected account whose
23
+ * webhook completion flips the SAME doc to `paid`
24
+ * (metadata type `commerce-draft`).
25
+ */
26
+ export declare const draftOrderHandler: PluginApiHandler;