@happyvertical/smrt-affiliates 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md ADDED
@@ -0,0 +1,62 @@
1
+ # @happyvertical/smrt-affiliates
2
+
3
+ Partner revenue sharing with multi-type partners, multi-tier commissions, and payout processing.
4
+
5
+ ## Models
6
+
7
+ - **Partner**: `partnerTypes` JSON array (publisher/salesperson/referrer — multi-role). `parentPartnerId` for site-attached salespeople. `referredById` for referral attribution. `commissionRate`, `parentCommissionShare`.
8
+ - **Commission**: 4 types per ad event — Display (publisher), Referral (referrer), Sales (salesperson), Parent (parent publisher's share). **Immutable** — no update/delete API.
9
+ - **Payout**: batch aggregation. Status: `PENDING → APPROVED → PROCESSING → COMPLETED` (or FAILED).
10
+
11
+ ## Currency
12
+
13
+ **All monetary fields are integer cents.** Helpers: `getTotalInDollars()`, `getAmountInDollars()`. `Commission.calculateAmount(grossRevenue, rate)` uses `Math.round()`.
14
+
15
+ ## Parent Commission Share
16
+
17
+ ```
18
+ Salesperson.parentCommissionShare = 0.20 (20% to parent publisher)
19
+ Effective sales rate = salesRate × (1 - parentCommissionShare)
20
+ ```
21
+
22
+ ## Cross-Package References (plain strings)
23
+
24
+ `profileId` → smrt-profiles, `propertyId` → smrt-properties, `eventId` → smrt-ads, `invoiceId` → smrt-commerce
25
+
26
+ ## Tenancy
27
+
28
+ Per `docs/content/standards.md §7`, tenant-aware models normally apply
29
+ `@TenantScoped({ mode: 'optional' })` from `@happyvertical/smrt-tenancy`. The
30
+ three models in this package deviate intentionally; each `@smrt(...)` block
31
+ carries an inline comment pointing back to this section.
32
+
33
+ `Partner`, `Commission`, and `Payout` are deliberately **NOT** tenant-scoped.
34
+ The affiliate network is a cross-tenant graph by design:
35
+
36
+ - A single `Partner` (e.g. a publisher operating multiple sites across
37
+ different tenants) needs a stable identity for revenue aggregation,
38
+ payout thresholds, and tax reporting. Slicing partner identity per
39
+ tenant would either duplicate the row or hide payouts owed across
40
+ tenants.
41
+ - `Commission` rows attribute revenue to a partner across whichever tenant
42
+ generated the ad event; the cross-tenant attribution is the point of the
43
+ network.
44
+ - `Payout` aggregates commissions for a partner regardless of which tenant
45
+ the underlying revenue came from. A tenant-scoped query would produce
46
+ systematically incorrect totals.
47
+
48
+ This is the same reasoning that keeps `TenantKey` in `packages/secrets`
49
+ out of the tenancy interceptor: rows that must be queried across tenants
50
+ to fulfil their purpose should not be silently filtered.
51
+
52
+ Operators that need tenant-attributed reporting should aggregate by
53
+ joining `Commission` rows back to `eventId` (smrt-ads) and the originating
54
+ ad's tenant — not by adding `@TenantScoped` here.
55
+
56
+ ## Gotchas
57
+
58
+ - **No tenancy** (intentional): cross-tenant network visibility for affiliate tracking — see Tenancy section above for rationale
59
+ - **partnerTypes is JSON string**: must parse with `getPartnerTypes()` helper
60
+ - **Commission rate copied at event time**: immutable record, not a live reference to Partner.commissionRate
61
+ - **Payout amounts in cents**: divide by 100 for display
62
+ - **No ledger integration**: Payout → Invoice mapping is external
package/CLAUDE.md ADDED
@@ -0,0 +1 @@
1
+ @AGENTS.md
package/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright <2025> <Happy Vertical Corporation>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,118 @@
1
+ # @happyvertical/smrt-affiliates
2
+
3
+ Affiliate partner and commission tracking models for the SMRT framework. Manages multi-type partners (publisher/salesperson/referrer), multi-tier commission attribution, and payout batch processing.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pnpm add @happyvertical/smrt-affiliates
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```typescript
14
+ import {
15
+ Partner, PartnerCollection,
16
+ Commission, CommissionCollection,
17
+ Payout, PayoutCollection,
18
+ PartnerType, CommissionType, CommissionStatus, PayoutStatus
19
+ } from '@happyvertical/smrt-affiliates';
20
+
21
+ // Register a publisher partner (earns display commissions)
22
+ const partners = new PartnerCollection(db);
23
+ const publisher = await partners.create({
24
+ profileId: 'profile-uuid',
25
+ propertyId: 'property-uuid',
26
+ partnerTypes: JSON.stringify([PartnerType.PUBLISHER]),
27
+ displayCommissionRate: 0.50,
28
+ status: 'active',
29
+ });
30
+
31
+ // Attach a salesperson to the publisher
32
+ // parentCommissionShare: 20% of sales commission goes to parent publisher
33
+ const salesperson = await partners.create({
34
+ profileId: 'sales-profile-uuid',
35
+ parentPartnerId: publisher.id,
36
+ partnerTypes: JSON.stringify([PartnerType.SALESPERSON]),
37
+ salesCommissionRate: 0.10,
38
+ parentCommissionShare: 0.20,
39
+ status: 'active',
40
+ });
41
+ // Effective sales rate: 0.10 * (1 - 0.20) = 0.08
42
+ salesperson.getEffectiveSalesRate(); // 0.08
43
+
44
+ // Record a commission (all monetary values in integer cents)
45
+ const commissions = new CommissionCollection(db);
46
+ await commissions.create({
47
+ eventId: 'adevent-uuid',
48
+ partnerId: publisher.id,
49
+ commissionType: CommissionType.DISPLAY,
50
+ grossRevenue: 1000, // $10.00
51
+ commissionRate: 0.50,
52
+ commissionAmount: Commission.calculateAmount(1000, 0.50), // 500 cents
53
+ currency: 'CAD',
54
+ status: CommissionStatus.PENDING,
55
+ });
56
+
57
+ // Create a payout batch for the publisher
58
+ const payouts = new PayoutCollection(db);
59
+ const payout = await payouts.create({
60
+ partnerId: publisher.id,
61
+ periodStart: new Date('2024-01-01'),
62
+ periodEnd: new Date('2024-01-31'),
63
+ displayEarnings: 25000, // $250.00
64
+ referralEarnings: 500, // $5.00
65
+ salesEarnings: 0,
66
+ parentEarnings: 0,
67
+ totalAmount: 25500, // $255.00
68
+ currency: 'CAD',
69
+ status: PayoutStatus.PENDING,
70
+ });
71
+
72
+ // Payout lifecycle: PENDING -> APPROVED -> PROCESSING -> COMPLETED (or FAILED)
73
+ payout.approve();
74
+ payout.markProcessing();
75
+ payout.complete('transfer-ref-123');
76
+ await payout.save();
77
+ ```
78
+
79
+ ### Commission Types
80
+
81
+ Each ad event can generate up to four commissions:
82
+
83
+ | Type | Recipient | Description |
84
+ |------|-----------|-------------|
85
+ | `DISPLAY` | Publisher | Site owner earns share of impression revenue |
86
+ | `REFERRAL` | Referrer | Partner who referred the publisher |
87
+ | `SALES` | Salesperson | Partner who brought in the advertiser |
88
+ | `PARENT` | Parent publisher | Share of salesperson's commission |
89
+
90
+ ## API
91
+
92
+ ### Models
93
+
94
+ | Export | Description |
95
+ |--------|------------|
96
+ | `Partner` | Affiliate partner with multi-type roles, commission rates, payout threshold, and parent hierarchy |
97
+ | `Commission` | Immutable revenue attribution record (no delete). `calculateAmount(grossRevenue, rate)` static helper |
98
+ | `Payout` | Aggregated payment batch with status lifecycle and per-type earnings breakdown |
99
+
100
+ ### Collections
101
+
102
+ `PartnerCollection`, `CommissionCollection`, `PayoutCollection`
103
+
104
+ ### Enums
105
+
106
+ | Export | Values |
107
+ |--------|--------|
108
+ | `PartnerType` | `publisher`, `salesperson`, `referrer` |
109
+ | `PartnerStatus` | `pending`, `active`, `suspended` |
110
+ | `CommissionType` | `display`, `referral`, `sales`, `parent` |
111
+ | `CommissionStatus` | `pending`, `included`, `paid` |
112
+ | `PayoutStatus` | `pending`, `approved`, `processing`, `completed`, `failed` |
113
+ | `PayoutMethod` | `bank_transfer`, `check`, `paypal`, `credit` |
114
+
115
+ ## Dependencies
116
+
117
+ - `@happyvertical/smrt-core` -- ORM and code generation
118
+ - Peer: `@happyvertical/smrt-ads`, `@happyvertical/smrt-commerce`, `@happyvertical/smrt-profiles`, `@happyvertical/smrt-properties`