@blocklet/payment-react
Version:
Reusable react components for payment kit v2
579 lines (578 loc) • 21.4 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
module.exports = PaymentSummary;
var _jsxRuntime = require("react/jsx-runtime");
var _context = require("@arcblock/ux/lib/Locale/context");
var _iconsMaterial = require("@mui/icons-material");
var _material = require("@mui/material");
var _util = require("@ocap/util");
var _ahooks = require("ahooks");
var _noop = _interopRequireDefault(require("lodash/noop"));
var _useBus = _interopRequireDefault(require("use-bus"));
var _ExpandMore = _interopRequireDefault(require("@mui/icons-material/ExpandMore"));
var _styles = require("@mui/material/styles");
var _status = _interopRequireDefault(require("../components/status"));
var _api = _interopRequireDefault(require("../libs/api"));
var _util2 = require("../libs/util");
var _amount = _interopRequireDefault(require("./amount"));
var _productDonation = _interopRequireDefault(require("./product-donation"));
var _productItem = _interopRequireDefault(require("./product-item"));
var _livemode = _interopRequireDefault(require("../components/livemode"));
var _payment = require("../contexts/payment");
var _mobile = require("../hooks/mobile");
var _loadingButton = _interopRequireDefault(require("../components/loading-button"));
var _promotionCode = _interopRequireDefault(require("../components/promotion-code"));
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
const ExpandMore = (0, _styles.styled)(props => {
const {
expand,
...other
} = props;
return /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.IconButton, {
...other
});
})(({
theme,
expand
}) => ({
transform: !expand ? "rotate(0deg)" : "rotate(180deg)",
marginLeft: "auto",
transition: theme.transitions.create("transform", {
duration: theme.transitions.duration.shortest
})
}));
async function fetchCrossSell(id, skipError = true) {
try {
const {
data
} = await _api.default.get(`/api/checkout-sessions/${id}/cross-sell?skipError=${skipError}`);
if (!data.error) {
return data;
}
return null;
} catch (err) {
return null;
}
}
function getStakingSetup(items, currency, billingThreshold = 0) {
const staking = {
licensed: new _util.BN(0),
metered: new _util.BN(0)
};
const recurringItems = items.map(x => x.upsell_price || x.price).filter(x => x.type === "recurring" && x.recurring);
if (recurringItems.length > 0) {
if (+billingThreshold) {
return (0, _util.fromTokenToUnit)(billingThreshold, currency.decimal).toString();
}
items.forEach(x => {
const price = x.upsell_price || x.price;
const unit = (0, _util2.getPriceUintAmountByCurrency)(price, currency);
const amount = new _util.BN(unit).mul(new _util.BN(x.quantity));
if (price.type === "recurring" && price.recurring) {
if (price.recurring.usage_type === "licensed") {
staking.licensed = staking.licensed.add(amount);
}
if (price.recurring.usage_type === "metered") {
staking.metered = staking.metered.add(amount);
}
}
});
return staking.licensed.add(staking.metered).toString();
}
return "0";
}
function PaymentSummary({
items,
currency,
trialInDays,
billingThreshold,
onUpsell = _noop.default,
onDownsell = _noop.default,
onQuantityChange = _noop.default,
onApplyCrossSell = _noop.default,
onCancelCrossSell = _noop.default,
onChangeAmount = _noop.default,
checkoutSessionId = "",
crossSellBehavior = "",
showStaking = false,
donationSettings = void 0,
action = "",
trialEnd = 0,
completed = false,
checkoutSession = void 0,
paymentMethods = [],
onPromotionUpdate = _noop.default,
showFeatures = false,
...rest
}) {
const {
t,
locale
} = (0, _context.useLocaleContext)();
const {
isMobile
} = (0, _mobile.useMobile)();
const {
paymentState,
...settings
} = (0, _payment.usePaymentContext)();
const [state, setState] = (0, _ahooks.useSetState)({
loading: false,
shake: false,
expanded: items?.length < 3
});
const {
data,
runAsync
} = (0, _ahooks.useRequest)(skipError => checkoutSessionId ? fetchCrossSell(checkoutSessionId, skipError) : Promise.resolve(null));
const sessionDiscounts = checkoutSession?.discounts || [];
const allowPromotionCodes = !!checkoutSession?.allow_promotion_codes;
const hasDiscounts = sessionDiscounts?.length > 0;
const discountCurrency = paymentMethods && checkoutSession ? (0, _util2.findCurrency)(paymentMethods, hasDiscounts ? checkoutSession?.currency_id || currency.id : currency.id) || settings.settings?.baseCurrency : currency;
const headlines = (0, _util2.formatCheckoutHeadlines)(items, discountCurrency, {
trialEnd,
trialInDays
}, locale);
const staking = showStaking ? getStakingSetup(items, discountCurrency, billingThreshold) : "0";
const getAppliedPromotionCodes = () => {
if (!sessionDiscounts?.length) return [];
return sessionDiscounts.map(discount => ({
id: discount.promotion_code || discount.coupon,
code: discount.verification_data?.code || "APPLIED",
discount_amount: discount.discount_amount
}));
};
const handlePromotionUpdate = () => {
onPromotionUpdate?.();
};
const handleRemovePromotion = async sessionId => {
if (paymentState.paying || paymentState.stripePaying) {
return;
}
try {
await _api.default.delete(`/api/checkout-sessions/${sessionId}/remove-promotion`);
onPromotionUpdate?.();
} catch (err) {
console.error("Failed to remove promotion code:", err);
}
};
const discountAmount = new _util.BN(checkoutSession?.total_details?.amount_discount || "0");
const subtotalAmount = (0, _util.fromUnitToToken)(new _util.BN((0, _util.fromTokenToUnit)(headlines.actualAmount, discountCurrency?.decimal)).add(new _util.BN(staking)).toString(), discountCurrency?.decimal);
const totalAmount = (0, _util.fromUnitToToken)(new _util.BN((0, _util.fromTokenToUnit)(subtotalAmount, discountCurrency?.decimal)).sub(discountAmount).toString(), discountCurrency?.decimal);
(0, _useBus.default)("error.REQUIRE_CROSS_SELL", () => {
setState({
shake: true
});
setTimeout(() => {
setState({
shake: false
});
}, 1e3);
}, []);
const handleUpsell = async (from, to) => {
await onUpsell(from, to);
runAsync(false);
};
const handleQuantityChange = async (itemId, quantity) => {
await onQuantityChange(itemId, quantity);
runAsync(false);
};
const handleDownsell = async from => {
await onDownsell(from);
runAsync(false);
};
const handleApplyCrossSell = async () => {
if (data) {
try {
setState({
loading: true
});
await onApplyCrossSell(data.id);
} catch (err) {
console.error(err);
} finally {
setState({
loading: false
});
}
}
};
const handleCancelCrossSell = async () => {
try {
setState({
loading: true
});
await onCancelCrossSell();
} catch (err) {
console.error(err);
} finally {
setState({
loading: false
});
}
};
const hasSubTotal = +staking > 0 || allowPromotionCodes;
const ProductCardList = /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
className: "cko-product-list",
sx: {
flex: "0 1 auto",
overflow: "auto"
},
children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Stack, {
spacing: {
xs: 1,
sm: 2
},
children: items.map(x => x.price.custom_unit_amount && onChangeAmount && donationSettings ? /* @__PURE__ */(0, _jsxRuntime.jsx)(_productDonation.default, {
item: x,
settings: donationSettings,
onChange: onChangeAmount,
currency: discountCurrency
}, `${x.price_id}-${discountCurrency.id}`) : /* @__PURE__ */(0, _jsxRuntime.jsx)(_productItem.default, {
item: x,
items,
trialInDays,
trialEnd,
currency: discountCurrency,
onUpsell: handleUpsell,
onDownsell: handleDownsell,
adjustableQuantity: x.adjustable_quantity,
completed,
showFeatures,
onQuantityChange: handleQuantityChange,
children: x.cross_sell && /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
direction: "row",
sx: {
alignItems: "center",
justifyContent: "space-between",
width: 1
},
children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {}), /* @__PURE__ */(0, _jsxRuntime.jsx)(_loadingButton.default, {
size: "small",
loadingPosition: "end",
endIcon: null,
color: "error",
variant: "text",
loading: state.loading,
onClick: handleCancelCrossSell,
children: t("payment.checkout.cross_sell.remove")
})]
})
}, `${x.price_id}-${discountCurrency.id}`))
}), data && items.some(x => x.price_id === data.id) === false && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Grow, {
in: true,
children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Stack, {
sx: {
mt: 1
},
children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_productItem.default, {
item: {
quantity: 1,
price: data,
price_id: data.id,
cross_sell: true
},
items,
trialInDays,
currency: discountCurrency,
trialEnd,
onUpsell: _noop.default,
onDownsell: _noop.default,
children: /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
direction: "row",
sx: {
alignItems: "center",
justifyContent: "space-between",
width: 1
},
children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
children: crossSellBehavior === "required" && /* @__PURE__ */(0, _jsxRuntime.jsx)(_status.default, {
label: t("payment.checkout.required"),
color: "info",
variant: "outlined",
sx: {
mr: 1
}
})
}), /* @__PURE__ */(0, _jsxRuntime.jsx)(_loadingButton.default, {
size: "small",
loadingPosition: "end",
endIcon: null,
color: crossSellBehavior === "required" ? "info" : "info",
variant: crossSellBehavior === "required" ? "text" : "text",
loading: state.loading,
onClick: handleApplyCrossSell,
children: t("payment.checkout.cross_sell.add")
})]
})
})
})
})]
});
return /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Fade, {
in: true,
children: /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
className: "cko-product",
direction: "column",
...rest,
children: [/* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Box, {
sx: {
display: "flex",
alignItems: "center",
mb: 2.5
},
children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
title: t("payment.checkout.orderSummary"),
sx: {
color: "text.primary",
fontSize: {
xs: "18px",
md: "24px"
},
fontWeight: "700",
lineHeight: "32px"
},
children: action || t("payment.checkout.orderSummary")
}), !settings.livemode && /* @__PURE__ */(0, _jsxRuntime.jsx)(_livemode.default, {})]
}), isMobile && !donationSettings ? /* @__PURE__ */(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
children: [/* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
onClick: () => setState({
expanded: !state.expanded
}),
sx: {
justifyContent: "space-between",
flexDirection: "row",
alignItems: "center",
mb: 1.5
},
children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
children: t("payment.checkout.productListTotal", {
total: items.length
})
}), /* @__PURE__ */(0, _jsxRuntime.jsx)(ExpandMore, {
expand: state.expanded,
"aria-expanded": state.expanded,
"aria-label": "show more",
children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_ExpandMore.default, {})
})]
}), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Collapse, {
in: state.expanded || !isMobile,
timeout: "auto",
unmountOnExit: true,
children: ProductCardList
})]
}) : ProductCardList, /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Divider, {
sx: {
mt: 2.5,
mb: 2.5
}
}), +staking > 0 && /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
spacing: 1,
children: [/* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
direction: "row",
spacing: 1,
sx: {
justifyContent: "space-between",
alignItems: "center"
},
children: [/* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
direction: "row",
spacing: 0.5,
sx: {
alignItems: "center"
},
children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
sx: {
color: "text.secondary"
},
children: t("payment.checkout.paymentRequired")
}), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Tooltip, {
title: t("payment.checkout.stakingConfirm"),
placement: "top",
sx: {
maxWidth: "150px"
},
children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_iconsMaterial.HelpOutline, {
fontSize: "small",
sx: {
color: "text.lighter"
}
})
})]
}), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
children: headlines.amount
})]
}), /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
direction: "row",
spacing: 1,
sx: {
justifyContent: "space-between",
alignItems: "center"
},
children: [/* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
direction: "row",
spacing: 0.5,
sx: {
alignItems: "center"
},
children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
sx: {
color: "text.secondary"
},
children: t("payment.checkout.staking.title")
}), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Tooltip, {
title: t("payment.checkout.staking.tooltip"),
placement: "top",
sx: {
maxWidth: "150px"
},
children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_iconsMaterial.HelpOutline, {
fontSize: "small",
sx: {
color: "text.lighter"
}
})
})]
}), /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Typography, {
children: [(0, _util2.formatAmount)(staking, discountCurrency.decimal), " ", discountCurrency.symbol]
})]
})]
}), (allowPromotionCodes || hasDiscounts) && /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
direction: "row",
spacing: 1,
sx: {
justifyContent: "space-between",
alignItems: "center",
...(+staking > 0 && {
borderTop: "1px solid",
borderColor: "divider",
pt: 1,
mt: 1
})
},
children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
className: "base-label",
children: t("common.subtotal")
}), /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Typography, {
children: [(0, _util2.formatNumber)(subtotalAmount), " ", discountCurrency.symbol]
})]
}), allowPromotionCodes && !hasDiscounts && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, {
sx: {
mt: 1
},
children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_promotionCode.default, {
checkoutSessionId: checkoutSession.id,
initialAppliedCodes: getAppliedPromotionCodes(),
disabled: completed,
onUpdate: handlePromotionUpdate,
currencyId: currency.id
})
}), hasDiscounts && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, {
children: sessionDiscounts.map(discount => {
const promotionCodeInfo = discount.promotion_code_details;
const couponInfo = discount.coupon_details;
const discountDescription = couponInfo ? (0, _util2.formatCouponTerms)(couponInfo, discountCurrency, locale) : "";
const notSupported = discountDescription === t("payment.checkout.coupon.noDiscount");
return /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
children: [/* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
direction: "row",
spacing: 1,
sx: {
justifyContent: "space-between",
alignItems: "center"
},
children: [/* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
direction: "row",
spacing: 1,
sx: {
alignItems: "center",
backgroundColor: "grey.100",
width: "fit-content",
px: 1,
py: 1,
borderRadius: 1
},
children: [/* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Typography, {
sx: {
fontWeight: "medium",
display: "flex",
alignItems: "center",
gap: 0.5
},
children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_iconsMaterial.LocalOffer, {
sx: {
color: "warning.main",
fontSize: "small"
}
}), promotionCodeInfo?.code || discount.verification_data?.code || t("payment.checkout.discount")]
}), !completed && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Button, {
size: "small",
disabled: paymentState.paying || paymentState.stripePaying,
onClick: () => handleRemovePromotion(checkoutSessionId),
sx: {
minWidth: "auto",
width: 16,
height: 16,
color: "text.secondary",
"&.Mui-disabled": {
color: "text.disabled"
}
},
children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_iconsMaterial.Close, {
sx: {
fontSize: 14
}
})
})]
}), /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Typography, {
sx: {
color: "text.secondary"
},
children: ["-", (0, _util2.formatAmount)(discount.discount_amount || "0", discountCurrency.decimal), " ", discountCurrency.symbol]
})]
}), discountDescription && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
sx: {
fontSize: "small",
color: notSupported ? "error.main" : "text.secondary",
mt: 0.5
},
children: discountDescription
})]
}, discount.promotion_code || discount.coupon || `discount-${discount.discount_amount}`);
})
}), hasSubTotal && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Divider, {
sx: {
my: 1
}
}), /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
sx: {
display: "flex",
justifyContent: "space-between",
flexDirection: "row",
alignItems: "center",
width: "100%"
},
children: [/* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Box, {
className: "base-label",
children: [t("common.total"), " "]
}), /* @__PURE__ */(0, _jsxRuntime.jsx)(_amount.default, {
amount: `${totalAmount} ${discountCurrency.symbol}`,
sx: {
fontSize: "16px"
}
})]
}), headlines.then && headlines.showThen && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
component: "div",
sx: {
fontSize: "0.7875rem",
color: "text.lighter",
textAlign: "right",
margin: "-2px 0 8px"
},
children: headlines.then
})]
})
});
}