mirror of
https://github.com/OpenHands/OpenHands.git
synced 2026-03-22 13:47:19 +08:00
Remove unused subscription-related frontend code (#12557)
This commit is contained in:
@@ -1,8 +1,4 @@
|
||||
import { openHands } from "../open-hands-axios";
|
||||
import {
|
||||
CancelSubscriptionResponse,
|
||||
SubscriptionAccess,
|
||||
} from "./billing.types";
|
||||
|
||||
/**
|
||||
* Billing Service API - Handles all billing-related API endpoints
|
||||
@@ -44,41 +40,6 @@ class BillingService {
|
||||
);
|
||||
return data.credits;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user's subscription access information
|
||||
* @returns The user's subscription access details or null if not available
|
||||
*/
|
||||
static async getSubscriptionAccess(): Promise<SubscriptionAccess | null> {
|
||||
const { data } = await openHands.get<SubscriptionAccess | null>(
|
||||
"/api/billing/subscription-access",
|
||||
);
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a subscription checkout session for subscribing to a plan
|
||||
* @returns The redirect URL for the subscription checkout session
|
||||
*/
|
||||
static async createSubscriptionCheckoutSession(): Promise<{
|
||||
redirect_url?: string;
|
||||
}> {
|
||||
const { data } = await openHands.post(
|
||||
"/api/billing/subscription-checkout-session",
|
||||
);
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel the user's subscription
|
||||
* @returns The response indicating the result of the cancellation request
|
||||
*/
|
||||
static async cancelSubscription(): Promise<CancelSubscriptionResponse> {
|
||||
const { data } = await openHands.post<CancelSubscriptionResponse>(
|
||||
"/api/billing/cancel-subscription",
|
||||
);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
export default BillingService;
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
export type SubscriptionAccess = {
|
||||
start_at: string;
|
||||
end_at: string;
|
||||
created_at: string;
|
||||
cancelled_at?: string | null;
|
||||
stripe_subscription_id?: string | null;
|
||||
};
|
||||
|
||||
export interface CancelSubscriptionResponse {
|
||||
status: string;
|
||||
message: string;
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
import React from "react";
|
||||
import { useTranslation, Trans } from "react-i18next";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import { BrandButton } from "#/components/features/settings/brand-button";
|
||||
import { ModalBackdrop } from "#/components/shared/modals/modal-backdrop";
|
||||
import { useCancelSubscription } from "#/hooks/mutation/use-cancel-subscription";
|
||||
import {
|
||||
displayErrorToast,
|
||||
displaySuccessToast,
|
||||
} from "#/utils/custom-toast-handlers";
|
||||
|
||||
interface CancelSubscriptionModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
endDate?: string;
|
||||
}
|
||||
|
||||
export function CancelSubscriptionModal({
|
||||
isOpen,
|
||||
onClose,
|
||||
endDate,
|
||||
}: CancelSubscriptionModalProps) {
|
||||
const { t } = useTranslation();
|
||||
const cancelSubscriptionMutation = useCancelSubscription();
|
||||
|
||||
const handleCancelSubscription = async () => {
|
||||
try {
|
||||
await cancelSubscriptionMutation.mutateAsync();
|
||||
displaySuccessToast(t(I18nKey.PAYMENT$SUBSCRIPTION_CANCELLED));
|
||||
onClose();
|
||||
} catch {
|
||||
displayErrorToast(t(I18nKey.ERROR$GENERIC));
|
||||
}
|
||||
};
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
<ModalBackdrop>
|
||||
<div
|
||||
data-testid="cancel-subscription-modal"
|
||||
className="bg-base-secondary p-6 rounded-xl flex flex-col gap-4 border border-tertiary w-[500px]"
|
||||
>
|
||||
<h3 className="text-xl font-bold">
|
||||
{t(I18nKey.PAYMENT$CANCEL_SUBSCRIPTION_TITLE)}
|
||||
</h3>
|
||||
<p className="text-sm">
|
||||
{endDate ? (
|
||||
<Trans
|
||||
i18nKey={I18nKey.PAYMENT$CANCEL_SUBSCRIPTION_MESSAGE_WITH_DATE}
|
||||
values={{ date: endDate }}
|
||||
components={{ date: <span className="underline" /> }}
|
||||
/>
|
||||
) : (
|
||||
t(I18nKey.PAYMENT$CANCEL_SUBSCRIPTION_MESSAGE)
|
||||
)}
|
||||
</p>
|
||||
<div className="w-full flex gap-2 mt-2">
|
||||
<BrandButton
|
||||
testId="confirm-cancel-button"
|
||||
type="button"
|
||||
variant="primary"
|
||||
className="grow"
|
||||
onClick={handleCancelSubscription}
|
||||
isDisabled={cancelSubscriptionMutation.isPending}
|
||||
>
|
||||
{t(I18nKey.BUTTON$CONFIRM)}
|
||||
</BrandButton>
|
||||
<BrandButton
|
||||
testId="modal-cancel-button"
|
||||
type="button"
|
||||
variant="secondary"
|
||||
className="grow"
|
||||
onClick={onClose}
|
||||
isDisabled={cancelSubscriptionMutation.isPending}
|
||||
>
|
||||
{t(I18nKey.BUTTON$CANCEL)}
|
||||
</BrandButton>
|
||||
</div>
|
||||
</div>
|
||||
</ModalBackdrop>
|
||||
);
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import BillingService from "#/api/billing-service/billing-service.api";
|
||||
|
||||
export const useCreateSubscriptionCheckoutSession = () =>
|
||||
useMutation({
|
||||
mutationFn: BillingService.createSubscriptionCheckoutSession,
|
||||
onSuccess: (data) => {
|
||||
if (data.redirect_url) {
|
||||
window.location.href = data.redirect_url;
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -1,16 +0,0 @@
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import BillingService from "#/api/billing-service/billing-service.api";
|
||||
|
||||
export const useCancelSubscription = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: BillingService.cancelSubscription,
|
||||
onSuccess: () => {
|
||||
// Invalidate subscription access query to refresh the UI
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["user", "subscription_access"],
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -1,18 +0,0 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useConfig } from "./use-config";
|
||||
import BillingService from "#/api/billing-service/billing-service.api";
|
||||
import { useIsOnTosPage } from "#/hooks/use-is-on-tos-page";
|
||||
|
||||
export const useSubscriptionAccess = () => {
|
||||
const { data: config } = useConfig();
|
||||
const isOnTosPage = useIsOnTosPage();
|
||||
|
||||
return useQuery({
|
||||
queryKey: ["user", "subscription_access"],
|
||||
queryFn: BillingService.getSubscriptionAccess,
|
||||
enabled:
|
||||
!isOnTosPage &&
|
||||
config?.app_mode === "saas" &&
|
||||
config?.feature_flags?.enable_billing,
|
||||
});
|
||||
};
|
||||
@@ -574,10 +574,7 @@ export enum I18nKey {
|
||||
PAYMENT$MANAGE_CREDITS = "PAYMENT$MANAGE_CREDITS",
|
||||
PAYMENT$CANCEL_SUBSCRIPTION = "PAYMENT$CANCEL_SUBSCRIPTION",
|
||||
PAYMENT$CANCEL_SUBSCRIPTION_TITLE = "PAYMENT$CANCEL_SUBSCRIPTION_TITLE",
|
||||
PAYMENT$CANCEL_SUBSCRIPTION_MESSAGE = "PAYMENT$CANCEL_SUBSCRIPTION_MESSAGE",
|
||||
PAYMENT$CANCEL_SUBSCRIPTION_MESSAGE_WITH_DATE = "PAYMENT$CANCEL_SUBSCRIPTION_MESSAGE_WITH_DATE",
|
||||
PAYMENT$SUBSCRIPTION_CANCELLED = "PAYMENT$SUBSCRIPTION_CANCELLED",
|
||||
PAYMENT$SUBSCRIPTION_CANCELLED_EXPIRES = "PAYMENT$SUBSCRIPTION_CANCELLED_EXPIRES",
|
||||
PAYMENT$NEXT_BILLING_DATE = "PAYMENT$NEXT_BILLING_DATE",
|
||||
WAITLIST$IF_NOT_JOINED = "WAITLIST$IF_NOT_JOINED",
|
||||
WAITLIST$PATIENCE_MESSAGE = "WAITLIST$PATIENCE_MESSAGE",
|
||||
|
||||
@@ -9183,38 +9183,7 @@
|
||||
"de": "Abonnement kündigen",
|
||||
"uk": "Скасувати підписку"
|
||||
},
|
||||
"PAYMENT$CANCEL_SUBSCRIPTION_MESSAGE": {
|
||||
"en": "Are you sure you want to cancel your subscription? You will lose access to premium features at the end of your current billing period.",
|
||||
"ja": "サブスクリプションをキャンセルしてもよろしいですか?現在の請求期間の終了時にプレミアム機能へのアクセスを失います。",
|
||||
"zh-CN": "您确定要取消订阅吗?您将在当前计费周期结束时失去对高级功能的访问权限。",
|
||||
"zh-TW": "您確定要取消訂閱嗎?您將在當前計費週期結束時失去對高級功能的訪問權限。",
|
||||
"ko-KR": "구독을 취소하시겠습니까? 현재 결제 기간이 끝나면 프리미엄 기능에 대한 액세스를 잃게 됩니다.",
|
||||
"no": "Er du sikker på at du vil avbryte abonnementet? Du vil miste tilgang til premium-funksjoner ved slutten av gjeldende faktureringsperiode.",
|
||||
"it": "Sei sicuro di voler annullare l'abbonamento? Perderai l'accesso alle funzionalità premium alla fine del periodo di fatturazione corrente.",
|
||||
"pt": "Tem certeza de que deseja cancelar sua assinatura? Você perderá o acesso aos recursos premium no final do período de cobrança atual.",
|
||||
"es": "¿Estás seguro de que quieres cancelar tu suscripción? Perderás el acceso a las funciones premium al final de tu período de facturación actual.",
|
||||
"ar": "هل أنت متأكد من أنك تريد إلغاء اشتراكك؟ ستفقد الوصول إلى الميزات المميزة في نهاية فترة الفوترة الحالية.",
|
||||
"fr": "Êtes-vous sûr de vouloir annuler votre abonnement ? Vous perdrez l'accès aux fonctionnalités premium à la fin de votre période de facturation actuelle.",
|
||||
"tr": "Aboneliğinizi iptal etmek istediğinizden emin misiniz? Mevcut faturalama döneminizin sonunda premium özelliklere erişiminizi kaybedeceksiniz.",
|
||||
"de": "Sind Sie sicher, dass Sie Ihr Abonnement kündigen möchten? Sie verlieren den Zugang zu Premium-Funktionen am Ende Ihres aktuellen Abrechnungszeitraums.",
|
||||
"uk": "Ві впевнені, що хочете скасувати підписку? Ви втратите доступ до преміум-функцій наприкінці поточного розрахункового періоду."
|
||||
},
|
||||
"PAYMENT$CANCEL_SUBSCRIPTION_MESSAGE_WITH_DATE": {
|
||||
"en": "Are you sure you want to cancel your subscription? You will lose access to premium features on {{date}}.",
|
||||
"ja": "サブスクリプションをキャンセルしてもよろしいですか?{{date}}にプレミアム機能へのアクセスを失います。",
|
||||
"zh-CN": "您确定要取消订阅吗?您将在{{date}}失去对高级功能的访问权限。",
|
||||
"zh-TW": "您確定要取消訂閱嗎?您將在{{date}}失去對高級功能的訪問權限。",
|
||||
"ko-KR": "구독을 취소하시겠습니까? {{date}}에 프리미엄 기능에 대한 액세스를 잃게 됩니다.",
|
||||
"no": "Er du sikker på at du vil avbryte abonnementet? Du vil miste tilgang til premium-funksjoner {{date}}.",
|
||||
"it": "Sei sicuro di voler annullare l'abbonamento? Perderai l'accesso alle funzionalità premium il {{date}}.",
|
||||
"pt": "Tem certeza de que deseja cancelar sua assinatura? Você perderá o acesso aos recursos premium em {{date}}.",
|
||||
"es": "¿Estás seguro de que quieres cancelar tu suscripción? Perderás el acceso a las funciones premium el {{date}}.",
|
||||
"ar": "هل أنت متأكد من أنك تريد إلغاء اشتراكك؟ ستفقد الوصول إلى الميزات المميزة في {{date}}.",
|
||||
"fr": "Êtes-vous sûr de vouloir annuler votre abonnement ? Vous perdrez l'accès aux fonctionnalités premium le {{date}}.",
|
||||
"tr": "Aboneliğinizi iptal etmek istediğinizden emin misiniz? {{date}} tarihinde premium özelliklere erişiminizi kaybedeceksiniz.",
|
||||
"de": "Sind Sie sicher, dass Sie Ihr Abonnement kündigen möchten? Sie verlieren den Zugang zu Premium-Funktionen am {{date}}.",
|
||||
"uk": "Ви впевнені, що хочете скасувати підписку? Ви втратите доступ до преміум-функцій {{date}}."
|
||||
},
|
||||
|
||||
"PAYMENT$SUBSCRIPTION_CANCELLED": {
|
||||
"en": "Subscription cancelled successfully",
|
||||
"ja": "サブスクリプションが正常にキャンセルされました",
|
||||
@@ -9231,22 +9200,7 @@
|
||||
"de": "Abonnement erfolgreich gekündigt",
|
||||
"uk": "Підписку успішно скасовано"
|
||||
},
|
||||
"PAYMENT$SUBSCRIPTION_CANCELLED_EXPIRES": {
|
||||
"en": "Your subscription has been cancelled and will expire on {{date}}",
|
||||
"ja": "サブスクリプションはキャンセルされ、{{date}}に期限切れになります",
|
||||
"zh-CN": "您的订阅已取消,将于{{date}}到期",
|
||||
"zh-TW": "您的訂閱已取消,將於{{date}}到期",
|
||||
"ko-KR": "구독이 취소되었으며 {{date}}에 만료됩니다",
|
||||
"no": "Abonnementet ditt er avbrutt og utløper {{date}}",
|
||||
"it": "Il tuo abbonamento è stato annullato e scadrà il {{date}}",
|
||||
"pt": "Sua assinatura foi cancelada e expirará em {{date}}",
|
||||
"es": "Tu suscripción ha sido cancelada y expirará el {{date}}",
|
||||
"ar": "تم إلغاء اشتراكك وسينتهي في {{date}}",
|
||||
"fr": "Votre abonnement a été annulé et expirera le {{date}}",
|
||||
"tr": "Aboneliğiniz iptal edildi ve {{date}} tarihinde sona erecek",
|
||||
"de": "Ihr Abonnement wurde gekündigt und läuft am {{date}} ab",
|
||||
"uk": "Ваша підписка скасована і закінчиться {{date}}"
|
||||
},
|
||||
|
||||
"PAYMENT$NEXT_BILLING_DATE": {
|
||||
"en": "Next billing date: {{date}}",
|
||||
"ja": "次回請求日: {{date}}",
|
||||
|
||||
@@ -1,101 +1,22 @@
|
||||
import { delay, http, HttpResponse } from "msw";
|
||||
import { SubscriptionAccess } from "#/api/billing-service/billing.types";
|
||||
|
||||
// Mock data for different subscription scenarios
|
||||
const MOCK_ACTIVE_SUBSCRIPTION: SubscriptionAccess = {
|
||||
start_at: "2024-01-01T00:00:00Z",
|
||||
end_at: "2024-12-31T23:59:59Z",
|
||||
created_at: "2024-01-01T00:00:00Z",
|
||||
cancelled_at: null,
|
||||
stripe_subscription_id: "sub_mock123456789",
|
||||
};
|
||||
export const BILLING_HANDLERS = [
|
||||
http.get("/api/billing/credits", async () => {
|
||||
await delay();
|
||||
return HttpResponse.json({ credits: "100" });
|
||||
}),
|
||||
|
||||
const MOCK_CANCELLED_SUBSCRIPTION: SubscriptionAccess = {
|
||||
start_at: "2024-01-01T00:00:00Z",
|
||||
end_at: "2025-01-01T23:59:59Z",
|
||||
created_at: "2024-01-01T00:00:00Z",
|
||||
cancelled_at: "2024-06-15T10:30:00Z",
|
||||
stripe_subscription_id: "sub_mock123456789",
|
||||
};
|
||||
http.post("/api/billing/create-checkout-session", async () => {
|
||||
await delay();
|
||||
return HttpResponse.json({
|
||||
redirect_url: "https://stripe.com/some-checkout",
|
||||
});
|
||||
}),
|
||||
|
||||
// Expired subscription (end_at < now) - will be filtered out by backend logic
|
||||
const MOCK_EXPIRED_SUBSCRIPTION: SubscriptionAccess = {
|
||||
start_at: "2024-01-01T00:00:00Z",
|
||||
end_at: "2024-06-01T00:00:00Z", // Expired
|
||||
created_at: "2024-01-01T00:00:00Z",
|
||||
cancelled_at: null,
|
||||
stripe_subscription_id: "sub_mock123456789",
|
||||
};
|
||||
|
||||
// Helper function to check if subscription is currently active (matches backend logic)
|
||||
function isSubscriptionActive(
|
||||
subscription: SubscriptionAccess | null,
|
||||
): boolean {
|
||||
if (!subscription) return false;
|
||||
|
||||
const now = new Date();
|
||||
const startAt = new Date(subscription.start_at);
|
||||
const endAt = new Date(subscription.end_at);
|
||||
|
||||
// Backend filters: status == 'ACTIVE' AND start_at <= now AND end_at >= now
|
||||
return startAt <= now && endAt >= now;
|
||||
}
|
||||
|
||||
// Factory function to create billing handlers with different subscription states
|
||||
function createBillingHandlers(subscriptionData: SubscriptionAccess | null) {
|
||||
return [
|
||||
http.get("/api/billing/credits", async () => {
|
||||
await delay();
|
||||
return HttpResponse.json({ credits: "100" });
|
||||
}),
|
||||
|
||||
http.get("/api/billing/subscription-access", async () => {
|
||||
await delay();
|
||||
// Apply backend filtering logic - only return if subscription is currently active
|
||||
const activeSubscription = isSubscriptionActive(subscriptionData)
|
||||
? subscriptionData
|
||||
: null;
|
||||
return HttpResponse.json(activeSubscription);
|
||||
}),
|
||||
|
||||
http.post("/api/billing/create-checkout-session", async () => {
|
||||
await delay();
|
||||
return HttpResponse.json({
|
||||
redirect_url: "https://stripe.com/some-checkout",
|
||||
});
|
||||
}),
|
||||
|
||||
http.post("/api/billing/subscription-checkout-session", async () => {
|
||||
await delay();
|
||||
return HttpResponse.json({
|
||||
redirect_url: "https://stripe.com/some-subscription-checkout",
|
||||
});
|
||||
}),
|
||||
|
||||
http.post("/api/billing/create-customer-setup-session", async () => {
|
||||
await delay();
|
||||
return HttpResponse.json({
|
||||
redirect_url: "https://stripe.com/some-customer-setup",
|
||||
});
|
||||
}),
|
||||
|
||||
http.post("/api/billing/cancel-subscription", async () => {
|
||||
await delay();
|
||||
return HttpResponse.json({
|
||||
status: "success",
|
||||
message: "Subscription cancelled successfully",
|
||||
});
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
// Export different handler sets for different testing scenarios
|
||||
export const STRIPE_BILLING_HANDLERS = createBillingHandlers(
|
||||
MOCK_ACTIVE_SUBSCRIPTION,
|
||||
);
|
||||
export const STRIPE_BILLING_HANDLERS_NO_SUBSCRIPTION =
|
||||
createBillingHandlers(null);
|
||||
export const STRIPE_BILLING_HANDLERS_CANCELLED_SUBSCRIPTION =
|
||||
createBillingHandlers(MOCK_CANCELLED_SUBSCRIPTION);
|
||||
export const STRIPE_BILLING_HANDLERS_EXPIRED_SUBSCRIPTION =
|
||||
createBillingHandlers(MOCK_EXPIRED_SUBSCRIPTION); // This will return null due to filtering
|
||||
http.post("/api/billing/create-customer-setup-session", async () => {
|
||||
await delay();
|
||||
return HttpResponse.json({
|
||||
redirect_url: "https://stripe.com/some-customer-setup",
|
||||
});
|
||||
}),
|
||||
];
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { API_KEYS_HANDLERS } from "./api-keys-handlers";
|
||||
import { STRIPE_BILLING_HANDLERS } from "./billing-handlers";
|
||||
import { BILLING_HANDLERS } from "./billing-handlers";
|
||||
import { FILE_SERVICE_HANDLERS } from "./file-service-handlers";
|
||||
import { TASK_SUGGESTIONS_HANDLERS } from "./task-suggestions-handlers";
|
||||
import { SECRETS_HANDLERS } from "./secrets-handlers";
|
||||
@@ -16,7 +16,7 @@ import { ANALYTICS_HANDLERS } from "./analytics-handlers";
|
||||
|
||||
export const handlers = [
|
||||
...API_KEYS_HANDLERS,
|
||||
...STRIPE_BILLING_HANDLERS,
|
||||
...BILLING_HANDLERS,
|
||||
...FILE_SERVICE_HANDLERS,
|
||||
...TASK_SUGGESTIONS_HANDLERS,
|
||||
...SECRETS_HANDLERS,
|
||||
|
||||
Reference in New Issue
Block a user