ducky/web

Paddy 2015-05-31 Parent:b9d0efb44eaa Child:62e0c0df28bb

5:efdc78cbdac5 Go to Latest

ducky/web/src/pages/payment.jsx

Update our ValidationError component to accept arrays. Allow our ValidationError component to match an array of fields, headers, or params. This is for components that may address multiple inputs (e.g., month/year inputs) but also lays the ground work for inverse-matching. Ideally, inverse-matching and matching ValidationErrors should mirror each other, logically, so the syntax is identical. But we also should have the common use case easily supported, so if you use field instead of fields (or header/headers, param/params) we'll automatically turn that into an array.

History
paddy@2 1 import app from 'ampersand-app'
paddy@2 2 import React from 'react'
paddy@2 3 import ScriptLoaderMixin from 'react-script-loader'
paddy@2 4 import LaddaButton from 'react-ladda'
paddy@2 5 import LaddaCSS from '../../node_modules/ladda/dist/ladda.min.css'
paddy@2 6 import HeroUnit from '../components/hero'
paddy@2 7 import ValidationError from '../components/validation-error'
paddy@2 8 import onboardStyles from '../styles/onboarding.scss'
paddy@2 9 import config from '../config'
paddy@2 10
paddy@2 11 export default React.createClass({
paddy@2 12 displayName: 'PaymentMethodPage',
paddy@2 13 mixins: [ScriptLoaderMixin.ReactScriptLoaderMixin, React.addons.LinkedStateMixin],
paddy@2 14 getScriptURL () {
paddy@2 15 return 'https://js.stripe.com/v2/'
paddy@2 16 },
paddy@2 17
paddy@2 18 getInitialState () {
paddy@2 19 return {
paddy@2 20 stripeLoading: true,
paddy@2 21 stripeFailedToLoad: false,
paddy@2 22 active: false,
paddy@2 23 errors: [],
paddy@2 24 number: null,
paddy@2 25 name: null,
paddy@2 26 cvc: null,
paddy@2 27 expireMonth: null,
paddy@2 28 expireYear: null,
paddy@2 29 }
paddy@2 30 },
paddy@2 31
paddy@2 32 nameValidationOutputs: {},
paddy@2 33 numberValidationOutputs: {},
paddy@2 34 cvcValidationOutputs: {},
paddy@2 35 expirationValidationOutputs: {},
paddy@2 36
paddy@2 37 onScriptLoaded () {
paddy@2 38 Stripe.setPublishableKey(config.stripeKey)
paddy@2 39 this.setState({stripeLoading: false})
paddy@2 40 },
paddy@2 41
paddy@2 42 onScriptError () {
paddy@2 43 this.setState({stripeFailedToLoad: true})
paddy@2 44 },
paddy@2 45
paddy@2 46 getChargeDate () {
paddy@2 47 let created = new Date()
paddy@2 48 if (app.me && app.me.profile && app.me.profile.created && app.me.profile.created < created) {
paddy@2 49 console.log("using register date...")
paddy@2 50 created = app.me.profile.created
paddy@2 51 }
paddy@2 52 let month = created.getMonth()
paddy@2 53 let day = created.getDate()
paddy@2 54 month = month + 1
paddy@2 55 if (day > 1) {
paddy@2 56 day = 1
paddy@2 57 month = month + 1
paddy@2 58 }
paddy@2 59 let result = new Date(created.toString())
paddy@2 60 result.setDate(day)
paddy@2 61 result.setMonth(month)
paddy@2 62 return result
paddy@2 63 },
paddy@2 64
paddy@2 65 addCard (e) {
paddy@2 66 e.preventDefault()
paddy@2 67 if (this.state.stripeLoading || this.state.stripeFailedToLoad) {
paddy@2 68 return
paddy@2 69 }
paddy@2 70 this.setState({active: true})
paddy@2 71 const t = this
paddy@2 72 const errors = []
paddy@2 73 Stripe.card.createToken({
paddy@2 74 number: this.state.number,
paddy@2 75 cvc: this.state.cvc,
paddy@2 76 exp_month: this.state.expireMonth,
paddy@2 77 exp_year: this.state.expireYear,
paddy@2 78 name: this.state.name,
paddy@2 79 }, function(status, response) {
paddy@2 80 console.log(status)
paddy@2 81 console.log(response)
paddy@2 82 if (response.error) {
paddy@2 83 if (response.error.type == 'card_error') {
paddy@2 84 switch (response.error.code) {
paddy@2 85 case 'incorrect_number':
paddy@2 86 errors.push({'error': 'invalid_value', 'field': '/number'})
paddy@2 87 break
paddy@2 88 case 'invalid_number':
paddy@2 89 errors.push({'error': 'invalid_format', 'field': '/number'})
paddy@2 90 break
paddy@2 91 case 'invalid_expiry_month':
paddy@2 92 errors.push({'error': 'invalid_format', 'field': '/expireMonth'})
paddy@2 93 break
paddy@2 94 case 'invalid_expiry_year':
paddy@2 95 errors.push({'error': 'invalid_format', 'field': '/expireYear'})
paddy@2 96 break
paddy@2 97 case 'invalid_cvc':
paddy@2 98 errors.push({'error': 'invalid_format', 'field': '/cvc'})
paddy@2 99 break
paddy@2 100 case 'expired_card':
paddy@2 101 errors.push({'error': 'invalid_value', 'field': '/expiration'})
paddy@2 102 break
paddy@2 103 case 'incorrect_cvc':
paddy@2 104 errors.push({'error': 'invalid_value', 'field': '/cvc'})
paddy@2 105 break
paddy@2 106 case 'incorrect_zip':
paddy@2 107 errors.push({'error': 'invalid_value', 'field': '/zip'})
paddy@2 108 break
paddy@2 109 case 'card_declined':
paddy@2 110 errors.push({'error': 'insufficient', 'field': '/balance'})
paddy@2 111 break
paddy@2 112 case 'missing':
paddy@2 113 errors.push({'error': 'missing', 'field': '/customer/card'})
paddy@2 114 break
paddy@2 115 case 'processing_error':
paddy@2 116 errors.push({'error': 'act_of_god', 'field': '/'})
paddy@2 117 break
paddy@2 118 case 'rate_limit':
paddy@2 119 errors.push({'error': 'access_denied', 'field': '/rate'})
paddy@2 120 break
paddy@2 121 default:
paddy@2 122 errors.push({'error': 'act_of_god', 'field': '/'})
paddy@2 123 break
paddy@2 124 }
paddy@2 125 } else {
paddy@2 126 console.log('Error:', response.error.message)
paddy@2 127 }
paddy@2 128 } else {
paddy@2 129 console.log('Sending '+response.id+' to server to create customer')
paddy@2 130 }
paddy@2 131 t.setState({active: false, errors: errors})
paddy@2 132 })
paddy@2 133 },
paddy@2 134
paddy@2 135 render () {
paddy@2 136 return (
paddy@2 137 <div className='container'>
paddy@2 138 <HeroUnit title='Add a Payment Method'>Gotta keep our servers online and food on the table.</HeroUnit>
paddy@2 139 <article className='onboarding payment'>
paddy@2 140 <p>Ducky costs $2 a month to use. We’ll charge the card you enter below on the first of every month until your account is disabled. You won’t be charged before {this.getChargeDate().toLocaleDateString(navigator.languages, {month: 'long', year: 'numeric', day: 'numeric'})}.</p>
paddy@2 141 <form onSubmit={this.addCard}>
paddy@2 142 <div>
paddy@2 143 <label htmlFor='name'>Cardholder Name</label>
paddy@2 144 <input id='name' type='text' placeholder='This is the name on your card' valueLink={this.linkState('name')} />
paddy@2 145 <ValidationError errors={this.state.errors} field='/name' outputs={this.nameValidationOutputs} />
paddy@2 146
paddy@2 147 <label htmlFor='cardNumber'>Card Number</label>
paddy@2 148 <input id='cardNumber' type='text' placeholder='4242 4242 4242 4242' valueLink={this.linkState('number')} />
paddy@2 149 <ValidationError errors={this.state.errors} field='/number' outputs={this.numberValidationOutputs} />
paddy@2 150
paddy@2 151 <label htmlFor='cvc'>Security Code / CVC</label>
paddy@2 152 <input id='cvc' className='cvc' type='password' placeholder='123' valueLink={this.linkState('cvc')} />
paddy@2 153
paddy@2 154 <label htmlFor='expireMonth' className='expiration'>Expires</label>
paddy@2 155 <input id='expireMonth' className='expiration month' type='text' placeholder='01' valueLink={this.linkState('expireMonth')} />
paddy@2 156 <input id='expireYear' className='expiration year' type='text' placeholder='15' valueLink={this.linkState('expireYear')} />
paddy@2 157 <ValidationError errors={this.state.errors} field='/cvc' outputs={this.cvcValidationOutputs} />
paddy@2 158 <ValidationError errors={this.state.errors} field='/expiration' outputs={this.expirationValidationOutputs} />
paddy@2 159
paddy@2 160 <div className='actionbuttons'>
paddy@2 161 <button type='button' onClick={this.skip} className='ladda-button' disabled={this.state.active}>Not Now</button>
paddy@2 162 <LaddaButton style='expand-right' active={this.state.active}>
paddy@2 163 <button type='submit' className='primary' disabled={this.state.stripeLoading || this.state.stripeFailedToLoad || this.state.active || this.state.errors.length}>Add Card</button>
paddy@2 164 </LaddaButton>
paddy@2 165 </div>
paddy@2 166 </div>
paddy@2 167 </form>
paddy@2 168 </article>
paddy@2 169 </div>
paddy@2 170 )
paddy@2 171 }
paddy@2 172 })