handling changes to stripe account where owner needs to provide information

This commit is contained in:
jackiettran
2026-01-08 19:08:14 -05:00
parent 0ea35e9d6f
commit e2e32f7632
8 changed files with 586 additions and 16 deletions

View File

@@ -213,6 +213,46 @@ class PaymentEmailService {
}
}
/**
* Send notification when owner's payouts are disabled due to requirements
* @param {string} ownerEmail - Owner's email address
* @param {Object} params - Email parameters
* @param {string} params.ownerName - Owner's name
* @param {string} params.disabledReason - Human-readable reason for disabling
* @returns {Promise<{success: boolean, messageId?: string, error?: string}>}
*/
async sendPayoutsDisabledEmail(ownerEmail, params) {
if (!this.initialized) {
await this.initialize();
}
try {
const { ownerName, disabledReason } = params;
const variables = {
ownerName: ownerName || "there",
disabledReason:
disabledReason ||
"Additional verification is required for your account.",
earningsUrl: `${process.env.FRONTEND_URL}/earnings`,
};
const htmlContent = await this.templateManager.renderTemplate(
"payoutsDisabledToOwner",
variables
);
return await this.emailClient.sendEmail(
ownerEmail,
"Action Required: Your payouts have been paused - Village Share",
htmlContent
);
} catch (error) {
console.error("Failed to send payouts disabled email:", error);
return { success: false, error: error.message };
}
}
/**
* Send dispute alert to platform admin
* Called when a new dispute is opened

View File

@@ -16,19 +16,23 @@ class StripeWebhookService {
/**
* Handle account.updated webhook event.
* Triggers payouts for owner when payouts_enabled becomes true.
* Tracks requirements, triggers payouts when enabled, and notifies when disabled.
* @param {Object} account - The Stripe account object from the webhook
* @returns {Object} - { processed, payoutsTriggered, payoutResults }
* @returns {Object} - { processed, payoutsTriggered, payoutResults, notificationSent }
*/
static async handleAccountUpdated(account) {
const accountId = account.id;
const payoutsEnabled = account.payouts_enabled;
const requirements = account.requirements || {};
logger.info("Processing account.updated webhook", {
accountId,
payoutsEnabled,
chargesEnabled: account.charges_enabled,
detailsSubmitted: account.details_submitted,
currentlyDue: requirements.currently_due?.length || 0,
pastDue: requirements.past_due?.length || 0,
disabledReason: requirements.disabled_reason,
});
// Find user with this Stripe account
@@ -41,18 +45,33 @@ class StripeWebhookService {
return { processed: false, reason: "user_not_found" };
}
// Store previous state before update
const previousPayoutsEnabled = user.stripePayoutsEnabled;
// Update user's payouts_enabled status
await user.update({ stripePayoutsEnabled: payoutsEnabled });
// Update user with all account status fields
await user.update({
stripePayoutsEnabled: payoutsEnabled,
stripeRequirementsCurrentlyDue: requirements.currently_due || [],
stripeRequirementsPastDue: requirements.past_due || [],
stripeDisabledReason: requirements.disabled_reason || null,
stripeRequirementsLastUpdated: new Date(),
});
logger.info("Updated user stripePayoutsEnabled", {
logger.info("Updated user Stripe account status", {
userId: user.id,
accountId,
previousPayoutsEnabled,
newPayoutsEnabled: payoutsEnabled,
currentlyDue: requirements.currently_due?.length || 0,
pastDue: requirements.past_due?.length || 0,
});
const result = {
processed: true,
payoutsTriggered: false,
notificationSent: false,
};
// If payouts just became enabled (false -> true), process pending payouts
if (payoutsEnabled && !previousPayoutsEnabled) {
logger.info("Payouts enabled for user, processing pending payouts", {
@@ -60,15 +79,69 @@ class StripeWebhookService {
accountId,
});
const result = await this.processPayoutsForOwner(user.id);
return {
processed: true,
payoutsTriggered: true,
payoutResults: result,
};
result.payoutsTriggered = true;
result.payoutResults = await this.processPayoutsForOwner(user.id);
}
return { processed: true, payoutsTriggered: false };
// If payouts just became disabled (true -> false), notify the owner
if (!payoutsEnabled && previousPayoutsEnabled) {
logger.warn("Payouts disabled for user", {
userId: user.id,
accountId,
disabledReason: requirements.disabled_reason,
currentlyDue: requirements.currently_due,
});
try {
const disabledReason = this.formatDisabledReason(requirements.disabled_reason);
await emailServices.payment.sendPayoutsDisabledEmail(user.email, {
ownerName: user.firstName || user.name,
disabledReason,
});
result.notificationSent = true;
logger.info("Sent payouts disabled notification to owner", {
userId: user.id,
accountId,
disabledReason: requirements.disabled_reason,
});
} catch (emailError) {
logger.error("Failed to send payouts disabled notification", {
userId: user.id,
accountId,
error: emailError.message,
});
}
}
return result;
}
/**
* Format Stripe disabled_reason code to user-friendly message.
* @param {string} reason - Stripe disabled_reason code
* @returns {string} User-friendly message
*/
static formatDisabledReason(reason) {
const reasonMap = {
"requirements.past_due":
"Some required information is past due and must be provided to continue receiving payouts.",
"requirements.pending_verification":
"Your submitted information is being verified. This usually takes a few minutes.",
listed: "Your account has been listed for review due to potential policy concerns.",
platform_paused:
"Payouts have been temporarily paused by the platform.",
rejected_fraud: "Your account was flagged for potential fraudulent activity.",
rejected_listed: "Your account has been rejected due to policy concerns.",
rejected_terms_of_service:
"Your account was rejected due to a terms of service violation.",
rejected_other: "Your account was rejected. Please contact support for more information.",
under_review: "Your account is under review. We'll notify you when the review is complete.",
};
return reasonMap[reason] || "Additional verification is required for your account.";
}
/**
@@ -444,6 +517,10 @@ class StripeWebhookService {
await user.update({
stripeConnectedAccountId: null,
stripePayoutsEnabled: false,
stripeRequirementsCurrentlyDue: [],
stripeRequirementsPastDue: [],
stripeDisabledReason: null,
stripeRequirementsLastUpdated: null,
});
logger.info("Cleared Stripe connection for deauthorized account", {