started payouts

This commit is contained in:
jackiettran
2025-08-29 00:32:06 -04:00
parent 0f04182768
commit b52104c3fa
13 changed files with 578 additions and 252 deletions

View 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;

View File

@@ -45,113 +45,76 @@ class StripeService {
}
}
// static async createConnectedAccount({ email, country = "US" }) {
// try {
// const account = await stripe.accounts.create({
// type: "standard",
// email,
// country,
// controller: {
// stripe_dashboard: {
// type: "full",
// },
// },
// capabilities: {
// transfers: { requested: true },
// },
// });
static async createConnectedAccount({ email, country = "US" }) {
try {
const account = await stripe.accounts.create({
type: "standard",
email,
country,
capabilities: {
transfers: { requested: true },
},
});
// return account;
// } catch (error) {
// console.error("Error creating connected account:", error);
// throw error;
// }
// }
return account;
} catch (error) {
console.error("Error creating connected account:", error);
throw error;
}
}
// static async createAccountLink(accountId, refreshUrl, returnUrl) {
// try {
// const accountLink = await stripe.accountLinks.create({
// account: accountId,
// refresh_url: refreshUrl,
// return_url: returnUrl,
// type: "account_onboarding",
// });
static async createAccountLink(accountId, refreshUrl, returnUrl) {
try {
const accountLink = await stripe.accountLinks.create({
account: accountId,
refresh_url: refreshUrl,
return_url: returnUrl,
type: "account_onboarding",
});
// return accountLink;
// } catch (error) {
// console.error("Error creating account link:", error);
// throw error;
// }
// }
return accountLink;
} catch (error) {
console.error("Error creating account link:", error);
throw error;
}
}
// static async getAccountStatus(accountId) {
// try {
// const account = await stripe.accounts.retrieve(accountId);
// return {
// id: account.id,
// details_submitted: account.details_submitted,
// payouts_enabled: account.payouts_enabled,
// capabilities: account.capabilities,
// requirements: account.requirements,
// };
// } catch (error) {
// console.error("Error retrieving account status:", error);
// throw error;
// }
// }
static async getAccountStatus(accountId) {
try {
const account = await stripe.accounts.retrieve(accountId);
return {
id: account.id,
details_submitted: account.details_submitted,
payouts_enabled: account.payouts_enabled,
capabilities: account.capabilities,
requirements: account.requirements,
};
} catch (error) {
console.error("Error retrieving account status:", error);
throw error;
}
}
// static async createPaymentIntent({
// amount,
// currency = "usd",
// connectedAccountId,
// applicationFeeAmount,
// metadata = {},
// }) {
// try {
// const paymentIntent = await stripe.paymentIntents.create({
// amount,
// currency,
// transfer_data: {
// destination: connectedAccountId,
// },
// application_fee_amount: applicationFeeAmount,
// metadata,
// automatic_payment_methods: {
// enabled: true,
// },
// });
static async createTransfer({
amount,
currency = "usd",
destination,
metadata = {},
}) {
try {
const transfer = await stripe.transfers.create({
amount: Math.round(amount * 100), // Convert to cents
currency,
destination,
metadata,
});
// return paymentIntent;
// } catch (error) {
// console.error("Error creating payment intent:", error);
// throw error;
// }
// }
// static async confirmPaymentIntent(paymentIntentId, paymentMethodId) {
// try {
// const paymentIntent = await stripe.paymentIntents.confirm(
// paymentIntentId,
// {
// payment_method: paymentMethodId,
// }
// );
// return paymentIntent;
// } catch (error) {
// console.error("Error confirming payment intent:", error);
// throw error;
// }
// }
// static async retrievePaymentIntent(paymentIntentId) {
// try {
// return await stripe.paymentIntents.retrieve(paymentIntentId);
// } catch (error) {
// console.error("Error retrieving payment intent:", error);
// throw error;
// }
// }
return transfer;
} catch (error) {
console.error("Error creating transfer:", error);
throw error;
}
}
}
module.exports = StripeService;