Skip to content

Commit

Permalink
feat(payments): display current customer payment info on management page
Browse files Browse the repository at this point in the history
issue #1086
  • Loading branch information
lmorchard committed May 14, 2019
1 parent 64ad973 commit f0579f4
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 6 deletions.
@@ -1,10 +1,11 @@
import React, { useCallback, useEffect } from 'react';
import { useBooleanState } from '../../lib/hooks';
import { injectStripe, CardElement, ReactStripeElements } from 'react-stripe-elements';
import { UpdatePaymentFetchState } from '../../store/types';
import { UpdatePaymentFetchState, CustomerFetchState } from '../../store/types';

type PaymentUpdateFormProps = {
accessToken: string,
customer: CustomerFetchState,
resetUpdatePayment: Function,
updatePayment: Function,
updatePaymentStatus: UpdatePaymentFetchState
Expand All @@ -14,6 +15,7 @@ export const PaymentUpdateForm = ({
updatePayment,
updatePaymentStatus,
resetUpdatePayment,
customer,
stripe
}: PaymentUpdateFormProps & ReactStripeElements.InjectedStripeProps) => {
const [ updateRevealed, revealUpdate, hideUpdate ] = useBooleanState();
Expand Down Expand Up @@ -43,6 +45,17 @@ export const PaymentUpdateForm = ({
}
}, [ accessToken, updatePayment, stripe ]);

if (customer.loading) {
// If the customer details are loading, then we have nothing to update yet.
return <span></span>;
}

if (customer.error) {
// If there's an error fetching the customer, there are no billing details to update.
// TODO: Specifically 404 error means no details, 401 / 500 could be reported differently.
return <span></span>;
}

if (updatePaymentStatus.loading) {
return (
<div>
Expand All @@ -61,12 +74,15 @@ export const PaymentUpdateForm = ({
);
}

const { payment_type, last4, exp_month, exp_year } = customer.result;
return (
<div>
<h3>Billing information</h3>
{/* TODO: TBD on UX for reporting payment update success */}
{(!!updatePaymentStatus.result) &&
<p>Updating... Success! {'' + updatePaymentStatus.result}</p>}
{! updateRevealed ? <>
<p>[{payment_type}] card ending {last4} Expires {exp_month} / {exp_year}</p>
<button onClick={revealUpdate}>Change...</button>
</> : <>
<form onSubmit={onSubmit}>
Expand Down
12 changes: 8 additions & 4 deletions packages/fxa-payments-server/src/routes/Subscriptions/index.tsx
Expand Up @@ -2,7 +2,7 @@ import React, { useCallback, useEffect } from 'react';
import { connect, useDispatch } from 'react-redux';
import { selectorsFromState, actions } from '../../store';
import { Elements } from 'react-stripe-elements';
import { SubscriptionsFetchState, UpdatePaymentFetchState } from '../../store/types';
import { SubscriptionsFetchState, UpdatePaymentFetchState, CustomerFetchState } from '../../store/types';
import LoadingSpinner from '../../components/LoadingSpinner';

import Subscription from './Subscription';
Expand All @@ -11,6 +11,7 @@ import PaymentUpdateForm from './PaymentUpdateForm';
type SubscriptionsProps = {
accessToken: string,
isLoading: boolean,
customer: CustomerFetchState,
subscriptions: SubscriptionsFetchState,
cancelSubscription: Function,
resetUpdatePayment: Function,
Expand All @@ -20,6 +21,7 @@ type SubscriptionsProps = {
export const Subscriptions = ({
accessToken,
isLoading,
customer,
subscriptions,
cancelSubscription,
updatePayment,
Expand All @@ -37,10 +39,11 @@ export const Subscriptions = ({
resetCancelSubscription();
}, [ resetCancelSubscription ]);

// Fetch subscriptions on initial render or auth change.
// Fetch subscriptions and customer on initial render or auth change.
useEffect(() => {
if (accessToken) {
dispatch(actions.fetchSubscriptions(accessToken));
dispatch(actions.fetchCustomer(accessToken));
}
}, [ dispatch, accessToken ]);

Expand Down Expand Up @@ -72,6 +75,7 @@ export const Subscriptions = ({
<Elements>
<PaymentUpdateForm {...{
accessToken,
customer,
updatePayment,
resetUpdatePayment,
updatePaymentStatus,
Expand All @@ -86,10 +90,10 @@ export const Subscriptions = ({

export default connect(
// TODO: replace this with a useSelector hook
selectorsFromState('subscriptions', 'updatePaymentStatus'),
selectorsFromState('customer', 'subscriptions', 'updatePaymentStatus'),
// TODO: replace this with a useDispatch hook
{
updatePayment: actions.updatePayment,
updatePayment: actions.updatePaymentAndRefresh,
resetUpdatePayment: actions.resetUpdatePayment,
cancelSubscription: actions.cancelSubscriptionAndRefresh,
}
Expand Down
14 changes: 13 additions & 1 deletion packages/fxa-payments-server/src/store/index.tsx
Expand Up @@ -27,6 +27,7 @@ export const defaultState: State = {
api: {
cancelSubscription: fetchDefault(false),
createSubscription: fetchDefault(false),
customer: fetchDefault({}),
plans: fetchDefault([]),
profile: fetchDefault({}),
updatePayment: fetchDefault(false),
Expand All @@ -40,6 +41,7 @@ export const selectors: Selectors = {
token: state => state.api.token,
subscriptions: state => state.api.subscriptions,
plans: state => state.api.plans,
customer: state => state.api.customer,
createSubscriptionStatus: state => state.api.createSubscription,
cancelSubscriptionStatus: state => state.api.cancelSubscription,
updatePaymentStatus: state => state.api.updatePayment,
Expand Down Expand Up @@ -81,6 +83,8 @@ export const actions: ActionCreators = {
apiGet(accessToken, `${config.AUTH_API_ROOT}/oauth/subscriptions/active`),
fetchToken: accessToken =>
apiPost(accessToken, `${config.OAUTH_API_ROOT}/introspect`, { token: accessToken }),
fetchCustomer: accessToken =>
apiGet(accessToken, `${config.AUTH_API_ROOT}/oauth/subscriptions/customer`),
createSubscription: (accessToken, params) =>
apiPost(
accessToken,
Expand Down Expand Up @@ -113,11 +117,17 @@ export const actions: ActionCreators = {
dispatch(actions.fetchSubscriptions(accessToken));
},

cancelSubscriptionAndRefresh: (accessToken: string, subscriptionId:object) =>
cancelSubscriptionAndRefresh: (accessToken: string, subscriptionId: object) =>
async (dispatch: Function, getState: Function) => {
await dispatch(actions.cancelSubscription(accessToken, subscriptionId));
dispatch(actions.fetchSubscriptions(accessToken));
},

updatePaymentAndRefresh: (accessToken: string, params: object) =>
async (dispatch: Function, getState: Function) => {
await dispatch(actions.updatePayment(accessToken, params));
dispatch(actions.fetchCustomer(accessToken));
},
};

export const reducers = {
Expand All @@ -131,6 +141,8 @@ export const reducers = {
fetchReducer('subscriptions'),
[actions.fetchToken.toString()]:
fetchReducer('token'),
[actions.fetchCustomer.toString()]:
fetchReducer('customer'),
[actions.createSubscription.toString()]:
fetchReducer('createSubscription'),
[actions.cancelSubscription.toString()]:
Expand Down
9 changes: 9 additions & 0 deletions packages/fxa-payments-server/src/store/types.tsx
Expand Up @@ -9,6 +9,13 @@ export interface Profile {
uid: string;
}

export interface Customer {
payment_type: string;
last4: string;
exp_month: string;
exp_year: string;
}

export interface Token {
active: boolean;
scope: string;
Expand Down Expand Up @@ -44,6 +51,7 @@ export interface CreateSubscriptionResult {
subscriptionId: string;
}

export type CustomerFetchState = FetchState<Customer>;
export type CreateSubscriptionFetchState = FetchState<CreateSubscriptionResult>;
export type CancelSubscriptionFetchState = FetchState<any>;
export type UpdatePaymentFetchState = FetchState<any>;
Expand All @@ -56,6 +64,7 @@ export interface State {
api: {
cancelSubscription: CreateSubscriptionFetchState;
createSubscription: CancelSubscriptionFetchState;
customer: CustomerFetchState;
plans: PlansFetchState;
profile: ProfileFetchState;
subscriptions: SubscriptionsFetchState;
Expand Down

0 comments on commit f0579f4

Please sign in to comment.