diff --git a/packages/fxa-payments-server/.storybook/components/MockApp.tsx b/packages/fxa-payments-server/.storybook/components/MockApp.tsx
index d855cf2036..f4cc9e019c 100644
--- a/packages/fxa-payments-server/.storybook/components/MockApp.tsx
+++ b/packages/fxa-payments-server/.storybook/components/MockApp.tsx
@@ -24,6 +24,9 @@ type MockAppProps = {
export const defaultAppContextValue: AppContextType = {
config: {
...config,
+ featureFlags: {
+ usePaypalUIByDefault: true,
+ },
productRedirectURLs: {
product_8675309: 'https://example.com/product',
},
diff --git a/packages/fxa-payments-server/src/components/PaymentConfirmation/index.tsx b/packages/fxa-payments-server/src/components/PaymentConfirmation/index.tsx
index 039e753d24..2f06caaa20 100644
--- a/packages/fxa-payments-server/src/components/PaymentConfirmation/index.tsx
+++ b/packages/fxa-payments-server/src/components/PaymentConfirmation/index.tsx
@@ -6,10 +6,7 @@ import { Plan, Profile, Customer } from '../../store/types';
import { PaymentProviderDetails } from '../PaymentProviderDetails';
import SubscriptionTitle from '../SubscriptionTitle';
import { TermsAndPrivacy } from '../TermsAndPrivacy';
-import {
- PaypalPaymentLegalBlurb,
- StripePaymentLegalBlurb,
-} from '../PaymentLegalBlurb';
+import PaymentLegalBlurb from '../PaymentLegalBlurb';
import circledCheckbox from './images/circled-confirm.svg';
@@ -135,8 +132,7 @@ export const PaymentConfirmation = ({
Continue to download
- {Provider.isPaypal(payment_provider) && }
- {Provider.isStripe(payment_provider) && }
+
diff --git a/packages/fxa-payments-server/src/components/PaymentForm/index.stories.tsx b/packages/fxa-payments-server/src/components/PaymentForm/index.stories.tsx
index 9ea643f194..57a9cdb875 100644
--- a/packages/fxa-payments-server/src/components/PaymentForm/index.stories.tsx
+++ b/packages/fxa-payments-server/src/components/PaymentForm/index.stories.tsx
@@ -56,7 +56,7 @@ const PLAN = {
'https://www.mozilla.org/fr/privacy/websites/',
},
};
-const CUSTOMER = {
+const CUSTOMER: Customer = {
billing_name: 'Foo Barson',
payment_provider: 'stripe',
payment_type: 'credit',
diff --git a/packages/fxa-payments-server/src/components/PaymentForm/index.tsx b/packages/fxa-payments-server/src/components/PaymentForm/index.tsx
index b31a1770dd..c3725f9743 100644
--- a/packages/fxa-payments-server/src/components/PaymentForm/index.tsx
+++ b/packages/fxa-payments-server/src/components/PaymentForm/index.tsx
@@ -39,6 +39,7 @@ import { Plan, Customer } from '../../store/types';
import { productDetailsFromPlan } from 'fxa-shared/subscriptions/metadata';
import './index.scss';
+import * as PaymentProvider from '../../lib/PaymentProvider';
export type PaymentSubmitResult = {
stripe: Stripe;
@@ -82,8 +83,10 @@ export const PaymentForm = ({
onChange: onChangeProp,
submitNonce,
}: BasePaymentFormProps) => {
- const hasExistingCard =
- customer && customer.last4 && customer.subscriptions.length > 0;
+ const isExistingStripeCustomer =
+ customer &&
+ PaymentProvider.isStripe(customer?.payment_provider) &&
+ customer.subscriptions.length > 0;
const stripe = useStripe();
const elements = useElements();
@@ -121,7 +124,7 @@ export const PaymentForm = ({
const { name } = validator.getValues();
const card = elements.getElement(CardElement);
/* istanbul ignore next - card should exist unless there was an external stripe loading error, handled above */
- if (hasExistingCard || card) {
+ if (isExistingStripeCustomer || card) {
onSubmitForParent({
stripe,
elements,
@@ -157,7 +160,7 @@ export const PaymentForm = ({
navigatorLanguages
));
}
- const paymentSource = hasExistingCard ? (
+ const paymentSource = isExistingStripeCustomer ? (
Stripe privacy policy.';
}
-export const PaypalPaymentLegalBlurb = () => (
+const PaypalPaymentLegalBlurb = () => (
Mozilla uses Paypal for secure payment processing.
@@ -38,7 +40,7 @@ export const PaypalPaymentLegalBlurb = () => (
);
-export const StripePaymentLegalBlurb = () => (
+const StripePaymentLegalBlurb = () => (
Mozilla uses Stripe for secure payment processing.
@@ -61,7 +63,7 @@ export const StripePaymentLegalBlurb = () => (
);
-export const PaymentLegalBlurb = () => (
+const DefaultPaymentLegalBlurb = () => (
Mozilla uses Stripe and Paypal for secure payment processing.
@@ -91,4 +93,17 @@ export const PaymentLegalBlurb = () => (
);
+export type PaymentLegalBlurbProps = {
+ provider: PaymentProvider.ProviderType | undefined;
+};
+
+export const PaymentLegalBlurb = ({ provider }: PaymentLegalBlurbProps) => {
+ return (
+ (PaymentProvider.isPaypal(provider) && ) ||
+ (PaymentProvider.isStripe(provider) && ) || (
+
+ )
+ );
+};
+
export default PaymentLegalBlurb;
diff --git a/packages/fxa-payments-server/src/components/PaymentProcessing/index.tsx b/packages/fxa-payments-server/src/components/PaymentProcessing/index.tsx
index 5448d449b6..0e082e7a15 100644
--- a/packages/fxa-payments-server/src/components/PaymentProcessing/index.tsx
+++ b/packages/fxa-payments-server/src/components/PaymentProcessing/index.tsx
@@ -1,18 +1,15 @@
import React from 'react';
import { Localized } from '@fluent/react';
-import * as Provider from '../../lib/PaymentProvider';
import { LoadingSpinner } from '../LoadingSpinner';
import SubscriptionTitle from '../SubscriptionTitle';
-import {
- PaypalPaymentLegalBlurb,
- StripePaymentLegalBlurb,
-} from '../PaymentLegalBlurb';
+import PaymentLegalBlurb from '../PaymentLegalBlurb';
+import { ProviderType } from 'fxa-payments-server/src/lib/PaymentProvider';
import './index.scss';
export type PaymentProcessingProps = {
- provider: 'stripe' | 'paypal';
+ provider?: ProviderType;
className?: string;
};
@@ -35,8 +32,7 @@ export const PaymentProcessing = ({
- {Provider.isPaypal(provider) &&
}
- {Provider.isStripe(provider) &&
}
+
>
diff --git a/packages/fxa-payments-server/src/lib/PaymentProvider.ts b/packages/fxa-payments-server/src/lib/PaymentProvider.ts
index 8e5de79af7..29a2aa8173 100644
--- a/packages/fxa-payments-server/src/lib/PaymentProvider.ts
+++ b/packages/fxa-payments-server/src/lib/PaymentProvider.ts
@@ -2,10 +2,16 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-export function isStripe(provider: string | undefined) {
+export type ProviderType = 'paypal' | 'stripe' | 'not_chosen';
+
+export function isStripe(provider: ProviderType | undefined): boolean {
return provider === 'stripe';
}
-export function isPaypal(provider: string | undefined) {
+export function isPaypal(provider: ProviderType | undefined): boolean {
return provider === 'paypal';
}
+
+export function isNotChosen(provider: ProviderType | undefined): boolean {
+ return provider === 'not_chosen' || provider === undefined;
+}
diff --git a/packages/fxa-payments-server/src/lib/test-utils.tsx b/packages/fxa-payments-server/src/lib/test-utils.tsx
index 2001e7d27c..3ee987ecc9 100644
--- a/packages/fxa-payments-server/src/lib/test-utils.tsx
+++ b/packages/fxa-payments-server/src/lib/test-utils.tsx
@@ -513,6 +513,7 @@ export const MOCK_ACTIVE_SUBSCRIPTIONS_AFTER_SUBSCRIPTION = [
export const MOCK_CUSTOMER = {
billing_name: 'Jane Doe',
payment_type: 'card',
+ payment_provider: 'stripe',
brand: 'Visa',
last4: '8675',
exp_month: '8',
diff --git a/packages/fxa-payments-server/src/routes/Product/SubscriptionCreate/index.stories.tsx b/packages/fxa-payments-server/src/routes/Product/SubscriptionCreate/index.stories.tsx
index e81f6b565d..1e16c974b5 100644
--- a/packages/fxa-payments-server/src/routes/Product/SubscriptionCreate/index.stories.tsx
+++ b/packages/fxa-payments-server/src/routes/Product/SubscriptionCreate/index.stories.tsx
@@ -11,7 +11,7 @@ import { linkTo } from '@storybook/addon-links';
import MockApp, {
defaultAppContextValue,
} from '../../../../.storybook/components/MockApp';
-import { CUSTOMER, PROFILE, PLAN } from '../../../lib/mock-data';
+import { CUSTOMER, PROFILE, PLAN, NEW_CUSTOMER } from '../../../lib/mock-data';
import { APIError } from '../../../lib/apiClient';
import { PickPartial } from '../../../lib/types';
import { SignInLayout } from '../../../components/AppLayout';
@@ -178,7 +178,7 @@ function init() {
const Subject = ({
isMobile = false,
- customer = CUSTOMER,
+ customer = NEW_CUSTOMER,
profile = PROFILE,
selectedPlan = PLAN,
apiClientOverrides = defaultApiClientOverrides,
diff --git a/packages/fxa-payments-server/src/routes/Product/SubscriptionCreate/index.test.tsx b/packages/fxa-payments-server/src/routes/Product/SubscriptionCreate/index.test.tsx
index 2d926dabfd..2bbf08a5a4 100644
--- a/packages/fxa-payments-server/src/routes/Product/SubscriptionCreate/index.test.tsx
+++ b/packages/fxa-payments-server/src/routes/Product/SubscriptionCreate/index.test.tsx
@@ -12,7 +12,13 @@ import {
import '@testing-library/jest-dom/extend-expect';
import { PaymentMethod, PaymentIntent } from '@stripe/stripe-js';
import { SignInLayout } from '../../../components/AppLayout';
-import { CUSTOMER, PROFILE, PLAN, NEW_CUSTOMER } from '../../../lib/mock-data';
+import {
+ CUSTOMER,
+ PROFILE,
+ PLAN,
+ NEW_CUSTOMER,
+ PAYPAL_CUSTOMER,
+} from '../../../lib/mock-data';
import { PickPartial } from '../../../lib/types';
import {
@@ -191,7 +197,7 @@ describe('routes/ProductV2/SubscriptionCreate', () => {
);
});
- it('renders as expected with PayPal UI enabled and an existing customer', async () => {
+ it('renders as expected with PayPal UI enabled and an existing Stripe customer', async () => {
const { queryByTestId } = screen;
updateConfig({
featureFlags: {
@@ -209,6 +215,35 @@ describe('routes/ProductV2/SubscriptionCreate', () => {
waitForExpect(() =>
expect(queryByTestId('paypal-button')).not.toBeInTheDocument()
);
+ waitForExpect(() =>
+ expect(queryByTestId('paymentForm')).toBeInTheDocument()
+ );
+ });
+
+ it('renders as expected with PayPal UI enabled and an existing PayPal customer', async () => {
+ const { queryByTestId } = screen;
+ updateConfig({
+ featureFlags: {
+ usePaypalUIByDefault: true,
+ },
+ });
+ const MockedButtonBase = ({}: ButtonBaseProps) => {
+ return ;
+ };
+ await act(async () => {
+ render(
+
+ );
+ });
+ waitForExpect(() =>
+ expect(queryByTestId('paypal-button')).not.toBeInTheDocument()
+ );
+ waitForExpect(() =>
+ expect(queryByTestId('paymentForm')).not.toBeInTheDocument()
+ );
});
it('renders as expected for mobile', async () => {
diff --git a/packages/fxa-payments-server/src/routes/Product/SubscriptionCreate/index.tsx b/packages/fxa-payments-server/src/routes/Product/SubscriptionCreate/index.tsx
index 37c41c4330..ad9f45d602 100644
--- a/packages/fxa-payments-server/src/routes/Product/SubscriptionCreate/index.tsx
+++ b/packages/fxa-payments-server/src/routes/Product/SubscriptionCreate/index.tsx
@@ -22,6 +22,7 @@ import PaymentLegalBlurb from '../../../components/PaymentLegalBlurb';
import { SubscriptionTitle } from '../../../components/SubscriptionTitle';
import { TermsAndPrivacy } from '../../../components/TermsAndPrivacy';
import { PaymentProcessing } from '../../../components/PaymentProcessing';
+import { ProviderType } from '../../../lib/PaymentProvider';
import * as Amplitude from '../../../lib/amplitude';
import { Localized } from '@fluent/react';
@@ -92,6 +93,10 @@ export const SubscriptionCreate = ({
const [paypalScriptLoaded, setPaypalScriptLoaded] = useState(false);
+ // The Stripe customer isn't created until payment is submitted, so
+ // customer can be null and customer.payment_provider can be undefined.
+ const paymentProvider: ProviderType | undefined = customer?.payment_provider;
+
useEffect(() => {
if (!config.featureFlags.usePaypalUIByDefault) {
return;
@@ -203,7 +208,7 @@ export const SubscriptionCreate = ({
})}
data-testid="subscription-create"
>
- {!hasExistingCard(customer) && paypalScriptLoaded && (
+ {isNewCustomer(customer) && paypalScriptLoaded && (
- {!hasExistingCard(customer) && !paypalScriptLoaded && (
+ {isNewCustomer(customer) && !paypalScriptLoaded && (
Pay with card
@@ -239,7 +244,7 @@ export const SubscriptionCreate = ({
)}
- {!hasExistingCard(customer) && paypalScriptLoaded && (
+ {isNewCustomer(customer) && paypalScriptLoaded && (
Or pay with card
@@ -248,39 +253,45 @@ export const SubscriptionCreate = ({
)}
-
-
- Payment information
-
-
+ {!isExistingPaypalCustomer(customer) && (
+ <>
+
+
+ Payment information
+
+
-
- {paymentError && (
-
-
- {getErrorMessage(paymentError.code || 'UNKNOWN')}
-
-
- )}
-
+
+ {paymentError && (
+
+
+ {getErrorMessage(paymentError.code || 'UNKNOWN')}
+
+
+ )}
+
-
+
+ >
+ )}
@@ -330,7 +341,7 @@ async function handleSubscriptionPayment({
} & SubscriptionCreateAuthServerAPIs) {
// If there's an existing card on record, GOTO 3
- if (hasExistingCard(customer)) {
+ if (isExistingStripeCustomer(customer)) {
const createSubscriptionResult = await apiCreateSubscriptionWithPaymentMethod(
{
priceId: selectedPlan.plan_id,
@@ -502,7 +513,18 @@ async function handlePaymentIntent({
}
}
-const hasExistingCard = (customer: Customer | null) =>
- customer && customer.last4 && customer.subscriptions.length > 0;
+const isExistingStripeCustomer = (customer: Customer | null) =>
+ customer &&
+ customer.payment_provider === 'stripe' &&
+ customer.subscriptions.length > 0;
+
+const isExistingPaypalCustomer = (customer: Customer | null) =>
+ customer &&
+ customer.payment_provider === 'paypal' &&
+ customer.subscriptions.length > 0;
+
+const isNewCustomer = (customer: Customer | null) =>
+ customer?.payment_provider === undefined ||
+ customer?.payment_provider === 'not_chosen';
export default SubscriptionCreate;
diff --git a/packages/fxa-payments-server/src/routes/Product/SubscriptionUpgrade/index.tsx b/packages/fxa-payments-server/src/routes/Product/SubscriptionUpgrade/index.tsx
index 57239ed176..b84e8d0469 100644
--- a/packages/fxa-payments-server/src/routes/Product/SubscriptionUpgrade/index.tsx
+++ b/packages/fxa-payments-server/src/routes/Product/SubscriptionUpgrade/index.tsx
@@ -26,6 +26,7 @@ import { useValidatorState } from '../../../lib/validator';
import DialogMessage from '../../../components/DialogMessage';
import PaymentLegalBlurb from '../../../components/PaymentLegalBlurb';
import { TermsAndPrivacy } from '../../../components/TermsAndPrivacy';
+import { ProviderType } from 'fxa-payments-server/src/lib/PaymentProvider';
import PlanUpgradeDetails from './PlanUpgradeDetails';
import Header from '../../../components/Header';
@@ -69,6 +70,8 @@ export const SubscriptionUpgrade = ({
const inProgress = updateSubscriptionPlanStatus.loading;
+ const paymentProvider: ProviderType | undefined = customer?.payment_provider;
+
useEffect(() => {
Amplitude.updateSubscriptionPlanMounted(selectedPlan);
}, [selectedPlan]);
@@ -236,7 +239,7 @@ export const SubscriptionUpgrade = ({
-
+
diff --git a/packages/fxa-payments-server/src/routes/Product/index.stories.tsx b/packages/fxa-payments-server/src/routes/Product/index.stories.tsx
index a2d97aa99d..a093c00eb6 100644
--- a/packages/fxa-payments-server/src/routes/Product/index.stories.tsx
+++ b/packages/fxa-payments-server/src/routes/Product/index.stories.tsx
@@ -15,10 +15,33 @@ import { PAYPAL_CUSTOMER } from '../../lib/mock-data';
function init() {
storiesOf('routes/Product', module)
- .add('subscribing with existing account', () => )
.add('subscribing with new account', () => (
))
+ .add('subscribing with existing Stripe account', () => (
+
+ ))
+ .add('subscribing with existing PayPal account', () => (
+
+ ))
.add('success with Stripe', () => (
{
product_metadata: {
...MOCK_PLANS[1].product_metadata,
webIconURL: null,
+ webIconBackground: null,
},
},
...MOCK_PLANS.slice(2),
diff --git a/packages/fxa-payments-server/src/store/types.tsx b/packages/fxa-payments-server/src/store/types.tsx
index efe1b41801..8ec4931eeb 100644
--- a/packages/fxa-payments-server/src/store/types.tsx
+++ b/packages/fxa-payments-server/src/store/types.tsx
@@ -1,3 +1,5 @@
+import { ProviderType } from '../lib/PaymentProvider';
+
export type {
PlanInterval,
RawMetadata,
@@ -73,7 +75,7 @@ export type Customer = {
exp_month?: string;
exp_year?: string;
last4?: string;
- payment_provider?: string;
+ payment_provider?: ProviderType;
payment_type?: string;
subscriptions: Array;
};