我的React组件文件为:-
var React = require('react');
var createReactClass = require('create-react-class');
var ReactScriptLoaderMixin = require('react-script-loader').ReactScriptLoaderMixin;
var zenApi = require('../../generics/zenApi');
var swal = require('sweetalert');
import CreditCardInput from 'react-credit-card-input';
var PaymentForm = createReactClass({
billing_post_url : process.env.API_URL + '/billings/',
plan_obj_fetch_url: process.env.API_URL + '/billings/getPlan/',
coupon_check_url : process.env.API_URL + '/billings/checkCoupon/',
mixins: [ ReactScriptLoaderMixin ],
getInitialState: function() {
return {
stripeLoading : true,
stripeLoadingError: false,
submitDisabled : false,
paymentError : null,
paymentComplete : false,
token : null,
planObj : {},
planObjFetchError : false,
redirectTo : '',
cardNumber : '',
isInvalid : false,
isLoading : false,
fullName : '',
taxYear : (new Date()).getFullYear(),
couponCode : '',
isCouponValid : false,
backupPlanObj : {},
subscriptionStatus: {},
normalUpgrade : '',
planId : this.props.plan_id
};
},
getScriptURL: function() {
return 'https://js.stripe.com/v2/';
},
onScriptLoaded: function() {
zenApi.get(this.plan_obj_fetch_url + this.state.planId + '?tax_year=' + this.props.taxYear)
.then(response => {
if(response.status)
this.setState({
planObj : response.data.plan,
subscriptionStatus: response.data.subscription_status,
backupPlanObj : response.data.plan
});
else
this.setState({
planObjFetchError: response.status,
});
});
if(!PaymentForm.getStripeToken)
this.setState({ stripeLoading: false, stripeLoadingError: false });
},
onScriptError: function() {
this.setState({ stripeLoading: false, stripeLoadingError: true });
},
onNameChange: function(e) {
this.setState({
fullName: e.target.value
});
},
onCouponChange: function(e) {
this.setState({
couponCode: e.target.value
});
},
onCouponBlur: function(e) {
if(e.target.value !== '')
this.checkCouponCode(this);
else
this.setState({ paymentError: null, planObj: this.state.backupPlanObj });
},
onTaxYearSelect: function(e) {
this.setState({
taxYear: e.target.value
});
},
onSubmit: function(event) {
Stripe.setPublishableKey(this.props.pub_key);
let plan_id = this.props.plan_id;
var self = this;
event.preventDefault();
this.setState({ submitDisabled: true, paymentError: null, isLoading: true });
// send form here
Stripe.createToken(event.target, function(status, response) {
if(response.error)
self.setState({ paymentError: response.error.message, submitDisabled: false, isLoading: false });
else
self.postBillings(response.id, plan_id, self);
});
},
postBillings: async(token, plan_id, self) => {
try {
zenApi.post(self.billing_post_url,
{
stripe_token : token,
plan_id : plan_id,
upgrade : self.state.subscriptionStatus.upgrade,
name : self.state.fullName,
year : self.props.taxYear,
coupon_code : self.state.couponCode,
normal_upgrade: self.props.upgradePlan
})
.then(response => {
if(response.status)
self.setState({
paymentComplete: response.status,
paymentError : null
});
else
self.setState({
paymentError : response.data,
submitDisabled: false,
isLoading : false
});
});
} catch(err) {
self.setState({
paymentError : err.message,
submitDisabled: false
});
}
},
checkCouponCode: async self => {
try {
zenApi.post(self.coupon_check_url,
{
coupon_code: self.state.couponCode,
plan_id : self.props.plan_id
})
.then(response => {
if(response.status)
self.setState({
planObj : response.data,
paymentError : null,
isCouponValid: true
});
else
self.setState({
planObj : self.state.backupPlanObj,
paymentError : response.message,
isCouponValid: false,
planObj : self.state.backupPlanObj
});
});
} catch(err) {
self.setState({
paymentError: err.message
});
}
},
capitalizeFirstLetter(value) {
var regex = /(\b[a-z](?!\s))/g;
return value ? value.replace(regex, function(v) {
return v.toUpperCase();
}) : '';
},
redirectToCallerSource: function() {
swal({
title : 'Payment Successfull!',
text : 'Your payment for ' + this.state.planObj.name + ' plan has succeeded.',
icon : 'success',
closeOnEsc : false,
closeOnClickOutside: false,
buttons : {
continue: {
text : 'Continue',
value: 'continue'
}
}
}).then(btnVal => {
if(btnVal == 'dashboard')
this.setState({ redirectTo: '/dashboard', paymentComplete: false });
else if(btnVal == 'continue')
if(this.props.callUrl == 'wizard') {
this.setState({ redirectTo: '/paymentSuccessPage?redirectTo=wizard', paymentComplete: false });
}
else if(this.props.callUrl == '/') {
this.setState({ redirectTo: '/paymentSuccessPage?redirectTo=/' });
}
else if(this.props.callUrl == 'settings') {
this.setState({ redirectTo: '/paymentSuccessPage?redirectTo=settings', paymentComplete: false });
}
else if(this.props.callUrl == 'marketing') {
this.setState({ redirectTo: '/paymentSuccessPage?redirectTo=pricing', paymentComplete: false });
}
else if(this.props.callUrl == 'dashboard') {
this.setState({ redirectTo: '/paymentSuccessPage?redirectTo=dashboard', paymentComplete: false });
}
});
},
handleCardNumberChange: function(e) {
this.setState({
cardNumber: e.target.value
});
},
render: function() {
const cardNumber = this.state.cardNumber;
const plan_amount = this.state.planObj.amount;
const subs_status = this.state.subscriptionStatus;
if(this.state.stripeLoading)
return <div className="container text-center"><div className="loader" /></div>;
else if(this.state.stripeLoadingError)
return <div className="container text-center"><h4>Error</h4></div>;
else if(this.state.redirectTo != '')
return <div>{window.location.replace(this.state.redirectTo)}</div>;
else if(this.state.planObjFetchError)
return <div className="container text-center"><h4>Error Fetching Plan. Contact Admin.</h4></div>;
else if(this.state.paymentComplete)
return <div>{this.redirectToCallerSource()}</div>;
else if(this.state.planObj.name !== 'Free')
return (
<div className="container">
<div className='row'>
<div className="col-sm-12 text-center pt-4">
<h4>Let's finish powering you up!</h4>
<p className="text-grey">You selected {this.state.planObj.name} plan.</p>
</div>
</div>
<div className="row">
<div className="col-md-12">
<div className="payment-form-container pb-5">
<div className="flex-dir-row space-between">
<h1 className="weight-300">{this.capitalizeFirstLetter(this.state.planObj.name)}</h1>
<h1 className="weight-300">{parseFloat(plan_amount - subs_status.discount_amount) ? '$' + parseFloat(plan_amount - subs_status.discount_amount) : '' }</h1>
</div>
<form onSubmit={this.onSubmit} className="payment-form">
{
(subs_status.upgrade) ? '' :
<div className="row">
<div className="col-sm-6 float-left">
<div className="form-group">
<label htmlFor="coupon_code">Coupon Code</label>
<input type='text' className='form-control' name='coupon_code' placeholder='Legit Coupon (If any)' onChange={this.onCouponChange} onBlur={this.onCouponBlur} />
</div>
</div>
</div>
}
<hr className='decrease-space-between' />
<span className="error-text">{ this.state.paymentError }</span><br />
<span className="year-text"><i>* You are paying for year <span className='tax-year-text'><b>{ this.state.taxYear }</b></span></i></span>
<div className="row">
<div className="col-sm-6 float-left">
<div className="form-group">
<label htmlFor="full_name">Full Name</label>
<input type='text' className='form-control' name='full_name' placeholder='Full Name' onChange={this.onNameChange} data-stripe='name' />
</div>
</div>
<div className="col-sm-6 float-right">
<div className="form-group">
<label htmlFor="formGroupExampleInput">Tax Year</label>
<select className="form-control" id="exampleSelect99" onChange={this.onTaxYearSelect}>
<option value={this.props.taxYear}>{this.props.taxYear}</option>
</select>
</div>
</div>
</div>
<div className="row">
<div className="col-sm-12">
<div className="form-group">
<label htmlFor="formGroupExampleInput">Card Number</label>
<CreditCardInput
data-stripe= 'number'
cardNumberInputProps={{ value: cardNumber, onChange: this.handleCardNumberChange, pattern: '(\\d*\\s){3}\\d*', 'data-stripe': 'number' }}
/>
</div>
</div>
</div>
<div className="row">
<div className="col-sm-4">
<div className="form-group">
<label htmlFor="formGroupExampleInput">Expiration month</label>
<select className="form-control" id="exampleSelect1" data-stripe='exp-month'>
<option>1</option>
<option>2</option>
<option>3</option>
<option>4</option>
<option>5</option>
<option>6</option>
<option>7</option>
<option>8</option>
<option>9</option>
<option>10</option>
<option>11</option>
<option>12</option>
</select>
</div>
</div>
<div className="col-sm-4">
<div className="form-group">
<label htmlFor="formGroupExampleInput">Expiration year</label>
<select className="form-control" id="exampleSelect2" data-stripe='exp-year'>
<option>2018</option>
<option>2019</option>
<option>2020</option>
<option>2021</option>
<option>2022</option>
<option>2023</option>
<option>2024</option>
</select>
</div>
</div>
<div className="col-sm-4">
<div className="form-group">
<label htmlFor="formGroupExampleInput" >CVC / CVV</label>
<input type='text' data-stripe='cvc' placeholder='cvc' className="form-control form-type-text" maxLength="4" />
</div>
</div>
</div>
<hr />
<div className="row">
{
subs_status.upgrade ?
<div className="col-sm-12 flex-dir-row space-between mt-3">
<p><b>New Plan minus Previous Plan</b></p><h4 className="weight-300">{parseFloat(plan_amount)} - {parseFloat(subs_status.discount_amount)}</h4>
</div> : ''
}
<div className="col-sm-12 flex-dir-row space-between mt-3">
<p><b>Total Billed</b></p>
{
subs_status.upgrade ? <h3 className="weight-300">= {parseFloat(plan_amount - subs_status.discount_amount) ? '$' + parseFloat(plan_amount - subs_status.discount_amount) : ''}</h3> : <h3 className="weight-300">{parseFloat(plan_amount - subs_status.discount_amount) ? '$' + parseFloat(plan_amount - subs_status.discount_amount) : ''}</h3>
}
</div>
</div>
<hr />
<div className="row">
<span className="terms p-left">*All taxes included</span>
<div className="col-sm-12 text-center mt-4">
<span className="terms">By continuing I agree that I have read and accepted the <a href="#">Term of Use</a> and <a href="#">Privacy Policy</a></span>
<button disabled={this.state.submitDisabled} type='submit' value="" className="btn btn-success fullWidth mt-2 flex-dir-row justify-center"><div className={this.state.isLoading ===true ? 'loader-small':''} style={{ margin: '0px' }} />Complete Purchase</button>
<img src="/static/images/powered_by_stripe.svg" alt="" className="mt-4" />
</div>
</div>
</form>
</div>
</div>
</div>
<style jsx>{`
.form-type-text{
width: 30%;
margin-bottom: 5px;
}
.p-left{
padding-left: 15px;
}
.payment-form-container{
width: 65%;
margin: 0 auto;
}
.fullWidth{
width: 100%;
}
.terms{
font-size: 14px;
}
.payment-form{
margin-top: 10px;
}
.error-text{
color: #e37775;
font-size: 14px;
}
.show{
display: block;
}
.hide{
display: none;
}
.form-group .form-control{
border-radius: 4px;
padding: 7px 15px;
width: 100%;
}
.form-control, select{
background: #fff!important;
padding: 7px 15px!important;
}
.year-text{
color: grey;
font-size: 0.8em;
}
.tax-year-text{
color: white;
background-color: #00ab84;
padding: 0 5px;
}
.decrease-space-between{
margin-bottom: -20px;
}
`}</style>
</div>
);
}
});
export default PaymentForm;
我在'onScriptLoaded'方法中获得未定义的this.state.planId。 getInitialState()方法不是接收道具的最佳位置,如果不是,那么这是通过使用props值进行网络调用的最佳位置。当我通过onScriptLoaded()方法访问道具时,道具有时可用,但有时无法使用,如下面的代码所示:
onScriptLoaded: function() {
zenApi.get(this.plan_obj_fetch_url + this.props.plan_id + '?tax_year=' + this.props.taxYear)
.then(response => {
if(response.status)
this.setState({
planObj : response.data.plan,
subscriptionStatus: response.data.subscription_status,
backupPlanObj : response.data.plan
});
else
this.setState({
planObjFetchError: response.status,
});
});
if(!PaymentForm.getStripeToken)
this.setState({ stripeLoading: false, stripeLoadingError: false });
},
getInitialState方法中道具不可用的原因是什么,以及onScriptLoaded方法中道具的随机可用性背后的原因是什么,即有时它可用并且可以工作,但有时它可用并且网络调用将错误提示为404。
我在PaymentFormPage.js文件中具有上述组件调用,如下所示。
import Layout from '../components/layouts/Main';
import PaymentForm from '../components/billings/paymentForm';
class PaymentFormPage extends React.Component {
constructor() {
super();
this.state = {
plan_id : '',
pub_key : '',
upgradePlan: '',
callUrl : '',
taxYear : (new Date()).getFullYear()
};
}
componentDidMount() {
let request = new URLSearchParams(location.search);
if(!!request.get('normalUpgrade'))
this.setState({ upgradePlan: request.get('normalUpgrade') });
if(!!request.get('callUrl'))
this.setState({ callUrl: request.get('callUrl') });
if(!!request.get('taxYear'))
this.setState({ taxYear: request.get('taxYear') });
if(!!request.get('plan_id') && !!request.get('key'))
this.setState({
plan_id: request.get('plan_id'),
pub_key: request.get('key')
});
}
render() {
let plan_id = this.state.plan_id;
let pub_key = this.state.pub_key;
return (
<Layout>
<div className="container">
<div className='row'>
<PaymentForm
pub_key={pub_key}
plan_id={plan_id}
upgradePlan={this.state.upgradePlan}
callUrl={this.state.callUrl}
taxYear={this.state.taxYear}
/>
</div>
</div>
</Layout>
);
}
}
export default (PaymentFormPage);
谢谢大家。