Skip to main content

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

FileResponsibility
PartnerService.tsCRUD for partnerOrganizations, member linking, metrics recalculation
PartnerInvitationService.tsInvitation lifecycle (generate, send, track, expire)
PartnerDiscountService.tsDiscount validation and application to member subscriptions
RevenueShareService.tsRevenue share calculation and Firestore ledger records
ReferralCreditService.tsCredit wallet management, awards, redemptions, gift card delivery
TremendousService.tsGift 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

CollectionDocument
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.

Invitation Code Format

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>

Revenue Share Formula

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

CollectionDocument
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

CallerUses
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 FunctionsRevenueShareService.recordRevenueShareEntry(), TremendousService.orderGiftCard()