Skip to content

feat: automated billing cycles, dunning retry, promo integration, reports UI [#1198 #1199 #1200 #1201]#1262

Merged
yusuftomilola merged 2 commits into
DistinctCodes:mainfrom
Tinna23:feature/tinna23-issues-1198-1199-1200-1201
Jun 28, 2026
Merged

feat: automated billing cycles, dunning retry, promo integration, reports UI [#1198 #1199 #1200 #1201]#1262
yusuftomilola merged 2 commits into
DistinctCodes:mainfrom
Tinna23:feature/tinna23-issues-1198-1199-1200-1201

Conversation

@Tinna23

@Tinna23 Tinna23 commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes #1194
Closes #1195
Closes #1196
Closes #1197


BE-25 — Automated Billing Cycles for Monthly Bookings

Introduces a BillingCycle entity and BillingService with a nightly cron job:

BillingCycle entity (billing_cycles table):

  • bookingId, userId, periodStart, periodEnd, amountKobo
  • status: pending | invoiced | paid | failed | cancelled
  • invoiceId (linked when paid), retryCount, nextRetryAt, failureReason

Cron job (@Cron(EVERY_DAY_AT_MIDNIGHT)): scans all CONFIRMED bookings with planType = MONTHLY and creates one BillingCycle per day if one doesn't already exist for that period. Idempotent — skips already-created cycles.

Endpoints:

Method Path Access
GET /billing/cycles Admin
GET /billing/my-cycles Member

BE-26 — Failed Payment Dunning & Retry Logic

DunningService runs daily at 06:00 (@Cron('0 6 * * *')). It:

  1. Fetches all FAILED billing cycles where nextRetryAt <= now
  2. Increments retryCount and schedules the next retry based on a staggered delay schedule: +24 h → +72 h → +168 h
  3. After 3 exhausted attempts, logs a warning and skips (cycle can be manually reviewed/cancelled)

The design is a clean hook point — the actual payment charge call would be inserted into processFailedPayments() where noted, without changing the retry scheduling logic.


BE-27 — Promo Code → Booking Integration

The PromoCodesModule and PromoCode entity already existed. This PR wires promo codes directly into confirmed bookings:

New endpoint: POST /promo-codes/apply

Body: { code, bookingId, bookingAmount }

Flow:

  1. Validates the code (same logic as POST /promo-codes/validate — checks expiry, usage limits, workspace restrictions, per-user usage)
  2. Calculates the discount (percentage or fixed kobo)
  3. Updates the Booking row: sets appliedPromoCodeId, promoDiscountApplied, and the reduced totalAmount
  4. Records a PromoCodeUsage entry and increments the promo code's usedCount — all in a single transaction

New DTO: ApplyPromoCodeDto (code, bookingId, bookingAmount)


FE-23 — Advanced Reports Frontend (/admin/reports)

Full-featured reports dashboard with four tabs:

Tab Data
Bookings Table: Member, Workspace, Start/End date, Status badge, Total
Revenue Stat cards (Total / Paid / Outstanding) + invoice table
Members Stat cards (Total / New in period) + member table
Occupancy Per-workspace booking count table

Features:

  • From / To date pickers — passed as ISO strings to the backend
  • Export CSV button — triggers GET /reports/:tab?format=csv and downloads the file via a temporary object URL (no server-side redirect needed)
  • All queries re-run when the active tab or date range changes (React Query)
  • "Reports" link added to the admin sidebar

Files Changed

Backend

  • backend/src/billing/entities/billing-cycle.entity.ts (new)
  • backend/src/billing/billing.service.ts (new)
  • backend/src/billing/billing.controller.ts (new)
  • backend/src/billing/billing.module.ts (new)
  • backend/src/dunning/dunning.service.ts (new)
  • backend/src/dunning/dunning.module.ts (new)
  • backend/src/promo-codes/dto/apply-promo-code.dto.ts (new)
  • backend/src/promo-codes/promo-codes.service.ts (applyToBooking method)
  • backend/src/promo-codes/promo-codes.controller.ts (POST /apply endpoint)
  • backend/src/promo-codes/promo-codes.module.ts (Booking entity added to TypeORM feature)
  • backend/src/app.module.ts (BillingModule, DunningModule registered)

Frontend

  • frontend/app/admin/reports/page.tsx (new)
  • frontend/components/dashboard/DashboardSidebar.tsx (Reports link in admin nav)

Test Plan

  • Billing cron fires at midnight and creates one cycle per active monthly booking
  • Duplicate cycles are not created (idempotent check on periodStart)
  • GET /billing/cycles returns all; /billing/my-cycles scoped to user
  • Dunning cron retries failed cycles respecting the staggered delay schedule
  • POST /promo-codes/apply with a valid code updates booking total and records usage
  • Reusing a code for the same booking/user returns 400
  • /admin/reports loads all 4 tabs with correct data
  • Date range filters narrow results
  • CSV export downloads a parseable file

…ation, reports frontend (DistinctCodes#1198-DistinctCodes#1201)

- BE-25: BillingCycle entity + BillingService with daily cron to generate monthly cycles for MONTHLY bookings; GET /billing/cycles (admin) and /billing/my-cycles (member)
- BE-26: DunningService with daily 06:00 cron retrying failed billing cycles using staggered delays (1d → 3d → 7d); auto-cancels after 3 exhausted retries
- BE-27: POST /promo-codes/apply endpoint records promo usage against a booking and updates booking.totalAmount, appliedPromoCodeId, promoDiscountApplied atomically
- FE-23: /admin/reports page with 4 tabs (Bookings, Revenue, Members, Occupancy), from/to date range filters, CSV export button, and summary stat cards for revenue/members views
@vercel

vercel Bot commented Jun 27, 2026

Copy link
Copy Markdown

@Tinna23 is attempting to deploy a commit to the naijabuz's projects Team on Vercel.

A member of the Team first needs to authorize it.

@drips-wave

drips-wave Bot commented Jun 27, 2026

Copy link
Copy Markdown

@Tinna23 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@yusuftomilola yusuftomilola merged commit 6d7bd7a into DistinctCodes:main Jun 28, 2026
4 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants