@wtree/payload-ecommerce-coupon 3.70.7 → 3.72.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.
package/README.md CHANGED
@@ -14,14 +14,15 @@ Production-ready coupon and referral system plugin for **Payload CMS** with seam
14
14
  - **Hybrid Mode** (`enableReferrals: true` + `referralConfig.allowBothSystems: true`) – Both systems active
15
15
 
16
16
  ### **Coupon Mode Features**
17
- - ✅ **Flexible Discounts** – Percentage or fixed amount discounts
18
- - ✅ **Usage Controls** – Usage limits; usage is counted when an **order is placed** (not on apply)
17
+ - ✅ **Flexible Discounts** – Percentage or fixed amount discounts (all amounts rounded to 2 decimals)
18
+ - ✅ **Usage Controls** – Global usage limit; usage is counted when an **order is placed** (not on apply)
19
+ - ✅ **Per-customer limit** – Optional limit per customer (requires `customerEmail` when applying)
19
20
  - ✅ **Conditions** – Minimum/maximum order values (top-level fields), product restrictions
20
- - ✅ **Auto-Application** – Seamless cart integration
21
+ - ✅ **Auto-Application** – Seamless cart integration; cart total is reduced when a code is applied
21
22
 
22
23
  ### **Referral Mode Features**
23
- - ✅ **Commission Rules** – **Required**: at least one rule per program; per-product/category commission rates
24
- - ✅ **Referrer/Referee Split** – **Partner (referrer)** receives **commission**; **customer (referee)** receives **discount**; configurable share ratios
24
+ - ✅ **Commission Rules** – **Required.** At least one rule per program. Each rule has **Referrer Reward** (partner commission) and **Referee Reward** (customer discount) inside it, plus appliesTo (all products / categories / products).
25
+ - ✅ **Referrer/Referee inside each rule** – Partner gets commission, customer gets discount; type (percentage/fixed), value, and optional max cap per rule.
25
26
  - ✅ **Partner Tracking** – Commission earnings and referral performance (credited when order is placed)
26
27
  - ✅ **Auto-Generated Codes** – Unique referral codes for each partner
27
28
  - ✅ **Partner Dashboard** – Ready-to-use React components for partner stats
@@ -32,6 +33,7 @@ Production-ready coupon and referral system plugin for **Payload CMS** with seam
32
33
  - ✅ **Frontend Hooks** – `useCouponCode()`, `usePartnerStats()`, `validateCouponCode()` for React/Next.js
33
34
  - ✅ **Auto-Integration** – Extends carts/orders automatically
34
35
  - ✅ **Usage on Order** – Coupon/referral usage and partner earnings are recorded when an order is placed (not when code is applied)
36
+ - ✅ **Cart total helper** – `getCartTotalWithDiscounts(cart)` for host app cart hooks so totals respect discounts
35
37
  - ✅ **Type-Safe** – Full TypeScript support
36
38
  - ✅ **Access Control** – Role-based permissions with partner role support
37
39
  - ✅ **Custom Admin Groups** – Separate "Coupons" and "Referrals" categories
@@ -100,6 +102,14 @@ export default buildConfig({
100
102
  isAdmin: ({ req }) => req.user?.role === 'admin',
101
103
  isPartner: ({ req }) => req.user?.role === 'partner',
102
104
  },
105
+
106
+ // Optional: for per-customer coupon limit (defaults shown)
107
+ // orderIntegration: {
108
+ // ordersSlug: 'orders',
109
+ // orderCustomerEmailField: 'customerEmail',
110
+ // orderPaymentStatusField: 'paymentStatus',
111
+ // orderPaidStatusValue: 'paid',
112
+ // },
103
113
  }),
104
114
  ],
105
115
  })
@@ -183,6 +193,32 @@ if (doc.paymentStatus === 'paid' && (doc.appliedCoupon || doc.appliedReferralCod
183
193
  - **Coupon:** increments the coupon’s `usageCount`.
184
194
  - **Referral:** increments the referral code’s `usageCount` and `successfulReferralsCount`, and adds `order.partnerCommission` to the referral code’s `totalEarnings` and `pendingEarnings` (referrer gets commission; referee discount is already on the order).
185
195
 
196
+ ### 4.5 Coupon usage rules and cart total
197
+
198
+ **Usage rule**
199
+ - A customer can use a coupon until the coupon’s **global usage limit** or **expiry date** (usage is counted when the order is placed, not when the code is applied).
200
+ - **Optional per-customer limit:** If you set **Per customer limit** on a coupon, the customer must provide their email when applying (e.g. `customerEmail` in the apply request). The coupon is rejected once they have that many **paid** orders with that coupon. You can pass `customerEmail` when validating so the UI can show “limit reached” before apply.
201
+
202
+ **Monetary values**
203
+ - All discount, commission, and total values are rounded to **2 decimal places**.
204
+
205
+ **Cart total in your app**
206
+ - The plugin writes the reduced `total` when a code is applied. If your host app recalculates the cart total (e.g. in a `beforeChange` hook when items change), use the formula **total = subtotal − discountAmount − customerDiscount** so the discount is not overwritten. Use the provided helper in your Carts collection:
207
+
208
+ ```typescript
209
+ import { getCartTotalWithDiscounts } from '@wtree/payload-ecommerce-coupon'
210
+
211
+ // In your Carts collection beforeChange hook, after setting items/subtotal:
212
+ data.total = getCartTotalWithDiscounts(data)
213
+ ```
214
+
215
+ - **Optional config** for per-customer limit: `orderIntegration` with `ordersSlug`, `orderCustomerEmailField`, `orderPaymentStatusField`, `orderPaidStatusValue` (defaults: `'orders'`, `'customerEmail'`, `'paymentStatus'`, `'paid'`).
216
+
217
+ **Server utilities (for host app)**
218
+
219
+ - `getCartTotalWithDiscounts(cart)` – Returns `roundTo2(subtotal - discountAmount - customerDiscount)`. Use in your Carts `beforeChange` (or wherever you compute total) so the displayed total always reflects coupon/referral discounts.
220
+ - `recordCouponUsageForOrder(payload, order, pluginConfig)` – Call when an order is paid to increment coupon/referral usage and credit partner earnings (see step 4 above).
221
+
186
222
  ### 5. Frontend Integration
187
223
 
188
224
  #### Apply Coupon/Referral Code
@@ -198,6 +234,8 @@ function CheckoutComponent() {
198
234
  const result = await useCouponCode({
199
235
  code,
200
236
  cartID: cartId,
237
+ // When a coupon has per-customer limit, pass customerEmail so the limit can be enforced
238
+ // customerEmail: customerEmailFromAuthOrForm,
201
239
  })
202
240
 
203
241
  if (result.success) {
@@ -297,30 +335,13 @@ Best when you need both traditional coupons AND partner referrals, but want to e
297
335
  ### **Setting Up Referral Mode**
298
336
 
299
337
  1. **Navigate to Admin Panel** → Go to "Referral Programs" (under "Referrals" group)
300
- 2. **Create Referral Program**:
301
- - **Name**: "Partner Affiliate Program"
302
- - **Description**: "Earn commissions by referring customers"
303
- - **Is Active**: Enable/disable program
304
- - **Commission Rules**: **Required** at least one rule per program (product/category-specific or "all products"). Each rule defines total commission and split between partner and customer.
305
-
306
- 3. **Configure Commission Rules** (required at least one):
307
- ```json
308
- {
309
- "name": "Electronics Category",
310
- "appliesTo": "categories",
311
- "categories": ["electronics"],
312
- "totalCommission": {
313
- "type": "percentage",
314
- "value": 15
315
- },
316
- "split": {
317
- "partnerPercentage": 70,
318
- "customerPercentage": 30
319
- }
320
- }
321
- ```
322
- - **Referrer (partner)** receives the **commission** share (`partnerPercentage`).
323
- - **Referee (customer)** receives the **discount** share (`customerPercentage`).
338
+ 2. **Create Referral Program** with **Commission Rules** (required – at least one). Each rule has:
339
+ - **Name**: e.g. "Default" or "Electronics"
340
+ - **Applies To**: All Products, Specific Categories, or Specific Products
341
+ - **Referrer Reward** (inside the rule): Commission for the partner. Type: Percentage of Order or Fixed Amount. Value: e.g. 65 = 65% of order value. Optional Max Reward.
342
+ - **Referee Reward** (inside the rule): Discount for the customer. Type: Percentage Discount or Fixed Amount. Value: e.g. 30 = 30% off. Optional Max Reward.
343
+
344
+ There are no outer Referrer Reward / Referee Reward fields – only **Commission Rules**, and each rule contains its own Referrer Reward and Referee Reward.
324
345
 
325
346
  ### **Commission and Discount (Referrer / Referee)**
326
347
 
@@ -345,21 +366,27 @@ Best when you need both traditional coupons AND partner referrals, but want to e
345
366
  ### **Coupon/Referral Endpoints**
346
367
 
347
368
  #### POST /api/coupons/validate
348
- Validate a code without applying it.
369
+ Validate a code without applying it. Optionally pass `customerEmail` to check per-customer limit for coupons that have one.
349
370
 
350
371
  ```bash
351
372
  curl -X POST http://localhost:3000/api/coupons/validate \
352
373
  -H "Content-Type: application/json" \
353
374
  -d '{"code": "WELCOME10", "cartValue": 5000}'
375
+
376
+ # With per-customer limit check:
377
+ # -d '{"code": "WELCOME10", "cartValue": 5000, "customerEmail": "user@example.com"}'
354
378
  ```
355
379
 
356
380
  #### POST /api/coupons/apply
357
- Apply a code to a cart. **Does not** increment usage; usage is recorded when you call the record-order-usage endpoint for a placed order.
381
+ Apply a code to a cart. **Does not** increment usage; usage is recorded when you call the record-order-usage endpoint for a placed order. For coupons with **per-customer limit**, include `customerEmail` so the limit can be enforced.
358
382
 
359
383
  ```bash
360
384
  curl -X POST http://localhost:3000/api/coupons/apply \
361
385
  -H "Content-Type: application/json" \
362
386
  -d '{"code": "WELCOME10", "cartID": "cart-123"}'
387
+
388
+ # With per-customer limit (required when coupon has per-customer limit):
389
+ # -d '{"code": "WELCOME10", "cartID": "cart-123", "customerEmail": "user@example.com"}'
363
390
  ```
364
391
 
365
392
  #### POST /api/coupons/record-order-usage
@@ -471,6 +498,14 @@ export type CouponPluginOptions = {
471
498
  showRecentReferrals?: boolean // Show recent referrals (default: true)
472
499
  showCommissionBreakdown?: boolean // Show breakdown (default: true)
473
500
  }
501
+
502
+ /** Optional: for per-customer coupon limit (query paid orders by customer) */
503
+ orderIntegration?: {
504
+ ordersSlug?: string // Default: 'orders'
505
+ orderCustomerEmailField?: string // Default: 'customerEmail'
506
+ orderPaymentStatusField?: string // Default: 'paymentStatus'
507
+ orderPaidStatusValue?: string // Default: 'paid'
508
+ }
474
509
  }
475
510
  ```
476
511
 
@@ -634,10 +669,8 @@ This occurs when `singleCodePerCart: true` and a code is already applied.
634
669
  - Usage is **not** incremented when a code is applied to the cart. Call **record-order-usage** (or `recordCouponUsageForOrder`) when an order is placed/paid. See [Record usage when order is placed](#4-record-usage-when-order-is-placed).
635
670
 
636
671
  #### **Commission not calculating correctly**
637
- - At least **one commission rule is required** per referral program
638
- - Verify commission rules (product/category/all) are configured
639
- - Check that products have correct category assignments
640
- - Ensure cart has valid `subtotal` or `total` field
672
+ - Ensure at least one **Commission Rule** exists and each rule has **Referrer Reward** and **Referee Reward** set
673
+ - Verify cart has valid `subtotal` or `total` and items match rule’s appliesTo (all / categories / products)
641
674
 
642
675
  ## 📋 Future Features (Roadmap)
643
676
 
@@ -1 +1 @@
1
- {"version":3,"file":"createReferralProgramsCollection.d.ts","sourceRoot":"","sources":["../../src/collections/createReferralProgramsCollection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAE/C,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,UAAU,CAAA;AAE5D,eAAO,MAAM,gCAAgC,GAC3C,cAAc,4BAA4B,KACzC,gBAoTF,CAAA"}
1
+ {"version":3,"file":"createReferralProgramsCollection.d.ts","sourceRoot":"","sources":["../../src/collections/createReferralProgramsCollection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAE/C,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,UAAU,CAAA;AAE5D,eAAO,MAAM,gCAAgC,GAC3C,cAAc,4BAA4B,KACzC,gBA4PF,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"applyCoupon.d.ts","sourceRoot":"","sources":["../../src/endpoints/applyCoupon.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAEvD,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,UAAU,CAAA;AAE5D,KAAK,IAAI,GAAG;IACV,YAAY,EAAE,4BAA4B,CAAA;CAC3C,CAAA;AAED,eAAO,MAAM,kBAAkB,GAC5B,kBAAkB,IAAI,KAAG,cAsFzB,CAAA;AAyXH,eAAO,MAAM,mBAAmB,GAAI,kBAAkB,IAAI,KAAG,QAI3D,CAAA"}
1
+ {"version":3,"file":"applyCoupon.d.ts","sourceRoot":"","sources":["../../src/endpoints/applyCoupon.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAEvD,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,UAAU,CAAA;AAG5D,KAAK,IAAI,GAAG;IACV,YAAY,EAAE,4BAA4B,CAAA;CAC3C,CAAA;AAED,eAAO,MAAM,kBAAkB,GAC5B,kBAAkB,IAAI,KAAG,cAsFzB,CAAA;AA6XH,eAAO,MAAM,mBAAmB,GAAI,kBAAkB,IAAI,KAAG,QAI3D,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"partnerStats.d.ts","sourceRoot":"","sources":["../../src/endpoints/partnerStats.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAEvD,OAAO,KAAK,EAAsC,4BAA4B,EAAE,MAAM,UAAU,CAAA;AAEhG,KAAK,IAAI,GAAG;IACV,YAAY,EAAE,4BAA4B,CAAA;CAC3C,CAAA;AAED,eAAO,MAAM,mBAAmB,GAC7B,kBAAkB,IAAI,KAAG,cAoKzB,CAAA;AAEH,eAAO,MAAM,oBAAoB,GAAI,kBAAkB,IAAI,KAAG,QAI5D,CAAA"}
1
+ {"version":3,"file":"partnerStats.d.ts","sourceRoot":"","sources":["../../src/endpoints/partnerStats.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAEvD,OAAO,KAAK,EAAsC,4BAA4B,EAAE,MAAM,UAAU,CAAA;AAEhG,KAAK,IAAI,GAAG;IACV,YAAY,EAAE,4BAA4B,CAAA;CAC3C,CAAA;AAED,eAAO,MAAM,mBAAmB,GAC7B,kBAAkB,IAAI,KAAG,cAqKzB,CAAA;AAEH,eAAO,MAAM,oBAAoB,GAAI,kBAAkB,IAAI,KAAG,QAI5D,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"validateCoupon.d.ts","sourceRoot":"","sources":["../../src/endpoints/validateCoupon.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAEvD,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,UAAU,CAAA;AAE5D,KAAK,IAAI,GAAG;IACV,YAAY,EAAE,4BAA4B,CAAA;CAC3C,CAAA;AAED,eAAO,MAAM,qBAAqB,GAC/B,kBAAkB,IAAI,KAAG,cA2BzB,CAAA;AA0OH,eAAO,MAAM,sBAAsB,GAAI,kBAAkB,IAAI,KAAG,QAI9D,CAAA"}
1
+ {"version":3,"file":"validateCoupon.d.ts","sourceRoot":"","sources":["../../src/endpoints/validateCoupon.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAEvD,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,UAAU,CAAA;AAG5D,KAAK,IAAI,GAAG;IACV,YAAY,EAAE,4BAA4B,CAAA;CAC3C,CAAA;AAED,eAAO,MAAM,qBAAqB,GAC/B,kBAAkB,IAAI,KAAG,cAiCzB,CAAA;AAkRH,eAAO,MAAM,sBAAsB,GAAI,kBAAkB,IAAI,KAAG,QAI9D,CAAA"}
package/dist/index.d.ts CHANGED
@@ -4,5 +4,8 @@ import { createReferralProgramsCollection } from './collections/createReferralPr
4
4
  import { payloadEcommerceCouponPlugin } from './plugin';
5
5
  export { useCouponCode, usePartnerStats, validateCouponCode } from './client/hooks';
6
6
  export { createCouponsCollection, createReferralCodesCollection, createReferralProgramsCollection, payloadEcommerceCouponPlugin as payloadEcommerceCoupon, };
7
- export type { AdminGroupConfig, ApplyCouponHook, ApplyCouponResponse, CouponPluginAccess, CouponPluginCollections, CouponPluginOptions, PartnerDashboardConfig, PartnerDashboardData, PartnerStats, ReferralProgramConfig, } from './types';
7
+ export { getCartTotalWithDiscounts } from './utilities/getCartTotalWithDiscounts';
8
+ export { recordCouponUsageForOrder } from './utilities/recordCouponUsageForOrder';
9
+ export type { AdminGroupConfig, ApplyCouponHook, ApplyCouponResponse, CouponPluginAccess, CouponPluginCollections, CouponPluginOptions, OrderIntegrationConfig, PartnerDashboardConfig, PartnerDashboardData, PartnerStats, ReferralProgramConfig, } from './types';
10
+ export type { CartLike } from './utilities/getCartTotalWithDiscounts';
8
11
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,uCAAuC,CAAA;AAC/E,OAAO,EAAE,6BAA6B,EAAE,MAAM,6CAA6C,CAAA;AAC3F,OAAO,EAAE,gCAAgC,EAAE,MAAM,gDAAgD,CAAA;AACjG,OAAO,EAAE,4BAA4B,EAAE,MAAM,UAAU,CAAA;AAEvD,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AACnF,OAAO,EACL,uBAAuB,EACvB,6BAA6B,EAC7B,gCAAgC,EAChC,4BAA4B,IAAI,sBAAsB,GACvD,CAAA;AAED,YAAY,EACV,gBAAgB,EAChB,eAAe,EACf,mBAAmB,EACnB,kBAAkB,EAClB,uBAAuB,EACvB,mBAAmB,EACnB,sBAAsB,EACtB,oBAAoB,EACpB,YAAY,EACZ,qBAAqB,GACtB,MAAM,SAAS,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,uCAAuC,CAAA;AAC/E,OAAO,EAAE,6BAA6B,EAAE,MAAM,6CAA6C,CAAA;AAC3F,OAAO,EAAE,gCAAgC,EAAE,MAAM,gDAAgD,CAAA;AACjG,OAAO,EAAE,4BAA4B,EAAE,MAAM,UAAU,CAAA;AAEvD,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AACnF,OAAO,EACL,uBAAuB,EACvB,6BAA6B,EAC7B,gCAAgC,EAChC,4BAA4B,IAAI,sBAAsB,GACvD,CAAA;AACD,OAAO,EAAE,yBAAyB,EAAE,MAAM,uCAAuC,CAAA;AACjF,OAAO,EAAE,yBAAyB,EAAE,MAAM,uCAAuC,CAAA;AAEjF,YAAY,EACV,gBAAgB,EAChB,eAAe,EACf,mBAAmB,EACnB,kBAAkB,EAClB,uBAAuB,EACvB,mBAAmB,EACnB,sBAAsB,EACtB,sBAAsB,EACtB,oBAAoB,EACpB,YAAY,EACZ,qBAAqB,GACtB,MAAM,SAAS,CAAA;AAChB,YAAY,EAAE,QAAQ,EAAE,MAAM,uCAAuC,CAAA"}