handling if owner disconnects their stripe account
This commit is contained in:
@@ -38,6 +38,7 @@ jest.mock("../../../utils/logger", () => ({
|
||||
jest.mock("../../../services/email", () => ({
|
||||
payment: {
|
||||
sendPayoutFailedNotification: jest.fn(),
|
||||
sendAccountDisconnectedEmail: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -389,4 +390,201 @@ describe("StripeWebhookService", () => {
|
||||
expect(mockRental.update).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("handleAccountDeauthorized", () => {
|
||||
it("should return missing_account_id when accountId is not provided", async () => {
|
||||
const result = await StripeWebhookService.handleAccountDeauthorized(null);
|
||||
|
||||
expect(result.processed).toBe(false);
|
||||
expect(result.reason).toBe("missing_account_id");
|
||||
});
|
||||
|
||||
it("should return missing_account_id for undefined accountId", async () => {
|
||||
const result = await StripeWebhookService.handleAccountDeauthorized(undefined);
|
||||
|
||||
expect(result.processed).toBe(false);
|
||||
expect(result.reason).toBe("missing_account_id");
|
||||
});
|
||||
|
||||
it("should return user_not_found when account does not match any user", async () => {
|
||||
User.findOne.mockResolvedValue(null);
|
||||
|
||||
const result = await StripeWebhookService.handleAccountDeauthorized("acct_unknown");
|
||||
|
||||
expect(result.processed).toBe(false);
|
||||
expect(result.reason).toBe("user_not_found");
|
||||
expect(User.findOne).toHaveBeenCalledWith({
|
||||
where: { stripeConnectedAccountId: "acct_unknown" },
|
||||
});
|
||||
});
|
||||
|
||||
it("should clear Stripe connection fields when account is deauthorized", async () => {
|
||||
const mockUser = {
|
||||
id: 1,
|
||||
email: "owner@test.com",
|
||||
firstName: "Test",
|
||||
name: "Test Owner",
|
||||
stripeConnectedAccountId: "acct_123",
|
||||
stripePayoutsEnabled: true,
|
||||
update: jest.fn().mockResolvedValue(true),
|
||||
};
|
||||
|
||||
User.findOne.mockResolvedValue(mockUser);
|
||||
Rental.findAll.mockResolvedValue([]);
|
||||
emailServices.payment.sendAccountDisconnectedEmail.mockResolvedValue({
|
||||
success: true,
|
||||
});
|
||||
|
||||
const result = await StripeWebhookService.handleAccountDeauthorized("acct_123");
|
||||
|
||||
expect(result.processed).toBe(true);
|
||||
expect(result.userId).toBe(1);
|
||||
expect(mockUser.update).toHaveBeenCalledWith({
|
||||
stripeConnectedAccountId: null,
|
||||
stripePayoutsEnabled: false,
|
||||
});
|
||||
});
|
||||
|
||||
it("should log warning when there are pending payouts", async () => {
|
||||
const mockUser = {
|
||||
id: 1,
|
||||
email: "owner@test.com",
|
||||
firstName: "Test",
|
||||
update: jest.fn().mockResolvedValue(true),
|
||||
};
|
||||
const mockRentals = [{ id: 100 }, { id: 101 }];
|
||||
|
||||
User.findOne.mockResolvedValue(mockUser);
|
||||
Rental.findAll.mockResolvedValue(mockRentals);
|
||||
emailServices.payment.sendAccountDisconnectedEmail.mockResolvedValue({
|
||||
success: true,
|
||||
});
|
||||
|
||||
const result = await StripeWebhookService.handleAccountDeauthorized("acct_123");
|
||||
|
||||
expect(result.pendingPayoutsCount).toBe(2);
|
||||
expect(Rental.findAll).toHaveBeenCalledWith({
|
||||
where: {
|
||||
ownerId: 1,
|
||||
payoutStatus: "pending",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("should send notification email to owner", async () => {
|
||||
const mockUser = {
|
||||
id: 1,
|
||||
email: "owner@test.com",
|
||||
firstName: "Test",
|
||||
update: jest.fn().mockResolvedValue(true),
|
||||
};
|
||||
|
||||
User.findOne.mockResolvedValue(mockUser);
|
||||
Rental.findAll.mockResolvedValue([]);
|
||||
emailServices.payment.sendAccountDisconnectedEmail.mockResolvedValue({
|
||||
success: true,
|
||||
});
|
||||
|
||||
const result = await StripeWebhookService.handleAccountDeauthorized("acct_123");
|
||||
|
||||
expect(result.notificationSent).toBe(true);
|
||||
expect(emailServices.payment.sendAccountDisconnectedEmail).toHaveBeenCalledWith(
|
||||
"owner@test.com",
|
||||
{
|
||||
ownerName: "Test",
|
||||
hasPendingPayouts: false,
|
||||
pendingPayoutCount: 0,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it("should send notification with pending payout info when there are pending payouts", async () => {
|
||||
const mockUser = {
|
||||
id: 1,
|
||||
email: "owner@test.com",
|
||||
firstName: "Owner",
|
||||
update: jest.fn().mockResolvedValue(true),
|
||||
};
|
||||
const mockRentals = [{ id: 100 }, { id: 101 }, { id: 102 }];
|
||||
|
||||
User.findOne.mockResolvedValue(mockUser);
|
||||
Rental.findAll.mockResolvedValue(mockRentals);
|
||||
emailServices.payment.sendAccountDisconnectedEmail.mockResolvedValue({
|
||||
success: true,
|
||||
});
|
||||
|
||||
await StripeWebhookService.handleAccountDeauthorized("acct_123");
|
||||
|
||||
expect(emailServices.payment.sendAccountDisconnectedEmail).toHaveBeenCalledWith(
|
||||
"owner@test.com",
|
||||
{
|
||||
ownerName: "Owner",
|
||||
hasPendingPayouts: true,
|
||||
pendingPayoutCount: 3,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it("should use name fallback when firstName is not available", async () => {
|
||||
const mockUser = {
|
||||
id: 1,
|
||||
email: "owner@test.com",
|
||||
firstName: null,
|
||||
name: "Full Name",
|
||||
update: jest.fn().mockResolvedValue(true),
|
||||
};
|
||||
|
||||
User.findOne.mockResolvedValue(mockUser);
|
||||
Rental.findAll.mockResolvedValue([]);
|
||||
emailServices.payment.sendAccountDisconnectedEmail.mockResolvedValue({
|
||||
success: true,
|
||||
});
|
||||
|
||||
await StripeWebhookService.handleAccountDeauthorized("acct_123");
|
||||
|
||||
expect(emailServices.payment.sendAccountDisconnectedEmail).toHaveBeenCalledWith(
|
||||
"owner@test.com",
|
||||
expect.objectContaining({
|
||||
ownerName: "Full Name",
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("should handle email sending failure gracefully", async () => {
|
||||
const mockUser = {
|
||||
id: 1,
|
||||
email: "owner@test.com",
|
||||
firstName: "Test",
|
||||
update: jest.fn().mockResolvedValue(true),
|
||||
};
|
||||
|
||||
User.findOne.mockResolvedValue(mockUser);
|
||||
Rental.findAll.mockResolvedValue([]);
|
||||
emailServices.payment.sendAccountDisconnectedEmail.mockRejectedValue(
|
||||
new Error("Email service down")
|
||||
);
|
||||
|
||||
const result = await StripeWebhookService.handleAccountDeauthorized("acct_123");
|
||||
|
||||
// Should still mark as processed even if notification fails
|
||||
expect(result.processed).toBe(true);
|
||||
expect(result.notificationSent).toBe(false);
|
||||
expect(mockUser.update).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should throw error when database update fails", async () => {
|
||||
const mockUser = {
|
||||
id: 1,
|
||||
email: "owner@test.com",
|
||||
firstName: "Test",
|
||||
update: jest.fn().mockRejectedValue(new Error("DB error")),
|
||||
};
|
||||
|
||||
User.findOne.mockResolvedValue(mockUser);
|
||||
|
||||
await expect(
|
||||
StripeWebhookService.handleAccountDeauthorized("acct_123")
|
||||
).rejects.toThrow("DB error");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user