payout retry lambda

This commit is contained in:
jackiettran
2026-01-14 18:05:41 -05:00
parent da82872297
commit 7f2f45b1c2
13 changed files with 1439 additions and 9 deletions

View File

@@ -0,0 +1,152 @@
const { db } = require("../shared");
/**
* Get all failed payouts with eligible owners (Stripe enabled).
* Matches the query from backend PayoutService.retryFailedPayouts()
* @returns {Promise<Array>} Array of failed payouts with owner and item details
*/
async function getFailedPayoutsWithOwners() {
const result = await db.query(
`SELECT
r.id,
r."itemId",
r."renterId",
r."ownerId",
r."startDateTime",
r."endDateTime",
r."totalAmount",
r."payoutAmount",
r."platformFee",
r."paymentStatus",
r."payoutStatus",
r.status,
r."stripeTransferId",
r."createdAt",
r."updatedAt",
-- Owner fields
owner.id AS "owner_id",
owner.email AS "owner_email",
owner."firstName" AS "owner_firstName",
owner."lastName" AS "owner_lastName",
owner."stripeConnectedAccountId" AS "owner_stripeConnectedAccountId",
owner."stripePayoutsEnabled" AS "owner_stripePayoutsEnabled",
-- Item fields
item.id AS "item_id",
item.name AS "item_name"
FROM "Rentals" r
INNER JOIN "Users" owner ON r."ownerId" = owner.id
INNER JOIN "Items" item ON r."itemId" = item.id
WHERE r.status = 'completed'
AND r."paymentStatus" = 'paid'
AND r."payoutStatus" = 'failed'
AND owner."stripeConnectedAccountId" IS NOT NULL
AND owner."stripePayoutsEnabled" = true`
);
// Transform flat results into nested structure
return result.rows.map((row) => ({
id: row.id,
itemId: row.itemId,
renterId: row.renterId,
ownerId: row.ownerId,
startDateTime: row.startDateTime,
endDateTime: row.endDateTime,
totalAmount: parseFloat(row.totalAmount),
payoutAmount: parseFloat(row.payoutAmount),
platformFee: parseFloat(row.platformFee),
paymentStatus: row.paymentStatus,
payoutStatus: row.payoutStatus,
status: row.status,
stripeTransferId: row.stripeTransferId,
createdAt: row.createdAt,
updatedAt: row.updatedAt,
owner: {
id: row.owner_id,
email: row.owner_email,
firstName: row.owner_firstName,
lastName: row.owner_lastName,
stripeConnectedAccountId: row.owner_stripeConnectedAccountId,
stripePayoutsEnabled: row.owner_stripePayoutsEnabled,
},
item: {
id: row.item_id,
name: row.item_name,
},
}));
}
/**
* Update rental payout status to pending (before retry attempt).
* @param {string} rentalId - UUID of the rental
* @returns {Promise<void>}
*/
async function resetPayoutToPending(rentalId) {
await db.query(
`UPDATE "Rentals"
SET "payoutStatus" = 'pending',
"updatedAt" = NOW()
WHERE id = $1`,
[rentalId]
);
}
/**
* Update rental with successful payout information.
* @param {string} rentalId - UUID of the rental
* @param {string} stripeTransferId - Stripe transfer ID
* @returns {Promise<void>}
*/
async function updatePayoutSuccess(rentalId, stripeTransferId) {
await db.query(
`UPDATE "Rentals"
SET "payoutStatus" = 'completed',
"payoutProcessedAt" = NOW(),
"stripeTransferId" = $2,
"updatedAt" = NOW()
WHERE id = $1`,
[rentalId, stripeTransferId]
);
}
/**
* Update rental payout status to failed.
* @param {string} rentalId - UUID of the rental
* @returns {Promise<void>}
*/
async function updatePayoutFailed(rentalId) {
await db.query(
`UPDATE "Rentals"
SET "payoutStatus" = 'failed',
"updatedAt" = NOW()
WHERE id = $1`,
[rentalId]
);
}
/**
* Clear Stripe connection for a disconnected account.
* Called when we detect an account is no longer connected.
* @param {string} stripeConnectedAccountId - The disconnected account ID
* @returns {Promise<Object|null>} The affected user or null
*/
async function clearDisconnectedAccount(stripeConnectedAccountId) {
const result = await db.query(
`UPDATE "Users"
SET "stripeConnectedAccountId" = NULL,
"stripePayoutsEnabled" = false,
"updatedAt" = NOW()
WHERE "stripeConnectedAccountId" = $1
RETURNING id, email, "firstName", "lastName"`,
[stripeConnectedAccountId]
);
return result.rows[0] || null;
}
module.exports = {
getFailedPayoutsWithOwners,
resetPayoutToPending,
updatePayoutSuccess,
updatePayoutFailed,
clearDisconnectedAccount,
};