Merged in 5645-payment_method_page (pull request #65)
5645 payment method page * wip payment method in new site * payment method page new page to add user's payment method (credit card / paypal) alone with billing address details * Update recurly/braintree tokens Approved-by: Seth Call
This commit is contained in:
parent
9058c8af1d
commit
c7e80a0694
|
|
@ -12,3 +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_RECURLY_PUBLIC_API_KEY=ewr1-hvDV1xQxDw0HPaaRFP4KNE
|
||||
REACT_APP_BRAINTREE_TOKEN=sandbox_pgjp8dvs_5v5rwm94m2vrfbms
|
||||
|
|
@ -8,4 +8,6 @@ REACT_APP_BITBUCKET_BUILD_NUMBER=dev
|
|||
REACT_APP_BITBUCKET_COMMIT=dev
|
||||
REACT_APP_ENV=development
|
||||
REACT_APP_COOKIE_DOMAIN=.jamkazam.com
|
||||
REACT_APP_GOOGLE_ANALYTICS_ID=G-MC9BTWXWY4
|
||||
REACT_APP_GOOGLE_ANALYTICS_ID=G-MC9BTWXWY4
|
||||
REACT_APP_RECURLY_PUBLIC_API_KEY=
|
||||
REACT_APP_BRAINTREE_TOKEN=
|
||||
|
|
@ -9,3 +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_RECURLY_PUBLIC_API_KEY=ewr1-hvDV1xQxDw0HPaaRFP4KNE
|
||||
REACT_APP_BRAINTREE_TOKEN=production_hc7z69yq_pwwc6zm3d478kfrh
|
||||
|
|
|
|||
|
|
@ -9,3 +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_RECURLY_PUBLIC_API_KEY=ewr1-AjUHUfcLtIsPdtetD4mj2x
|
||||
REACT_APP_BRAINTREE_TOKEN=sandbox_pgjp8dvs_5v5rwm94m2vrfbms
|
||||
|
|
|
|||
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
|
|
@ -37,6 +37,7 @@ import JKEditProfile from '../page/JKEditProfile';
|
|||
import JKEditAccount from '../page/JKEditAccount';
|
||||
import JKAccountSubscription from '../page/JKAccountSubscription';
|
||||
import JKPaymentHistory from '../page/JKPaymentHistory';
|
||||
import JKPaymentMethod from '../page/JKPaymentMethod';
|
||||
import JKAccountPreferences from '../page/JKAccountPreferences';
|
||||
|
||||
import JKAffiliateProgram from '../affiliate/JKAffiliateProgram';
|
||||
|
|
@ -276,6 +277,10 @@ 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_RECURLY_PUBLIC_API_KEY);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={isFluid || isKanban ? 'container-fluid' : 'container'}>
|
||||
|
|
@ -297,6 +302,7 @@ function JKDashboardMain() {
|
|||
<PrivateRoute path="/account/identity" component={JKEditAccount} />
|
||||
<PrivateRoute path="/account/subscription" component={JKAccountSubscription} />
|
||||
<PrivateRoute path="/account/payments" component={JKPaymentHistory} />
|
||||
<PrivateRoute path="/account/payment-method" component={JKPaymentMethod} />
|
||||
<PrivateRoute path="/account/preferences" component={JKAccountPreferences} />
|
||||
<PrivateRoute path="/affiliate/program" component={JKAffiliateProgram} />
|
||||
<PrivateRoute path="/affiliate/payee" component={JKAffiliatePayee} />
|
||||
|
|
|
|||
|
|
@ -0,0 +1,507 @@
|
|||
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 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('payment_method.page_title')} titleClass="font-weight-bold" />
|
||||
<CardBody className="pt-3" style={{ backgroundColor: '#edf2f9' }}>
|
||||
<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>
|
||||
);
|
||||
};
|
||||
|
||||
export default JKPaymentMethod;
|
||||
|
|
@ -554,6 +554,18 @@ export const getBillingInfo = () => {
|
|||
});
|
||||
};
|
||||
|
||||
export const updateBillingInfo = (options = {}) => {
|
||||
const params = { billing_info: options };
|
||||
return new Promise((resolve, reject) => {
|
||||
apiFetch(`/recurly/update_billing_info`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(params)
|
||||
})
|
||||
.then(response => resolve(response))
|
||||
.catch(error => reject(error));
|
||||
});
|
||||
};
|
||||
|
||||
export const createRecurlyAccount = (options = {}) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
apiFetch(`/recurly/create_account`, {
|
||||
|
|
@ -704,3 +716,37 @@ export const paypalPlaceOrder = (options = {}) => {
|
|||
.catch(error => reject(error));
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
export const submitStripe = (options = {}) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
apiFetch(`/stripe`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(options)
|
||||
})
|
||||
.then(response => resolve(response))
|
||||
.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">
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ Rails.application.config.middleware.insert_before 0, Rack::Cors do
|
|||
|
||||
resource '/api/*',
|
||||
headers: :any,
|
||||
methods: [:get, :post, :delete, :options],
|
||||
methods: [:get, :post, :put, :delete, :options],
|
||||
credentials: true
|
||||
end
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue