@lacspace/coupon 1.0.0 → 1.0.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/README.md +66 -43
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,19 +1,35 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
1
3
|
# @lacspace/coupon
|
|
2
4
|
|
|
3
|
-
A
|
|
5
|
+
**A discount / coupon engine — percent, fixed & free-shipping codes with validity windows, minimum-subtotal thresholds, discount caps and usage limits.**
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/@lacspace/coupon)
|
|
8
|
+
[](https://packagephobia.com/result?p=@lacspace/coupon)
|
|
9
|
+
[](https://bundlephobia.com/package/@lacspace/coupon)
|
|
10
|
+
[](https://www.npmjs.com/package/@lacspace/coupon)
|
|
11
|
+
[](https://github.com/lacspace/npm-packages/blob/main/LICENSE)
|
|
12
|
+
|
|
13
|
+
</div>
|
|
4
14
|
|
|
5
|
-
- **
|
|
6
|
-
|
|
7
|
-
- **
|
|
15
|
+
> The discount logic every checkout re-implements badly: is this code valid right now, and what does it actually take off the total? A tiny pair of **pure functions** over a plain `Coupon` object — validate the window / threshold / usage limit, then compute `percent`, `fixed` or `free-shipping` discounts. Integer **minor units** throughout, so there's no floating-point drift.
|
|
16
|
+
|
|
17
|
+
- 🏷️ **Three code kinds** — `percent`, `fixed` (flat amount) and `free-shipping`
|
|
18
|
+
- ⏱️ **Validity window** — `startsAt` / `endsAt` (ISO-8601), plus `minSubtotal` threshold and `usageLimit`
|
|
19
|
+
- 🧢 **Discount caps** — `maxDiscount`, and the discount is never more than the subtotal
|
|
20
|
+
- 🪙 **Exact money** — integer **minor units** (cents / paisa) everywhere, never a float
|
|
21
|
+
- ⚡ Isomorphic — Node, edge runtimes & browsers · 📦 ESM + CJS · fully typed · zero deps
|
|
22
|
+
|
|
23
|
+
## Install
|
|
8
24
|
|
|
9
25
|
```bash
|
|
10
|
-
npm install @lacspace/coupon
|
|
26
|
+
npm install @lacspace/coupon # or pnpm add / yarn add / bun add
|
|
11
27
|
```
|
|
12
28
|
|
|
13
|
-
##
|
|
29
|
+
## Apply a coupon
|
|
14
30
|
|
|
15
31
|
```ts
|
|
16
|
-
import { applyCoupon,
|
|
32
|
+
import { applyCoupon, type Coupon } from "@lacspace/coupon";
|
|
17
33
|
|
|
18
34
|
const coupon: Coupon = {
|
|
19
35
|
code: "SAVE20",
|
|
@@ -24,58 +40,65 @@ const coupon: Coupon = {
|
|
|
24
40
|
};
|
|
25
41
|
|
|
26
42
|
applyCoupon(coupon, { subtotal: 3000, shipping: 400 });
|
|
27
|
-
// { valid: true, discount: 500, shippingDiscount: 0, total: 2900 }
|
|
43
|
+
// → { valid: true, discount: 500, shippingDiscount: 0, total: 2900 }
|
|
44
|
+
// 20% of 3000 is 600, but maxDiscount caps it at 500
|
|
45
|
+
// total = max(0, subtotal - discount + shipping - shippingDiscount)
|
|
28
46
|
```
|
|
29
47
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
### `validateCoupon(coupon, { subtotal, now? }): { valid, reason? }`
|
|
48
|
+
`percent` is `round(subtotal * value / 100)`, `fixed` is `min(value, subtotal)` — both capped by `maxDiscount` and clamped to the subtotal:
|
|
33
49
|
|
|
34
|
-
|
|
50
|
+
```ts
|
|
51
|
+
// flat $5.00 off (value is in minor units)
|
|
52
|
+
applyCoupon({ code: "FIVER", type: "fixed", value: 500 }, { subtotal: 1200 });
|
|
53
|
+
// → { valid: true, discount: 500, shippingDiscount: 0, total: 700 }
|
|
35
54
|
|
|
36
|
-
|
|
55
|
+
// free shipping zeroes the shipping line only
|
|
56
|
+
applyCoupon({ code: "FREESHIP", type: "free-shipping" }, { subtotal: 1000, shipping: 300 });
|
|
57
|
+
// → { valid: true, discount: 0, shippingDiscount: 300, total: 1000 }
|
|
58
|
+
```
|
|
37
59
|
|
|
38
|
-
|
|
60
|
+
## Validate before applying
|
|
39
61
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
| `percent` | `round(subtotal * value / 100)`, capped by `maxDiscount` and the subtotal |
|
|
43
|
-
| `fixed` | `min(value, subtotal)`, capped by `maxDiscount` |
|
|
44
|
-
| `free-shipping` | `shippingDiscount = shipping` |
|
|
62
|
+
```ts
|
|
63
|
+
import { validateCoupon } from "@lacspace/coupon";
|
|
45
64
|
|
|
46
|
-
|
|
47
|
-
|
|
65
|
+
validateCoupon(
|
|
66
|
+
{ code: "XMAS", type: "percent", value: 10, endsAt: "2025-12-26T00:00:00Z" },
|
|
67
|
+
{ subtotal: 5000, now: new Date("2026-01-01") },
|
|
68
|
+
);
|
|
69
|
+
// → { valid: false, reason: "expired" }
|
|
48
70
|
```
|
|
49
71
|
|
|
50
|
-
|
|
72
|
+
`validateCoupon` checks the window (`not-yet-started` / `expired`), `minSubtotal` (`below-min-subtotal`) and `usageLimit` vs `used` (`usage-limit-reached`) — it does **not** compute a discount. `applyCoupon` runs it first; when a coupon is invalid it returns zero discounts and the untouched `subtotal + shipping`.
|
|
51
73
|
|
|
52
|
-
|
|
53
|
-
applyCoupon({ code: "FREESHIP", type: "free-shipping" }, { subtotal: 1000, shipping: 300 });
|
|
54
|
-
// { valid: true, discount: 0, shippingDiscount: 300, total: 1000 }
|
|
55
|
-
```
|
|
74
|
+
## API
|
|
56
75
|
|
|
57
|
-
|
|
76
|
+
| Function | Description |
|
|
77
|
+
| --- | --- |
|
|
78
|
+
| `validateCoupon(coupon, { subtotal, now? })` | `{ valid, reason? }` — checks window, `minSubtotal`, `usageLimit`; no discount computed |
|
|
79
|
+
| `applyCoupon(coupon, { subtotal, shipping?, now? })` | `{ valid, reason?, discount, shippingDiscount, total }` in minor units |
|
|
58
80
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
81
|
+
| `type` | discount |
|
|
82
|
+
| --- | --- |
|
|
83
|
+
| `percent` | `round(subtotal * value / 100)`, capped by `maxDiscount` and the subtotal |
|
|
84
|
+
| `fixed` | `min(value, subtotal)`, capped by `maxDiscount` |
|
|
85
|
+
| `free-shipping` | `shippingDiscount = shipping` |
|
|
86
|
+
|
|
87
|
+
`total = max(0, subtotal - discount + shipping - shippingDiscount)`. `value` is a whole percent `0..100` for `percent`, or an amount in minor units for `fixed` (ignored for `free-shipping`). Types exported: `Coupon`, `CouponValidation`, `CouponResult`.
|
|
88
|
+
|
|
89
|
+
## Licensing
|
|
90
|
+
|
|
91
|
+
This package is **free** under the **[Lacspace Free Licence](https://developer.lacspace.com/licenses/lacspace-free-1.0)** — permissive freedoms. Use it in personal and commercial projects at no cost; just keep the notice.
|
|
92
|
+
|
|
93
|
+
Not every Lacspace package is free. We also offer **Commercial** (paid), **Client-specific**, and **Private** (proprietary) packages under separate terms. See the full **[Lacspace Licence Centre](https://developer.lacspace.com/licenses)**.
|
|
94
|
+
|
|
95
|
+
<!-- LACSPACE-DEV-PLATFORM -->
|
|
73
96
|
|
|
74
97
|
---
|
|
75
98
|
|
|
76
99
|
## The Lacspace Developer Platform
|
|
77
100
|
|
|
78
|
-
`@lacspace/coupon` is part of **
|
|
101
|
+
`@lacspace/coupon` is part of **80+ zero-dependency, isomorphic TypeScript packages**. Explore the ecosystem:
|
|
79
102
|
|
|
80
103
|
- 🗂️ **All packages** — https://developer.lacspace.com/packages
|
|
81
104
|
- 🧭 **Developer handbook** — https://developer.lacspace.com/handbook
|
|
@@ -83,4 +106,4 @@ interface Coupon {
|
|
|
83
106
|
- 🖥️ **Finished app templates** — https://templates.lacspace.com
|
|
84
107
|
- 🚀 **Scaffold a full app** — `npm create lacspace-app@latest`
|
|
85
108
|
|
|
86
|
-
Free under the **[Lacspace Free Licence](https://lacspace.com/licenses/lacspace-free-1.0)** — a permissive, free-to-use licence.
|
|
109
|
+
Free under the **[Lacspace Free Licence](https://developer.lacspace.com/licenses/lacspace-free-1.0)** — a permissive, free-to-use licence.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lacspace/coupon",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Discount & coupon engine — percent, fixed and free-shipping codes with validity windows, minimum-subtotal, discount caps and usage limits. Integer minor units, no float drift. Zero-dependency, isomorphic.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|