Kuma/tests/products/pass-webhook.test.ts
2026-10-02 14:30:54 +02:00

127 lines
4.6 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from "vitest";
vi.mock("@/src/lib/prisma", () => ({
prisma: {
customerPass: { findUnique: vi.fn(), create: vi.fn() },
product: { findUnique: vi.fn() },
user: { findUnique: vi.fn() },
},
}));
vi.mock("@/src/lib/stripe", () => ({
stripe: { paymentIntents: { retrieve: vi.fn() } },
onAccount: (id: string | null) => (id ? { stripeAccount: id } : undefined),
}));
vi.mock("@/src/lib/stripe-fee", () => ({ recordStripeFee: vi.fn() }));
vi.mock("@/src/lib/stripe-refund", () => ({ refundStripePayment: vi.fn() }));
vi.mock("@/src/lib/mail/resend", () => ({ sendMail: vi.fn() }));
vi.mock("@/src/modules/notifications/lib/create-notification", () => ({
notifyBusinessManager: vi.fn(),
}));
import {
handlePassCheckoutCompleted,
isPassCheckout,
} from "@/src/modules/products/lib/pass-webhook";
import { prisma } from "@/src/lib/prisma";
import { stripe } from "@/src/lib/stripe";
import { recordStripeFee } from "@/src/lib/stripe-fee";
import { refundStripePayment } from "@/src/lib/stripe-refund";
import { sendMail } from "@/src/lib/mail/resend";
import { notifyBusinessManager } from "@/src/modules/notifications/lib/create-notification";
const session = {
id: "cs_1",
payment_status: "paid",
payment_intent: "pi_1",
amount_total: 12500,
customer_details: null,
metadata: { kind: "pass", productId: "prod-1", userId: "user-1" },
};
beforeEach(() => {
vi.clearAllMocks();
vi.mocked(prisma.customerPass.findUnique).mockResolvedValue(null);
vi.mocked(prisma.customerPass.create).mockResolvedValue({ id: "pass-1" } as never);
vi.mocked(prisma.product.findUnique).mockResolvedValue({
id: "prod-1",
name: "10er-Karte",
credits: 10,
validityMonths: 6,
sports: ["pilates"],
businessId: "biz-1",
business: { name: "Studio", branding: null },
} as never);
vi.mocked(prisma.user.findUnique).mockResolvedValue({
id: "user-1",
email: "Kunde@Example.com",
name: "Anna",
} as never);
vi.mocked(stripe.paymentIntents.retrieve).mockResolvedValue({
application_fee_amount: 500,
} as never);
vi.mocked(recordStripeFee).mockResolvedValue(undefined);
vi.mocked(sendMail).mockResolvedValue(undefined as never);
vi.mocked(notifyBusinessManager).mockResolvedValue(undefined as never);
});
describe("isPassCheckout", () => {
it("erkennt Kartenkäufe an den Metadaten", () => {
expect(isPassCheckout(session as never)).toBe(true);
expect(isPassCheckout({ metadata: { courseId: "c1" } } as never)).toBe(false);
});
});
describe("handlePassCheckoutCompleted", () => {
it("legt die Karte mit Momentaufnahme des Produkts und Ablaufdatum an", async () => {
await handlePassCheckoutCompleted(session as never, "acct_studio");
const { data } = vi.mocked(prisma.customerPass.create).mock.calls[0][0];
expect(data).toMatchObject({
productId: "prod-1",
businessId: "biz-1",
userId: "user-1",
email: "kunde@example.com",
name: "10er-Karte",
sports: ["pilates"],
creditsTotal: 10,
creditsRemaining: 10,
source: "purchase",
amountPaid: 12500,
platformFee: 500,
priceCents: 12000,
stripeAccountId: "acct_studio",
stripeSessionId: "cs_1",
});
expect(data.validUntil).toBeInstanceOf(Date);
expect(recordStripeFee).toHaveBeenCalledWith("pi_1", "acct_studio");
expect(sendMail).toHaveBeenCalled();
expect(vi.mocked(notifyBusinessManager).mock.calls[0][1].type).toBe("pass.sold");
});
it("legt bei erneuter Zustellung derselben Session keine zweite Karte an", async () => {
vi.mocked(prisma.customerPass.findUnique).mockResolvedValue({ id: "pass-1" } as never);
await handlePassCheckoutCompleted(session as never, "acct_studio");
expect(prisma.customerPass.create).not.toHaveBeenCalled();
});
it("wartet bei SEPA, bis die Zahlung angekommen ist", async () => {
await handlePassCheckoutCompleted({ ...session, payment_status: "unpaid" } as never, "acct");
expect(prisma.customerPass.create).not.toHaveBeenCalled();
});
it("erstattet, wenn das Produkt inzwischen gelöscht wurde", async () => {
vi.mocked(prisma.product.findUnique).mockResolvedValue(null);
vi.mocked(refundStripePayment).mockResolvedValue({
success: true,
stripeRefundId: "re_1",
amountRefundedCents: 12500,
});
await handlePassCheckoutCompleted(session as never, "acct_studio");
expect(prisma.customerPass.create).not.toHaveBeenCalled();
expect(refundStripePayment).toHaveBeenCalledWith(
expect.objectContaining({ paymentIntentId: "pi_1", refundApplicationFee: true })
);
});
});