@blocklet/payment-react
Version:
Reusable react components for payment kit v2
783 lines (770 loc) • 23.8 kB
JavaScript
;
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;
}
}
`;