resolved merge conflicts

This commit is contained in:
John Yeo
2026-05-05 10:46:53 +08:00
2 changed files with 75 additions and 4 deletions

View File

@@ -10,14 +10,18 @@ import { CusProductService } from "@/internal/customers/cusProducts/CusProductSe
/**
* Maps update phase format to create phase format (strips start_date).
* Preserves inline `price_data` items so standalone schedule creation
* can carry entity-scoped recurring prices forward correctly.
*
* Phase metadata carries `autumn_managed: "true"` because Stripe's
* `default_settings` has no metadata field — phase metadata is the only
* vehicle to propagate the flag onto the spawned sub.
*/
const toCreatePhase = (
phase: Stripe.SubscriptionScheduleUpdateParams.Phase,
): Stripe.SubscriptionScheduleCreateParams.Phase => ({
items: phase.items?.map((item) => ({
...(item.price_data ? { price_data: item.price_data } : { price: item.price }),
...(item.price_data
? { price_data: item.price_data }
: { price: item.price }),
quantity: item.quantity,
...(item.metadata && { metadata: item.metadata }),
})),
@@ -25,6 +29,7 @@ const toCreatePhase = (
discounts: phase.discounts as
| Stripe.SubscriptionScheduleCreateParams.Phase.Discount[]
| undefined,
metadata: { ...(phase.metadata ?? {}), autumn_managed: "true" },
});
/**
@@ -169,7 +174,6 @@ export const executeStripeSubscriptionScheduleAction = async ({
});
}
// No subscription - create standalone schedule
return await stripeCli.subscriptionSchedules.create({
customer: billingContext.stripeCustomer?.id ?? "none",
phases: params.phases?.map(toCreatePhase) ?? [],

View File

@@ -0,0 +1,67 @@
import { organizations } from "@autumn/shared";
import {
createTestContext,
type TestContext,
} from "@tests/utils/testInitUtils/createTestContext";
import { eq } from "drizzle-orm";
import { initDrizzle } from "@/db/initDrizzle";
import { OrgService } from "@/internal/orgs/OrgService";
import { clearOrgCache } from "@/internal/orgs/orgUtils/clearOrgCache";
import { encryptData } from "@/utils/encryptUtils";
const ensureTestOrgUsesStripeSandboxKey = async (): Promise<TestContext> => {
const sandboxSecretKey = process.env.STRIPE_SANDBOX_SECRET_KEY;
if (!sandboxSecretKey?.startsWith("sk_test_")) {
throw new Error(
"STRIPE_SANDBOX_SECRET_KEY must be set to a Stripe test-mode secret key",
);
}
const sandboxWebhookSecret = process.env.STRIPE_SANDBOX_WEBHOOK_SECRET;
if (!sandboxWebhookSecret?.startsWith("whsec_")) {
throw new Error(
"STRIPE_SANDBOX_WEBHOOK_SECRET must be set; the webhook handler reads this off the org's stripe_config to verify signatures",
);
}
const { db } = initDrizzle();
const organizationSlug = process.env.TESTS_ORG;
if (!organizationSlug) {
throw new Error("TESTS_ORG must be set before running integration tests");
}
const organization = await OrgService.getBySlug({
db,
slug: organizationSlug,
});
if (!organization) {
throw new Error(`Org with slug "${organizationSlug}" not found`);
}
await db
.update(organizations)
.set({
stripe_connected: true,
stripe_config: {
...(organization.stripe_config || {}),
test_api_key: encryptData(sandboxSecretKey),
test_webhook_secret: encryptData(sandboxWebhookSecret),
},
test_stripe_connect: {},
})
.where(eq(organizations.id, organization.id));
await clearOrgCache({
db,
orgId: organization.id,
});
return createTestContext();
};
let stripeSandboxContext: Promise<TestContext> | undefined;
export const getStripeSandboxContext = () => {
stripeSandboxContext ??= ensureTestOrgUsesStripeSandboxKey();
return stripeSandboxContext;
};