@blocklet/payment-react
Version:
Reusable react components for payment kit v2
363 lines (362 loc) • 10.2 kB
JavaScript
"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
})
})
});
}