rerobe-js-orm 4.8.8 → 4.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/lib/constants/marketplace-constants.d.ts +52 -0
  2. package/lib/constants/marketplace-constants.js +82 -0
  3. package/lib/constants/product-constants.d.ts +6 -0
  4. package/lib/constants/product-constants.js +43 -1
  5. package/lib/constants/tax-constants.d.ts +19 -0
  6. package/lib/constants/tax-constants.js +70 -0
  7. package/lib/factories/Product/RetailProductFactory.d.ts +102 -0
  8. package/lib/factories/Product/RetailProductFactory.js +260 -0
  9. package/lib/form-states/Product/ProductFormState.d.ts +9 -0
  10. package/lib/form-states/Product/ProductFormState.js +46 -0
  11. package/lib/helpers/MarketplaceProductHelpers.d.ts +181 -0
  12. package/lib/helpers/MarketplaceProductHelpers.js +555 -0
  13. package/lib/helpers/OrderHelpers.d.ts +5 -2
  14. package/lib/helpers/OrderHelpers.js +18 -4
  15. package/lib/helpers/PricingHelpers.d.ts +29 -0
  16. package/lib/helpers/PricingHelpers.js +215 -0
  17. package/lib/helpers/ProductInventoryHelpers.d.ts +31 -0
  18. package/lib/helpers/ProductInventoryHelpers.js +66 -0
  19. package/lib/helpers/ProductSkuHelpers.d.ts +13 -0
  20. package/lib/helpers/ProductSkuHelpers.js +70 -0
  21. package/lib/helpers/TaxHelpers.d.ts +15 -0
  22. package/lib/helpers/TaxHelpers.js +76 -0
  23. package/lib/helpers/marketplace/CommissionPolicy.d.ts +20 -0
  24. package/lib/helpers/marketplace/CommissionPolicy.js +9 -0
  25. package/lib/helpers/marketplace/CommissionPolicyRegistry.d.ts +4 -0
  26. package/lib/helpers/marketplace/CommissionPolicyRegistry.js +21 -0
  27. package/lib/helpers/marketplace/EvenPerSellerStripeFeePolicy.d.ts +5 -0
  28. package/lib/helpers/marketplace/EvenPerSellerStripeFeePolicy.js +46 -0
  29. package/lib/helpers/marketplace/FeeAllocationPolicy.d.ts +10 -0
  30. package/lib/helpers/marketplace/FeeAllocationPolicy.js +36 -0
  31. package/lib/helpers/marketplace/FeeAllocationPolicyRegistry.d.ts +6 -0
  32. package/lib/helpers/marketplace/FeeAllocationPolicyRegistry.js +26 -0
  33. package/lib/helpers/marketplace/InventoryPolicy.d.ts +18 -0
  34. package/lib/helpers/marketplace/InventoryPolicy.js +7 -0
  35. package/lib/helpers/marketplace/InventoryPolicyRegistry.d.ts +4 -0
  36. package/lib/helpers/marketplace/InventoryPolicyRegistry.js +29 -0
  37. package/lib/helpers/marketplace/LineNetCommissionPolicy.d.ts +5 -0
  38. package/lib/helpers/marketplace/LineNetCommissionPolicy.js +25 -0
  39. package/lib/helpers/marketplace/MarketplaceErrors.d.ts +6 -0
  40. package/lib/helpers/marketplace/MarketplaceErrors.js +21 -0
  41. package/lib/helpers/marketplace/MarketplaceLedgerHelpers.d.ts +40 -0
  42. package/lib/helpers/marketplace/MarketplaceLedgerHelpers.js +120 -0
  43. package/lib/helpers/marketplace/MarketplaceLegacyAdapters.d.ts +53 -0
  44. package/lib/helpers/marketplace/MarketplaceLegacyAdapters.js +99 -0
  45. package/lib/helpers/marketplace/MarketplaceLineDisplayHelpers.d.ts +40 -0
  46. package/lib/helpers/marketplace/MarketplaceLineDisplayHelpers.js +125 -0
  47. package/lib/helpers/marketplace/MarketplaceOrderHelpers.d.ts +15 -0
  48. package/lib/helpers/marketplace/MarketplaceOrderHelpers.js +77 -0
  49. package/lib/helpers/marketplace/MultivariantLocationInventoryPolicy.d.ts +9 -0
  50. package/lib/helpers/marketplace/MultivariantLocationInventoryPolicy.js +60 -0
  51. package/lib/helpers/marketplace/OneOfAKindInventoryPolicy.d.ts +9 -0
  52. package/lib/helpers/marketplace/OneOfAKindInventoryPolicy.js +55 -0
  53. package/lib/helpers/marketplace/OriginalCommissionShareCommissionPolicy.d.ts +5 -0
  54. package/lib/helpers/marketplace/OriginalCommissionShareCommissionPolicy.js +26 -0
  55. package/lib/helpers/marketplace/ProportionalToLineNetPolicy.d.ts +5 -0
  56. package/lib/helpers/marketplace/ProportionalToLineNetPolicy.js +35 -0
  57. package/lib/helpers/marketplace/UntrackedStockInventoryPolicy.d.ts +7 -0
  58. package/lib/helpers/marketplace/UntrackedStockInventoryPolicy.js +23 -0
  59. package/lib/index.d.ts +18 -1
  60. package/lib/index.js +56 -1
  61. package/lib/models/Product.d.ts +16 -0
  62. package/lib/models/Product.js +140 -0
  63. package/lib/types/pricing-types.d.ts +85 -0
  64. package/lib/types/pricing-types.js +5 -0
  65. package/lib/types/rerobe-order-types.d.ts +86 -0
  66. package/lib/types/rerobe-product-types.d.ts +19 -0
  67. package/package.json +1 -1
@@ -0,0 +1,29 @@
1
+ import { IsTaxableValue, OrderLinePricing, OrderLinePricingLike, ProductPricing, ProductPricingLike, RateInput, TaxResolution, VariantPricing, VariantPricingLike } from '../types/pricing-types';
2
+ export default class PricingHelpers {
3
+ static isTaxableYes(value: IsTaxableValue): boolean;
4
+ static resolveTaxRate(input: RateInput): TaxResolution;
5
+ static passThroughString(value: string | number | undefined | null): string;
6
+ static toNetString(gross: string | number | undefined | null, rate: number): string;
7
+ static toGrossString(net: string | number | undefined | null, rate: number): string;
8
+ static getProductPricing(product: ProductPricingLike | null | undefined, opts: {
9
+ countryCode: string;
10
+ }): ProductPricing;
11
+ static getVariantPricing(variant: VariantPricingLike | null | undefined, parent: ProductPricingLike | null | undefined, opts: {
12
+ countryCode: string;
13
+ }): VariantPricing;
14
+ static getOrderLinePricing(line: OrderLinePricingLike | null | undefined): OrderLinePricing;
15
+ static buildAccountingSavePatch(values: {
16
+ price?: number | string;
17
+ isTaxable?: IsTaxableValue;
18
+ taxCategory?: string | null;
19
+ }, countryCode: string): {
20
+ price: string;
21
+ taxCategory: string;
22
+ isTaxable: 'yes' | 'no';
23
+ };
24
+ static buildAccountingEditSeed(values: {
25
+ price?: number | string;
26
+ isTaxable?: IsTaxableValue;
27
+ taxCategory?: string | null;
28
+ }, countryCode: string): string;
29
+ }
@@ -0,0 +1,215 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tax_constants_1 = require("../constants/tax-constants");
4
+ const TaxHelpers_1 = require("./TaxHelpers");
5
+ // Catalog vs frozen-rate type narrow used by `resolveTaxRate`. We treat
6
+ // any object that surfaces `countryCode` or `taxCategory` as catalog mode
7
+ // and everything else as frozen-rate mode (which only carries `taxRate`
8
+ // and `isTaxable`).
9
+ function isCatalogInput(input) {
10
+ return input.countryCode !== undefined || input.taxCategory !== undefined;
11
+ }
12
+ function coerceFrozenRate(rate) {
13
+ if (rate === null || rate === undefined)
14
+ return 0;
15
+ const n = typeof rate === 'number' ? rate : parseFloat(String(rate));
16
+ return Number.isFinite(n) && n > 0 ? n : 0;
17
+ }
18
+ function readMoneyAmount(value) {
19
+ if (value === null || value === undefined)
20
+ return null;
21
+ if (typeof value === 'number' || typeof value === 'string')
22
+ return value;
23
+ return value.amount === undefined ? null : value.amount;
24
+ }
25
+ // Pricing helpers — pure functions that resolve effective tax rates and
26
+ // project net/gross snapshots for products, variants, and order lines.
27
+ //
28
+ // Two lookup modes:
29
+ // - Catalog (country + tax category) → product editors, list screens
30
+ // - Frozen rate (taxRate captured at order time) → orders, refunds, POS
31
+ //
32
+ // Lifted from the ribbn-web-app `lib/pricing/core` module so backend,
33
+ // mobile, and web all share the same conversions and snapshot shapes.
34
+ class PricingHelpers {
35
+ static isTaxableYes(value) {
36
+ if (value === true)
37
+ return true;
38
+ if (typeof value === 'string')
39
+ return value.toLowerCase() === 'yes';
40
+ return false;
41
+ }
42
+ static resolveTaxRate(input) {
43
+ const taxable = PricingHelpers.isTaxableYes(input.isTaxable);
44
+ if (!taxable) {
45
+ return { rate: 0, taxable: false };
46
+ }
47
+ if (isCatalogInput(input)) {
48
+ const category = TaxHelpers_1.default.normalizeTaxCategory(input.taxCategory);
49
+ if (category === tax_constants_1.TAX_CATEGORIES.EXEMPT) {
50
+ return { rate: 0, taxable: true, category };
51
+ }
52
+ return {
53
+ rate: TaxHelpers_1.default.getTaxRate(input.countryCode, category),
54
+ taxable: true,
55
+ category,
56
+ };
57
+ }
58
+ const frozen = input;
59
+ return { rate: coerceFrozenRate(frozen.taxRate), taxable: true };
60
+ }
61
+ // Pass-through string normalization used when no rate applies (exempt
62
+ // / non-taxable). Trims and returns the stored value as a plain string
63
+ // so callers don't have to special-case empty inputs.
64
+ static passThroughString(value) {
65
+ if (value === null || value === undefined)
66
+ return '';
67
+ if (typeof value === 'number')
68
+ return String(value);
69
+ return String(value).trim();
70
+ }
71
+ static toNetString(gross, rate) {
72
+ return TaxHelpers_1.default.grossToNetPriceString(gross, rate);
73
+ }
74
+ static toGrossString(net, rate) {
75
+ return TaxHelpers_1.default.netToGrossPriceString(net, rate);
76
+ }
77
+ // Pricing snapshot for a product (parent fields). Resolves the rate
78
+ // once, then projects every price-like field into both stored (net)
79
+ // and displayable (gross) forms.
80
+ static getProductPricing(product, opts) {
81
+ const safeProduct = product || {};
82
+ const { rate, taxable } = PricingHelpers.resolveTaxRate({
83
+ countryCode: opts.countryCode,
84
+ taxCategory: safeProduct.taxCategory,
85
+ isTaxable: safeProduct.isTaxable,
86
+ });
87
+ const projectGross = (net) => {
88
+ const trimmed = PricingHelpers.passThroughString(net);
89
+ if (!trimmed)
90
+ return '';
91
+ if (!rate)
92
+ return trimmed;
93
+ return PricingHelpers.toGrossString(trimmed, rate);
94
+ };
95
+ return {
96
+ rate,
97
+ taxable,
98
+ priceNet: PricingHelpers.passThroughString(safeProduct.price),
99
+ salePriceNet: PricingHelpers.passThroughString(safeProduct.salePrice),
100
+ compareAtPriceNet: PricingHelpers.passThroughString(safeProduct.compareAtPrice),
101
+ costPerItemNet: PricingHelpers.passThroughString(safeProduct.costPerItem),
102
+ priceGross: projectGross(safeProduct.price),
103
+ salePriceGross: projectGross(safeProduct.salePrice),
104
+ compareAtPriceGross: projectGross(safeProduct.compareAtPrice),
105
+ costPerItemGross: projectGross(safeProduct.costPerItem),
106
+ };
107
+ }
108
+ // Pricing snapshot for a single variant inventory row. Honors the
109
+ // variant-level `taxable` override; falls back to the parent product's
110
+ // `isTaxable` and `taxCategory` when the variant doesn't specify one.
111
+ static getVariantPricing(variant, parent, opts) {
112
+ const safeVariant = variant || {};
113
+ const safeParent = parent || {};
114
+ const variantTaxableSpecified = safeVariant.taxable !== undefined && safeVariant.taxable !== null;
115
+ const effectiveTaxable = variantTaxableSpecified
116
+ ? PricingHelpers.isTaxableYes(safeVariant.taxable)
117
+ ? 'yes'
118
+ : 'no'
119
+ : safeParent.isTaxable;
120
+ const { rate, taxable } = PricingHelpers.resolveTaxRate({
121
+ countryCode: opts.countryCode,
122
+ taxCategory: safeParent.taxCategory,
123
+ isTaxable: effectiveTaxable,
124
+ });
125
+ const projectGross = (net) => {
126
+ const trimmed = PricingHelpers.passThroughString(net);
127
+ if (!trimmed)
128
+ return '';
129
+ if (!rate)
130
+ return trimmed;
131
+ return PricingHelpers.toGrossString(trimmed, rate);
132
+ };
133
+ return {
134
+ rate,
135
+ taxable,
136
+ priceNet: PricingHelpers.passThroughString(safeVariant.price),
137
+ salePriceNet: PricingHelpers.passThroughString(safeVariant.salePrice),
138
+ compareAtPriceNet: PricingHelpers.passThroughString(safeVariant.compareAtPrice),
139
+ costPerItemNet: PricingHelpers.passThroughString(safeVariant.costPerItem),
140
+ priceGross: projectGross(safeVariant.price),
141
+ salePriceGross: projectGross(safeVariant.salePrice),
142
+ compareAtPriceGross: projectGross(safeVariant.compareAtPrice),
143
+ costPerItemGross: projectGross(safeVariant.costPerItem),
144
+ };
145
+ }
146
+ // Pricing snapshot for an order/refund/POS line item. Uses the line's
147
+ // frozen `taxRate` field captured at order creation rather than
148
+ // consulting the catalog — at order time we want to display the tax
149
+ // that was actually charged, not whatever the current catalog says.
150
+ static getOrderLinePricing(line) {
151
+ const safeLine = line || {};
152
+ const { rate, taxable } = PricingHelpers.resolveTaxRate({
153
+ taxRate: safeLine.taxRate,
154
+ isTaxable: safeLine.isTaxable,
155
+ });
156
+ const projectGross = (net) => {
157
+ const trimmed = PricingHelpers.passThroughString(net);
158
+ if (!trimmed)
159
+ return '';
160
+ if (!rate)
161
+ return trimmed;
162
+ return PricingHelpers.toGrossString(trimmed, rate);
163
+ };
164
+ const unit = PricingHelpers.passThroughString(readMoneyAmount(safeLine.originalUnitPrice));
165
+ const total = PricingHelpers.passThroughString(readMoneyAmount(safeLine.originalTotalPrice));
166
+ const sale = PricingHelpers.passThroughString(readMoneyAmount(safeLine.salePrice));
167
+ return {
168
+ rate,
169
+ taxable,
170
+ unitPriceNet: unit,
171
+ unitPriceGross: projectGross(unit),
172
+ totalPriceNet: total,
173
+ totalPriceGross: projectGross(total),
174
+ salePriceNet: sale,
175
+ salePriceGross: projectGross(sale),
176
+ };
177
+ }
178
+ // Convenience helper for the retail accounting "save" path:
179
+ // - merchant types gross
180
+ // - we resolve the catalog rate
181
+ // - return the triple to persist (net price + normalized category +
182
+ // normalized isTaxable)
183
+ //
184
+ // Lifted from ribbn-web-app `ProductDetailsV2.buildAccountingSavePatch`.
185
+ static buildAccountingSavePatch(values, countryCode) {
186
+ const isTaxable = PricingHelpers.isTaxableYes(values.isTaxable);
187
+ const taxCategory = isTaxable ? TaxHelpers_1.default.normalizeTaxCategory(values.taxCategory) : tax_constants_1.TAX_CATEGORIES.EXEMPT;
188
+ const { rate } = PricingHelpers.resolveTaxRate({
189
+ countryCode,
190
+ taxCategory,
191
+ isTaxable: isTaxable ? 'yes' : 'no',
192
+ });
193
+ return {
194
+ price: PricingHelpers.toNetString(values.price, rate),
195
+ taxCategory: String(taxCategory),
196
+ isTaxable: isTaxable ? 'yes' : 'no',
197
+ };
198
+ }
199
+ // Convenience helper for the retail accounting "edit" path: seeds the
200
+ // price input with the gross-converted value of the stored net price
201
+ // so the merchant always sees / edits gross.
202
+ static buildAccountingEditSeed(values, countryCode) {
203
+ const taxCategory = TaxHelpers_1.default.normalizeTaxCategory(values.taxCategory);
204
+ const isTaxable = PricingHelpers.isTaxableYes(values.isTaxable);
205
+ const { rate } = PricingHelpers.resolveTaxRate({
206
+ countryCode,
207
+ taxCategory,
208
+ isTaxable: isTaxable ? 'yes' : 'no',
209
+ });
210
+ if (!rate)
211
+ return String(values.price === undefined || values.price === null ? '' : values.price);
212
+ return PricingHelpers.toGrossString(values.price, rate);
213
+ }
214
+ }
215
+ exports.default = PricingHelpers;
@@ -0,0 +1,31 @@
1
+ export declare const PLACEHOLDER_VARIANT_ID = "default";
2
+ export interface PlaceholderVariantLocation {
3
+ id: string;
4
+ name: string;
5
+ incomingAmount: number;
6
+ availableAmount: number;
7
+ }
8
+ export interface PlaceholderVariantInventory {
9
+ id: string;
10
+ options: {
11
+ [k: string]: never;
12
+ };
13
+ price?: string | null;
14
+ sku?: string | null;
15
+ taxable?: 'yes' | 'no' | null;
16
+ continueSellingWhenOutOfStock?: boolean | null;
17
+ locations?: PlaceholderVariantLocation[] | null;
18
+ }
19
+ export interface PlaceholderVariantInput {
20
+ sku?: string | null;
21
+ price?: string | null;
22
+ taxable?: 'yes' | 'no' | null;
23
+ quantity?: number;
24
+ locationId?: string;
25
+ locationName?: string;
26
+ continueSellingWhenOutOfStock?: boolean;
27
+ }
28
+ export default class ProductInventoryHelpers {
29
+ static buildPlaceholderVariantInventory(input?: PlaceholderVariantInput): PlaceholderVariantInventory[];
30
+ static aggregateQuantityFromPlaceholder(placeholder: PlaceholderVariantInventory[] | null | undefined): number;
31
+ }
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ // Placeholder variant inventory + aggregation helpers. Pure,
3
+ // dependency-free — used by non-clothing retail product creation flows
4
+ // (cafe items, services, supplies) that need a single-row
5
+ // variantInventory entry without modeling explicit size/color axes.
6
+ //
7
+ // Modelling these items as MULTIVARIANT with a one-row variantInventory
8
+ // keeps every retail SKU under existing ORM productClass values (no new
9
+ // productClass, no SUPPLY misuse) and gives every flow that needs
10
+ // per-location stock the same shape.
11
+ //
12
+ // Tracked items: pass the merchant-entered `quantity` and the
13
+ // merchant's default location id. Indefinite-stock items (cafe /
14
+ // services): pass quantity 0 and `continueSellingWhenOutOfStock: true`
15
+ // — the order processor uses `product.stockStatus = IN_STOCK_UNLIMITED`
16
+ // to skip the actual decrement (see `Product.isUntrackedStock`).
17
+ //
18
+ // Lifted from the ribbn-web-app `lib/utilities/placeholderVariant.ts`
19
+ // shim so backend, mobile, and web share the same construction rule.
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.PLACEHOLDER_VARIANT_ID = void 0;
22
+ exports.PLACEHOLDER_VARIANT_ID = 'default';
23
+ class ProductInventoryHelpers {
24
+ static buildPlaceholderVariantInventory(input = {}) {
25
+ const sku = input.sku === undefined || input.sku === null ? '' : input.sku;
26
+ const price = input.price === undefined || input.price === null ? '' : input.price;
27
+ const taxable = input.taxable === undefined ? null : input.taxable;
28
+ const quantity = input.quantity === undefined ? 0 : input.quantity;
29
+ const locationId = input.locationId === undefined ? '' : input.locationId;
30
+ const locationName = input.locationName === undefined ? '' : input.locationName;
31
+ const continueSellingWhenOutOfStock = input.continueSellingWhenOutOfStock === undefined ? false : input.continueSellingWhenOutOfStock;
32
+ const safeQuantity = Number.isFinite(quantity) ? Math.max(0, Math.floor(Number(quantity))) : 0;
33
+ const variant = {
34
+ id: exports.PLACEHOLDER_VARIANT_ID,
35
+ options: {},
36
+ price: price ? String(price) : '',
37
+ sku: sku ? String(sku) : '',
38
+ taxable: taxable || null,
39
+ continueSellingWhenOutOfStock: Boolean(continueSellingWhenOutOfStock),
40
+ locations: locationId
41
+ ? [
42
+ {
43
+ id: locationId,
44
+ name: locationName || '',
45
+ incomingAmount: 0,
46
+ availableAmount: safeQuantity,
47
+ },
48
+ ]
49
+ : [],
50
+ };
51
+ return [variant];
52
+ }
53
+ // Aggregate quantity across the placeholder variant's locations.
54
+ // Mirrors what `ProductFormState.aggregateInventoryLocationsFromVariants`
55
+ // does for the multi-axis case so the product-level `quantity` stays
56
+ // accurate.
57
+ static aggregateQuantityFromPlaceholder(placeholder) {
58
+ if (!Array.isArray(placeholder) || placeholder.length === 0)
59
+ return 0;
60
+ const variant = placeholder[0];
61
+ if (!variant || !variant.locations || variant.locations.length === 0)
62
+ return 0;
63
+ return variant.locations.reduce((acc, loc) => acc + (Number(loc.availableAmount) || 0), 0);
64
+ }
65
+ }
66
+ exports.default = ProductInventoryHelpers;
@@ -0,0 +1,13 @@
1
+ export declare const SKU_ALPHABET = "ABCDEFGHJKMNPQRSTVWXYZ23456789";
2
+ export declare const DEFAULT_SKU_LENGTH = 10;
3
+ export default class ProductSkuHelpers {
4
+ static generateSku(length?: number): string;
5
+ static effectiveVariantSku(product: {
6
+ sku?: string | null;
7
+ } | null | undefined, variant: {
8
+ sku?: string | null;
9
+ } | null | undefined): string;
10
+ static describeVariantOptions(options: {
11
+ [k: string]: string;
12
+ } | null | undefined): string;
13
+ }
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ // SKU and variant-description helpers. Pure, dependency-free — used by
3
+ // retail product creation, label printing, receipt rendering, and any
4
+ // surface that needs to derive a scan-friendly SKU or human-readable
5
+ // variant title from a product / variant pair.
6
+ //
7
+ // Lifted from the ribbn-web-app `lib/utilities/sku.ts` shim so backend,
8
+ // mobile, and web all consume the same alphabet, fallback rules, and
9
+ // variant-description join.
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.DEFAULT_SKU_LENGTH = exports.SKU_ALPHABET = void 0;
12
+ // 30-character Crockford-style alphabet that strips visually ambiguous
13
+ // characters (0/O, 1/I/L, U) so a merchant who has to read or type a
14
+ // SKU off a printed tag isn't tripped up by lookalike glyphs.
15
+ //
16
+ // 10 characters from a 30-character alphabet → ~49 bits of entropy.
17
+ // Within a single merchant's catalog the collision probability is
18
+ // effectively zero (you'd need ~10 million products before the chance
19
+ // hits 1%).
20
+ exports.SKU_ALPHABET = 'ABCDEFGHJKMNPQRSTVWXYZ23456789';
21
+ exports.DEFAULT_SKU_LENGTH = 10;
22
+ class ProductSkuHelpers {
23
+ // Generate a short, scan-friendly random SKU. Used as the default
24
+ // value when creating products in flows where the merchant hasn't
25
+ // provided their own SKU. The value is short enough to fit a Code 128
26
+ // barcode on even the smallest practical price-tag labels (≈30×20 mm).
27
+ static generateSku(length = exports.DEFAULT_SKU_LENGTH) {
28
+ const buf = new Uint8Array(length);
29
+ if (typeof globalThis !== 'undefined' &&
30
+ typeof globalThis.crypto !== 'undefined' &&
31
+ typeof globalThis.crypto.getRandomValues === 'function') {
32
+ globalThis.crypto.getRandomValues(buf);
33
+ }
34
+ else {
35
+ // SSR / test environments without WebCrypto. Not cryptographically
36
+ // strong, but acceptable for a SKU's collision-avoidance use case.
37
+ for (let i = 0; i < length; i = i + 1) {
38
+ buf[i] = Math.floor(Math.random() * 256);
39
+ }
40
+ }
41
+ let out = '';
42
+ for (let i = 0; i < length; i = i + 1) {
43
+ out = out + exports.SKU_ALPHABET[buf[i] % exports.SKU_ALPHABET.length];
44
+ }
45
+ return out;
46
+ }
47
+ // Resolves the SKU that should be encoded on a printed label / scanned
48
+ // barcode for a specific variant. Falls back to the parent product's
49
+ // SKU when the variant doesn't carry its own, matching the convention
50
+ // the retail entry surfaces in the variants table ("Leave a row's SKU
51
+ // blank to fall back to the product SKU").
52
+ static effectiveVariantSku(product, variant) {
53
+ const variantSku = String((variant && variant.sku) || '').trim();
54
+ if (variantSku)
55
+ return variantSku;
56
+ return String((product && product.sku) || '').trim();
57
+ }
58
+ // Builds a human-readable variant title from a variantInventory row's
59
+ // `options` map (e.g. `{ Size: 'M', Color: 'Black' }` → "M / Black").
60
+ // Useful both on printed labels and inside cart line item summaries.
61
+ static describeVariantOptions(options) {
62
+ if (!options)
63
+ return '';
64
+ return Object.keys(options)
65
+ .map((k) => String(options[k] || '').trim())
66
+ .filter(Boolean)
67
+ .join(' / ');
68
+ }
69
+ }
70
+ exports.default = ProductSkuHelpers;
@@ -0,0 +1,15 @@
1
+ import { TaxCategoryValue } from '../constants/tax-constants';
2
+ import { TaxCategoryOption } from '../types/pricing-types';
3
+ export default class TaxHelpers {
4
+ static normalizeTaxCategory(taxCategory?: string | null): TaxCategoryValue;
5
+ static getTaxCategoryLabel(taxCategory?: string | null): string;
6
+ static normalizeCountryCode(countryCode?: string | null): string;
7
+ static getCountryTaxRates(countryCode?: string | null): {
8
+ [category: string]: number;
9
+ };
10
+ static getTaxRate(countryCode?: string | null, taxCategory?: string | null): number;
11
+ static formatRatePercent(rate: number): string;
12
+ static getTaxCategoryOptionsWithRates(countryCode?: string | null): TaxCategoryOption[];
13
+ static grossToNetPriceString(grossPrice: string | number | undefined | null, rate: number): string;
14
+ static netToGrossPriceString(netPrice: string | number | undefined | null, rate: number): string;
15
+ }
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tax_constants_1 = require("../constants/tax-constants");
4
+ // Tax-rate and category helpers. Pure, dependency-free — safe to use in
5
+ // backend services, mobile, and web. Lifted from the ribbn-web-app
6
+ // `lib/utilities/tax.ts` shim so every surface that needs to read or
7
+ // edit a product's tax shape consumes the same normalization.
8
+ class TaxHelpers {
9
+ static normalizeTaxCategory(taxCategory) {
10
+ const raw = String(taxCategory || tax_constants_1.TAX_CATEGORIES.STANDARD).trim();
11
+ return tax_constants_1.TAX_CATEGORY_ALIASES[raw] || tax_constants_1.TAX_CATEGORY_ALIASES[raw.toUpperCase()] || tax_constants_1.TAX_CATEGORIES.STANDARD;
12
+ }
13
+ static getTaxCategoryLabel(taxCategory) {
14
+ return tax_constants_1.TAX_CATEGORY_BASE_LABELS[TaxHelpers.normalizeTaxCategory(taxCategory)];
15
+ }
16
+ static normalizeCountryCode(countryCode) {
17
+ const cc = String(countryCode || '').toUpperCase();
18
+ return tax_constants_1.TAX_CATEGORY_RATES[cc] ? cc : tax_constants_1.FALLBACK_TAX_COUNTRY_CODE;
19
+ }
20
+ static getCountryTaxRates(countryCode) {
21
+ return tax_constants_1.TAX_CATEGORY_RATES[TaxHelpers.normalizeCountryCode(countryCode)];
22
+ }
23
+ static getTaxRate(countryCode, taxCategory) {
24
+ const rates = TaxHelpers.getCountryTaxRates(countryCode);
25
+ const cat = TaxHelpers.normalizeTaxCategory(taxCategory);
26
+ if (cat in rates)
27
+ return rates[cat];
28
+ return rates.STANDARD || 0;
29
+ }
30
+ // Format a rate as a short percent label, e.g. 0.25 -> "25%", 0.055 -> "5.5%".
31
+ static formatRatePercent(rate) {
32
+ if (!rate)
33
+ return '0%';
34
+ const pct = rate * 100;
35
+ if (Math.abs(pct - Math.round(pct)) < 0.05) {
36
+ return `${Math.round(pct)}%`;
37
+ }
38
+ return `${Number(pct.toFixed(2))}%`;
39
+ }
40
+ // Returns option list with rate-aware labels (e.g. "Standard (25%)") for the
41
+ // given country. Useful for select dropdowns in admin product editors.
42
+ static getTaxCategoryOptionsWithRates(countryCode) {
43
+ const rates = TaxHelpers.getCountryTaxRates(countryCode);
44
+ return Object.keys(tax_constants_1.TAX_CATEGORIES).map((key) => {
45
+ const value = tax_constants_1.TAX_CATEGORIES[key];
46
+ const rate = rates[value] || 0;
47
+ const rateLabel = TaxHelpers.formatRatePercent(rate);
48
+ const baseLabel = tax_constants_1.TAX_CATEGORY_BASE_LABELS[value];
49
+ const label = value === tax_constants_1.TAX_CATEGORIES.EXEMPT ? `${baseLabel} (no tax)` : `${baseLabel} (${rateLabel})`;
50
+ return { value, label, rate, rateLabel };
51
+ });
52
+ }
53
+ // Convert a tax-inclusive (gross) price string to a tax-exclusive (net)
54
+ // price string formatted with 2 decimals. Returns the original string if
55
+ // it can't be parsed or if rate is 0.
56
+ static grossToNetPriceString(grossPrice, rate) {
57
+ const gross = typeof grossPrice === 'number' ? grossPrice : parseFloat(String(grossPrice == null ? '' : grossPrice));
58
+ if (!Number.isFinite(gross) || gross <= 0) {
59
+ return String(grossPrice == null ? '' : grossPrice);
60
+ }
61
+ if (!rate)
62
+ return gross.toFixed(2);
63
+ return (gross / (1 + rate)).toFixed(2);
64
+ }
65
+ // Convert a stored net price back to a gross price for display in inputs.
66
+ static netToGrossPriceString(netPrice, rate) {
67
+ const net = typeof netPrice === 'number' ? netPrice : parseFloat(String(netPrice == null ? '' : netPrice));
68
+ if (!Number.isFinite(net) || net <= 0) {
69
+ return String(netPrice == null ? '' : netPrice);
70
+ }
71
+ if (!rate)
72
+ return net.toFixed(2);
73
+ return (net * (1 + rate)).toFixed(2);
74
+ }
75
+ }
76
+ exports.default = TaxHelpers;
@@ -0,0 +1,20 @@
1
+ export type CommissionPolicyContext = {
2
+ taxableAmount: number;
3
+ originalRate: number;
4
+ marketplaceRate: number;
5
+ };
6
+ export type CommissionPolicyResult = {
7
+ originalProductCommissionAmount: number;
8
+ marketplaceCommissionAmount: number;
9
+ managedMerchantProceedsAmount: number;
10
+ sellerProceedsAmount: number;
11
+ };
12
+ export type CommissionPolicyFeeContext = {
13
+ stripeFee: number;
14
+ ribbnFee: number;
15
+ };
16
+ export interface CommissionPolicy {
17
+ readonly basis: string;
18
+ calculate(context: CommissionPolicyContext, feeContext: CommissionPolicyFeeContext): CommissionPolicyResult;
19
+ }
20
+ export declare const roundCommissionAmount: (value: number) => number;
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ // Strategy interface for marketplace commission calculation. Concrete
3
+ // implementations live in sibling files. Callers should depend on this
4
+ // abstraction (and the registered `basis` string) rather than any concrete
5
+ // class so new bases can be added without modifying existing code paths.
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.roundCommissionAmount = void 0;
8
+ const roundCommissionAmount = (value) => Number(Number(value || 0).toFixed(2));
9
+ exports.roundCommissionAmount = roundCommissionAmount;
@@ -0,0 +1,4 @@
1
+ import { CommissionPolicy } from './CommissionPolicy';
2
+ export declare const register: (policy: CommissionPolicy) => void;
3
+ export declare const get: (basis: string) => CommissionPolicy;
4
+ export declare const has: (basis: string) => boolean;
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.has = exports.get = exports.register = void 0;
4
+ const marketplace_constants_1 = require("../../constants/marketplace-constants");
5
+ const LineNetCommissionPolicy_1 = require("./LineNetCommissionPolicy");
6
+ const OriginalCommissionShareCommissionPolicy_1 = require("./OriginalCommissionShareCommissionPolicy");
7
+ // Process-wide registry for marketplace commission policies. Built-in
8
+ // strategies are registered eagerly so the default exported registry is
9
+ // usable without setup. New bases can be added at runtime via
10
+ // `register(policy)` to satisfy the open/closed principle.
11
+ const REGISTRY = {};
12
+ const register = (policy) => {
13
+ REGISTRY[policy.basis] = policy;
14
+ };
15
+ exports.register = register;
16
+ const get = (basis) => REGISTRY[basis] || REGISTRY[marketplace_constants_1.DEFAULT_MARKETPLACE_COMMISSION_BASIS];
17
+ exports.get = get;
18
+ const has = (basis) => Boolean(REGISTRY[basis]);
19
+ exports.has = has;
20
+ (0, exports.register)(new LineNetCommissionPolicy_1.default());
21
+ (0, exports.register)(new OriginalCommissionShareCommissionPolicy_1.default());
@@ -0,0 +1,5 @@
1
+ import { FeeAllocationContext, FeeAllocationPolicy } from './FeeAllocationPolicy';
2
+ export default class EvenPerSellerStripeFeePolicy implements FeeAllocationPolicy {
3
+ readonly basis: "EVEN_PER_SELLER";
4
+ allocate({ totalFeeAmount, basket }: FeeAllocationContext): MarketplaceFeeAllocation[];
5
+ }
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const marketplace_constants_1 = require("../../constants/marketplace-constants");
4
+ const FeeAllocationPolicy_1 = require("./FeeAllocationPolicy");
5
+ // Mirrors the existing backend `applyStripeFeeRecoupBump` semantics:
6
+ // merchantShare = totalFee / numUniqueManagedSellersInBasket
7
+ // linePerSellerCt = lines for that seller
8
+ // per-line amount = merchantShare / linePerSellerCt
9
+ //
10
+ // Lines whose `lineNetAmount` is non-positive are skipped so we never spread
11
+ // a fee onto a freebie line, matching the legacy guard against zero-priced
12
+ // items.
13
+ class EvenPerSellerStripeFeePolicy {
14
+ constructor() {
15
+ this.basis = marketplace_constants_1.MARKETPLACE_FEE_ALLOCATION_BASES.evenPerSeller;
16
+ }
17
+ allocate({ totalFeeAmount, basket }) {
18
+ if (!Array.isArray(basket) || basket.length === 0) {
19
+ return [];
20
+ }
21
+ const safeTotal = Number(totalFeeAmount);
22
+ if (!Number.isFinite(safeTotal) || safeTotal <= 0) {
23
+ return basket.map((line) => ({ lineKey: line.lineKey, amount: 0 }));
24
+ }
25
+ const eligibleLines = basket.filter((line) => Number.isFinite(Number(line === null || line === void 0 ? void 0 : line.lineNetAmount)) &&
26
+ Number(line.lineNetAmount) > 0 &&
27
+ Boolean(line.sellerMerchantId));
28
+ if (eligibleLines.length === 0) {
29
+ return basket.map((line) => ({ lineKey: line.lineKey, amount: 0 }));
30
+ }
31
+ const sellerIds = Array.from(new Set(eligibleLines.map((line) => String(line.sellerMerchantId))));
32
+ const merchantShare = safeTotal / sellerIds.length;
33
+ const lineCountByVendor = sellerIds.reduce((acc, sellerId) => {
34
+ acc[sellerId] = eligibleLines.filter((line) => line.sellerMerchantId === sellerId).length;
35
+ return acc;
36
+ }, {});
37
+ const allocations = basket.map((line) => {
38
+ const vendorLineCount = lineCountByVendor[String(line.sellerMerchantId)] || 0;
39
+ const isEligible = vendorLineCount > 0 && Number.isFinite(Number(line === null || line === void 0 ? void 0 : line.lineNetAmount)) && Number(line.lineNetAmount) > 0;
40
+ const amount = isEligible ? (0, FeeAllocationPolicy_1.roundFeeAmount)(merchantShare / vendorLineCount) : 0;
41
+ return { lineKey: line.lineKey, amount };
42
+ });
43
+ return (0, FeeAllocationPolicy_1.reconcileAllocationsToTotal)(allocations, safeTotal);
44
+ }
45
+ }
46
+ exports.default = EvenPerSellerStripeFeePolicy;
@@ -0,0 +1,10 @@
1
+ export type FeeAllocationContext = {
2
+ totalFeeAmount: number;
3
+ basket: MarketplaceFeeBasketLine[];
4
+ };
5
+ export interface FeeAllocationPolicy {
6
+ readonly basis: string;
7
+ allocate(context: FeeAllocationContext): MarketplaceFeeAllocation[];
8
+ }
9
+ export declare const roundFeeAmount: (value: number) => number;
10
+ export declare const reconcileAllocationsToTotal: (allocations: MarketplaceFeeAllocation[], totalFeeAmount: number) => MarketplaceFeeAllocation[];