@blocklet/payment-react
Version:
Reusable react components for payment kit v2
498 lines (497 loc) • 16.7 kB
JavaScript
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
import { useState, useCallback } from "react";
import {
Box,
Typography,
Stack,
Button,
CircularProgress,
Card,
CardContent,
IconButton,
Tooltip,
Collapse
} from "@mui/material";
import { AddOutlined, CreditCard, SettingsOutlined, AccountBalanceWalletOutlined } from "@mui/icons-material";
import { useLocaleContext } from "@arcblock/ux/lib/Locale/context";
import { useNavigate } from "react-router-dom";
import { joinURL } from "ufo";
import { useRequest } from "ahooks";
import { getPrefix, formatBNStr, formatNumber, formatPrice } from "../../libs/util.js";
import { createLink, handleNavigation } from "../../libs/navigation.js";
import { usePaymentContext } from "../../contexts/payment.js";
import api from "../../libs/api.js";
import AutoTopupModal from "./modal.js";
import { useMobile } from "../../hooks/mobile.js";
const fetchConfig = async (customerId, currencyId) => {
const { data } = await api.get(`/api/auto-recharge-configs/customer/${customerId}`, {
params: { currency_id: currencyId }
});
return data;
};
const fetchCurrencyBalance = async (currencyId, payerAddress) => {
const { data } = await api.get("/api/customers/payer-token", {
params: { currencyId, payerAddress }
});
return data;
};
const cardStyle = {
height: "100%",
width: "100%",
border: "1px solid",
borderColor: "divider",
boxShadow: 1,
borderRadius: 1,
backgroundColor: "background.default"
};
export default function AutoTopupCard({
currencyId,
onConfigChange = () => {
},
sx = {},
mode = "default",
children = void 0
}) {
const { t } = useLocaleContext();
const navigate = useNavigate();
const { session } = usePaymentContext();
const [modalOpen, setModalOpen] = useState(false);
const [paymentData, setPaymentData] = useState(null);
const [quickSetupMode, setQuickSetupMode] = useState(false);
const [expanded, setExpanded] = useState(mode === "default");
const customerId = session?.user?.did || "";
const { isMobile } = useMobile();
const {
data: config,
loading,
refresh
} = useRequest(() => fetchConfig(customerId, currencyId), {
refreshDeps: [customerId, currencyId],
ready: !!customerId && !!currencyId,
onSuccess: (data) => {
loadPaymentInfo(data);
}
});
const loadPaymentInfo = useCallback(async (data) => {
if (!data?.recharge_currency_id) return;
try {
const paymentMethodType = data?.paymentMethod?.type;
const paymentInfo = data?.payment_settings?.payment_method_options?.[paymentMethodType];
const balanceInfo = paymentInfo?.payer && paymentMethodType !== "stripe" ? await fetchCurrencyBalance(data.recharge_currency_id, paymentInfo.payer) : null;
setPaymentData({
paymentInfo,
balanceInfo
});
} catch (error) {
console.error("Failed to load payment info:", error);
}
}, []);
const handleRecharge = (e) => {
if (!paymentData?.paymentInfo?.payer) return;
const url = joinURL(
getPrefix(),
`/customer/recharge/${config?.recharge_currency_id}?rechargeAddress=${paymentData.paymentInfo.payer}`
);
const link = createLink(url, true);
handleNavigation(e, link, navigate);
};
const handleConfigSuccess = (newConfig) => {
refresh();
onConfigChange?.(newConfig);
setModalOpen(false);
setQuickSetupMode(false);
};
const handleToggleExpanded = () => {
setExpanded(!expanded);
};
if (loading) {
return /* @__PURE__ */ jsx(Card, { sx: { ...cardStyle, ...sx }, children: /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsx(Box, { sx: { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 80 }, children: /* @__PURE__ */ jsx(CircularProgress, { size: 24 }) }) }) });
}
if (!config) {
return null;
}
const renderPurchaseDetails = () => {
const { paymentInfo, balanceInfo } = paymentData || {};
if (!paymentInfo) {
return /* @__PURE__ */ jsx(
Typography,
{
variant: "body2",
sx: {
color: "text.secondary"
},
children: t("payment.autoTopup.notConfigured")
}
);
}
const purchaseAmount = formatPrice(
config.price,
config.rechargeCurrency,
config.price.product?.unit_label,
config.quantity,
true
);
if (config?.paymentMethod?.type === "stripe") {
const cardBrand = (paymentInfo?.card_brand || "Card").charAt(0).toUpperCase() + (paymentInfo?.card_brand || "Card").slice(1).toLowerCase();
const last4 = paymentInfo?.card_last4;
return /* @__PURE__ */ jsxs(Stack, { spacing: 1, className: "auto-topup-method-info", children: [
/* @__PURE__ */ jsxs(
Box,
{
sx: {
display: "flex",
alignItems: "center",
gap: 0.5,
justifyContent: {
xs: "space-between",
sm: "flex-start"
}
},
children: [
/* @__PURE__ */ jsxs(
Typography,
{
variant: "body2",
sx: {
color: "text.secondary"
},
children: [
t("payment.autoTopup.purchaseAmount"),
"\uFF1A"
]
}
),
/* @__PURE__ */ jsx(Typography, { variant: "body2", sx: { fontWeight: 600, color: "text.primary" }, children: purchaseAmount })
]
}
),
/* @__PURE__ */ jsxs(
Box,
{
sx: {
display: "flex",
alignItems: "center",
gap: 0.5,
justifyContent: {
xs: "space-between",
sm: "flex-start"
}
},
children: [
/* @__PURE__ */ jsxs(
Typography,
{
variant: "body2",
sx: {
color: "text.secondary"
},
children: [
t("payment.autoTopup.paymentMethod"),
"\uFF1A"
]
}
),
/* @__PURE__ */ jsxs(
Stack,
{
direction: "row",
spacing: 1,
sx: {
alignItems: "center"
},
children: [
/* @__PURE__ */ jsx(CreditCard, { fontSize: "small", sx: { color: "text.secondary" } }),
/* @__PURE__ */ jsxs(Typography, { variant: "body2", sx: { color: "text.primary", fontWeight: 500 }, children: [
cardBrand,
"(",
last4,
")"
] })
]
}
)
]
}
)
] });
}
return /* @__PURE__ */ jsxs(Stack, { spacing: 1, className: "auto-topup-method-info", children: [
/* @__PURE__ */ jsxs(
Box,
{
sx: {
display: "flex",
alignItems: "center",
gap: 0.5,
justifyContent: {
xs: "space-between",
sm: "flex-start"
}
},
children: [
/* @__PURE__ */ jsxs(
Typography,
{
variant: "body2",
sx: {
color: "text.secondary"
},
children: [
t("payment.autoTopup.purchaseAmount"),
"\uFF1A"
]
}
),
/* @__PURE__ */ jsx(Typography, { variant: "body2", sx: { fontWeight: 600, color: "text.primary" }, children: purchaseAmount })
]
}
),
/* @__PURE__ */ jsxs(
Box,
{
sx: {
display: "flex",
alignItems: "center",
justifyContent: {
xs: "space-between",
sm: "flex-start"
}
},
children: [
/* @__PURE__ */ jsxs(
Typography,
{
variant: "body2",
sx: {
color: "text.secondary",
whiteSpace: "nowrap"
},
children: [
t("payment.autoTopup.walletBalance"),
"\uFF1A"
]
}
),
/* @__PURE__ */ jsxs(Stack, { direction: "row", spacing: 1, sx: { alignItems: "center" }, children: [
/* @__PURE__ */ jsx(
Tooltip,
{
title: paymentInfo?.payer ? `${t("payment.autoTopup.paymentAddress")}: ${paymentInfo.payer}` : "",
placement: "top",
children: /* @__PURE__ */ jsxs(Box, { sx: { display: "flex", alignItems: "center", gap: 0.5 }, children: [
/* @__PURE__ */ jsx(AccountBalanceWalletOutlined, { sx: { fontSize: 16, color: "text.secondary" } }),
/* @__PURE__ */ jsx(Typography, { variant: "body2", sx: { fontWeight: 600, color: "text.primary" }, children: balanceInfo ? `${formatBNStr(balanceInfo?.token || "0", config?.rechargeCurrency?.decimal || 18)} ${config?.rechargeCurrency?.symbol || ""}` : "--" })
] })
}
),
balanceInfo && !isMobile && /* @__PURE__ */ jsxs(
Button,
{
size: "small",
variant: "text",
onClick: handleRecharge,
className: "auto-topup-add-funds-button",
sx: {
color: "primary.main",
display: "flex",
alignItems: "center"
},
children: [
/* @__PURE__ */ jsx(AddOutlined, { fontSize: "small" }),
t("payment.autoTopup.addFunds")
]
}
)
] })
]
}
),
balanceInfo && isMobile && /* @__PURE__ */ jsxs(
Button,
{
size: "small",
variant: "text",
onClick: handleRecharge,
className: "auto-topup-add-funds-button",
sx: {
color: "primary.main",
display: "flex",
alignItems: "center",
justifyContent: "flex-end"
},
children: [
/* @__PURE__ */ jsx(AddOutlined, { fontSize: "small" }),
t("payment.autoTopup.addFunds")
]
}
)
] });
};
const openModal = () => setModalOpen(true);
const renderInnerView = () => {
if (mode === "custom") {
return children && typeof children === "function" ? /* @__PURE__ */ jsx(Fragment, { children: children(openModal, config, paymentData, loading) }) : /* @__PURE__ */ jsxs(Typography, { children: [
"Please provide a valid render function",
/* @__PURE__ */ jsx("pre", { children: "(openModal, config, paymentData, loading) => ReactNode" })
] });
}
return /* @__PURE__ */ jsx(Card, { sx: { ...cardStyle, ...sx }, children: /* @__PURE__ */ jsxs(CardContent, { children: [
/* @__PURE__ */ jsxs(
Stack,
{
direction: "row",
className: "auto-topup-header",
sx: {
justifyContent: "space-between",
alignItems: "center",
borderBottom: "1px solid",
borderColor: "divider",
pb: 1.5
},
children: [
/* @__PURE__ */ jsx(Typography, { variant: "subtitle2", sx: { fontWeight: 600, color: "text.primary" }, children: t("payment.autoTopup.title") }),
/* @__PURE__ */ jsx(Box, { sx: { display: "flex", alignItems: "center", gap: 0.5 }, children: /* @__PURE__ */ jsx(
IconButton,
{
size: "small",
onClick: openModal,
sx: {
p: 0.5,
color: "text.secondary",
"&:hover": {
bgcolor: "grey.50",
color: "text.primary"
}
},
children: /* @__PURE__ */ jsx(SettingsOutlined, { fontSize: "small" })
}
) })
]
}
),
config?.enabled ? /* @__PURE__ */ jsxs(Stack, { spacing: 1.5, className: "auto-topup-content", sx: { pt: 1.5 }, children: [
(() => {
const threshold = `${formatNumber(config.threshold)} ${config.currency?.symbol || ""}`;
const credits = `${formatNumber(
Number(config.price.metadata?.credit_config?.credit_amount || 0) * Number(config.quantity)
)} ${config.currency?.name || ""}`;
return /* @__PURE__ */ jsxs(
Typography,
{
variant: "body2",
sx: {
color: "text.secondary"
},
children: [
t("payment.autoTopup.activeDescriptionWithCredits", { threshold, credits }),
mode === "simple" && /* @__PURE__ */ jsx(
Button,
{
component: "span",
size: "small",
variant: "text",
onClick: handleToggleExpanded,
sx: {
color: "primary.main",
minWidth: "auto",
ml: 1,
p: 0,
fontSize: "inherit",
textTransform: "none",
"&:hover": {
backgroundColor: "transparent",
textDecoration: "underline"
}
},
children: expanded ? t("payment.autoTopup.hideDetails") : t("payment.autoTopup.showDetails")
}
)
]
}
);
})(),
/* @__PURE__ */ jsx(Collapse, { in: mode === "default" || expanded, children: /* @__PURE__ */ jsx(
Box,
{
sx: {
bgcolor: "grey.50",
borderRadius: 1,
p: 1.5
},
children: renderPurchaseDetails()
}
) })
] }) : /* @__PURE__ */ jsx(
Stack,
{
className: "auto-topup-content",
sx: {
minHeight: 80,
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
pt: 1.5,
gap: 2
},
children: /* @__PURE__ */ jsxs(
Typography,
{
variant: "body2",
sx: {
color: "text.secondary",
textAlign: "left"
},
children: [
t("payment.autoTopup.inactiveDescription", {
name: config?.currency?.name
}),
/* @__PURE__ */ jsx(
Button,
{
component: "span",
variant: "text",
size: "small",
onClick: () => {
setQuickSetupMode(true);
setModalOpen(true);
},
sx: {
color: "primary.main",
minWidth: "auto",
ml: 1,
p: 0,
fontSize: "inherit",
textTransform: "none",
"&:hover": {
backgroundColor: "transparent",
textDecoration: "underline"
}
},
children: t("payment.autoTopup.setup")
}
)
]
}
)
}
)
] }) });
};
return /* @__PURE__ */ jsxs(Fragment, { children: [
renderInnerView(),
modalOpen && /* @__PURE__ */ jsx(
AutoTopupModal,
{
open: modalOpen,
onClose: () => {
setModalOpen(false);
setQuickSetupMode(false);
},
currencyId,
onSuccess: handleConfigSuccess,
defaultEnabled: quickSetupMode
}
)
] });
}