@wtree/payload-ecommerce-coupon 3.77.5 β 3.77.7
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 +374 -660
- package/dist/client/hooks.d.ts +21 -13
- package/dist/client/hooks.d.ts.map +1 -1
- package/dist/client/index.d.ts +8 -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/CommissionBreakdown.d.ts +9 -0
- package/dist/components/PartnerDashboard/CommissionBreakdown.d.ts.map +1 -0
- package/dist/components/PartnerDashboard/EarningsSummary.d.ts +2 -2
- package/dist/components/PartnerDashboard/EarningsSummary.d.ts.map +1 -1
- package/dist/components/PartnerDashboard/ProgramOverview.d.ts +8 -0
- package/dist/components/PartnerDashboard/ProgramOverview.d.ts.map +1 -0
- 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 +3 -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 +17 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3110 -569
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1818 -542
- package/dist/index.mjs.map +1 -1
- package/dist/plugin.d.ts +2 -2
- package/dist/plugin.d.ts.map +1 -1
- package/dist/styles.css +714 -0
- 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 +9 -9
package/README.md
CHANGED
|
@@ -4,779 +4,493 @@
|
|
|
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" }
|
|
92
|
+
bun add @wtree/payload-ecommerce-coupon
|
|
209
93
|
```
|
|
210
94
|
|
|
211
|
-
|
|
95
|
+
---
|
|
212
96
|
|
|
213
|
-
|
|
214
|
-
import { recordCouponUsageForOrder } from "@wtree/payload-ecommerce-coupon";
|
|
97
|
+
## Quick start
|
|
215
98
|
|
|
216
|
-
|
|
217
|
-
if (doc.paymentStatus === "paid" && (doc.appliedCoupon || doc.appliedReferralCode)) {
|
|
218
|
-
await recordCouponUsageForOrder(payload, doc, pluginConfig);
|
|
219
|
-
}
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
- **Coupon:** increments the couponβs `usageCount`.
|
|
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
|
|
226
|
-
|
|
227
|
-
**Usage rule**
|
|
99
|
+
### 1) Register plugin
|
|
228
100
|
|
|
229
|
-
|
|
230
|
-
|
|
101
|
+
```ts
|
|
102
|
+
import { buildConfig } from 'payload'
|
|
103
|
+
import { ecommercePlugin } from '@payloadcms/plugin-ecommerce'
|
|
104
|
+
import { payloadEcommerceCoupon } from '@wtree/payload-ecommerce-coupon'
|
|
231
105
|
|
|
232
|
-
|
|
233
|
-
|
|
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
|
-
|
|
205
|
+
- `useCouponCode({ code, cartID, customerEmail? })`
|
|
206
|
+
- `validateCouponCode({ code, cartValue?, cartID?, customerEmail? })`
|
|
207
|
+
- `usePartnerStats()`
|
|
342
208
|
|
|
343
|
-
###
|
|
209
|
+
### PartnerDashboard UI component
|
|
344
210
|
|
|
345
|
-
|
|
211
|
+
The package also exports ready-to-use partner dashboard UI components:
|
|
346
212
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
1. **Navigate to Admin Panel** β Go to "Coupons" collection (under "Coupons" group)
|
|
360
|
-
2. **Create New Coupon**:
|
|
361
|
-
- **Code**: `WELCOME10` (unique identifier)
|
|
362
|
-
- **Type**: `Percentage` or `Fixed Amount`
|
|
363
|
-
- **Value**: `10` (10% or $10)
|
|
364
|
-
- **Description**: "Welcome discount for new customers"
|
|
365
|
-
- **Active From/Until**: Set validity period
|
|
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
|
|
213
|
+
```ts
|
|
214
|
+
import {
|
|
215
|
+
PartnerDashboard,
|
|
216
|
+
EarningsSummary,
|
|
217
|
+
ReferralPerformance,
|
|
218
|
+
RecentReferrals,
|
|
219
|
+
ReferralCodes,
|
|
220
|
+
ProgramOverview,
|
|
221
|
+
CommissionBreakdown,
|
|
222
|
+
} from '@wtree/payload-ecommerce-coupon'
|
|
223
|
+
import '@wtree/payload-ecommerce-coupon/dist/styles.css'
|
|
224
|
+
```
|
|
401
225
|
|
|
402
|
-
|
|
226
|
+
> The dashboard components are client-side React components. Use them in your frontend app (for example, a Next.js client component).
|
|
403
227
|
|
|
404
|
-
####
|
|
228
|
+
#### Quick usage
|
|
405
229
|
|
|
406
|
-
|
|
230
|
+
```tsx
|
|
231
|
+
'use client'
|
|
407
232
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
-H "Content-Type: application/json" \
|
|
411
|
-
-d '{"code": "WELCOME10", "cartValue": 5000}'
|
|
233
|
+
import { PartnerDashboard } from '@wtree/payload-ecommerce-coupon'
|
|
234
|
+
import '@wtree/payload-ecommerce-coupon/dist/styles.css'
|
|
412
235
|
|
|
413
|
-
|
|
414
|
-
|
|
236
|
+
export default function PartnerPage() {
|
|
237
|
+
return <PartnerDashboard apiEndpoint="/api/referrals/partner-stats" />
|
|
238
|
+
}
|
|
415
239
|
```
|
|
416
240
|
|
|
417
|
-
####
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
241
|
+
#### PartnerDashboard props
|
|
242
|
+
|
|
243
|
+
```ts
|
|
244
|
+
type PartnerDashboardProps = {
|
|
245
|
+
showEarningsSummary?: boolean
|
|
246
|
+
showReferralPerformance?: boolean
|
|
247
|
+
showRecentReferrals?: boolean
|
|
248
|
+
showReferralCodes?: boolean
|
|
249
|
+
showProgramOverview?: boolean
|
|
250
|
+
showCommissionBreakdown?: boolean
|
|
251
|
+
apiEndpoint?: string
|
|
252
|
+
}
|
|
428
253
|
```
|
|
429
254
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
255
|
+
Example with custom widget visibility:
|
|
256
|
+
|
|
257
|
+
```tsx
|
|
258
|
+
<PartnerDashboard
|
|
259
|
+
apiEndpoint="/api/referrals/partner-stats"
|
|
260
|
+
showEarningsSummary
|
|
261
|
+
showReferralPerformance
|
|
262
|
+
showRecentReferrals={false}
|
|
263
|
+
showReferralCodes
|
|
264
|
+
showProgramOverview
|
|
265
|
+
showCommissionBreakdown
|
|
266
|
+
/>
|
|
440
267
|
```
|
|
441
268
|
|
|
442
|
-
|
|
269
|
+
#### Theming and style-guide compatibility
|
|
443
270
|
|
|
444
|
-
|
|
271
|
+
The `PartnerDashboard` styles are tokenized with CSS variables so your app can keep its own design system and branding.
|
|
445
272
|
|
|
446
|
-
|
|
273
|
+
By default, dashboard typography inherits from the host app (`font-family: inherit`) and colors use overridable tokens.
|
|
447
274
|
|
|
448
|
-
|
|
275
|
+
Basic override example:
|
|
449
276
|
|
|
450
|
-
```
|
|
451
|
-
|
|
452
|
-
-
|
|
277
|
+
```css
|
|
278
|
+
.partner-dashboard {
|
|
279
|
+
--pd-color-text: var(--app-text-color);
|
|
280
|
+
--pd-color-text-muted: var(--app-text-muted);
|
|
281
|
+
--pd-color-surface: var(--app-surface);
|
|
282
|
+
--pd-color-surface-muted: var(--app-surface-muted);
|
|
283
|
+
--pd-color-border: var(--app-border);
|
|
284
|
+
--pd-color-primary: var(--app-primary);
|
|
285
|
+
--pd-color-primary-strong: var(--app-primary-strong);
|
|
286
|
+
--pd-color-success: var(--app-success);
|
|
287
|
+
--pd-color-warning: var(--app-warning);
|
|
288
|
+
--pd-color-danger: var(--app-danger);
|
|
289
|
+
}
|
|
453
290
|
```
|
|
454
291
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
```
|
|
458
|
-
{
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
},
|
|
471
|
-
"referralCodes": [...],
|
|
472
|
-
"program": {
|
|
473
|
-
"name": "Partner Program",
|
|
474
|
-
"commissionRate": 10,
|
|
475
|
-
"customerDiscount": 5
|
|
476
|
-
}
|
|
477
|
-
},
|
|
478
|
-
"currency": "USD"
|
|
292
|
+
You can also set global aliases once:
|
|
293
|
+
|
|
294
|
+
```css
|
|
295
|
+
:root {
|
|
296
|
+
--color-text: #111827;
|
|
297
|
+
--color-text-muted: #6b7280;
|
|
298
|
+
--color-surface: #ffffff;
|
|
299
|
+
--color-surface-muted: #f9fafb;
|
|
300
|
+
--color-border: #e5e7eb;
|
|
301
|
+
--color-primary: #2563eb;
|
|
302
|
+
--color-primary-strong: #1d4ed8;
|
|
303
|
+
--color-primary-soft: #dbeafe;
|
|
304
|
+
--color-success: #059669;
|
|
305
|
+
--color-warning: #d97706;
|
|
306
|
+
--color-danger: #dc2626;
|
|
479
307
|
}
|
|
480
308
|
```
|
|
481
309
|
|
|
482
|
-
|
|
310
|
+
Common dashboard tokens you may override:
|
|
483
311
|
|
|
484
|
-
|
|
312
|
+
- Layout/spacing: `--pd-space-*`, `--pd-grid-min-column`, `--pd-referrals-status-col-width`
|
|
313
|
+
- Typography: `--pd-font-family`, `--pd-font-size-*`, `--pd-font-weight-*`
|
|
314
|
+
- Surfaces/borders: `--pd-color-bg`, `--pd-color-surface`, `--pd-color-surface-muted`, `--pd-color-border`
|
|
315
|
+
- Semantic colors: `--pd-color-primary`, `--pd-color-success`, `--pd-color-warning`, `--pd-color-danger`
|
|
316
|
+
- Widget accents:
|
|
317
|
+
- `--pd-color-earnings-total-*`
|
|
318
|
+
- `--pd-color-earnings-pending-*`
|
|
319
|
+
- `--pd-color-earnings-paid-*`
|
|
320
|
+
- `--pd-color-status-pending-*`
|
|
321
|
+
- `--pd-color-status-paid-*`
|
|
322
|
+
- `--pd-color-status-cancelled-*`
|
|
485
323
|
|
|
486
|
-
|
|
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)
|
|
324
|
+
This keeps plugin UI behavior consistent while respecting your productβs style guide.
|
|
493
325
|
|
|
494
|
-
|
|
495
|
-
couponsSlug?: string; // Default: 'coupons'
|
|
496
|
-
referralProgramsSlug?: string; // Default: 'referral-programs'
|
|
497
|
-
referralCodesSlug?: string; // Default: 'referral-codes'
|
|
326
|
+
---
|
|
498
327
|
|
|
499
|
-
|
|
500
|
-
couponsCollectionOverride?: (params: { defaultCollection: any }) => any | Promise<any>;
|
|
328
|
+
## Configuration overview
|
|
501
329
|
|
|
502
|
-
|
|
503
|
-
|
|
330
|
+
```ts
|
|
331
|
+
type CouponPluginOptions = {
|
|
332
|
+
enabled?: boolean
|
|
333
|
+
enableReferrals?: boolean
|
|
334
|
+
allowStackWithOtherCoupons?: boolean
|
|
335
|
+
defaultCurrency?: string
|
|
336
|
+
autoIntegrate?: boolean
|
|
504
337
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
338
|
+
collections?: {
|
|
339
|
+
couponsSlug?: string
|
|
340
|
+
referralProgramsSlug?: string
|
|
341
|
+
referralCodesSlug?: string
|
|
342
|
+
referralPartnersSlug?: string
|
|
343
|
+
couponsCollectionOverride?: (params: { defaultCollection: any }) => any | Promise<any>
|
|
344
|
+
referralProgramsCollectionOverride?: (params: { defaultCollection: any }) => any | Promise<any>
|
|
345
|
+
referralCodesCollectionOverride?: (params: { defaultCollection: any }) => any | Promise<any>
|
|
346
|
+
}
|
|
508
347
|
|
|
509
348
|
endpoints?: {
|
|
510
|
-
applyCoupon?: string
|
|
511
|
-
validateCoupon?: string
|
|
512
|
-
partnerStats?: string
|
|
513
|
-
recordOrderUsage?: string
|
|
514
|
-
}
|
|
349
|
+
applyCoupon?: string
|
|
350
|
+
validateCoupon?: string
|
|
351
|
+
partnerStats?: string
|
|
352
|
+
recordOrderUsage?: string
|
|
353
|
+
}
|
|
515
354
|
|
|
355
|
+
// Legacy access support
|
|
516
356
|
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
|
-
```
|
|
357
|
+
canUseCoupons?: any
|
|
358
|
+
canUseReferrals?: any
|
|
359
|
+
isAdmin?: any
|
|
360
|
+
isPartner?: any
|
|
361
|
+
}
|
|
666
362
|
|
|
667
|
-
|
|
363
|
+
// Preferred policy-first API
|
|
364
|
+
policies?: {
|
|
365
|
+
canApplyCoupon?: (ctx: { req: unknown; user?: unknown; payload?: unknown }) => boolean | Promise<boolean>
|
|
366
|
+
canApplyReferral?: (ctx: { req: unknown; user?: unknown; payload?: unknown }) => boolean | Promise<boolean>
|
|
367
|
+
canViewPartnerStats?: (ctx: { req: unknown; user?: unknown; payload?: unknown; requestedPartnerID?: string | number }) => boolean | Promise<boolean>
|
|
368
|
+
canRecordOrderUsage?: (ctx: { req: unknown; user?: unknown; payload?: unknown; order: unknown }) => boolean | Promise<boolean>
|
|
369
|
+
}
|
|
668
370
|
|
|
669
|
-
|
|
371
|
+
// Integration-driven mapping
|
|
372
|
+
integration?: {
|
|
373
|
+
collections?: {
|
|
374
|
+
cartsSlug?: string
|
|
375
|
+
ordersSlug?: string
|
|
376
|
+
productsSlug?: string
|
|
377
|
+
usersSlug?: string
|
|
378
|
+
categoriesSlug?: string
|
|
379
|
+
tagsSlug?: string
|
|
380
|
+
}
|
|
381
|
+
fields?: {
|
|
382
|
+
cartItemsField?: string
|
|
383
|
+
cartSubtotalField?: string
|
|
384
|
+
cartTotalField?: string
|
|
385
|
+
cartAppliedCouponField?: string
|
|
386
|
+
cartAppliedReferralCodeField?: string
|
|
387
|
+
cartDiscountAmountField?: string
|
|
388
|
+
cartCustomerDiscountField?: string
|
|
389
|
+
cartPartnerCommissionField?: string
|
|
390
|
+
orderAppliedCouponField?: string
|
|
391
|
+
orderAppliedReferralCodeField?: string
|
|
392
|
+
orderDiscountAmountField?: string
|
|
393
|
+
orderCustomerDiscountField?: string
|
|
394
|
+
orderPartnerCommissionField?: string
|
|
395
|
+
orderCustomerEmailField?: string
|
|
396
|
+
orderPaymentStatusField?: string
|
|
397
|
+
orderCreatedAtField?: string
|
|
398
|
+
productPriceField?: string
|
|
399
|
+
productCurrencyCodeField?: string
|
|
400
|
+
}
|
|
401
|
+
resolvers?: {
|
|
402
|
+
getUserID?: (args: { req: unknown; user?: unknown }) => string | number | null | undefined
|
|
403
|
+
getCartItems?: (cart: unknown) => any[]
|
|
404
|
+
getCartSubtotal?: (cart: unknown) => number
|
|
405
|
+
getCartTotal?: (cart: unknown) => number
|
|
406
|
+
isOrderPaid?: (order: unknown) => boolean
|
|
407
|
+
getProductUnitPrice?: (args: { item: unknown; product: unknown; variant?: unknown; currencyCode?: string }) => number
|
|
408
|
+
}
|
|
409
|
+
}
|
|
670
410
|
|
|
671
|
-
|
|
411
|
+
referralConfig?: {
|
|
412
|
+
allowBothSystems?: boolean
|
|
413
|
+
singleCodePerCart?: boolean
|
|
414
|
+
defaultPartnerSplit?: number
|
|
415
|
+
defaultCustomerSplit?: number
|
|
416
|
+
allowedTotalCommissionTypes?: Array<'fixed' | 'percentage'>
|
|
417
|
+
}
|
|
672
418
|
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
419
|
+
orderIntegration?: {
|
|
420
|
+
ordersSlug?: string
|
|
421
|
+
orderCustomerEmailField?: string
|
|
422
|
+
orderPaymentStatusField?: string
|
|
423
|
+
orderPaidStatusValue?: string
|
|
424
|
+
}
|
|
677
425
|
|
|
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
|
-
});
|
|
426
|
+
roleConfig?: {
|
|
427
|
+
roleFieldPaths?: string[]
|
|
428
|
+
adminRoleValues?: string[]
|
|
429
|
+
partnerRoleValues?: string[]
|
|
430
|
+
customRoleResolver?: (user: unknown) => string[]
|
|
431
|
+
}
|
|
432
|
+
}
|
|
691
433
|
```
|
|
692
434
|
|
|
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.
|
|
704
|
-
|
|
705
|
-
- Solution: Remove the existing code before applying a new one, or set `singleCodePerCart: false`
|
|
435
|
+
---
|
|
706
436
|
|
|
707
|
-
|
|
437
|
+
## Migration notes (from role-assumption setups)
|
|
708
438
|
|
|
709
|
-
|
|
710
|
-
- Check the `isPartner` access control function
|
|
439
|
+
If youβre upgrading from older configs:
|
|
711
440
|
|
|
712
|
-
|
|
441
|
+
1. Prefer `policies` over role-specific assumptions.
|
|
442
|
+
2. Set `integration.collections` and `integration.fields` if your schema uses custom slugs/field names.
|
|
443
|
+
3. Ensure order completion flow calls usage recording.
|
|
444
|
+
4. Validate endpoint paths if you previously relied on custom routes.
|
|
713
445
|
|
|
714
|
-
|
|
446
|
+
---
|
|
715
447
|
|
|
716
|
-
|
|
448
|
+
## Troubleshooting
|
|
717
449
|
|
|
718
|
-
|
|
719
|
-
|
|
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
|
|
450
|
+
### Build/lint mismatch after changes
|
|
451
|
+
Run:
|
|
751
452
|
|
|
752
453
|
```bash
|
|
753
|
-
|
|
754
|
-
|
|
454
|
+
bun run lint
|
|
455
|
+
bun run build
|
|
456
|
+
bun test --runInBand
|
|
457
|
+
```
|
|
755
458
|
|
|
756
|
-
|
|
757
|
-
|
|
459
|
+
### Code applies but usage not incrementing
|
|
460
|
+
Expected behavior until order completion. Call record-order-usage on paid/completed order.
|
|
758
461
|
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
```
|
|
462
|
+
### Partner stats forbidden
|
|
463
|
+
Adjust `policies.canViewPartnerStats` to match your auth model.
|
|
762
464
|
|
|
763
|
-
|
|
465
|
+
### Per-customer limit not enforced
|
|
466
|
+
Pass `customerEmail` on validate/apply for coupons that define per-customer limits, and ensure order integration fields map to your schema.
|
|
764
467
|
|
|
765
|
-
|
|
766
|
-
- [Compatibility Matrix](./COMPATIBILITY.md)
|
|
767
|
-
- [Contributing Guide](./CONTRIBUTING.md)
|
|
468
|
+
---
|
|
768
469
|
|
|
769
|
-
##
|
|
470
|
+
## Exports
|
|
770
471
|
|
|
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
|
|
472
|
+
Main exports include:
|
|
775
473
|
|
|
776
|
-
|
|
474
|
+
- `payloadEcommerceCoupon` (plugin)
|
|
475
|
+
- `createCouponsCollection`
|
|
476
|
+
- `createReferralCodesCollection`
|
|
477
|
+
- `createReferralProgramsCollection`
|
|
478
|
+
- `useCouponCode`
|
|
479
|
+
- `validateCouponCode`
|
|
480
|
+
- `usePartnerStats`
|
|
481
|
+
- `PartnerDashboard`
|
|
482
|
+
- `EarningsSummary`
|
|
483
|
+
- `ReferralPerformance`
|
|
484
|
+
- `RecentReferrals`
|
|
485
|
+
- `ReferralCodes`
|
|
486
|
+
- `ProgramOverview`
|
|
487
|
+
- `CommissionBreakdown`
|
|
488
|
+
- `getCartTotalWithDiscounts`
|
|
489
|
+
- `recordCouponUsageForOrder`
|
|
490
|
+
- full TypeScript types for plugin options/policies/integration
|
|
777
491
|
|
|
778
|
-
|
|
492
|
+
---
|
|
779
493
|
|
|
780
|
-
##
|
|
494
|
+
## License
|
|
781
495
|
|
|
782
|
-
|
|
496
|
+
MIT
|