feat(subscription): support Stripe payment element
This commit is contained in:
@@ -4,13 +4,14 @@
|
||||
*
|
||||
* 包装 `SubscriptionCtaButton` —— 通过 payment 状态机创建后端订单并拉起支付。
|
||||
*/
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
import {
|
||||
usePaymentDispatch,
|
||||
usePaymentState,
|
||||
} from "@/stores/payment/payment-context";
|
||||
|
||||
import { StripePaymentDialog } from "./stripe-payment-dialog";
|
||||
import { SubscriptionCtaButton } from "./subscription-cta-button";
|
||||
import styles from "./subscription-cta-button.module.css";
|
||||
|
||||
@@ -25,6 +26,9 @@ export function SubscriptionCheckoutButton({
|
||||
const payment = usePaymentState();
|
||||
const paymentDispatch = usePaymentDispatch();
|
||||
const launchedNonceRef = useRef(0);
|
||||
const [hiddenStripeClientSecret, setHiddenStripeClientSecret] = useState<
|
||||
string | null
|
||||
>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!payment.payParams || payment.launchNonce <= launchedNonceRef.current) {
|
||||
@@ -33,15 +37,21 @@ export function SubscriptionCheckoutButton({
|
||||
|
||||
launchedNonceRef.current = payment.launchNonce;
|
||||
const paymentUrl = getPaymentUrl(payment.payParams);
|
||||
if (!paymentUrl) {
|
||||
paymentDispatch({
|
||||
type: "PaymentLaunchFailed",
|
||||
errorMessage: "Payment parameters did not include a supported URL.",
|
||||
});
|
||||
if (paymentUrl) {
|
||||
window.location.href = paymentUrl;
|
||||
return;
|
||||
}
|
||||
|
||||
window.location.href = paymentUrl;
|
||||
const clientSecret = getStripeClientSecret(payment.payParams);
|
||||
if (clientSecret) {
|
||||
return;
|
||||
}
|
||||
|
||||
paymentDispatch({
|
||||
type: "PaymentLaunchFailed",
|
||||
errorMessage:
|
||||
"Payment parameters did not include a supported URL or Stripe client secret.",
|
||||
});
|
||||
}, [payment.launchNonce, payment.payParams, paymentDispatch]);
|
||||
|
||||
const isLoading =
|
||||
@@ -58,9 +68,33 @@ export function SubscriptionCheckoutButton({
|
||||
|
||||
const handleClick = () => {
|
||||
if (disabled || isLoading) return;
|
||||
setHiddenStripeClientSecret(null);
|
||||
paymentDispatch({ type: "PaymentCreateOrderSubmitted" });
|
||||
};
|
||||
|
||||
const handleStripeClose = () => {
|
||||
if (stripeClientSecret) {
|
||||
setHiddenStripeClientSecret(stripeClientSecret);
|
||||
}
|
||||
if (payment.orderStatus !== "paid") {
|
||||
paymentDispatch({ type: "PaymentReset" });
|
||||
}
|
||||
};
|
||||
|
||||
const handleStripeConfirmed = () => {
|
||||
if (stripeClientSecret) {
|
||||
setHiddenStripeClientSecret(stripeClientSecret);
|
||||
}
|
||||
};
|
||||
|
||||
const stripeClientSecret = payment.payParams
|
||||
? getStripeClientSecret(payment.payParams)
|
||||
: null;
|
||||
const shouldShowStripeDialog =
|
||||
stripeClientSecret !== null &&
|
||||
stripeClientSecret !== hiddenStripeClientSecret &&
|
||||
!payment.isPaid;
|
||||
|
||||
return (
|
||||
<>
|
||||
<SubscriptionCtaButton
|
||||
@@ -85,6 +119,13 @@ export function SubscriptionCheckoutButton({
|
||||
{payment.errorMessage}
|
||||
</p>
|
||||
) : null}
|
||||
{shouldShowStripeDialog ? (
|
||||
<StripePaymentDialog
|
||||
clientSecret={stripeClientSecret}
|
||||
onClose={handleStripeClose}
|
||||
onConfirmed={handleStripeConfirmed}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -107,3 +148,22 @@ function getPaymentUrl(payParams: Record<string, unknown>): string | null {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getStripeClientSecret(
|
||||
payParams: Record<string, unknown>,
|
||||
): string | null {
|
||||
const provider = payParams.provider;
|
||||
const clientSecret = payParams.clientSecret ?? payParams.client_secret;
|
||||
const isStripeProvider =
|
||||
typeof provider !== "string" || provider.toLowerCase() === "stripe";
|
||||
|
||||
if (
|
||||
isStripeProvider &&
|
||||
typeof clientSecret === "string" &&
|
||||
clientSecret.length > 0
|
||||
) {
|
||||
return clientSecret;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user