Files
HiveGrid-Wireframes/hivegrid-cloud/apps/web/app/utils/stripe.server.test.ts
T

660 lines
27 KiB
TypeScript

// @vitest-environment node
import { beforeEach, describe, expect, it, vi } from "vitest";
import { SubscriptionBillingPeriod } from "~/application/enums/subscriptions/SubscriptionBillingPeriod";
const stripeMocks = vi.hoisted(() => {
const client = {
billing: {
meterEvents: { create: vi.fn() },
meters: { listEventSummaries: vi.fn() },
},
billingPortal: {
sessions: { create: vi.fn() },
},
checkout: {
sessions: {
create: vi.fn(),
retrieve: vi.fn(),
},
},
coupons: { retrieve: vi.fn() },
customers: {
create: vi.fn(),
del: vi.fn(),
retrieve: vi.fn(),
update: vi.fn(),
},
invoices: {
createPreview: vi.fn(),
list: vi.fn(),
},
paymentIntents: {
list: vi.fn(),
retrieve: vi.fn(),
},
paymentMethods: {
detach: vi.fn(),
list: vi.fn(),
retrieve: vi.fn(),
},
prices: {
create: vi.fn(),
update: vi.fn(),
},
products: {
create: vi.fn(),
del: vi.fn(),
update: vi.fn(),
},
promotionCodes: { retrieve: vi.fn() },
subscriptions: {
cancel: vi.fn(),
create: vi.fn(),
retrieve: vi.fn(),
update: vi.fn(),
},
};
return {
client,
config: {
secretKey: "sk_test_stripe_unit_default",
webhookSigningSecret: "whsec_unit",
},
logger: {
debug: vi.fn(),
error: vi.fn(),
warn: vi.fn(),
},
stripeConstructor: vi.fn(() => client),
};
});
vi.mock("stripe", () => ({
default: stripeMocks.stripeConstructor,
}));
vi.mock("@toolctrl/config", () => ({
loadWebConfig: vi.fn(() => ({
ok: true,
diagnostics: [],
config: {
stripe: {
secretKey: stripeMocks.config.secretKey,
webhookSigningSecret: stripeMocks.config.webhookSigningSecret,
},
},
})),
}));
vi.mock("./cache.server", () => ({
cachified: vi.fn((options: { getFreshValue: () => unknown }) => options.getFreshValue()),
}));
vi.mock("./db/subscriptionProducts.db.server", () => ({
getAllSubscriptionProducts: vi.fn(),
}));
vi.mock("./logger.server", () => ({
createScopedLogger: vi.fn(() => stripeMocks.logger),
}));
vi.mock("./services/.server/subscriptionService", () => ({
convertToCurrency: vi.fn(({ price }: { price: number }) => price * 2),
}));
vi.mock("./url.server", () => ({
getAbsoluteCurrentUrl: vi.fn(() => "https://app.example.test/settings/subscription"),
}));
async function importStripeServer(secretKey = "sk_test_stripe_unit_default") {
stripeMocks.config.secretKey = secretKey;
vi.resetModules();
return await import("./stripe.server");
}
beforeEach(() => {
vi.clearAllMocks();
stripeMocks.config.secretKey = "sk_test_stripe_unit_default";
stripeMocks.config.webhookSigningSecret = "whsec_unit";
stripeMocks.client.checkout.sessions.create.mockResolvedValue({ id: "cs_1" });
stripeMocks.client.checkout.sessions.retrieve.mockResolvedValue({ id: "cs_1", line_items: [] });
stripeMocks.client.invoices.createPreview.mockResolvedValue({ id: "in_preview" });
stripeMocks.client.invoices.list.mockResolvedValue({ data: [], has_more: false });
stripeMocks.client.paymentIntents.list.mockResolvedValue({ data: [], has_more: false });
stripeMocks.client.paymentIntents.retrieve.mockResolvedValue({ id: "pi_1" });
stripeMocks.client.paymentMethods.detach.mockResolvedValue({ id: "pm_1" });
stripeMocks.client.paymentMethods.list.mockResolvedValue({ data: [] });
stripeMocks.client.paymentMethods.retrieve.mockResolvedValue({ id: "pm_1", customer: "cus_1" });
stripeMocks.client.prices.create.mockResolvedValue({ id: "price_1" });
stripeMocks.client.prices.update.mockResolvedValue({ id: "price_1", active: false });
stripeMocks.client.products.create.mockResolvedValue({ id: "prod_1" });
stripeMocks.client.products.del.mockResolvedValue({ id: "prod_1", deleted: true });
stripeMocks.client.products.update.mockResolvedValue({ id: "prod_1" });
stripeMocks.client.subscriptions.cancel.mockResolvedValue({ id: "sub_1", status: "canceled" });
stripeMocks.client.subscriptions.create.mockResolvedValue({ id: "sub_1" });
stripeMocks.client.subscriptions.retrieve.mockResolvedValue({ id: "sub_1" });
stripeMocks.client.subscriptions.update.mockResolvedValue({ id: "sub_1", cancel_at_period_end: false });
});
describe("stripe server config and client", () => {
it("reads typed Stripe config and fails fast when no secret key is configured", async () => {
const stripeServer = await importStripeServer("");
expect(stripeServer.getStripeSecretKey()).toBe("");
expect(stripeServer.getStripeWebhookSigningSecret()).toBe("whsec_unit");
expect(stripeServer.isStripeConfigured()).toBe(false);
expect(stripeServer.isStripeTestMode()).toBe(false);
expect(() => stripeServer.getStripeClient()).toThrow("STRIPE_SK is required before using Stripe services.");
});
it("constructs the Stripe client with the pinned API version", async () => {
const stripeServer = await importStripeServer("sk_test_stripe_unit_client");
expect(stripeServer.isStripeConfigured()).toBe(true);
expect(stripeServer.isStripeTestMode()).toBe(true);
expect(stripeServer.getStripeClient()).toBe(stripeMocks.client);
expect(stripeMocks.stripeConstructor).toHaveBeenCalledWith("sk_test_stripe_unit_client", {
apiVersion: "2025-10-29.clover",
});
});
});
describe("stripe checkout and payment methods", () => {
it("maps checkout session options for coupons, referrals, and subscription billing settings", async () => {
const stripeServer = await importStripeServer("sk_test_stripe_unit_checkout");
await expect(
stripeServer.createStripeCheckoutSession({
subscriptionProduct: { billingAddressCollection: "required" },
customer: "cus_1",
line_items: [{ price: "price_monthly", quantity: 2 }],
mode: "subscription",
success_url: "https://app.example.test/success",
cancel_url: "https://app.example.test/cancel",
freeTrialDays: 14,
coupon: "coupon_1",
referral: "ref_1",
})
).resolves.toEqual({ id: "cs_1" });
expect(stripeMocks.client.checkout.sessions.create).toHaveBeenCalledWith({
discounts: [{ coupon: "coupon_1" }],
customer: "cus_1",
line_items: [{ price: "price_monthly", quantity: 2 }],
mode: "subscription",
success_url: "https://app.example.test/success",
cancel_url: "https://app.example.test/cancel",
customer_creation: undefined,
payment_method_collection: "if_required",
subscription_data: { trial_period_days: 14 },
billing_address_collection: "required",
client_reference_id: "ref_1",
allow_promotion_codes: undefined,
});
});
it("enables promotion codes and customer creation for one-time payment sessions without an existing customer", async () => {
const stripeServer = await importStripeServer("sk_test_stripe_unit_payment");
await stripeServer.createStripeCheckoutSession({
subscriptionProduct: { billingAddressCollection: "auto" },
line_items: [{ price: "price_once" }],
mode: "payment",
success_url: "https://app.example.test/success",
cancel_url: "https://app.example.test/cancel",
});
expect(stripeMocks.client.checkout.sessions.create).toHaveBeenCalledWith(
expect.objectContaining({
allow_promotion_codes: true,
billing_address_collection: "auto",
customer_creation: "always",
discounts: undefined,
payment_method_collection: undefined,
})
);
});
it("maps explicit promotion-code discounts without enabling open promotion codes", async () => {
const stripeServer = await importStripeServer("sk_test_stripe_unit_promo");
await stripeServer.createStripeCheckoutSession({
subscriptionProduct: { billingAddressCollection: null },
line_items: [{ price: "price_once" }],
mode: "payment",
success_url: "https://app.example.test/success",
cancel_url: "https://app.example.test/cancel",
promo_code: "promo_1",
});
expect(stripeMocks.client.checkout.sessions.create).toHaveBeenCalledWith(
expect.objectContaining({
discounts: [{ promotion_code: "promo_1" }],
allow_promotion_codes: undefined,
billing_address_collection: undefined,
})
);
});
it("logs and rethrows checkout session creation failures", async () => {
const stripeServer = await importStripeServer("sk_test_stripe_unit_checkout_error");
const error = new Error("stripe checkout down");
stripeMocks.client.checkout.sessions.create.mockRejectedValueOnce(error);
await expect(
stripeServer.createStripeCheckoutSession({
subscriptionProduct: { billingAddressCollection: null },
line_items: [{ price: "price_once" }],
mode: "payment",
success_url: "https://app.example.test/success",
cancel_url: "https://app.example.test/cancel",
})
).rejects.toBe(error);
expect(stripeMocks.logger.error).toHaveBeenCalledWith({ err: error }, "Stripe checkout session creation failed");
});
it("detaches a payment method without ownership lookup when no customer id is provided", async () => {
const stripeServer = await importStripeServer("sk_test_stripe_unit_pm_no_customer");
await expect(stripeServer.deleteStripePaymentMethod("pm_1")).resolves.toEqual({ id: "pm_1" });
expect(stripeMocks.client.paymentMethods.retrieve).not.toHaveBeenCalled();
expect(stripeMocks.client.paymentMethods.detach).toHaveBeenCalledWith("pm_1");
});
it("prevents detaching a payment method that belongs to a different customer", async () => {
const stripeServer = await importStripeServer("sk_test_stripe_unit_pm");
stripeMocks.client.paymentMethods.retrieve.mockResolvedValue({ id: "pm_1", customer: { id: "cus_other" } });
await expect(stripeServer.deleteStripePaymentMethod("pm_1", "cus_1")).rejects.toThrow("Payment method not found");
expect(stripeMocks.client.paymentMethods.detach).not.toHaveBeenCalled();
});
it("detaches a payment method when ownership matches", async () => {
const stripeServer = await importStripeServer("sk_test_stripe_unit_pm_ok");
stripeMocks.client.paymentMethods.retrieve.mockResolvedValue({ id: "pm_1", customer: "cus_1" });
await expect(stripeServer.deleteStripePaymentMethod("pm_1", "cus_1")).resolves.toEqual({ id: "pm_1" });
expect(stripeMocks.client.paymentMethods.detach).toHaveBeenCalledWith("pm_1");
});
});
describe("stripe price creation", () => {
it("returns undefined instead of creating a price when the product id is missing", async () => {
const stripeServer = await importStripeServer("sk_test_stripe_unit_price_empty");
await expect(
stripeServer.createStripePrice("", {
billingPeriod: SubscriptionBillingPeriod.MONTHLY,
currency: "eur",
price: 12,
})
).resolves.toBeUndefined();
expect(stripeMocks.client.prices.create).not.toHaveBeenCalled();
});
it("maps recurring price billing periods and cents", async () => {
const stripeServer = await importStripeServer("sk_test_stripe_unit_price");
await stripeServer.createStripePrice("prod_1", {
billingPeriod: SubscriptionBillingPeriod.QUARTERLY,
currency: "eur",
price: 12.34,
trialDays: 7,
});
expect(stripeMocks.client.prices.create).toHaveBeenCalledWith({
unit_amount: 1234,
currency: "eur",
recurring: { interval: "month", interval_count: 3, trial_period_days: 7 },
product: "prod_1",
active: true,
});
});
it("maps all remaining recurring price billing periods", async () => {
const stripeServer = await importStripeServer("sk_test_stripe_unit_price_periods");
const cases = [
[SubscriptionBillingPeriod.MONTHLY, { interval: "month", trial_period_days: undefined }],
[SubscriptionBillingPeriod.SEMI_ANNUAL, { interval: "month", interval_count: 6, trial_period_days: undefined }],
[SubscriptionBillingPeriod.WEEKLY, { interval: "week", trial_period_days: undefined }],
[SubscriptionBillingPeriod.YEARLY, { interval: "year", trial_period_days: undefined }],
[SubscriptionBillingPeriod.DAILY, { interval: "day", trial_period_days: undefined }],
] as const;
for (const [billingPeriod, recurring] of cases) {
await stripeServer.createStripePrice("prod_1", {
billingPeriod,
currency: "eur",
price: 5,
});
}
expect(stripeMocks.client.prices.create).toHaveBeenCalledTimes(cases.length);
cases.forEach(([, recurring], index) => {
expect(stripeMocks.client.prices.create).toHaveBeenNthCalledWith(
index + 1,
expect.objectContaining({ recurring })
);
});
});
it("returns undefined instead of creating a usage-based price when the product id is missing", async () => {
const stripeServer = await importStripeServer("sk_test_stripe_unit_usage_price_empty");
await expect(
stripeServer.createStripeUsageBasedPrice("", {
billingPeriod: SubscriptionBillingPeriod.MONTHLY,
billingScheme: "tiered",
currency: "eur",
tiersMode: "graduated",
unitTitle: "Seats",
usageType: "licensed",
tiers: [{ from: 1, to: null }],
})
).resolves.toBeUndefined();
expect(stripeMocks.client.prices.create).not.toHaveBeenCalled();
});
it("maps usage-based tiers, metering, and open-ended tier bounds", async () => {
const stripeServer = await importStripeServer("sk_test_stripe_unit_usage_price");
await stripeServer.createStripeUsageBasedPrice("prod_usage", {
billingPeriod: SubscriptionBillingPeriod.SEMI_ANNUAL,
billingScheme: "tiered",
currency: "usd",
tiersMode: "graduated",
unitTitle: "Seats",
usageType: "licensed",
tiers: [
{ from: 1, to: 10, perUnitPrice: 10 },
{ from: 11, to: null, perUnitPrice: 8, flatFeePrice: 20 },
],
});
expect(stripeMocks.client.prices.create).toHaveBeenCalledWith({
currency: "usd",
tiers_mode: "graduated",
billing_scheme: "tiered",
nickname: "Seats",
recurring: { interval: "month", interval_count: 6, usage_type: "licensed" },
product: "prod_usage",
tiers: [
{ up_to: 10, unit_amount_decimal: "1000", flat_amount_decimal: undefined },
{ up_to: "inf", unit_amount_decimal: "800", flat_amount_decimal: "2000" },
],
active: true,
expand: ["tiers"],
});
});
it("maps usage-based billing periods and default tier settings", async () => {
const stripeServer = await importStripeServer("sk_test_stripe_unit_usage_price_defaults");
const cases = [
[SubscriptionBillingPeriod.MONTHLY, { interval: "month", interval_count: undefined, usage_type: "metered" }],
[SubscriptionBillingPeriod.QUARTERLY, { interval: "month", interval_count: 3, usage_type: "metered" }],
[SubscriptionBillingPeriod.WEEKLY, { interval: "week", interval_count: undefined, usage_type: "metered" }],
[SubscriptionBillingPeriod.YEARLY, { interval: "year", interval_count: undefined, usage_type: "metered" }],
[SubscriptionBillingPeriod.DAILY, { interval: "day", interval_count: undefined, usage_type: "metered" }],
] as const;
for (const [billingPeriod] of cases) {
await stripeServer.createStripeUsageBasedPrice("prod_usage", {
billingPeriod,
billingScheme: "per_unit",
currency: "eur",
tiersMode: "volume",
unitTitle: "API calls",
usageType: "metered",
tiers: [
{ from: 11, to: null },
{ from: 1, to: 10, perUnitPrice: 2 },
],
});
}
expect(stripeMocks.client.prices.create).toHaveBeenCalledTimes(cases.length);
cases.forEach(([, recurring], index) => {
expect(stripeMocks.client.prices.create).toHaveBeenNthCalledWith(
index + 1,
expect.objectContaining({
billing_scheme: "per_unit",
tiers_mode: "volume",
recurring,
tiers: [
{ up_to: "inf", unit_amount_decimal: undefined, flat_amount_decimal: undefined },
{ up_to: 10, unit_amount_decimal: "200", flat_amount_decimal: undefined },
],
})
);
});
});
});
describe("stripe read fallback helpers", () => {
it("returns null or empty arrays when optional Stripe lookups fail", async () => {
const stripeServer = await importStripeServer("sk_test_stripe_unit_fallbacks");
stripeMocks.client.checkout.sessions.retrieve.mockRejectedValue(new Error("stripe down"));
stripeMocks.client.invoices.list.mockRejectedValue(new Error("stripe down"));
stripeMocks.client.paymentIntents.list.mockRejectedValue(new Error("stripe down"));
await expect(stripeServer.getStripeSession("cs_missing")).resolves.toBeNull();
await expect(stripeServer.getStripeInvoices("cus_1")).resolves.toEqual([]);
await expect(stripeServer.getStripeInvoices(null)).resolves.toEqual([]);
await expect(stripeServer.getStripePaymentIntents("cus_1")).resolves.toEqual([]);
await expect(stripeServer.getStripePaymentIntents(null)).resolves.toEqual([]);
});
it("caps invoice list requests with an explicit Stripe limit", async () => {
const stripeServer = await importStripeServer("sk_test_stripe_unit_invoice_limit");
stripeMocks.client.invoices.list.mockResolvedValueOnce({ data: [{ id: "in_1" }], has_more: false });
await expect(stripeServer.getStripeInvoices("cus_1", { limit: 250 })).resolves.toEqual([{ id: "in_1" }]);
expect(stripeMocks.client.invoices.list).toHaveBeenCalledWith({ customer: "cus_1", limit: 100 });
});
it("uses the conservative invoice list limit by default", async () => {
const stripeServer = await importStripeServer("sk_test_stripe_unit_invoice_default_limit");
await expect(stripeServer.getStripeInvoices("cus_1")).resolves.toEqual([]);
expect(stripeMocks.client.invoices.list).toHaveBeenCalledWith({
customer: "cus_1",
limit: stripeServer.DEFAULT_STRIPE_INVOICE_LIST_LIMIT,
});
});
it("returns null or empty arrays when read wrapper helpers reject", async () => {
const stripeServer = await importStripeServer("sk_test_stripe_unit_read_errors");
stripeMocks.client.subscriptions.retrieve.mockRejectedValueOnce(new Error("stripe down"));
stripeMocks.client.paymentIntents.retrieve.mockRejectedValueOnce(new Error("stripe down"));
stripeMocks.client.invoices.createPreview.mockRejectedValueOnce(new Error("stripe down"));
stripeMocks.client.paymentMethods.list.mockRejectedValueOnce(new Error("stripe down"));
await expect(stripeServer.getStripeSubscription("sub_missing")).resolves.toBeNull();
await expect(stripeServer.getStripePaymentIntent("pi_missing")).resolves.toBeNull();
await expect(stripeServer.getStripeUpcomingInvoice(null)).resolves.toBeNull();
await expect(stripeServer.getStripeUpcomingInvoice("cus_missing")).resolves.toBeNull();
await expect(stripeServer.getStripePaymentMethods("cus_missing")).resolves.toEqual([]);
});
it("filters failed upcoming invoice previews out of batch results", async () => {
const stripeServer = await importStripeServer("sk_test_stripe_unit_upcoming");
stripeMocks.client.invoices.createPreview
.mockResolvedValueOnce({ id: "in_preview_1" })
.mockRejectedValueOnce(new Error("stripe down"))
.mockResolvedValueOnce({ id: "in_preview_2" });
await expect(stripeServer.getStripeUpcomingInvoices(["sub_1", "sub_2", "sub_3"])).resolves.toEqual([{ id: "in_preview_1" }, { id: "in_preview_2" }]);
expect(stripeMocks.logger.error).toHaveBeenCalledWith(expect.objectContaining({ err: expect.any(Error) }), "Could not get upcoming invoice");
});
it("returns null or empty data for meter APIs when Stripe rejects", async () => {
const stripeServer = await importStripeServer("sk_test_stripe_unit_meters");
stripeMocks.client.billing.meterEvents.create.mockRejectedValue(new Error("stripe down"));
stripeMocks.client.billing.meters.listEventSummaries.mockRejectedValue(new Error("stripe down"));
await expect(
stripeServer.createMeterEvent({
eventName: "seats_used",
stripeCustomerId: "cus_1",
value: 3,
identifier: "event_1",
timestamp: 1_700_000_000,
})
).resolves.toBeNull();
await expect(
stripeServer.listMeterEventSummaries("meter_1", {
customer: "cus_1",
startTime: 1,
endTime: 2,
})
).resolves.toEqual([]);
});
it("wraps setup, subscription, payment intent, payment method, and upcoming invoice reads", async () => {
const stripeServer = await importStripeServer("sk_test_stripe_unit_wrappers");
stripeMocks.client.paymentMethods.list.mockResolvedValue({ data: [{ id: "pm_1" }] });
await expect(stripeServer.createStripeSetupSession(new Request("https://app.example.test/settings/subscription"), "cus_1")).resolves.toEqual({
id: "cs_1",
});
await expect(stripeServer.cancelStripeSubscription("sub_1")).resolves.toEqual({ id: "sub_1", status: "canceled" });
await expect(stripeServer.reactivateStripeSubscription("sub_1")).resolves.toEqual({ id: "sub_1", cancel_at_period_end: false });
await expect(stripeServer.getStripeSubscription("sub_1")).resolves.toEqual({ id: "sub_1" });
await expect(stripeServer.createStripeSubscription("cus_1", "price_1", 1_700_000_000)).resolves.toEqual({ id: "sub_1" });
await expect(stripeServer.getStripePaymentIntent("pi_1")).resolves.toEqual({ id: "pi_1" });
await expect(stripeServer.getStripeUpcomingInvoice("cus_1")).resolves.toEqual({ id: "in_preview" });
await expect(stripeServer.getStripePaymentMethods("cus_1")).resolves.toEqual([{ id: "pm_1" }]);
await expect(stripeServer.getStripePaymentMethods(null)).resolves.toEqual([]);
expect(stripeMocks.client.checkout.sessions.create).toHaveBeenCalledWith({
customer: "cus_1",
success_url: "https://app.example.test/settings/subscription?session_id={CHECKOUT_SESSION_ID}",
cancel_url: "https://app.example.test/settings/subscription",
mode: "setup",
payment_method_types: ["card"],
});
});
it("wraps customer, product, coupon, promotion code, cache, and portal helpers", async () => {
const stripeServer = await importStripeServer("sk_test_stripe_unit_catalog");
stripeMocks.client.billingPortal.sessions.create.mockResolvedValue({ id: "bps_1" });
stripeMocks.client.coupons.retrieve.mockResolvedValue({ id: "coupon_1" });
stripeMocks.client.customers.create.mockResolvedValue({ id: "cus_1" });
stripeMocks.client.customers.del.mockResolvedValue({ id: "cus_1", deleted: true });
stripeMocks.client.customers.retrieve.mockResolvedValue({ id: "cus_1" });
stripeMocks.client.customers.update.mockResolvedValue({ id: "cus_1", invoice_settings: { default_payment_method: "pm_1" } });
stripeMocks.client.invoices.list.mockResolvedValue({ data: [{ id: "in_open" }] });
stripeMocks.client.promotionCodes.retrieve.mockResolvedValue({ id: "promo_1" });
await expect(stripeServer.createStripeCustomer("person@example.test", "Person Test")).resolves.toEqual({ id: "cus_1" });
await expect(stripeServer.deleteStripeCustomer("cus_1")).resolves.toEqual({ id: "cus_1", deleted: true });
await expect(stripeServer.updateStripeCustomerPaymentMethod("cus_1", "pm_1")).resolves.toEqual({
id: "cus_1",
invoice_settings: { default_payment_method: "pm_1" },
});
await expect(stripeServer.createStripeProduct({ title: "Pro" })).resolves.toEqual({ id: "prod_1" });
await expect(stripeServer.updateStripeProduct("prod_1", { title: "Pro Plus" })).resolves.toEqual({ id: "prod_1" });
await expect(stripeServer.deleteStripeProduct("prod_1")).resolves.toEqual({ id: "prod_1", deleted: true });
await expect(stripeServer.archiveStripeProduct("prod_1")).resolves.toEqual({ id: "prod_1" });
await expect(stripeServer.archiveStripePrice("price_1")).resolves.toEqual({ id: "price_1", active: false });
await expect(stripeServer.getStripeCoupon("coupon_1")).resolves.toEqual({ id: "coupon_1" });
await expect(stripeServer.getStripePromotionCode("promo_1")).resolves.toEqual({ id: "promo_1" });
await expect(stripeServer.getStripeCustomer("cus_1")).resolves.toEqual({ id: "cus_1" });
await expect(stripeServer.getStripeCustomer(null)).resolves.toBeNull();
await expect(stripeServer.getOpenInvoices("cus_1")).resolves.toEqual({ data: [{ id: "in_open" }] });
await expect(stripeServer.createCustomerPortalSession(new Request("https://app.example.test/settings"), "cus_1")).resolves.toEqual({
id: "bps_1",
});
});
it("paginates payments and builds invoice lookup maps for revenue aggregation", async () => {
const stripeServer = await importStripeServer("sk_test_stripe_unit_revenue");
const { getAllSubscriptionProducts } = await import("./db/subscriptionProducts.db.server");
vi.mocked(getAllSubscriptionProducts).mockResolvedValue([
{
stripeId: "prod_1",
title: "Pro",
prices: [{ stripeId: "price_1", billingPeriod: SubscriptionBillingPeriod.MONTHLY }],
},
] as never);
stripeMocks.client.paymentIntents.list.mockResolvedValueOnce({
data: [
{ id: "pi_1", amount: 1000, currency: "eur", status: "succeeded" },
{ id: "pi_ignored", amount: 5000, currency: "usd", status: "requires_payment_method" },
],
has_more: false,
});
stripeMocks.client.invoices.list.mockResolvedValueOnce({
data: [
{
id: "in_1",
lines: {
data: [
{
pricing: {
price_details: {
price: "price_1",
product: "prod_1",
},
},
},
],
},
payments: {
data: [
{
payment: {
payment_intent: { id: "pi_1" },
},
},
],
},
},
],
has_more: false,
});
await expect(stripeServer.getAllStripePayments()).resolves.toEqual([
{ id: "pi_1", amount: 1000, currency: "eur", status: "succeeded" },
{ id: "pi_ignored", amount: 5000, currency: "usd", status: "requires_payment_method" },
]);
stripeMocks.client.paymentIntents.list.mockResolvedValueOnce({
data: [{ id: "pi_1", amount: 1000, currency: "eur", status: "succeeded" }],
has_more: false,
});
stripeMocks.client.invoices.list.mockResolvedValueOnce({
data: [
{
id: "in_1",
lines: {
data: [{ pricing: { price_details: { price: "price_1", product: "prod_1" } } }],
},
payments: { data: [{ payment: { payment_intent: "pi_1" } }] },
},
],
has_more: false,
});
await expect(stripeServer.getStripeRevenueByProductPriceCurrency()).resolves.toEqual([
{
product: "Pro",
billingPeriod: SubscriptionBillingPeriod.MONTHLY,
currency: "eur",
revenueInCurrency: 10,
count: 1,
countPercentage: 1,
revenueUsd: 20,
revenuePercentageUsd: 1,
},
]);
});
});