Merge pull request #19778 from mozilla/PAY-3417-remove-name-as-it-appears-on-card-field

fix(payments-next): "Name as it appears on credit card" field does not appear prior to entering credit card number
This commit is contained in:
Liza Ilina
2025-12-12 13:46:16 -05:00
committed by GitHub
16 changed files with 9 additions and 181 deletions

View File

@@ -44,7 +44,6 @@ export const CheckoutCustomerDataFactory = (
override?: Partial<CheckoutCustomerData>
): CheckoutCustomerData => ({
locale: faker.helpers.arrayElement(['en-US', 'de', 'es', 'fr-FR']),
displayName: faker.person.fullName(),
...override,
});

View File

@@ -13,7 +13,6 @@ import Stripe from 'stripe';
export type CheckoutCustomerData = {
locale: string;
displayName: string;
};
export type FinishCart = {

View File

@@ -164,7 +164,6 @@ export class CheckoutService {
customer = await this.customerManager.create({
uid,
email,
displayName: customerData.displayName,
taxAddress,
});

View File

@@ -120,7 +120,6 @@ describe('CustomerManager', () => {
const result = await customerManager.create({
uid: faker.string.uuid(),
email: faker.internet.email(),
displayName: faker.person.fullName(),
taxAddress: taxAddress,
});

View File

@@ -45,10 +45,9 @@ export class CustomerManager {
async create(args: {
uid: string;
email: string;
displayName: string;
taxAddress?: TaxAddress;
}) {
const { uid, email, displayName, taxAddress } = args;
const { uid, email, taxAddress } = args;
const shipping = taxAddress
? {
@@ -62,7 +61,6 @@ export class CustomerManager {
const customer = await this.stripeClient.customersCreate({
email,
name: displayName || '',
description: uid,
metadata: {
[STRIPE_CUSTOMER_METADATA.Userid]: uid,

View File

@@ -1477,7 +1477,6 @@ describe('SubscriptionManagementService', () => {
const mockPaymentMethod = StripeResponseFactory(
StripePaymentMethodFactory()
);
const mockFullName = faker.person.fullName();
jest
.spyOn(accountCustomerManager, 'getAccountCustomerByUid')
@@ -1486,8 +1485,7 @@ describe('SubscriptionManagementService', () => {
await subscriptionManagementService.setDefaultStripePaymentDetails(
mockCustomer.id,
mockPaymentMethod.id,
mockFullName
mockPaymentMethod.id
);
expect(customerManager.update).toHaveBeenCalledWith(
@@ -1496,7 +1494,6 @@ describe('SubscriptionManagementService', () => {
invoice_settings: {
default_payment_method: mockPaymentMethod.id,
},
name: mockFullName,
}
);
});
@@ -1513,7 +1510,6 @@ describe('SubscriptionManagementService', () => {
subscriptionManagementService.setDefaultStripePaymentDetails(
mockAccountCustomer.uid,
'pm_12345',
'john doe'
)
).rejects.toBeInstanceOf(SetDefaultPaymentAccountCustomerMissingStripeId);
});

View File

@@ -940,7 +940,6 @@ export class SubscriptionManagementService {
async setDefaultStripePaymentDetails(
uid: string,
paymentMethodId: string,
fullName: string
) {
const accountCustomer =
await this.accountCustomerManager.getAccountCustomerByUid(uid);
@@ -952,8 +951,7 @@ export class SubscriptionManagementService {
await this.customerManager.update(accountCustomer.stripeCustomerId, {
invoice_settings: {
default_payment_method: paymentMethodId,
},
name: fullName,
}
});
}

View File

@@ -13,7 +13,6 @@ export const checkoutCartWithStripe = async (
confirmationTokenId: string,
customerData: {
locale: string;
displayName: string;
},
attribution: SubscriptionAttributionParams,
sessionUid?: string

View File

@@ -9,13 +9,11 @@ import { getApp } from '../nestapp/app';
export const setDefaultStripePaymentDetails = async (
uid: string,
paymentMethodId: string,
fullName: string
) => {
const actionsService = getApp().getActionsService();
return await actionsService.setDefaultStripePaymentDetails({
uid,
paymentMethodId,
fullName,
});
};

View File

@@ -1,10 +1,5 @@
## Checkout Form
next-new-user-submit = Subscribe Now
next-payment-validate-name-error = Please enter your full name
next-pay-with-heading-paypal = Pay with { -brand-paypal }
# Label for the Full Name input
payment-name-label = Name as it appears on your card
payment-name-placeholder = Full Name

View File

@@ -3,7 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use client';
import { Localized, useLocalization } from '@fluent/react';
import { Localized, } from '@fluent/react';
import { PayPalButtons } from '@paypal/react-paypal-js';
import * as Form from '@radix-ui/react-form';
import Stripe from 'stripe';
@@ -97,7 +97,6 @@ export function CheckoutForm({
sessionUid,
sessionEmail,
}: CheckoutFormProps) {
const { l10n } = useLocalization();
const elements = useElements();
const router = useRouter();
const stripe = useStripe();
@@ -113,8 +112,6 @@ export function CheckoutForm({
const [isPaymentElementLoading, setIsPaymentElementLoading] = useState(true);
const [loading, setLoading] = useState(false);
const [stripeFieldsComplete, setStripeFieldsComplete] = useState(false);
const [fullName, setFullName] = useState('');
const [hasFullNameError, setHasFullNameError] = useState(false);
const [selectedPaymentMethod, setSelectedPaymentMethod] = useState('');
const [isSavedPaymentMethod, setIsSavedPaymentMethod] = useState(
!!cart?.paymentInfo?.type
@@ -123,7 +120,6 @@ export function CheckoutForm({
const linkAuthOptions = sessionEmail
? { defaultValues: { email: sessionEmail } }
: {};
const [isNotCard, setIsNotCard] = useState(false);
const engageGlean = useCallbackOnce(() => {
recordEmitterEventAction(
@@ -155,10 +151,6 @@ export function CheckoutForm({
const isNewCardSelected =
event?.value?.type === 'card' && !hasSavedPaymentMethod;
const selectedType = event?.value?.type || '';
const isNotCardType = selectedType !== 'card';
setIsNotCard(isNotCardType);
setShowLinkAuthElement(isNewCardSelected && hasSavedPaymentMethod);
setSelectedPaymentMethod(event?.value?.type || '');
@@ -172,13 +164,6 @@ export function CheckoutForm({
const showPayPalButton = selectedPaymentMethod === 'external_paypal';
const isStripe = cart?.paymentInfo?.type !== 'external_paypal';
const showFullNameInput =
!isPaymentElementLoading &&
!showPayPalButton &&
!isSavedPaymentMethod &&
selectedPaymentMethod === 'card' &&
!isNotCard;
const nonStripeFieldsComplete = !showFullNameInput || !!fullName;
const submitHandler = async (
event: React.SyntheticEvent<HTMLFormElement>
@@ -220,16 +205,6 @@ export function CheckoutForm({
return;
}
if (showFullNameInput) {
setHasFullNameError(!fullName);
if (!fullName) {
setLoading(false);
return;
}
} else {
setHasFullNameError(false);
}
// Trigger form validation and wallet collection
const { error: submitError } = await elements.submit();
if (submitError) {
@@ -248,7 +223,6 @@ export function CheckoutForm({
? {
payment_method_data: {
billing_details: {
name: fullName,
email: sessionEmail || undefined,
},
},
@@ -291,7 +265,6 @@ export function CheckoutForm({
confirmationToken.id,
{
locale,
displayName: fullName,
},
getAttributionParams(searchParams),
sessionUid
@@ -329,55 +302,6 @@ export function CheckoutForm({
}
onClick={() => setShowConsentError(true)}
>
{showFullNameInput && (
<>
<Localized id="next-new-user-card-title">
<h3 className="font-semibold text-grey-600 text-start">
Enter your card information
</h3>
</Localized>
<Form.Field
name="name"
serverInvalid={hasFullNameError}
className="my-6"
>
<Form.Label className="text-grey-400 block mb-1 text-start">
<Localized id="payment-name-label">
Name as it appears on your card
</Localized>
</Form.Label>
<Form.Control asChild>
<input
className="w-full border rounded-md border-black/30 p-3 placeholder:text-grey-500 placeholder:font-normal focus:border focus:!border-black/30 focus:!shadow-[0_0_0_3px_rgba(10,132,255,0.3)] focus-visible:outline-none data-[invalid=true]:border-alert-red data-[invalid=true]:text-alert-red data-[invalid=true]:shadow-inputError"
type="text"
data-testid="name"
placeholder={l10n.getString(
'payment-name-placeholder',
{},
'Full Name'
)}
readOnly={!formEnabled}
tabIndex={formEnabled ? 0 : -1}
value={fullName}
onChange={(e) => {
setFullName(e.target.value);
setHasFullNameError(!e.target.value);
}}
aria-required
/>
</Form.Control>
{hasFullNameError && (
<Form.Message asChild>
<Localized id="next-payment-validate-name-error">
<p className="mt-1 text-alert-red" role="alert">
Please enter your name
</p>
</Localized>
</Form.Message>
)}
</Form.Field>
</>
)}
{cart?.paymentInfo?.type === 'external_paypal' ? (
<div className="bg-white rounded-lg border border-[#e6e6e6] shadow-stripeBox">
<h3 className="p-4 text-sm text-[#0570de] font-semibold">Saved</h3>
@@ -470,8 +394,7 @@ export function CheckoutForm({
variant={ButtonVariant.Primary}
aria-disabled={
!formEnabled ||
(isStripe &&
!(stripeFieldsComplete && nonStripeFieldsComplete)) ||
(isStripe && !stripeFieldsComplete) ||
loading
}
>

View File

@@ -42,8 +42,6 @@ export function PaymentMethodManagement({
const [isReady, setIsReady] = useState(false);
const [error, setError] = useState<string | null>(null);
const [isInputNewCardDetails, setIsInputNewCardDetails] = useState(false);
const [fullName, setFullName] = useState('');
const [hasFullNameError, setHasFullNameError] = useState(false);
const [isNonDefaultCardSelected, setIsNonDefaultCardSelected] =
useState(false);
const [isNonCardSelected, setIsNonCardSelected] = useState(false);
@@ -68,7 +66,6 @@ export function PaymentMethodManagement({
if (event.value.type !== 'card') {
setIsNonCardSelected(true);
setIsInputNewCardDetails(false);
setHasFullNameError(false);
if (!!event.value.payment_method) {
if (event.value.payment_method.id !== defaultPaymentMethodId) {
setIsNonDefaultCardSelected(true);
@@ -82,10 +79,6 @@ export function PaymentMethodManagement({
if (event.value.type === 'card' && !event.value.payment_method) {
setIsInputNewCardDetails(true);
if (event.complete) {
setHasFullNameError(fullName.length === 0);
}
} else if (event.value.type === 'card' && !!event.value.payment_method) {
setIsInputNewCardDetails(false);
@@ -117,8 +110,7 @@ export function PaymentMethodManagement({
uid ?? '',
typeof response.setupIntent.payment_method === 'string'
? response.setupIntent.payment_method
: (response.setupIntent.payment_method?.id ?? ''),
fullName
: (response.setupIntent.payment_method?.id ?? '')
);
} else {
throw new Error('We could not confirm your payment method');
@@ -132,11 +124,6 @@ export function PaymentMethodManagement({
return;
}
if (isInputNewCardDetails && !fullName) {
setHasFullNameError(true);
return;
}
setIsLoading(true);
setError(null);
@@ -154,7 +141,6 @@ export function PaymentMethodManagement({
params: {
payment_method_data: {
billing_details: {
name: fullName,
email: sessionEmail || undefined,
},
},
@@ -206,56 +192,6 @@ export function PaymentMethodManagement({
onSubmit={handleSubmit}
className="px-4 tablet:px-0"
>
{isInputNewCardDetails && (
<>
<Localized id="next-new-user-card-title">
<h2 className="font-semibold text-grey-600 text-start mt-6">
Enter your card information
</h2>
</Localized>
<Form.Field
name="name"
serverInvalid={hasFullNameError}
className="my-6"
>
<Form.Label className="text-grey-400 block mb-1 text-start">
<Localized id="payment-name-label">
Name as it appears on your card
</Localized>
</Form.Label>
<Form.Control asChild>
<input
className="w-full border rounded-md border-black/30 p-3 placeholder:text-grey-500 placeholder:font-normal focus:border focus:!border-black/30 focus:!shadow-[0_0_0_3px_rgba(10,132,255,0.3)] focus-visible:outline-none data-[invalid=true]:border-alert-red data-[invalid=true]:text-alert-red data-[invalid=true]:shadow-inputError"
type="text"
data-testid="name"
placeholder={l10n.getString(
'payment-name-placeholder',
{},
'Full Name'
)}
value={fullName}
onChange={(e) => {
setFullName(e.target.value);
setHasFullNameError(!e.target.value);
}}
aria-required
/>
</Form.Control>
{hasFullNameError && (
<Form.Message asChild>
<Localized id="next-payment-validate-name-error">
<p
className="mt-1 text-alert-red font-normal"
role="alert"
>
Please enter your full name
</p>
</Localized>
</Form.Message>
)}
</Form.Field>
</>
)}
<Form.Field name="payment">
<Form.Control asChild>
<div
@@ -297,10 +233,10 @@ export function PaymentMethodManagement({
type="submit"
variant={ButtonVariant.Primary}
aria-disabled={
!stripe || !isComplete || isLoading || hasFullNameError
!stripe || !isComplete || isLoading
}
disabled={
!stripe || !isComplete || isLoading || hasFullNameError
!stripe || !isComplete || isLoading
}
>
{isLoading ? (

View File

@@ -1,3 +0,0 @@
## Payment Section
next-new-user-card-title = Enter your card information

View File

@@ -480,7 +480,7 @@ export class NextJSActionsService {
cartId: string;
version: number;
confirmationTokenId: string;
customerData: { locale: string; displayName: string };
customerData: { locale: string;};
attribution: SubscriptionAttributionParams;
sessionUid?: string;
}) {
@@ -878,12 +878,10 @@ export class NextJSActionsService {
async setDefaultStripePaymentDetails(args: {
uid: string;
paymentMethodId: string;
fullName: string;
}) {
return await this.subscriptionManagementService.setDefaultStripePaymentDetails(
args.uid,
args.paymentMethodId,
args.fullName
);
}

View File

@@ -49,9 +49,6 @@ export class CheckoutCartWithStripeActionAttributionData {
export class CheckoutCartWithStripeActionCustomerData {
@IsString()
locale!: string;
@IsString()
displayName!: string;
}
export class CheckoutCartWithStripeActionArgs {

View File

@@ -10,7 +10,4 @@ export class SetDefaultStripePaymentDetailsActionArgs {
@IsString()
paymentMethodId!: string;
@IsString()
fullName!: string;
}