793 lines
25 KiB
CoffeeScript
793 lines
25 KiB
CoffeeScript
context = window
|
|
rest = context.JK.Rest()
|
|
logger = context.JK.logger
|
|
|
|
AppStore = context.AppStore
|
|
UserStore = context.UserStore
|
|
|
|
profileUtils = context.JK.ProfileUtils
|
|
|
|
@AccountPaymentHistoryScreen = React.createClass({
|
|
|
|
mixins: [
|
|
ICheckMixin,
|
|
Reflux.listenTo(AppStore, "onAppInit"),
|
|
Reflux.listenTo(UserStore, "onUserChanged")
|
|
]
|
|
|
|
shownOnce: false
|
|
screenVisible: false
|
|
|
|
LIMIT: 20
|
|
TILE_PAYMENTS_TO_YOU: 'payments to you'
|
|
TILE_PAYMENTS_TO_JAMKAZAM: 'payments to jamkazam'
|
|
TILE_PAYMENT_METHOD: 'payment method'
|
|
|
|
STUDENT_TILES: ['payment method', 'payments to jamkazam']
|
|
TEACHER_TILES: ['payments to jamkazam', 'payments to you']
|
|
|
|
onAppInit: (@app) ->
|
|
@app.bindScreen('account/paymentHistory', {beforeShow: @beforeShow, afterShow: @afterShow, beforeHide: @beforeHide})
|
|
|
|
onUserChanged: (userState) ->
|
|
if !@shouldShowNameSet
|
|
@shouldShowNameSet = true
|
|
if userState?.user?
|
|
username = userState.user.name
|
|
first_name = userState.user.first_name
|
|
last_name = userState.user.last_name
|
|
shouldShowName = !username? || username.trim() == '' || username.toLowerCase().indexOf('anonymous') > -1
|
|
else
|
|
shouldShowName = @state.shouldShowName
|
|
|
|
@setState({user: userState?.user, shouldShowName: shouldShowName})
|
|
|
|
|
|
componentDidMount: () ->
|
|
@checkboxes = [{selector: 'input.billing-address-in-us', stateKey: 'billingInUS'}]
|
|
|
|
@root = $(@getDOMNode())
|
|
@endOfList = @root.find('.end-of-payments-list')
|
|
@contentBodyScroller = @root
|
|
@iCheckify()
|
|
|
|
componentDidUpdate: (prevProps, prevState) ->
|
|
@iCheckify()
|
|
|
|
$expiration = @root.find('input.expiration')
|
|
if !$expiration.data('payment-applied')
|
|
$expiration.payment('formatCardExpiry').data('payment-applied', true)
|
|
$cardNumber = @root.find("input.card-number")
|
|
if !$cardNumber.data('payment-applied')
|
|
$cardNumber.payment('formatCardNumber').data('payment-applied', true)
|
|
$cvv = @root.find("input.cvv")
|
|
if !$cvv.data('payment-applied')
|
|
$cvv.payment('formatCardCVC').data('payment-applied', true)
|
|
|
|
if @currentNext() == null
|
|
@contentBodyScroller.off('scroll')
|
|
if @state[@getCurrentPageName()] == 1 and @getCurrentList().length == 0
|
|
@endOfList.show()
|
|
logger.debug("PaymentHistoryScreen: empty search")
|
|
else if @state[@getCurrentPageName()] > 0
|
|
logger.debug("end of search")
|
|
@endOfList.show()
|
|
else
|
|
@registerInfiniteScroll(@contentBodyScroller)
|
|
|
|
if @activeTile(prevState.selected) != @activeTile() && @getCurrentList().length == 0
|
|
@refresh()
|
|
|
|
|
|
registerInfiniteScroll:() ->
|
|
$scroller = @contentBodyScroller
|
|
logger.debug("registering infinite scroll")
|
|
$scroller.off('scroll')
|
|
$scroller.on('scroll', () =>
|
|
# be sure to not fire off many refreshes when user hits the bottom
|
|
return if @refreshing
|
|
|
|
if $scroller.scrollTop() + $scroller.innerHeight() + 100 >= $scroller[0].scrollHeight
|
|
#$scroller.append('<div class="infinite-scroll-loader-2">... Loading more Payments ...</div>')
|
|
@setState({searching: true})
|
|
logger.debug("refreshing more payments for infinite scroll")
|
|
@nextPage()
|
|
)
|
|
|
|
nextPage: () ->
|
|
#nextPage = @state.salesCurrentPage + 1
|
|
@incrementCurrentPage()
|
|
@refresh()
|
|
|
|
checkboxChanged: (e) ->
|
|
checked = $(e.target).is(':checked')
|
|
|
|
value = $(e.target).val()
|
|
|
|
@setState({userSchedulingComm: value})
|
|
|
|
beforeHide: (e) ->
|
|
@screenVisible = false
|
|
@resetErrors()
|
|
|
|
beforeShow: (e) ->
|
|
|
|
afterShow: (e) ->
|
|
@clearResults()
|
|
@screenVisible = true
|
|
@refresh()
|
|
@getUncollectables()
|
|
|
|
resetErrors: () ->
|
|
@setState({ccError: null, cvvError: null, expiryError: null, billingInUSError: null, zipCodeError: null, nameError: null})
|
|
|
|
onBack: (e) ->
|
|
e.preventDefault()
|
|
|
|
window.history.go(-1);
|
|
|
|
checkboxChanged: (e) ->
|
|
checked = $(e.target).is(':checked')
|
|
|
|
@setState({billingInUS: checked})
|
|
|
|
refresh: () ->
|
|
@buildQuery()
|
|
if @activeTile() == @TILE_PAYMENTS_TO_YOU
|
|
@refreshTeacherDistributions()
|
|
else if @activeTile() == @TILE_PAYMENTS_TO_JAMKAZAM
|
|
@refreshSales()
|
|
else
|
|
logger.debug("dropping refresh because no tile match", @activeTile)
|
|
|
|
refreshSales: () ->
|
|
@refreshing = true
|
|
rest.getSalesHistory(@currentQuery)
|
|
.done(@salesHistoryDone)
|
|
.fail(@salesHistoryFail)
|
|
|
|
refreshTeacherDistributions: () ->
|
|
@refreshing = true
|
|
rest.listTeacherDistributions(@currentQuery)
|
|
.done(@teacherDistributionsDone)
|
|
.fail(@teacherDistributionsFail)
|
|
|
|
getUncollectables: () ->
|
|
rest.getUncollectables({})
|
|
.done(@uncollectablesDone)
|
|
.fail(@uncollectablesFail)
|
|
|
|
salesHistoryDone:(response) ->
|
|
@refreshing = false
|
|
this.setState({salesNext: response.next, sales: this.state.sales.concat(response.entries)})
|
|
|
|
salesHistoryFail:(jqXHR) ->
|
|
@refreshing = false
|
|
@app.notifyServerError jqXHR, 'Payments to JamKazam Unavailable'
|
|
|
|
teacherDistributionsDone:(response) ->
|
|
@refreshing = false
|
|
this.setState({distributionsNext: response.next, distributions: this.state.distributions.concat(response.entries)})
|
|
|
|
teacherDistributionsFail:(jqXHR) ->
|
|
@refreshing = false
|
|
@app.notifyServerError jqXHR, 'Payments to You Unavailable'
|
|
|
|
uncollectablesDone: (response) ->
|
|
this.setState({uncollectables: response})
|
|
|
|
uncollectablesFail: (jqXHR) ->
|
|
@app.notifyServerError jqXHR, 'Unable to fetch uncollectable info'
|
|
|
|
clearResults:() ->
|
|
this.setState({salesCurrentPage: 0, sales: [], distributionsCurrentPage: 0, distributions: [], salesNext: null, distributionsNext: null, updating: false})
|
|
|
|
buildQuery:(page = @getCurrentPage()) ->
|
|
@currentQuery = this.defaultQuery(page)
|
|
|
|
defaultQuery:(page = @getCurrentPage()) ->
|
|
query =
|
|
per_page: @LIMIT
|
|
page: page + 1
|
|
if @currentNext()
|
|
query.page = @currentNext()
|
|
query
|
|
|
|
getCurrentPage: () ->
|
|
page = this.state[@getCurrentPageName()]
|
|
if !page?
|
|
page = 1
|
|
page
|
|
|
|
incrementCurrentPage: () ->
|
|
newState = {}
|
|
newState[@getCurrentPageName] = @state[@getCurrentPageName()] + 1
|
|
this.setState(newState)
|
|
|
|
getCurrentPageName: () ->
|
|
if @activeTile() == @TILE_PAYMENTS_TO_JAMKAZAM
|
|
'salesCurrentPage'
|
|
else if @activeTile() == @TILE_PAYMENTS_TO_YOU
|
|
'distributionsCurrentPage'
|
|
else
|
|
1
|
|
|
|
getCurrentList: () ->
|
|
if @activeTile() == @TILE_PAYMENTS_TO_JAMKAZAM
|
|
@state['sales']
|
|
else if @activeTile() == @TILE_PAYMENTS_TO_YOU
|
|
@state['distributions']
|
|
else
|
|
@state['sales']
|
|
|
|
currentNext: () ->
|
|
if @activeTile() == @TILE_PAYMENTS_TO_JAMKAZAM
|
|
@state.salesNext
|
|
else if @activeTile() == @TILE_PAYMENTS_TO_YOU
|
|
@state.distributionsNext
|
|
else
|
|
null
|
|
|
|
onClick: (e) ->
|
|
e.preventDefault()
|
|
|
|
context.location.href = '/client#/account'
|
|
getInitialState: () ->
|
|
{
|
|
user: null,
|
|
nextPager: null,
|
|
salesCurrentPage: 0,
|
|
distributionsCurrentPage: 0
|
|
salesNext: null,
|
|
distributionsNext: null
|
|
sales: [],
|
|
distributions: []
|
|
selected: 'payments to jamkazam',
|
|
updating: false,
|
|
billingInUS: true,
|
|
userWantsUpdateCC: false,
|
|
uncollectables: []
|
|
}
|
|
|
|
onCancel: (e) ->
|
|
e.preventDefault()
|
|
context.location.href = '/client#/account'
|
|
|
|
|
|
mainContent: () ->
|
|
if !@state.user?
|
|
`<div className="loading">Loading...</div>`
|
|
else if @state.selected == @TILE_PAYMENTS_TO_YOU
|
|
@paymentsToYou()
|
|
else if @state.selected == @TILE_PAYMENTS_TO_JAMKAZAM
|
|
@paymentsToJamKazam()
|
|
else if @state.selected == @TILE_PAYMENT_METHOD
|
|
@paymentMethod()
|
|
else
|
|
@paymentsToJamKazam()
|
|
|
|
paymentsToYou: () ->
|
|
rows = []
|
|
|
|
for paymentHistory in @getCurrentList()
|
|
paymentMethod = 'Stripe'
|
|
|
|
if paymentHistory.distributed
|
|
date = paymentHistory.teacher_payment.teacher_payment_charge.last_billing_attempt_at
|
|
status = 'Paid'
|
|
else
|
|
date = paymentHistory.created_at
|
|
if paymentHistory.not_collectable
|
|
status = 'Uncollectible'
|
|
else if !paymentHistory.teacher?.teacher?.stripe_account_id?
|
|
status = 'No Stripe Acct'
|
|
else
|
|
status = 'Collecting'
|
|
|
|
|
|
date = context.JK.formatDate(date, true)
|
|
description = paymentHistory.description
|
|
|
|
if paymentHistory.teacher_payment?
|
|
amt = paymentHistory.teacher_payment.real_distribution_in_cents
|
|
else
|
|
amt = paymentHistory.real_distribution_in_cents
|
|
|
|
displayAmount = ' $' + (amt/100).toFixed(2)
|
|
|
|
amountClasses = {status: status}
|
|
|
|
row =
|
|
`<tr>
|
|
<td>{date}</td>
|
|
<td className="capitalize">{paymentMethod}</td>
|
|
<td>{description}</td>
|
|
<td className="capitalize">{status}</td>
|
|
<td className={classNames(amountClasses)}>{displayAmount}</td>
|
|
</tr>`
|
|
rows.push(row)
|
|
|
|
`<div>
|
|
<table className="payment-table">
|
|
<thead>
|
|
<tr>
|
|
<th>DATE</th>
|
|
<th>METHOD</th>
|
|
<th>DESCRIPTION</th>
|
|
<th>STATUS</th>
|
|
<th>AMOUNT</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{rows}
|
|
</tbody>
|
|
|
|
</table>
|
|
<a className="btn-next-pager" href="/api/sales?page=1">Next</a>
|
|
<div className="end-of-payments-list end-of-list">No more payment history</div>
|
|
<div className="input-aligner">
|
|
<a className="back button-grey" onClick={this.onBack}>BACK</a>
|
|
</div>
|
|
<br className="clearall" />
|
|
</div>`
|
|
|
|
paymentMethod: () ->
|
|
disabled = @state.updating || @reuseStoredCard()
|
|
|
|
submitClassNames = {'button-orange': true, 'purchase-btn': true, disabled: disabled && @state.updating}
|
|
updateCardClassNames = {'button-grey': true, 'update-btn': true, disabled: disabled && @state.updating}
|
|
backClassNames = {'button-grey': true, disabled: disabled && @state.updating}
|
|
|
|
cardNumberFieldClasses = {field: true, "card-number": true, error: @state.ccError}
|
|
expirationFieldClasses = {field: true, "expiration": true, error: @state.expiryError}
|
|
cvvFieldClasses = {field: true, "card-number": true, error: @state.cvvError}
|
|
inUSClasses = {field: true, "billing-in-us": true, error: @state.billingInUSError}
|
|
zipCodeClasses = {field: true, "zip-code": true, error: @state.zipCodeError}
|
|
nameClasses= {field: true, "name": true, error: @state.nameError}
|
|
formClasses= {stored: @reuseStoredCard()}
|
|
leftColumnClasses = {column: true, 'column-left': true, stored: @reuseStoredCard()}
|
|
rightColumnClasses = {column: true, 'column-right': true, stored: @reuseStoredCard()}
|
|
|
|
if @state.uncollectables.length > 0
|
|
uncollectable = @state.uncollectables[0]
|
|
uncollectableMessage = `<div className="uncollectable-msg">A charge for your music lesson with {uncollectable.teacher.name} failed. Please update your credit card information immediately so that we can pay the instructor. If you have called your credit card provider and believe there should be no problem with your card, please email us at <a href="mailto:support@jamkazam.com">support@jamkazam.com</a> so that we can figure out what's gone wrong. Thank you!</div>`
|
|
|
|
if @state.user?['has_stored_credit_card?'] && @state.uncollectables.length == 0
|
|
if @state.userWantsUpdateCC
|
|
header = 'Please update your billing address and payment information below.'
|
|
updateCardAction = `<a className={classNames(updateCardClassNames)} onClick={this.onLockPaymentInfo}>NEVERMIND</a>`
|
|
actions = `<div className="actions">
|
|
<a className={classNames(backClassNames)} onClick={this.onBack}>BACK</a>
|
|
{updateCardAction}
|
|
<a className={classNames(submitClassNames)} onClick={this.onSubmit}>SUBMIT CARD INFORMATION</a>
|
|
</div>`
|
|
else
|
|
header = 'You have already entered a credit card in JamKazam.'
|
|
updateCardAction = `<a className={classNames(updateCardClassNames)} onClick={this.onUnlockPaymentInfo}>I'D LIKE TO UPDATE MY PAYMENT INFO</a>`
|
|
actions = `<div className="actions">
|
|
<a className={classNames(backClassNames)} onClick={this.onBack}>BACK</a>
|
|
{updateCardAction}
|
|
</div>`
|
|
else
|
|
header = 'Please enter your billing address and payment information below.'
|
|
actions = `<div className="actions">
|
|
<a className={classNames(backClassNames)} onClick={this.onBack}>BACK</a><a
|
|
className={classNames(submitClassNames)} onClick={this.onSubmit}>SUBMIT CARD INFORMATION</a>
|
|
</div>`
|
|
if @state.shouldShowName && @state.user?.name?
|
|
username = @state.user?.name
|
|
nameField =
|
|
`<div className={classNames(nameClasses)}>
|
|
<label>Name:</label>
|
|
<input id="set-user-on-card" disabled={disabled} type="text" name="name" className="name" defaultValue={username}></input>
|
|
</div>`
|
|
|
|
`<div>
|
|
<div className={classNames(leftColumnClasses)}>
|
|
{uncollectableMessage}
|
|
<div className="paymethod-header">{header}</div>
|
|
<form autoComplete="on" onSubmit={this.onSubmit} className={classNames(formClasses)}>
|
|
{nameField}
|
|
<div className={classNames(cardNumberFieldClasses)}>
|
|
<label>Card Number:</label>
|
|
<input placeholder="1234 5678 9123 4567" type="tel" autoComplete="cc-number" disabled={disabled}
|
|
type="text" name="card-number" className="card-number"></input>
|
|
</div>
|
|
<div className={classNames(expirationFieldClasses)}>
|
|
<label>Expiration Date:</label>
|
|
<input placeholder="MM / YY" autoComplete="cc-expiry" disabled={disabled} type="text" name="expiration"
|
|
className="expiration"></input>
|
|
</div>
|
|
<div className={classNames(cvvFieldClasses)}>
|
|
<label>CVV:</label>
|
|
<input autoComplete="off" disabled={disabled} type="text" name="cvv" className="cvv"></input>
|
|
</div>
|
|
<div className={classNames(zipCodeClasses)}>
|
|
<label>Zip Code</label>
|
|
<input autoComplete="off" disabled={disabled || !this.state.billingInUS} type="text" name="zip"
|
|
className="zip"></input>
|
|
</div>
|
|
<div className={classNames(inUSClasses)}>
|
|
<label>Billing Address<br/>is in the U.S.</label>
|
|
<input type="checkbox" name="billing-address-in-us" className="billing-address-in-us"
|
|
value={this.state.billingInUS}/>
|
|
</div>
|
|
<input style={{'display':'none'}} type="submit" name="submit"/>
|
|
</form>
|
|
{actions}
|
|
</div>
|
|
<br className="clearall"/>
|
|
</div>`
|
|
|
|
paymentsToJamKazam: () ->
|
|
rows = []
|
|
uncollectables = []
|
|
|
|
for uncollectable in @state.uncollectables
|
|
date = context.JK.formatDate(uncollectable.last_billed_at_date, true)
|
|
paymentMethod = 'Credit Card'
|
|
amt = uncollectable.expected_price_in_cents
|
|
displayAmount = ' $' + (amt/100).toFixed(2)
|
|
|
|
if uncollectable['is_card_declined?']
|
|
reason = 'card declined'
|
|
else if uncollectable['is_card_expired?']
|
|
reason = 'card expired'
|
|
else
|
|
reason = 'charge fail'
|
|
|
|
row =
|
|
`<tr>
|
|
<td>{date}</td>
|
|
<td className="capitalize">{paymentMethod}</td>
|
|
<td>{uncollectable.description}</td>
|
|
<td className="capitalize">{reason}</td>
|
|
<td>{displayAmount}</td>
|
|
</tr>`
|
|
uncollectables.push(row)
|
|
|
|
if uncollectables.length > 0
|
|
uncollectableTable = `
|
|
<div className="uncollectables">
|
|
<div className="uncollectable-msg">You have unpaid lessons, which are listed immediately below. <a onClick={this.selectionMade.bind(this, this.TILE_PAYMENT_METHOD)}>Click here</a> to update your credit card info.</div>
|
|
<div className="table-header">Unpaid Lessons</div>
|
|
<table className="payment-table unpaid">
|
|
<thead>
|
|
<tr>
|
|
<th>CHARGED AT</th>
|
|
<th>METHOD</th>
|
|
<th>DESCRIPTION</th>
|
|
<th>REASON</th>
|
|
<th>AMOUNT</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{uncollectables}
|
|
</tbody>
|
|
</table>
|
|
<div className="table-header second">Payments</div>
|
|
</div>`
|
|
for paymentHistory in @getCurrentList()
|
|
paymentMethod = 'Credit Card'
|
|
if paymentHistory.sale?
|
|
sale = paymentHistory.sale
|
|
amt = sale.recurly_total_in_cents
|
|
status = 'paid'
|
|
displayAmount = ' $' + (amt/100).toFixed(2)
|
|
date = context.JK.formatDate(sale.created_at, true)
|
|
items = []
|
|
for line_item in sale.line_items
|
|
items.push(line_item.product_info?.name)
|
|
description = items.join(', ')
|
|
else
|
|
# this is a recurly webhook
|
|
transaction = paymentHistory.transaction
|
|
amt = transaction.amount_in_cents
|
|
status = transaction.transaction_type
|
|
displayAmount = '($' + (amt/100).toFixed(2) + ')'
|
|
date = context.JK.formatDate(transaction.transaction_at, true)
|
|
description = transaction.admin_description
|
|
|
|
amountClasses = {status: status}
|
|
|
|
row =
|
|
`<tr>
|
|
<td>{date}</td>
|
|
<td className="capitalize">{paymentMethod}</td>
|
|
<td>{description}</td>
|
|
<td className="capitalize">{status}</td>
|
|
<td className={classNames(amountClasses)}>{displayAmount}</td>
|
|
</tr>`
|
|
rows.push(row)
|
|
|
|
`<div>
|
|
{uncollectableTable}
|
|
<table className="payment-table">
|
|
<thead>
|
|
<tr>
|
|
<th>DATE</th>
|
|
<th>METHOD</th>
|
|
<th>DESCRIPTION</th>
|
|
<th>STATUS</th>
|
|
<th>AMOUNT</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{rows}
|
|
</tbody>
|
|
|
|
</table>
|
|
<a className="btn-next-pager" href="/api/sales?page=1">Next</a>
|
|
<div className="end-of-payments-list end-of-list">No more payment history</div>
|
|
<div className="input-aligner">
|
|
<a className="back button-grey" onClick={this.onBack}>BACK</a>
|
|
</div>
|
|
<br className="clearall" />
|
|
</div>`
|
|
|
|
selectionMade: (selection, e) ->
|
|
e.preventDefault()
|
|
@getUncollectables()
|
|
@setState({selected: selection})
|
|
|
|
activeTile: (selected = this.state.selected) ->
|
|
if selected?
|
|
selected
|
|
else
|
|
@tiles()[-1]
|
|
|
|
createTileLink: (i, tile) ->
|
|
if this.state.selected?
|
|
active = this.state.selected == tile
|
|
else
|
|
active = i == 0
|
|
|
|
|
|
tileClasses = {activeTile: active, 'profile-tile': true}
|
|
tileClasses[@myRole()] = true
|
|
tileClasses = classNames(tileClasses)
|
|
|
|
classes = classNames({last: i == @tiles().length - 1})
|
|
|
|
return `<div key={i} className={tileClasses}><a className={classes}
|
|
onClick={this.selectionMade.bind(this, tile)}>{tile}</a></div>`
|
|
|
|
tiles: () ->
|
|
if @viewerStudent()
|
|
tiles = @STUDENT_TILES
|
|
else
|
|
tiles = @TEACHER_TILES
|
|
tiles
|
|
|
|
myRole: () ->
|
|
if @viewerStudent()
|
|
'student'
|
|
else
|
|
'teacher'
|
|
|
|
viewerStudent: () ->
|
|
!@viewerTeacher()
|
|
|
|
viewerTeacher: () ->
|
|
this.state.user?.is_a_teacher
|
|
|
|
onCustomBack: (customBack, e) ->
|
|
e.preventDefault()
|
|
context.location = customBack
|
|
|
|
render: () ->
|
|
mainContent = @mainContent()
|
|
|
|
profileSelections = []
|
|
for tile, i in @tiles()
|
|
profileSelections.push(@createTileLink(i, tile, profileSelections))
|
|
|
|
profileNavClasses = {"profile-nav": true}
|
|
profileNavClasses[@myRole()] = true
|
|
profileNavClasses = classNames(profileNavClasses)
|
|
profileNav = `<div className={classNames(profileNavClasses)}>
|
|
{profileSelections}
|
|
</div>`
|
|
|
|
`<div className="content-body-scroller">
|
|
<div className="profile-header profile-head">
|
|
<div className="account-header">payment<br/>history:</div>
|
|
{profileNav}
|
|
<div className="clearall"></div>
|
|
</div>
|
|
|
|
<div className="profile-body">
|
|
<div className="profile-wrapper">
|
|
<div className="main-content">
|
|
{mainContent}
|
|
<br />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>`
|
|
|
|
onUpdate: (e) ->
|
|
e.preventDefault()
|
|
|
|
if this.state.updating
|
|
return
|
|
name = @root.find('input[name="name"]').val()
|
|
if @isSchoolManaged()
|
|
scheduling_communication = 'school'
|
|
else
|
|
scheduling_communication = 'teacher'
|
|
correspondence_email = @root.find('input[name="correspondence_email"]').val()
|
|
|
|
@setState(updating: true)
|
|
rest.updateSchool({
|
|
id: this.state.school.id,
|
|
name: name,
|
|
scheduling_communication: scheduling_communication,
|
|
correspondence_email: correspondence_email
|
|
}).done((response) => @onUpdateDone(response)).fail((jqXHR) => @onUpdateFail(jqXHR))
|
|
|
|
onUpdateDone: (response) ->
|
|
@setState({school: response, userSchedulingComm: null, schoolName: null, updateErrors: null, updating: false})
|
|
|
|
@app.layout.notify({title: "update success", text: "Your school information has been successfully updated"})
|
|
|
|
onUpdateFail: (jqXHR) ->
|
|
handled = false
|
|
|
|
@setState({updating: false})
|
|
|
|
if jqXHR.status == 422
|
|
errors = JSON.parse(jqXHR.responseText)
|
|
handled = true
|
|
@setState({updateErrors: errors})
|
|
|
|
onSubmit: (e) ->
|
|
@resetErrors()
|
|
|
|
e.preventDefault()
|
|
|
|
if !window.Stripe?
|
|
@app.layout.notify({
|
|
title: 'Payment System Not Loaded',
|
|
text: "Please refresh this page and try to enter your info again. Sorry for the inconvenience!"
|
|
})
|
|
else
|
|
ccNumber = @root.find('input.card-number').val()
|
|
expiration = @root.find('input.expiration').val()
|
|
cvv = @root.find('input.cvv').val()
|
|
inUS = @root.find('input.billing-address-in-us').is(':checked')
|
|
zip = @root.find('input.zip').val()
|
|
|
|
error = false
|
|
|
|
if @state.shouldShowName
|
|
name = @root.find('#set-user-on-card').val()
|
|
|
|
if name.indexOf('Anonymous') > -1
|
|
@setState({nameError: true})
|
|
error = true
|
|
|
|
if !$.payment.validateCardNumber(ccNumber)
|
|
@setState({ccError: true})
|
|
error = true
|
|
|
|
bits = expiration.split('/')
|
|
|
|
if bits.length == 2
|
|
month = bits[0].trim();
|
|
year = bits[1].trim()
|
|
|
|
month = new Number(month)
|
|
year = new Number(year)
|
|
|
|
if year < 2000
|
|
year += 2000
|
|
|
|
if !$.payment.validateCardExpiry(month, year)
|
|
@setState({expiryError: true})
|
|
error = true
|
|
else
|
|
@setState({expiryError: true})
|
|
error = true
|
|
|
|
|
|
cardType = $.payment.cardType(ccNumber)
|
|
|
|
if !$.payment.validateCardCVC(cvv, cardType)
|
|
@setState({cvvError: true})
|
|
error = true
|
|
|
|
if inUS && (!zip? || zip == '')
|
|
@setState({zipCodeError: true})
|
|
|
|
if error
|
|
return
|
|
|
|
data = {
|
|
number: ccNumber,
|
|
cvc: cvv,
|
|
exp_month: month,
|
|
exp_year: year,
|
|
}
|
|
|
|
@setState({updating: true})
|
|
|
|
window.Stripe.card.createToken(data, (status, response) => (@stripeResponseHandler(status, response)));
|
|
|
|
stripeResponseHandler: (status, response) ->
|
|
console.log("stripe response", JSON.stringify(response))
|
|
|
|
|
|
if response.error
|
|
@setState({updating: false})
|
|
if response.error.code == "invalid_number"
|
|
@setState({ccError: true, cvvError: null, expiryError: null})
|
|
else if response.error.code == "invalid_cvc"
|
|
@setState({ccError: null, cvvError: true, expiryError: null})
|
|
else if response.error.code == "invalid_expiry_year" || response.error.code == "invalid_expiry_month"
|
|
@setState({ccError: null, cvvError: null, expiryError: true})
|
|
|
|
#@setState({userWantsUpdateCC: false})
|
|
#window.UserActions.refresh()
|
|
@storeCC(response.id)
|
|
|
|
storeCC: (token) ->
|
|
if this.state.billingInUS
|
|
zip = @root.find('input.zip').val()
|
|
|
|
data = {
|
|
token: token,
|
|
zip: zip,
|
|
test_drive: false,
|
|
normal: false
|
|
}
|
|
|
|
if @state.shouldShowName
|
|
data.name = @root.find('#set-user-on-card').val()
|
|
|
|
@setState({updating: true})
|
|
rest.submitStripe(data).done((response) => @stripeSubmitted(response)).fail((jqXHR) => @stripeSubmitFailure(jqXHR))
|
|
|
|
stripeSubmitted: (response) ->
|
|
@setState({updating: false})
|
|
|
|
logger.debug("stripe submitted: " + JSON.stringify(response))
|
|
|
|
@setState({userWantsUpdateCC: false})
|
|
|
|
#if @state.shouldShowName
|
|
window.UserActions.refresh()
|
|
|
|
if response.uncollectables
|
|
context.JK.Banner.showAlert('Credit Card Updated', 'Than you. Your credit card info has been updated.<br/><br/>We will try to bill any unpaid lessons within the next hour, and an email will be sent at that time.')
|
|
else
|
|
@app.layout.notify({title: 'Credit Card Updated', text: 'Your credit card info has been updated.'})
|
|
|
|
|
|
stripeSubmitFailure: (jqXHR) ->
|
|
@setState({updating: false})
|
|
handled = false
|
|
if jqXHR.status == 422
|
|
errors = JSON.parse(jqXHR.responseText)
|
|
if errors.errors.name?
|
|
@setState({name: errors.errors.name[0]})
|
|
handled = true
|
|
else if errors.errors.user?
|
|
@app.layout.notify({title: "Can't Update Credit Card", text: "You " + errors.errors.user[0] + '.' })
|
|
handled = true
|
|
|
|
if !handled
|
|
@app.notifyServerError(jqXHR, 'Credit Card Not Stored')
|
|
|
|
onUnlockPaymentInfo: (e) ->
|
|
e.preventDefault()
|
|
@setState({userWantsUpdateCC: true})
|
|
|
|
onLockPaymentInfo: (e) ->
|
|
e.preventDefault()
|
|
@setState({userWantsUpdateCC: false})
|
|
|
|
reuseStoredCard: () ->
|
|
!@state.userWantsUpdateCC && @state.user?['has_stored_credit_card?'] && @state.uncollectables.length == 0
|
|
|
|
}) |