mirror of
https://github.com/Since-3/Kuma.git
synced 2026-10-12 20:41:16 +00:00
147 lines
5.1 KiB
TypeScript
147 lines
5.1 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
|
|
vi.mock("@/src/lib/stripe", () => ({
|
|
stripe: {
|
|
paymentIntents: { retrieve: vi.fn() },
|
|
balanceTransactions: { retrieve: vi.fn() },
|
|
},
|
|
}));
|
|
|
|
vi.mock("@/src/lib/prisma", () => ({
|
|
prisma: {
|
|
courseBooking: { updateMany: vi.fn(), count: vi.fn() },
|
|
customerPass: { updateMany: vi.fn(), count: vi.fn() },
|
|
},
|
|
}));
|
|
|
|
import {
|
|
recordStripeFee,
|
|
recordStripeFeeForCharge,
|
|
stripeProcessingFeeCents,
|
|
} from "@/src/lib/stripe-fee";
|
|
import { stripe } from "@/src/lib/stripe";
|
|
import { prisma } from "@/src/lib/prisma";
|
|
|
|
// Echte Form vom Test-Account: fee=171 enthält BEIDE Abzüge.
|
|
const balanceTransaction = {
|
|
fee: 171,
|
|
fee_details: [
|
|
{ type: "stripe_fee", amount: 121 },
|
|
{ type: "application_fee", amount: 50 },
|
|
],
|
|
};
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
vi.mocked(prisma.courseBooking.updateMany).mockResolvedValue({ count: 1 } as never);
|
|
vi.mocked(prisma.customerPass.updateMany).mockResolvedValue({ count: 0 } as never);
|
|
vi.mocked(prisma.customerPass.count).mockResolvedValue(0 as never);
|
|
});
|
|
|
|
describe("stripeProcessingFeeCents", () => {
|
|
it("zählt unsere application_fee nicht als Stripe-Gebühr", () => {
|
|
// Mit bt.fee stünden hier 171 - 50 Cent zu viel im Zahlungen-Tab.
|
|
expect(stripeProcessingFeeCents(balanceTransaction as never)).toBe(121);
|
|
});
|
|
|
|
it("rechnet weitere Stripe-Abzüge wie Steuern mit", () => {
|
|
const withTax = {
|
|
fee_details: [...balanceTransaction.fee_details, { type: "tax", amount: 23 }],
|
|
};
|
|
expect(stripeProcessingFeeCents(withTax as never)).toBe(144);
|
|
});
|
|
});
|
|
|
|
describe("recordStripeFee", () => {
|
|
it("liest die Gebühr auf dem Studio-Account und speichert sie nur einmal", async () => {
|
|
vi.mocked(stripe.paymentIntents.retrieve).mockResolvedValue({
|
|
latest_charge: { balance_transaction: balanceTransaction },
|
|
} as never);
|
|
|
|
await recordStripeFee("pi_1", "acct_studio");
|
|
|
|
expect(stripe.paymentIntents.retrieve).toHaveBeenCalledWith(
|
|
"pi_1",
|
|
{ expand: ["latest_charge.balance_transaction"] },
|
|
{ stripeAccount: "acct_studio" }
|
|
);
|
|
expect(prisma.courseBooking.updateMany).toHaveBeenCalledWith({
|
|
// stripeFee: null - ein zweites Event darf den Wert nicht überschreiben.
|
|
where: { stripePaymentIntentId: "pi_1", stripeFee: null },
|
|
data: { stripeFee: 121 },
|
|
});
|
|
});
|
|
|
|
it("trägt die Gebühr auch an einem Kartenkauf ein", async () => {
|
|
vi.mocked(stripe.paymentIntents.retrieve).mockResolvedValue({
|
|
latest_charge: { balance_transaction: balanceTransaction },
|
|
} as never);
|
|
|
|
await recordStripeFee("pi_pass", "acct_studio");
|
|
|
|
expect(prisma.customerPass.updateMany).toHaveBeenCalledWith({
|
|
where: { stripePaymentIntentId: "pi_pass", stripeFee: null },
|
|
data: { stripeFee: 121 },
|
|
});
|
|
});
|
|
|
|
it("tut nichts, solange Stripe die Zahlung nicht abgerechnet hat", async () => {
|
|
vi.mocked(stripe.paymentIntents.retrieve).mockResolvedValue({
|
|
latest_charge: { balance_transaction: null },
|
|
} as never);
|
|
|
|
await recordStripeFee("pi_1", "acct_studio");
|
|
|
|
expect(prisma.courseBooking.updateMany).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe("recordStripeFeeForCharge", () => {
|
|
const charge = { payment_intent: "pi_1", balance_transaction: "txn_1" };
|
|
|
|
it("trägt die Gebühr nach, wenn eine Buchung auf sie wartet", async () => {
|
|
vi.mocked(prisma.courseBooking.count).mockResolvedValue(1 as never);
|
|
vi.mocked(stripe.balanceTransactions.retrieve).mockResolvedValue(balanceTransaction as never);
|
|
|
|
await recordStripeFeeForCharge(charge as never, "acct_studio");
|
|
|
|
expect(stripe.balanceTransactions.retrieve).toHaveBeenCalledWith("txn_1", undefined, {
|
|
stripeAccount: "acct_studio",
|
|
});
|
|
expect(prisma.courseBooking.updateMany).toHaveBeenCalledWith({
|
|
where: { stripePaymentIntentId: "pi_1", stripeFee: null },
|
|
data: { stripeFee: 121 },
|
|
});
|
|
});
|
|
|
|
it("trägt die Gebühr nach, wenn ein Kartenkauf auf sie wartet", async () => {
|
|
vi.mocked(prisma.courseBooking.count).mockResolvedValue(0 as never);
|
|
vi.mocked(prisma.customerPass.count).mockResolvedValue(1 as never);
|
|
vi.mocked(stripe.balanceTransactions.retrieve).mockResolvedValue(balanceTransaction as never);
|
|
|
|
await recordStripeFeeForCharge(charge as never, "acct_studio");
|
|
|
|
expect(prisma.customerPass.updateMany).toHaveBeenCalledWith({
|
|
where: { stripePaymentIntentId: "pi_1", stripeFee: null },
|
|
data: { stripeFee: 121 },
|
|
});
|
|
});
|
|
|
|
it("fragt Stripe gar nicht erst, wenn keine Buchung wartet", async () => {
|
|
// charge.updated feuert auch bei Erstattungen - dann ist die Gebühr längst da.
|
|
vi.mocked(prisma.courseBooking.count).mockResolvedValue(0 as never);
|
|
|
|
await recordStripeFeeForCharge(charge as never, "acct_studio");
|
|
|
|
expect(stripe.balanceTransactions.retrieve).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("ignoriert Charges ohne Abrechnung", async () => {
|
|
await recordStripeFeeForCharge(
|
|
{ payment_intent: "pi_1", balance_transaction: null } as never,
|
|
"acct_studio"
|
|
);
|
|
|
|
expect(prisma.courseBooking.count).not.toHaveBeenCalled();
|
|
});
|
|
});
|