Skip to content

Refactoring - subscription handling #1275

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Nov 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
364 changes: 6 additions & 358 deletions client/packages/lowcoder/src/api/subscriptionApi.ts
Original file line number Diff line number Diff line change
@@ -1,128 +1,16 @@
import Api from "api/api";
import axios, { AxiosInstance, AxiosRequestConfig } from "axios";
import { useDispatch, useSelector } from "react-redux";
import { getUser, getCurrentUser } from "redux/selectors/usersSelectors";
import { useEffect, useState} from "react";
import { calculateFlowCode } from "./apiUtils";
import { getDeploymentId } from "@lowcoder-ee/redux/selectors/configSelectors";
import { fetchOrgUsersAction } from "redux/reduxActions/orgActions";
import { getOrgUsers } from "redux/selectors/orgSelectors";
import { AppState } from "@lowcoder-ee/redux/reducers";

// Interfaces
export interface CustomerAddress {
line1: string;
line2: string;
city: string;
state: string;
country: string;
postalCode: string;
}

export interface LowcoderNewCustomer {
hostname: string;
hostId: string;
email: string;
orgId: string;
userId: string;
userName: string;
type: string;
companyName: string;
address?: CustomerAddress;
}

export interface LowcoderSearchCustomer {
hostname: string;
hostId: string;
email: string;
orgId: string;
userId: string;
}

interface LowcoderMetadata {
lowcoder_host: string;
lowcoder_hostId: string;
lowcoder_orgId: string;
lowcoder_type: string;
lowcoder_userId: string;
}

export interface StripeCustomer {
id: string;
object: string;
address?: object | null;
balance: number;
created: number;
currency: string | null;
default_source: string | null;
delinquent: boolean;
description: string | null;
discount: string | null;
email: string;
invoice_prefix: string;
invoice_settings: object | null;
livemode: boolean;
metadata: LowcoderMetadata;
name: string;
phone: string | null;
preferred_locales: string[];
shipping: string | null;
tax_exempt: string;
test_clock: string | null;
}

export interface Pricing {
type: string;
amount: string;
}

export interface Product {
title?: string;
description?: string;
image?: string;
pricingType: string;
product: string;
activeSubscription: boolean;
accessLink: string;
subscriptionId: string;
checkoutLink: string;
checkoutLinkDataLoaded?: boolean;
type?: string;
quantity_entity?: string;
}

export interface SubscriptionItem {
id: string;
object: string;
plan: {
id: string;
product: string;
};
quantity: number;
}

export interface Subscription {
id: string;
collection_method: string;
current_period_end: number;
current_period_start: number;
product: string;
currency: string;
interval: string;
tiers_mode: string;
status: string;
start_date: number;
quantity: number;
billing_scheme: string;
price: string;
}

export interface SubscriptionsData {
subscriptions: Subscription[];
subscriptionDataLoaded: boolean;
subscriptionDataError: boolean;
loading: boolean;
}
import type {
LowcoderNewCustomer,
LowcoderSearchCustomer,
StripeCustomer,
} from "@lowcoder-ee/constants/subscriptionConstants";

export type ResponseType = {
response: any;
Expand Down Expand Up @@ -274,7 +162,7 @@ export const getProducts = async () => {
};
try {
const result = await SubscriptionApi.secureRequest(apiBody);
return result?.data as any;
return result?.data?.data as any[];
} catch (error) {
console.error("Error fetching product:", error);
throw error;
Expand Down Expand Up @@ -380,244 +268,4 @@ export const useOrgUserCount = (orgId: string) => {
return userCount;
};

export const InitializeSubscription = () => {
const [customer, setCustomer] = useState<StripeCustomer | null>(null);
const [isCreatingCustomer, setIsCreatingCustomer] = useState<boolean>(false); // Track customer creation
const [customerDataError, setCustomerDataError] = useState<boolean>(false);
const [subscriptions, setSubscriptions] = useState<SubscriptionItem[]>([]);
const [subscriptionDataLoaded, setSubscriptionDataLoaded] = useState<boolean>(false);
const [subscriptionDataError, setSubscriptionDataError] = useState<boolean>(false);
const [checkoutLinkDataLoaded, setCheckoutLinkDataLoaded] = useState<boolean>(false);
const [checkoutLinkDataError, setCheckoutLinkDataError] = useState<boolean>(false);
const [products, setProducts] = useState<Product[]>([
{
pricingType: "Monthly, per User",
activeSubscription: false,
accessLink: "1PhH38DDlQgecLSfSukEgIeV",
product: "QW8L3WPMiNjQjI",
subscriptionId: "",
checkoutLink: "",
checkoutLinkDataLoaded: false,
type: "org",
quantity_entity: "orgUser",
},
{
pricingType: "Monthly, per User",
activeSubscription: false,
accessLink: "1Pf65wDDlQgecLSf6OFlbsD5",
product: "QW8MpIBHxieKXd",
checkoutLink: "",
checkoutLinkDataLoaded: false,
subscriptionId: "",
type: "user",
quantity_entity: "singleItem",
},
{
pricingType: "Monthly, per User",
activeSubscription: false,
accessLink: "1PttHIDDlQgecLSf0XP27tXt",
product: "QlQ7cdOh8Lv4dy",
subscriptionId: "",
checkoutLink: "",
checkoutLinkDataLoaded: false,
type: "org",
quantity_entity: "singleItem",
},
]);


const user = useSelector(getUser);
const currentUser = useSelector(getCurrentUser);
const deploymentId = useSelector(getDeploymentId);
const currentOrg = user.orgs.find(org => org.id === user.currentOrgId);
const orgID = user.currentOrgId;
const domain = window.location.protocol + "//" + window.location.hostname + (window.location.port ? ':' + window.location.port : '');
const admin = user.orgRoleMap.get(orgID) === "admin" ? "admin" : "member";
const dispatch = useDispatch();

const userCount = useOrgUserCount(orgID);

const subscriptionSearchCustomer: LowcoderSearchCustomer = {
hostname: domain,
hostId: deploymentId,
email: currentUser.email,
orgId: orgID,
userId: user.id,
};

const subscriptionNewCustomer: LowcoderNewCustomer = {
hostname: domain,
hostId: deploymentId,
email: currentUser.email,
orgId: orgID,
userId: user.id,
userName: user.username,
type: admin,
companyName: currentOrg?.name || "Unknown",
};

useEffect(() => {
const initializeCustomer = async () => {
try {
setIsCreatingCustomer(true);
const existingCustomer = await searchCustomer(subscriptionSearchCustomer);
if (existingCustomer != null) {
setCustomer(existingCustomer);
} else {
const newCustomer = await createCustomer(subscriptionNewCustomer);
setCustomer(newCustomer);
}
} catch (error) {
setCustomerDataError(true);
} finally {
setIsCreatingCustomer(false);
}
};

if (Boolean(deploymentId)) {
initializeCustomer();
}
}, [deploymentId]);

useEffect(() => {
const fetchSubscriptions = async () => {
if (customer) {
try {
const subs = await searchSubscriptions(customer.id);
setSubscriptions(subs);
setSubscriptionDataLoaded(true);
} catch (error) {
setSubscriptionDataError(true);
}
}
};

fetchSubscriptions();
}, [customer]);

useEffect(() => {
const prepareCheckout = async () => {
if (subscriptionDataLoaded && userCount > 0) { // Ensure user count is available
try {
console.log("Total Users in Organization:", userCount);

const updatedProducts = await Promise.all(
products.map(async (product) => {
const matchingSubscription = subscriptions.find(
(sub) => sub.plan.id === "price_" + product.accessLink
);

if (matchingSubscription) {
return {
...product,
activeSubscription: true,
checkoutLinkDataLoaded: true,
subscriptionId: matchingSubscription.id.substring(4),
};
} else {
// Use the user count to set the quantity for checkout link
const checkoutLink = await createCheckoutLink(customer!, product.accessLink, userCount);
return {
...product,
activeSubscription: false,
checkoutLink: checkoutLink ? checkoutLink.url : "",
checkoutLinkDataLoaded: true,
};
}
})
);

setProducts(updatedProducts);
} catch (error) {
setCheckoutLinkDataError(true);
}
}
};

prepareCheckout();
}, [subscriptionDataLoaded, userCount]);

return {
customer,
isCreatingCustomer,
customerDataError,
subscriptions,
subscriptionDataLoaded,
subscriptionDataError,
checkoutLinkDataLoaded,
checkoutLinkDataError,
products,
admin,
};
};

export enum SubscriptionProducts {
SUPPORT = "QW8L3WPMiNjQjI",
MEDIAPACKAGE = 'QW8MpIBHxieKXd',
AZUREAPIS = 'premium',
GOOGLEAPIS = 'enterprise',
AWSAPIS = 'enterprise-global',
PRIVATECLOUD = 'private-cloud',
MATRIXCLOUD = 'matrix-cloud',
AGORATOKENSERVER = 'agora-tokenserver',
SIGNALSERVER = 'signal-server',
DATABASE = 'database',
STORAGE = 'storage',
IOSAPP = 'ios-app',
ANDROIDAPP = 'android-app',
AUDITLOG = 'audit-log',
APPLOG = 'app-log',
ENVIRONMENTS = 'environments',
GITREPOS = 'git-repos',
}

export const CheckSubscriptions = () => {
const [subscriptions, setSubscriptions] = useState<Subscription[]>([]);
const [subscriptionDataLoaded, setSubscriptionDataLoaded] = useState<boolean>(false);
const [subscriptionDataError, setSubscriptionDataError] = useState<boolean>(false);
const [loading, setLoading] = useState<boolean>(true);

const user = useSelector(getUser);
const currentUser = useSelector(getCurrentUser);
const deploymentId = useSelector(getDeploymentId);
const orgID = user.currentOrgId;
const domain = window.location.protocol + "//" + window.location.hostname + (window.location.port ? ':' + window.location.port : '');

const subscriptionSearchCustomer: LowcoderSearchCustomer = {
hostname: domain,
hostId: deploymentId,
email: currentUser.email,
orgId: orgID,
userId: user.id,
};

useEffect(() => {
const fetchCustomerAndSubscriptions = async () => {
try {
const subs = await searchCustomersSubscriptions(subscriptionSearchCustomer);
setSubscriptions(subs);
setSubscriptionDataLoaded(true);
} catch (error) {
setSubscriptionDataError(true);
} finally {
setLoading(false);
}
};
if (
Boolean(currentUser.email)
&& Boolean(orgID)
&& Boolean(user.id)
&& Boolean(deploymentId)
)
fetchCustomerAndSubscriptions();
}, [subscriptionSearchCustomer]);

return {
subscriptions,
subscriptionDataLoaded,
subscriptionDataError,
loading,
};
};

export default SubscriptionApi;
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,8 @@ export const ReduxActionErrorTypes = {
CREATE_APP_SNAPSHOT_ERROR: "CREATE_APP_SNAPSHOT_ERROR",
FETCH_APP_SNAPSHOTS_ERROR: "FETCH_APP_SNAPSHOTS_ERROR",
FETCH_APP_SNAPSHOT_DSL_ERROR: "FETCH_APP_SNAPSHOT_DSL_ERROR",

FETCH_DATASOURCE_ERROR: "FETCH_DATASOURCE_ERROR",
};

export type ReduxActionType = typeof ReduxActionTypes[keyof typeof ReduxActionTypes];
Expand Down
Loading
Loading