Merge pull request #226 from sidgaikwad/Fixes/adding-new-deep-link-format-stripe
Fixes/adding new deep link format stripe
This commit is contained in:
@@ -2,13 +2,12 @@ import { AppEnv, ErrCode } from "@autumn/shared";
|
||||
import Stripe from "stripe";
|
||||
import { z } from "zod";
|
||||
import { ensureStripeProductsWithEnv } from "@/external/stripe/stripeEnsureUtils.js";
|
||||
|
||||
import {
|
||||
checkKeyValid,
|
||||
createWebhookEndpoint,
|
||||
checkKeyValid, createWebhookEndpoint,
|
||||
} from "@/external/stripe/stripeOnboardingUtils.js";
|
||||
import { createStripeCli } from "@/external/stripe/utils.js";
|
||||
import { encryptData } from "@/utils/encryptUtils.js";
|
||||
import RecaseError from "@/utils/errorUtils.js";
|
||||
import RecaseError, { handleRequestError } from "@/utils/errorUtils.js";
|
||||
import { nullish } from "@/utils/genUtils.js";
|
||||
import { routeHandler } from "@/utils/routerUtils.js";
|
||||
import { OrgService } from "../OrgService.js";
|
||||
@@ -287,3 +286,17 @@ export const handleConnectStripe = async (req: any, res: any) =>
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const handleGetStripe = async (req: any, res: any) => {
|
||||
try {
|
||||
const org = await OrgService.getFromReq(req);
|
||||
|
||||
const stripeCli = createStripeCli({ org, env: req.env });
|
||||
|
||||
const account_details = await stripeCli.accounts.retrieve();
|
||||
|
||||
res.status(200).json(account_details);
|
||||
} catch (error) {
|
||||
handleRequestError({ req, error, res, action: "Get invoice" });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -10,7 +10,10 @@ import { createOrgResponse } from "./orgUtils.js";
|
||||
import { handleGetUploadUrl } from "./handlers/handleGetUploadUrl.js";
|
||||
import { handleDeleteOrg } from "./handlers/handleDeleteOrg.js";
|
||||
import { handleGetInvites } from "./handlers/handleGetInvites.js";
|
||||
import { handleConnectStripe } from "./handlers/handleConnectStripe.js";
|
||||
import {
|
||||
handleConnectStripe,
|
||||
handleGetStripe,
|
||||
} from "./handlers/handleConnectStripe.js";
|
||||
import { handleDeleteStripe } from "./handlers/handleDeleteStripe.js";
|
||||
import { handleGetOrg } from "./handlers/handleGetOrg.js";
|
||||
|
||||
@@ -29,6 +32,8 @@ orgRouter.delete("/delete-user", async (req: any, res) => {
|
||||
|
||||
orgRouter.get("", handleGetOrg);
|
||||
|
||||
orgRouter.get("/stripe", handleGetStripe);
|
||||
|
||||
orgRouter.post("/stripe", handleConnectStripe);
|
||||
|
||||
orgRouter.delete("/stripe", handleDeleteStripe);
|
||||
|
||||
@@ -1,24 +1,36 @@
|
||||
import { AppEnv } from "@autumn/shared";
|
||||
|
||||
export const getStripeCusLink = (customerId: string, env: AppEnv) => {
|
||||
return `https://dashboard.stripe.com${
|
||||
env == AppEnv.Live ? "" : "/test"
|
||||
}/customers/${customerId}`;
|
||||
export const getStripeCusLink = (
|
||||
customerId: string,
|
||||
env: AppEnv,
|
||||
accountId?: string
|
||||
) => {
|
||||
return `https://dashboard.stripe.com${
|
||||
env == AppEnv.Live ? "" : "/test"
|
||||
}/${accountId}/customers/${customerId}`;
|
||||
};
|
||||
|
||||
export const getStripeSubLink = (subscriptionId: string, env: AppEnv) => {
|
||||
return `https://dashboard.stripe.com${
|
||||
env == AppEnv.Live ? "" : "/test"
|
||||
}/subscriptions/${subscriptionId}`;
|
||||
export const getStripeSubLink = (
|
||||
subscriptionId: string,
|
||||
env: AppEnv,
|
||||
accountId?: string
|
||||
) => {
|
||||
return `https://dashboard.stripe.com${
|
||||
env == AppEnv.Live ? "" : "/test"
|
||||
}/${accountId}/subscriptions/${subscriptionId}`;
|
||||
};
|
||||
export const getStripeSubScheduleLink = (scheduledId: string, env: AppEnv) => {
|
||||
return `https://dashboard.stripe.com${
|
||||
env == AppEnv.Live ? "" : "/test"
|
||||
}/subscription_schedules/${scheduledId}`;
|
||||
export const getStripeSubScheduleLink = (
|
||||
scheduledId: string,
|
||||
env: AppEnv,
|
||||
accountId?: string
|
||||
) => {
|
||||
return `https://dashboard.stripe.com${
|
||||
env == AppEnv.Live ? "" : "/test"
|
||||
}/${accountId}/subscription_schedules/${scheduledId}`;
|
||||
};
|
||||
|
||||
export const getStripeInvoiceLink = (stripeInvoice: any) => {
|
||||
return `https://dashboard.stripe.com${
|
||||
stripeInvoice.livemode ? "" : "/test"
|
||||
}/invoices/${stripeInvoice.id || stripeInvoice.stripe_id}`;
|
||||
return `https://dashboard.stripe.com${
|
||||
stripeInvoice.livemode ? "" : "/test"
|
||||
}/invoices/${stripeInvoice.id || stripeInvoice.stripe_id}`;
|
||||
};
|
||||
|
||||
@@ -10,119 +10,158 @@ import { Link } from "react-router";
|
||||
import { useEnv } from "@/utils/envUtils";
|
||||
import { SidebarLabel } from "@/components/general/sidebar/sidebar-label";
|
||||
import { useCusQuery } from "../../hooks/useCusQuery";
|
||||
import { useOrg } from "@/hooks/common/useOrg";
|
||||
import Stripe from "stripe";
|
||||
import { useAxiosInstance } from "@/services/useAxiosInstance";
|
||||
import { toast } from "sonner";
|
||||
|
||||
export const CustomerDetails = ({
|
||||
setIsModalOpen,
|
||||
setModalType,
|
||||
setIsModalOpen,
|
||||
setModalType,
|
||||
}: {
|
||||
setIsModalOpen: (isModalOpen: boolean) => void;
|
||||
setModalType: (modalType: string) => void;
|
||||
setIsModalOpen: (isModalOpen: boolean) => void;
|
||||
setModalType: (modalType: string) => void;
|
||||
}) => {
|
||||
const { customer } = useCusQuery();
|
||||
const env = useEnv();
|
||||
const { customer } = useCusQuery();
|
||||
const env = useEnv();
|
||||
const { org } = useOrg();
|
||||
|
||||
return (
|
||||
<div className="flex w-full border-b mt-[2.5px] p-4">
|
||||
<SideAccordion title="Details" value="details">
|
||||
<div className="grid grid-cols-8 auto-rows-[16px] gap-y-4 w-full items-center">
|
||||
<SidebarLabel>ID</SidebarLabel>
|
||||
<div className="col-span-6 justify-end flex">
|
||||
<div className="w-full flex justify-end">
|
||||
{customer.id ? (
|
||||
<CopyButton text={customer.id} className="">
|
||||
{customer.id}
|
||||
</CopyButton>
|
||||
) : (
|
||||
<Button
|
||||
variant="sidebarItem"
|
||||
onClick={() => {
|
||||
setIsModalOpen(true);
|
||||
setModalType("customer");
|
||||
}}
|
||||
>
|
||||
<span className="truncate text-t3">N/A</span>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
const axiosInstance = useAxiosInstance();
|
||||
|
||||
<span className="text-t3 text-xs font-medium col-span-2">Name</span>
|
||||
<div className="col-span-6 justify-end flex">
|
||||
<Button
|
||||
variant="sidebarItem"
|
||||
onClick={() => {
|
||||
setIsModalOpen(true);
|
||||
setModalType("customer");
|
||||
}}
|
||||
>
|
||||
<span className="truncate">
|
||||
{customer.name || <span className="text-t3">None</span>}
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
const getStripeAccountInfo = async () => {
|
||||
try {
|
||||
const { data } = await axiosInstance.get(`/organization/stripe`);
|
||||
return data;
|
||||
} catch (error) {
|
||||
toast.error("Failed to get invoice URL");
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
<span className="text-t3 text-xs font-medium col-span-2">Email</span>
|
||||
<div className="col-span-6 justify-end flex">
|
||||
<Button
|
||||
variant="sidebarItem"
|
||||
onClick={() => {
|
||||
setIsModalOpen(true);
|
||||
setModalType("customer");
|
||||
}}
|
||||
>
|
||||
<span className="truncate">
|
||||
{customer.email || <span className="text-t3">None</span>}
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
return (
|
||||
<div className="flex w-full border-b mt-[2.5px] p-4">
|
||||
<SideAccordion title="Details" value="details">
|
||||
<div className="grid grid-cols-8 auto-rows-[16px] gap-y-4 w-full items-center">
|
||||
<SidebarLabel>ID</SidebarLabel>
|
||||
<div className="col-span-6 justify-end flex">
|
||||
<div className="w-full flex justify-end">
|
||||
{customer.id ? (
|
||||
<CopyButton text={customer.id} className="">
|
||||
{customer.id}
|
||||
</CopyButton>
|
||||
) : (
|
||||
<Button
|
||||
variant="sidebarItem"
|
||||
onClick={() => {
|
||||
setIsModalOpen(true);
|
||||
setModalType("customer");
|
||||
}}
|
||||
>
|
||||
<span className="truncate text-t3">N/A</span>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span className="text-t3 text-xs font-medium col-span-2">
|
||||
Fingerprint
|
||||
</span>
|
||||
<div className="col-span-6 justify-end flex">
|
||||
<Button
|
||||
variant="sidebarItem"
|
||||
className="text-t2 px-2 h-fit py-0.5"
|
||||
onClick={() => {
|
||||
setIsModalOpen(true);
|
||||
setModalType("customer");
|
||||
}}
|
||||
>
|
||||
<span className="truncate">
|
||||
{customer.fingerprint || <span className="text-t3">None</span>}
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
<span className="text-t3 text-xs font-medium col-span-2">Name</span>
|
||||
<div className="col-span-6 justify-end flex">
|
||||
<Button
|
||||
variant="sidebarItem"
|
||||
onClick={() => {
|
||||
setIsModalOpen(true);
|
||||
setModalType("customer");
|
||||
}}
|
||||
>
|
||||
<span className="truncate">
|
||||
{customer.name || <span className="text-t3">None</span>}
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{customer.processor?.id && (
|
||||
<>
|
||||
<span className="text-t3 text-xs font-medium col-span-2 h-4">
|
||||
Stripe
|
||||
</span>
|
||||
<div className="col-span-6">
|
||||
<Link
|
||||
className="!cursor-pointer hover:underline"
|
||||
to={getStripeCusLink(customer.processor?.id, env)}
|
||||
target="_blank"
|
||||
>
|
||||
<div className="flex items-center gap-2 justify-end">
|
||||
<Button
|
||||
variant="sidebarItem"
|
||||
// className="bg-white border shadow-sm rounded-md gap-2 h-6 max-h-6 !py-0"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faStripe}
|
||||
className="!h-6 !w-6 text-t2"
|
||||
/>
|
||||
<ArrowUpRightFromSquare size={12} className="text-t2" />
|
||||
</Button>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</SideAccordion>
|
||||
</div>
|
||||
);
|
||||
<span className="text-t3 text-xs font-medium col-span-2">Email</span>
|
||||
<div className="col-span-6 justify-end flex">
|
||||
<Button
|
||||
variant="sidebarItem"
|
||||
onClick={() => {
|
||||
setIsModalOpen(true);
|
||||
setModalType("customer");
|
||||
}}
|
||||
>
|
||||
<span className="truncate">
|
||||
{customer.email || <span className="text-t3">None</span>}
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<span className="text-t3 text-xs font-medium col-span-2">
|
||||
Fingerprint
|
||||
</span>
|
||||
<div className="col-span-6 justify-end flex">
|
||||
<Button
|
||||
variant="sidebarItem"
|
||||
className="text-t2 px-2 h-fit py-0.5"
|
||||
onClick={() => {
|
||||
setIsModalOpen(true);
|
||||
setModalType("customer");
|
||||
}}
|
||||
>
|
||||
<span className="truncate">
|
||||
{customer.fingerprint || <span className="text-t3">None</span>}
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{customer.processor?.id && (
|
||||
<>
|
||||
<span className="text-t3 text-xs font-medium col-span-2 h-4">
|
||||
Stripe
|
||||
</span>
|
||||
<div className="col-span-6">
|
||||
<div className="!cursor-pointer hover:underline">
|
||||
<div className="flex items-center gap-2 justify-end">
|
||||
<Button
|
||||
variant="sidebarItem"
|
||||
className="!cursor-pointer hover:underline"
|
||||
onClick={async () => {
|
||||
try {
|
||||
const result = await getStripeAccountInfo();
|
||||
if (result) {
|
||||
console.log("result", result);
|
||||
window.open(
|
||||
getStripeCusLink(
|
||||
customer.processor?.id,
|
||||
env,
|
||||
result.id
|
||||
),
|
||||
"_blank"
|
||||
);
|
||||
} else {
|
||||
window.location.href = getStripeCusLink(
|
||||
customer.processor?.id,
|
||||
env
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(
|
||||
"Failed to get Stripe customer link:",
|
||||
err
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faStripe}
|
||||
className="!h-6 !w-6 text-t2"
|
||||
/>
|
||||
<ArrowUpRightFromSquare size={12} className="text-t2" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</SideAccordion>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,67 +1,98 @@
|
||||
import { useOrg } from "@/hooks/common/useOrg";
|
||||
import { useAxiosInstance } from "@/services/useAxiosInstance";
|
||||
import { useEnv } from "@/utils/envUtils";
|
||||
import { getStripeSubLink, getStripeSubScheduleLink } from "@/utils/linkUtils";
|
||||
import { CusProductStatus, FullCusProduct } from "@autumn/shared";
|
||||
import { ArrowUpRightFromSquare } from "lucide-react";
|
||||
import React from "react";
|
||||
import { Link } from "react-router";
|
||||
import { toast } from "sonner";
|
||||
|
||||
export const CusProductStripeLink = ({
|
||||
cusProduct,
|
||||
cusProduct,
|
||||
}: {
|
||||
cusProduct: FullCusProduct;
|
||||
cusProduct: FullCusProduct;
|
||||
}) => {
|
||||
const env = useEnv();
|
||||
return (
|
||||
<>
|
||||
{cusProduct.subscription_ids &&
|
||||
cusProduct.subscription_ids.length > 0 && (
|
||||
<React.Fragment>
|
||||
{cusProduct.subscription_ids.map((subId: string) => {
|
||||
return (
|
||||
<Link
|
||||
key={subId}
|
||||
to={getStripeSubLink(subId, env)}
|
||||
target="_blank"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<div className="flex justify-center items-center w-fit px-2 gap-2 h-6">
|
||||
<ArrowUpRightFromSquare
|
||||
size={12}
|
||||
className="text-[#665CFF]"
|
||||
/>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</React.Fragment>
|
||||
)}
|
||||
{cusProduct.status == CusProductStatus.Scheduled &&
|
||||
cusProduct.scheduled_ids &&
|
||||
cusProduct.scheduled_ids.length > 0 && (
|
||||
<React.Fragment>
|
||||
{cusProduct.scheduled_ids.map((subId: string) => {
|
||||
return (
|
||||
<Link
|
||||
key={subId}
|
||||
to={getStripeSubScheduleLink(subId, env)}
|
||||
target="_blank"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<div className="flex justify-center items-center w-fit px-2 gap-2 h-6">
|
||||
<ArrowUpRightFromSquare
|
||||
size={12}
|
||||
className="text-[#665CFF]"
|
||||
/>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</React.Fragment>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
const env = useEnv();
|
||||
|
||||
const { org } = useOrg();
|
||||
|
||||
const axiosInstance = useAxiosInstance();
|
||||
|
||||
const getStripeAccountInfo = async () => {
|
||||
try {
|
||||
const { data } = await axiosInstance.get(`/organization/stripe`);
|
||||
return data;
|
||||
} catch (error) {
|
||||
toast.error("Failed to get invoice URL");
|
||||
return null;
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
{cusProduct.subscription_ids &&
|
||||
cusProduct.subscription_ids.length > 0 && (
|
||||
<React.Fragment>
|
||||
{cusProduct.subscription_ids.map((subId: string) => {
|
||||
return (
|
||||
<div
|
||||
key={subId}
|
||||
onClick={async (e) => {
|
||||
e.stopPropagation();
|
||||
const account = await getStripeAccountInfo();
|
||||
if (account) {
|
||||
window.open(
|
||||
getStripeSubLink(subId, env, account.id),
|
||||
"_blank"
|
||||
);
|
||||
} else {
|
||||
window.open(getStripeSubLink(subId, env));
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="flex justify-center items-center w-fit px-2 gap-2 h-6">
|
||||
<ArrowUpRightFromSquare
|
||||
size={12}
|
||||
className="text-[#665CFF]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</React.Fragment>
|
||||
)}
|
||||
{cusProduct.status == CusProductStatus.Scheduled &&
|
||||
cusProduct.scheduled_ids &&
|
||||
cusProduct.scheduled_ids.length > 0 && (
|
||||
<React.Fragment>
|
||||
{cusProduct.scheduled_ids.map((subId: string) => {
|
||||
return (
|
||||
<div
|
||||
key={subId}
|
||||
onClick={async (e) => {
|
||||
e.stopPropagation();
|
||||
const account = await getStripeAccountInfo();
|
||||
if (account) {
|
||||
window.open(
|
||||
getStripeSubScheduleLink(subId, env, account.id),
|
||||
"_blank"
|
||||
);
|
||||
} else {
|
||||
window.open(getStripeSubScheduleLink(subId, env));
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="flex justify-center items-center w-fit px-2 gap-2 h-6">
|
||||
<ArrowUpRightFromSquare
|
||||
size={12}
|
||||
className="text-[#665CFF]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</React.Fragment>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user