mirror of
https://github.com/kingomarnajjar/flex-template-web.git
synced 2026-07-25 22:37:18 +10:00
Create PaymentMethodsPage
This commit is contained in:
parent
d4c265c014
commit
905f99043b
7 changed files with 466 additions and 3 deletions
28
src/containers/PaymentMethodsPage/PaymentMethodsPage.css
Normal file
28
src/containers/PaymentMethodsPage/PaymentMethodsPage.css
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
@import '../../marketplace.css';
|
||||
|
||||
.content {
|
||||
@media (--viewportMedium) {
|
||||
margin: 32px auto 0 auto;
|
||||
width: 100%;
|
||||
max-width: 564px;
|
||||
}
|
||||
|
||||
@media (--viewportLarge) {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.desktopTopbar,
|
||||
.mobileTopbar {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.title {
|
||||
hyphens: auto;
|
||||
margin-top: 8px;
|
||||
margin-bottom: 19px;
|
||||
|
||||
@media (--viewportMedium) {
|
||||
margin-bottom: 47px;
|
||||
}
|
||||
}
|
||||
106
src/containers/PaymentMethodsPage/PaymentMethodsPage.duck.js
Normal file
106
src/containers/PaymentMethodsPage/PaymentMethodsPage.duck.js
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
import { fetchCurrentUser } from '../../ducks/user.duck';
|
||||
import { setInitialValues as setInitialValuesForPaymentMethods } from '../../ducks/paymentMethods.duck';
|
||||
import { storableError } from '../../util/errors';
|
||||
import * as log from '../../util/log';
|
||||
|
||||
// ================ Action types ================ //
|
||||
|
||||
export const SETUP_INTENT_REQUEST = 'app/PaymentMethodsPage/SETUP_INTENT_REQUEST';
|
||||
export const SETUP_INTENT_SUCCESS = 'app/PaymentMethodsPage/SETUP_INTENT_SUCCESS';
|
||||
export const SETUP_INTENT_ERROR = 'app/PaymentMethodsPage/SETUP_INTENT_ERROR';
|
||||
|
||||
export const STRIPE_CUSTOMER_REQUEST = 'app/PaymentMethodsPage/STRIPE_CUSTOMER_REQUEST';
|
||||
export const STRIPE_CUSTOMER_SUCCESS = 'app/PaymentMethodsPage/STRIPE_CUSTOMER_SUCCESS';
|
||||
export const STRIPE_CUSTOMER_ERROR = 'app/PaymentMethodsPage/STRIPE_CUSTOMER_ERROR';
|
||||
|
||||
// ================ Reducer ================ //
|
||||
|
||||
const initialState = {
|
||||
setupIntentInProgress: false,
|
||||
setupIntentError: null,
|
||||
setupIntent: null,
|
||||
stripeCustomerFetched: false,
|
||||
};
|
||||
|
||||
export default function payoutMethodsPageReducer(state = initialState, action = {}) {
|
||||
const { type, payload } = action;
|
||||
switch (type) {
|
||||
case SETUP_INTENT_REQUEST:
|
||||
return { ...state, setupIntentInProgress: true, setupIntentError: null };
|
||||
case SETUP_INTENT_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
setupIntentInProgress: false,
|
||||
setupIntentError: null,
|
||||
setupIntent: payload,
|
||||
};
|
||||
case SETUP_INTENT_ERROR:
|
||||
console.error(payload); // eslint-disable-line no-console
|
||||
return { ...state, setupIntentInProgress: false, setupIntentError: null };
|
||||
case STRIPE_CUSTOMER_REQUEST:
|
||||
return { ...state, stripeCustomerFetched: false };
|
||||
case STRIPE_CUSTOMER_SUCCESS:
|
||||
return { ...state, stripeCustomerFetched: true };
|
||||
case STRIPE_CUSTOMER_ERROR:
|
||||
console.error(payload); // eslint-disable-line no-console
|
||||
return { ...state, stripeCustomerFetchError: payload };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
// ================ Action creators ================ //
|
||||
|
||||
export const setupIntentRequest = () => ({ type: SETUP_INTENT_REQUEST });
|
||||
export const setupIntentSuccess = () => ({ type: SETUP_INTENT_SUCCESS });
|
||||
export const setupIntentError = e => ({
|
||||
type: SETUP_INTENT_ERROR,
|
||||
error: true,
|
||||
payload: e,
|
||||
});
|
||||
|
||||
export const stripeCustomerRequest = () => ({ type: STRIPE_CUSTOMER_REQUEST });
|
||||
export const stripeCustomerSuccess = () => ({ type: STRIPE_CUSTOMER_SUCCESS });
|
||||
export const stripeCustomerError = e => ({
|
||||
type: STRIPE_CUSTOMER_ERROR,
|
||||
error: true,
|
||||
payload: e,
|
||||
});
|
||||
// ================ Thunks ================ //
|
||||
|
||||
export const createStripeSetupIntent = () => (dispatch, getState, sdk) => {
|
||||
dispatch(setupIntentRequest());
|
||||
return sdk.stripeSetupIntents
|
||||
.create()
|
||||
.then(response => {
|
||||
const setupIntent = response.data.data;
|
||||
dispatch(setupIntentSuccess(setupIntent));
|
||||
return setupIntent;
|
||||
})
|
||||
.catch(e => {
|
||||
const error = storableError(e);
|
||||
log.error(error, 'create-setup-intent-failed');
|
||||
dispatch(setupIntentError(error));
|
||||
return { createStripeSetupIntentSuccess: false };
|
||||
});
|
||||
};
|
||||
|
||||
export const stripeCustomer = () => (dispatch, getState, sdk) => {
|
||||
dispatch(stripeCustomerRequest());
|
||||
|
||||
return dispatch(fetchCurrentUser({ include: ['stripeCustomer.defaultPaymentMethod'] }))
|
||||
.then(response => {
|
||||
dispatch(stripeCustomerSuccess());
|
||||
})
|
||||
.catch(e => {
|
||||
const error = storableError(e);
|
||||
log.error(error, 'fetch-stripe-customer-failed');
|
||||
dispatch(stripeCustomerError(error));
|
||||
});
|
||||
};
|
||||
|
||||
export const loadData = () => (dispatch, getState, sdk) => {
|
||||
dispatch(setInitialValuesForPaymentMethods());
|
||||
|
||||
return dispatch(stripeCustomer());
|
||||
};
|
||||
306
src/containers/PaymentMethodsPage/PaymentMethodsPage.js
Normal file
306
src/containers/PaymentMethodsPage/PaymentMethodsPage.js
Normal file
|
|
@ -0,0 +1,306 @@
|
|||
import React, { useState } from 'react';
|
||||
import { bool, func, object } from 'prop-types';
|
||||
import { compose } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import { injectIntl, intlShape, FormattedMessage } from 'react-intl';
|
||||
import { ensureCurrentUser, ensureStripeCustomer, ensurePaymentMethodCard } from '../../util/data';
|
||||
import { propTypes } from '../../util/types';
|
||||
import { savePaymentMethod, deletePaymentMethod } from '../../ducks/paymentMethods.duck';
|
||||
import { handleCardSetup } from '../../ducks/stripe.duck';
|
||||
import { manageDisableScrolling, isScrollingDisabled } from '../../ducks/UI.duck';
|
||||
import {
|
||||
SavedCardDetails,
|
||||
LayoutSideNavigation,
|
||||
LayoutWrapperMain,
|
||||
LayoutWrapperSideNav,
|
||||
LayoutWrapperTopbar,
|
||||
LayoutWrapperFooter,
|
||||
Footer,
|
||||
Page,
|
||||
UserNav,
|
||||
} from '../../components';
|
||||
import { TopbarContainer } from '../../containers';
|
||||
import { PaymentMethodsForm } from '../../forms';
|
||||
import { createStripeSetupIntent, stripeCustomer, loadData } from './PaymentMethodsPage.duck.js';
|
||||
|
||||
import css from './PaymentMethodsPage.css';
|
||||
|
||||
const PaymentMethodsPageComponent = props => {
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [cardState, setCardState] = useState(null);
|
||||
|
||||
const {
|
||||
currentUser,
|
||||
addPaymentMethodError,
|
||||
deletePaymentMethodError,
|
||||
createStripeCustomerError,
|
||||
handleCardSetupError,
|
||||
deletePaymentMethodInProgress,
|
||||
onCreateSetupIntent,
|
||||
onHandleCardSetup,
|
||||
onSavePaymentMethod,
|
||||
onDeletePaymentMethod,
|
||||
fetchStripeCustomer,
|
||||
scrollingDisabled,
|
||||
onManageDisableScrolling,
|
||||
intl,
|
||||
stripeCustomerFetched,
|
||||
} = props;
|
||||
|
||||
const getClientSecret = setupIntent => {
|
||||
return setupIntent && setupIntent.attributes ? setupIntent.attributes.clientSecret : null;
|
||||
};
|
||||
const getPaymentParams = (currentUser, formValues) => {
|
||||
const { name, addressLine1, addressLine2, postal, state, city, country } = formValues;
|
||||
const addressMaybe =
|
||||
addressLine1 && postal
|
||||
? {
|
||||
address: {
|
||||
city: city,
|
||||
country: country,
|
||||
line1: addressLine1,
|
||||
line2: addressLine2,
|
||||
postal_code: postal,
|
||||
state: state,
|
||||
},
|
||||
}
|
||||
: {};
|
||||
const billingDetails = {
|
||||
name,
|
||||
email: ensureCurrentUser(currentUser).attributes.email,
|
||||
...addressMaybe,
|
||||
};
|
||||
|
||||
const paymentParams = {
|
||||
payment_method_data: {
|
||||
billing_details: billingDetails,
|
||||
},
|
||||
};
|
||||
|
||||
return paymentParams;
|
||||
};
|
||||
|
||||
const handleSubmit = params => {
|
||||
setIsSubmitting(true);
|
||||
const ensuredCurrentUser = ensureCurrentUser(currentUser);
|
||||
const stripeCustomer = ensuredCurrentUser.stripeCustomer;
|
||||
const { stripe, card, formValues } = params;
|
||||
|
||||
onCreateSetupIntent()
|
||||
.then(setupIntent => {
|
||||
const stripeParams = {
|
||||
stripe,
|
||||
card,
|
||||
setupIntentClientSecret: getClientSecret(setupIntent),
|
||||
paymentParams: getPaymentParams(currentUser, formValues),
|
||||
};
|
||||
|
||||
return onHandleCardSetup(stripeParams);
|
||||
})
|
||||
.then(result => {
|
||||
const newPaymentMethod = result.setupIntent.payment_method;
|
||||
// Note: stripe.handleCardSetup might return an error inside successful call (200), but those are rejected in thunk functions.
|
||||
|
||||
return onSavePaymentMethod(stripeCustomer, newPaymentMethod);
|
||||
})
|
||||
.then(() => {
|
||||
// Update currentUser entity and its sub entities: stripeCustomer and defaultPaymentMethod
|
||||
fetchStripeCustomer();
|
||||
setIsSubmitting(false);
|
||||
setCardState('default');
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
setIsSubmitting(false);
|
||||
});
|
||||
};
|
||||
|
||||
const handleRemovePaymentMethod = () => {
|
||||
onDeletePaymentMethod().then(() => {
|
||||
fetchStripeCustomer();
|
||||
});
|
||||
};
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
text: <FormattedMessage id="PaymentMethodsPage.contactDetailsTabTitle" />,
|
||||
selected: false,
|
||||
linkProps: {
|
||||
name: 'ContactDetailsPage',
|
||||
},
|
||||
},
|
||||
{
|
||||
text: <FormattedMessage id="PaymentMethodsPage.passwordTabTitle" />,
|
||||
selected: false,
|
||||
linkProps: {
|
||||
name: 'PasswordChangePage',
|
||||
},
|
||||
},
|
||||
{
|
||||
text: <FormattedMessage id="PaymentMethodsPage.paymentsTabTitle" />,
|
||||
selected: false,
|
||||
linkProps: {
|
||||
name: 'PayoutPreferencesPage',
|
||||
},
|
||||
},
|
||||
{
|
||||
text: <FormattedMessage id="PaymentMethodsPage.paymentMethodsTabTitle" />,
|
||||
selected: true,
|
||||
linkProps: {
|
||||
name: 'PaymentMethodsPage',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const title = intl.formatMessage({ id: 'PaymentMethodsPage.title' });
|
||||
|
||||
const ensuredCurrentUser = ensureCurrentUser(currentUser);
|
||||
const currentUserLoaded = !!ensuredCurrentUser.id;
|
||||
|
||||
const hasDefaultPaymentMethod =
|
||||
currentUser &&
|
||||
ensureStripeCustomer(currentUser.stripeCustomer).attributes.stripeCustomerId &&
|
||||
ensurePaymentMethodCard(currentUser.stripeCustomer.defaultPaymentMethod).id;
|
||||
|
||||
// Get first and last name of the current user and use it in the StripePaymentForm to autofill the name field
|
||||
const userName = currentUserLoaded
|
||||
? `${ensuredCurrentUser.attributes.profile.firstName} ${
|
||||
ensuredCurrentUser.attributes.profile.lastName
|
||||
}`
|
||||
: null;
|
||||
|
||||
const initalValuesForStripePayment = { name: userName };
|
||||
|
||||
const card = hasDefaultPaymentMethod
|
||||
? ensurePaymentMethodCard(currentUser.stripeCustomer.defaultPaymentMethod).attributes.card
|
||||
: null;
|
||||
|
||||
const showForm = cardState === 'replaceCard' || !hasDefaultPaymentMethod;
|
||||
const showCardDetails = !!hasDefaultPaymentMethod;
|
||||
return (
|
||||
<Page title={title} scrollingDisabled={scrollingDisabled}>
|
||||
<LayoutSideNavigation>
|
||||
<LayoutWrapperTopbar>
|
||||
<TopbarContainer
|
||||
currentPage="PaymentMethodsPage"
|
||||
desktopClassName={css.desktopTopbar}
|
||||
mobileClassName={css.mobileTopbar}
|
||||
/>
|
||||
<UserNav selectedPageName="PaymentMethodsPage" />
|
||||
</LayoutWrapperTopbar>
|
||||
<LayoutWrapperSideNav tabs={tabs} />
|
||||
<LayoutWrapperMain>
|
||||
<div className={css.content}>
|
||||
<h1 className={css.title}>
|
||||
<FormattedMessage id="PaymentMethodsPage.heading" />
|
||||
</h1>
|
||||
{!stripeCustomerFetched ? null : (
|
||||
<>
|
||||
{showCardDetails ? (
|
||||
<SavedCardDetails
|
||||
card={card}
|
||||
onManageDisableScrolling={onManageDisableScrolling}
|
||||
onChange={setCardState}
|
||||
onDeleteCard={handleRemovePaymentMethod}
|
||||
deletePaymentMethodInProgress={deletePaymentMethodInProgress}
|
||||
/>
|
||||
) : null}
|
||||
{showForm ? (
|
||||
<PaymentMethodsForm
|
||||
className={css.paymentForm}
|
||||
formId="PaymentMethodsForm"
|
||||
initialValues={initalValuesForStripePayment}
|
||||
onSubmit={handleSubmit}
|
||||
handleRemovePaymentMethod={handleRemovePaymentMethod}
|
||||
hasDefaultPaymentMethod={hasDefaultPaymentMethod}
|
||||
addPaymentMethodError={addPaymentMethodError}
|
||||
deletePaymentMethodError={deletePaymentMethodError}
|
||||
createStripeCustomerError={createStripeCustomerError}
|
||||
handleCardSetupError={handleCardSetupError}
|
||||
inProgress={isSubmitting}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</LayoutWrapperMain>
|
||||
<LayoutWrapperFooter>
|
||||
<Footer />
|
||||
</LayoutWrapperFooter>
|
||||
</LayoutSideNavigation>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
PaymentMethodsPageComponent.defaultProps = {
|
||||
currentUser: null,
|
||||
addPaymentMethodError: null,
|
||||
deletePaymentMethodError: null,
|
||||
createStripeCustomerError: null,
|
||||
handleCardSetupError: null,
|
||||
};
|
||||
|
||||
PaymentMethodsPageComponent.propTypes = {
|
||||
currentUser: propTypes.currentUser,
|
||||
scrollingDisabled: bool.isRequired,
|
||||
addPaymentMethodError: object,
|
||||
deletePaymentMethodError: object,
|
||||
createStripeCustomerError: object,
|
||||
handleCardSetupError: object,
|
||||
onCreateSetupIntent: func.isRequired,
|
||||
onHandleCardSetup: func.isRequired,
|
||||
onSavePaymentMethod: func.isRequired,
|
||||
onDeletePaymentMethod: func.isRequired,
|
||||
fetchStripeCustomer: func.isRequired,
|
||||
|
||||
// from injectIntl
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
const mapStateToProps = state => {
|
||||
const { currentUser } = state.user;
|
||||
|
||||
const {
|
||||
deletePaymentMethodInProgress,
|
||||
addPaymentMethodError,
|
||||
deletePaymentMethodError,
|
||||
createStripeCustomerError,
|
||||
} = state.paymentMethods;
|
||||
|
||||
const { stripeCustomerFetched } = state.PaymentMethodsPage;
|
||||
|
||||
const { handleCardSetupError } = state.stripe;
|
||||
return {
|
||||
currentUser,
|
||||
scrollingDisabled: isScrollingDisabled(state),
|
||||
deletePaymentMethodInProgress,
|
||||
addPaymentMethodError,
|
||||
deletePaymentMethodError,
|
||||
createStripeCustomerError,
|
||||
handleCardSetupError,
|
||||
stripeCustomerFetched,
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
onManageDisableScrolling: (componentId, disableScrolling) =>
|
||||
dispatch(manageDisableScrolling(componentId, disableScrolling)),
|
||||
fetchStripeCustomer: () => dispatch(stripeCustomer()),
|
||||
onHandleCardSetup: params => dispatch(handleCardSetup(params)),
|
||||
onCreateSetupIntent: params => dispatch(createStripeSetupIntent(params)),
|
||||
onSavePaymentMethod: (stripeCustomer, newPaymentMethod) =>
|
||||
dispatch(savePaymentMethod(stripeCustomer, newPaymentMethod)),
|
||||
onDeletePaymentMethod: params => dispatch(deletePaymentMethod(params)),
|
||||
});
|
||||
|
||||
const PaymentMethodsPage = compose(
|
||||
connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
),
|
||||
injectIntl
|
||||
)(PaymentMethodsPageComponent);
|
||||
|
||||
PaymentMethodsPage.loadData = loadData;
|
||||
|
||||
export default PaymentMethodsPage;
|
||||
|
|
@ -12,6 +12,7 @@ export { default as NotFoundPage } from './NotFoundPage/NotFoundPage';
|
|||
export { default as PasswordChangePage } from './PasswordChangePage/PasswordChangePage';
|
||||
export { default as PasswordRecoveryPage } from './PasswordRecoveryPage/PasswordRecoveryPage';
|
||||
export { default as PasswordResetPage } from './PasswordResetPage/PasswordResetPage';
|
||||
export { default as PaymentMethodsPage } from './PaymentMethodsPage/PaymentMethodsPage';
|
||||
export { default as PayoutPreferencesPage } from './PayoutPreferencesPage/PayoutPreferencesPage';
|
||||
export { default as PrivacyPolicyPage } from './PrivacyPolicyPage/PrivacyPolicyPage';
|
||||
export { default as ProfilePage } from './ProfilePage/ProfilePage';
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import PasswordChangePage from './PasswordChangePage/PasswordChangePage.duck';
|
|||
import PasswordRecoveryPage from './PasswordRecoveryPage/PasswordRecoveryPage.duck';
|
||||
import PasswordResetPage from './PasswordResetPage/PasswordResetPage.duck';
|
||||
import PayoutPreferencesPage from './PayoutPreferencesPage/PayoutPreferencesPage.duck';
|
||||
import PaymentMethodsPage from './PaymentMethodsPage/PaymentMethodsPage.duck';
|
||||
import ProfilePage from './ProfilePage/ProfilePage.duck';
|
||||
import ProfileSettingsPage from './ProfileSettingsPage/ProfileSettingsPage.duck';
|
||||
import SearchPage from './SearchPage/SearchPage.duck';
|
||||
|
|
@ -29,6 +30,7 @@ export {
|
|||
PasswordRecoveryPage,
|
||||
PasswordResetPage,
|
||||
PayoutPreferencesPage,
|
||||
PaymentMethodsPage,
|
||||
ProfilePage,
|
||||
ProfileSettingsPage,
|
||||
SearchPage,
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import {
|
|||
PasswordRecoveryPage,
|
||||
PasswordResetPage,
|
||||
PayoutPreferencesPage,
|
||||
PaymentMethodsPage,
|
||||
PrivacyPolicyPage,
|
||||
ProfilePage,
|
||||
ProfileSettingsPage,
|
||||
|
|
@ -33,6 +34,7 @@ export const ACCOUNT_SETTINGS_PAGES = [
|
|||
'ContactDetailsPage',
|
||||
'PasswordChangePage',
|
||||
'PayoutPreferencesPage',
|
||||
'PaymentMethodsPage',
|
||||
];
|
||||
|
||||
// https://en.wikipedia.org/wiki/Universally_unique_identifier#Nil_UUID
|
||||
|
|
@ -249,6 +251,14 @@ const routeConfiguration = () => {
|
|||
component: props => <PayoutPreferencesPage {...props} />,
|
||||
loadData: PayoutPreferencesPage.loadData,
|
||||
},
|
||||
{
|
||||
path: '/account/payment-methods',
|
||||
name: 'PaymentMethodsPage',
|
||||
auth: true,
|
||||
authPage: 'LoginPage',
|
||||
component: props => <PaymentMethodsPage {...props} />,
|
||||
loadData: PaymentMethodsPage.loadData,
|
||||
},
|
||||
{
|
||||
path: '/terms-of-service',
|
||||
name: 'TermsOfServicePage',
|
||||
|
|
|
|||
|
|
@ -475,6 +475,16 @@
|
|||
"PaymentMethodsForm.infoText": "I authorise Saunatime to send instructions to the financial institution that issued my card to take payments from my card account in accordance with the terms of my agreement with you.",
|
||||
"PaymentMethodsForm.paymentHeading": "Payment",
|
||||
"PaymentMethodsForm.submitPaymentInfo": "Save credit card",
|
||||
"PaymentMethodsPage.contactDetailsTabTitle": "Contact details",
|
||||
"PaymentMethodsPage.deletePaymentMethod": "Delete card",
|
||||
"PaymentMethodsPage.heading": "Payment methods",
|
||||
"PaymentMethodsPage.loadingData": "Loading data…",
|
||||
"PaymentMethodsPage.passwordTabTitle": "Password",
|
||||
"PaymentMethodsPage.paymentsTabTitle": "Payout details",
|
||||
"PaymentMethodsPage.paymentMethodsTabTitle": "Payment methods",
|
||||
"PaymentMethodsPage.savedPaymentMethodTitle": "Credit card details",
|
||||
"PaymentMethodsPage.savedPaymentMethodPlaceholder": "•••• •••• •••• {lastFour}",
|
||||
"PaymentMethodsPage.title": "Payment methods",
|
||||
"PayoutDetailsForm.accountOpenerInfoText": "Account opener needs to be an individual with authorization to sign on behalf of the organization.",
|
||||
"PayoutDetailsForm.accountOpenerTitle": "Account opener",
|
||||
"PayoutDetailsForm.accountTypeTitle": "Account type",
|
||||
|
|
@ -694,11 +704,11 @@
|
|||
"ReviewModal.later": "Later",
|
||||
"ReviewModal.title": "Leave a review for {revieweeName}",
|
||||
"SavedCardDetails.cancel": "Cancel",
|
||||
"SavedCardDetails.deletePaymentMethod": "Delete payment card",
|
||||
"SavedCardDetails.deletePaymentMethod": "Remove payment card",
|
||||
"SavedCardDetails.expiredCardText": "This payment card has expired",
|
||||
"SavedCardDetails.removeCard": "Remove card",
|
||||
"SavedCardDetails.removeCardModalContent": "The card will be removed from your account and can't be used for future purchases.",
|
||||
"SavedCardDetails.removeCardModalTitle": "Are you sure you want to remove this credit card?",
|
||||
"SavedCardDetails.removeCardModalTitle": "Are you sure you want to remove this payment card?",
|
||||
"SavedCardDetails.replaceCardText": "Credit or debit card",
|
||||
"SavedCardDetails.replaceCardTitle": "Use another payment method",
|
||||
"SavedCardDetails.savedPaymentMethodPlaceholderDesktop": "•••• •••• •••• {lastFour}",
|
||||
|
|
@ -816,7 +826,7 @@
|
|||
"StripePaymentAddress.addressLine1Label": "Street address",
|
||||
"StripePaymentAddress.addressLine1Placeholder": "123 Example Street",
|
||||
"StripePaymentAddress.addressLine1Required": "Street address is required",
|
||||
"StripePaymentAddress.addressLine2Label": "Apt, suite, building # {optionalText}",
|
||||
"StripePaymentAddress.addressLine2Label": "Apt # {optionalText}",
|
||||
"StripePaymentAddress.addressLine2Placeholder": "A 42",
|
||||
"StripePaymentAddress.cityLabel": "City",
|
||||
"StripePaymentAddress.cityPlaceholder": "Helsinki",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue