@wtree/payload-ecommerce-coupon 3.77.5 β†’ 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.
Files changed (53) hide show
  1. package/README.md +274 -684
  2. package/dist/client/hooks.d.ts +21 -13
  3. package/dist/client/hooks.d.ts.map +1 -1
  4. package/dist/client/index.d.ts +6 -6
  5. package/dist/client/index.d.ts.map +1 -1
  6. package/dist/collections/createCouponsCollection.d.ts +2 -2
  7. package/dist/collections/createCouponsCollection.d.ts.map +1 -1
  8. package/dist/collections/createReferralCodesCollection.d.ts +2 -2
  9. package/dist/collections/createReferralCodesCollection.d.ts.map +1 -1
  10. package/dist/collections/createReferralProgramsCollection.d.ts +2 -2
  11. package/dist/collections/createReferralProgramsCollection.d.ts.map +1 -1
  12. package/dist/components/PartnerDashboard/EarningsSummary.d.ts +2 -2
  13. package/dist/components/PartnerDashboard/EarningsSummary.d.ts.map +1 -1
  14. package/dist/components/PartnerDashboard/RecentReferrals.d.ts +3 -3
  15. package/dist/components/PartnerDashboard/RecentReferrals.d.ts.map +1 -1
  16. package/dist/components/PartnerDashboard/ReferralCodes.d.ts +3 -3
  17. package/dist/components/PartnerDashboard/ReferralCodes.d.ts.map +1 -1
  18. package/dist/components/PartnerDashboard/ReferralPerformance.d.ts +2 -2
  19. package/dist/components/PartnerDashboard/ReferralPerformance.d.ts.map +1 -1
  20. package/dist/components/PartnerDashboard/index.d.ts +2 -2
  21. package/dist/components/PartnerDashboard/index.d.ts.map +1 -1
  22. package/dist/endpoints/applyCoupon.d.ts +2 -2
  23. package/dist/endpoints/applyCoupon.d.ts.map +1 -1
  24. package/dist/endpoints/partnerStats.d.ts +2 -2
  25. package/dist/endpoints/partnerStats.d.ts.map +1 -1
  26. package/dist/endpoints/validateCoupon.d.ts +2 -2
  27. package/dist/endpoints/validateCoupon.d.ts.map +1 -1
  28. package/dist/hooks/recalculateCart.d.ts +2 -2
  29. package/dist/hooks/recalculateCart.d.ts.map +1 -1
  30. package/dist/index.d.ts +10 -10
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +1124 -544
  33. package/dist/index.js.map +1 -1
  34. package/dist/index.mjs +1123 -542
  35. package/dist/index.mjs.map +1 -1
  36. package/dist/plugin.d.ts +2 -2
  37. package/dist/plugin.d.ts.map +1 -1
  38. package/dist/types.d.ts +103 -9
  39. package/dist/types.d.ts.map +1 -1
  40. package/dist/utilities/calculateValues.d.ts +2 -2
  41. package/dist/utilities/calculateValues.d.ts.map +1 -1
  42. package/dist/utilities/getCartTotalWithDiscounts.d.ts.map +1 -1
  43. package/dist/utilities/migrateReferralRulesV2.d.ts.map +1 -1
  44. package/dist/utilities/pricing.d.ts.map +1 -1
  45. package/dist/utilities/pushTypeScriptProperties.d.ts +1 -1
  46. package/dist/utilities/pushTypeScriptProperties.d.ts.map +1 -1
  47. package/dist/utilities/recordCouponUsageForOrder.d.ts +11 -17
  48. package/dist/utilities/recordCouponUsageForOrder.d.ts.map +1 -1
  49. package/dist/utilities/sanitizePluginConfig.d.ts +1 -1
  50. package/dist/utilities/sanitizePluginConfig.d.ts.map +1 -1
  51. package/dist/utilities/userRoles.d.ts +3 -3
  52. package/dist/utilities/userRoles.d.ts.map +1 -1
  53. package/package.json +8 -8
package/README.md CHANGED
@@ -4,779 +4,369 @@
4
4
  [![License](https://img.shields.io/badge/license-MIT-blue?style=flat-square)](./LICENSE)
5
5
  [![Node Version](https://img.shields.io/node/v/@wtree/payload-ecommerce-coupon?style=flat-square)](https://nodejs.org)
6
6
 
7
- Production-ready coupon and referral system plugin for **Payload CMS** with seamless integration to the **@payloadcms/plugin-ecommerce** package.
7
+ Production-ready coupon and referral plugin for **Payload CMS** + **@payloadcms/plugin-ecommerce** with a **policy-first**, **integration-driven** architecture.
8
8
 
9
- ## πŸš€ Features
9
+ ---
10
10
 
11
- ### **System Modes**
11
+ ## Why this version
12
12
 
13
- - **Coupon Mode** (`enableReferrals: false`) – Traditional discount codes
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
- ### **Coupon Mode Features**
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
- - βœ… **Flexible Discounts** – Percentage or fixed amount discounts (all amounts rounded to 2 decimals)
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
- ### **Referral Mode Features**
23
+ ## Features
26
24
 
27
- - βœ… **Commission Rules** – **Required.** At least one rule per program. Each rule supports:
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
- ### **Core Features**
27
+ - **Coupon Mode** (`enableReferrals: false`)
28
+ - **Referral Mode** (`enableReferrals: true`)
29
+ - **Hybrid Mode** (`enableReferrals: true` + `referralConfig.allowBothSystems: true`)
39
30
 
40
- - βœ… **REST API** – Validate, apply, and record usage when order is placed
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
- ## πŸ“¦ Installation
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
- ```bash
54
- npm install @wtree/payload-ecommerce-coupon
55
- ```
40
+ ### Referral capabilities
56
41
 
57
- ### Requirements
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
- - `payload@^3.0.0` (Payload CMS)
60
- - `@payloadcms/plugin-ecommerce@>=3.0.0` (required peer dependency)
61
- - `node@>=18.0.0`
48
+ ### Core plugin behavior
62
49
 
63
- ## πŸ”§ Quick Start
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
- ### 1. Register the Plugin
56
+ ---
66
57
 
67
- In your `payload.config.ts`:
58
+ ## Requirements
68
59
 
69
- ```typescript
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
- export default buildConfig({
75
- plugins: [
76
- ecommercePlugin({
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
- // Referral-specific configuration
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
- // Custom admin panel groups
95
- adminGroups: {
96
- couponsGroup: "Coupons",
97
- referralsGroup: "Referrals",
98
- },
68
+ Your app should include (or map via integration config):
99
69
 
100
- // Partner dashboard configuration
101
- partnerDashboard: {
102
- enabled: true,
103
- showEarningsSummary: true,
104
- showReferralPerformance: true,
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
- // Access control
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
- // Optional: role resolver for custom user schemas
118
- // roleConfig: {
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
- ### 2. Database Migration
81
+ ---
137
82
 
138
- After adding the plugin, run your Payload migration to create the new collections:
83
+ ## Installation
139
84
 
140
85
  ```bash
141
- npm run payload migrate
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
- ### 4. Record Usage When Order Is Placed
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
- POST /api/coupons/record-order-usage
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
- - **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
95
+ ---
226
96
 
227
- **Usage rule**
97
+ ## Quick start
228
98
 
229
- - A customer can use a coupon until the coupon’s **global usage limit** or **expiry date** (usage is counted when the order is placed, not when the code is applied).
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
- **Monetary values**
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
- - All discount, commission, and total values are rounded to **2 decimal places**.
235
-
236
- **Cart total in your app**
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
- - The plugin writes the reduced `total` when a code is applied. If your host app recalculates the cart total (e.g. in a `beforeChange` hook when items change), use the formula **total = subtotal βˆ’ discountAmount βˆ’ customerDiscount** so the discount is not overwritten. Use the provided helper in your Carts collection:
116
+ referralConfig: {
117
+ allowBothSystems: false,
118
+ singleCodePerCart: true,
119
+ defaultPartnerSplit: 70,
120
+ defaultCustomerSplit: 30,
121
+ allowedTotalCommissionTypes: ['fixed', 'percentage'],
122
+ },
239
123
 
240
- ```typescript
241
- import { getCartTotalWithDiscounts } from "@wtree/payload-ecommerce-coupon";
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
- // In your Carts collection beforeChange hook, after setting items/subtotal:
244
- data.total = getCartTotalWithDiscounts(data);
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
- - **Optional config** for per-customer limit: `orderIntegration` with `ordersSlug`, `orderCustomerEmailField`, `orderPaymentStatusField`, `orderPaidStatusValue` (defaults: `'orders'`, `'customerEmail'`, `'paymentStatus'`, `'paid'`).
168
+ ---
248
169
 
249
- **Server utilities (for host app)**
170
+ ## Usage lifecycle (important)
250
171
 
251
- - `getCartTotalWithDiscounts(cart)` – Returns `roundTo2(subtotal - discountAmount - customerDiscount)`. Use in your Carts `beforeChange` (or wherever you compute total) so the displayed total always reflects coupon/referral discounts.
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
- ### 5. Frontend Integration
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
- #### Apply Coupon/Referral Code
177
+ ### Usage is recorded when order is placed
257
178
 
258
- ```typescript
259
- import { useCouponCode } from '@wtree/payload-ecommerce-coupon'
179
+ Call usage recording when an order is paid/completed:
260
180
 
261
- function CheckoutComponent() {
262
- const [code, setCode] = useState('')
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
- const applyCode = async () => {
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
- if (result.success) {
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
- return (
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
- #### Partner Dashboard
190
+ Default paths (customizable via `endpoints` config):
300
191
 
301
- Use the `usePartnerStats` hook to build a custom dashboard, or use the pre-built dashboard components when available from the package. See [Partner Dashboard docs](./docs/partner-dashboard.md).
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
- ```typescript
304
- import { usePartnerStats } from '@wtree/payload-ecommerce-coupon'
197
+ ---
305
198
 
306
- // Build custom dashboard with the hook
307
- function CustomPartnerDashboard() {
308
- const [data, setData] = useState(null)
199
+ ## Frontend helpers
309
200
 
310
- useEffect(() => {
311
- const fetchStats = async () => {
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
- ## πŸ‘¨β€πŸ’Ό Admin Usage Guide
342
-
343
- ### **Choosing Your Mode**
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
- Best when you need both traditional coupons AND partner referrals, but want to enforce only one code per order.
209
+ ---
356
210
 
357
- ### **Setting Up Coupon Mode**
211
+ ## Configuration overview
358
212
 
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
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; // Default: 'coupons'
496
- referralProgramsSlug?: string; // Default: 'referral-programs'
497
- referralCodesSlug?: string; // Default: 'referral-codes'
498
-
499
- /** Override the default coupons collection configuration */
500
- couponsCollectionOverride?: (params: { defaultCollection: any }) => any | Promise<any>;
501
-
502
- /** Override the default referral programs collection configuration */
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; // Default: '/coupons/apply'
511
- validateCoupon?: string; // Default: '/coupons/validate'
512
- partnerStats?: string; // Default: '/referrals/partner-stats'
513
- recordOrderUsage?: string; // Default: '/coupons/record-order-usage'
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?: Access; // Who can use coupons
518
- canUseReferrals?: Access; // Who can use referrals
519
- isAdmin?: Access; // Who can manage codes/programs
520
- isPartner?: Access; // Who has partner access
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
- Dashboard components (`PartnerDashboard`, `EarningsSummary`, `ReferralPerformance`, `RecentReferrals`, `ReferralCodes`) are available from the package source; see [Partner Dashboard docs](./docs/partner-dashboard.md) for usage.
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
- ### **Collection Creation Functions**
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
- You can use the collection creation functions directly in your Payload config to customize collections before they're added to the config.
294
+ referralConfig?: {
295
+ allowBothSystems?: boolean
296
+ singleCodePerCart?: boolean
297
+ defaultPartnerSplit?: number
298
+ defaultCustomerSplit?: number
299
+ allowedTotalCommissionTypes?: Array<'fixed' | 'percentage'>
300
+ }
672
301
 
673
- ```typescript
674
- import { buildConfig } from "payload";
675
- import { ecommercePlugin } from "@payloadcms/plugin-ecommerce";
676
- import { payloadEcommerceCoupon, createCouponsCollection } from "@wtree/payload-ecommerce-coupon";
302
+ orderIntegration?: {
303
+ ordersSlug?: string
304
+ orderCustomerEmailField?: string
305
+ orderPaymentStatusField?: string
306
+ orderPaidStatusValue?: string
307
+ }
677
308
 
678
- export default buildConfig({
679
- plugins: [
680
- ecommercePlugin({
681
- // your ecommerce configuration
682
- }),
683
- payloadEcommerceCoupon({
684
- // plugin configuration
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
- ## 🎨 Partner Dashboard
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
- - Solution: Remove the existing code before applying a new one, or set `singleCodePerCart: false`
320
+ ## Migration notes (from role-assumption setups)
706
321
 
707
- #### **Partner can't see their referral codes**
322
+ If you’re upgrading from older configs:
708
323
 
709
- - Ensure the user has `role: 'partner'` or `roles: ['partner']`
710
- - Check the `isPartner` access control function
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
- #### **Usage count or partner earnings not updating**
329
+ ---
713
330
 
714
- - Usage is **not** incremented when a code is applied to the cart. Call **record-order-usage** (or `recordCouponUsageForOrder`) when an order is placed/paid. See [Record usage when order is placed](#4-record-usage-when-order-is-placed).
331
+ ## Troubleshooting
715
332
 
716
- #### **Commission not calculating correctly**
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
- # Run all tests
754
- npm test
337
+ bun run lint
338
+ bun run build
339
+ bun test --runInBand
340
+ ```
755
341
 
756
- # Watch mode
757
- npm run test:watch
342
+ ### Code applies but usage not incrementing
343
+ Expected behavior until order completion. Call record-order-usage on paid/completed order.
758
344
 
759
- # Coverage report
760
- npm run test:coverage
761
- ```
345
+ ### Partner stats forbidden
346
+ Adjust `policies.canViewPartnerStats` to match your auth model.
762
347
 
763
- ## πŸ“š Documentation
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
- - [API Reference](./docs/api.md)
766
- - [Compatibility Matrix](./COMPATIBILITY.md)
767
- - [Contributing Guide](./CONTRIBUTING.md)
351
+ ---
768
352
 
769
- ## πŸ”— Links
353
+ ## Exports
770
354
 
771
- - **GitHub**: https://github.com/technewwings/payload-ecommerce-coupon
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
- ## πŸ“„ License
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
- MIT License Β© 2026 wtree. See [LICENSE](./LICENSE) for details.
368
+ ---
779
369
 
780
- ## 🀝 Contributing
370
+ ## License
781
371
 
782
- Contributions are welcome! See [CONTRIBUTING.md](./CONTRIBUTING.md) for guidelines.
372
+ MIT