started payouts
This commit is contained in:
194
backend/services/payoutService.js
Normal file
194
backend/services/payoutService.js
Normal file
@@ -0,0 +1,194 @@
|
||||
const { Rental, User } = require("../models");
|
||||
const StripeService = require("./stripeService");
|
||||
const { Op } = require("sequelize");
|
||||
|
||||
class PayoutService {
|
||||
static async getEligiblePayouts() {
|
||||
try {
|
||||
const eligibleRentals = await Rental.findAll({
|
||||
where: {
|
||||
status: "completed",
|
||||
paymentStatus: "paid",
|
||||
payoutStatus: "pending",
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: User,
|
||||
as: "owner",
|
||||
where: {
|
||||
stripeConnectedAccountId: {
|
||||
[Op.not]: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
return eligibleRentals;
|
||||
} catch (error) {
|
||||
console.error("Error getting eligible payouts:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
static async processRentalPayout(rental) {
|
||||
try {
|
||||
if (!rental.owner || !rental.owner.stripeConnectedAccountId) {
|
||||
throw new Error("Owner does not have a connected Stripe account");
|
||||
}
|
||||
|
||||
if (rental.payoutStatus !== "pending") {
|
||||
throw new Error("Rental payout has already been processed");
|
||||
}
|
||||
|
||||
if (!rental.payoutAmount || rental.payoutAmount <= 0) {
|
||||
throw new Error("Invalid payout amount");
|
||||
}
|
||||
|
||||
// Update status to processing
|
||||
await rental.update({
|
||||
payoutStatus: "processing",
|
||||
});
|
||||
|
||||
// Create Stripe transfer
|
||||
const transfer = await StripeService.createTransfer({
|
||||
amount: rental.payoutAmount,
|
||||
destination: rental.owner.stripeConnectedAccountId,
|
||||
metadata: {
|
||||
rentalId: rental.id,
|
||||
ownerId: rental.ownerId,
|
||||
baseAmount: rental.baseRentalAmount.toString(),
|
||||
platformFee: rental.platformFee.toString(),
|
||||
},
|
||||
});
|
||||
|
||||
// Update rental with successful payout
|
||||
await rental.update({
|
||||
payoutStatus: "completed",
|
||||
payoutProcessedAt: new Date(),
|
||||
stripeTransferId: transfer.id,
|
||||
});
|
||||
|
||||
console.log(
|
||||
`Payout completed for rental ${rental.id}: $${rental.payoutAmount} to ${rental.owner.stripeConnectedAccountId}`
|
||||
);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
transferId: transfer.id,
|
||||
amount: rental.payoutAmount,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(`Error processing payout for rental ${rental.id}:`, error);
|
||||
|
||||
// Update status to failed
|
||||
await rental.update({
|
||||
payoutStatus: "failed",
|
||||
});
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
static async processAllEligiblePayouts() {
|
||||
try {
|
||||
const eligibleRentals = await this.getEligiblePayouts();
|
||||
|
||||
console.log(
|
||||
`Found ${eligibleRentals.length} eligible rentals for payout`
|
||||
);
|
||||
|
||||
const results = {
|
||||
successful: [],
|
||||
failed: [],
|
||||
totalProcessed: eligibleRentals.length,
|
||||
};
|
||||
|
||||
for (const rental of eligibleRentals) {
|
||||
try {
|
||||
const result = await this.processRentalPayout(rental);
|
||||
results.successful.push({
|
||||
rentalId: rental.id,
|
||||
amount: result.amount,
|
||||
transferId: result.transferId,
|
||||
});
|
||||
} catch (error) {
|
||||
results.failed.push({
|
||||
rentalId: rental.id,
|
||||
error: error.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
console.log(
|
||||
`Payout processing complete: ${results.successful.length} successful, ${results.failed.length} failed`
|
||||
);
|
||||
|
||||
return results;
|
||||
} catch (error) {
|
||||
console.error("Error processing all eligible payouts:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
static async retryFailedPayouts() {
|
||||
try {
|
||||
const failedRentals = await Rental.findAll({
|
||||
where: {
|
||||
status: "completed",
|
||||
paymentStatus: "paid",
|
||||
payoutStatus: "failed",
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: User,
|
||||
as: "owner",
|
||||
where: {
|
||||
stripeConnectedAccountId: {
|
||||
[Op.not]: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
console.log(`Found ${failedRentals.length} failed payouts to retry`);
|
||||
|
||||
const results = {
|
||||
successful: [],
|
||||
failed: [],
|
||||
totalProcessed: failedRentals.length,
|
||||
};
|
||||
|
||||
for (const rental of failedRentals) {
|
||||
try {
|
||||
// Reset to pending before retrying
|
||||
await rental.update({ payoutStatus: "pending" });
|
||||
|
||||
const result = await this.processRentalPayout(rental);
|
||||
results.successful.push({
|
||||
rentalId: rental.id,
|
||||
amount: result.amount,
|
||||
transferId: result.transferId,
|
||||
});
|
||||
} catch (error) {
|
||||
results.failed.push({
|
||||
rentalId: rental.id,
|
||||
error: error.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
console.log(
|
||||
`Retry processing complete: ${results.successful.length} successful, ${results.failed.length} failed`
|
||||
);
|
||||
|
||||
return results;
|
||||
} catch (error) {
|
||||
console.error("Error retrying failed payouts:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PayoutService;
|
||||
Reference in New Issue
Block a user