feat(subscription): add Stripe express checkout logging
This commit is contained in:
@@ -46,6 +46,10 @@
|
|||||||
gap: 18px;
|
gap: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.expressCheckout {
|
||||||
|
min-height: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
.error {
|
.error {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: #c0392b;
|
color: #c0392b;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
import { useState, type FormEvent } from "react";
|
import { useState, type FormEvent } from "react";
|
||||||
import {
|
import {
|
||||||
Elements,
|
Elements,
|
||||||
|
ExpressCheckoutElement,
|
||||||
PaymentElement,
|
PaymentElement,
|
||||||
useElements,
|
useElements,
|
||||||
useStripe,
|
useStripe,
|
||||||
@@ -106,6 +107,57 @@ function StripePaymentForm({
|
|||||||
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
|
|
||||||
|
const handleExpressReady = (event: Parameters<
|
||||||
|
NonNullable<React.ComponentProps<typeof ExpressCheckoutElement>["onReady"]>
|
||||||
|
>[0]) => {
|
||||||
|
log.important(
|
||||||
|
"info",
|
||||||
|
"[stripe-payment-dialog] ExpressCheckoutElement ready",
|
||||||
|
{
|
||||||
|
availablePaymentMethods: event.availablePaymentMethods ?? null,
|
||||||
|
userAgent: navigator.userAgent,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleExpressAvailablePaymentMethodsChange = (event: Parameters<
|
||||||
|
NonNullable<
|
||||||
|
React.ComponentProps<
|
||||||
|
typeof ExpressCheckoutElement
|
||||||
|
>["onAvailablePaymentMethodsChange"]
|
||||||
|
>
|
||||||
|
>[0]) => {
|
||||||
|
log.important(
|
||||||
|
"info",
|
||||||
|
"[stripe-payment-dialog] ExpressCheckoutElement available payment methods changed",
|
||||||
|
{
|
||||||
|
paymentMethods: event.paymentMethods ?? null,
|
||||||
|
userAgent: navigator.userAgent,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleExpressLoadError = (event: Parameters<
|
||||||
|
NonNullable<
|
||||||
|
React.ComponentProps<typeof ExpressCheckoutElement>["onLoadError"]
|
||||||
|
>
|
||||||
|
>[0]) => {
|
||||||
|
log.warn("[stripe-payment-dialog] ExpressCheckoutElement load failed", {
|
||||||
|
type: event.error.type,
|
||||||
|
code: event.error.code,
|
||||||
|
message: event.error.message,
|
||||||
|
declineCode: event.error.decline_code,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleExpressCancel = (event: Parameters<
|
||||||
|
NonNullable<React.ComponentProps<typeof ExpressCheckoutElement>["onCancel"]>
|
||||||
|
>[0]) => {
|
||||||
|
log.debug("[stripe-payment-dialog] Express checkout cancelled", {
|
||||||
|
elementType: event.elementType,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const handleLoadError = (event: Parameters<
|
const handleLoadError = (event: Parameters<
|
||||||
NonNullable<React.ComponentProps<typeof PaymentElement>["onLoadError"]>
|
NonNullable<React.ComponentProps<typeof PaymentElement>["onLoadError"]>
|
||||||
>[0]) => {
|
>[0]) => {
|
||||||
@@ -121,16 +173,37 @@ function StripePaymentForm({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {
|
const confirmStripePayment = async (
|
||||||
event.preventDefault();
|
source: "payment_element" | "express_checkout",
|
||||||
if (!stripe || !elements || isSubmitting) return;
|
expressEvent?: Parameters<
|
||||||
|
NonNullable<
|
||||||
|
React.ComponentProps<typeof ExpressCheckoutElement>["onConfirm"]
|
||||||
|
>
|
||||||
|
>[0],
|
||||||
|
) => {
|
||||||
|
if (!stripe || !elements || isSubmitting) {
|
||||||
|
expressEvent?.paymentFailed({
|
||||||
|
reason: "fail",
|
||||||
|
message: "Payment is not ready yet. Please try again.",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug("[stripe-payment-dialog] confirming payment", {
|
||||||
|
source,
|
||||||
|
expressPaymentType: expressEvent?.expressPaymentType,
|
||||||
|
});
|
||||||
setIsSubmitting(true);
|
setIsSubmitting(true);
|
||||||
setErrorMessage(null);
|
setErrorMessage(null);
|
||||||
|
|
||||||
const { error: submitError } = await elements.submit();
|
const { error: submitError } = await elements.submit();
|
||||||
if (submitError) {
|
if (submitError) {
|
||||||
setErrorMessage(submitError.message ?? "Please check your payment info.");
|
const message = submitError.message ?? "Please check your payment info.";
|
||||||
|
setErrorMessage(message);
|
||||||
|
expressEvent?.paymentFailed({
|
||||||
|
reason: "invalid_payment_data",
|
||||||
|
message,
|
||||||
|
});
|
||||||
setIsSubmitting(false);
|
setIsSubmitting(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -148,16 +221,59 @@ function StripePaymentForm({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
setErrorMessage(error.message ?? "Payment confirmation failed.");
|
const message = error.message ?? "Payment confirmation failed.";
|
||||||
|
setErrorMessage(message);
|
||||||
|
expressEvent?.paymentFailed({
|
||||||
|
reason: "fail",
|
||||||
|
message,
|
||||||
|
});
|
||||||
setIsSubmitting(false);
|
setIsSubmitting(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.info("[stripe-payment-dialog] payment confirmed", {
|
||||||
|
source,
|
||||||
|
expressPaymentType: expressEvent?.expressPaymentType,
|
||||||
|
});
|
||||||
onConfirmed?.();
|
onConfirmed?.();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
|
||||||
|
event.preventDefault();
|
||||||
|
void confirmStripePayment("payment_element");
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleExpressConfirm = (event: Parameters<
|
||||||
|
NonNullable<React.ComponentProps<typeof ExpressCheckoutElement>["onConfirm"]>
|
||||||
|
>[0]) => {
|
||||||
|
void confirmStripePayment("express_checkout", event);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form className={styles.form} onSubmit={handleSubmit}>
|
<form className={styles.form} onSubmit={handleSubmit}>
|
||||||
|
<div className={styles.expressCheckout}>
|
||||||
|
<ExpressCheckoutElement
|
||||||
|
options={{
|
||||||
|
buttonHeight: 48,
|
||||||
|
layout: {
|
||||||
|
maxColumns: 1,
|
||||||
|
maxRows: 3,
|
||||||
|
},
|
||||||
|
paymentMethods: {
|
||||||
|
applePay: "auto",
|
||||||
|
googlePay: "auto",
|
||||||
|
link: "auto",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
onReady={handleExpressReady}
|
||||||
|
onLoadError={handleExpressLoadError}
|
||||||
|
onAvailablePaymentMethodsChange={
|
||||||
|
handleExpressAvailablePaymentMethodsChange
|
||||||
|
}
|
||||||
|
onCancel={handleExpressCancel}
|
||||||
|
onConfirm={handleExpressConfirm}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<PaymentElement onLoadError={handleLoadError} />
|
<PaymentElement onLoadError={handleLoadError} />
|
||||||
{errorMessage ? (
|
{errorMessage ? (
|
||||||
<p role="alert" className={styles.error}>
|
<p role="alert" className={styles.error}>
|
||||||
|
|||||||
Reference in New Issue
Block a user