mirror of
https://github.com/makeplane/plane.git
synced 2026-07-12 13:29:56 +02:00
fix: website payment. (#1026)
This commit is contained in:
@@ -13,7 +13,6 @@ from plane.ee.models import WorkspaceLicense
|
||||
|
||||
|
||||
def fetch_workspace_license(workspace_id, workspace_slug, free_seats=12):
|
||||
|
||||
# If the number of free seats is less than 12, set it to 12
|
||||
workspace_free_seats = 12 if free_seats <= 12 else free_seats
|
||||
owner_email = Workspace.objects.get(slug=workspace_slug).owner.email
|
||||
@@ -78,6 +77,7 @@ def is_on_trial(workspace_license):
|
||||
"Check if the workspace is on a trial"
|
||||
if (
|
||||
workspace_license.subscription
|
||||
and not has_upgraded(workspace_license)
|
||||
and workspace_license.trial_end_date
|
||||
and workspace_license.trial_end_date >= timezone.now()
|
||||
):
|
||||
@@ -89,6 +89,7 @@ def trial_remaining_days(workspace_license):
|
||||
"""Calculate the remaining days of the trial"""
|
||||
if (
|
||||
workspace_license.subscription
|
||||
and not has_upgraded(workspace_license)
|
||||
and workspace_license.trial_end_date
|
||||
and workspace_license.trial_end_date >= timezone.now()
|
||||
):
|
||||
@@ -96,6 +97,21 @@ def trial_remaining_days(workspace_license):
|
||||
return None
|
||||
|
||||
|
||||
def is_billing_active(workspace_license):
|
||||
"""Check if the billing is active"""
|
||||
|
||||
if workspace_license.plan == WorkspaceLicense.PlanChoice.FREE:
|
||||
return False
|
||||
|
||||
if (
|
||||
workspace_license.plan == WorkspaceLicense.PlanChoice.PRO
|
||||
and is_on_trial(workspace_license)
|
||||
):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def show_payment_button(workspace_license):
|
||||
"""Check if the workspace is allowed to show the payment button"""
|
||||
|
||||
@@ -135,7 +151,6 @@ def resync_workspace_license(workspace_slug, force=False):
|
||||
if (
|
||||
workspace_license.last_synced_at - timezone.now()
|
||||
).total_seconds() > 3600 or force:
|
||||
|
||||
# Fetch the workspace license
|
||||
response = fetch_workspace_license(
|
||||
workspace_id=str(workspace.id),
|
||||
|
||||
@@ -21,6 +21,8 @@ from plane.ee.models import WorkspaceLicense
|
||||
from plane.utils.exception_logger import log_exception
|
||||
from plane.payment.utils.workspace_license_request import (
|
||||
resync_workspace_license,
|
||||
is_billing_active,
|
||||
is_on_trial,
|
||||
)
|
||||
|
||||
|
||||
@@ -93,36 +95,55 @@ class WebsiteUserWorkspaceEndpoint(BaseAPIView):
|
||||
def get(self, request):
|
||||
try:
|
||||
# Get all the workspaces where the user is admin
|
||||
workspace_ids = WorkspaceMember.objects.filter(
|
||||
member=request.user,
|
||||
is_active=True,
|
||||
role=20,
|
||||
).values_list("workspace_id", flat=True)
|
||||
|
||||
# Fetch the workspaces from the workspace license
|
||||
workspace_licenses = (
|
||||
WorkspaceLicense.objects.filter(workspace_id__in=workspace_ids)
|
||||
.annotate(slug=F("workspace__slug"))
|
||||
.annotate(name=F("workspace__name"))
|
||||
.annotate(logo=F("workspace__logo"))
|
||||
.annotate(product=F("plan"))
|
||||
workspaces = (
|
||||
WorkspaceMember.objects.filter(
|
||||
member=request.user,
|
||||
is_active=True,
|
||||
role=20,
|
||||
)
|
||||
.annotate(
|
||||
slug=F("workspace__slug"),
|
||||
logo=F("workspace__logo"),
|
||||
name=F("workspace__name"),
|
||||
)
|
||||
.values(
|
||||
"workspace_id",
|
||||
"slug",
|
||||
"name",
|
||||
"logo",
|
||||
"product",
|
||||
"trial_end_date",
|
||||
"has_activated_free_trial",
|
||||
"has_added_payment_method",
|
||||
"current_period_end_date",
|
||||
"is_offline_payment",
|
||||
"subscription",
|
||||
)
|
||||
)
|
||||
|
||||
workspace_ids = [workspace["workspace_id"] for workspace in workspaces]
|
||||
|
||||
|
||||
# Fetch the workspaces from the workspace license
|
||||
workspace_licenses = WorkspaceLicense.objects.filter(
|
||||
workspace_id__in=workspace_ids
|
||||
)
|
||||
|
||||
for workspace in workspaces:
|
||||
licenses = [
|
||||
license
|
||||
for license in workspace_licenses
|
||||
if str(license.workspace_id) == str(workspace["workspace_id"])
|
||||
]
|
||||
|
||||
if licenses:
|
||||
workspace["product"] = licenses[0].plan
|
||||
workspace["is_on_trial"] = is_on_trial(
|
||||
licenses[0]
|
||||
)
|
||||
workspace["is_billing_active"] = is_billing_active(
|
||||
licenses[0]
|
||||
)
|
||||
else:
|
||||
workspace["product"] = "FREE"
|
||||
workspace["is_on_trial"] = False
|
||||
workspace["is_billing_active"] = False
|
||||
|
||||
# Get the workspace details
|
||||
return Response(workspace_licenses, status=status.HTTP_200_OK)
|
||||
return Response(workspaces, status=status.HTTP_200_OK)
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
if e.response.status_code == 400:
|
||||
@@ -165,7 +186,6 @@ class WorkspaceProductEndpoint(BaseAPIView):
|
||||
|
||||
|
||||
class WorkspaceLicenseRefreshEndpoint(BaseAPIView):
|
||||
|
||||
def post(self, request, slug):
|
||||
# Resync the workspace license
|
||||
_ = resync_workspace_license(workspace_slug=slug, force=True)
|
||||
|
||||
@@ -57,13 +57,6 @@ const CloudUpgradePlanPage = observer(() => {
|
||||
}
|
||||
);
|
||||
|
||||
// fetch workspace current plan
|
||||
const { data: workspaceSubscription, isLoading: workspaceSubscriptionLoading } = useSWR(
|
||||
workspaceSlug ? `CLOUD_PRO_WORKSPACE_SUBSCRIPTION_PLAN_${workspaceSlug}` : null,
|
||||
workspaceSlug ? () => paymentService.getWorkspaceCurrentPlan(workspaceSlug.toString()) : null,
|
||||
{ revalidateIfStale: false, revalidateOnFocus: false }
|
||||
);
|
||||
|
||||
// derived values
|
||||
const totalWorkspaceMembers = (workspaceMembers || [])?.filter((member) => member.role >= 15)?.length;
|
||||
const proProduct = (products || [])?.find((product: IPaymentProduct) => product?.type === "PRO");
|
||||
@@ -75,9 +68,6 @@ const CloudUpgradePlanPage = observer(() => {
|
||||
const yearlyPlanUnitPrice = (yearlyPlan?.unit_amount || 0) / 1200;
|
||||
const yearlyDiscountedPrice = calculateYearlyDiscount(monthlyPlanUnitPrice, yearlyPlanUnitPrice);
|
||||
|
||||
const workspaceOnTrial =
|
||||
workspaceSubscription?.has_activated_free_trial && workspaceSubscription?.trial_end_date ? true : false;
|
||||
|
||||
const handleStripeCheckout = (priceId: string) => {
|
||||
if (!workspaceSlug) {
|
||||
setToast({
|
||||
@@ -93,38 +83,8 @@ const CloudUpgradePlanPage = observer(() => {
|
||||
product_id: proProduct?.id,
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.payment_link) {
|
||||
window.open(response.payment_link, "_self");
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: "Error!",
|
||||
message: error?.detail ?? "Failed to generate payment link. Please try again.",
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
const handleTrialStripeCheckout = (priceId: string) => {
|
||||
if (!workspaceSlug) {
|
||||
setToast({
|
||||
type: TOAST_TYPE.INFO,
|
||||
title: "Please select a workspace to continue",
|
||||
});
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
paymentService
|
||||
.modifyTrailSubscription(workspaceSlug.toString(), {
|
||||
price_id: priceId,
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.session_url) {
|
||||
window.open(response.session_url, "_self");
|
||||
if (response.url) {
|
||||
window.open(response.url, "_self");
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -143,12 +103,12 @@ const CloudUpgradePlanPage = observer(() => {
|
||||
<div className="w-full h-full flex flex-col items-center justify-center gap-2">
|
||||
<div className="flex flex-col items-center gap-2 pb-4">
|
||||
<div className="text-3xl font-semibold">Choose your billing frequency</div>
|
||||
{yearlyDiscountedPrice && (
|
||||
{!!yearlyDiscountedPrice && (
|
||||
<div className="text-center text-base text-custom-text-300">{`Upgrade to our yearly plan and get ${Math.round(yearlyDiscountedPrice)}% off.`}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{isFetching || isLoadingProduct || workspaceSubscriptionLoading ? (
|
||||
{isFetching || isLoadingProduct ? (
|
||||
<div className="flex flex-col rounded gap-1 w-full">
|
||||
<Loader.Item height="90px" />
|
||||
<Loader.Item height="90px" />
|
||||
@@ -209,12 +169,7 @@ const CloudUpgradePlanPage = observer(() => {
|
||||
|
||||
{selectedPlan?.recurring && selectedPlan.unit_amount ? (
|
||||
<>
|
||||
<Button
|
||||
className="w-full px-2 my-4"
|
||||
onClick={() =>
|
||||
workspaceOnTrial ? handleTrialStripeCheckout(selectedPlan.id) : handleStripeCheckout(selectedPlan.id)
|
||||
}
|
||||
>
|
||||
<Button className="w-full px-2 my-4" onClick={() => handleStripeCheckout(selectedPlan.id)}>
|
||||
{isLoading
|
||||
? "Redirecting to Stripe..."
|
||||
: `Pay $${renderPlanPricing(selectedPlan.unit_amount, totalWorkspaceMembers, selectedPlan.recurring)} every ${selectedPlan?.recurring}`}
|
||||
|
||||
@@ -72,16 +72,11 @@ const CloudUpgradePage = observer(() => {
|
||||
};
|
||||
|
||||
const workspaceSubscription = (workspace: TWorkspaceWithProductDetails): "FREE" | "PRO" | "TRIAL" => {
|
||||
if (!workspace.subscription) return "FREE";
|
||||
switch (workspace.product) {
|
||||
case "PRO":
|
||||
if (workspace.is_offline_payment) {
|
||||
return "PRO";
|
||||
} else if (workspace.trial_end_date && workspace.has_added_payment_method) {
|
||||
return "PRO";
|
||||
} else if (workspace.trial_end_date && !workspace.has_added_payment_method) {
|
||||
if (workspace.is_on_trial) {
|
||||
return "TRIAL";
|
||||
}
|
||||
} else return "PRO";
|
||||
case "FREE":
|
||||
return "FREE";
|
||||
default:
|
||||
@@ -95,7 +90,7 @@ const CloudUpgradePage = observer(() => {
|
||||
name="workspace-upgrade-radio-input"
|
||||
label={
|
||||
<div className="flex flex-col items-center gap-2 pb-4">
|
||||
<div className="text-3xl font-semibold">
|
||||
<div className="text-center text-3xl font-semibold">
|
||||
{isAnyWorkspaceAvailable
|
||||
? "Choose your workspace"
|
||||
: "We didn't find an eligible workspace for this upgrade. Try another email address."}
|
||||
@@ -164,7 +159,7 @@ const CloudUpgradePage = observer(() => {
|
||||
</div>
|
||||
),
|
||||
value: workspace.slug,
|
||||
disabled: ["PRO"].includes(workspaceSubscription(workspace)),
|
||||
disabled: workspace.is_billing_active,
|
||||
}))
|
||||
: []
|
||||
}
|
||||
|
||||
8
web/ee/types/workspace.d.ts
vendored
8
web/ee/types/workspace.d.ts
vendored
@@ -7,10 +7,6 @@ export type TWorkspaceWithProductDetails = {
|
||||
name: string;
|
||||
logo: string;
|
||||
product: TProductSubscriptionType;
|
||||
current_period_end_date: string;
|
||||
has_activated_free_trial: boolean;
|
||||
has_added_payment_method: boolean;
|
||||
trial_end_date: string;
|
||||
is_offline_payment: boolean;
|
||||
subscription: string;
|
||||
is_on_trial: boolean;
|
||||
is_billing_active: boolean;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user