payment method page
new page to add user's payment method (credit card / paypal) alone with billing address details
This commit is contained in:
parent
c7ce60e0e4
commit
a21dde88e0
|
|
@ -12,5 +12,5 @@ REACT_APP_SITE_KEY=6Let8dgSAAAAAFheKGWrs6iaq_hIlPOZ2f3Bb56B
|
|||
REACT_APP_GOOGLE_ANALYTICS_ID=G-MC9BTWXWY4
|
||||
PUBLIC_URL=
|
||||
REACT_APP_COOKIE_DOMAIN=.jamkazam.local
|
||||
REACT_APP_STRIPE_PUBLISHABLE_KEY=pk_test_9vO8ZnxBpb9Udb0paruV3qLv
|
||||
REACT_APP_RECURRY_PUBLIC_API_KEY=ewr1-hvDV1xQxDw0HPaaRFP4KNE
|
||||
REACT_APP_RECURLY_PUBLIC_API_KEY=ewr1-hvDV1xQxDw0HPaaRFP4KNE
|
||||
REACT_APP_BRAINTREE_TOKEN=sandbox_pgjp8dvs_5v5rwm94m2vrfbms
|
||||
|
|
@ -9,5 +9,5 @@ REACT_APP_BITBUCKET_COMMIT=dev
|
|||
REACT_APP_ENV=development
|
||||
REACT_APP_COOKIE_DOMAIN=.jamkazam.com
|
||||
REACT_APP_GOOGLE_ANALYTICS_ID=G-MC9BTWXWY4
|
||||
REACT_APP_STRIPE_PUBLISHABLE_KEY=
|
||||
REACT_APP_RECURRY_PUBLIC_API_KEY=
|
||||
REACT_APP_RECURLY_PUBLIC_API_KEY=
|
||||
REACT_APP_BRAINTREE_TOKEN=
|
||||
|
|
@ -9,5 +9,5 @@ REACT_APP_RECAPTCHA_ENABLED=true
|
|||
REACT_APP_SITE_KEY=6Let8dgSAAAAAFheKGWrs6iaq_hIlPOZ2f3Bb56B
|
||||
REACT_APP_COOKIE_DOMAIN=.jamkazam.com
|
||||
REACT_APP_GOOGLE_ANALYTICS_ID=G-SPTNJRW7WB
|
||||
REACT_APP_STRIPE_PUBLISHABLE_KEY=
|
||||
REACT_APP_RECURRY_PUBLIC_API_KEY=
|
||||
REACT_APP_RECURLY_PUBLIC_API_KEY=
|
||||
REACT_APP_BRAINTREE_TOKEN=
|
||||
|
|
@ -9,5 +9,5 @@ REACT_APP_RECAPTCHA_ENABLED=false
|
|||
REACT_APP_SITE_KEY=6Let8dgSAAAAAFheKGWrs6iaq_hIlPOZ2f3Bb56B
|
||||
REACT_APP_COOKIE_DOMAIN=.staging.jamkazam.com
|
||||
REACT_APP_GOOGLE_ANALYTICS_ID=G-8W0GTL53NT
|
||||
REACT_APP_STRIPE_PUBLISHABLE_KEY=
|
||||
REACT_APP_RECURRY_PUBLIC_API_KEY=
|
||||
REACT_APP_RECURLY_PUBLIC_API_KEY=
|
||||
REACT_APP_BRAINTREE_TOKEN=
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -278,7 +278,7 @@ function JKDashboardMain() {
|
|||
useScript(`${process.env.REACT_APP_CLIENT_BASE_URL}/client_scripts`, initJKScripts);
|
||||
useScript('https://js.recurly.com/v4/recurly.js', () => {
|
||||
console.log('Recurly.js script loaded');
|
||||
window.recurly.configure(process.env.REACT_APP_RECURRY_PUBLIC_API_KEY);
|
||||
window.recurly.configure(process.env.REACT_APP_RECURLY_PUBLIC_API_KEY);
|
||||
});
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -1,29 +1,504 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { Row, Col, Card, CardBody } from 'reactstrap';
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import {
|
||||
Card,
|
||||
CardBody,
|
||||
Col,
|
||||
Button,
|
||||
Row,
|
||||
CustomInput,
|
||||
Label
|
||||
} from 'reactstrap';
|
||||
import Flex from '../common/Flex';
|
||||
import FalconCardHeader from '../common/FalconCardHeader';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import JKBillingDetails from '../payments/JKBillingDetails';
|
||||
import JKPaymentOptions from '../payments/JKPaymentOptions';
|
||||
import iconPaymentMethodsGrid from '../../assets/img/icons/icon-payment-methods-grid.png';
|
||||
import iconPaypalFull from '../../assets/img/icons/icon-paypal-full.png';
|
||||
import { toast } from 'react-toastify';
|
||||
import { updatePayment } from '../../helpers/rest';
|
||||
import { useAuth } from '../../context/UserAuth';
|
||||
import { getBillingInfo, updateBillingInfo, getUserDetail, getCountries } from '../../helpers/rest';
|
||||
import { useForm, Controller } from 'react-hook-form';
|
||||
import Select from 'react-select';
|
||||
import { useResponsive } from '@farfetch/react-context-responsive';
|
||||
|
||||
const JKPaymentMethod = () => {
|
||||
const { t } = useTranslation('account');
|
||||
const [billingInfo, setBillingInfo] = useState({});
|
||||
const [hasStoredCreditCard, setHasStoredCreditCard] = useState(false);
|
||||
const [paymentMethod, setPaymentMethod] = useState('credit-card');
|
||||
const { currentUser } = useAuth();
|
||||
const [countries, setCountries] = useState([]);
|
||||
const labelClassName = 'ls text-600 font-weight-semi-bold mb-0';
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [billingDataLoaded, setBillingDataLoaded] = useState(false);
|
||||
const [isCardValid, setIsCardValid] = useState(false);
|
||||
const { greaterThan } = useResponsive();
|
||||
|
||||
const elementsRef = useRef(null);
|
||||
const formRef = useRef(null);
|
||||
const recurlyConfigured = useRef(false);
|
||||
const paypal = useRef(null);
|
||||
|
||||
const {
|
||||
register,
|
||||
control,
|
||||
handleSubmit,
|
||||
setValue,
|
||||
formState: { errors }
|
||||
} = useForm({
|
||||
defaultValues: {
|
||||
first_name: '',
|
||||
last_name: '',
|
||||
address1: '',
|
||||
address2: '',
|
||||
city: '',
|
||||
state: '',
|
||||
zip: '',
|
||||
country: 'US',
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (currentUser) {
|
||||
populateUserData();
|
||||
}
|
||||
}, [currentUser]);
|
||||
|
||||
const populateUserData = async () => {
|
||||
const options = {
|
||||
id: currentUser.id
|
||||
};
|
||||
try {
|
||||
const userResp = await getUserDetail(options);
|
||||
const userData = await userResp.json();
|
||||
if (userData.has_recurly_account) {
|
||||
setHasStoredCreditCard(userData.has_stored_credit_card);
|
||||
await populateBillingAddress();
|
||||
setBillingDataLoaded(true);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to get user details:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const populateBillingAddress = async () => {
|
||||
try {
|
||||
const resp = await getBillingInfo();
|
||||
const data = await resp.json();
|
||||
const bi = data.billing_info;
|
||||
setBillingInfo(bi);
|
||||
} catch (error) {
|
||||
console.error('Failed to get billing info:', error);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (currentUser) {
|
||||
fetchCountries();
|
||||
}
|
||||
}, [currentUser]);
|
||||
|
||||
const fetchCountries = () => {
|
||||
getCountries()
|
||||
.then(response => {
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
}
|
||||
})
|
||||
.then(data => {
|
||||
setCountries(data.countriesx);
|
||||
})
|
||||
.catch(error => console.log(error));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (billingInfo) {
|
||||
setValue('first_name', billingInfo.first_name || '');
|
||||
setValue('last_name', billingInfo.last_name || '');
|
||||
setValue('address1', billingInfo.address1 || '');
|
||||
setValue('address2', billingInfo.address2 || '');
|
||||
setValue('city', billingInfo.city || '');
|
||||
setValue('state', billingInfo.state || '');
|
||||
setValue('zip', billingInfo.zip || '');
|
||||
setValue('country', billingInfo.country || 'US');
|
||||
}
|
||||
}, [billingInfo, setValue]);
|
||||
|
||||
const handleCountryChange = selectedOption => {
|
||||
setValue('country', selectedOption.value);
|
||||
};
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (!window.recurly) return;
|
||||
|
||||
if (recurlyConfigured.current) return;
|
||||
|
||||
const interval = setInterval(() => {
|
||||
const container = document.querySelector('#recurly-elements');
|
||||
console.log('Checking for Recurly Elements container:', container);
|
||||
if (container && window.recurly) {
|
||||
console.log('Initializing Recurly Elements...');
|
||||
window.recurly.configure({ publicKey: process.env.REACT_APP_RECURLY_PUBLIC_API_KEY });
|
||||
const elements = window.recurly.Elements();
|
||||
const cardElement = elements.CardElement();
|
||||
cardElement.attach('#recurly-elements');
|
||||
|
||||
cardElement.on('change', (event) => {
|
||||
if (event.complete) {
|
||||
setIsCardValid(true);
|
||||
} else if (event.error) {
|
||||
setIsCardValid(false);
|
||||
} else {
|
||||
setIsCardValid(false);
|
||||
}
|
||||
});
|
||||
|
||||
//then load paypal:
|
||||
const paypalInst = window.recurly.PayPal({ braintree: { clientAuthorization: process.env.REACT_APP_BRAINTREE_TOKEN } })
|
||||
paypal.current = paypalInst;
|
||||
paypal.current.on('error', onPayPalError);
|
||||
paypal.current.on('token', onPayPalToken);
|
||||
|
||||
elementsRef.current = elements;
|
||||
recurlyConfigured.current = true;
|
||||
clearInterval(interval);
|
||||
}
|
||||
}, 100);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
const onPayPalError = (error) => {
|
||||
console.error('PayPal Error:', error);
|
||||
toast.error('PayPal Error: ' + (error.message || t('payment_method.alerts.try_again')));
|
||||
setSubmitting(false);
|
||||
}
|
||||
|
||||
const onPayPalToken = (token) => {
|
||||
handleUpdatePayment(token);
|
||||
}
|
||||
|
||||
const handleUpdatePayment = (token) => {
|
||||
updatePayment({ recurly_token: token.id }).then((response) => {
|
||||
toast.success(t('payment_method.alerts.payment_method_updated'));
|
||||
}).catch((error) => {
|
||||
console.error('Error updating payment with PayPal token:', error);
|
||||
if (error.response && error.response.data && error.response.data.message) {
|
||||
toast.error(error.response.data.message);
|
||||
} else {
|
||||
console.error('Error updating payment with PayPal token:', error);
|
||||
toast.error(t('payment_method.alerts.card_update_error'));
|
||||
}
|
||||
}).finally(() => {
|
||||
setSubmitting(false);
|
||||
});
|
||||
};
|
||||
|
||||
const onSubmit = async (data) => {
|
||||
//first update billing address
|
||||
setSubmitting(true);
|
||||
const resp = await updateBillingInfo(data)
|
||||
if (!resp.ok) {
|
||||
setSubmitting(false);
|
||||
const errorData = await resp.json();
|
||||
console.error('Error updating billing info:', errorData);
|
||||
toast.error(errorData.message || t('payment_method.alerts.billing_update_error'));
|
||||
return;
|
||||
}
|
||||
if (paymentMethod === 'paypal') {
|
||||
handoverToPaypal();
|
||||
return;
|
||||
} else {
|
||||
|
||||
if (!elementsRef.current) {
|
||||
console.error('Recurly elementsRef.current is not ready');
|
||||
setSubmitting(false);
|
||||
return;
|
||||
}
|
||||
if (!formRef.current) {
|
||||
console.error('formRef.current is not ready');
|
||||
setSubmitting(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isCardValid) {
|
||||
console.error('Card is not valid');
|
||||
toast.error(t('payment_method.validations.card.invalid'));
|
||||
setSubmitting(false);
|
||||
return;
|
||||
}
|
||||
|
||||
window.recurly.token(elementsRef.current, formRef.current, (err, token) => {
|
||||
if (err) {
|
||||
console.error('Recurly token error:', err);
|
||||
toast.error(err.message || t('payment_method.alerts.card_processing_error'));
|
||||
setSubmitting(false);
|
||||
} else {
|
||||
console.log('Recurly token:', token.id);
|
||||
// send token.id to backend
|
||||
handleUpdatePayment(token);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
const handoverToPaypal = () => {
|
||||
// Handover to Paypal
|
||||
setSubmitting(true);
|
||||
paypal.current.start()
|
||||
};
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<FalconCardHeader title={t('subscription.page_title')} titleClass="font-weight-bold" />
|
||||
<FalconCardHeader title={t('payment_method.page_title')} titleClass="font-weight-bold" />
|
||||
<CardBody className="pt-3" style={{ backgroundColor: '#edf2f9' }}>
|
||||
{true ? (
|
||||
<Row>
|
||||
<Col className="mb-2" xs={12} md={6} lg={5}>
|
||||
<JKBillingDetails billingInfo={billingInfo} setBillingInfo={setBillingInfo} />
|
||||
</Col>
|
||||
<Col xs={12} md={6} lg={5}>
|
||||
<JKPaymentOptions billingInfo={billingInfo} />
|
||||
</Col>
|
||||
<Col className='d-none d-sm-block' />
|
||||
</Row>
|
||||
) : 'Loading...' }
|
||||
<div className='mb-3'>
|
||||
{hasStoredCreditCard ? (
|
||||
<span>
|
||||
<strong>{t('payment_method.help_text_has_card')}</strong>
|
||||
</span>
|
||||
) : (
|
||||
<span>
|
||||
<strong>{t('payment_method.help_text_no_card')}</strong>
|
||||
</span>
|
||||
)}
|
||||
{t('payment_method.help_text')}
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit(onSubmit)} ref={formRef}>
|
||||
<Card style={{ width: greaterThan.sm ? "90%" : '100%' }} className='mx-auto'>
|
||||
<FalconCardHeader title={t('payment_method.header')} titleTag="h5" />
|
||||
<CardBody>
|
||||
<Row>
|
||||
<Col className="mb-2" xs={12} md={6}>
|
||||
<Row className="mb-2">
|
||||
<Col xs={12} md={5} lg={4} className="text-md-right">
|
||||
<Label for="first_name" className={labelClassName}>
|
||||
{t('payment_method.first_name')}
|
||||
</Label>
|
||||
</Col>
|
||||
<Col>
|
||||
<input {...register('first_name', { required: t('payment_method.validations.first_name.required') })} className="form-control" data-recurly="first_name" />
|
||||
{errors.first_name && (
|
||||
<div className="text-danger">
|
||||
<small>{errors.first_name.message}</small>
|
||||
</div>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className="mb-2">
|
||||
<Col xs={12} md={5} lg={4} className="text-md-right">
|
||||
<Label for="last_name" className={labelClassName}>
|
||||
{t('payment_method.last_name')}
|
||||
</Label>
|
||||
</Col>
|
||||
<Col>
|
||||
<input {...register('last_name', { required: t('payment_method.validations.last_name.required') })} className="form-control" data-recurly="last_name" />
|
||||
{errors.last_name && (
|
||||
<div className="text-danger">
|
||||
<small>{errors.last_name.message}</small>
|
||||
</div>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className="mb-2">
|
||||
<Col xs={12} md={5} lg={4} className="text-md-right">
|
||||
<Label for="address1" className={labelClassName}>
|
||||
{t('payment_method.address1')}
|
||||
</Label>
|
||||
</Col>
|
||||
<Col>
|
||||
<input {...register('address1', { required: t('payment_method.validations.address1.required') })} className="form-control" data-recurly="address1" />
|
||||
{errors.address1 && (
|
||||
<div className="text-danger">
|
||||
<small>{errors.address1.message}</small>
|
||||
</div>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className="mb-2">
|
||||
<Col xs={12} md={5} lg={4} className="text-md-right">
|
||||
<Label for="address2" className={labelClassName}>
|
||||
{t('payment_method.address2')}
|
||||
</Label>
|
||||
</Col>
|
||||
<Col>
|
||||
<input {...register('address2')} className="form-control" data-recurly="address2" />
|
||||
{errors.address2 && (
|
||||
<div className="text-danger">
|
||||
<small>{errors.address2.message}</small>
|
||||
</div>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className="mb-2">
|
||||
<Col xs={12} md={5} lg={4} className="text-md-right">
|
||||
<Label for="city" className={labelClassName}>
|
||||
{t('payment_method.city')}
|
||||
</Label>
|
||||
</Col>
|
||||
<Col>
|
||||
<input {...register('city', { required: t('payment_method.validations.city.required') })} className="form-control" data-recurly="city" />
|
||||
{errors.city && (
|
||||
<div className="text-danger">
|
||||
<small>{errors.city.message}</small>
|
||||
</div>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className="mb-2">
|
||||
<Col xs={12} md={5} lg={4} className="text-md-right">
|
||||
<Label for="state" className={labelClassName}>
|
||||
{t('payment_method.state')}
|
||||
</Label>
|
||||
</Col>
|
||||
<Col>
|
||||
<input {...register('state', { required: t('payment_method.validations.state.required') })} className="form-control" data-recurly="state" />
|
||||
{errors.state && (
|
||||
<div className="text-danger">
|
||||
<small>{errors.state.message}</small>
|
||||
</div>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className="mb-2">
|
||||
<Col xs={12} md={5} lg={4} className="text-md-right">
|
||||
<Label for="zip" className={labelClassName}>
|
||||
{t('payment_method.zip_code')}
|
||||
</Label>
|
||||
</Col>
|
||||
<Col>
|
||||
<input
|
||||
{...register('zip', { required: t('payment_method.validations.zip_code.required') })}
|
||||
className="form-control" data-recurly="postal_code"
|
||||
/>
|
||||
{errors.zip && (
|
||||
<div className="text-danger">
|
||||
<small>{errors.zip.message}</small>
|
||||
</div>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className="mb-2">
|
||||
<Col xs={12} md={5} lg={4} className="text-md-right">
|
||||
<Label for="country" className={labelClassName}>
|
||||
{t('payment_method.country')}
|
||||
</Label>
|
||||
</Col>
|
||||
<Col>
|
||||
<Controller
|
||||
name="country"
|
||||
control={control}
|
||||
rules={{ required: t('payment_method.validations.country.required') }}
|
||||
render={({ field: { onChange, value } }) => {
|
||||
const country = countries.find(country => country.countrycode === value);
|
||||
if (!country) {
|
||||
return (
|
||||
<Select
|
||||
data-testid="countrySelect"
|
||||
data-recurly="country"
|
||||
onChange={handleCountryChange}
|
||||
options={countries.map(c => {
|
||||
return { value: c.countrycode, label: c.countryname };
|
||||
})}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Select
|
||||
data-testid="countrySelect"
|
||||
data-recurly="country"
|
||||
value={{ value: country.countrycode, label: country.countryname }}
|
||||
onChange={handleCountryChange}
|
||||
options={countries.map(c => {
|
||||
return { value: c.countrycode, label: c.countryname };
|
||||
})}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<input type="hidden" name="country" data-recurly="country" {...register('country')} />
|
||||
{errors.country && (
|
||||
<div className="text-danger">
|
||||
<small>{errors.country.message}</small>
|
||||
</div>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
<Col xs={12} md={6} className="mb-2 pl-5">
|
||||
<Row>
|
||||
<Col xs={12}>
|
||||
<CustomInput
|
||||
label={
|
||||
<Flex align="center" className="mb-2 fs-1">
|
||||
<span>{t('payment_method.credit_card')}</span>
|
||||
</Flex>
|
||||
}
|
||||
id="credit-card"
|
||||
value="credit-card"
|
||||
checked={paymentMethod === 'credit-card'}
|
||||
onChange={({ target }) => setPaymentMethod(target.value)}
|
||||
type="radio"
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col sm={8}>
|
||||
<div id="recurly-elements"></div>
|
||||
{!isCardValid && errors.recurly && (
|
||||
<div className="text-danger">
|
||||
<small>{errors.recurly.message}</small>
|
||||
</div>
|
||||
)}
|
||||
<input type="hidden" name="recurly-token" data-recurly="token" />
|
||||
</Col>
|
||||
<div className="col-4 text-center pt-2 d-none d-sm-block">
|
||||
<div className="rounded p-2 mt-3 bg-100">
|
||||
<div className="text-uppercase fs--2 font-weight-bold">{t('payment_method.we_accept')}</div>
|
||||
<img src={iconPaymentMethodsGrid} alt="" width="120" />
|
||||
</div>
|
||||
</div>
|
||||
</Row>
|
||||
<hr />
|
||||
<Row className="mt-3">
|
||||
<Col xs={12}>
|
||||
<CustomInput
|
||||
label={<img className="pull-right" src={iconPaypalFull} height="20" alt="" />}
|
||||
id="paypal"
|
||||
value="paypal"
|
||||
checked={paymentMethod === 'paypal'}
|
||||
onChange={({ target }) => setPaymentMethod(target.value)}
|
||||
type="radio"
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<div className="d-flex justify-content-center">
|
||||
<Button
|
||||
color="primary"
|
||||
type="submit"
|
||||
disabled={submitting || !billingDataLoaded }
|
||||
className="mt-3"
|
||||
>
|
||||
{submitting ? t('payment_method.submitting') : t('payment_method.save_payment_info')}
|
||||
</Button>
|
||||
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<p className="fs--1 mt-3 mb-0">
|
||||
{t('payment_method.aggreement.text1')} <strong>{t('payment_method.aggreement.text2')} </strong>{t('payment_method.aggreement.text3')}{' '}
|
||||
<br />
|
||||
<a href="https://www.jamkazam.com/corp/terms" target='_blank'>{t('payment_method.aggreement.terms_of_service')}</a>
|
||||
</p>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</form>
|
||||
|
||||
</CardBody>
|
||||
</Card>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,310 +0,0 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
Card,
|
||||
CardBody,
|
||||
Col,
|
||||
Button,
|
||||
Row,
|
||||
Label
|
||||
} from 'reactstrap';
|
||||
import Select from 'react-select';
|
||||
import FalconCardHeader from '../common/FalconCardHeader';
|
||||
import { useForm, Controller } from 'react-hook-form';
|
||||
import Flex from '../common/Flex';
|
||||
import { getBillingInfo, updateBillingInfo, getUserDetail, getCountries } from '../../helpers/rest';
|
||||
import { useAuth } from '../../context/UserAuth';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
const JKBillingDetails = ({ billingInfo, setBillingInfo}) => {
|
||||
const { currentUser } = useAuth();
|
||||
|
||||
//const [billingInfo, setBillingInfo] = useState({});
|
||||
const [countries, setCountries] = useState([]);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
|
||||
const labelClassName = 'ls text-600 font-weight-semi-bold mb-0';
|
||||
|
||||
const {
|
||||
register,
|
||||
control,
|
||||
handleSubmit,
|
||||
setValue,
|
||||
setError,
|
||||
formState: { errors }
|
||||
} = useForm({
|
||||
defaultValues: {
|
||||
first_name: '',
|
||||
last_name: '',
|
||||
address1: '',
|
||||
address2: '',
|
||||
city: '',
|
||||
state: '',
|
||||
zip: '',
|
||||
country: 'US',
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (currentUser) {
|
||||
fetchCountries();
|
||||
populateData();
|
||||
}
|
||||
}, [currentUser]);
|
||||
|
||||
const fetchCountries = () => {
|
||||
getCountries()
|
||||
.then(response => {
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
}
|
||||
})
|
||||
.then(data => {
|
||||
setCountries(data.countriesx);
|
||||
})
|
||||
.catch(error => console.log(error));
|
||||
};
|
||||
|
||||
const populateData = async () => {
|
||||
const options = {
|
||||
id: currentUser.id
|
||||
};
|
||||
try {
|
||||
const userResp = await getUserDetail(options);
|
||||
const userData = await userResp.json();
|
||||
if (userData.has_recurly_account) {
|
||||
await populateBillingAddress();
|
||||
} else {
|
||||
setValue('first_name', userData.first_name);
|
||||
setValue('last_name', userData.last_name);
|
||||
setValue('address1', userData.address1);
|
||||
setValue('address2', userData.address2);
|
||||
setValue('city', userData.city);
|
||||
setValue('state', userData.state);
|
||||
setValue('zip', userData.zip);
|
||||
setValue('country', userData.country);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to get user details:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const populateBillingAddress = async () => {
|
||||
try {
|
||||
const resp = await getBillingInfo();
|
||||
const data = await resp.json();
|
||||
const bi = data.billing_info;
|
||||
|
||||
setValue('first_name', bi.first_name);
|
||||
setValue('last_name', bi.last_name);
|
||||
setValue('address1', bi.address1);
|
||||
setValue('address2', billingInfo.address2);
|
||||
setValue('city', bi.city);
|
||||
setValue('state', bi.state);
|
||||
setValue('zip', bi.zip);
|
||||
setValue('country', bi.country);
|
||||
|
||||
setBillingInfo(bi);
|
||||
} catch (error) {
|
||||
console.error('Failed to get billing info:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCountryChange = selectedOption => {
|
||||
setValue('country', selectedOption.value);
|
||||
};
|
||||
|
||||
const onSubmit = async data => {
|
||||
setSubmitting(true);
|
||||
try {
|
||||
const response = await updateBillingInfo(data);
|
||||
if (response.ok) {
|
||||
toast.success('Billing details updated successfully');
|
||||
const updatedData = await response.json();
|
||||
setBillingInfo(updatedData.billing_info);
|
||||
} else {
|
||||
toast.error('Failed to update billing details');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error updating billing details:', error);
|
||||
toast.error('An error occurred while updating billing details');
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<Card className="mb-3">
|
||||
<FalconCardHeader title="Billing Address" titleTag="h5" />
|
||||
<CardBody>
|
||||
<Row className="mb-2">
|
||||
<Col xs={12} md={5} lg={4} className="text-md-right">
|
||||
<Label for="first_name" className={labelClassName}>
|
||||
First Name
|
||||
</Label>
|
||||
</Col>
|
||||
<Col>
|
||||
<input {...register('first_name', { required: 'First Name is required' })} className="form-control" />
|
||||
{errors.first_name && (
|
||||
<div className="text-danger">
|
||||
<small>{errors.first_name.message}</small>
|
||||
</div>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className="mb-2">
|
||||
<Col xs={12} md={5} lg={4} className="text-md-right">
|
||||
<Label for="last_name" className={labelClassName}>
|
||||
Last Name
|
||||
</Label>
|
||||
</Col>
|
||||
<Col>
|
||||
<input {...register('last_name', { required: 'Last Name is required' })} className="form-control" />
|
||||
{errors.last_name && (
|
||||
<div className="text-danger">
|
||||
<small>{errors.last_name.message}</small>
|
||||
</div>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className="mb-2">
|
||||
<Col xs={12} md={5} lg={4} className="text-md-right">
|
||||
<Label for="address1" className={labelClassName}>
|
||||
Address 1
|
||||
</Label>
|
||||
</Col>
|
||||
<Col>
|
||||
<input {...register('address1', { required: 'Address is required' })} className="form-control" />
|
||||
{errors.address1 && (
|
||||
<div className="text-danger">
|
||||
<small>{errors.address1.message}</small>
|
||||
</div>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className="mb-2">
|
||||
<Col xs={12} md={5} lg={4} className="text-md-right">
|
||||
<Label for="address2" className={labelClassName}>
|
||||
Address 2
|
||||
</Label>
|
||||
</Col>
|
||||
<Col>
|
||||
<input {...register('address2')} className="form-control" />
|
||||
{errors.address2 && (
|
||||
<div className="text-danger">
|
||||
<small>{errors.address2.message}</small>
|
||||
</div>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className="mb-2">
|
||||
<Col xs={12} md={5} lg={4} className="text-md-right">
|
||||
<Label for="city" className={labelClassName}>
|
||||
City
|
||||
</Label>
|
||||
</Col>
|
||||
<Col>
|
||||
<input {...register('city', { required: 'City is required' })} className="form-control" />
|
||||
{errors.city && (
|
||||
<div className="text-danger">
|
||||
<small>{errors.city.message}</small>
|
||||
</div>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className="mb-2">
|
||||
<Col xs={12} md={5} lg={4} className="text-md-right">
|
||||
<Label for="state" className={labelClassName}>
|
||||
State or Region
|
||||
</Label>
|
||||
</Col>
|
||||
<Col>
|
||||
<input {...register('state', { required: 'State or Region is required' })} className="form-control" />
|
||||
{errors.state && (
|
||||
<div className="text-danger">
|
||||
<small>{errors.state.message}</small>
|
||||
</div>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className="mb-2">
|
||||
<Col xs={12} md={5} lg={4} className="text-md-right">
|
||||
<Label for="zip" className={labelClassName}>
|
||||
Zip or Postal Code
|
||||
</Label>
|
||||
</Col>
|
||||
<Col>
|
||||
<input
|
||||
{...register('zip', { required: 'Zip or Postal Code is required' })}
|
||||
className="form-control"
|
||||
/>
|
||||
{errors.zip && (
|
||||
<div className="text-danger">
|
||||
<small>{errors.zip.message}</small>
|
||||
</div>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className="mb-2">
|
||||
<Col xs={12} md={5} lg={4} className="text-md-right">
|
||||
<Label for="country" className={labelClassName}>
|
||||
Country
|
||||
</Label>
|
||||
</Col>
|
||||
<Col>
|
||||
<Controller
|
||||
name="country"
|
||||
control={control}
|
||||
rules={{ required: 'Country is required' }}
|
||||
render={({ field: { onChange, value } }) => {
|
||||
const country = countries.find(country => country.countrycode === value);
|
||||
if (!country) {
|
||||
return (
|
||||
<Select
|
||||
data-testid="countrySelect"
|
||||
onChange={handleCountryChange}
|
||||
options={countries.map(c => {
|
||||
return { value: c.countrycode, label: c.countryname };
|
||||
})}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Select
|
||||
data-testid="countrySelect"
|
||||
value={{ value: country.countrycode, label: country.countryname }}
|
||||
onChange={handleCountryChange}
|
||||
options={countries.map(c => {
|
||||
return { value: c.countrycode, label: c.countryname };
|
||||
})}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
{errors.country && (
|
||||
<div className="text-danger">
|
||||
<small>{errors.country.message}</small>
|
||||
</div>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
<div className="d-flex justify-content-end">
|
||||
|
||||
<Button
|
||||
color="primary"
|
||||
type="submit"
|
||||
disabled={submitting}
|
||||
className="mt-3"
|
||||
>
|
||||
{submitting ? 'Submitting...' : 'Save Address'}
|
||||
</Button>
|
||||
|
||||
</div>
|
||||
</CardBody>
|
||||
</Card>
|
||||
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
export default JKBillingDetails
|
||||
|
|
@ -1,340 +0,0 @@
|
|||
import React, { useState, useContext, useEffect, useMemo } from 'react';
|
||||
import AppContext from '../../context/Context';
|
||||
import {
|
||||
Card,
|
||||
CardBody,
|
||||
Col,
|
||||
Button,
|
||||
Row,
|
||||
FormGroup,
|
||||
CustomInput,
|
||||
Label
|
||||
} from 'reactstrap';
|
||||
import FalconCardHeader from '../common/FalconCardHeader';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import Flex from '../common/Flex';
|
||||
import iconPaymentMethodsGrid from '../../assets/img/icons/icon-payment-methods-grid.png';
|
||||
import iconPaypalFull from '../../assets/img/icons/icon-paypal-full.png';
|
||||
//import { useResponsive } from '@farfetch/react-context-responsive';
|
||||
//import { useShoppingCart } from '../../hooks/useShoppingCart';
|
||||
import { createRecurlyAccount, placeOrder, submitStripe } from '../../helpers/rest';
|
||||
//import { useAuth } from '../../context/UserAuth';
|
||||
import { isValid, isExpirationDateValid, isSecurityCodeValid, getCreditCardNameByNumber } from 'creditcard.js';
|
||||
import { useCheckout } from '../../hooks/useCheckout';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
const JKPaymentOptions = ({ billingInfo }) => {
|
||||
const history = useHistory();
|
||||
const { setPreserveBillingInfo, refreshPreserveBillingInfo, shouldPreserveBillingInfo, deletePreserveBillingInfo } = useCheckout();
|
||||
|
||||
const [paymentMethod, setPaymentMethod] = useState('credit-card');
|
||||
const [paymentErrorMessage, setPaymentErrorMessage] = useState('');
|
||||
//const [orderErrorMessage, setOrderErrorMessage] = useState('');
|
||||
const [cardNumber, setCardNumber] = useState('');
|
||||
//const [billingInfo, setBillingInfo] = useState({});
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
|
||||
const [reuseExistingCard, setReuseExistingCard] = useState(false);
|
||||
const [saveThisCard, setSaveThisCard] = useState(false);
|
||||
|
||||
const {
|
||||
register,
|
||||
control,
|
||||
handleSubmit,
|
||||
setValue,
|
||||
setError,
|
||||
formState: { errors }
|
||||
} = useForm({
|
||||
defaultValues: {
|
||||
number: '',
|
||||
month: '',
|
||||
year: '',
|
||||
verification_value: ''
|
||||
}
|
||||
});
|
||||
|
||||
const disableCardFields = useMemo(() => {
|
||||
return paymentMethod === 'existing-card';
|
||||
}, [paymentMethod]);
|
||||
|
||||
const onSubmit = async data => {
|
||||
console.log('Form Data:', data);
|
||||
|
||||
if (!window.recurly){
|
||||
console.error('Recurly is not loaded');
|
||||
toast.error('Payment System is not loaded. Please refresh this page and try to enter your info again. Sorry for the inconvenience!');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isValidateCard(data)) {
|
||||
toast.error('Please fix the errors in the form before submitting.');
|
||||
console.error('Form validation failed');
|
||||
return;
|
||||
}
|
||||
|
||||
const params = {
|
||||
number: data.number.replace(/\s+/g, ''),
|
||||
cvc: data.verification_value,
|
||||
exp_month: data.month,
|
||||
exp_year: data.year,
|
||||
}
|
||||
|
||||
window.Stripe.card.createToken(params, (status, response) => {
|
||||
if (response.error) {
|
||||
// Handle error
|
||||
console.error('Stripe Error:', response.error.message);
|
||||
switch (response.error.code) {
|
||||
case 'invalid_number':
|
||||
setError('number', { type: 'manual', message: response.error.message }, { shouldFocus: false });
|
||||
break;
|
||||
case 'invalid_cvc':
|
||||
setError('cvc', { type: 'manual', message: response.error.message }, { shouldFocus: false });
|
||||
break;
|
||||
case 'invalid_expiry_year':
|
||||
setError('exp_year', { type: 'manual', message: response.error.message }, { shouldFocus: false });
|
||||
break;
|
||||
case 'invalid_expiry_month':
|
||||
setError('exp_month', { type: 'manual', message: response.error.message }, { shouldFocus: false });
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// Handle success
|
||||
// console.log('Stripe Token:', response.id);
|
||||
// data.stripeToken = response.id;
|
||||
// data.stripeTokenType = response.type;
|
||||
// // Proceed with creating Recurly account or placing order
|
||||
// constructRecurlyAccount(data);
|
||||
|
||||
const stripData = {
|
||||
token: response.id,
|
||||
zip: billingInfo.zip,
|
||||
test_drive: false,
|
||||
normal: false,
|
||||
}
|
||||
}
|
||||
submitStripe(params).then((response) => {
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
} else {
|
||||
throw new Error('Failed to submit Stripe data');
|
||||
}
|
||||
}).then((data) => {
|
||||
console.log('Stripe Response:', data);
|
||||
toast.success('Payment information submitted successfully');
|
||||
}).catch((error) => {
|
||||
console.error('Error submitting Stripe data:', error);
|
||||
setPaymentErrorMessage('Failed to submit payment information. Please try again.');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const isValidateCard = data => {
|
||||
let _isValid = true;
|
||||
|
||||
if (!isValid(cardNumber)) {
|
||||
_isValid = false;
|
||||
//console.log('Invalid Card Number');
|
||||
setError('number', { type: 'manual', message: 'Invalid Card Number' }, { shouldFocus: false });
|
||||
}
|
||||
if (!isExpirationDateValid(data.month, data.year)) {
|
||||
_isValid = false;
|
||||
//console.log('Invalid Expiration Date');
|
||||
setError('month', { type: 'manual', message: 'Invalid Expiration Date' }, { shouldFocus: false });
|
||||
setError('year', { type: 'manual', message: 'Invalid Expiration Date' }, { shouldFocus: false });
|
||||
}
|
||||
// if (!isSecurityCodeValid(data.verification_value)) {
|
||||
// _isValid = false;
|
||||
// console.log('Invalid Security Code');
|
||||
// setError('verification_value', { type: 'manual', message: 'Invalid Security Code' }, { shouldFocus: false });
|
||||
// }
|
||||
return _isValid;
|
||||
};
|
||||
|
||||
function formatCardNumber(value) {
|
||||
const v = value.replace(/\s+/g, '').replace(/[^0-9]/gi, '');
|
||||
const matches = v.match(/\d{4,16}/g);
|
||||
const match = (matches && matches[0]) || '';
|
||||
const parts = [];
|
||||
|
||||
for (let i = 0; i < match.length; i += 4) {
|
||||
parts.push(match.substring(i, i + 4));
|
||||
}
|
||||
|
||||
if (parts.length) {
|
||||
return parts.join(' ');
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
const handleOnCardNumberChange = e => {
|
||||
const cardNumber = e.target.value;
|
||||
//console.log('Formatted Card Number:', formatCardNumber(cardNumber));
|
||||
setCardNumber(formatCardNumber(cardNumber));
|
||||
};
|
||||
|
||||
const handoverToPaypal = () => {
|
||||
// Handover to Paypal
|
||||
setSubmitting(true);
|
||||
window.location = `${process.env.REACT_APP_CLIENT_BASE_URL}/paypal/checkout/start`;
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="mb-3">
|
||||
<Card className="mb-3">
|
||||
<FalconCardHeader title="Payment Method" titleTag="h5" />
|
||||
<CardBody>
|
||||
{paymentErrorMessage && (
|
||||
<div className="alert alert-danger" role="alert">
|
||||
{paymentErrorMessage}
|
||||
</div>
|
||||
)}
|
||||
{reuseExistingCard && (
|
||||
<>
|
||||
<Row className="mt-3">
|
||||
<Col xs={12}>
|
||||
<CustomInput
|
||||
label={
|
||||
<>
|
||||
<Flex align="center" className="mb-2">
|
||||
<div className="fs-1">Reuse Existing Card</div>
|
||||
</Flex>
|
||||
<div>Use card ending with {billingInfo.last_four}</div>
|
||||
</>
|
||||
}
|
||||
id="existing-card"
|
||||
value="existing-card"
|
||||
checked={paymentMethod === 'existing-card'}
|
||||
onChange={({ target }) => setPaymentMethod(target.value)}
|
||||
type="radio"
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<hr />
|
||||
</>
|
||||
)}
|
||||
<Row>
|
||||
<Col xs={12}>
|
||||
<CustomInput
|
||||
label={
|
||||
<Flex align="center" className="mb-2 fs-1">
|
||||
Credit Card
|
||||
</Flex>
|
||||
}
|
||||
id="credit-card"
|
||||
value="credit-card"
|
||||
checked={paymentMethod === 'credit-card'}
|
||||
onChange={({ target }) => setPaymentMethod(target.value)}
|
||||
type="radio"
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={12} className="pl-4">
|
||||
<Row>
|
||||
<Col sm={8}>
|
||||
<Row className="align-items-center">
|
||||
<Col>
|
||||
<FormGroup>
|
||||
<input
|
||||
type="text"
|
||||
value={cardNumber}
|
||||
className={errors.number ? 'form-control form-control-is-invalid' : 'form-control'}
|
||||
placeholder="•••• •••• •••• ••••"
|
||||
onChange={handleOnCardNumberChange}
|
||||
disabled={disableCardFields}
|
||||
/>
|
||||
{/* {errors.number && (
|
||||
<div className="text-danger">
|
||||
<small>{errors.number.message}</small>
|
||||
</div>
|
||||
)} */}
|
||||
</FormGroup>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className="align-items-center">
|
||||
<Col xs={4}>
|
||||
<FormGroup>
|
||||
<Label>Month</Label>
|
||||
<input
|
||||
type="text"
|
||||
{...register('month')}
|
||||
className={errors.month ? 'form-control form-control-is-invalid' : 'form-control'}
|
||||
placeholder="MM"
|
||||
maxLength={2}
|
||||
disabled={disableCardFields}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Col>
|
||||
<Col xs={4}>
|
||||
<FormGroup>
|
||||
<Label>Year</Label>
|
||||
<input
|
||||
type="text"
|
||||
{...register('year')}
|
||||
className={errors.year ? 'form-control form-control-is-invalid' : 'form-control'}
|
||||
placeholder="YYYY"
|
||||
maxLength={4}
|
||||
disabled={disableCardFields}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Col>
|
||||
<Col xs={4}>
|
||||
<FormGroup>
|
||||
<Label>CVV</Label>
|
||||
<input
|
||||
type="text"
|
||||
{...register('verification_value')}
|
||||
className={
|
||||
errors.verification_value ? 'form-control form-control-is-invalid' : 'form-control'
|
||||
}
|
||||
placeholder="123"
|
||||
maxLength={3}
|
||||
disabled={disableCardFields}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
<div className="col-4 text-center pt-2 d-none d-sm-block">
|
||||
<div className="rounded p-2 mt-3 bg-100">
|
||||
<div className="text-uppercase fs--2 font-weight-bold">We Accept</div>
|
||||
<img src={iconPaymentMethodsGrid} alt="" width="120" />
|
||||
</div>
|
||||
</div>
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
<hr />
|
||||
<Row className="mt-3">
|
||||
<Col xs={12}>
|
||||
<CustomInput
|
||||
label={<img className="pull-right" src={iconPaypalFull} height="20" alt="" />}
|
||||
id="paypal"
|
||||
value="paypal"
|
||||
checked={paymentMethod === 'paypal'}
|
||||
onChange={({ target }) => setPaymentMethod(target.value)}
|
||||
type="radio"
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
{/* <hr className="border-dashed my-5" /> */}
|
||||
<Row>
|
||||
<Col className="pl-lg-4 pl-xl-2 pl-xxl-5 text-center">
|
||||
<Button type="submit" color="primary" className="mt-3 px-5" disabled={submitting}>
|
||||
Save Payment Information
|
||||
</Button>
|
||||
<p className="fs--1 mt-3 mb-0">
|
||||
By clicking <strong>Save Payment Information</strong>, you agree to JamKazam's{' '}
|
||||
<a href="https://www.jamkazam.com/corp/terms" target='_blank'>Terms Of Service</a>.
|
||||
</p>
|
||||
</Col>
|
||||
</Row>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
export default JKPaymentOptions
|
||||
|
|
@ -728,3 +728,25 @@ export const submitStripe = (options = {}) => {
|
|||
.catch(error => reject(error));
|
||||
})
|
||||
}
|
||||
|
||||
// function updatePayment(options) {
|
||||
// options = options || {}
|
||||
// return $.ajax({
|
||||
// type: "POST",
|
||||
// url: '/api/recurly/update_payment',
|
||||
// dataType: "json",
|
||||
// contentType: 'application/json',
|
||||
// data: JSON.stringify(options)
|
||||
// })
|
||||
// }
|
||||
|
||||
export const updatePayment = (options = {}) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
apiFetch(`/recurly/update_payment`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(options)
|
||||
})
|
||||
.then(response => resolve(response))
|
||||
.catch(error => reject(error));
|
||||
});
|
||||
};
|
||||
|
|
@ -109,5 +109,68 @@
|
|||
"no_payments": "No payments found.",
|
||||
"load_more": "Load More",
|
||||
"loading": "Loading..."
|
||||
},
|
||||
"payment_method": {
|
||||
"page_title": "Payment Method",
|
||||
"header": "Address and Payment Method",
|
||||
"help_text_no_card": "You do not currently have a payment method on file.",
|
||||
"help_text_has_card": "You currently have a payment method on file.",
|
||||
"help_text": "To update your payment method, first enter your billing address and click the save button. If credit card, enter your card information and click the Save button. If PayPal, click the save button and follow PayPal's on-screen instructions to sign into your account and authorize payment to JamKazam.",
|
||||
"credit_card_number": "Credit Card Number",
|
||||
"expiration_date": "Expiration Date (MM/YY)",
|
||||
"cvv": "CVV",
|
||||
"submit": "Save Payment Method",
|
||||
"first_name": "First Name",
|
||||
"last_name": "Last Name",
|
||||
"address1": "Address 1",
|
||||
"address2": "Address 2",
|
||||
"city": "City",
|
||||
"state": "State or Region",
|
||||
"zip_code": "Zip/Postal Code",
|
||||
"country": "Country",
|
||||
"credit_card": "Credit Card",
|
||||
"paypal": "PayPal",
|
||||
"we_accept": "We accept",
|
||||
"submitting": "Submitting...",
|
||||
"save_payment_info": "Save Payment Information",
|
||||
"validations": {
|
||||
"first_name": {
|
||||
"required": "First Name is required"
|
||||
},
|
||||
"last_name": {
|
||||
"required": "Last Name is required"
|
||||
},
|
||||
"address1": {
|
||||
"required": "Address Line 1 is required"
|
||||
},
|
||||
"city": {
|
||||
"required": "City is required"
|
||||
},
|
||||
"state": {
|
||||
"required": "State or Region is required"
|
||||
},
|
||||
"zip_code": {
|
||||
"required": "Zip/Postal Code is required"
|
||||
},
|
||||
"country": {
|
||||
"required": "Country is required"
|
||||
},
|
||||
"card": {
|
||||
"invalid": "Credit card details are invalid"
|
||||
}
|
||||
},
|
||||
"aggreement": {
|
||||
"text1": "By clicking",
|
||||
"text2": "Save Payment Information",
|
||||
"text3": "you agree to JamKazam's",
|
||||
"terms_of_service": "Terms of Service"
|
||||
},
|
||||
"alerts": {
|
||||
"try_again": "Please try again.",
|
||||
"payment_method_updated": "Your payment method has been successfully updated.",
|
||||
"card_update_error": "Failed to update payment method. Please try again later.",
|
||||
"card_processing_error": "There was an error processing your card. Please check your details and try again.",
|
||||
"billing_update_error": "There was an error processing your billing information. Please check your details and try again."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -619,7 +619,7 @@ profileUtils = context.JK.ProfileUtils
|
|||
<a className={classNames(submitClassNames)} onClick={this.onSubmitForm}>SUBMIT CARD INFORMATION</a>
|
||||
</div>`
|
||||
else
|
||||
header = 'You have have a payment method on file already.'
|
||||
header = 'You have a payment method on file already.'
|
||||
updateCardAction = `<a className={classNames(updateCardClassNames)} onClick={this.onUnlockPaymentInfo}>I'D LIKE TO UPDATE MY PAYMENT INFO</a>`
|
||||
managedSubscriptionAction = `<a href="/client#/account/subscription" className="button-orange">MANAGE MY SUBSCRIPTION</a>`
|
||||
actions = `<div className="actions">
|
||||
|
|
|
|||
Loading…
Reference in New Issue