UNPKG

@blocklet/payment-react

Version:

Reusable react components for payment kit v2

363 lines (362 loc) 10.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); module.exports = StripeCheckout; var _jsxRuntime = require("react/jsx-runtime"); var _Center = _interopRequireDefault(require("@arcblock/ux/lib/Center")); var _Dialog = _interopRequireDefault(require("@arcblock/ux/lib/Dialog")); var _context = require("@arcblock/ux/lib/Locale/context"); var _material = require("@mui/material"); var _system = require("@mui/system"); var _ahooks = require("ahooks"); var _react = require("react"); var _mobile = require("../../../hooks/mobile"); var _loadingButton = _interopRequireDefault(require("../../../components/loading-button")); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } const { Elements, PaymentElement, useElements, useStripe, loadStripe, LinkAuthenticationElement } = globalThis.__STRIPE_COMPONENTS__; const PaymentElementContainer = (0, _system.styled)("div")` width: 100%; opacity: 0; transition: opacity 300ms ease; &.visible { opacity: 1; } `; function StripeCheckoutForm({ clientSecret, intentType, customer, mode, onConfirm, returnUrl = "", submitButtonText = "" }) { const stripe = useStripe(); const elements = useElements(); const { t } = (0, _context.useLocaleContext)(); const theme = (0, _material.useTheme)(); const [state, setState] = (0, _ahooks.useSetState)({ message: "", confirming: false, loaded: false, showBillingForm: false, isTransitioning: false, paymentMethod: "card" }); const handlePaymentMethodChange = event => { const method = event.value?.type; const needsBillingInfo = method === "google_pay" || method === "apple_pay"; const shouldShowForm = needsBillingInfo && !isCompleteBillingAddress(customer.address); if (shouldShowForm && !state.showBillingForm) { setState({ isTransitioning: true }); setTimeout(() => { setState({ isTransitioning: false, paymentMethod: method, showBillingForm: true }); }, 300); } else { setState({ showBillingForm: false, paymentMethod: method, isTransitioning: false }); } }; const isCompleteBillingAddress = address => { return address && address.line1 && address.city && address.state && address.postal_code && address.country; }; (0, _react.useEffect)(() => { if (!stripe) { return; } if (!clientSecret) { return; } const method = intentType === "payment_intent" ? "retrievePaymentIntent" : "retrieveSetupIntent"; stripe[method](clientSecret).then(({ paymentIntent, setupIntent }) => { const intent = paymentIntent || setupIntent; switch (intent?.status) { case "succeeded": setState({ message: t("paymentCredit.preparePayMessage.succeeded") }); break; case "processing": setState({ message: t("paymentCredit.preparePayMessage.processing") }); break; case "requires_payment_method": // 忽略该状态 default: break; } }); }, [stripe, clientSecret]); const handleSubmit = (0, _react.useCallback)(async e => { e.preventDefault(); if (!stripe || !elements) { return; } try { setState({ confirming: true, message: "" }); const method = intentType === "payment_intent" ? "confirmPayment" : "confirmSetup"; const { error: submitError } = await elements.submit(); if (submitError) { setState({ confirming: false }); return; } const { error, paymentIntent, setupIntent } = await stripe[method]({ elements, redirect: "if_required", confirmParams: { return_url: returnUrl || window.location.href, ...(!state.showBillingForm ? { payment_method_data: { billing_details: { name: customer.name, phone: customer.phone, email: customer.email, address: { ...(customer.address || {}), country: customer.address?.country || "us", line1: customer.address?.line1 || "", line2: customer.address?.line2 || "", city: customer.address?.city || "", state: customer.address?.state || "", postal_code: customer.address?.postal_code || "00000" } } } } : {}) } }); const intent = paymentIntent || setupIntent; if (intent?.status === "canceled" || intent?.status === "requires_payment_method") { setState({ confirming: false }); return; } setState({ confirming: false }); if (error) { if (error.type === "validation_error") { return; } setState({ message: error.message }); return; } onConfirm(); } catch (err) { console.error(err); setState({ confirming: false, message: err.message }); } }, [customer, intentType, stripe, state.showBillingForm, returnUrl] // eslint-disable-line ); return /* @__PURE__ */(0, _jsxRuntime.jsxs)(Content, { onSubmit: handleSubmit, children: [(!state.paymentMethod || ["link", "card"].includes(state.paymentMethod)) && /* @__PURE__ */(0, _jsxRuntime.jsx)(LinkAuthenticationElement, { options: { defaultValues: { email: customer.email } } }), /* @__PURE__ */(0, _jsxRuntime.jsx)(PaymentElementContainer, { className: !state.isTransitioning ? "visible" : "", children: /* @__PURE__ */(0, _jsxRuntime.jsx)(PaymentElement, { options: { layout: "auto", fields: { billingDetails: state.showBillingForm ? "auto" : "never" }, readOnly: state.confirming, defaultValues: { billingDetails: { name: customer.name, phone: customer.phone, email: customer.email, address: customer.address } }, appearance: { theme: theme.palette.mode, variables: { colorPrimary: theme.palette.primary.main, colorBackground: theme.palette.background.paper, colorText: theme.palette.text.primary, colorDanger: theme.palette.error.main, borderRadius: "4px" } } }, onChange: handlePaymentMethodChange, onReady: () => setState({ loaded: true }) }) }), (!stripe || !elements || !state.loaded) && /* @__PURE__ */(0, _jsxRuntime.jsx)(_Center.default, { relative: "parent", children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.CircularProgress, {}) }), stripe && elements && state.loaded && /* @__PURE__ */(0, _jsxRuntime.jsx)(_loadingButton.default, { fullWidth: true, sx: { mt: 2, mb: 1, borderRadius: 0, fontSize: "0.875rem" }, type: "submit", disabled: state.confirming || !state.loaded, loading: state.confirming, variant: "contained", color: "primary", size: "large", children: submitButtonText || t("payment.checkout.continue", { action: t(`payment.checkout.${mode}`) }) }), state.message && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, { sx: { mt: 1, color: "error.main" }, children: state.message })] }); } const Content = (0, _system.styled)("form")` display: flex; flex-direction: column; justify-content: center; align-items: center; width: 100%; height: 100%; min-height: 320px; `; function StripeCheckout({ clientSecret, intentType, publicKey, mode, customer, onConfirm, onCancel, returnUrl = "", title = "", submitButtonText = "" }) { const stripePromise = loadStripe(publicKey); const { isMobile } = (0, _mobile.useMobile)(); const { t, locale } = (0, _context.useLocaleContext)(); const theme = (0, _material.useTheme)(); const [state, setState] = (0, _ahooks.useSetState)({ open: true, closable: true }); const handleClose = (_, reason) => { if (reason === "backdropClick") { return; } setState({ open: false }); onCancel(); }; return /* @__PURE__ */(0, _jsxRuntime.jsx)(_Dialog.default, { title: title || t("payment.checkout.cardPay", { action: t(`payment.checkout.${mode}`) }), showCloseButton: state.closable, open: state.open, onClose: handleClose, disableEscapeKeyDown: true, sx: { ".StripeElement": { minWidth: isMobile ? "100%" : "500px", py: 1 }, form: { justifyContent: "flex-start" }, ".StripeElement--focus": { borderColor: theme.palette.primary.main }, ".StripeElement--invalid": { borderColor: theme.palette.error.main }, ".StripeElement--complete": { borderColor: theme.palette.success.main } }, PaperProps: { style: { minWidth: isMobile ? "100%" : "500px" } }, children: /* @__PURE__ */(0, _jsxRuntime.jsx)(Elements, { options: { clientSecret, locale: locale === "zh" ? "zh-CN" : "en", appearance: { theme: theme.palette.mode, variables: { colorPrimary: theme.palette.primary.main, colorBackground: theme.palette.background.paper, colorText: theme.palette.text.primary, colorDanger: theme.palette.error.main } } }, stripe: stripePromise, children: /* @__PURE__ */(0, _jsxRuntime.jsx)(StripeCheckoutForm, { clientSecret, intentType, mode, customer, onConfirm, returnUrl, submitButtonText }) }) }); }