Files
fluxer/fluxer_api/src/api/admin/tests/AdminBillingOverview.test.ts
T

925 lines
31 KiB
TypeScript

// SPDX-License-Identifier: AGPL-3.0-or-later
import {UserPremiumTypes} from '@fluxer/constants/src/UserConstants';
import type {
AdminBillingOverviewResponse,
AdminBillingRefundLatestInvoiceCancelResponse,
AdminInvoiceListResponse,
} from '@fluxer/schema/src/domains/admin/AdminBillingSchemas';
import type Stripe from 'stripe';
import {afterAll, afterEach, beforeAll, beforeEach, describe, expect, test} from 'vitest';
import {createTestAccount, setUserACLs} from '../../auth/tests/AuthTestUtils';
import {createUserID} from '../../BrandedTypes';
import {getBillingRepository} from '../../middleware/ServiceRegistry';
import {type ApiTestHarness, createApiTestHarness} from '../../test/ApiTestHarness';
import {createStripeApiHandlers} from '../../test/msw/handlers/StripeApiHandlers';
import {server} from '../../test/msw/server';
import {createBuilder} from '../../test/TestRequestBuilder';
import {PaymentRepository} from '../../user/repositories/PaymentRepository';
const DAY_SECONDS = 24 * 60 * 60;
function stripeFixture<T>(value: object): T {
return value as T;
}
describe('Admin billing overview', () => {
let harness: ApiTestHarness;
beforeAll(async () => {
harness = await createApiTestHarness();
});
afterAll(async () => {
await harness.shutdown();
});
beforeEach(async () => {
await harness.reset();
});
afterEach(() => {
server.resetHandlers();
});
async function setStripeCustomerId(userId: string, stripeCustomerId: string): Promise<void> {
await createBuilder(harness, '')
.post(`/test/users/${userId}/premium`)
.body({stripe_customer_id: stripeCustomerId})
.execute();
}
async function mirrorCustomer(params: {stripeCustomerId: string; userId?: string}): Promise<void> {
await getBillingRepository().customers.upsertFromStripe(
{
id: params.stripeCustomerId,
object: 'customer',
created: Math.floor(Date.now() / 1000),
email: null,
invoice_settings: {default_payment_method: null},
livemode: false,
metadata: params.userId ? {userId: params.userId} : {},
} as Stripe.Customer,
params.userId ? {knownUserId: BigInt(params.userId)} : undefined,
);
}
async function mirrorSubscription(params: {
currentPeriodEnd?: number;
currentPeriodStart?: number;
latestInvoiceId?: string;
status?: Stripe.Subscription.Status;
stripeCustomerId: string;
stripeSubscriptionId: string;
userId?: string;
}): Promise<void> {
const now = Math.floor(Date.now() / 1000);
const currentPeriodStart = params.currentPeriodStart ?? now - DAY_SECONDS;
const currentPeriodEnd = params.currentPeriodEnd ?? now + 29 * DAY_SECONDS;
await getBillingRepository().subscriptions.upsertFromStripe(
{
id: params.stripeSubscriptionId,
cancel_at: null,
cancel_at_period_end: false,
canceled_at: null,
collection_method: 'charge_automatically',
created: currentPeriodStart,
currency: 'eur',
customer: params.stripeCustomerId,
items: {
data: [
{
id: `si_${params.stripeSubscriptionId}`,
current_period_start: currentPeriodStart,
current_period_end: currentPeriodEnd,
price: {
id: 'price_monthly_eur',
product: 'prod_monthly',
unit_amount: 499,
},
quantity: 1,
},
],
},
latest_invoice: params.latestInvoiceId ?? null,
livemode: false,
metadata: params.userId ? {userId: params.userId} : {},
status: params.status ?? 'active',
},
params.userId ? {knownUserId: BigInt(params.userId)} : undefined,
);
}
async function mirrorInvoice(params: {
amountPaidCents: number;
chargeId?: string;
created?: number;
currency?: string;
invoiceId: string;
paymentIntentId?: string;
paymentId?: string;
stripeCustomerId: string;
stripeSubscriptionId?: string;
userId?: string;
}): Promise<void> {
const created = params.created ?? Math.floor(Date.now() / 1000);
await getBillingRepository().invoices.upsertFromStripe(
stripeFixture<Stripe.Invoice>({
id: params.invoiceId,
object: 'invoice',
amount_due: params.amountPaidCents,
amount_paid: params.amountPaidCents,
amount_remaining: 0,
attempt_count: 1,
attempted: true,
billing_reason: 'subscription_cycle',
collection_method: 'charge_automatically',
created,
currency: params.currency ?? 'eur',
customer: params.stripeCustomerId,
livemode: false,
metadata: params.userId ? {userId: params.userId} : {},
paid: true,
payments:
params.paymentIntentId || params.chargeId
? {
object: 'list',
data: [
{
id: params.paymentId ?? `inpay_${params.invoiceId}`,
object: 'invoice_payment',
amount_paid: params.amountPaidCents,
amount_requested: params.amountPaidCents,
created,
currency: params.currency ?? 'eur',
invoice: params.invoiceId,
is_default: true,
livemode: false,
payment: {
type: 'payment_intent',
payment_intent: params.paymentIntentId ?? null,
charge: params.chargeId ?? null,
},
status: 'paid',
status_transitions: {canceled_at: null, paid_at: created + 20},
},
],
has_more: false,
url: `/v1/invoices/${params.invoiceId}/payments`,
}
: {object: 'list', data: [], has_more: false, url: `/v1/invoices/${params.invoiceId}/payments`},
status: 'paid',
status_transitions: {finalized_at: created, paid_at: created + 20, voided_at: null},
subscription: params.stripeSubscriptionId ?? null,
subtotal: params.amountPaidCents,
total: params.amountPaidCents,
}),
params.userId ? {knownUserId: BigInt(params.userId)} : undefined,
);
}
async function mirrorPaymentIntent(params: {
amountCents?: number;
chargeId?: string;
invoiceId?: string;
paymentIntentId: string;
stripeCustomerId: string;
}): Promise<void> {
await getBillingRepository().paymentIntents.upsertFromStripe(
stripeFixture<Stripe.PaymentIntent>({
id: params.paymentIntentId,
object: 'payment_intent',
amount: params.amountCents ?? 499,
amount_capturable: 0,
amount_received: params.amountCents ?? 499,
capture_method: 'automatic',
confirmation_method: 'automatic',
created: Math.floor(Date.now() / 1000),
currency: 'eur',
customer: params.stripeCustomerId,
invoice: params.invoiceId ?? null,
latest_charge: params.chargeId ?? null,
livemode: false,
metadata: {},
payment_method_types: ['card'],
status: 'succeeded',
}),
);
}
async function mirrorCharge(params: {
amountCents?: number;
chargeId: string;
invoiceId?: string;
paymentIntentId?: string;
stripeCustomerId: string;
}): Promise<void> {
await getBillingRepository().charges.upsertFromStripe(
stripeFixture<Stripe.Charge>({
id: params.chargeId,
object: 'charge',
amount: params.amountCents ?? 499,
amount_captured: params.amountCents ?? 499,
amount_refunded: 0,
billing_details: {address: {country: null}},
captured: true,
created: Math.floor(Date.now() / 1000),
currency: 'eur',
customer: params.stripeCustomerId,
invoice: params.invoiceId ?? null,
livemode: false,
metadata: {},
paid: true,
payment_intent: params.paymentIntentId ?? null,
payment_method_details: {type: 'card', card: {brand: 'visa', last4: '4242', country: null}},
refunded: false,
status: 'succeeded',
}),
);
}
async function mirrorPaymentMethod(params: {paymentMethodId: string; stripeCustomerId: string}): Promise<void> {
await getBillingRepository().paymentMethods.upsertFromStripe(
{
id: params.paymentMethodId,
object: 'payment_method',
billing_details: {address: {country: 'US'}, email: null, name: null, phone: null},
card: {brand: 'visa', country: 'US', exp_month: 12, exp_year: 2031, funding: 'credit', last4: '4242'},
created: Math.floor(Date.now() / 1000),
customer: params.stripeCustomerId,
livemode: false,
metadata: {},
type: 'card',
} as Stripe.PaymentMethod,
{isDefault: true},
);
}
async function setStripeSubscriptionState(params: {
userId: string;
stripeCustomerId: string;
stripeSubscriptionId: string;
}): Promise<void> {
await createBuilder(harness, '')
.post(`/test/users/${params.userId}/premium`)
.body({
stripe_customer_id: params.stripeCustomerId,
stripe_subscription_id: params.stripeSubscriptionId,
premium_type: UserPremiumTypes.SUBSCRIPTION,
premium_billing_cycle: 'monthly',
premium_will_cancel: false,
})
.execute();
}
function createRefundPolicyStripeHandlers(params: {
amountPaidCents: number;
currency?: string;
elapsedDays: number;
invoiceId: string;
stripeCustomerId: string;
stripeSubscriptionId: string;
}) {
const now = Math.floor(Date.now() / 1000);
const currentPeriodStart = now - params.elapsedDays * DAY_SECONDS;
const currentPeriodEnd = currentPeriodStart + 30 * DAY_SECONDS;
return createStripeApiHandlers({
subscriptions: {
[params.stripeSubscriptionId]: {
customer: params.stripeCustomerId,
current_period_start: currentPeriodStart,
current_period_end: currentPeriodEnd,
latest_invoice: params.invoiceId,
status: 'active',
},
},
invoices: {
[params.invoiceId]: {
customer: params.stripeCustomerId,
subscriptionId: params.stripeSubscriptionId,
amount_due: params.amountPaidCents,
amount_paid: params.amountPaidCents,
billing_reason: 'subscription_cycle',
currency: params.currency ?? 'eur',
created: now - 300,
status: 'paid',
},
},
});
}
async function createPaymentRecord(params: {
userId: string;
checkoutSessionId: string;
completedAt?: Date;
euWithdrawalWaiverAccepted?: boolean;
euWithdrawalWaiverAcceptedAt?: Date | null;
euWithdrawalWaiverRequired?: boolean;
euWithdrawalWaiverTextVersion?: string | null;
invoiceId: string;
purchaseClientCountryCode?: string | null;
purchaseGeoipCountryCode?: string | null;
subscriptionId: string;
stripeCustomerId: string;
}): Promise<void> {
const paymentRepository = new PaymentRepository();
const createdAt = params.completedAt ?? new Date('2026-02-23T14:27:32.409Z');
await paymentRepository.createPayment({
checkout_session_id: params.checkoutSessionId,
user_id: createUserID(BigInt(params.userId)),
price_id: 'price_monthly_eur',
product_type: 'monthly_subscription',
status: 'completed',
is_gift: false,
created_at: createdAt,
purchase_geoip_country_code: params.purchaseGeoipCountryCode ?? null,
purchase_client_country_code: params.purchaseClientCountryCode ?? null,
eu_withdrawal_waiver_required: params.euWithdrawalWaiverRequired ?? false,
eu_withdrawal_waiver_accepted: params.euWithdrawalWaiverAccepted ?? false,
eu_withdrawal_waiver_accepted_at: params.euWithdrawalWaiverAcceptedAt ?? null,
eu_withdrawal_waiver_text_version: params.euWithdrawalWaiverTextVersion ?? null,
});
await paymentRepository.updatePayment({
checkout_session_id: params.checkoutSessionId,
stripe_customer_id: params.stripeCustomerId,
payment_intent_id: null,
subscription_id: params.subscriptionId,
invoice_id: params.invoiceId,
amount_cents: 499,
currency: 'eur',
status: 'completed',
completed_at: createdAt,
purchase_geoip_country_code: params.purchaseGeoipCountryCode ?? null,
purchase_client_country_code: params.purchaseClientCountryCode ?? null,
eu_withdrawal_waiver_required: params.euWithdrawalWaiverRequired ?? false,
eu_withdrawal_waiver_accepted: params.euWithdrawalWaiverAccepted ?? false,
eu_withdrawal_waiver_accepted_at: params.euWithdrawalWaiverAcceptedAt ?? null,
eu_withdrawal_waiver_text_version: params.euWithdrawalWaiverTextVersion ?? null,
});
}
test('resolves missing payment intents from Stripe invoice payments for overview and invoice listings', async () => {
const admin = await setUserACLs(harness, await createTestAccount(harness), ['admin:authenticate', 'billing:view']);
const targetUser = await createTestAccount(harness);
const stripeCustomerId = 'cus_billing_target';
await setStripeCustomerId(targetUser.userId, stripeCustomerId);
await createPaymentRecord({
userId: targetUser.userId,
checkoutSessionId: 'cs_billing_overview_1',
invoiceId: 'in_local_checkout_1',
subscriptionId: 'sub_billing_target',
stripeCustomerId,
});
const stripeHandlers = createStripeApiHandlers({
invoices: {
in_local_checkout_1: {
customer: stripeCustomerId,
subscriptionId: 'sub_billing_target',
amount_due: 499,
amount_paid: 499,
billing_reason: 'subscription_create',
currency: 'eur',
created: 1771862851,
payments: {
object: 'list',
data: [
{
id: 'inpay_local_checkout_1',
object: 'invoice_payment',
amount_paid: 499,
amount_requested: 499,
created: 1771862851,
currency: 'eur',
invoice: 'in_local_checkout_1',
is_default: true,
livemode: false,
payment: {
type: 'payment_intent',
payment_intent: 'pi_local_checkout_1',
charge: 'ch_local_checkout_1',
},
status: 'paid',
status_transitions: {
canceled_at: null,
paid_at: 1771862871,
},
},
],
has_more: false,
url: '/v1/invoices/in_local_checkout_1/payments',
},
},
in_renewal_1: {
customer: stripeCustomerId,
subscriptionId: 'sub_billing_target',
amount_due: 499,
amount_paid: 499,
billing_reason: 'subscription_cycle',
currency: 'eur',
created: 1776065330,
payments: {
object: 'list',
data: [
{
id: 'inpay_renewal_1',
object: 'invoice_payment',
amount_paid: 499,
amount_requested: 499,
created: 1776065330,
currency: 'eur',
invoice: 'in_renewal_1',
is_default: true,
livemode: false,
payment: {
type: 'payment_intent',
payment_intent: 'pi_renewal_1',
charge: 'ch_renewal_1',
},
status: 'paid',
status_transitions: {
canceled_at: null,
paid_at: 1776065360,
},
},
],
has_more: false,
url: '/v1/invoices/in_renewal_1/payments',
},
},
},
paymentIntents: {
pi_local_checkout_1: {
customer: stripeCustomerId,
currency: 'eur',
latest_charge: 'ch_local_checkout_1',
},
pi_renewal_1: {
customer: stripeCustomerId,
currency: 'eur',
latest_charge: 'ch_renewal_1',
},
},
paymentMethods: {
pm_billing_target_1: {
customer: stripeCustomerId,
type: 'card',
card: {
brand: 'visa',
last4: '4242',
exp_month: 12,
exp_year: 2031,
country: 'US',
},
},
},
});
server.use(...stripeHandlers.handlers);
await mirrorInvoice({
amountPaidCents: 499,
chargeId: 'ch_local_checkout_1',
created: 1771862851,
invoiceId: 'in_local_checkout_1',
paymentId: 'inpay_local_checkout_1',
paymentIntentId: 'pi_local_checkout_1',
stripeCustomerId,
stripeSubscriptionId: 'sub_billing_target',
userId: targetUser.userId,
});
await mirrorInvoice({
amountPaidCents: 499,
chargeId: 'ch_renewal_1',
created: 1776065330,
invoiceId: 'in_renewal_1',
paymentId: 'inpay_renewal_1',
paymentIntentId: 'pi_renewal_1',
stripeCustomerId,
stripeSubscriptionId: 'sub_billing_target',
userId: targetUser.userId,
});
await mirrorPaymentIntent({
chargeId: 'ch_local_checkout_1',
invoiceId: 'in_local_checkout_1',
paymentIntentId: 'pi_local_checkout_1',
stripeCustomerId,
});
await mirrorPaymentIntent({
chargeId: 'ch_renewal_1',
invoiceId: 'in_renewal_1',
paymentIntentId: 'pi_renewal_1',
stripeCustomerId,
});
await mirrorCharge({
chargeId: 'ch_local_checkout_1',
invoiceId: 'in_local_checkout_1',
paymentIntentId: 'pi_local_checkout_1',
stripeCustomerId,
});
await mirrorCharge({
chargeId: 'ch_renewal_1',
invoiceId: 'in_renewal_1',
paymentIntentId: 'pi_renewal_1',
stripeCustomerId,
});
await mirrorPaymentMethod({paymentMethodId: 'pm_billing_target_1', stripeCustomerId});
const overview = await createBuilder<AdminBillingOverviewResponse>(harness, `${admin.token}`)
.get(`/admin/billing/users/${targetUser.userId}/overview`)
.execute();
expect(overview.payments).toHaveLength(2);
const localCheckoutPayment = overview.payments.find((payment) => payment.invoice_id === 'in_local_checkout_1');
expect(localCheckoutPayment?.payment_intent_id).toBe('pi_local_checkout_1');
expect(localCheckoutPayment?.resolved_payment_intent_id).toBe('pi_local_checkout_1');
expect(localCheckoutPayment?.charge_id).toBe('ch_local_checkout_1');
expect(localCheckoutPayment?.refundable_via_payment_intent).toBe(true);
expect(overview.payment_methods[0]?.id).toBe('pm_billing_target_1');
const invoices = await createBuilder<AdminInvoiceListResponse>(harness, `${admin.token}`)
.get(`/admin/billing/users/${targetUser.userId}/invoices`)
.execute();
expect(invoices.invoices).toHaveLength(2);
expect(invoices.invoices[0]?.id).toBe('in_renewal_1');
expect(invoices.invoices[0]?.payment_intent_id).toBe('pi_renewal_1');
expect(invoices.invoices[0]?.charge_id).toBe('ch_renewal_1');
expect(invoices.invoices[0]?.billing_reason).toBe('subscription_cycle');
expect(invoices.invoices[1]?.payment_intent_id).toBe('pi_local_checkout_1');
});
test('resolves billing overview from Stripe metadata even when local Stripe linkage is missing', async () => {
const admin = await setUserACLs(harness, await createTestAccount(harness), ['admin:authenticate', 'billing:view']);
const targetUser = await createTestAccount(harness);
const stripeCustomerId = 'cus_billing_metadata_only';
const stripeSubscriptionId = 'sub_billing_metadata_only';
const invoiceId = 'in_billing_metadata_only';
const now = Math.floor(Date.now() / 1000);
const stripeHandlers = createStripeApiHandlers({
customers: {
[stripeCustomerId]: {
email: '[email protected]',
metadata: {
userId: targetUser.userId,
},
},
},
invoices: {
[invoiceId]: {
customer: stripeCustomerId,
subscriptionId: stripeSubscriptionId,
amount_due: 499,
amount_paid: 499,
billing_reason: 'subscription_create',
currency: 'eur',
created: now - 300,
status: 'paid',
},
},
paymentMethods: {
pm_billing_metadata_only: {
customer: stripeCustomerId,
type: 'card',
card: {
brand: 'visa',
last4: '1111',
exp_month: 8,
exp_year: 2031,
country: 'FR',
},
},
},
subscriptions: {
[stripeSubscriptionId]: {
customer: stripeCustomerId,
latest_invoice: invoiceId,
status: 'active',
current_period_start: now - DAY_SECONDS,
current_period_end: now + 29 * DAY_SECONDS,
},
},
});
server.use(...stripeHandlers.handlers);
await mirrorCustomer({stripeCustomerId, userId: targetUser.userId});
await mirrorSubscription({
currentPeriodEnd: now + 29 * DAY_SECONDS,
currentPeriodStart: now - DAY_SECONDS,
latestInvoiceId: invoiceId,
stripeCustomerId,
stripeSubscriptionId,
userId: targetUser.userId,
});
await mirrorInvoice({
amountPaidCents: 499,
chargeId: `ch_${invoiceId}`,
created: now - 300,
invoiceId,
paymentIntentId: `pi_${invoiceId}`,
stripeCustomerId,
stripeSubscriptionId,
userId: targetUser.userId,
});
await mirrorPaymentIntent({
chargeId: `ch_${invoiceId}`,
invoiceId,
paymentIntentId: `pi_${invoiceId}`,
stripeCustomerId,
});
await mirrorCharge({
chargeId: `ch_${invoiceId}`,
invoiceId,
paymentIntentId: `pi_${invoiceId}`,
stripeCustomerId,
});
await mirrorPaymentMethod({paymentMethodId: 'pm_billing_metadata_only', stripeCustomerId});
const overview = await createBuilder<AdminBillingOverviewResponse>(harness, `${admin.token}`)
.get(`/admin/billing/users/${targetUser.userId}/overview`)
.execute();
expect(overview.stripe_customer_id).toBe(stripeCustomerId);
expect(overview.subscription?.id).toBe(stripeSubscriptionId);
expect(overview.subscription?.status).toBe('active');
expect(overview.payment_methods[0]?.id).toBe('pm_billing_metadata_only');
expect(overview.payments[0]?.invoice_id).toBe(invoiceId);
expect(overview.payments[0]?.resolved_payment_intent_id).toBe(`pi_${invoiceId}`);
});
test('cancels a Stripe subscription at period end after resolving missing local Stripe IDs from Stripe metadata', async () => {
const admin = await setUserACLs(harness, await createTestAccount(harness), [
'admin:authenticate',
'billing:manage_subscription',
]);
const targetUser = await createTestAccount(harness);
const stripeCustomerId = 'cus_billing_cancel_metadata';
const stripeSubscriptionId = 'sub_billing_cancel_metadata';
const now = Math.floor(Date.now() / 1000);
const stripeHandlers = createStripeApiHandlers({
customers: {
[stripeCustomerId]: {
metadata: {
userId: targetUser.userId,
},
},
},
subscriptions: {
[stripeSubscriptionId]: {
customer: stripeCustomerId,
status: 'active',
current_period_start: now - DAY_SECONDS,
current_period_end: now + 29 * DAY_SECONDS,
},
},
});
server.use(...stripeHandlers.handlers);
await mirrorCustomer({stripeCustomerId, userId: targetUser.userId});
await mirrorSubscription({
currentPeriodEnd: now + 29 * DAY_SECONDS,
currentPeriodStart: now - DAY_SECONDS,
stripeCustomerId,
stripeSubscriptionId,
userId: targetUser.userId,
});
await createBuilder(harness, `${admin.token}`)
.post(`/admin/billing/users/${targetUser.userId}/cancel-subscription`)
.body({})
.expect(204)
.execute();
expect(stripeHandlers.spies.updatedSubscriptions).toContainEqual({
id: stripeSubscriptionId,
params: {
cancel_at_period_end: 'true',
},
});
});
test('allows admin refunds when the payment intent belongs to the target Stripe customer even without a local payment-intent index', async () => {
const admin = await setUserACLs(harness, await createTestAccount(harness), [
'admin:authenticate',
'billing:refund',
]);
const targetUser = await createTestAccount(harness);
const stripeCustomerId = 'cus_refund_target';
await setStripeCustomerId(targetUser.userId, stripeCustomerId);
const stripeHandlers = createStripeApiHandlers({
paymentIntents: {
pi_remote_only_refund: {
customer: stripeCustomerId,
latest_charge: 'ch_remote_only_refund',
},
},
});
server.use(...stripeHandlers.handlers);
await mirrorPaymentIntent({
chargeId: 'ch_remote_only_refund',
paymentIntentId: 'pi_remote_only_refund',
stripeCustomerId,
});
await createBuilder(harness, `${admin.token}`)
.post(`/admin/billing/users/${targetUser.userId}/refund`)
.body({
payment_intent_id: 'pi_remote_only_refund',
reason: 'Customer requested a refund',
})
.expect(204)
.execute();
expect(stripeHandlers.spies.createdRefunds).toHaveLength(1);
expect(stripeHandlers.spies.createdRefunds[0]).toMatchObject({
payment_intent: 'pi_remote_only_refund',
reason: 'requested_by_customer',
metadata: {
admin_user_id: admin.userId,
target_user_id: targetUser.userId,
admin_reason: 'Customer requested a refund',
},
});
});
test('forces a full refund when the latest invoice is inside the EU withdrawal window without a waiver', async () => {
const admin = await setUserACLs(harness, await createTestAccount(harness), [
'admin:authenticate',
'billing:refund',
'billing:manage_subscription',
]);
const targetUser = await createTestAccount(harness);
const stripeCustomerId = 'cus_eu_missing_waiver';
const stripeSubscriptionId = 'sub_eu_missing_waiver';
const invoiceId = 'in_eu_missing_waiver';
await setStripeSubscriptionState({userId: targetUser.userId, stripeCustomerId, stripeSubscriptionId});
await createPaymentRecord({
userId: targetUser.userId,
checkoutSessionId: 'cs_eu_missing_waiver',
completedAt: new Date(Date.now() - 6 * DAY_SECONDS * 1000),
euWithdrawalWaiverRequired: true,
euWithdrawalWaiverAccepted: false,
euWithdrawalWaiverTextVersion: '2026-04-23',
invoiceId,
purchaseClientCountryCode: 'DE',
purchaseGeoipCountryCode: 'DE',
subscriptionId: stripeSubscriptionId,
stripeCustomerId,
});
const stripeHandlers = createRefundPolicyStripeHandlers({
amountPaidCents: 499,
elapsedDays: 6,
invoiceId,
stripeCustomerId,
stripeSubscriptionId,
});
server.use(...stripeHandlers.handlers);
const now = Math.floor(Date.now() / 1000);
await mirrorSubscription({
currentPeriodEnd: now + 24 * DAY_SECONDS,
currentPeriodStart: now - 6 * DAY_SECONDS,
latestInvoiceId: invoiceId,
stripeCustomerId,
stripeSubscriptionId,
userId: targetUser.userId,
});
await mirrorInvoice({
amountPaidCents: 499,
chargeId: 'ch_eu_missing_waiver',
invoiceId,
paymentIntentId: 'pi_eu_missing_waiver',
stripeCustomerId,
stripeSubscriptionId,
userId: targetUser.userId,
});
await mirrorCharge({
chargeId: 'ch_eu_missing_waiver',
invoiceId,
paymentIntentId: 'pi_eu_missing_waiver',
stripeCustomerId,
});
const result = await createBuilder<AdminBillingRefundLatestInvoiceCancelResponse>(harness, `${admin.token}`)
.post(`/admin/billing/users/${targetUser.userId}/refund-policy-cancel-now`)
.body({reason: 'Withdrawal waiver missing'})
.execute();
expect(result.refund_policy).toBe('full_refund');
expect(result.refund_policy_basis).toBe('eu_eea_withdrawal_no_waiver');
expect(result.refunded_amount_cents).toBe(499);
expect(result.eu_withdrawal_waiver_required).toBe(true);
expect(result.eu_withdrawal_waiver_accepted).toBe(false);
expect(result.purchase_geoip_country_code).toBe('DE');
expect(stripeHandlers.spies.createdRefunds[0]).toMatchObject({
amount: '499',
metadata: {
refund_policy: 'full_refund',
refund_policy_basis: 'eu_eea_withdrawal_no_waiver',
eu_withdrawal_waiver_required: 'true',
eu_withdrawal_waiver_accepted: 'false',
},
});
expect(stripeHandlers.spies.cancelledSubscriptions).toContain(stripeSubscriptionId);
});
test('uses the support prorate policy when an EU waiver was accepted', async () => {
const admin = await setUserACLs(harness, await createTestAccount(harness), [
'admin:authenticate',
'billing:refund',
'billing:manage_subscription',
]);
const targetUser = await createTestAccount(harness);
const stripeCustomerId = 'cus_eu_accepted_waiver';
const stripeSubscriptionId = 'sub_eu_accepted_waiver';
const invoiceId = 'in_eu_accepted_waiver';
await setStripeSubscriptionState({userId: targetUser.userId, stripeCustomerId, stripeSubscriptionId});
await createPaymentRecord({
userId: targetUser.userId,
checkoutSessionId: 'cs_eu_accepted_waiver',
completedAt: new Date(Date.now() - 6 * DAY_SECONDS * 1000),
euWithdrawalWaiverRequired: true,
euWithdrawalWaiverAccepted: true,
euWithdrawalWaiverAcceptedAt: new Date(Date.now() - 6 * DAY_SECONDS * 1000),
euWithdrawalWaiverTextVersion: '2026-04-23',
invoiceId,
purchaseClientCountryCode: 'DE',
purchaseGeoipCountryCode: 'DE',
subscriptionId: stripeSubscriptionId,
stripeCustomerId,
});
const stripeHandlers = createRefundPolicyStripeHandlers({
amountPaidCents: 499,
elapsedDays: 6,
invoiceId,
stripeCustomerId,
stripeSubscriptionId,
});
server.use(...stripeHandlers.handlers);
const now = Math.floor(Date.now() / 1000);
await mirrorSubscription({
currentPeriodEnd: now + 24 * DAY_SECONDS,
currentPeriodStart: now - 6 * DAY_SECONDS,
latestInvoiceId: invoiceId,
stripeCustomerId,
stripeSubscriptionId,
userId: targetUser.userId,
});
await mirrorInvoice({
amountPaidCents: 499,
chargeId: 'ch_eu_accepted_waiver',
invoiceId,
paymentIntentId: 'pi_eu_accepted_waiver',
stripeCustomerId,
stripeSubscriptionId,
userId: targetUser.userId,
});
await mirrorCharge({
chargeId: 'ch_eu_accepted_waiver',
invoiceId,
paymentIntentId: 'pi_eu_accepted_waiver',
stripeCustomerId,
});
const result = await createBuilder<AdminBillingRefundLatestInvoiceCancelResponse>(harness, `${admin.token}`)
.post(`/admin/billing/users/${targetUser.userId}/refund-policy-cancel-now`)
.body({})
.execute();
expect(result.refund_policy).toBe('prorated_refund');
expect(result.refund_policy_basis).toBe('support_policy');
expect(result.refunded_amount_cents).toBe(400);
expect(stripeHandlers.spies.createdRefunds[0]).toMatchObject({
amount: '400',
metadata: {
refund_policy: 'prorated_refund',
refund_policy_basis: 'support_policy',
eu_withdrawal_waiver_required: 'true',
eu_withdrawal_waiver_accepted: 'true',
},
});
expect(stripeHandlers.spies.cancelledSubscriptions).toContain(stripeSubscriptionId);
});
test('cancels without refund after the support refund window', async () => {
const admin = await setUserACLs(harness, await createTestAccount(harness), [
'admin:authenticate',
'billing:refund',
'billing:manage_subscription',
]);
const targetUser = await createTestAccount(harness);
const stripeCustomerId = 'cus_cancel_only';
const stripeSubscriptionId = 'sub_cancel_only';
const invoiceId = 'in_cancel_only';
await setStripeSubscriptionState({userId: targetUser.userId, stripeCustomerId, stripeSubscriptionId});
await createPaymentRecord({
userId: targetUser.userId,
checkoutSessionId: 'cs_cancel_only',
completedAt: new Date(Date.now() - 20 * DAY_SECONDS * 1000),
invoiceId,
subscriptionId: stripeSubscriptionId,
stripeCustomerId,
});
const stripeHandlers = createRefundPolicyStripeHandlers({
amountPaidCents: 499,
elapsedDays: 20,
invoiceId,
stripeCustomerId,
stripeSubscriptionId,
});
server.use(...stripeHandlers.handlers);
const now = Math.floor(Date.now() / 1000);
await mirrorSubscription({
currentPeriodEnd: now + 10 * DAY_SECONDS,
currentPeriodStart: now - 20 * DAY_SECONDS,
latestInvoiceId: invoiceId,
stripeCustomerId,
stripeSubscriptionId,
userId: targetUser.userId,
});
await mirrorInvoice({
amountPaidCents: 499,
chargeId: 'ch_cancel_only',
invoiceId,
paymentIntentId: 'pi_cancel_only',
stripeCustomerId,
stripeSubscriptionId,
userId: targetUser.userId,
});
await mirrorCharge({
chargeId: 'ch_cancel_only',
invoiceId,
paymentIntentId: 'pi_cancel_only',
stripeCustomerId,
});
const result = await createBuilder<AdminBillingRefundLatestInvoiceCancelResponse>(harness, `${admin.token}`)
.post(`/admin/billing/users/${targetUser.userId}/refund-policy-cancel-now`)
.body({})
.execute();
expect(result.refund_policy).toBe('cancel_only');
expect(result.refund_policy_basis).toBe('support_policy');
expect(result.refunded_amount_cents).toBe(0);
expect(stripeHandlers.spies.createdRefunds).toHaveLength(0);
expect(stripeHandlers.spies.cancelledSubscriptions).toContain(stripeSubscriptionId);
});
});