Sponsor Ad Service
The SponsorAdService manages the full lifecycle of sponsor advertisements -- from submission and payment through admin review, activation, expiration, and renewal.
Source: lib/services/sponsor-ad.service.ts
Overview
Sponsor ads allow users to promote items on the platform by purchasing placement slots. The service enforces a multi-step workflow:
PENDING_PAYMENT --> (payment confirmed) --> PENDING --> (admin approved) --> ACTIVE
--> (admin rejected) --> REJECTED
ACTIVE --> (time elapsed) --> EXPIRED
ACTIVE --> (user/admin cancel) --> CANCELLED
EXPIRED/ACTIVE --> (renewed) --> ACTIVE
Initialization
The service is exported as a singleton:
import { sponsorAdService } from '@/lib/services/sponsor-ad.service';
Or create a new instance:
import { SponsorAdService } from '@/lib/services/sponsor-ad.service';
const service = new SponsorAdService();
Status Lifecycle
| Status | Display Name | Description |
|---|---|---|
PENDING_PAYMENT | Waiting for Payment | Submitted, awaiting payment |
PENDING | Pending Review | Payment received, awaiting admin review |
REJECTED | Rejected | Admin rejected the submission |
ACTIVE | Active | Live and displayed on the site |
EXPIRED | Expired | Duration elapsed, no longer displayed |
CANCELLED | Cancelled | Manually cancelled by user or admin |
Pricing Intervals
| Interval | Configuration Source |
|---|---|
weekly | getSponsorAdWeeklyPrice() |
monthly | getSponsorAdMonthlyPrice() |
Prices are read from application settings at runtime, with built-in defaults. Currency is also configurable via getSponsorAdCurrency().
Read Operations
Get Sponsor Ad by ID
const ad = await sponsorAdService.getSponsorAdById('ad-uuid');
Get Sponsor Ad with User Details
const adWithUser = await sponsorAdService.getSponsorAdWithUser('ad-uuid');
// Returns SponsorAdWithUser (includes user profile data)
Get Ads by User
const userAds = await sponsorAdService.getSponsorAdsByUserId('user-uuid');
Get Active Ads for Display
// Simple list
const activeAds = await sponsorAdService.getActiveSponsorAds(5);
// With associated item data (for sidebar display)
const adsWithItems = await sponsorAdService.getActiveSponsorAdsWithItems(5);
Get Pending Ads for Admin Review
const pendingAds = await sponsorAdService.getPendingSponsorAds();
Paginated List with Filters
const result = await sponsorAdService.getSponsorAdsPaginated({
page: 1,
limit: 20,
status: 'active',
});
// result: { sponsorAds, total, page, limit, totalPages }
Statistics
// Global statistics
const stats = await sponsorAdService.getSponsorAdStats();
// Per-user statistics
const userStats = await sponsorAdService.getSponsorAdStatsByUser('user-uuid');
Write Operations
Create a Sponsor Ad
Creates a new submission in PENDING_PAYMENT status:
const ad = await sponsorAdService.createSponsorAd('user-uuid', {
itemSlug: 'my-awesome-tool',
interval: 'weekly',
paymentProvider: 'stripe',
});
The method enforces uniqueness -- a user cannot have:
- An existing pending sponsorship for the same item
- An existing active sponsorship for the same item
The amount is automatically calculated based on the selected interval using configured pricing.
Confirm Payment
Called by the payment webhook when payment is received. Transitions from PENDING_PAYMENT to PENDING:
const ad = await sponsorAdService.confirmPayment(
'ad-uuid',
'sub_stripe_123', // optional subscriptionId
'cus_stripe_456', // optional customerId
);
Approve Sponsor Ad (Admin)
Approves and immediately activates the ad, calculating start and end dates:
// Normal approval (from PENDING status, payment already confirmed)
const ad = await sponsorAdService.approveSponsorAd('ad-uuid', 'admin-user-id');
// Force approval (from PENDING_PAYMENT, without payment)
const ad = await sponsorAdService.approveSponsorAd('ad-uuid', 'admin-user-id', true);
The forceApprove parameter allows admins to activate ads without payment (e.g., for promotional partnerships).
Reject Sponsor Ad (Admin)
Rejects the ad with a reason. Can reject from either PENDING_PAYMENT or PENDING status:
const ad = await sponsorAdService.rejectSponsorAd(
'ad-uuid',
'admin-user-id',
'Content does not meet our guidelines'
);
Cancel Sponsor Ad
Can cancel from PENDING_PAYMENT, PENDING, or ACTIVE status:
const ad = await sponsorAdService.cancelSponsorAd('ad-uuid', 'User requested cancellation');
Expire Sponsor Ad
Called by a cron job or webhook when the ad duration has elapsed. Only ACTIVE ads can be expired:
const ad = await sponsorAdService.expireSponsorAd('ad-uuid');
Renew Sponsor Ad
Extends an active or expired ad for another interval period:
const ad = await sponsorAdService.renewSponsorAd('ad-uuid');
The new start date is set to the current end date (or now if no end date), and the new end date is calculated from the interval.
Delete Sponsor Ad (Admin)
Hard delete, available to admins only:
await sponsorAdService.deleteSponsorAd('ad-uuid');
Helper Methods
Date Calculation
The end date is calculated based on the interval:
const endDate = sponsorAdService.calculateEndDate(startDate, 'weekly');
// 7 days from startDate
const endDate = sponsorAdService.calculateEndDate(startDate, 'monthly');
// 1 month from startDate
Amount Formatting
const display = sponsorAdService.formatAmount(29.99, 'usd');
// "$29.99"
Display Names
sponsorAdService.getIntervalDisplayName('weekly'); // "Weekly"
sponsorAdService.getIntervalDisplayName('monthly'); // "Monthly"
sponsorAdService.getStatusDisplayName('pending_payment'); // "Waiting for Payment"
sponsorAdService.getStatusDisplayName('active'); // "Active"
State Checks
// Can the ad be edited? (only in PENDING_PAYMENT status)
const editable = sponsorAdService.canEdit(ad);
// Can the ad be cancelled?
const cancellable = sponsorAdService.canCancel(ad);
// Has payment been received?
const paid = sponsorAdService.hasPayment(ad);
Integration with Payment Webhooks
The typical flow for Stripe integration:
- User submits a sponsor ad via the UI (creates ad in
PENDING_PAYMENTstatus) - User is redirected to Stripe checkout
- Stripe sends a
checkout.session.completedwebhook - Webhook handler calls
sponsorAdService.confirmPayment(adId, subscriptionId, customerId) - Ad moves to
PENDINGstatus - Admin reviews and calls
sponsorAdService.approveSponsorAd(adId, adminId) - Ad is now
ACTIVEwith calculated start/end dates
Expiration via Cron Job
Sponsor ad expiration is typically handled by a scheduled job that:
- Queries all
ACTIVEads whereendDateis in the past - Calls
sponsorAdService.expireSponsorAd(adId)for each
For subscription-based ads, the payment provider webhook can also trigger renewal or expiration.