mirror of
https://github.com/formbricks/formbricks.git
synced 2026-03-13 19:30:36 -05:00
fix: reuse existing stripe customer for billing actions
This commit is contained in:
@@ -109,6 +109,28 @@ describe("billing actions", () => {
|
||||
expect(result).toEqual({ success: true });
|
||||
});
|
||||
|
||||
test("startHobbyAction reuses an existing stripe customer id", async () => {
|
||||
mocks.getOrganization.mockResolvedValue({
|
||||
id: "org_1",
|
||||
billing: {
|
||||
stripeCustomerId: "cus_existing",
|
||||
},
|
||||
});
|
||||
|
||||
const result = await startHobbyAction({
|
||||
ctx: { user: { id: "user_1" } },
|
||||
parsedInput: { organizationId: "org_1" },
|
||||
} as any);
|
||||
|
||||
expect(mocks.ensureStripeCustomerForOrganization).not.toHaveBeenCalled();
|
||||
expect(mocks.reconcileCloudStripeSubscriptionsForOrganization).toHaveBeenCalledWith(
|
||||
"org_1",
|
||||
"start-hobby"
|
||||
);
|
||||
expect(mocks.syncOrganizationBillingFromStripe).toHaveBeenCalledWith("org_1");
|
||||
expect(result).toEqual({ success: true });
|
||||
});
|
||||
|
||||
test("startScaleTrialAction uses ensured customer when org snapshot has no stripe customer id", async () => {
|
||||
const result = await startScaleTrialAction({
|
||||
ctx: { user: { id: "user_1" } },
|
||||
@@ -125,4 +147,27 @@ describe("billing actions", () => {
|
||||
expect(mocks.syncOrganizationBillingFromStripe).toHaveBeenCalledWith("org_1");
|
||||
expect(result).toEqual({ success: true });
|
||||
});
|
||||
|
||||
test("startScaleTrialAction reuses an existing stripe customer id", async () => {
|
||||
mocks.getOrganization.mockResolvedValue({
|
||||
id: "org_1",
|
||||
billing: {
|
||||
stripeCustomerId: "cus_existing",
|
||||
},
|
||||
});
|
||||
|
||||
const result = await startScaleTrialAction({
|
||||
ctx: { user: { id: "user_1" } },
|
||||
parsedInput: { organizationId: "org_1" },
|
||||
} as any);
|
||||
|
||||
expect(mocks.ensureStripeCustomerForOrganization).not.toHaveBeenCalled();
|
||||
expect(mocks.createScaleTrialSubscription).toHaveBeenCalledWith("org_1", "cus_existing");
|
||||
expect(mocks.reconcileCloudStripeSubscriptionsForOrganization).toHaveBeenCalledWith(
|
||||
"org_1",
|
||||
"scale-trial"
|
||||
);
|
||||
expect(mocks.syncOrganizationBillingFromStripe).toHaveBeenCalledWith("org_1");
|
||||
expect(result).toEqual({ success: true });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -169,7 +169,9 @@ export const startHobbyAction = authenticatedActionClient
|
||||
throw new ResourceNotFoundError("organization", parsedInput.organizationId);
|
||||
}
|
||||
|
||||
const { customerId } = await ensureStripeCustomerForOrganization(parsedInput.organizationId);
|
||||
const customerId =
|
||||
organization.billing?.stripeCustomerId ??
|
||||
(await ensureStripeCustomerForOrganization(parsedInput.organizationId)).customerId;
|
||||
if (!customerId) {
|
||||
throw new ResourceNotFoundError("OrganizationBilling", parsedInput.organizationId);
|
||||
}
|
||||
@@ -198,7 +200,9 @@ export const startScaleTrialAction = authenticatedActionClient
|
||||
throw new ResourceNotFoundError("organization", parsedInput.organizationId);
|
||||
}
|
||||
|
||||
const { customerId } = await ensureStripeCustomerForOrganization(parsedInput.organizationId);
|
||||
const customerId =
|
||||
organization.billing?.stripeCustomerId ??
|
||||
(await ensureStripeCustomerForOrganization(parsedInput.organizationId)).customerId;
|
||||
if (!customerId) {
|
||||
throw new ResourceNotFoundError("OrganizationBilling", parsedInput.organizationId);
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ const CUSTOMER_LOGOS = [
|
||||
export const SelectPlanCard = ({ nextUrl, organizationId }: SelectPlanCardProps) => {
|
||||
const router = useRouter();
|
||||
const [isStartingTrial, setIsStartingTrial] = useState(false);
|
||||
const [isStayingOnHobby, setIsStayingOnHobby] = useState(false);
|
||||
const [isStartingHobby, setIsStartingHobby] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const TRIAL_FEATURE_KEYS = [
|
||||
@@ -62,18 +62,18 @@ export const SelectPlanCard = ({ nextUrl, organizationId }: SelectPlanCardProps)
|
||||
};
|
||||
|
||||
const handleContinueFree = async () => {
|
||||
setIsStayingOnHobby(true);
|
||||
setIsStartingHobby(true);
|
||||
try {
|
||||
const result = await startHobbyAction({ organizationId });
|
||||
if (result?.data) {
|
||||
router.push(nextUrl);
|
||||
} else {
|
||||
toast.error(t("common.something_went_wrong_please_try_again"));
|
||||
setIsStayingOnHobby(false);
|
||||
setIsStartingHobby(false);
|
||||
}
|
||||
} catch {
|
||||
toast.error(t("common.something_went_wrong_please_try_again"));
|
||||
setIsStayingOnHobby(false);
|
||||
setIsStartingHobby(false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -107,7 +107,7 @@ export const SelectPlanCard = ({ nextUrl, organizationId }: SelectPlanCardProps)
|
||||
onClick={handleStartTrial}
|
||||
className="mt-4 w-full"
|
||||
loading={isStartingTrial}
|
||||
disabled={isStartingTrial || isStayingOnHobby}>
|
||||
disabled={isStartingTrial || isStartingHobby}>
|
||||
{t("common.start_free_trial")}
|
||||
</Button>
|
||||
</div>
|
||||
@@ -134,9 +134,9 @@ export const SelectPlanCard = ({ nextUrl, organizationId }: SelectPlanCardProps)
|
||||
|
||||
<button
|
||||
onClick={handleContinueFree}
|
||||
disabled={isStartingTrial || isStayingOnHobby}
|
||||
disabled={isStartingTrial || isStartingHobby}
|
||||
className="text-sm text-slate-400 underline-offset-2 transition-colors hover:text-slate-600 hover:underline">
|
||||
{isStayingOnHobby ? t("common.loading") : t("environments.settings.billing.stay_on_hobby_plan")}
|
||||
{isStartingHobby ? t("common.loading") : t("environments.settings.billing.stay_on_hobby_plan")}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user