@growupsell/commerce 0.0.0-stage → 1.0.0-alpha.1
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.
- package/LICENSE +33 -0
- package/README.md +6 -2
- package/dist/chunk-4PHF3WVQ.js +76 -0
- package/dist/chunk-4PHF3WVQ.js.map +1 -0
- package/dist/chunk-YZJTPFCH.js +2789 -0
- package/dist/chunk-YZJTPFCH.js.map +1 -0
- package/dist/http/index.d.ts +2 -0
- package/dist/http/index.js +9 -0
- package/dist/http/index.js.map +1 -0
- package/dist/index-tlJ3s67B.d.ts +2107 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/promotions-Ck9FZD7h.d.ts +298 -0
- package/dist/rules/index.d.ts +111 -0
- package/dist/rules/index.js +19 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/stores/index.d.ts +2 -0
- package/dist/stores/index.js +1 -0
- package/dist/stores/index.js.map +1 -0
- package/package.json +42 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
DRAFT - NOT YET REVIEWED. Confirm the legal entity name and have this text reviewed
|
|
2
|
+
before the first stable (1.0.0) release. Remove this paragraph once approved.
|
|
3
|
+
|
|
4
|
+
Growupsell Commerce Libraries License
|
|
5
|
+
|
|
6
|
+
Copyright (c) 2026 Tenthspire Technologies. All rights reserved.
|
|
7
|
+
|
|
8
|
+
This license covers the @growupsell/* packages published from this repository
|
|
9
|
+
(the "Software").
|
|
10
|
+
|
|
11
|
+
1. Grant. Tenthspire Technologies ("Licensor") grants each customer with an active
|
|
12
|
+
Growupsell subscription or agreement, and any developer or agency working on that
|
|
13
|
+
customer's behalf ("Licensee"), a non-exclusive, non-transferable, revocable license
|
|
14
|
+
to install, use and modify the Software solely to build, run and maintain
|
|
15
|
+
applications that connect to the Growupsell platform for that customer.
|
|
16
|
+
|
|
17
|
+
2. Restrictions. Licensee may not (a) use the Software with any commerce platform or
|
|
18
|
+
backend other than Growupsell; (b) sell, sublicense, or redistribute the Software,
|
|
19
|
+
or modified versions of it, as a standalone product or library; or (c) remove or
|
|
20
|
+
alter this notice.
|
|
21
|
+
|
|
22
|
+
3. Distribution inside applications. Licensee may include the Software, compiled or
|
|
23
|
+
bundled, inside applications covered by section 1 and distribute those applications
|
|
24
|
+
to their end users.
|
|
25
|
+
|
|
26
|
+
4. Termination. This license ends automatically when Licensee's Growupsell
|
|
27
|
+
subscription or agreement ends, or if Licensee breaches these terms.
|
|
28
|
+
|
|
29
|
+
5. No warranty. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
30
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
31
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE LICENSOR
|
|
32
|
+
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY ARISING FROM, OUT OF OR IN
|
|
33
|
+
CONNECTION WITH THE SOFTWARE OR ITS USE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @growupsell/commerce
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
All Growupsell business logic: HTTP client, business rules and state stores. No framework, no browser globals, zero dependencies.
|
|
4
|
+
|
|
5
|
+
Subpaths: `@growupsell/commerce/http`, `/rules`, `/stores`. Built in milestones M1–M3.
|
|
6
|
+
|
|
7
|
+
Status: empty skeleton (milestone M0). See the repository README.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// src/rules/checkout.ts
|
|
2
|
+
function giftCardBalance(card) {
|
|
3
|
+
return roundMoney(Math.max(card.amount - card.usedAmount, 0));
|
|
4
|
+
}
|
|
5
|
+
function requiresShippingAddress(lines) {
|
|
6
|
+
return lines.length === 0 || lines.some((line) => !line.isDigital);
|
|
7
|
+
}
|
|
8
|
+
function paymentOptions(input) {
|
|
9
|
+
const { cart, features } = input;
|
|
10
|
+
const online = cart.type !== "cod";
|
|
11
|
+
return {
|
|
12
|
+
cashOnDelivery: input.settings.allowCashOnDelivery && !cart.lines.some((l) => l.isDigital),
|
|
13
|
+
giftCards: online && features.includes("giftcard"),
|
|
14
|
+
rewardPoints: online && features.includes("reward-point") && input.rewardPoints.enabled && input.availablePoints > 0 && input.giftCardsSelected !== true
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function checkoutPreview(input) {
|
|
18
|
+
const balances = (input.giftCards ?? []).reduce((sum, card) => sum + giftCardBalance(card), 0);
|
|
19
|
+
const giftCardAmount = Math.min(balances, input.payable);
|
|
20
|
+
const remaining = input.payable - giftCardAmount;
|
|
21
|
+
let pointsUsed = 0;
|
|
22
|
+
const { rewardPoints } = input;
|
|
23
|
+
if (rewardPoints && rewardPoints.currencyPerPoint > 0) {
|
|
24
|
+
const rate = rewardPoints.currencyPerPoint;
|
|
25
|
+
pointsUsed = Math.max(
|
|
26
|
+
Math.min(rewardPoints.available, input.subtotal / rate, remaining / rate),
|
|
27
|
+
0
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
const rewardPointsAmount = pointsUsed * (rewardPoints?.currencyPerPoint ?? 0);
|
|
31
|
+
return {
|
|
32
|
+
giftCardAmount: roundMoney(giftCardAmount),
|
|
33
|
+
rewardPointsAmount: roundMoney(rewardPointsAmount),
|
|
34
|
+
pointsUsed: roundMoney(pointsUsed),
|
|
35
|
+
toPay: roundMoney(Math.max(remaining - rewardPointsAmount, 0))
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function roundMoney(value) {
|
|
39
|
+
return Math.round(value * 100) / 100;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// src/rules/pricing.ts
|
|
43
|
+
function discountPercent({ price, mrp }) {
|
|
44
|
+
if (mrp == null || mrp <= 0 || price <= 0 || price >= mrp) return 0;
|
|
45
|
+
return Math.round((mrp - price) / mrp * 100);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// src/rules/sku.ts
|
|
49
|
+
function resolveSku(skus, selectedVariantIds) {
|
|
50
|
+
const wanted = new Set(selectedVariantIds);
|
|
51
|
+
return skus.find(
|
|
52
|
+
(sku) => sku.active && sku.variantIds.length === wanted.size && sku.variantIds.every((id) => wanted.has(id))
|
|
53
|
+
) ?? null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/rules/specifications.ts
|
|
57
|
+
function specificationsFor(groups, selection) {
|
|
58
|
+
const options = new Set(selection.variantIds);
|
|
59
|
+
return groups.map((group) => ({
|
|
60
|
+
...group,
|
|
61
|
+
items: group.items.filter(
|
|
62
|
+
({ appliesTo }) => appliesTo.type === "all" ? true : appliesTo.type === "variant" ? options.has(appliesTo.variantId) : appliesTo.skuId === selection.skuId
|
|
63
|
+
)
|
|
64
|
+
})).filter((group) => group.items.length > 0);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export {
|
|
68
|
+
giftCardBalance,
|
|
69
|
+
requiresShippingAddress,
|
|
70
|
+
paymentOptions,
|
|
71
|
+
checkoutPreview,
|
|
72
|
+
discountPercent,
|
|
73
|
+
resolveSku,
|
|
74
|
+
specificationsFor
|
|
75
|
+
};
|
|
76
|
+
//# sourceMappingURL=chunk-4PHF3WVQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/rules/checkout.ts","../src/rules/pricing.ts","../src/rules/sku.ts","../src/rules/specifications.ts"],"sourcesContent":["/**\n * Checkout previews: what the shopper sees before placing the order. The backend calculates\n * the cart totals and charges gift cards and reward points itself (ADR-001); these rules only\n * preview the result. The confirmed amount is what `checkout.placeOrder()` returns.\n */\nimport type { CartLine, OrderType } from '../types/cart';\nimport type { GiftCard, RewardPointSettings } from '../types/promotions';\n\ntype GiftCardAmounts = Pick<GiftCard, 'amount' | 'usedAmount'>;\n\n/** What is left on a gift card: amount minus used, never negative. */\nexport function giftCardBalance(card: GiftCardAmounts): number {\n return roundMoney(Math.max(card.amount - card.usedAmount, 0));\n}\n\n/**\n * Whether checkout needs a shipping address: always, unless the cart has lines and every line\n * is digital (the backend's rule, `Order.is_all_digital_order`).\n */\nexport function requiresShippingAddress(lines: readonly Pick<CartLine, 'isDigital'>[]): boolean {\n return lines.length === 0 || lines.some((line) => !line.isDigital);\n}\n\nexport interface PaymentOptionsInput {\n /** A `Cart`, or just its type and the lines' `isDigital`. */\n cart: { type: OrderType; lines: readonly Pick<CartLine, 'isDigital'>[] };\n /** From `checkout.settings()`. */\n settings: { allowCashOnDelivery: boolean };\n rewardPoints: Pick<RewardPointSettings, 'enabled'>;\n /** The customer's reward point balance. */\n availablePoints: number;\n /** The business's enabled features (`giftcard`, `reward-point`, …). */\n features: readonly string[];\n /** Gift cards are selected for this order. */\n giftCardsSelected?: boolean;\n}\n\nexport interface PaymentOptions {\n cashOnDelivery: boolean;\n giftCards: boolean;\n rewardPoints: boolean;\n}\n\n/**\n * Which payment options to offer:\n * - cash on delivery when the business allows it and no line is digital (the backend accepts\n * both anyway, W39);\n * - gift cards and reward points only for online payment (`delivery` carts; the backend refuses\n * them with COD) and when the business has the feature;\n * - reward points also need the setting, a positive balance, and no gift cards selected: the\n * backend over-charges gift cards when both are used (W36; ADR-023).\n */\nexport function paymentOptions(input: PaymentOptionsInput): PaymentOptions {\n const { cart, features } = input;\n const online = cart.type !== 'cod';\n return {\n cashOnDelivery: input.settings.allowCashOnDelivery && !cart.lines.some((l) => l.isDigital),\n giftCards: online && features.includes('giftcard'),\n rewardPoints:\n online &&\n features.includes('reward-point') &&\n input.rewardPoints.enabled &&\n input.availablePoints > 0 &&\n input.giftCardsSelected !== true,\n };\n}\n\nexport interface CheckoutPreviewInput {\n /** `Cart.payable`. */\n payable: number;\n /** `Cart.subtotal`: the backend caps reward points at its value. */\n subtotal: number;\n /** The selected gift cards (from `promotions.validateGiftCard()`). */\n giftCards?: readonly GiftCardAmounts[];\n /** To spend reward points: the balance and `RewardPointSettings.currencyPerPoint`. */\n rewardPoints?: { available: number; currencyPerPoint: number };\n}\n\nexport interface CheckoutPreview {\n giftCardAmount: number;\n rewardPointsAmount: number;\n pointsUsed: number;\n /** What is left to pay online; never negative. */\n toPay: number;\n}\n\n/**\n * Previews gift cards, then reward points, against the payable. Gift cards cover at most the\n * payable; points at most what is left, the subtotal and the balance. Unlike the old\n * storefront, the result never goes below zero.\n */\nexport function checkoutPreview(input: CheckoutPreviewInput): CheckoutPreview {\n const balances = (input.giftCards ?? []).reduce((sum, card) => sum + giftCardBalance(card), 0);\n const giftCardAmount = Math.min(balances, input.payable);\n const remaining = input.payable - giftCardAmount;\n\n let pointsUsed = 0;\n const { rewardPoints } = input;\n if (rewardPoints && rewardPoints.currencyPerPoint > 0) {\n const rate = rewardPoints.currencyPerPoint;\n // The first two caps are the backend's (`calculate_reward_points_debit`); the last keeps\n // the preview from spending points on nothing.\n pointsUsed = Math.max(\n Math.min(rewardPoints.available, input.subtotal / rate, remaining / rate),\n 0,\n );\n }\n const rewardPointsAmount = pointsUsed * (rewardPoints?.currencyPerPoint ?? 0);\n\n return {\n giftCardAmount: roundMoney(giftCardAmount),\n rewardPointsAmount: roundMoney(rewardPointsAmount),\n pointsUsed: roundMoney(pointsUsed),\n toPay: roundMoney(Math.max(remaining - rewardPointsAmount, 0)),\n };\n}\n\nfunction roundMoney(value: number): number {\n return Math.round(value * 100) / 100;\n}\n","/**\n * Pricing rules. The one place a discount is calculated, for every app (ADR-001).\n * T2.1 adds SKU resolution for selected variants on top of this.\n */\n\n/**\n * Whole-percent discount of `price` against `mrp`, e.g. 58999 vs 60000 → 2.\n * 0 when there is no MRP, the item is free, or the price is at or above the MRP (the backend\n * reports a negative discount in that case; a negative discount is never shown).\n */\nexport function discountPercent({ price, mrp }: { price: number; mrp: number | null }): number {\n if (mrp == null || mrp <= 0 || price <= 0 || price >= mrp) return 0;\n return Math.round(((mrp - price) / mrp) * 100);\n}\n","import type { ProductSku } from '../types/product';\n\n/**\n * The SKU a customer is buying: the active SKU made of exactly the selected variant options\n * (order does not matter). A product without variants has one SKU, chosen with no selection.\n * Returns null while the selection is incomplete or matches nothing: never falls back to\n * another SKU (the old storefront's `sku[0]` showed a deleted SKU's price).\n * T2.1 extends this (stock, made-to-order).\n */\nexport function resolveSku(\n skus: readonly ProductSku[],\n selectedVariantIds: readonly number[],\n): ProductSku | null {\n const wanted = new Set(selectedVariantIds);\n return (\n skus.find(\n (sku) =>\n sku.active &&\n sku.variantIds.length === wanted.size &&\n sku.variantIds.every((id) => wanted.has(id)),\n ) ?? null\n );\n}\n","import type { ProductSpecificationGroup } from '../types/product';\n\n/**\n * The specification rows that apply to what the customer has selected: rows for every\n * variant, rows for a selected option, and rows for the resolved SKU. Groups left empty are\n * dropped. Without a resolved SKU (incomplete selection), SKU-specific rows are hidden.\n */\nexport function specificationsFor(\n groups: readonly ProductSpecificationGroup[],\n selection: { skuId: number | null; variantIds: readonly number[] },\n): ProductSpecificationGroup[] {\n const options = new Set(selection.variantIds);\n return groups\n .map((group) => ({\n ...group,\n items: group.items.filter(({ appliesTo }) =>\n appliesTo.type === 'all'\n ? true\n : appliesTo.type === 'variant'\n ? options.has(appliesTo.variantId)\n : appliesTo.skuId === selection.skuId,\n ),\n }))\n .filter((group) => group.items.length > 0);\n}\n"],"mappings":";AAWO,SAAS,gBAAgB,MAA+B;AAC7D,SAAO,WAAW,KAAK,IAAI,KAAK,SAAS,KAAK,YAAY,CAAC,CAAC;AAC9D;AAMO,SAAS,wBAAwB,OAAwD;AAC9F,SAAO,MAAM,WAAW,KAAK,MAAM,KAAK,CAAC,SAAS,CAAC,KAAK,SAAS;AACnE;AA+BO,SAAS,eAAe,OAA4C;AACzE,QAAM,EAAE,MAAM,SAAS,IAAI;AAC3B,QAAM,SAAS,KAAK,SAAS;AAC7B,SAAO;AAAA,IACL,gBAAgB,MAAM,SAAS,uBAAuB,CAAC,KAAK,MAAM,KAAK,CAAC,MAAM,EAAE,SAAS;AAAA,IACzF,WAAW,UAAU,SAAS,SAAS,UAAU;AAAA,IACjD,cACE,UACA,SAAS,SAAS,cAAc,KAChC,MAAM,aAAa,WACnB,MAAM,kBAAkB,KACxB,MAAM,sBAAsB;AAAA,EAChC;AACF;AA0BO,SAAS,gBAAgB,OAA8C;AAC5E,QAAM,YAAY,MAAM,aAAa,CAAC,GAAG,OAAO,CAAC,KAAK,SAAS,MAAM,gBAAgB,IAAI,GAAG,CAAC;AAC7F,QAAM,iBAAiB,KAAK,IAAI,UAAU,MAAM,OAAO;AACvD,QAAM,YAAY,MAAM,UAAU;AAElC,MAAI,aAAa;AACjB,QAAM,EAAE,aAAa,IAAI;AACzB,MAAI,gBAAgB,aAAa,mBAAmB,GAAG;AACrD,UAAM,OAAO,aAAa;AAG1B,iBAAa,KAAK;AAAA,MAChB,KAAK,IAAI,aAAa,WAAW,MAAM,WAAW,MAAM,YAAY,IAAI;AAAA,MACxE;AAAA,IACF;AAAA,EACF;AACA,QAAM,qBAAqB,cAAc,cAAc,oBAAoB;AAE3E,SAAO;AAAA,IACL,gBAAgB,WAAW,cAAc;AAAA,IACzC,oBAAoB,WAAW,kBAAkB;AAAA,IACjD,YAAY,WAAW,UAAU;AAAA,IACjC,OAAO,WAAW,KAAK,IAAI,YAAY,oBAAoB,CAAC,CAAC;AAAA,EAC/D;AACF;AAEA,SAAS,WAAW,OAAuB;AACzC,SAAO,KAAK,MAAM,QAAQ,GAAG,IAAI;AACnC;;;AC7GO,SAAS,gBAAgB,EAAE,OAAO,IAAI,GAAkD;AAC7F,MAAI,OAAO,QAAQ,OAAO,KAAK,SAAS,KAAK,SAAS,IAAK,QAAO;AAClE,SAAO,KAAK,OAAQ,MAAM,SAAS,MAAO,GAAG;AAC/C;;;ACJO,SAAS,WACd,MACA,oBACmB;AACnB,QAAM,SAAS,IAAI,IAAI,kBAAkB;AACzC,SACE,KAAK;AAAA,IACH,CAAC,QACC,IAAI,UACJ,IAAI,WAAW,WAAW,OAAO,QACjC,IAAI,WAAW,MAAM,CAAC,OAAO,OAAO,IAAI,EAAE,CAAC;AAAA,EAC/C,KAAK;AAET;;;ACfO,SAAS,kBACd,QACA,WAC6B;AAC7B,QAAM,UAAU,IAAI,IAAI,UAAU,UAAU;AAC5C,SAAO,OACJ,IAAI,CAAC,WAAW;AAAA,IACf,GAAG;AAAA,IACH,OAAO,MAAM,MAAM;AAAA,MAAO,CAAC,EAAE,UAAU,MACrC,UAAU,SAAS,QACf,OACA,UAAU,SAAS,YACjB,QAAQ,IAAI,UAAU,SAAS,IAC/B,UAAU,UAAU,UAAU;AAAA,IACtC;AAAA,EACF,EAAE,EACD,OAAO,CAAC,UAAU,MAAM,MAAM,SAAS,CAAC;AAC7C;","names":[]}
|