// @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, }, ]); }); });