Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions backend/routes/subscriptionRoutes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// subscriptionRoutes.js
// API endpoints for Dynamic Subscription Management Dashboard

const express = require('express');
const router = express.Router();
const SubscriptionManagementService = require('./SubscriptionManagementService');

// Middleware to get userId (replace with real auth)
router.use((req, res, next) => {
req.userId = req.headers['x-user-id'] || 'demoUser';
next();
});

// GET /subscriptions/dashboard
router.get('/dashboard', async (req, res) => {
try {
const service = new SubscriptionManagementService(req.userId);
const dashboardData = await service.getDashboardData();
res.json({ subscriptions: dashboardData });
} catch (err) {
res.status(500).json({ error: err.message });
}
});

// POST /subscriptions/cancel
router.post('/cancel', async (req, res) => {
// Example endpoint to cancel a subscription
const { merchant, amount } = req.body;
// Implement cancellation logic here
res.json({ status: 'cancelled', merchant, amount });
});

// POST /subscriptions/negotiate
router.post('/negotiate', async (req, res) => {
// Example endpoint to negotiate a subscription
const { merchant, amount } = req.body;
// Implement negotiation logic here
res.json({ status: 'negotiation_started', merchant, amount });
});

module.exports = router;
86 changes: 86 additions & 0 deletions backend/services/SubscriptionManagementService.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// SubscriptionManagementService.js
// Dynamic Subscription Management Dashboard Backend
// Detects recurring charges, categorizes subscriptions, and provides actionable insights

const db = require('../db'); // Example DB import
const moment = require('moment');

class SubscriptionManagementService {
constructor(userId) {
this.userId = userId;
}

// Fetch user transactions from DB
async getUserTransactions() {
// Replace with actual DB query
return db.getTransactionsForUser(this.userId);
}

// Detect recurring charges
async detectRecurringCharges() {
const transactions = await this.getUserTransactions();
const recurring = {};
// Group by merchant and amount
transactions.forEach(tx => {
const key = `${tx.merchant}_${tx.amount}`;
if (!recurring[key]) recurring[key] = [];
recurring[key].push(tx);
});
// Filter for monthly/weekly patterns
const subscriptions = [];
Object.values(recurring).forEach(group => {
if (group.length < 3) return; // Require at least 3 occurrences
const dates = group.map(tx => moment(tx.date));
const intervals = dates.slice(1).map((d, i) => d.diff(dates[i], 'days'));
const avgInterval = intervals.reduce((a, b) => a + b, 0) / intervals.length;
if (avgInterval > 20 && avgInterval < 35) {
subscriptions.push({
merchant: group[0].merchant,
amount: group[0].amount,
frequency: 'monthly',
lastDate: group[group.length - 1].date,
});
}
});
return subscriptions;
}

// Categorize subscriptions
async categorizeSubscriptions(subscriptions) {
// Simple categorization by merchant keywords
const categories = {
streaming: ['netflix', 'prime', 'spotify', 'hulu'],
utilities: ['electric', 'water', 'gas'],
saas: ['zoom', 'office', 'adobe'],
};
return subscriptions.map(sub => {
let category = 'other';
Object.entries(categories).forEach(([cat, keywords]) => {
if (keywords.some(k => sub.merchant.toLowerCase().includes(k))) {
category = cat;
}
});
return { ...sub, category };
});
}

// Actionable insights
async generateInsights(subscriptions) {
return subscriptions.map(sub => {
let action = 'review';
if (sub.category === 'streaming' && sub.amount > 20) action = 'negotiate';
if (sub.category === 'other') action = 'cancel';
return { ...sub, action };
});
}

// Main dashboard data
async getDashboardData() {
const recurring = await this.detectRecurringCharges();
const categorized = await this.categorizeSubscriptions(recurring);
const insights = await this.generateInsights(categorized);
return insights;
}
}

module.exports = SubscriptionManagementService;
95 changes: 95 additions & 0 deletions backend/services/billPaymentScheduler/billService.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// BillService: CRUD, scheduling logic

const { Bill } = require('./models');

// In-memory bill storage
const bills = [];

class BillService {
static createBill(billData) {
const bill = new Bill(
bills.length + 1,
billData.userId,
billData.name,
billData.amount,
billData.dueDate,
billData.recurring,
billData.frequency,
'pending',
billData.paymentMethodId
);
bills.push(bill);
return bill;
}

static getBillsByUser(userId) {
return bills.filter(b => b.userId === userId);
}

static getBillById(billId) {
return bills.find(b => b.id === billId);
}

static updateBill(billId, updateData) {
const bill = this.getBillById(billId);
if (!bill) return null;
Object.assign(bill, updateData);
return bill;
}

static deleteBill(billId) {
const idx = bills.findIndex(b => b.id === billId);
if (idx !== -1) {
bills.splice(idx, 1);
return true;
}
return false;
}

static getUpcomingBills(userId, daysAhead = 7) {
const now = new Date();
const future = new Date(now.getTime() + daysAhead * 24 * 60 * 60 * 1000);
return bills.filter(b => b.userId === userId && new Date(b.dueDate) <= future && b.status === 'pending');
}

static markBillPaid(billId) {
const bill = this.getBillById(billId);
if (bill) {
bill.status = 'paid';
return bill;
}
return null;
}

static scheduleRecurringBills() {
bills.forEach(bill => {
if (bill.recurring && bill.status === 'paid') {
// Schedule next bill
let nextDue;
const currentDue = new Date(bill.dueDate);
switch (bill.frequency) {
case 'monthly':
nextDue = new Date(currentDue);
nextDue.setMonth(nextDue.getMonth() + 1);
break;
case 'weekly':
nextDue = new Date(currentDue);
nextDue.setDate(nextDue.getDate() + 7);
break;
case 'yearly':
nextDue = new Date(currentDue);
nextDue.setFullYear(nextDue.getFullYear() + 1);
break;
default:
nextDue = null;
}
if (nextDue) {
bill.dueDate = nextDue.toISOString();
bill.status = 'pending';
}
}
});
}
}

module.exports = BillService;
43 changes: 43 additions & 0 deletions backend/services/billPaymentScheduler/controllers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Controllers: API endpoints for bills, payments, schedules

const BillService = require('./billService');
const PaymentService = require('./paymentService');
const SchedulerService = require('./schedulerService');

const controllers = {
createBill: (req, res) => {
const bill = BillService.createBill(req.body);
res.status(201).json(bill);
},
getBills: (req, res) => {
const bills = BillService.getBillsByUser(req.params.userId);
res.json(bills);
},
updateBill: (req, res) => {
const bill = BillService.updateBill(parseInt(req.params.billId), req.body);
if (bill) res.json(bill);
else res.status(404).json({ error: 'Bill not found' });
},
deleteBill: (req, res) => {
const success = BillService.deleteBill(parseInt(req.params.billId));
if (success) res.json({ success: true });
else res.status(404).json({ error: 'Bill not found' });
},
processPayment: (req, res) => {
const payment = PaymentService.processPayment(req.body);
if (payment.status === 'success') {
BillService.markBillPaid(payment.billId);
}
res.status(201).json(payment);
},
getPayments: (req, res) => {
const payments = PaymentService.getPaymentsByUser(req.params.userId);
res.json(payments);
},
runScheduler: (req, res) => {
SchedulerService.runScheduler();
res.json({ success: true });
}
};

module.exports = controllers;
59 changes: 59 additions & 0 deletions backend/services/billPaymentScheduler/models.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Bill, User, Payment, Schedule models
// ...existing code...
// User Model
class User {
constructor(id, name, email, phone, paymentMethods = []) {
this.id = id;
this.name = name;
this.email = email;
this.phone = phone;
this.paymentMethods = paymentMethods;
}
}

// Bill Model
class Bill {
constructor(id, userId, name, amount, dueDate, recurring, frequency, status = 'pending', paymentMethodId = null) {
this.id = id;
this.userId = userId;
this.name = name;
this.amount = amount;
this.dueDate = dueDate;
this.recurring = recurring;
this.frequency = frequency; // e.g. 'monthly', 'weekly', 'yearly'
this.status = status;
this.paymentMethodId = paymentMethodId;
}
}

// Payment Model
class Payment {
constructor(id, billId, userId, amount, date, status, gatewayResponse = null) {
this.id = id;
this.billId = billId;
this.userId = userId;
this.amount = amount;
this.date = date;
this.status = status; // 'success', 'failed', 'pending'
this.gatewayResponse = gatewayResponse;
}
}

// Schedule Model
class Schedule {
constructor(id, billId, userId, nextRun, frequency, enabled = true) {
this.id = id;
this.billId = billId;
this.userId = userId;
this.nextRun = nextRun;
this.frequency = frequency;
this.enabled = enabled;
}
}

module.exports = {
User,
Bill,
Payment,
Schedule
};
40 changes: 40 additions & 0 deletions backend/services/billPaymentScheduler/paymentService.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// PaymentService: payment gateway integration

const { Payment } = require('./models');
const payments = [];

class PaymentService {
static processPayment(paymentData) {
// Simulate payment gateway integration
const gatewayResponse = {
transactionId: 'TXN' + Math.floor(Math.random() * 1000000),
status: 'success',
timestamp: new Date().toISOString()
};
const payment = new Payment(
payments.length + 1,
paymentData.billId,
paymentData.userId,
paymentData.amount,
new Date().toISOString(),
gatewayResponse.status,
gatewayResponse
);
payments.push(payment);
return payment;
}

static getPaymentsByUser(userId) {
return payments.filter(p => p.userId === userId);
}

static getPaymentsByBill(billId) {
return payments.filter(p => p.billId === billId);
}

static getPaymentById(paymentId) {
return payments.find(p => p.id === paymentId);
}
}

module.exports = PaymentService;
23 changes: 23 additions & 0 deletions backend/services/billPaymentScheduler/reminderService.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// ReminderService: notifications for upcoming bills

const BillService = require('./billService');
const { User } = require('./models');

class ReminderService {
static sendReminder(user, bill) {
// Simulate sending email/SMS
console.log(`Reminder sent to ${user.email} for bill '${bill.name}' due on ${bill.dueDate}`);
return true;
}

static sendUpcomingReminders(users, daysAhead = 7) {
users.forEach(user => {
const upcomingBills = BillService.getUpcomingBills(user.id, daysAhead);
upcomingBills.forEach(bill => {
this.sendReminder(user, bill);
});
});
}
}

module.exports = ReminderService;
Loading
Loading