Documentation Index
Fetch the complete documentation index at: https://grantmaster.dev/llms.txt
Use this file to discover all available pages before exploring further.
shared/partnerships — API Reference
The shared/partnerships module implements GrantMaster’s Partner Member Benefit Program — a reseller and referral network where partner organisations (NGO umbrella bodies, consultancies, accelerators) recruit member tenants, earn revenue share, and distribute subscription discounts.
Module Map
| File | Responsibility |
|---|
PartnerService.ts | CRUD for partnerOrganizations, member linking, metrics recalculation |
PartnerInvitationService.ts | Invitation lifecycle (generate, send, track, expire) |
PartnerDiscountService.ts | Discount validation and application to member subscriptions |
RevenueShareService.ts | Revenue share calculation and Firestore ledger records |
ReferralCreditService.ts | Credit wallet management, awards, redemptions, gift card delivery |
TremendousService.ts | Gift card order fulfilment via the Tremendous API |
PartnerService
Manages the partnerOrganizations Firestore collection and member org links.
Key Methods
// Create a new partner org (SuperAdmin only)
PartnerService.createPartnerOrg(data, createdBy): Promise<PartnerOrganization>
// Read
PartnerService.getPartnerOrg(partnerId): Promise<PartnerOrganization | null>
PartnerService.getPartnerByName(name): Promise<PartnerOrganization | null>
PartnerService.listPartnerOrgs(filters?: PartnerOrgFilters): Promise<PartnerOrganization[]>
// Update
PartnerService.updatePartnerOrg(partnerId, updates): Promise<void>
PartnerService.activatePartner(partnerId): Promise<void>
PartnerService.deactivatePartner(partnerId, unlinkMembers?): Promise<void>
// Member management
PartnerService.getMemberOrganizations(partnerId): Promise<Organization[]>
PartnerService.linkOrganizationToPartner(orgId, partnerId, discountPct?): Promise<void>
PartnerService.unlinkOrganizationFromPartner(orgId): Promise<void>
PartnerService.recalculatePartnerMetrics(partnerId): Promise<void>
// Analytics
PartnerService.getPartnerSummaryStats(): Promise<PartnerSummaryStats>
// Utilities
PartnerService.generateCodePrefix(name): string // "Grantmakers UK" → "GRA"
PartnerService.validatePartnerData(data): ValidationResult
PartnerService.calculateConversionRate(sent, converted): number
PartnerOrgFilters
interface PartnerOrgFilters {
status?: PartnerOrganizationStatus[]; // ACTIVE | INACTIVE | PENDING
minMemberCount?: number;
maxMemberCount?: number;
tags?: string[];
searchQuery?: string; // client-side name/email search
sortBy?: 'name' | 'memberCount' | 'totalRevenue' | 'createdAt';
sortOrder?: 'asc' | 'desc';
limitResults?: number;
}
Business Rules
discountPercentage must be 20–25%.
revenueSharePercentage must be 10–20%.
recalculatePartnerMetrics() is called automatically after link/unlink; it recomputes memberCount, activeSubscriptions, and totalRevenue from live Firestore data.
deactivatePartner(id, true) batch-unlinks all member orgs in one Firestore writeBatch.
Firestore Collections
| Collection | Document |
|---|
partnerOrganizations/{partnerId} | PartnerOrganization — partner profile and aggregate metrics |
organizations/{orgId} | Member org; updated with partnerOrgId, isPartnerMember, discountPercentage |
PartnerInvitationService
Handles the full invitation lifecycle from code generation through conversion tracking.
PARTNER_{PARTNER_PREFIX}_{RANDOM_6}
Example: PARTNER_GRA_X7K9P2
Key Methods
// Create
PartnerInvitationService.createInvitation(partnerOrgId, data, createdBy): Promise<MemberInvitation>
PartnerInvitationService.createBulkInvitations(partnerOrgId, invites, createdBy): Promise<MemberInvitation[]>
// Read
PartnerInvitationService.getInvitationByCode(code): Promise<MemberInvitation | null>
PartnerInvitationService.getInvitationById(id): Promise<MemberInvitation | null>
PartnerInvitationService.listInvitations(filters?: InvitationFilters): Promise<MemberInvitation[]>
PartnerInvitationService.getPartnerInvitationStats(partnerOrgId): Promise<InvitationStats>
// Status transitions
PartnerInvitationService.markInvitationSent(invitationId): Promise<void>
PartnerInvitationService.acceptInvitation(code, orgId): Promise<MemberInvitation>
PartnerInvitationService.rejectInvitation(code): Promise<void>
PartnerInvitationService.expireInvitation(invitationId): Promise<void>
PartnerInvitationService.expirePendingInvitations(): Promise<number> // batch job
// Reminders
PartnerInvitationService.sendInvitationReminder(invitationId): Promise<void>
Invitation Status Flow
PENDING → SENT → ACCEPTED (links org to partner, triggers metrics recalc)
→ REJECTED
→ EXPIRED (30-day default TTL)
Firestore Collection
memberInvitations/{invitationId} — stores MemberInvitation with code, status, expiry, and conversion metadata.
PartnerDiscountService
Validates and applies subscription discounts for partner member organisations.
Key Methods
PartnerDiscountService.getDiscountForOrg(orgId): Promise<DiscountInfo | null>
PartnerDiscountService.applyDiscount(orgId, basePrice): Promise<{ finalPrice, discountAmount, discountPct }>
PartnerDiscountService.validateDiscountPercentage(pct): boolean // must be 20–25
RevenueShareService
Calculates revenue share owed to partner organisations and records entries to a Firestore ledger.
Key Methods
RevenueShareService.calculateRevenueShare(partnerId, period): Promise<RevenueShareCalculation>
RevenueShareService.recordRevenueShareEntry(entry): Promise<string>
RevenueShareService.getRevenueShareHistory(partnerId, limit?): Promise<RevenueShareEntry[]>
RevenueShareService.getPendingPayouts(partnerId): Promise<RevenueShareEntry[]>
RevenueShareService.markPayoutComplete(entryId, transactionRef): Promise<void>
revenueShare = memberMRR × (1 - discountPct / 100) × (revenueSharePct / 100)
Partners earn 10–20% of net MRR generated by their member organisations.
Firestore Collection
revenueShareEntries/{entryId} — ledger of monthly revenue share calculations.
ReferralCreditService
Manages the mission-credit wallet for individual users participating in the referral programme.
Key Methods
// Wallet
getMissionCredit(userId, organizationId): Promise<MissionCredit>
// Credits
awardReferralCredits(referrerId, referralId, orgId): Promise<void>
getCreditTransactions(userId, limitN?): Promise<CreditTransaction[]>
getMissionImpactStats(userId): Promise<MissionImpactStats>
getCampaigns(): Promise<Campaign[]>
// Redemptions
redeemForOrgDiscount(userId, orgId, amount): Promise<CreditRedemption>
redeemForGiftCard(userId, orgId, amount, type, recipientEmail): Promise<CreditRedemption>
getRedemptionHistory(userId, limitN?): Promise<CreditRedemption[]>
Credit Constants
MISSION_CREDIT_CONSTANTS = {
DEFAULT_REWARD_AMOUNT: 100, // €100 per Growth Tier referral
MINIMUM_REDEMPTION: 50, // €50 minimum for any redemption
GROWTH_TIER_ONLY: true, // Starter Tier signups earn €0
STARTER_TIER_REWARD: 0,
}
Firestore Collections
| Collection | Document |
|---|
missionCredits/{userId} | MissionCredit — balance, totals, referral count |
creditTransactions/{txId} | Individual credit/debit transaction records |
creditRedemptions/{id} | Redemption requests with status and fulfilment metadata |
TremendousService
Wraps the Tremendous gift card API for referral credit redemptions.
Key Methods
TremendousService.orderGiftCard(params: GiftCardOrderParams): Promise<TremendousOrder>
TremendousService.getOrderStatus(orderId): Promise<TremendousOrderStatus>
TremendousService.getSupportedGiftCards(): Promise<GiftCardType[]>
GiftCardOrderParams
interface GiftCardOrderParams {
recipientEmail: string;
recipientName: string;
amount: number; // EUR
giftCardType: GiftCardType;
redemptionId: string; // linked credit redemption
}
The sendReferralGiftCardEmail() in referralCreditEmails.ts sends the Postmark REFERRAL_GIFT_CARD_DELIVERY template once Tremendous confirms the order.
EventBus Events
This module emits no EventBus events directly. Revenue share and credit award flows trigger Postmark email notifications instead of in-app events.
Gap: There are no PARTNER_JOINED, REFERRAL_CREDIT_AWARDED, or REVENUE_SHARE_PAID EventBus events. If downstream features (compliance, audit) need to react to partnership lifecycle changes, EventBus emissions should be added to PartnerInvitationService.acceptInvitation() and ReferralCreditService.awardReferralCredits().
Callers
| Caller | Uses |
|---|
src/features/partnerships/ | UI layer — reads/writes via all services |
src/features/superadmin/ | PartnerService CRUD, summary stats |
src/features/mission/ | ReferralCreditService wallet and stats |
| Firebase Cloud Functions | RevenueShareService.recordRevenueShareEntry(), TremendousService.orderGiftCard() |