UNPKG

@blocklet/payment-react

Version:

Reusable react components for payment kit v2

964 lines (951 loc) 31.6 kB
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; } } `;