UNPKG

@blocklet/payment-react

Version:

Reusable react components for payment kit v2

783 lines (770 loc) 23.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Root = void 0; module.exports = Payment; var _jsxRuntime = require("react/jsx-runtime"); var _context = require("@arcblock/ux/lib/Locale/context"); var _Toast = _interopRequireDefault(require("@arcblock/ux/lib/Toast")); var _Header = _interopRequireDefault(require("@blocklet/ui-react/lib/Header")); var _iconsMaterial = require("@mui/icons-material"); var _material = require("@mui/material"); var _system = require("@mui/system"); var _util = require("@ocap/util"); var _ahooks = require("ahooks"); var _react = require("react"); var _reactHookForm = require("react-hook-form"); var _trim = _interopRequireDefault(require("lodash/trim")); var _payment = require("../contexts/payment"); var _api = _interopRequireDefault(require("../libs/api")); var _util2 = require("../libs/util"); var _error = _interopRequireDefault(require("./error")); var _footer = _interopRequireDefault(require("./footer")); var _form = _interopRequireWildcard(require("./form")); var _overview = _interopRequireDefault(require("./skeleton/overview")); var _payment2 = _interopRequireDefault(require("./skeleton/payment")); var _success = _interopRequireDefault(require("./success")); var _summary = _interopRequireDefault(require("./summary")); var _mobile = require("../hooks/mobile"); var _phoneValidator = require("../libs/phone-validator"); var _currency = require("../libs/currency"); function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } function PaymentInner({ checkoutSession, paymentMethods, paymentLink, paymentIntent, customer, completed = false, mode, onPaid, onError, onChange, action, showCheckoutSummary = true }) { const { t } = (0, _context.useLocaleContext)(); const { settings, session } = (0, _payment.usePaymentContext)(); const { isMobile } = (0, _mobile.useMobile)(); const [state, setState] = (0, _ahooks.useSetState)({ checkoutSession }); const query = (0, _util2.getQueryParams)(window.location.href); const availableCurrencyIds = (0, _react.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 = (0, _react.useMemo)(() => { if (query.currencyId && availableCurrencyIds.includes(query.currencyId)) { return query.currencyId; } if (session?.user && !(0, _form.hasDidWallet)(session.user)) { const stripeCurrencyId = paymentMethods.find(m => m.type === "stripe")?.payment_currencies.find(c => c.active)?.id; if (stripeCurrencyId) { return stripeCurrencyId; } } const savedPreference = (0, _currency.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.currency_id, paymentMethods]); const defaultMethodId = paymentMethods.find(m => m.payment_currencies.some(c => c.id === defaultCurrencyId))?.id; const hideSummaryCard = mode.endsWith("-minimal") || !showCheckoutSummary; const methods = (0, _reactHookForm.useForm)({ defaultValues: { customer_name: customer?.name || session?.user?.fullName || "", customer_email: customer?.email || session?.user?.email || "", customer_phone: (0, _phoneValidator.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: (0, _util2.isValidCountry)(customer?.address?.country || session?.user?.address?.country || "") ? customer?.address?.country : "us" }) } }); (0, _react.useEffect)(() => { if (defaultCurrencyId) { methods.setValue("payment_currency", defaultCurrencyId); } if (defaultMethodId) { methods.setValue("payment_method", defaultMethodId); } }, [defaultCurrencyId, defaultMethodId]); (0, _react.useEffect)(() => { if (!(0, _util2.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 = (0, _reactHookForm.useWatch)({ control: methods.control, name: "payment_currency", defaultValue: defaultCurrencyId }); const currency = (0, _util2.findCurrency)(paymentMethods, currencyId) || settings.baseCurrency; const method = paymentMethods.find(x => x.id === currency.payment_method_id); const recalculatePromotion = () => { if (state.checkoutSession?.discounts?.length) { _api.default.post(`/api/checkout-sessions/${state.checkoutSession.id}/recalculate-promotion`, { currency_id: currencyId }).then(() => { onPromotionUpdate(); }); } }; (0, _react.useEffect)(() => { if (onChange) { onChange(methods.getValues()); } recalculatePromotion(); }, [currencyId]); const onUpsell = async (from, to) => { try { const { data } = await _api.default.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.default.error((0, _util2.formatError)(err)); } }; const onDownsell = async from => { try { const { data } = await _api.default.put(`/api/checkout-sessions/${state.checkoutSession.id}/downsell`, { from }); if (data.discounts?.length) { recalculatePromotion(); return; } setState({ checkoutSession: data }); } catch (err) { console.error(err); _Toast.default.error((0, _util2.formatError)(err)); } }; const onApplyCrossSell = async to => { try { const { data } = await _api.default.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.default.error((0, _util2.formatError)(err)); } }; const onQuantityChange = async (itemId, quantity) => { try { const { data } = await _api.default.put(`/api/checkout-sessions/${state.checkoutSession.id}/adjust-quantity`, { itemId, quantity }); if (data.discounts?.length) { recalculatePromotion(); return; } setState({ checkoutSession: data }); } catch (err) { console.error(err); _Toast.default.error((0, _util2.formatError)(err)); } }; const onCancelCrossSell = async () => { try { const { data } = await _api.default.delete(`/api/checkout-sessions/${state.checkoutSession.id}/cross-sell`); if (data.discounts?.length) { recalculatePromotion(); return; } setState({ checkoutSession: data }); } catch (err) { console.error(err); _Toast.default.error((0, _util2.formatError)(err)); } }; const onChangeAmount = async ({ priceId, amount }) => { try { const { data } = await _api.default.put(`/api/checkout-sessions/${state.checkoutSession.id}/amount`, { priceId, amount: (0, _util.fromTokenToUnit)(amount, currency.decimal).toString() }); if (data.discounts?.length) { recalculatePromotion(); return; } setState({ checkoutSession: data }); } catch (err) { console.error(err); _Toast.default.error((0, _util2.formatError)(err)); } }; const onPromotionUpdate = async () => { try { const { data } = await _api.default.get(`/api/checkout-sessions/retrieve/${state.checkoutSession.id}`); setState({ checkoutSession: data.checkoutSession }); } catch (err) { console.error(err); _Toast.default.error((0, _util2.formatError)(err)); } }; const handlePaid = result => { setState({ checkoutSession: result.checkoutSession }); onPaid(result); }; 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.default).filter(Boolean); if (trialCurrencyIds.length > 0 && trialCurrencyIds.includes(currencyId) === false) { trialInDays = 0; trialEnd = 0; } const showFeatures = `${paymentLink?.metadata?.show_product_features}` === "true"; return /* @__PURE__ */(0, _jsxRuntime.jsx)(_reactHookForm.FormProvider, { ...methods, children: /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, { className: "cko-container", sx: { gap: { sm: mode === "standalone" ? 0 : mode === "inline" ? 4 : 8 } }, children: [!hideSummaryCard && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Fade, { in: true, children: /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, { className: "base-card cko-overview", direction: "column", children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_summary.default, { 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: (0, _util2.showStaking)(method, currency, !!state.checkoutSession.subscription_data?.no_stake), currency, 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 }), mode === "standalone" && !isMobile && /* @__PURE__ */(0, _jsxRuntime.jsx)(_footer.default, { className: "cko-footer", sx: { color: "text.lighter" } })] }) }), /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, { className: "base-card cko-payment", direction: "column", spacing: { xs: 2, sm: 3 }, children: [completed && /* @__PURE__ */(0, _jsxRuntime.jsx)(_success.default, { 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: (0, _util2.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__ */(0, _jsxRuntime.jsx)(_form.default, { currencyId, checkoutSession: state.checkoutSession, paymentMethods, paymentIntent, paymentLink, customer, onPaid: handlePaid, onError, mode, action })] }), mode === "standalone" && isMobile && /* @__PURE__ */(0, _jsxRuntime.jsx)(_footer.default, { className: "cko-footer", sx: { color: "text.lighter" } })] }) }); } function Payment({ checkoutSession, paymentMethods, paymentIntent, paymentLink, customer, completed = false, error = null, mode, onPaid, onError, onChange, goBack, action, showCheckoutSummary = true }) { const { t } = (0, _context.useLocaleContext)(); const { refresh, livemode, setLivemode } = (0, _payment.usePaymentContext)(); const [delay, setDelay] = (0, _react.useState)(false); const { isMobile } = (0, _mobile.useMobile)(); const hideSummaryCard = mode.endsWith("-minimal") || !showCheckoutSummary; const isMobileSafariEnv = (0, _util2.isMobileSafari)(); (0, _react.useEffect)(() => { setTimeout(() => { setDelay(true); }, 500); }, []); (0, _react.useEffect)(() => { if (checkoutSession) { if (livemode !== checkoutSession.livemode) { setLivemode(checkoutSession.livemode); } } }, [checkoutSession, livemode, setLivemode, refresh]); const renderContent = () => { if (error) { return /* @__PURE__ */(0, _jsxRuntime.jsx)(_error.default, { mode, title: "Oops", description: (0, _util2.formatError)(error) }); } if (!checkoutSession || !delay) { return /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, { className: "cko-container", sx: { gap: { sm: mode === "standalone" ? 0 : mode === "inline" ? 4 : 8 } }, children: [!hideSummaryCard && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Stack, { className: "base-card cko-overview", children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, { className: "cko-product", children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_overview.default, {}) }) }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Stack, { className: "base-card cko-payment", children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_payment2.default, {}) })] }); } if (checkoutSession.expires_at <= Math.round(Date.now() / 1e3)) { return /* @__PURE__ */(0, _jsxRuntime.jsx)(_error.default, { mode, title: t("payment.checkout.expired.title"), description: t("payment.checkout.expired.description") }); } if (!checkoutSession.line_items.length) { return /* @__PURE__ */(0, _jsxRuntime.jsx)(_error.default, { mode, title: t("payment.checkout.emptyItems.title"), description: t("payment.checkout.emptyItems.description") }); } return /* @__PURE__ */(0, _jsxRuntime.jsx)(PaymentInner, { checkoutSession, paymentMethods, paymentLink, paymentIntent, completed: completed || checkoutSession.status === "complete", customer, onPaid, onError, onChange, goBack, mode, action, showCheckoutSummary }); }; return /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, { sx: { display: "flex", flexDirection: "column", height: mode === "standalone" ? "100vh" : "auto", overflow: isMobileSafariEnv ? "visible" : "hidden" }, children: [mode === "standalone" ? /* @__PURE__ */(0, _jsxRuntime.jsx)(_Header.default, { 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__ */(0, _jsxRuntime.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__ */(0, _jsxRuntime.jsx)(_iconsMaterial.ArrowBackOutlined, { sx: { mr: 0.5, color: "text.secondary", alignSelf: "flex-start", margin: "16px 0", cursor: "pointer" }, onClick: goBack, fontSize: "medium" }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Stack, { className: "cko-container", sx: { gap: { sm: mode === "standalone" ? 0 : mode === "inline" ? 4 : 8 } }, children: renderContent() })] })] }); } const Root = exports.Root = (0, _system.styled)(_material.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; } } `;