@wtree/payload-ecommerce-coupon 3.77.4 β 3.77.6
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 +274 -684
- package/dist/client/hooks.d.ts +21 -13
- package/dist/client/hooks.d.ts.map +1 -1
- package/dist/client/index.d.ts +6 -6
- package/dist/client/index.d.ts.map +1 -1
- package/dist/collections/createCouponsCollection.d.ts +2 -2
- package/dist/collections/createCouponsCollection.d.ts.map +1 -1
- package/dist/collections/createReferralCodesCollection.d.ts +2 -2
- package/dist/collections/createReferralCodesCollection.d.ts.map +1 -1
- package/dist/collections/createReferralProgramsCollection.d.ts +2 -2
- package/dist/collections/createReferralProgramsCollection.d.ts.map +1 -1
- package/dist/components/PartnerDashboard/EarningsSummary.d.ts +2 -2
- package/dist/components/PartnerDashboard/EarningsSummary.d.ts.map +1 -1
- package/dist/components/PartnerDashboard/RecentReferrals.d.ts +3 -3
- package/dist/components/PartnerDashboard/RecentReferrals.d.ts.map +1 -1
- package/dist/components/PartnerDashboard/ReferralCodes.d.ts +3 -3
- package/dist/components/PartnerDashboard/ReferralCodes.d.ts.map +1 -1
- package/dist/components/PartnerDashboard/ReferralPerformance.d.ts +2 -2
- package/dist/components/PartnerDashboard/ReferralPerformance.d.ts.map +1 -1
- package/dist/components/PartnerDashboard/index.d.ts +2 -2
- package/dist/components/PartnerDashboard/index.d.ts.map +1 -1
- package/dist/endpoints/applyCoupon.d.ts +2 -2
- package/dist/endpoints/applyCoupon.d.ts.map +1 -1
- package/dist/endpoints/partnerStats.d.ts +2 -2
- package/dist/endpoints/partnerStats.d.ts.map +1 -1
- package/dist/endpoints/validateCoupon.d.ts +2 -2
- package/dist/endpoints/validateCoupon.d.ts.map +1 -1
- package/dist/hooks/recalculateCart.d.ts +2 -2
- package/dist/hooks/recalculateCart.d.ts.map +1 -1
- package/dist/index.d.ts +10 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1123 -543
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1122 -541
- package/dist/index.mjs.map +1 -1
- package/dist/plugin.d.ts +2 -2
- package/dist/plugin.d.ts.map +1 -1
- package/dist/types.d.ts +103 -9
- package/dist/types.d.ts.map +1 -1
- package/dist/utilities/calculateValues.d.ts +2 -2
- package/dist/utilities/calculateValues.d.ts.map +1 -1
- package/dist/utilities/getCartTotalWithDiscounts.d.ts.map +1 -1
- package/dist/utilities/migrateReferralRulesV2.d.ts.map +1 -1
- package/dist/utilities/pricing.d.ts.map +1 -1
- package/dist/utilities/pushTypeScriptProperties.d.ts +1 -1
- package/dist/utilities/pushTypeScriptProperties.d.ts.map +1 -1
- package/dist/utilities/recordCouponUsageForOrder.d.ts +11 -17
- package/dist/utilities/recordCouponUsageForOrder.d.ts.map +1 -1
- package/dist/utilities/sanitizePluginConfig.d.ts +1 -1
- package/dist/utilities/sanitizePluginConfig.d.ts.map +1 -1
- package/dist/utilities/userRoles.d.ts +3 -3
- package/dist/utilities/userRoles.d.ts.map +1 -1
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -4,779 +4,369 @@
|
|
|
4
4
|
[](./LICENSE)
|
|
5
5
|
[](https://nodejs.org)
|
|
6
6
|
|
|
7
|
-
Production-ready coupon and referral
|
|
7
|
+
Production-ready coupon and referral plugin for **Payload CMS** + **@payloadcms/plugin-ecommerce** with a **policy-first**, **integration-driven** architecture.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
---
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
## Why this version
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
- **Referral Mode** (`enableReferrals: true`) β Partner commissions + customer discounts
|
|
15
|
-
- **Hybrid Mode** (`enableReferrals: true` + `referralConfig.allowBothSystems: true`) β Both systems active
|
|
13
|
+
This release removes hard assumptions and gives you explicit control:
|
|
16
14
|
|
|
17
|
-
|
|
15
|
+
- **Policy-first access** (no required role shape)
|
|
16
|
+
- **Integration-driven mapping** for collection slugs, field names, and data resolvers
|
|
17
|
+
- **Deterministic code validation/apply flow** with normalized code lookup
|
|
18
|
+
- **Dependency-free runtime** (beyond Payload + Payload Ecommerce peer deps)
|
|
19
|
+
- **Usage accounting on order placement** (not on cart apply)
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
- β
**Usage Controls** β Global usage limit; usage is counted when an **order is placed** (not on apply)
|
|
21
|
-
- β
**Per-customer limit** β Optional limit per customer (requires `customerEmail` when applying)
|
|
22
|
-
- β
**Conditions** β Minimum/maximum order values (top-level fields), product restrictions
|
|
23
|
-
- β
**Auto-Application** β Seamless cart integration; cart total is reduced when a code is applied
|
|
21
|
+
---
|
|
24
22
|
|
|
25
|
-
|
|
23
|
+
## Features
|
|
26
24
|
|
|
27
|
-
|
|
28
|
-
- **Direct Basis**: Separate Reward (Partner) and Referee Reward (Customer).
|
|
29
|
-
- **Shared Basis**: Define a total "pot" (e.g., 20% of order) and split it (e.g., 50/50) between partner and customer.
|
|
30
|
-
- β
**Rule-level Minimum Order** β Optional `minOrderAmount` per commission rule, validated on apply/validate/recalculate
|
|
31
|
-
- β
**Fixed-only Mode** β Restrict `totalCommission.type` to fixed only via `referralConfig.allowedTotalCommissionTypes: ['fixed']`
|
|
32
|
-
- β
**Referrer/Referee inside each rule** β Partner gets commission, customer gets discount; type (percentage/fixed), value, and optional max cap per rule.
|
|
33
|
-
- β
**Partner Tracking** β Commission earnings and referral performance (credited when order is placed)
|
|
34
|
-
- β
**Auto-Generated Codes** β Unique referral codes for each partner
|
|
35
|
-
- β
**Partner Dashboard** β Ready-to-use React components for partner stats
|
|
36
|
-
- β
**Single Code Per Cart** β Enforce one code (coupon or referral) per order
|
|
25
|
+
### System modes
|
|
37
26
|
|
|
38
|
-
|
|
27
|
+
- **Coupon Mode** (`enableReferrals: false`)
|
|
28
|
+
- **Referral Mode** (`enableReferrals: true`)
|
|
29
|
+
- **Hybrid Mode** (`enableReferrals: true` + `referralConfig.allowBothSystems: true`)
|
|
39
30
|
|
|
40
|
-
|
|
41
|
-
- β
**Frontend Hooks** β `useCouponCode()`, `usePartnerStats()`, `validateCouponCode()` for React/Next.js
|
|
42
|
-
- β
**Auto-Integration** β Extends carts/orders automatically
|
|
43
|
-
- β
**Usage on Order** β Coupon/referral usage and partner earnings are recorded when an order is placed (not when code is applied)
|
|
44
|
-
- β
**Cart total helper** β `getCartTotalWithDiscounts(cart)` for host app cart hooks so totals respect discounts
|
|
45
|
-
- β
**Automatic Cart Recalculation** β Hook ensures commissions and discounts are recalculated whenever cart items change
|
|
46
|
-
- β
**Type-Safe** β Full TypeScript support
|
|
47
|
-
- β
**Access Control** β Role-based permissions with partner role support
|
|
48
|
-
- β
**Custom Admin Groups** β Separate "Coupons" and "Referrals" categories
|
|
49
|
-
- β
**Production-Ready** β Comprehensive testing and error handling
|
|
31
|
+
### Coupon capabilities
|
|
50
32
|
|
|
51
|
-
|
|
33
|
+
- Percentage and fixed discounts
|
|
34
|
+
- Global usage limits
|
|
35
|
+
- Optional per-customer limits
|
|
36
|
+
- Min/max order rules
|
|
37
|
+
- Code normalization (`trim + uppercase`) with fallback lookups
|
|
38
|
+
- Discount totals rounded to 2 decimals
|
|
52
39
|
|
|
53
|
-
|
|
54
|
-
npm install @wtree/payload-ecommerce-coupon
|
|
55
|
-
```
|
|
40
|
+
### Referral capabilities
|
|
56
41
|
|
|
57
|
-
|
|
42
|
+
- Program/rule-driven commissions and customer discounts
|
|
43
|
+
- Rule min-order enforcement
|
|
44
|
+
- Fixed-only or mixed commission type support
|
|
45
|
+
- Partner stats endpoint
|
|
46
|
+
- Optional single code per cart across systems
|
|
58
47
|
|
|
59
|
-
|
|
60
|
-
- `@payloadcms/plugin-ecommerce@>=3.0.0` (required peer dependency)
|
|
61
|
-
- `node@>=18.0.0`
|
|
48
|
+
### Core plugin behavior
|
|
62
49
|
|
|
63
|
-
|
|
50
|
+
- Auto-adds collections and endpoints (configurable)
|
|
51
|
+
- Auto-integrates cart/order fields (configurable)
|
|
52
|
+
- Recalculate hook support for cart totals/discounts/commissions
|
|
53
|
+
- Server utility for usage recording at order completion
|
|
54
|
+
- Frontend helpers for validate/apply/stats
|
|
64
55
|
|
|
65
|
-
|
|
56
|
+
---
|
|
66
57
|
|
|
67
|
-
|
|
58
|
+
## Requirements
|
|
68
59
|
|
|
69
|
-
|
|
70
|
-
import { buildConfig } from "payload";
|
|
71
|
-
import { ecommercePlugin } from "@payloadcms/plugin-ecommerce";
|
|
72
|
-
import { payloadEcommerceCoupon } from "@wtree/payload-ecommerce-coupon";
|
|
60
|
+
### Runtime requirements
|
|
73
61
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
// your ecommerce configuration
|
|
78
|
-
}),
|
|
79
|
-
payloadEcommerceCoupon({
|
|
80
|
-
enabled: true,
|
|
81
|
-
enableReferrals: true, // Enable referral system
|
|
82
|
-
defaultCurrency: "USD",
|
|
62
|
+
- `node >= 18.0.0`
|
|
63
|
+
- `payload ^3.79.0` (peer dependency)
|
|
64
|
+
- `@payloadcms/plugin-ecommerce ^3.79.0` (peer dependency)
|
|
83
65
|
|
|
84
|
-
|
|
85
|
-
referralConfig: {
|
|
86
|
-
allowBothSystems: false, // Set true to allow both coupons and referrals
|
|
87
|
-
singleCodePerCart: true, // Only one code per order
|
|
88
|
-
defaultPartnerSplit: 70, // 70% to partner
|
|
89
|
-
defaultCustomerSplit: 30, // 30% discount to customer
|
|
90
|
-
// Set fixed-only mode for production partner programs
|
|
91
|
-
allowedTotalCommissionTypes: ["fixed"], // Default: ["fixed", "percentage"]
|
|
92
|
-
},
|
|
66
|
+
### Project assumptions
|
|
93
67
|
|
|
94
|
-
|
|
95
|
-
adminGroups: {
|
|
96
|
-
couponsGroup: "Coupons",
|
|
97
|
-
referralsGroup: "Referrals",
|
|
98
|
-
},
|
|
68
|
+
Your app should include (or map via integration config):
|
|
99
69
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
showRecentReferrals: true,
|
|
106
|
-
showCommissionBreakdown: true,
|
|
107
|
-
},
|
|
70
|
+
- cart collection
|
|
71
|
+
- order collection
|
|
72
|
+
- product collection
|
|
73
|
+
- user collection
|
|
74
|
+
- category + tag collections (if used by referral rules)
|
|
108
75
|
|
|
109
|
-
|
|
110
|
-
access: {
|
|
111
|
-
canUseCoupons: () => true,
|
|
112
|
-
canUseReferrals: () => true,
|
|
113
|
-
isAdmin: ({ req }) => req.user?.role === "admin",
|
|
114
|
-
isPartner: ({ req }) => req.user?.role === "partner",
|
|
115
|
-
},
|
|
76
|
+
### Recommended
|
|
116
77
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
// roleFieldPaths: ["role", "roles", "account.roles"],
|
|
120
|
-
// adminRoleValues: ["admin"],
|
|
121
|
-
// partnerRoleValues: ["partner", "affiliate"],
|
|
122
|
-
// },
|
|
123
|
-
|
|
124
|
-
// Optional: for per-customer coupon limit (defaults shown)
|
|
125
|
-
// orderIntegration: {
|
|
126
|
-
// ordersSlug: 'orders',
|
|
127
|
-
// orderCustomerEmailField: 'customerEmail',
|
|
128
|
-
// orderPaymentStatusField: 'paymentStatus',
|
|
129
|
-
// orderPaidStatusValue: 'paid',
|
|
130
|
-
// },
|
|
131
|
-
}),
|
|
132
|
-
],
|
|
133
|
-
});
|
|
134
|
-
```
|
|
78
|
+
- Node 20 LTS for production
|
|
79
|
+
- Payload and ecommerce plugin versions aligned within the same major/minor range
|
|
135
80
|
|
|
136
|
-
|
|
81
|
+
---
|
|
137
82
|
|
|
138
|
-
|
|
83
|
+
## Installation
|
|
139
84
|
|
|
140
85
|
```bash
|
|
141
|
-
npm
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
This will create collections for:
|
|
145
|
-
|
|
146
|
-
- **Coupons** β Manage discount codes (in "Coupons" group)
|
|
147
|
-
- **Referral Programs** β Set up partner commission structures (in "Referrals" group)
|
|
148
|
-
- **Referral Codes** β Track generated referral links (in "Referrals" group)
|
|
149
|
-
|
|
150
|
-
### 3. Setting Up Partner Role
|
|
151
|
-
|
|
152
|
-
To enable the partner dashboard and role-based access, add a `role` or `roles` field to your Users collection (or configure custom paths with `roleConfig`):
|
|
153
|
-
|
|
154
|
-
```typescript
|
|
155
|
-
// collections/Users.ts
|
|
156
|
-
import type { CollectionConfig } from "payload";
|
|
157
|
-
|
|
158
|
-
export const Users: CollectionConfig = {
|
|
159
|
-
slug: "users",
|
|
160
|
-
auth: true,
|
|
161
|
-
fields: [
|
|
162
|
-
{
|
|
163
|
-
name: "role",
|
|
164
|
-
type: "select",
|
|
165
|
-
options: [
|
|
166
|
-
{ label: "Admin", value: "admin" },
|
|
167
|
-
{ label: "Partner", value: "partner" },
|
|
168
|
-
{ label: "Customer", value: "customer" },
|
|
169
|
-
],
|
|
170
|
-
defaultValue: "customer",
|
|
171
|
-
required: true,
|
|
172
|
-
},
|
|
173
|
-
// Or use multiple roles
|
|
174
|
-
{
|
|
175
|
-
name: "roles",
|
|
176
|
-
type: "select",
|
|
177
|
-
hasMany: true,
|
|
178
|
-
options: [
|
|
179
|
-
{ label: "Admin", value: "admin" },
|
|
180
|
-
{ label: "Partner", value: "partner" },
|
|
181
|
-
{ label: "Customer", value: "customer" },
|
|
182
|
-
],
|
|
183
|
-
defaultValue: ["customer"],
|
|
184
|
-
},
|
|
185
|
-
],
|
|
186
|
-
};
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
If your user schema stores roles in a custom structure, configure:
|
|
190
|
-
|
|
191
|
-
```typescript
|
|
192
|
-
roleConfig: {
|
|
193
|
-
roleFieldPaths: ["role", "roles", "profile.accountRoles"],
|
|
194
|
-
adminRoleValues: ["admin"],
|
|
195
|
-
partnerRoleValues: ["partner", "affiliate"],
|
|
196
|
-
}
|
|
86
|
+
npm install @wtree/payload-ecommerce-coupon
|
|
197
87
|
```
|
|
198
88
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
Coupon and referral **usage is not counted when a code is applied** to the cart. It is counted only when an **order is placed successfully** (e.g. paid). You must call the plugin when converting cart to order:
|
|
202
|
-
|
|
203
|
-
**Option A β Call the API** (e.g. from your Orders collection `afterChange` hook when `paymentStatus === 'paid'`):
|
|
89
|
+
or
|
|
204
90
|
|
|
205
91
|
```bash
|
|
206
|
-
|
|
207
|
-
Content-Type: application/json
|
|
208
|
-
{ "orderId": "your-order-id" }
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
**Option B β Use the server utility** (in your Payload config or Orders hook):
|
|
212
|
-
|
|
213
|
-
```typescript
|
|
214
|
-
import { recordCouponUsageForOrder } from "@wtree/payload-ecommerce-coupon";
|
|
215
|
-
|
|
216
|
-
// In your Orders collection afterChange hook, when order is paid/completed:
|
|
217
|
-
if (doc.paymentStatus === "paid" && (doc.appliedCoupon || doc.appliedReferralCode)) {
|
|
218
|
-
await recordCouponUsageForOrder(payload, doc, pluginConfig);
|
|
219
|
-
}
|
|
92
|
+
bun add @wtree/payload-ecommerce-coupon
|
|
220
93
|
```
|
|
221
94
|
|
|
222
|
-
|
|
223
|
-
- **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).
|
|
224
|
-
|
|
225
|
-
### 4.5 Coupon usage rules and cart total
|
|
95
|
+
---
|
|
226
96
|
|
|
227
|
-
|
|
97
|
+
## Quick start
|
|
228
98
|
|
|
229
|
-
|
|
230
|
-
- **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.
|
|
99
|
+
### 1) Register plugin
|
|
231
100
|
|
|
232
|
-
|
|
101
|
+
```ts
|
|
102
|
+
import { buildConfig } from 'payload'
|
|
103
|
+
import { ecommercePlugin } from '@payloadcms/plugin-ecommerce'
|
|
104
|
+
import { payloadEcommerceCoupon } from '@wtree/payload-ecommerce-coupon'
|
|
233
105
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
106
|
+
export default buildConfig({
|
|
107
|
+
plugins: [
|
|
108
|
+
ecommercePlugin({
|
|
109
|
+
// ecommerce config
|
|
110
|
+
}),
|
|
111
|
+
payloadEcommerceCoupon({
|
|
112
|
+
enabled: true,
|
|
113
|
+
enableReferrals: true,
|
|
114
|
+
defaultCurrency: 'USD',
|
|
237
115
|
|
|
238
|
-
|
|
116
|
+
referralConfig: {
|
|
117
|
+
allowBothSystems: false,
|
|
118
|
+
singleCodePerCart: true,
|
|
119
|
+
defaultPartnerSplit: 70,
|
|
120
|
+
defaultCustomerSplit: 30,
|
|
121
|
+
allowedTotalCommissionTypes: ['fixed', 'percentage'],
|
|
122
|
+
},
|
|
239
123
|
|
|
240
|
-
|
|
241
|
-
|
|
124
|
+
// Policy-first access gates
|
|
125
|
+
policies: {
|
|
126
|
+
canApplyCoupon: ({ req }) => Boolean(req),
|
|
127
|
+
canApplyReferral: ({ req }) => Boolean(req),
|
|
128
|
+
canViewPartnerStats: ({ user }) => Boolean(user),
|
|
129
|
+
canRecordOrderUsage: ({ user }) => Boolean(user),
|
|
130
|
+
},
|
|
242
131
|
|
|
243
|
-
//
|
|
244
|
-
|
|
132
|
+
// Integration mapping (override defaults if your schema differs)
|
|
133
|
+
integration: {
|
|
134
|
+
collections: {
|
|
135
|
+
cartsSlug: 'carts',
|
|
136
|
+
ordersSlug: 'orders',
|
|
137
|
+
productsSlug: 'products',
|
|
138
|
+
usersSlug: 'users',
|
|
139
|
+
categoriesSlug: 'categories',
|
|
140
|
+
tagsSlug: 'tags',
|
|
141
|
+
},
|
|
142
|
+
fields: {
|
|
143
|
+
cartItemsField: 'items',
|
|
144
|
+
cartSubtotalField: 'subtotal',
|
|
145
|
+
cartTotalField: 'total',
|
|
146
|
+
cartAppliedCouponField: 'appliedCoupon',
|
|
147
|
+
cartAppliedReferralCodeField: 'appliedReferralCode',
|
|
148
|
+
cartDiscountAmountField: 'discountAmount',
|
|
149
|
+
cartCustomerDiscountField: 'customerDiscount',
|
|
150
|
+
cartPartnerCommissionField: 'partnerCommission',
|
|
151
|
+
orderAppliedCouponField: 'appliedCoupon',
|
|
152
|
+
orderAppliedReferralCodeField: 'appliedReferralCode',
|
|
153
|
+
orderDiscountAmountField: 'discountAmount',
|
|
154
|
+
orderCustomerDiscountField: 'customerDiscount',
|
|
155
|
+
orderPartnerCommissionField: 'partnerCommission',
|
|
156
|
+
orderCustomerEmailField: 'customerEmail',
|
|
157
|
+
orderPaymentStatusField: 'paymentStatus',
|
|
158
|
+
orderCreatedAtField: 'createdAt',
|
|
159
|
+
productPriceField: 'price',
|
|
160
|
+
productCurrencyCodeField: 'currencyCode',
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
}),
|
|
164
|
+
],
|
|
165
|
+
})
|
|
245
166
|
```
|
|
246
167
|
|
|
247
|
-
|
|
168
|
+
---
|
|
248
169
|
|
|
249
|
-
|
|
170
|
+
## Usage lifecycle (important)
|
|
250
171
|
|
|
251
|
-
|
|
252
|
-
- `recordCouponUsageForOrder(payload, order, pluginConfig)` β Call when an order is paid to increment coupon/referral usage and credit partner earnings (see step 4 above).
|
|
172
|
+
### Validate/apply does **not** increment usage
|
|
253
173
|
|
|
254
|
-
|
|
174
|
+
- `POST /api/coupons/validate` checks code validity and returns computed values
|
|
175
|
+
- `POST /api/coupons/apply` applies code to cart and updates discount/commission fields
|
|
255
176
|
|
|
256
|
-
|
|
177
|
+
### Usage is recorded when order is placed
|
|
257
178
|
|
|
258
|
-
|
|
259
|
-
import { useCouponCode } from '@wtree/payload-ecommerce-coupon'
|
|
179
|
+
Call usage recording when an order is paid/completed:
|
|
260
180
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
const [cartId, setCartId] = useState('your-cart-id')
|
|
181
|
+
- Endpoint: `POST /api/coupons/record-order-usage` with `{ "orderId": "..." }`
|
|
182
|
+
- Or utility: `recordCouponUsageForOrder(payload, order, pluginConfig)`
|
|
264
183
|
|
|
265
|
-
|
|
266
|
-
const result = await useCouponCode({
|
|
267
|
-
code,
|
|
268
|
-
cartID: cartId,
|
|
269
|
-
// When a coupon has per-customer limit, pass customerEmail so the limit can be enforced
|
|
270
|
-
// customerEmail: customerEmailFromAuthOrForm,
|
|
271
|
-
})
|
|
184
|
+
This keeps usage and earnings accounting accurate and idempotent for order lifecycle events.
|
|
272
185
|
|
|
273
|
-
|
|
274
|
-
if (result.coupon) {
|
|
275
|
-
console.log('Coupon applied! Discount:', result.discount)
|
|
276
|
-
} else if (result.referralCode) {
|
|
277
|
-
console.log('Referral applied!')
|
|
278
|
-
console.log('Your discount:', result.customerDiscount)
|
|
279
|
-
console.log('Partner commission:', result.partnerCommission)
|
|
280
|
-
}
|
|
281
|
-
} else {
|
|
282
|
-
console.error('Error:', result.error)
|
|
283
|
-
}
|
|
284
|
-
}
|
|
186
|
+
---
|
|
285
187
|
|
|
286
|
-
|
|
287
|
-
<div>
|
|
288
|
-
<input
|
|
289
|
-
value={code}
|
|
290
|
-
onChange={(e) => setCode(e.target.value)}
|
|
291
|
-
placeholder="Enter coupon or referral code"
|
|
292
|
-
/>
|
|
293
|
-
<button onClick={applyCode}>Apply Code</button>
|
|
294
|
-
</div>
|
|
295
|
-
)
|
|
296
|
-
}
|
|
297
|
-
```
|
|
188
|
+
## Endpoints
|
|
298
189
|
|
|
299
|
-
|
|
190
|
+
Default paths (customizable via `endpoints` config):
|
|
300
191
|
|
|
301
|
-
|
|
192
|
+
- `POST /api/coupons/validate`
|
|
193
|
+
- `POST /api/coupons/apply`
|
|
194
|
+
- `GET /api/referrals/partner-stats`
|
|
195
|
+
- `POST /api/coupons/record-order-usage` (when enabled)
|
|
302
196
|
|
|
303
|
-
|
|
304
|
-
import { usePartnerStats } from '@wtree/payload-ecommerce-coupon'
|
|
197
|
+
---
|
|
305
198
|
|
|
306
|
-
|
|
307
|
-
function CustomPartnerDashboard() {
|
|
308
|
-
const [data, setData] = useState(null)
|
|
199
|
+
## Frontend helpers
|
|
309
200
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
const result = await usePartnerStats()
|
|
313
|
-
if (result.success) {
|
|
314
|
-
setData(result.data)
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
fetchStats()
|
|
318
|
-
}, [])
|
|
319
|
-
|
|
320
|
-
if (!data) return <div>Loading...</div>
|
|
321
|
-
|
|
322
|
-
return (
|
|
323
|
-
<div>
|
|
324
|
-
<h2>Your Earnings</h2>
|
|
325
|
-
<p>Total: ${data.stats.totalEarnings}</p>
|
|
326
|
-
<p>Pending: ${data.stats.pendingEarnings}</p>
|
|
327
|
-
<p>Paid: ${data.stats.paidEarnings}</p>
|
|
328
|
-
|
|
329
|
-
<h2>Your Referral Codes</h2>
|
|
330
|
-
{data.referralCodes.map(code => (
|
|
331
|
-
<div key={code.id}>
|
|
332
|
-
<span>{code.code}</span>
|
|
333
|
-
<span>Uses: {code.usageCount}</span>
|
|
334
|
-
</div>
|
|
335
|
-
))}
|
|
336
|
-
</div>
|
|
337
|
-
)
|
|
338
|
-
}
|
|
201
|
+
```ts
|
|
202
|
+
import { useCouponCode, validateCouponCode, usePartnerStats } from '@wtree/payload-ecommerce-coupon'
|
|
339
203
|
```
|
|
340
204
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
#### **Coupon Mode** (`enableReferrals: false`)
|
|
346
|
-
|
|
347
|
-
Best for traditional discount campaigns, seasonal sales, and customer loyalty programs.
|
|
348
|
-
|
|
349
|
-
#### **Referral Mode** (`enableReferrals: true`)
|
|
350
|
-
|
|
351
|
-
Best for affiliate marketing, partner programs, and customer acquisition through referrals.
|
|
352
|
-
|
|
353
|
-
#### **Hybrid Mode** (`enableReferrals: true` + `allowBothSystems: true`)
|
|
205
|
+
- `useCouponCode({ code, cartID, customerEmail? })`
|
|
206
|
+
- `validateCouponCode({ code, cartValue?, cartID?, customerEmail? })`
|
|
207
|
+
- `usePartnerStats()`
|
|
354
208
|
|
|
355
|
-
|
|
209
|
+
---
|
|
356
210
|
|
|
357
|
-
|
|
211
|
+
## Configuration overview
|
|
358
212
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
- **Usage Limit**: Maximum uses (optional)
|
|
367
|
-
- **Per Customer Limit**: Uses per customer (optional)
|
|
368
|
-
- **Min/Max Order Value**: Order value constraints
|
|
369
|
-
|
|
370
|
-
### **Setting Up Referral Mode**
|
|
371
|
-
|
|
372
|
-
1. **Navigate to Admin Panel** β Go to "Referral Programs" (under "Referrals" group)
|
|
373
|
-
2. **Create Referral Program** with **Commission Rules** (required β at least one). Each rule has:
|
|
374
|
-
- **Name**: e.g. "Default" or "Electronics"
|
|
375
|
-
- **Applies To**: All Products, Specific Categories, or Specific Products
|
|
376
|
-
- **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.
|
|
377
|
-
- **Referee Reward** (inside the rule): Discount for the customer. Type: Percentage Discount or Fixed Amount. Value: e.g. 30 = 30% off. Optional Max Reward.
|
|
378
|
-
|
|
379
|
-
There are no outer Referrer Reward / Referee Reward fields β only **Commission Rules**, and each rule contains its own Referrer Reward and Referee Reward.
|
|
380
|
-
|
|
381
|
-
### **Commission and Discount (Referrer / Referee)**
|
|
382
|
-
|
|
383
|
-
- **Referrer (partner)** receives **commission** β credited to the referral codeβs `totalEarnings` and `pendingEarnings` when the order is placed (via record-order-usage).
|
|
384
|
-
- **Referee (customer)** receives a **discount** β applied to the order; stored on cart/order as `customerDiscount`.
|
|
385
|
-
|
|
386
|
-
#### **Example: Commission Rules with Split**
|
|
387
|
-
|
|
388
|
-
- **Order Total**: $100 (Electronics category)
|
|
389
|
-
- **Total Commission**: 15% = $15
|
|
390
|
-
- **Partner Share**: 70% of $15 = $10.50 (commission to referrer)
|
|
391
|
-
- **Customer Discount**: 30% of $15 = $4.50 (discount to referee)
|
|
392
|
-
|
|
393
|
-
### **Managing Partners**
|
|
394
|
-
|
|
395
|
-
1. **Create Partner Account**: Set user role to "partner"
|
|
396
|
-
2. **Generate Referral Code**: Partners can create codes in "Referral Codes" collection
|
|
397
|
-
3. **Track Performance**: View usage count, earnings, and successful referrals
|
|
398
|
-
4. **Payout Management**: Track pending vs paid earnings
|
|
399
|
-
|
|
400
|
-
## π REST API Endpoints
|
|
401
|
-
|
|
402
|
-
### **Coupon/Referral Endpoints**
|
|
403
|
-
|
|
404
|
-
#### POST /api/coupons/validate
|
|
405
|
-
|
|
406
|
-
Validate a code without applying it. Optionally pass `customerEmail` to check per-customer limit for coupons that have one.
|
|
407
|
-
|
|
408
|
-
```bash
|
|
409
|
-
curl -X POST http://localhost:3000/api/coupons/validate \
|
|
410
|
-
-H "Content-Type: application/json" \
|
|
411
|
-
-d '{"code": "WELCOME10", "cartValue": 5000}'
|
|
412
|
-
|
|
413
|
-
# With per-customer limit check:
|
|
414
|
-
# -d '{"code": "WELCOME10", "cartValue": 5000, "customerEmail": "user@example.com"}'
|
|
415
|
-
```
|
|
416
|
-
|
|
417
|
-
#### POST /api/coupons/apply
|
|
418
|
-
|
|
419
|
-
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.
|
|
420
|
-
|
|
421
|
-
```bash
|
|
422
|
-
curl -X POST http://localhost:3000/api/coupons/apply \
|
|
423
|
-
-H "Content-Type: application/json" \
|
|
424
|
-
-d '{"code": "WELCOME10", "cartID": "cart-123"}'
|
|
425
|
-
|
|
426
|
-
# With per-customer limit (required when coupon has per-customer limit):
|
|
427
|
-
# -d '{"code": "WELCOME10", "cartID": "cart-123", "customerEmail": "user@example.com"}'
|
|
428
|
-
```
|
|
429
|
-
|
|
430
|
-
#### POST /api/coupons/record-order-usage
|
|
431
|
-
|
|
432
|
-
Record coupon and referral usage for a successfully placed order. Call this once per order when the order is paid/completed (e.g. from your Orders `afterChange` hook).
|
|
433
|
-
|
|
434
|
-
**Request body:** `{ "orderId": "string" }`
|
|
435
|
-
|
|
436
|
-
```bash
|
|
437
|
-
curl -X POST http://localhost:3000/api/coupons/record-order-usage \
|
|
438
|
-
-H "Content-Type: application/json" \
|
|
439
|
-
-d '{"orderId": "order-123"}'
|
|
440
|
-
```
|
|
441
|
-
|
|
442
|
-
**Response:** `{ "success": true, "recordedCoupon": boolean, "recordedReferral": boolean }`
|
|
443
|
-
|
|
444
|
-
### **Partner Stats Endpoint**
|
|
445
|
-
|
|
446
|
-
#### GET /api/referrals/partner-stats
|
|
447
|
-
|
|
448
|
-
Get partner dashboard data (requires authentication).
|
|
449
|
-
|
|
450
|
-
```bash
|
|
451
|
-
curl -X GET http://localhost:3000/api/referrals/partner-stats \
|
|
452
|
-
-H "Cookie: payload-token=your-auth-token"
|
|
453
|
-
```
|
|
454
|
-
|
|
455
|
-
**Response:**
|
|
456
|
-
|
|
457
|
-
```json
|
|
458
|
-
{
|
|
459
|
-
"success": true,
|
|
460
|
-
"data": {
|
|
461
|
-
"stats": {
|
|
462
|
-
"totalEarnings": 1250.50,
|
|
463
|
-
"pendingEarnings": 350.00,
|
|
464
|
-
"paidEarnings": 900.50,
|
|
465
|
-
"totalReferrals": 45,
|
|
466
|
-
"successfulReferrals": 38,
|
|
467
|
-
"conversionRate": 84.44,
|
|
468
|
-
"recentReferrals": [...],
|
|
469
|
-
"monthlyEarnings": [...]
|
|
470
|
-
},
|
|
471
|
-
"referralCodes": [...],
|
|
472
|
-
"program": {
|
|
473
|
-
"name": "Partner Program",
|
|
474
|
-
"commissionRate": 10,
|
|
475
|
-
"customerDiscount": 5
|
|
476
|
-
}
|
|
477
|
-
},
|
|
478
|
-
"currency": "USD"
|
|
479
|
-
}
|
|
480
|
-
```
|
|
481
|
-
|
|
482
|
-
## βοΈ Configuration
|
|
483
|
-
|
|
484
|
-
### **Core Options**
|
|
485
|
-
|
|
486
|
-
```typescript
|
|
487
|
-
export type CouponPluginOptions = {
|
|
488
|
-
enabled?: boolean; // Enable/disable the plugin (default: true)
|
|
489
|
-
enableReferrals?: boolean; // Enable referral system (default: false)
|
|
490
|
-
allowStackWithOtherCoupons?: boolean; // Allow multiple coupons (default: false)
|
|
491
|
-
defaultCurrency?: string; // Currency code (default: 'USD')
|
|
492
|
-
autoIntegrate?: boolean; // Auto-extend carts/orders (default: true)
|
|
213
|
+
```ts
|
|
214
|
+
type CouponPluginOptions = {
|
|
215
|
+
enabled?: boolean
|
|
216
|
+
enableReferrals?: boolean
|
|
217
|
+
allowStackWithOtherCoupons?: boolean
|
|
218
|
+
defaultCurrency?: string
|
|
219
|
+
autoIntegrate?: boolean
|
|
493
220
|
|
|
494
221
|
collections?: {
|
|
495
|
-
couponsSlug?: string
|
|
496
|
-
referralProgramsSlug?: string
|
|
497
|
-
referralCodesSlug?: string
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
referralProgramsCollectionOverride?: (params: { defaultCollection: any }) => any | Promise<any>;
|
|
504
|
-
|
|
505
|
-
/** Override the default referral codes collection configuration */
|
|
506
|
-
referralCodesCollectionOverride?: (params: { defaultCollection: any }) => any | Promise<any>;
|
|
507
|
-
};
|
|
222
|
+
couponsSlug?: string
|
|
223
|
+
referralProgramsSlug?: string
|
|
224
|
+
referralCodesSlug?: string
|
|
225
|
+
referralPartnersSlug?: string
|
|
226
|
+
couponsCollectionOverride?: (params: { defaultCollection: any }) => any | Promise<any>
|
|
227
|
+
referralProgramsCollectionOverride?: (params: { defaultCollection: any }) => any | Promise<any>
|
|
228
|
+
referralCodesCollectionOverride?: (params: { defaultCollection: any }) => any | Promise<any>
|
|
229
|
+
}
|
|
508
230
|
|
|
509
231
|
endpoints?: {
|
|
510
|
-
applyCoupon?: string
|
|
511
|
-
validateCoupon?: string
|
|
512
|
-
partnerStats?: string
|
|
513
|
-
recordOrderUsage?: string
|
|
514
|
-
}
|
|
232
|
+
applyCoupon?: string
|
|
233
|
+
validateCoupon?: string
|
|
234
|
+
partnerStats?: string
|
|
235
|
+
recordOrderUsage?: string
|
|
236
|
+
}
|
|
515
237
|
|
|
238
|
+
// Legacy access support
|
|
516
239
|
access?: {
|
|
517
|
-
canUseCoupons?:
|
|
518
|
-
canUseReferrals?:
|
|
519
|
-
isAdmin?:
|
|
520
|
-
isPartner?:
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
referralConfig?: {
|
|
524
|
-
allowBothSystems?: boolean; // Allow coupons + referrals (default: false)
|
|
525
|
-
singleCodePerCart?: boolean; // One code per order (default: true)
|
|
526
|
-
defaultPartnerSplit?: number; // Default partner % (default: 70)
|
|
527
|
-
defaultCustomerSplit?: number; // Default customer % (default: 30)
|
|
528
|
-
};
|
|
529
|
-
|
|
530
|
-
adminGroups?: {
|
|
531
|
-
couponsGroup?: string; // Admin group for coupons (default: 'Coupons')
|
|
532
|
-
referralsGroup?: string; // Admin group for referrals (default: 'Referrals')
|
|
533
|
-
};
|
|
534
|
-
|
|
535
|
-
partnerDashboard?: {
|
|
536
|
-
enabled?: boolean; // Enable dashboard (default: true)
|
|
537
|
-
showEarningsSummary?: boolean; // Show earnings widget (default: true)
|
|
538
|
-
showReferralPerformance?: boolean; // Show performance widget (default: true)
|
|
539
|
-
showRecentReferrals?: boolean; // Show recent referrals (default: true)
|
|
540
|
-
showCommissionBreakdown?: boolean; // Show breakdown (default: true)
|
|
541
|
-
};
|
|
542
|
-
|
|
543
|
-
/** Optional: for per-customer coupon limit (query paid orders by customer) */
|
|
544
|
-
orderIntegration?: {
|
|
545
|
-
ordersSlug?: string; // Default: 'orders'
|
|
546
|
-
orderCustomerEmailField?: string; // Default: 'customerEmail'
|
|
547
|
-
orderPaymentStatusField?: string; // Default: 'paymentStatus'
|
|
548
|
-
orderPaidStatusValue?: string; // Default: 'paid'
|
|
549
|
-
};
|
|
550
|
-
};
|
|
551
|
-
```
|
|
552
|
-
|
|
553
|
-
### **Collection Overrides**
|
|
554
|
-
|
|
555
|
-
You can override the default collection configurations to customize fields, hooks, or other collection settings. This allows you to extend or modify the plugin's behavior without forking the code.
|
|
556
|
-
|
|
557
|
-
```typescript
|
|
558
|
-
payloadEcommerceCoupon({
|
|
559
|
-
collections: {
|
|
560
|
-
// Override coupons collection
|
|
561
|
-
couponsCollectionOverride: async ({ defaultCollection }) => {
|
|
562
|
-
return {
|
|
563
|
-
...defaultCollection,
|
|
564
|
-
fields: [
|
|
565
|
-
...defaultCollection.fields,
|
|
566
|
-
// Add custom field to coupons
|
|
567
|
-
{
|
|
568
|
-
name: "customField",
|
|
569
|
-
type: "text",
|
|
570
|
-
label: "Custom Field",
|
|
571
|
-
},
|
|
572
|
-
],
|
|
573
|
-
hooks: {
|
|
574
|
-
...defaultCollection.hooks,
|
|
575
|
-
// Add custom hook
|
|
576
|
-
beforeChange: [
|
|
577
|
-
...(defaultCollection.hooks?.beforeChange || []),
|
|
578
|
-
async ({ data, req, operation }) => {
|
|
579
|
-
// Custom beforeChange logic
|
|
580
|
-
return data;
|
|
581
|
-
},
|
|
582
|
-
],
|
|
583
|
-
},
|
|
584
|
-
};
|
|
585
|
-
},
|
|
586
|
-
|
|
587
|
-
// Override referral programs collection
|
|
588
|
-
referralProgramsCollectionOverride: ({ defaultCollection }) => {
|
|
589
|
-
return {
|
|
590
|
-
...defaultCollection,
|
|
591
|
-
admin: {
|
|
592
|
-
...defaultCollection.admin,
|
|
593
|
-
defaultColumns: ["name", "isActive", "totalReferrals"],
|
|
594
|
-
},
|
|
595
|
-
};
|
|
596
|
-
},
|
|
597
|
-
|
|
598
|
-
// Override referral codes collection
|
|
599
|
-
referralCodesCollectionOverride: async ({ defaultCollection }) => {
|
|
600
|
-
return {
|
|
601
|
-
...defaultCollection,
|
|
602
|
-
fields: [
|
|
603
|
-
...defaultCollection.fields,
|
|
604
|
-
{
|
|
605
|
-
name: "customCodeField",
|
|
606
|
-
type: "select",
|
|
607
|
-
label: "Custom Code Type",
|
|
608
|
-
options: ["standard", "premium"],
|
|
609
|
-
defaultValue: "standard",
|
|
610
|
-
},
|
|
611
|
-
],
|
|
612
|
-
};
|
|
613
|
-
},
|
|
614
|
-
},
|
|
615
|
-
});
|
|
616
|
-
```
|
|
617
|
-
|
|
618
|
-
### **Access Control Examples**
|
|
619
|
-
|
|
620
|
-
```typescript
|
|
621
|
-
payloadEcommerceCoupon({
|
|
622
|
-
access: {
|
|
623
|
-
// Anyone can use coupons
|
|
624
|
-
canUseCoupons: () => true,
|
|
625
|
-
|
|
626
|
-
// Only authenticated users can use referrals
|
|
627
|
-
canUseReferrals: ({ req }) => Boolean(req.user),
|
|
628
|
-
|
|
629
|
-
// Only admins can manage
|
|
630
|
-
isAdmin: ({ req }) => req.user?.role === "admin",
|
|
631
|
-
|
|
632
|
-
// Partner role check (supports both single role and array)
|
|
633
|
-
isPartner: ({ req }) => {
|
|
634
|
-
const user = req.user;
|
|
635
|
-
if (!user) return false;
|
|
636
|
-
if (user.role === "partner") return true;
|
|
637
|
-
if (Array.isArray(user.roles) && user.roles.includes("partner")) return true;
|
|
638
|
-
return false;
|
|
639
|
-
},
|
|
640
|
-
},
|
|
641
|
-
});
|
|
642
|
-
```
|
|
643
|
-
|
|
644
|
-
## π¦ API Reference
|
|
645
|
-
|
|
646
|
-
### **Exported Functions**
|
|
647
|
-
|
|
648
|
-
```typescript
|
|
649
|
-
import {
|
|
650
|
-
payloadEcommerceCoupon,
|
|
651
|
-
|
|
652
|
-
// Collection creation functions
|
|
653
|
-
createCouponsCollection,
|
|
654
|
-
createReferralCodesCollection,
|
|
655
|
-
createReferralProgramsCollection,
|
|
656
|
-
|
|
657
|
-
// Frontend hooks
|
|
658
|
-
useCouponCode,
|
|
659
|
-
validateCouponCode,
|
|
660
|
-
usePartnerStats,
|
|
661
|
-
|
|
662
|
-
// Server-only: record usage when order is placed
|
|
663
|
-
recordCouponUsageForOrder,
|
|
664
|
-
} from "@wtree/payload-ecommerce-coupon";
|
|
665
|
-
```
|
|
240
|
+
canUseCoupons?: any
|
|
241
|
+
canUseReferrals?: any
|
|
242
|
+
isAdmin?: any
|
|
243
|
+
isPartner?: any
|
|
244
|
+
}
|
|
666
245
|
|
|
667
|
-
|
|
246
|
+
// Preferred policy-first API
|
|
247
|
+
policies?: {
|
|
248
|
+
canApplyCoupon?: (ctx: { req: unknown; user?: unknown; payload?: unknown }) => boolean | Promise<boolean>
|
|
249
|
+
canApplyReferral?: (ctx: { req: unknown; user?: unknown; payload?: unknown }) => boolean | Promise<boolean>
|
|
250
|
+
canViewPartnerStats?: (ctx: { req: unknown; user?: unknown; payload?: unknown; requestedPartnerID?: string | number }) => boolean | Promise<boolean>
|
|
251
|
+
canRecordOrderUsage?: (ctx: { req: unknown; user?: unknown; payload?: unknown; order: unknown }) => boolean | Promise<boolean>
|
|
252
|
+
}
|
|
668
253
|
|
|
669
|
-
|
|
254
|
+
// Integration-driven mapping
|
|
255
|
+
integration?: {
|
|
256
|
+
collections?: {
|
|
257
|
+
cartsSlug?: string
|
|
258
|
+
ordersSlug?: string
|
|
259
|
+
productsSlug?: string
|
|
260
|
+
usersSlug?: string
|
|
261
|
+
categoriesSlug?: string
|
|
262
|
+
tagsSlug?: string
|
|
263
|
+
}
|
|
264
|
+
fields?: {
|
|
265
|
+
cartItemsField?: string
|
|
266
|
+
cartSubtotalField?: string
|
|
267
|
+
cartTotalField?: string
|
|
268
|
+
cartAppliedCouponField?: string
|
|
269
|
+
cartAppliedReferralCodeField?: string
|
|
270
|
+
cartDiscountAmountField?: string
|
|
271
|
+
cartCustomerDiscountField?: string
|
|
272
|
+
cartPartnerCommissionField?: string
|
|
273
|
+
orderAppliedCouponField?: string
|
|
274
|
+
orderAppliedReferralCodeField?: string
|
|
275
|
+
orderDiscountAmountField?: string
|
|
276
|
+
orderCustomerDiscountField?: string
|
|
277
|
+
orderPartnerCommissionField?: string
|
|
278
|
+
orderCustomerEmailField?: string
|
|
279
|
+
orderPaymentStatusField?: string
|
|
280
|
+
orderCreatedAtField?: string
|
|
281
|
+
productPriceField?: string
|
|
282
|
+
productCurrencyCodeField?: string
|
|
283
|
+
}
|
|
284
|
+
resolvers?: {
|
|
285
|
+
getUserID?: (args: { req: unknown; user?: unknown }) => string | number | null | undefined
|
|
286
|
+
getCartItems?: (cart: unknown) => any[]
|
|
287
|
+
getCartSubtotal?: (cart: unknown) => number
|
|
288
|
+
getCartTotal?: (cart: unknown) => number
|
|
289
|
+
isOrderPaid?: (order: unknown) => boolean
|
|
290
|
+
getProductUnitPrice?: (args: { item: unknown; product: unknown; variant?: unknown; currencyCode?: string }) => number
|
|
291
|
+
}
|
|
292
|
+
}
|
|
670
293
|
|
|
671
|
-
|
|
294
|
+
referralConfig?: {
|
|
295
|
+
allowBothSystems?: boolean
|
|
296
|
+
singleCodePerCart?: boolean
|
|
297
|
+
defaultPartnerSplit?: number
|
|
298
|
+
defaultCustomerSplit?: number
|
|
299
|
+
allowedTotalCommissionTypes?: Array<'fixed' | 'percentage'>
|
|
300
|
+
}
|
|
672
301
|
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
302
|
+
orderIntegration?: {
|
|
303
|
+
ordersSlug?: string
|
|
304
|
+
orderCustomerEmailField?: string
|
|
305
|
+
orderPaymentStatusField?: string
|
|
306
|
+
orderPaidStatusValue?: string
|
|
307
|
+
}
|
|
677
308
|
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
}),
|
|
686
|
-
],
|
|
687
|
-
collections: [
|
|
688
|
-
// The plugin adds collections automatically; use overrides in plugin config to customize
|
|
689
|
-
],
|
|
690
|
-
});
|
|
309
|
+
roleConfig?: {
|
|
310
|
+
roleFieldPaths?: string[]
|
|
311
|
+
adminRoleValues?: string[]
|
|
312
|
+
partnerRoleValues?: string[]
|
|
313
|
+
customRoleResolver?: (user: unknown) => string[]
|
|
314
|
+
}
|
|
315
|
+
}
|
|
691
316
|
```
|
|
692
317
|
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
The plugin provides hooks and (when using the source) React components for partner dashboards. Use `usePartnerStats()` to fetch stats; for pre-built dashboard components and styling, see [Partner Dashboard documentation](./docs/partner-dashboard.md).
|
|
696
|
-
|
|
697
|
-
## π§ Troubleshooting
|
|
698
|
-
|
|
699
|
-
### **Common Issues**
|
|
700
|
-
|
|
701
|
-
#### **"A code has already been applied to this cart"**
|
|
702
|
-
|
|
703
|
-
This occurs when `singleCodePerCart: true` and a code is already applied.
|
|
318
|
+
---
|
|
704
319
|
|
|
705
|
-
|
|
320
|
+
## Migration notes (from role-assumption setups)
|
|
706
321
|
|
|
707
|
-
|
|
322
|
+
If youβre upgrading from older configs:
|
|
708
323
|
|
|
709
|
-
|
|
710
|
-
|
|
324
|
+
1. Prefer `policies` over role-specific assumptions.
|
|
325
|
+
2. Set `integration.collections` and `integration.fields` if your schema uses custom slugs/field names.
|
|
326
|
+
3. Ensure order completion flow calls usage recording.
|
|
327
|
+
4. Validate endpoint paths if you previously relied on custom routes.
|
|
711
328
|
|
|
712
|
-
|
|
329
|
+
---
|
|
713
330
|
|
|
714
|
-
|
|
331
|
+
## Troubleshooting
|
|
715
332
|
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
- Ensure at least one **Commission Rule** exists and each rule has **Referrer Reward** and **Referee Reward** set
|
|
719
|
-
- Verify cart has valid `subtotal` or `total` and items match ruleβs appliesTo (all / categories / products)
|
|
720
|
-
|
|
721
|
-
## π Future Features (Roadmap)
|
|
722
|
-
|
|
723
|
-
The following features are planned for future releases:
|
|
724
|
-
|
|
725
|
-
| Feature | Status | Description |
|
|
726
|
-
| ---------------------- | ---------- | ---------------------------------------------------------------- |
|
|
727
|
-
| Multi-tier commissions | π Planned | Support for tiered commission rates based on performance |
|
|
728
|
-
| Automatic payouts | π Planned | Integration with payment providers for automatic partner payouts |
|
|
729
|
-
| Referral analytics | π Planned | Advanced analytics and reporting dashboard |
|
|
730
|
-
| Email notifications | π Planned | Automated emails for referral events |
|
|
731
|
-
| Custom code generation | π Planned | Allow partners to create custom branded codes |
|
|
732
|
-
| Fraud detection | π Planned | Automatic detection of suspicious referral patterns |
|
|
733
|
-
| Bulk code import | π Planned | Import coupons/codes from CSV |
|
|
734
|
-
| A/B testing | π Planned | Test different commission structures |
|
|
735
|
-
|
|
736
|
-
### **Comparison with Other Solutions**
|
|
737
|
-
|
|
738
|
-
| Feature | This Plugin | ReferralCandy | Refersion | Custom Build |
|
|
739
|
-
| ----------------------- | ----------- | ------------- | --------- | ------------ |
|
|
740
|
-
| Payload CMS Integration | β
Native | β | β | β οΈ Manual |
|
|
741
|
-
| Coupon System | β
| β | β | β οΈ Manual |
|
|
742
|
-
| Referral System | β
| β
| β
| β οΈ Manual |
|
|
743
|
-
| Partner Dashboard | β
| β
| β
| β οΈ Manual |
|
|
744
|
-
| Commission Rules | β
| β οΈ Limited | β
| β οΈ Manual |
|
|
745
|
-
| Single Code Enforcement | β
| β | β | β οΈ Manual |
|
|
746
|
-
| TypeScript Support | β
| β | β | β οΈ Varies |
|
|
747
|
-
| Self-Hosted | β
| β | β | β
|
|
|
748
|
-
| Monthly Cost | Free | $49+ | $89+ | Dev Time |
|
|
749
|
-
|
|
750
|
-
## π§ͺ Testing
|
|
333
|
+
### Build/lint mismatch after changes
|
|
334
|
+
Run:
|
|
751
335
|
|
|
752
336
|
```bash
|
|
753
|
-
|
|
754
|
-
|
|
337
|
+
bun run lint
|
|
338
|
+
bun run build
|
|
339
|
+
bun test --runInBand
|
|
340
|
+
```
|
|
755
341
|
|
|
756
|
-
|
|
757
|
-
|
|
342
|
+
### Code applies but usage not incrementing
|
|
343
|
+
Expected behavior until order completion. Call record-order-usage on paid/completed order.
|
|
758
344
|
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
```
|
|
345
|
+
### Partner stats forbidden
|
|
346
|
+
Adjust `policies.canViewPartnerStats` to match your auth model.
|
|
762
347
|
|
|
763
|
-
|
|
348
|
+
### Per-customer limit not enforced
|
|
349
|
+
Pass `customerEmail` on validate/apply for coupons that define per-customer limits, and ensure order integration fields map to your schema.
|
|
764
350
|
|
|
765
|
-
|
|
766
|
-
- [Compatibility Matrix](./COMPATIBILITY.md)
|
|
767
|
-
- [Contributing Guide](./CONTRIBUTING.md)
|
|
351
|
+
---
|
|
768
352
|
|
|
769
|
-
##
|
|
353
|
+
## Exports
|
|
770
354
|
|
|
771
|
-
|
|
772
|
-
- **NPM**: https://npmjs.com/package/@wtree/payload-ecommerce-coupon
|
|
773
|
-
- **Payload CMS**: https://payloadcms.com
|
|
774
|
-
- **Payload Dashboard Docs**: https://payloadcms.com/docs/custom-components/dashboard
|
|
355
|
+
Main exports include:
|
|
775
356
|
|
|
776
|
-
|
|
357
|
+
- `payloadEcommerceCoupon` (plugin)
|
|
358
|
+
- `createCouponsCollection`
|
|
359
|
+
- `createReferralCodesCollection`
|
|
360
|
+
- `createReferralProgramsCollection`
|
|
361
|
+
- `useCouponCode`
|
|
362
|
+
- `validateCouponCode`
|
|
363
|
+
- `usePartnerStats`
|
|
364
|
+
- `getCartTotalWithDiscounts`
|
|
365
|
+
- `recordCouponUsageForOrder`
|
|
366
|
+
- full TypeScript types for plugin options/policies/integration
|
|
777
367
|
|
|
778
|
-
|
|
368
|
+
---
|
|
779
369
|
|
|
780
|
-
##
|
|
370
|
+
## License
|
|
781
371
|
|
|
782
|
-
|
|
372
|
+
MIT
|