@hanzo/commerce 7.6.0 → 7.6.3

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 (258) hide show
  1. package/LICENSE.md +21 -0
  2. package/client.ts +155 -39
  3. package/dist/billing.d.ts +2 -0
  4. package/dist/billing.js +3 -0
  5. package/dist/billing.js.map +1 -0
  6. package/dist/client.d.ts +464 -0
  7. package/dist/client.js +424 -0
  8. package/dist/client.js.map +1 -0
  9. package/dist/components/Icons.d.ts +18 -0
  10. package/dist/components/Icons.js +19 -0
  11. package/dist/components/Icons.js.map +1 -0
  12. package/dist/components/add-to-cart-widget.d.ts +11 -0
  13. package/dist/components/add-to-cart-widget.js +85 -0
  14. package/dist/components/add-to-cart-widget.js.map +1 -0
  15. package/dist/components/buy/buy-card.d.ts +30 -0
  16. package/dist/components/buy/buy-card.js +109 -0
  17. package/dist/components/buy/buy-card.js.map +1 -0
  18. package/dist/components/buy/carousel-buy-card.d.ts +12 -0
  19. package/dist/components/buy/carousel-buy-card.js +94 -0
  20. package/dist/components/buy/carousel-buy-card.js.map +1 -0
  21. package/dist/components/buy/multi-family/all-variants-carousel.d.ts +4 -0
  22. package/dist/components/buy/multi-family/all-variants-carousel.js +115 -0
  23. package/dist/components/buy/multi-family/all-variants-carousel.js.map +1 -0
  24. package/dist/components/buy/multi-family/family-carousel/index.d.ts +4 -0
  25. package/dist/components/buy/multi-family/family-carousel/index.js +27 -0
  26. package/dist/components/buy/multi-family/family-carousel/index.js.map +1 -0
  27. package/dist/components/buy/multi-family/family-carousel/slide.d.ts +11 -0
  28. package/dist/components/buy/multi-family/family-carousel/slide.js +35 -0
  29. package/dist/components/buy/multi-family/family-carousel/slide.js.map +1 -0
  30. package/dist/components/buy/multi-family/family-carousel/state.d.ts +20 -0
  31. package/dist/components/buy/multi-family/family-carousel/state.js +59 -0
  32. package/dist/components/buy/multi-family/family-carousel/state.js.map +1 -0
  33. package/dist/components/buy/multi-family/index.d.ts +2 -0
  34. package/dist/components/buy/multi-family/index.js +3 -0
  35. package/dist/components/buy/multi-family/index.js.map +1 -0
  36. package/dist/components/buy/single-family-selector.d.ts +15 -0
  37. package/dist/components/buy/single-family-selector.js +28 -0
  38. package/dist/components/buy/single-family-selector.js.map +1 -0
  39. package/dist/components/buy/title-and-byline.d.ts +8 -0
  40. package/dist/components/buy/title-and-byline.js +7 -0
  41. package/dist/components/buy/title-and-byline.js.map +1 -0
  42. package/dist/components/cart/cart-panel/cart-line-item.d.ts +11 -0
  43. package/dist/components/cart/cart-panel/cart-line-item.js +25 -0
  44. package/dist/components/cart/cart-panel/cart-line-item.js.map +1 -0
  45. package/dist/components/cart/cart-panel/index.d.ts +19 -0
  46. package/dist/components/cart/cart-panel/index.js +65 -0
  47. package/dist/components/cart/cart-panel/index.js.map +1 -0
  48. package/dist/components/cart/cart-panel/promo-code.d.ts +4 -0
  49. package/dist/components/cart/cart-panel/promo-code.js +62 -0
  50. package/dist/components/cart/cart-panel/promo-code.js.map +1 -0
  51. package/dist/components/cart/cart-panel/total-area.d.ts +7 -0
  52. package/dist/components/cart/cart-panel/total-area.js +14 -0
  53. package/dist/components/cart/cart-panel/total-area.js.map +1 -0
  54. package/dist/components/checkout/payment-step-form/card-icon-row.d.ts +2 -0
  55. package/dist/components/checkout/payment-step-form/card-icon-row.js +14 -0
  56. package/dist/components/checkout/payment-step-form/card-icon-row.js.map +1 -0
  57. package/dist/components/checkout/payment-step-form/card-icons/amex.d.ts +4 -0
  58. package/dist/components/checkout/payment-step-form/card-icons/amex.js +6 -0
  59. package/dist/components/checkout/payment-step-form/card-icons/amex.js.map +1 -0
  60. package/dist/components/checkout/payment-step-form/card-icons/diners-club.d.ts +4 -0
  61. package/dist/components/checkout/payment-step-form/card-icons/diners-club.js +6 -0
  62. package/dist/components/checkout/payment-step-form/card-icons/diners-club.js.map +1 -0
  63. package/dist/components/checkout/payment-step-form/card-icons/discover.d.ts +4 -0
  64. package/dist/components/checkout/payment-step-form/card-icons/discover.js +6 -0
  65. package/dist/components/checkout/payment-step-form/card-icons/discover.js.map +1 -0
  66. package/dist/components/checkout/payment-step-form/card-icons/jcb.d.ts +4 -0
  67. package/dist/components/checkout/payment-step-form/card-icons/jcb.js +6 -0
  68. package/dist/components/checkout/payment-step-form/card-icons/jcb.js.map +1 -0
  69. package/dist/components/checkout/payment-step-form/card-icons/mastercard.d.ts +4 -0
  70. package/dist/components/checkout/payment-step-form/card-icons/mastercard.js +6 -0
  71. package/dist/components/checkout/payment-step-form/card-icons/mastercard.js.map +1 -0
  72. package/dist/components/checkout/payment-step-form/card-icons/visa.d.ts +4 -0
  73. package/dist/components/checkout/payment-step-form/card-icons/visa.js +6 -0
  74. package/dist/components/checkout/payment-step-form/card-icons/visa.js.map +1 -0
  75. package/dist/components/checkout/payment-step-form/cc-button.d.ts +3 -0
  76. package/dist/components/checkout/payment-step-form/cc-button.js +6 -0
  77. package/dist/components/checkout/payment-step-form/cc-button.js.map +1 -0
  78. package/dist/components/checkout/payment-step-form/contact-form.d.ts +5 -0
  79. package/dist/components/checkout/payment-step-form/contact-form.js +6 -0
  80. package/dist/components/checkout/payment-step-form/contact-form.js.map +1 -0
  81. package/dist/components/checkout/payment-step-form/crypto-icons/btc.d.ts +4 -0
  82. package/dist/components/checkout/payment-step-form/crypto-icons/btc.js +6 -0
  83. package/dist/components/checkout/payment-step-form/crypto-icons/btc.js.map +1 -0
  84. package/dist/components/checkout/payment-step-form/crypto-icons/eth.d.ts +4 -0
  85. package/dist/components/checkout/payment-step-form/crypto-icons/eth.js +6 -0
  86. package/dist/components/checkout/payment-step-form/crypto-icons/eth.js.map +1 -0
  87. package/dist/components/checkout/payment-step-form/crypto-icons/usdt.d.ts +4 -0
  88. package/dist/components/checkout/payment-step-form/crypto-icons/usdt.js +6 -0
  89. package/dist/components/checkout/payment-step-form/crypto-icons/usdt.js.map +1 -0
  90. package/dist/components/checkout/payment-step-form/index.d.ts +4 -0
  91. package/dist/components/checkout/payment-step-form/index.js +66 -0
  92. package/dist/components/checkout/payment-step-form/index.js.map +1 -0
  93. package/dist/components/checkout/payment-step-form/methods/bank-transfer.d.ts +4 -0
  94. package/dist/components/checkout/payment-step-form/methods/bank-transfer.js +24 -0
  95. package/dist/components/checkout/payment-step-form/methods/bank-transfer.js.map +1 -0
  96. package/dist/components/checkout/payment-step-form/methods/card.d.ts +4 -0
  97. package/dist/components/checkout/payment-step-form/methods/card.js +160 -0
  98. package/dist/components/checkout/payment-step-form/methods/card.js.map +1 -0
  99. package/dist/components/checkout/payment-step-form/methods/crypto.d.ts +9 -0
  100. package/dist/components/checkout/payment-step-form/methods/crypto.js +137 -0
  101. package/dist/components/checkout/payment-step-form/methods/crypto.js.map +1 -0
  102. package/dist/components/checkout/payment-step-form/methods/index.d.ts +6 -0
  103. package/dist/components/checkout/payment-step-form/methods/index.js +21 -0
  104. package/dist/components/checkout/payment-step-form/methods/index.js.map +1 -0
  105. package/dist/components/checkout/shipping-step-form.d.ts +3 -0
  106. package/dist/components/checkout/shipping-step-form.js +53 -0
  107. package/dist/components/checkout/shipping-step-form.js.map +1 -0
  108. package/dist/components/index.d.ts +8 -0
  109. package/dist/components/index.js +9 -0
  110. package/dist/components/index.js.map +1 -0
  111. package/dist/components/item/product-card.d.ts +7 -0
  112. package/dist/components/item/product-card.js +9 -0
  113. package/dist/components/item/product-card.js.map +1 -0
  114. package/dist/components/item-selector/button.d.ts +4 -0
  115. package/dist/components/item-selector/button.js +47 -0
  116. package/dist/components/item-selector/button.js.map +1 -0
  117. package/dist/components/item-selector/carousel/index.d.ts +12 -0
  118. package/dist/components/item-selector/carousel/index.js +74 -0
  119. package/dist/components/item-selector/carousel/index.js.map +1 -0
  120. package/dist/components/item-selector/carousel/slider.d.ts +8 -0
  121. package/dist/components/item-selector/carousel/slider.js +12 -0
  122. package/dist/components/item-selector/carousel/slider.js.map +1 -0
  123. package/dist/components/item-selector/index.d.ts +2 -0
  124. package/dist/components/item-selector/index.js +3 -0
  125. package/dist/components/item-selector/index.js.map +1 -0
  126. package/dist/components/item-selector/quantity-indicator.d.ts +9 -0
  127. package/dist/components/item-selector/quantity-indicator.js +16 -0
  128. package/dist/components/item-selector/quantity-indicator.js.map +1 -0
  129. package/dist/components/node-tabs/index.d.ts +14 -0
  130. package/dist/components/node-tabs/index.js +42 -0
  131. package/dist/components/node-tabs/index.js.map +1 -0
  132. package/dist/components/node-tabs/node-image.d.ts +6 -0
  133. package/dist/components/node-tabs/node-image.js +13 -0
  134. package/dist/components/node-tabs/node-image.js.map +1 -0
  135. package/dist/index.d.ts +9 -0
  136. package/dist/index.js +9 -0
  137. package/dist/index.js.map +1 -0
  138. package/dist/metering.d.ts +162 -0
  139. package/dist/metering.js +204 -0
  140. package/dist/metering.js.map +1 -0
  141. package/dist/metering.test.d.ts +13 -0
  142. package/dist/metering.test.js +181 -0
  143. package/dist/metering.test.js.map +1 -0
  144. package/dist/service/context.d.ts +8 -0
  145. package/dist/service/context.js +19 -0
  146. package/dist/service/context.js.map +1 -0
  147. package/dist/service/debug.d.ts +10 -0
  148. package/dist/service/debug.js +30 -0
  149. package/dist/service/debug.js.map +1 -0
  150. package/dist/service/impls/standalone/actual-line-item.d.ts +40 -0
  151. package/dist/service/impls/standalone/actual-line-item.js +84 -0
  152. package/dist/service/impls/standalone/actual-line-item.js.map +1 -0
  153. package/dist/service/impls/standalone/get-instance.d.ts +2 -0
  154. package/dist/service/impls/standalone/get-instance.js +39 -0
  155. package/dist/service/impls/standalone/get-instance.js.map +1 -0
  156. package/dist/service/impls/standalone/index.d.ts +67 -0
  157. package/dist/service/impls/standalone/index.js +416 -0
  158. package/dist/service/impls/standalone/index.js.map +1 -0
  159. package/dist/service/impls/standalone/order/firebase.d.ts +6 -0
  160. package/dist/service/impls/standalone/order/firebase.js +6 -0
  161. package/dist/service/impls/standalone/order/firebase.js.map +1 -0
  162. package/dist/service/impls/standalone/order/index.d.ts +30 -0
  163. package/dist/service/impls/standalone/order/index.js +21 -0
  164. package/dist/service/impls/standalone/order/index.js.map +1 -0
  165. package/dist/service/impls/standalone/persistence.d.ts +4 -0
  166. package/dist/service/impls/standalone/persistence.js +22 -0
  167. package/dist/service/impls/standalone/persistence.js.map +1 -0
  168. package/dist/service/path-utils.d.ts +7 -0
  169. package/dist/service/path-utils.js +16 -0
  170. package/dist/service/path-utils.js.map +1 -0
  171. package/dist/service/sep.d.ts +6 -0
  172. package/dist/service/sep.js +6 -0
  173. package/dist/service/sep.js.map +1 -0
  174. package/dist/types/category-node.d.ts +36 -0
  175. package/dist/types/category-node.js +2 -0
  176. package/dist/types/category-node.js.map +1 -0
  177. package/dist/types/checkout.d.ts +33 -0
  178. package/dist/types/checkout.js +2 -0
  179. package/dist/types/checkout.js.map +1 -0
  180. package/dist/types/commerce-config.d.ts +11 -0
  181. package/dist/types/commerce-config.js +2 -0
  182. package/dist/types/commerce-config.js.map +1 -0
  183. package/dist/types/commerce-service.d.ts +109 -0
  184. package/dist/types/commerce-service.js +2 -0
  185. package/dist/types/commerce-service.js.map +1 -0
  186. package/dist/types/family.d.ts +16 -0
  187. package/dist/types/family.js +2 -0
  188. package/dist/types/family.js.map +1 -0
  189. package/dist/types/index.d.ts +13 -0
  190. package/dist/types/index.js +8 -0
  191. package/dist/types/index.js.map +1 -0
  192. package/dist/types/item-selector.d.ts +72 -0
  193. package/dist/types/item-selector.js +2 -0
  194. package/dist/types/item-selector.js.map +1 -0
  195. package/dist/types/line-item.d.ts +14 -0
  196. package/dist/types/line-item.js +2 -0
  197. package/dist/types/line-item.js.map +1 -0
  198. package/dist/types/multi-family-selector-props.d.ts +16 -0
  199. package/dist/types/multi-family-selector-props.js +2 -0
  200. package/dist/types/multi-family-selector-props.js.map +1 -0
  201. package/dist/types/product.d.ts +15 -0
  202. package/dist/types/product.js +2 -0
  203. package/dist/types/product.js.map +1 -0
  204. package/dist/types/promo.d.ts +7 -0
  205. package/dist/types/promo.js +2 -0
  206. package/dist/types/promo.js.map +1 -0
  207. package/dist/types/selection-ui-specifier.d.ts +40 -0
  208. package/dist/types/selection-ui-specifier.js +2 -0
  209. package/dist/types/selection-ui-specifier.js.map +1 -0
  210. package/dist/types/string-mutator.d.ts +9 -0
  211. package/dist/types/string-mutator.js +2 -0
  212. package/dist/types/string-mutator.js.map +1 -0
  213. package/dist/types/token-separators.d.ts +6 -0
  214. package/dist/types/token-separators.js +2 -0
  215. package/dist/types/token-separators.js.map +1 -0
  216. package/dist/util/analytics.d.ts +9 -0
  217. package/dist/util/analytics.js +10 -0
  218. package/dist/util/analytics.js.map +1 -0
  219. package/dist/util/countries.d.ts +7 -0
  220. package/dist/util/countries.js +197 -0
  221. package/dist/util/countries.js.map +1 -0
  222. package/dist/util/error.d.ts +1 -0
  223. package/dist/util/error.js +22 -0
  224. package/dist/util/error.js.map +1 -0
  225. package/dist/util/index.d.ts +15 -0
  226. package/dist/util/index.js +54 -0
  227. package/dist/util/index.js.map +1 -0
  228. package/dist/util/item-selector-options-accessor.d.ts +3 -0
  229. package/dist/util/item-selector-options-accessor.js +27 -0
  230. package/dist/util/item-selector-options-accessor.js.map +1 -0
  231. package/dist/util/line-item-ref.d.ts +8 -0
  232. package/dist/util/line-item-ref.js +15 -0
  233. package/dist/util/line-item-ref.js.map +1 -0
  234. package/dist/util/multi-family-selector-options-accessor.d.ts +3 -0
  235. package/dist/util/multi-family-selector-options-accessor.js +11 -0
  236. package/dist/util/multi-family-selector-options-accessor.js.map +1 -0
  237. package/dist/util/obs-string-mutator.d.ts +8 -0
  238. package/dist/util/obs-string-mutator.js +15 -0
  239. package/dist/util/obs-string-mutator.js.map +1 -0
  240. package/dist/util/product-media-accessor.d.ts +29 -0
  241. package/dist/util/product-media-accessor.js +22 -0
  242. package/dist/util/product-media-accessor.js.map +1 -0
  243. package/dist/util/promo-codes.d.ts +3 -0
  244. package/dist/util/promo-codes.js +100 -0
  245. package/dist/util/promo-codes.js.map +1 -0
  246. package/dist/util/selection-ui-specifiers.d.ts +3 -0
  247. package/dist/util/selection-ui-specifiers.js +24 -0
  248. package/dist/util/selection-ui-specifiers.js.map +1 -0
  249. package/dist/util/square-payment.d.ts +7 -0
  250. package/dist/util/square-payment.js +37 -0
  251. package/dist/util/square-payment.js.map +1 -0
  252. package/dist/util/use-sync-sku-param-w-current-item.d.ts +2 -0
  253. package/dist/util/use-sync-sku-param-w-current-item.js +61 -0
  254. package/dist/util/use-sync-sku-param-w-current-item.js.map +1 -0
  255. package/index.ts +18 -1
  256. package/metering.test.ts +199 -0
  257. package/metering.ts +289 -0
  258. package/package.json +18 -16
@@ -0,0 +1,100 @@
1
+ const referallCodeDiscountedSkus = [
2
+ 'LXM-CR-B-ABT', 'LXM-CR-B-GM', 'LXM-CR-F-CC', 'LXM-CR-F-IC', 'LXM-CR-E-24G', 'LXM-CR-E-SS', 'LXM-CR-S-RT', // credit cards
3
+ 'LXM-VL-GN', 'LXM-VL-VL', 'LXM-VL-MI', 'LXM-VL-NA', // validators
4
+ 'LXM-PS-PS', // pass
5
+ 'LXM-AG-B-1_OZ', // silver
6
+ ];
7
+ const PROMO_CODES = [
8
+ {
9
+ type: 'percent',
10
+ code: 'ANGEL',
11
+ value: 22
12
+ },
13
+ {
14
+ type: 'percent',
15
+ code: 'NASSER',
16
+ value: 11.11
17
+ },
18
+ {
19
+ type: 'percent',
20
+ code: 'ROSEY',
21
+ value: 20
22
+ },
23
+ {
24
+ type: 'percent',
25
+ code: 'IYKYK',
26
+ value: 11.1
27
+ },
28
+ {
29
+ type: 'percent',
30
+ code: 'GENESIS',
31
+ value: 99,
32
+ skus: ['LXM-PS-PS']
33
+ },
34
+ {
35
+ type: 'percent',
36
+ code: 'ANTJE',
37
+ value: 33.3,
38
+ skus: referallCodeDiscountedSkus
39
+ },
40
+ {
41
+ type: 'percent',
42
+ code: 'ASHLEY',
43
+ value: 33.3,
44
+ skus: referallCodeDiscountedSkus
45
+ },
46
+ {
47
+ type: 'percent',
48
+ code: 'CALE',
49
+ value: 33.3,
50
+ skus: referallCodeDiscountedSkus
51
+ },
52
+ {
53
+ type: 'percent',
54
+ code: 'DARA',
55
+ value: 33.3,
56
+ skus: referallCodeDiscountedSkus
57
+ },
58
+ {
59
+ type: 'percent',
60
+ code: 'KENJI',
61
+ value: 33.3,
62
+ skus: referallCodeDiscountedSkus
63
+ },
64
+ {
65
+ type: 'percent',
66
+ code: 'MAJOR',
67
+ value: 33.3,
68
+ skus: referallCodeDiscountedSkus
69
+ },
70
+ {
71
+ type: 'percent',
72
+ code: 'CYRUS',
73
+ value: 33.3,
74
+ skus: referallCodeDiscountedSkus
75
+ },
76
+ {
77
+ type: 'percent',
78
+ code: 'STOBIE',
79
+ value: 33.3,
80
+ skus: referallCodeDiscountedSkus
81
+ },
82
+ {
83
+ type: 'percent',
84
+ code: 'SKYLER',
85
+ value: 33.3,
86
+ skus: referallCodeDiscountedSkus
87
+ },
88
+ {
89
+ type: 'percent',
90
+ code: 'ZEEKAY',
91
+ value: 33.3,
92
+ skus: referallCodeDiscountedSkus
93
+ }
94
+ ];
95
+ // TODO: implement a real API call
96
+ const getPromoFromApi = async (code) => {
97
+ return PROMO_CODES.find(promo => promo.code === code);
98
+ };
99
+ export { getPromoFromApi as default };
100
+ //# sourceMappingURL=promo-codes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"promo-codes.js","sourceRoot":"","sources":["../../util/promo-codes.ts"],"names":[],"mappings":"AAEA,MAAM,0BAA0B,GAAG;IACjC,cAAc,EAAE,aAAa,EAAE,aAAa,EAAE,aAAa,EAAE,cAAc,EAAE,aAAa,EAAE,aAAa,EAAE,eAAe;IAC1H,WAAW,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,EAAE,aAAa;IACjE,WAAW,EAAG,OAAO;IACrB,eAAe,EAAE,SAAS;CAC3B,CAAA;AAED,MAAM,WAAW,GAAY;IAC3B;QACE,IAAI,EAAE,SAAS;QACf,IAAI,EAAE,OAAO;QACb,KAAK,EAAE,EAAE;KACV;IACD;QACE,IAAI,EAAE,SAAS;QACf,IAAI,EAAE,QAAQ;QACd,KAAK,EAAE,KAAK;KACb;IACD;QACE,IAAI,EAAE,SAAS;QACf,IAAI,EAAE,OAAO;QACb,KAAK,EAAE,EAAE;KACV;IACD;QACE,IAAI,EAAE,SAAS;QACf,IAAI,EAAE,OAAO;QACb,KAAK,EAAE,IAAI;KACZ;IACD;QACE,IAAI,EAAE,SAAS;QACf,IAAI,EAAE,SAAS;QACf,KAAK,EAAE,EAAE;QACT,IAAI,EAAE,CAAC,WAAW,CAAC;KACpB;IACD;QACE,IAAI,EAAE,SAAS;QACf,IAAI,EAAE,OAAO;QACb,KAAK,EAAE,IAAI;QACX,IAAI,EAAE,0BAA0B;KACjC;IACD;QACE,IAAI,EAAE,SAAS;QACf,IAAI,EAAE,QAAQ;QACd,KAAK,EAAE,IAAI;QACX,IAAI,EAAE,0BAA0B;KACjC;IACD;QACE,IAAI,EAAE,SAAS;QACf,IAAI,EAAE,MAAM;QACZ,KAAK,EAAE,IAAI;QACX,IAAI,EAAE,0BAA0B;KACjC;IACD;QACE,IAAI,EAAE,SAAS;QACf,IAAI,EAAE,MAAM;QACZ,KAAK,EAAE,IAAI;QACX,IAAI,EAAE,0BAA0B;KACjC;IACD;QACE,IAAI,EAAE,SAAS;QACf,IAAI,EAAE,OAAO;QACb,KAAK,EAAE,IAAI;QACX,IAAI,EAAE,0BAA0B;KACjC;IACD;QACE,IAAI,EAAE,SAAS;QACf,IAAI,EAAE,OAAO;QACb,KAAK,EAAE,IAAI;QACX,IAAI,EAAE,0BAA0B;KACjC;IACD;QACE,IAAI,EAAE,SAAS;QACf,IAAI,EAAE,OAAO;QACb,KAAK,EAAE,IAAI;QACX,IAAI,EAAE,0BAA0B;KACjC;IACD;QACE,IAAI,EAAE,SAAS;QACf,IAAI,EAAE,QAAQ;QACd,KAAK,EAAE,IAAI;QACX,IAAI,EAAE,0BAA0B;KACjC;IACD;QACE,IAAI,EAAE,SAAS;QACf,IAAI,EAAE,QAAQ;QACd,KAAK,EAAE,IAAI;QACX,IAAI,EAAE,0BAA0B;KACjC;IACD;QACE,IAAI,EAAE,SAAS;QACf,IAAI,EAAE,QAAQ;QACd,KAAK,EAAE,IAAI;QACX,IAAI,EAAE,0BAA0B;KACjC;CACF,CAAA;AAED,kCAAkC;AAClC,MAAM,eAAe,GAAG,KAAK,EAAE,IAAY,EAAE,EAAE;IAC7C,OAAO,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,CAAA;AACvD,CAAC,CAAA;AAED,OAAO,EACL,eAAe,IAAI,OAAO,EAC3B,CAAA"}
@@ -0,0 +1,3 @@
1
+ import type { SelectionUISpecifier } from '../types';
2
+ export declare const initSelectionUI: (v: Record<string, SelectionUISpecifier>) => void;
3
+ export declare const getSelectionUISpecifier: (skuPath: string) => SelectionUISpecifier;
@@ -0,0 +1,24 @@
1
+ import sep from '../service/sep';
2
+ const DEFAULT = {
3
+ multiFamily: {
4
+ type: 'all-variants-carousel'
5
+ },
6
+ singleFamily: { type: 'carousel' }
7
+ };
8
+ const map = new Map();
9
+ // key: first two tokens of path
10
+ export const initSelectionUI = (v) => {
11
+ // Might have multiple calls client and server side
12
+ if (map.size > 0) {
13
+ map.clear();
14
+ }
15
+ for (let [key, value] of Object.entries(v)) {
16
+ map.set(key, value);
17
+ }
18
+ };
19
+ export const getSelectionUISpecifier = (skuPath) => {
20
+ const key = skuPath.split(sep.tok).slice(0, 2).join(sep.tok);
21
+ const result = map.get(key);
22
+ return result ?? DEFAULT;
23
+ };
24
+ //# sourceMappingURL=selection-ui-specifiers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selection-ui-specifiers.js","sourceRoot":"","sources":["../../util/selection-ui-specifiers.ts"],"names":[],"mappings":"AACA,OAAO,GAAG,MAAM,gBAAgB,CAAA;AAEhC,MAAM,OAAO,GAAG;IACd,WAAW,EAAE;QACX,IAAI,EAAE,uBAAuB;KAC9B;IACD,YAAY,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE;CACJ,CAAA;AAEhC,MAAM,GAAG,GAAG,IAAI,GAAG,EAAgC,CAAA;AAEjD,gCAAgC;AAClC,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,CAAuC,EAAE,EAAE;IAEvE,mDAAmD;IACrD,IAAI,GAAG,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;QACjB,GAAG,CAAC,KAAK,EAAE,CAAA;IACb,CAAC;IAED,KAAK,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3C,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;IACrB,CAAC;AACH,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,OAAe,EAAwB,EAAE;IAC/E,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IAC5D,MAAM,MAAM,GAAG,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IAC3B,OAAO,MAAM,IAAI,OAAO,CAAA;AAC1B,CAAC,CAAA"}
@@ -0,0 +1,7 @@
1
+ declare global {
2
+ interface BigInt {
3
+ toJSON(): string;
4
+ }
5
+ }
6
+ declare const processPayment: (sourceId: string, amount: number, verificationToken: string) => Promise<any>;
7
+ export { processPayment as default };
@@ -0,0 +1,37 @@
1
+ // Payment processing - requires API endpoint
2
+ // For static builds, this will call an external payment API
3
+ BigInt.prototype.toJSON = function () { return this.toString(); };
4
+ const processPayment = async (sourceId, amount, verificationToken) => {
5
+ // For static builds, payments should be processed via an API endpoint
6
+ // Check if we're in a browser environment
7
+ if (typeof window !== 'undefined') {
8
+ // Client-side: call the payment API
9
+ try {
10
+ const response = await fetch('/api/payment', {
11
+ method: 'POST',
12
+ headers: {
13
+ 'Content-Type': 'application/json',
14
+ },
15
+ body: JSON.stringify({
16
+ sourceId,
17
+ amount,
18
+ verificationToken
19
+ })
20
+ });
21
+ if (!response.ok) {
22
+ console.error('Payment API error:', response.statusText);
23
+ return null;
24
+ }
25
+ return await response.json();
26
+ }
27
+ catch (error) {
28
+ console.error('Error processing payment:', error);
29
+ return null;
30
+ }
31
+ }
32
+ // Server-side (for SSR builds only, not static export)
33
+ console.warn('Server-side payment processing not available in static export');
34
+ return null;
35
+ };
36
+ export { processPayment as default };
37
+ //# sourceMappingURL=square-payment.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"square-payment.js","sourceRoot":"","sources":["../../util/square-payment.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,4DAA4D;AAS5D,MAAM,CAAC,SAAS,CAAC,MAAM,GAAG,cAAa,OAAO,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAA;AAEhE,MAAM,cAAc,GAAG,KAAK,EAAE,QAAgB,EAAE,MAAc,EAAE,iBAAyB,EAAE,EAAE;IAC3F,sEAAsE;IACtE,0CAA0C;IAC1C,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;QAClC,oCAAoC;QACpC,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,cAAc,EAAE;gBAC3C,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE;oBACP,cAAc,EAAE,kBAAkB;iBACnC;gBACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;oBACnB,QAAQ;oBACR,MAAM;oBACN,iBAAiB;iBAClB,CAAC;aACH,CAAC,CAAA;YAEF,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBACjB,OAAO,CAAC,KAAK,CAAC,oBAAoB,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAA;gBACxD,OAAO,IAAI,CAAA;YACb,CAAC;YAED,OAAO,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAA;QAC9B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,2BAA2B,EAAE,KAAK,CAAC,CAAA;YACjD,OAAO,IAAI,CAAA;QACb,CAAC;IACH,CAAC;IAED,uDAAuD;IACvD,OAAO,CAAC,IAAI,CAAC,+DAA+D,CAAC,CAAA;IAC7E,OAAO,IAAI,CAAA;AACb,CAAC,CAAA;AAED,OAAO,EACL,cAAc,IAAI,OAAO,EAC1B,CAAA"}
@@ -0,0 +1,2 @@
1
+ declare const useSyncSkuParamWithCurrentItem: (familyLevel: number, setMessage: (m: string | undefined) => void, setLoading?: (l: boolean) => void) => void;
2
+ export default useSyncSkuParamWithCurrentItem;
@@ -0,0 +1,61 @@
1
+ "use client";
2
+ import { useEffect } from 'react';
3
+ import { reaction } from 'mobx';
4
+ import { useQueryState, parseAsString, parseAsBoolean, } from 'next-usequerystate';
5
+ import { useCommerce } from '../service/context';
6
+ import sep from '../service/sep';
7
+ const PLEASE_SELECT_FACETS = 'Please select an option from each group.';
8
+ const useSyncSkuParamWithCurrentItem = (familyLevel, setMessage, setLoading) => {
9
+ const cmmc = useCommerce();
10
+ const [skuParam, setSkuParam] = useQueryState('sku', parseAsString.withDefault('').withOptions({ clearOnDefault: true }));
11
+ const [addParam, setAddParam] = useQueryState('add', parseAsBoolean.withDefault(false).withOptions({ clearOnDefault: true }));
12
+ useEffect(() => {
13
+ return reaction(() => ({
14
+ specifiedCat: cmmc.selectedFamilies.length === 1 ? cmmc.selectedFamilies[0] : undefined,
15
+ currentItem: cmmc.currentItem
16
+ }), ({ specifiedCat, currentItem }) => {
17
+ //console.log("REACTION: " + `CAT ID: ${family?.id} SKU: ${item?.sku}`)
18
+ if (currentItem) {
19
+ // if we set the currentItem w sku, then user selects other facets
20
+ if (specifiedCat && currentItem.familyId != specifiedCat.id) {
21
+ cmmc.setCurrentItem(specifiedCat.products[0].sku);
22
+ }
23
+ setSkuParam(cmmc.currentItem.sku);
24
+ }
25
+ else if (specifiedCat) {
26
+ cmmc.setCurrentItem(specifiedCat.products[0].sku);
27
+ setSkuParam(cmmc.currentItem.sku);
28
+ }
29
+ });
30
+ }, []);
31
+ useEffect(() => {
32
+ const setCurrentFamilyFromSku = (sku) => {
33
+ const toks = sku.split(sep.tok);
34
+ const fv = {};
35
+ // TODO: confirm that extra trailing nonsense tokens won't break selectPaths()
36
+ for (let i = 1; i < familyLevel; i++) {
37
+ if (i in toks) {
38
+ fv[i] = [toks[i]];
39
+ }
40
+ }
41
+ cmmc.selectPaths(fv);
42
+ };
43
+ // setCI returns true if it's a recognized sku
44
+ if (skuParam && cmmc.setCurrentItem(skuParam)) {
45
+ if (addParam) {
46
+ if (cmmc.currentItem.quantity === 0) {
47
+ cmmc.currentItem.increment();
48
+ }
49
+ setAddParam(false);
50
+ }
51
+ setCurrentFamilyFromSku(skuParam);
52
+ setMessage(undefined);
53
+ }
54
+ else {
55
+ setMessage(PLEASE_SELECT_FACETS);
56
+ }
57
+ setLoading && setLoading(false);
58
+ }, [skuParam]);
59
+ };
60
+ export default useSyncSkuParamWithCurrentItem;
61
+ //# sourceMappingURL=use-sync-sku-param-w-current-item.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-sync-sku-param-w-current-item.js","sourceRoot":"","sources":["../../util/use-sync-sku-param-w-current-item.ts"],"names":[],"mappings":"AAAA,YAAY,CAAA;AACZ,OAAO,EAAG,SAAS,EAAE,MAAM,OAAO,CAAA;AAClC,OAAO,EAAE,QAAQ,EAAE,MAAM,MAAM,CAAA;AAE/B,OAAO,EACL,aAAa,EACb,aAAa,EACb,cAAc,GACf,MAAM,oBAAoB,CAAA;AAE3B,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAEhD,OAAO,GAAG,MAAM,gBAAgB,CAAA;AAEhC,MAAM,oBAAoB,GAAG,0CAA0C,CAAA;AAEvE,MAAM,8BAA8B,GAAG,CACrC,WAAmB,EACnB,UAA2C,EAC3C,UAAiC,EACjC,EAAE;IAEF,MAAM,IAAI,GAAG,WAAW,EAAE,CAAA;IAE1B,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,aAAa,CAAC,KAAK,EACjD,aAAa,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,WAAW,CAAC,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC,CACpE,CAAA;IAED,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,aAAa,CAAC,KAAK,EACjD,cAAc,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,WAAW,CAAC,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC,CACxE,CAAA;IAED,SAAS,CAAC,GAAG,EAAE;QAEb,OAAO,QAAQ,CAAC,GAAG,EAAE,CAAC,CAAC;YACrB,YAAY,EAAE,IAAI,CAAC,gBAAgB,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS;YACvF,WAAW,EAAE,IAAI,CAAC,WAAW;SAC9B,CAAC,EACF,CAAC,EAAC,YAAY,EAAE,WAAW,EAAC,EAAE,EAAE;YAC9B,uEAAuE;YACvE,IAAI,WAAW,EAAE,CAAC;gBACd,kEAAkE;gBACpE,IAAI,YAAY,IAAI,WAAW,CAAC,QAAQ,IAAI,YAAY,CAAC,EAAE,EAAG,CAAC;oBAC7D,IAAI,CAAC,cAAc,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;gBACnD,CAAC;gBACD,WAAW,CAAC,IAAI,CAAC,WAAY,CAAC,GAAG,CAAC,CAAA;YACpC,CAAC;iBACI,IAAI,YAAY,EAAE,CAAC;gBACtB,IAAI,CAAC,cAAc,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;gBACjD,WAAW,CAAC,IAAI,CAAC,WAAY,CAAC,GAAG,CAAC,CAAA;YACpC,CAAC;QACH,CAAC,CAAC,CAAA;IACJ,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,SAAS,CAAC,GAAG,EAAE;QAEb,MAAM,uBAAuB,GAAG,CAAC,GAAW,EAAE,EAAE;YAC9C,MAAM,IAAI,GAAa,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;YACzC,MAAM,EAAE,GAAkB,EAAE,CAAA;YAC1B,8EAA8E;YAChF,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,WAAW,EAAE,CAAC,EAAE,EAAE,CAAC;gBACrC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC;oBACd,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAA;gBACnB,CAAC;YACH,CAAC;YACD,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC,CAAA;QACtB,CAAC,CAAA;QAEC,8CAA8C;QAChD,IAAI,QAAQ,IAAI,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC9C,IAAI,QAAQ,EAAE,CAAC;gBACb,IAAI,IAAI,CAAC,WAAY,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC;oBACrC,IAAI,CAAC,WAAY,CAAC,SAAS,EAAE,CAAA;gBAC/B,CAAC;gBACD,WAAW,CAAC,KAAK,CAAC,CAAA;YACpB,CAAC;YACD,uBAAuB,CAAC,QAAQ,CAAC,CAAA;YACjC,UAAU,CAAC,SAAS,CAAC,CAAA;QACvB,CAAC;aACI,CAAC;YACJ,UAAU,CAAC,oBAAoB,CAAC,CAAA;QAClC,CAAC;QAED,UAAU,IAAI,UAAU,CAAC,KAAK,CAAC,CAAA;IACjC,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAA;AAChB,CAAC,CAAA;AAED,eAAe,8BAA8B,CAAA"}
package/index.ts CHANGED
@@ -17,6 +17,7 @@ export { Commerce, CommerceApiError, hanzoCommerce } from './client'
17
17
  export type {
18
18
  CommerceClientConfig,
19
19
  Balance,
20
+ TierResponse,
20
21
  Transaction,
21
22
  Subscription,
22
23
  Plan,
@@ -37,4 +38,20 @@ export type {
37
38
  Referrer,
38
39
  Affiliate,
39
40
  CreditGrant,
40
- } from './client'
41
+ } from './client'
42
+
43
+ // Server-side usage metering (the one way every product meters to commerce)
44
+ export {
45
+ Metering,
46
+ DEFAULT_COMMERCE_URL,
47
+ identityFromHeaders,
48
+ HEADER_USER_ID,
49
+ HEADER_ORG_ID,
50
+ } from './metering'
51
+ export type {
52
+ MeteringConfig,
53
+ MeteringIdentity,
54
+ UsageEvent,
55
+ RecordResult,
56
+ AuthDecision,
57
+ } from './metering'
@@ -0,0 +1,199 @@
1
+ /**
2
+ * Contract tests for @hanzo/commerce/metering.
3
+ *
4
+ * Self-contained: mocks global fetch and asserts the exact commerce wire
5
+ * contract, mirroring github.com/hanzoai/go-sdk/metering's tests so the two
6
+ * clients gate on the identical balance source. Runnable standalone with:
7
+ *
8
+ * npx -y -p typescript@5.9.3 -p tsx tsx pkgs/commerce/metering.test.ts
9
+ *
10
+ * It uses no test-runner dependency (the package ships none); a tiny harness
11
+ * below prints PASS/FAIL and exits non-zero on failure.
12
+ */
13
+
14
+ import { Metering, identityFromHeaders } from './metering'
15
+
16
+ // ---- tiny assert harness ----
17
+ let failures = 0
18
+ let count = 0
19
+ function test(name: string, fn: () => Promise<void> | void) {
20
+ count++
21
+ Promise.resolve()
22
+ .then(fn)
23
+ .then(() => console.log(`ok - ${name}`))
24
+ .catch((err) => {
25
+ failures++
26
+ console.error(`FAIL - ${name}\n ${err?.message ?? err}`)
27
+ })
28
+ }
29
+ function eq(actual: unknown, expected: unknown, msg = '') {
30
+ if (actual !== expected) throw new Error(`${msg} expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`)
31
+ }
32
+ function ok(cond: boolean, msg = '') {
33
+ if (!cond) throw new Error(msg || 'expected true')
34
+ }
35
+
36
+ // ---- fetch mock ----
37
+ type Captured = { url: string; method: string; headers: Record<string, string>; body: unknown }
38
+ function mockFetch(status: number, json: unknown): { restore: () => void; last: () => Captured | null } {
39
+ const original = globalThis.fetch
40
+ let last: Captured | null = null
41
+ globalThis.fetch = (async (input: RequestInfo | URL, init?: RequestInit) => {
42
+ const hdrs: Record<string, string> = {}
43
+ const h = init?.headers as Record<string, string> | undefined
44
+ if (h) for (const [k, v] of Object.entries(h)) hdrs[k.toLowerCase()] = v as string
45
+ last = {
46
+ url: input.toString(),
47
+ method: init?.method ?? 'GET',
48
+ headers: hdrs,
49
+ body: init?.body ? JSON.parse(init.body as string) : undefined,
50
+ }
51
+ return {
52
+ ok: status >= 200 && status < 300,
53
+ status,
54
+ statusText: `HTTP ${status}`,
55
+ json: async () => json,
56
+ text: async () => JSON.stringify(json),
57
+ } as Response
58
+ }) as typeof fetch
59
+ return { restore: () => { globalThis.fetch = original }, last: () => last }
60
+ }
61
+
62
+ const BASE = 'http://commerce.test'
63
+
64
+ test('authorize allows when available > 0 and hits the canonical contract', async () => {
65
+ const fx = mockFetch(200, { user: 'hanzo/alice', currency: 'usd', balance: 5000, holds: 0, available: 5000 })
66
+ const meter = new Metering({ baseUrl: BASE, token: 'svc-token', org: 'hanzo' })
67
+ const d = await meter.authorize({ user: 'hanzo/alice' })
68
+ fx.restore()
69
+
70
+ ok(d.allowed, 'should allow')
71
+ eq(d.available, 5000, 'available')
72
+ const c = fx.last()!
73
+ eq(c.method, 'GET', 'method')
74
+ ok(c.url.startsWith(`${BASE}/v1/billing/balance?`), `url=${c.url}`)
75
+ ok(c.url.includes('user=hanzo%2Falice'), `url must url-encode user: ${c.url}`)
76
+ ok(c.url.includes('currency=usd'), `url must carry currency: ${c.url}`)
77
+ eq(c.headers['authorization'], 'Bearer svc-token', 'bearer')
78
+ eq(c.headers['x-iam-org-id'], 'hanzo', 'org header')
79
+ })
80
+
81
+ test('authorize denies (insufficient) when available == 0', async () => {
82
+ const fx = mockFetch(200, { available: 0 })
83
+ const meter = new Metering({ baseUrl: BASE, token: 't', org: 'hanzo' })
84
+ const d = await meter.authorize({ user: 'hanzo/alice' })
85
+ fx.restore()
86
+ ok(!d.allowed, 'should deny')
87
+ eq(d.reason, 'insufficient_balance', 'reason')
88
+ })
89
+
90
+ test('authorize fail-closed on commerce error (503/unavailable)', async () => {
91
+ const fx = mockFetch(500, { error: 'boom' })
92
+ const meter = new Metering({ baseUrl: BASE, token: 't', org: 'hanzo' })
93
+ const d = await meter.authorize({ user: 'hanzo/alice' })
94
+ fx.restore()
95
+ ok(!d.allowed, 'fail-closed must deny on 500')
96
+ eq(d.reason, 'balance_unavailable', 'reason')
97
+ })
98
+
99
+ test('authorize fail-open allows on commerce error when configured', async () => {
100
+ const fx = mockFetch(503, { error: 'down' })
101
+ const meter = new Metering({ baseUrl: BASE, token: 't', org: 'hanzo', failOpen: true })
102
+ const d = await meter.authorize({ user: 'hanzo/alice' })
103
+ fx.restore()
104
+ ok(d.allowed, 'fail-open must allow')
105
+ })
106
+
107
+ test('authorize not-configured allows', async () => {
108
+ const meter = new Metering({}) // no baseUrl
109
+ ok(!meter.enabled, 'enabled should be false')
110
+ const d = await meter.authorize({ user: 'hanzo/alice' })
111
+ ok(d.allowed, 'not-configured must allow')
112
+ })
113
+
114
+ test('tier-aware authorize uses effectiveAvailable and hits /v1/billing/tier', async () => {
115
+ const fx = mockFetch(200, { user: 'hanzo/alice', tier: { name: 'free' }, balance: { currency: 'usd', prepaidAvailable: 0, dailyRemaining: 100, effectiveAvailable: 100 } })
116
+ const meter = new Metering({ baseUrl: BASE, token: 't', org: 'hanzo', tierAware: true })
117
+ const d = await meter.authorize({ user: 'hanzo/alice' })
118
+ fx.restore()
119
+ ok(d.allowed, 'included allotment should allow')
120
+ eq(d.available, 100, 'effective available')
121
+ ok(fx.last()!.url.startsWith(`${BASE}/v1/billing/tier?`), `tier url=${fx.last()!.url}`)
122
+ })
123
+
124
+ test('tier-aware authorize denies when effectiveAvailable == 0', async () => {
125
+ const fx = mockFetch(200, { balance: { effectiveAvailable: 0, prepaidAvailable: 0, dailyRemaining: 0, currency: 'usd' } })
126
+ const meter = new Metering({ baseUrl: BASE, token: 't', org: 'hanzo', tierAware: true })
127
+ const d = await meter.authorize({ user: 'hanzo/alice' })
128
+ fx.restore()
129
+ ok(!d.allowed && d.reason === 'insufficient_balance', 'exhausted must deny insufficient')
130
+ })
131
+
132
+ test('per-call org overrides default', async () => {
133
+ const fx = mockFetch(200, { available: 1 })
134
+ const meter = new Metering({ baseUrl: BASE, token: 't', org: 'hanzo' })
135
+ await meter.authorize({ user: 'zoo/bob', org: 'zoo' })
136
+ fx.restore()
137
+ eq(fx.last()!.headers['x-iam-org-id'], 'zoo', 'per-call org override')
138
+ })
139
+
140
+ test('record posts canonical payload', async () => {
141
+ const fx = mockFetch(201, { transactionId: 'tx_123', user: 'hanzo/alice', amount: 250, currency: 'usd', type: 'withdraw' })
142
+ const meter = new Metering({ baseUrl: BASE, token: 'svc-token', org: 'hanzo' })
143
+ const res = await meter.record({ user: 'hanzo/alice', amount: 250, provider: 'search', requestId: 'req-9', status: 'success' })
144
+ fx.restore()
145
+
146
+ ok(res !== null && res.transactionId === 'tx_123' && res.amount === 250, 'record result')
147
+ const c = fx.last()!
148
+ eq(c.method, 'POST', 'method')
149
+ eq(c.url, `${BASE}/v1/billing/usage`, 'usage url')
150
+ eq(c.headers['authorization'], 'Bearer svc-token', 'bearer')
151
+ eq(c.headers['x-iam-org-id'], 'hanzo', 'org header')
152
+ const b = c.body as Record<string, unknown>
153
+ eq(b.user, 'hanzo/alice', 'body.user')
154
+ eq(b.amount, 250, 'body.amount')
155
+ eq(b.currency, 'usd', 'body.currency defaulted')
156
+ eq(b.provider, 'search', 'body.provider')
157
+ eq(b.requestId, 'req-9', 'body.requestId')
158
+ ok(!('org' in b), 'org must not leak into the body')
159
+ })
160
+
161
+ test('record zero-amount is a no-op (no fetch)', async () => {
162
+ const fx = mockFetch(200, {})
163
+ const meter = new Metering({ baseUrl: BASE, token: 't', org: 'hanzo' })
164
+ const res = await meter.record({ user: 'hanzo/alice', amount: 0 })
165
+ fx.restore()
166
+ ok(res === null, 'zero-amount returns null')
167
+ ok(fx.last() === null, 'zero-amount must not call commerce')
168
+ })
169
+
170
+ test('record not-configured is a no-op', async () => {
171
+ const meter = new Metering({})
172
+ const res = await meter.record({ user: 'hanzo/alice', amount: 100 })
173
+ ok(res === null, 'not-configured returns null')
174
+ })
175
+
176
+ test('fromEnv applies defaults and reads KMS-sourced token', () => {
177
+ const meter = Metering.fromEnv({ COMMERCE_SERVICE_TOKEN: 'kms-token', METERING_TIER_AWARE: 'true' })
178
+ ok(meter.enabled, 'fromEnv default base URL enables the meter')
179
+ })
180
+
181
+ test('fromEnv honors METERING_DISABLED', () => {
182
+ const meter = Metering.fromEnv({ METERING_DISABLED: 'true', COMMERCE_URL: BASE })
183
+ ok(!meter.enabled, 'disabled must not be enabled')
184
+ })
185
+
186
+ test('identityFromHeaders builds org/sub from gateway headers', () => {
187
+ const id = identityFromHeaders({ 'x-org-id': 'zoo', 'x-user-id': 'bob' })
188
+ eq(id.user, 'zoo/bob', 'user')
189
+ eq(id.org, 'zoo', 'org')
190
+ })
191
+
192
+ // summary
193
+ setTimeout(() => {
194
+ if (failures > 0) {
195
+ console.error(`\n${failures}/${count} tests FAILED`)
196
+ process.exit(1)
197
+ }
198
+ console.log(`\nall ${count} tests passed`)
199
+ }, 200)