@blocklet/payment-react
Version:
Reusable react components for payment kit v2
964 lines (951 loc) • 31.6 kB
JavaScript
import { jsx, jsxs } from "react/jsx-runtime";
import { useLocaleContext } from "@arcblock/ux/lib/Locale/context";
import Toast from "@arcblock/ux/lib/Toast";
import Header from "@blocklet/ui-react/lib/Header";
import { ArrowBackOutlined } from "@mui/icons-material";
import { Box, Fade, Stack } from "@mui/material";
import { styled } from "@mui/system";
import { fromTokenToUnit } from "@ocap/util";
import { useSetState } from "ahooks";
import { useEffect, useMemo, useRef, useState } from "react";
import { FormProvider, useForm, useWatch } from "react-hook-form";
import trim from "lodash/trim";
import { usePaymentContext } from "../contexts/payment.js";
import api from "../libs/api.js";
import {
findCurrency,
formatError,
getQueryParams,
getStatementDescriptor,
isMobileSafari,
isValidCountry,
showStaking
} from "../libs/util.js";
import PaymentError from "./error.js";
import CheckoutFooter from "./footer.js";
import PaymentForm, { hasDidWallet } from "./form/index.js";
import OverviewSkeleton from "./skeleton/overview.js";
import PaymentSkeleton from "./skeleton/payment.js";
import PaymentSuccess from "./success.js";
import PaymentSummary from "./summary.js";
import { useMobile } from "../hooks/mobile.js";
import { formatPhone } from "../libs/phone-validator.js";
import { getCurrencyPreference } from "../libs/currency.js";
function PaymentInner({
checkoutSession,
paymentMethods,
paymentLink,
paymentIntent,
customer,
completed = false,
mode,
onPaid,
onError,
onChange,
action,
showCheckoutSummary = true,
quotes,
rateUnavailable,
rateError
}) {
const { t } = useLocaleContext();
const { settings, session } = usePaymentContext();
const { isMobile } = useMobile();
const [state, setState] = useSetState({
checkoutSession,
quotes,
rateUnavailable,
rateError,
paymentIntent,
liveRateInfo: void 0,
liveQuoteSnapshot: void 0,
liveRateUnavailable: false,
liveRateError: void 0,
isRateLoading: false
});
const isCurrencySwitchRef = useRef(false);
const prevCurrencyIdRef = useRef(null);
const query = getQueryParams(window.location.href);
const availableCurrencyIds = useMemo(() => {
const currencyIds = /* @__PURE__ */ new Set();
paymentMethods.forEach((method2) => {
method2.payment_currencies.forEach((currency2) => {
if (currency2.active) {
currencyIds.add(currency2.id);
}
});
});
return Array.from(currencyIds);
}, [paymentMethods]);
const defaultCurrencyId = useMemo(() => {
const hasAppliedDiscount = Boolean(state.checkoutSession?.discounts?.length);
if (hasAppliedDiscount && state.checkoutSession.currency_id && availableCurrencyIds.includes(state.checkoutSession.currency_id)) {
return state.checkoutSession.currency_id;
}
if (query.currencyId && availableCurrencyIds.includes(query.currencyId)) {
return query.currencyId;
}
if (session?.user && !hasDidWallet(session.user)) {
const stripeCurrencyId = paymentMethods.find((m) => m.type === "stripe")?.payment_currencies.find((c) => c.active)?.id;
if (stripeCurrencyId) {
return stripeCurrencyId;
}
}
const savedPreference = getCurrencyPreference(session?.user?.did, availableCurrencyIds);
if (savedPreference) {
return savedPreference;
}
if (state.checkoutSession.currency_id && availableCurrencyIds.includes(state.checkoutSession.currency_id)) {
return state.checkoutSession.currency_id;
}
return availableCurrencyIds?.[0];
}, [query.currencyId, availableCurrencyIds, session?.user, state.checkoutSession, paymentMethods]);
const defaultMethodId = paymentMethods.find((m) => m.payment_currencies.some((c) => c.id === defaultCurrencyId))?.id;
const hideSummaryCard = mode.endsWith("-minimal") || !showCheckoutSummary;
const methods = useForm({
defaultValues: {
customer_name: customer?.name || session?.user?.fullName || "",
customer_email: customer?.email || session?.user?.email || "",
customer_phone: formatPhone(customer?.phone || session?.user?.phone || ""),
payment_method: defaultMethodId,
payment_currency: defaultCurrencyId,
billing_address: Object.assign(
{
country: session?.user?.address?.country || "",
state: session?.user?.address?.province || "",
city: session?.user?.address?.city || "",
line1: session?.user?.address?.line1 || "",
line2: session?.user?.address?.line2 || "",
postal_code: session?.user?.address?.postalCode || ""
},
customer?.address || {},
{
country: isValidCountry(customer?.address?.country || session?.user?.address?.country || "") ? customer?.address?.country : "us"
}
)
}
});
useEffect(() => {
const hasAppliedDiscount = Boolean(state.checkoutSession?.discounts?.length);
const currentCurrency = methods.getValues("payment_currency");
const currentMethod = methods.getValues("payment_method");
if (defaultCurrencyId) {
if (!hasAppliedDiscount || !currentCurrency) {
methods.setValue("payment_currency", defaultCurrencyId);
}
}
if (defaultMethodId) {
if (!hasAppliedDiscount || !currentMethod) {
methods.setValue("payment_method", defaultMethodId);
}
}
}, [defaultCurrencyId, defaultMethodId, state.checkoutSession.discounts]);
useEffect(() => {
if (!isMobileSafari()) {
return () => {
};
}
let scrollTop = 0;
const focusinHandler = () => {
scrollTop = window.scrollY;
};
const focusoutHandler = () => {
window.scrollTo(0, scrollTop);
};
document.body.addEventListener("focusin", focusinHandler);
document.body.addEventListener("focusout", focusoutHandler);
return () => {
document.body.removeEventListener("focusin", focusinHandler);
document.body.removeEventListener("focusout", focusoutHandler);
};
}, []);
const currencyId = useWatch({ control: methods.control, name: "payment_currency", defaultValue: defaultCurrencyId });
const currency = findCurrency(paymentMethods, currencyId) || settings.baseCurrency;
const method = paymentMethods.find((x) => x.id === currency.payment_method_id);
const hasDynamicPricing = useMemo(
() => state.checkoutSession.line_items.some(
(item) => (item.upsell_price || item.price)?.pricing_type === "dynamic"
),
[state.checkoutSession.line_items]
);
const isStripePayment = method?.type === "stripe";
const needsExchangeRate = hasDynamicPricing && !isStripePayment;
const effectiveRateUnavailable = needsExchangeRate && (state.rateUnavailable || state.liveRateUnavailable);
if (state.liveRateError || state.rateError) {
console.error("[Rate Error]", { liveRateError: state.liveRateError, rateError: state.rateError });
}
const refreshRateRef = useRef(null);
useEffect(() => {
if (!currencyId) {
return;
}
const isCurrencySwitch = prevCurrencyIdRef.current !== null && prevCurrencyIdRef.current !== currencyId;
prevCurrencyIdRef.current = currencyId;
if (isCurrencySwitch) {
isCurrencySwitchRef.current = true;
setState({ isRateLoading: true });
}
if (needsExchangeRate) {
setState({
liveRateInfo: void 0,
liveQuoteSnapshot: void 0,
liveRateUnavailable: false,
liveRateError: void 0
});
liveRateRefreshRef.current = false;
refreshRateRef.current?.();
} else {
setState({
liveRateInfo: void 0,
liveQuoteSnapshot: void 0,
liveRateUnavailable: false,
liveRateError: void 0
});
liveRateRefreshRef.current = false;
if (isCurrencySwitch && !state.checkoutSession?.discounts?.length) {
setState({ isRateLoading: false });
isCurrencySwitchRef.current = false;
}
}
}, [currencyId, needsExchangeRate]);
useEffect(() => {
if (!state.checkoutSession?.id || completed || !needsExchangeRate) {
return void 0;
}
let cancelled = false;
let consecutiveFailures = 0;
const baseInterval = 3e4;
const MAX_INTERVAL = 5 * 60 * 1e3;
const QUICK_RETRY_DELAY = 1e3;
const MAX_QUICK_RETRIES = 2;
let currentInterval = baseInterval;
let timer = null;
const scheduleNext = () => {
if (timer) {
clearInterval(timer);
}
timer = window.setInterval(() => {
fetchRate(false);
}, currentInterval);
};
const fetchRate = async (isManualRetry = false) => {
if (document.hidden && !isManualRetry) {
return;
}
if (typeof navigator !== "undefined" && !navigator.onLine) {
return;
}
if (liveRateRefreshRef.current) {
return;
}
liveRateRefreshRef.current = true;
let quickRetryCount = 0;
let lastError = null;
try {
while (quickRetryCount <= MAX_QUICK_RETRIES) {
try {
const { data } = await api.get(`/api/checkout-sessions/${state.checkoutSession.id}/exchange-rate`, {
params: currencyId ? { currency_id: currencyId } : void 0
});
if (cancelled) {
return;
}
consecutiveFailures = 0;
currentInterval = baseInterval;
setState({
liveRateInfo: data,
liveQuoteSnapshot: void 0,
// Quote is created at submit time only
liveRateUnavailable: false,
liveRateError: void 0
});
if (isCurrencySwitchRef.current && !state.checkoutSession?.discounts?.length) {
setState({ isRateLoading: false });
isCurrencySwitchRef.current = false;
}
scheduleNext();
return;
} catch (err) {
lastError = err;
quickRetryCount++;
if (quickRetryCount <= MAX_QUICK_RETRIES && !cancelled) {
console.log(
`[Exchange Rate] Quick retry ${quickRetryCount}/${MAX_QUICK_RETRIES} after ${QUICK_RETRY_DELAY}ms`
);
await new Promise((resolve) => {
setTimeout(resolve, QUICK_RETRY_DELAY);
});
}
}
}
if (cancelled) {
return;
}
consecutiveFailures++;
const technicalError = lastError?.response?.data?.error || formatError(lastError);
console.error("[Exchange Rate Fetch Error]", {
error: technicalError,
consecutiveFailures,
sessionId: state.checkoutSession?.id,
currencyId
});
setState({
liveRateUnavailable: true,
liveRateError: void 0
});
if (consecutiveFailures >= 3) {
console.warn("Exchange rate fetch failed multiple times", { consecutiveFailures, technicalError });
}
const nextInterval = Math.min(baseInterval * 2 ** (consecutiveFailures - 1), MAX_INTERVAL);
currentInterval = nextInterval;
scheduleNext();
} finally {
liveRateRefreshRef.current = false;
}
};
refreshRateRef.current = async () => {
liveRateRefreshRef.current = false;
await fetchRate(true);
};
fetchRate(false);
const handleVisibilityChange = () => {
if (!document.hidden) {
fetchRate(false);
}
};
document.addEventListener("visibilitychange", handleVisibilityChange);
return () => {
cancelled = true;
refreshRateRef.current = null;
if (timer) {
clearInterval(timer);
}
document.removeEventListener("visibilitychange", handleVisibilityChange);
};
}, [state.checkoutSession?.id, currencyId, completed, needsExchangeRate]);
const recalculatePromotion = async () => {
if (state.checkoutSession?.discounts?.length) {
try {
await api.post(`/api/checkout-sessions/${state.checkoutSession.id}/recalculate-promotion`, {
currency_id: currencyId
});
await onPromotionUpdate();
} catch (err) {
console.error("[recalculatePromotion] Error:", err);
} finally {
if (isCurrencySwitchRef.current) {
setState({ isRateLoading: false });
isCurrencySwitchRef.current = false;
}
}
} else if (isCurrencySwitchRef.current) {
setState({ isRateLoading: false });
isCurrencySwitchRef.current = false;
}
};
useEffect(() => {
if (onChange) {
onChange(methods.getValues());
}
recalculatePromotion();
}, [currencyId]);
const onUpsell = async (from, to) => {
try {
const { data } = await api.put(`/api/checkout-sessions/${state.checkoutSession.id}/upsell`, { from, to });
if (data.discounts?.length) {
recalculatePromotion();
return;
}
setState({ checkoutSession: data });
} catch (err) {
console.error(err);
Toast.error(formatError(err));
}
};
const onDownsell = async (from) => {
try {
const { data } = await api.put(`/api/checkout-sessions/${state.checkoutSession.id}/downsell`, { from });
if (data.discounts?.length) {
recalculatePromotion();
return;
}
setState({ checkoutSession: data });
} catch (err) {
console.error(err);
Toast.error(formatError(err));
}
};
const onApplyCrossSell = async (to) => {
try {
const { data } = await api.put(`/api/checkout-sessions/${state.checkoutSession.id}/cross-sell`, { to });
if (data.discounts?.length) {
recalculatePromotion();
return;
}
setState({ checkoutSession: data });
} catch (err) {
console.error(err);
Toast.error(formatError(err));
}
};
const onQuantityChange = async (itemId, quantity) => {
try {
const { data } = await api.put(`/api/checkout-sessions/${state.checkoutSession.id}/adjust-quantity`, {
itemId,
quantity,
currency_id: currencyId
});
if (data.discounts?.length) {
recalculatePromotion();
return;
}
setState({
checkoutSession: data,
...data.rateUnavailable !== void 0 && { rateUnavailable: data.rateUnavailable },
...data.rateError !== void 0 && { rateError: data.rateError },
...data.quotes !== void 0 && { quotes: data.quotes }
});
} catch (err) {
console.error(err);
Toast.error(formatError(err));
}
};
const onCancelCrossSell = async () => {
try {
const { data } = await api.delete(`/api/checkout-sessions/${state.checkoutSession.id}/cross-sell`);
if (data.discounts?.length) {
recalculatePromotion();
return;
}
setState({ checkoutSession: data });
} catch (err) {
console.error(err);
Toast.error(formatError(err));
}
};
const onChangeAmount = async ({ priceId, amount }) => {
try {
const { data } = await api.put(`/api/checkout-sessions/${state.checkoutSession.id}/amount`, {
priceId,
amount: fromTokenToUnit(amount, currency.decimal).toString()
});
if (data.discounts?.length) {
recalculatePromotion();
return;
}
setState({ checkoutSession: data });
} catch (err) {
console.error(err);
Toast.error(formatError(err));
}
};
const onPromotionUpdate = async () => {
try {
const { data } = await api.get(`/api/checkout-sessions/retrieve/${state.checkoutSession.id}`);
setState({ checkoutSession: data.checkoutSession });
} catch (err) {
console.error(err);
Toast.error(formatError(err));
}
};
const handlePaid = (result) => {
setState({ checkoutSession: result.checkoutSession });
onPaid(result);
};
const handleQuoteUpdated = (payload) => {
setState({
checkoutSession: payload.checkoutSession,
...payload.quotes !== void 0 && { quotes: payload.quotes },
...payload.rateUnavailable !== void 0 && { rateUnavailable: payload.rateUnavailable },
...payload.rateError !== void 0 && { rateError: payload.rateError },
...payload.paymentIntent !== void 0 && { paymentIntent: payload.paymentIntent }
});
};
const handlePaymentIntentUpdate = (intent) => {
setState({ paymentIntent: intent });
};
const quoteRefreshRef = useRef(false);
const liveRateRefreshRef = useRef(false);
const handleQuoteExpired = async (forceRefresh = false) => {
if (quoteRefreshRef.current) {
return;
}
quoteRefreshRef.current = true;
try {
const { data } = await api.get(`/api/checkout-sessions/retrieve/${state.checkoutSession.id}`, {
params: forceRefresh ? { forceRefresh: "1" } : void 0
});
handleQuoteUpdated({
checkoutSession: data.checkoutSession,
quotes: data.quotes,
rateUnavailable: data.rateUnavailable,
rateError: data.rateError,
paymentIntent: data.paymentIntent
});
} catch (err) {
console.error(err);
Toast.error(formatError(err));
} finally {
quoteRefreshRef.current = false;
}
};
let trialInDays = Number(state.checkoutSession?.subscription_data?.trial_period_days || 0);
let trialEnd = Number(state.checkoutSession?.subscription_data?.trial_end || 0);
const trialCurrencyIds = (state.checkoutSession?.subscription_data?.trial_currency || "").split(",").map(trim).filter(Boolean);
if (trialCurrencyIds.length > 0 && trialCurrencyIds.includes(currencyId) === false) {
trialInDays = 0;
trialEnd = 0;
}
const showFeatures = `${paymentLink?.metadata?.show_product_features}` === "true";
return /* @__PURE__ */ jsx(FormProvider, { ...methods, children: /* @__PURE__ */ jsxs(Stack, { className: "cko-container", sx: { gap: { sm: mode === "standalone" ? 0 : mode === "inline" ? 4 : 8 } }, children: [
!hideSummaryCard && /* @__PURE__ */ jsx(Fade, { in: true, children: /* @__PURE__ */ jsxs(Stack, { className: "base-card cko-overview", direction: "column", children: [
/* @__PURE__ */ jsx(
PaymentSummary,
{
items: state.checkoutSession.line_items,
trialInDays,
trialEnd,
billingThreshold: Math.max(
state.checkoutSession.subscription_data?.billing_threshold_amount || 0,
// @ts-ignore
state.checkoutSession.subscription_data?.min_stake_amount || 0
),
showStaking: showStaking(method, currency, !!state.checkoutSession.subscription_data?.no_stake),
currency,
paymentIntent: state.paymentIntent || paymentIntent,
onUpsell,
onDownsell,
onQuantityChange,
onApplyCrossSell,
onCancelCrossSell,
onChangeAmount,
checkoutSessionId: state.checkoutSession.id,
crossSellBehavior: state.checkoutSession.cross_sell_behavior,
donationSettings: paymentLink?.donation_settings,
action,
completed,
checkoutSession: state.checkoutSession,
onPromotionUpdate,
paymentMethods,
showFeatures,
rateUnavailable: effectiveRateUnavailable,
isRateLoading: state.isRateLoading,
liveRate: state.liveRateInfo,
liveQuoteSnapshot: state.liveQuoteSnapshot,
onQuoteExpired: handleQuoteExpired,
onRefreshRate: refreshRateRef.current || void 0,
isStripePayment,
onSlippageChange: async (slippageConfig) => {
try {
const { data } = await api.put(`/api/checkout-sessions/${state.checkoutSession.id}/slippage`, {
slippage_config: slippageConfig
});
handleQuoteUpdated({
checkoutSession: data.checkoutSession || state.checkoutSession,
quotes: data.quotes,
rateUnavailable: data.rateUnavailable,
rateError: data.rateError
});
} catch (err) {
console.error("Failed to update slippage", err);
}
}
}
),
mode === "standalone" && !isMobile && /* @__PURE__ */ jsx(CheckoutFooter, { className: "cko-footer", sx: { color: "text.lighter" } })
] }) }),
/* @__PURE__ */ jsxs(
Stack,
{
className: "base-card cko-payment",
direction: "column",
spacing: {
xs: 2,
sm: 3
},
children: [
completed && /* @__PURE__ */ jsx(
PaymentSuccess,
{
mode,
pageInfo: state.checkoutSession.metadata?.page_info,
vendorCount: state.checkoutSession.line_items.reduce((total, item) => {
return total + (item.price?.product?.vendor_config?.length || 0);
}, 0),
sessionId: state.checkoutSession.id,
payee: getStatementDescriptor(state.checkoutSession.line_items),
action: state.checkoutSession.mode,
invoiceId: state.checkoutSession.invoice_id,
subscriptionId: state.checkoutSession.subscription_id,
subscriptions: state.checkoutSession.subscriptions,
message: paymentLink?.after_completion?.hosted_confirmation?.custom_message || t(
`payment.checkout.completed.${state.checkoutSession.submit_type === "donate" ? "donate" : state.checkoutSession.mode}`
)
}
),
!completed && /* @__PURE__ */ jsx(
PaymentForm,
{
currencyId,
checkoutSession: state.checkoutSession,
paymentMethods,
paymentIntent: state.paymentIntent || paymentIntent,
paymentLink,
customer,
onPaid: handlePaid,
onError,
onQuoteUpdated: handleQuoteUpdated,
onPaymentIntentUpdate: handlePaymentIntentUpdate,
mode,
action,
rateUnavailable: effectiveRateUnavailable
}
)
]
}
),
mode === "standalone" && isMobile && /* @__PURE__ */ jsx(CheckoutFooter, { className: "cko-footer", sx: { color: "text.lighter" } })
] }) });
}
export default function Payment({
checkoutSession,
paymentMethods,
paymentIntent,
paymentLink,
customer,
completed = false,
error = null,
mode,
onPaid,
onError,
onChange,
goBack,
action,
showCheckoutSummary = true,
quotes,
rateUnavailable,
rateError
}) {
const { t } = useLocaleContext();
const { refresh, livemode, setLivemode } = usePaymentContext();
const [delay, setDelay] = useState(false);
const { isMobile } = useMobile();
const hideSummaryCard = mode.endsWith("-minimal") || !showCheckoutSummary;
const isMobileSafariEnv = isMobileSafari();
useEffect(() => {
setTimeout(() => {
setDelay(true);
}, 500);
}, []);
useEffect(() => {
if (checkoutSession) {
if (livemode !== checkoutSession.livemode) {
setLivemode(checkoutSession.livemode);
}
}
}, [checkoutSession, livemode, setLivemode, refresh]);
const renderContent = () => {
if (error) {
return /* @__PURE__ */ jsx(PaymentError, { mode, title: "Oops", description: formatError(error) });
}
if (!checkoutSession || !delay) {
return /* @__PURE__ */ jsxs(Stack, { className: "cko-container", sx: { gap: { sm: mode === "standalone" ? 0 : mode === "inline" ? 4 : 8 } }, children: [
!hideSummaryCard && /* @__PURE__ */ jsx(Stack, { className: "base-card cko-overview", children: /* @__PURE__ */ jsx(Box, { className: "cko-product", children: /* @__PURE__ */ jsx(OverviewSkeleton, {}) }) }),
/* @__PURE__ */ jsx(Stack, { className: "base-card cko-payment", children: /* @__PURE__ */ jsx(PaymentSkeleton, {}) })
] });
}
if (checkoutSession.expires_at <= Math.round(Date.now() / 1e3)) {
return /* @__PURE__ */ jsx(
PaymentError,
{
mode,
title: t("payment.checkout.expired.title"),
description: t("payment.checkout.expired.description")
}
);
}
if (!checkoutSession.line_items.length) {
return /* @__PURE__ */ jsx(
PaymentError,
{
mode,
title: t("payment.checkout.emptyItems.title"),
description: t("payment.checkout.emptyItems.description")
}
);
}
return /* @__PURE__ */ jsx(
PaymentInner,
{
checkoutSession,
paymentMethods,
paymentLink,
paymentIntent,
completed: completed || checkoutSession.status === "complete",
customer,
onPaid,
onError,
onChange,
goBack,
mode,
action,
showCheckoutSummary,
quotes,
rateUnavailable,
rateError
}
);
};
return /* @__PURE__ */ jsxs(
Stack,
{
sx: {
display: "flex",
flexDirection: "column",
height: mode === "standalone" ? "100vh" : "auto",
overflow: isMobileSafariEnv ? "visible" : "hidden"
},
children: [
mode === "standalone" ? /* @__PURE__ */ jsx(
Header,
{
meta: void 0,
addons: void 0,
sessionManagerProps: void 0,
homeLink: void 0,
theme: void 0,
hideNavMenu: void 0,
maxWidth: false,
sx: { borderBottom: "1px solid", borderColor: "divider" }
}
) : null,
/* @__PURE__ */ jsxs(
Root,
{
mode,
sx: {
flex: 1,
overflow: {
xs: isMobileSafariEnv ? "visible" : "auto",
md: "hidden"
},
...isMobile && mode === "standalone" ? {
".cko-payment-submit-btn": {
position: "fixed",
bottom: 20,
left: 0,
right: 0,
zIndex: 999,
backgroundColor: "background.paper",
padding: "10px",
textAlign: "center",
button: {
maxWidth: 542
}
},
".cko-footer": {
position: "fixed",
bottom: 0,
left: 0,
right: 0,
zIndex: 999,
backgroundColor: "background.paper",
marginBottom: 0
},
".cko-payment": {
paddingBottom: "100px"
}
} : {}
},
children: [
goBack && /* @__PURE__ */ jsx(
ArrowBackOutlined,
{
sx: { mr: 0.5, color: "text.secondary", alignSelf: "flex-start", margin: "16px 0", cursor: "pointer" },
onClick: goBack,
fontSize: "medium"
}
),
/* @__PURE__ */ jsx(Stack, { className: "cko-container", sx: { gap: { sm: mode === "standalone" ? 0 : mode === "inline" ? 4 : 8 } }, children: renderContent() })
]
}
)
]
}
);
}
export const Root = styled(Box)`
box-sizing: border-box;
display: flex;
flex-direction: column;
align-items: center;
overflow: hidden;
position: relative;
.cko-container {
overflow: hidden;
width: 100%;
display: flex;
flex-direction: row;
justify-content: center;
position: relative;
flex: 1;
padding: 1px;
}
.base-card {
border: none;
border-radius: 0;
padding: ${(props) => props.mode === "standalone" ? "100px 40px 20px" : "20px 0"};
box-shadow: none;
flex: 1;
max-width: 582px;
}
.cko-overview {
position: relative;
flex-direction: column;
display: ${(props) => props.mode.endsWith("-minimal") ? "none" : "flex"};
background: ${({ theme }) => theme.palette.background.default};
min-height: 'auto';
}
.cko-header {
left: 0;
margin-bottom: 0;
position: absolute;
right: 0;
top: 0;
transition:
background-color 0.15s ease,
box-shadow 0.15s ease-out;
}
.cko-product {
flex: 1;
overflow: hidden;
}
.cko-product-summary {
width: 100%;
}
.cko-ellipsis {
width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.cko-payment {
width: 502px;
padding-left: ${(props) => props.mode === "standalone" ? "40px" : "20px"};
position: relative;
&:before {
-webkit-animation-fill-mode: both;
content: '';
height: 100%;
position: absolute;
left: 0px;
top: 0px;
transform-origin: left center;
width: 8px;
box-shadow: -4px 0px 8px 0px rgba(2, 7, 19, 0.04);
}
}
.cko-payment-contact {
overflow: hidden;
}
.cko-footer {
display: ${(props) => props.mode.endsWith("-minimal") ? "none" : "block"};
text-align: center;
margin-top: 20px;
}
.cko-payment-form {
.MuiFormLabel-root {
color: ${({ theme }) => theme.palette.grey.A700};
font-weight: 500;
margin-top: 12px;
margin-bottom: 4px;
}
.MuiBox-root {
margin: 0;
}
.MuiFormHelperText-root {
margin-left: 14px;
}
}
.cko-payment-methods {
}
.cko-payment-submit {
.MuiButtonBase-root {
font-size: 1.3rem;
position: relative;
}
.cko-submit-progress {
position: absolute;
top: 0;
width: 100%;
height: 100%;
opacity: 0.3;
}
}
.cko-header {
}
.product-item {
border-radius: ${({ theme }) => `${2 * theme.shape.borderRadius}px`};
border: 1px solid;
border-color: ${({ theme }) => theme.palette.divider};
.product-item-content {
padding: 16px;
background: ${({ theme }) => theme.palette.grey[50]};
border-top-left-radius: ${({ theme }) => `${2 * theme.shape.borderRadius}px`};
border-top-right-radius: ${({ theme }) => `${2 * theme.shape.borderRadius}px`};
}
.product-item-upsell {
margin-top: 0;
padding: 16px;
background: ${({ theme }) => theme.palette.grey[100]};
border-bottom-left-radius: ${({ theme }) => `${2 * theme.shape.borderRadius}px`};
border-bottom-right-radius: ${({ theme }) => `${2 * theme.shape.borderRadius}px`};
}
.product-item-content:only-child {
border-radius: ${({ theme }) => `${2 * theme.shape.borderRadius}px`};
}
}
@media (max-width: ${({ theme }) => theme.breakpoints.values.md}px) {
padding-top: 0;
overflow: auto;
&:before {
display: none;
}
.cko-container {
flex-direction: column;
align-items: center;
justify-content: flex-start;
gap: 0;
overflow: visible;
min-width: 200px;
}
.cko-overview {
width: 100%;
min-height: auto;
flex: none;
}
.cko-payment {
width: 100%;
height: fit-content;
flex: none;
border-top: 1px solid;
border-color: ${({ theme }) => theme.palette.divider};
&:before {
display: none;
}
}
.cko-footer {
position: relative;
margin-bottom: 4px;
margin-top: 0;
bottom: 0;
left: 0;
transform: translateX(0);
}
.base-card {
box-shadow: none;
border-radius: 0;
padding: 20px;
}
}
`;