context = window rest = context.JK.Rest() logger = context.JK.logger UserStore = context.UserStore @LessonPayment = React.createClass({ mixins: [ ICheckMixin, Reflux.listenTo(AppStore, "onAppInit"), Reflux.listenTo(UserStore, "onUserChanged") ] shouldShowNameSet: false onAppInit: (@app) -> @app.bindScreen('jamclass/lesson-payment', {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()) @root.find('input.expiration').payment('formatCardExpiry') @root.find("input.card-number").payment('formatCardNumber') @root.find("input.cvv").payment('formatCardCVC') @iCheckify() componentDidUpdate: () -> @iCheckify() getInitialState: () -> { user: null, lesson: null, updating: false, billingInUS: true, userWantsUpdateCC: false } beforeHide: (e) -> @resetErrors() beforeShow: (e) -> afterShow: (e) -> @resetState() @resetErrors() @setState({updating: true}) rest.getUnprocessedLessonOrIntent().done((response) => @unprocessLoaded(response)).fail((jqXHR) => @failedBooking(jqXHR)) resetErrors: () -> @setState({ccError: null, cvvError: null, expiryError: null, billingInUSError: null, zipCodeError: null, nameError: null}) checkboxChanged: (e) -> checked = $(e.target).is(':checked') @setState({billingInUS: checked}) resetState: () -> @setState({updating: false, lesson: null}) unprocessLoaded: (response) -> @setState({updating: false}) logger.debug("unprocessed loaded", response) @setState(response) failedBooking: (jqXHR) -> @setState({updating: false}) @setState({lesson: null}) if jqXHR.status == 404 # no unprocessed lessons. That's arguably OK; the user is just going to enter their info up front. console.log("nothing") failedUnprocessLoad: (jqXHR) -> @setState({updating: false}) @app.layout.notify({ title: 'Unable to load lesson', text: 'Please attempt to book a free lesson first or refresh this page.' }) onBack: (e) -> e.preventDefault() window.location.href = '/client#/teachers/search' 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 if !@state.userWantsUpdateCC @attemptPurchase(null) 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, } window.Stripe.card.createToken(data, (status, response) => (@stripeResponseHandler(status, response))); stripeResponseHandler: (status, response) -> console.log("response", response) if response.error 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}) else @attemptPurchase(response.id) attemptPurchase: (token) -> if this.state.billingInUS zip = @root.find('input.zip').val() data = { token: token, zip: zip, test_drive: @state.lesson?.lesson_type == 'test-drive' || (@state.intent?.intent == 'book-test-drive') } if @state.shouldShowName data.name = @root.find('#set-user-on-card').val() rest.submitStripe(data).done((response) => @stripeSubmitted(response)).fail((jqXHR) => @stripeSubmitFailure(jqXHR)) stripeSubmitted: (response) -> logger.debug("stripe submitted", response) if @state.shouldShowName window.UserActions.refresh() # if the response has a lesson, take them there if response.lesson?.id? context.Banner.showNotice({ title: "Lesson Requested", text: "The teacher has been notified of your lesson request, and should respond soon.

We've taken you automatically to the page for this request, and sent an email to you with a link here as well. All communication with the teacher will show up on this page and in email." }) window.location = "/client#/jamclass/lesson-session/" + response.lesson.id else if response.test_drive? || response.intent_book_test_drive? if response.test_drive?.teacher_id teacher_id = response.test_drive?.teacher_id else if response.intent_book_test_drive? teacher_id = response.intent_book_test_drive?.teacher_id if teacher_id? text = "You now have 4 lessons that you can take with 4 different teachers.

We've taken you automatically to the lesson booking screen for the teacher you initially showed interest in." location = "/client#/profile/teacher/" + teacher_id location = "/client#/jamclass/book-lesson/test-drive_" + teacher_id else text = "You now have 4 lessons that you can take with 4 different teachers.

We've taken you automatically to the Teacher Search screen, so you can search for teachers right for you." location = "/client#/teachers/search" context.JK.Banner.showNotice({ title: "Test Drive Purchased", text: text }) window.location = location else window.location = "/client#/teachers/search" stripeSubmitFailure: (jqXHR) -> 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 Purchase Test Drive", 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?'] render: () -> disabled = @state.updating || @reuseStoredCard() if @state.updating photo_url = '/assets/shared/avatar_generic.png' name = 'Loading ...' teacherDetails = `
{name}
` else if @state.lesson? || @state.intent? if @state.lesson? photo_url = @state.lesson.teacher.photo_url name = @state.lesson.teacher.name lesson_length = this.state.lesson.lesson_length lesson_type = this.state.lesson.lesson_type else photo_url = @state.intent.teacher.photo_url name = @state.intent.teacher.name lesson_length = 30 lesson_type = @state.intent.intent.substring('book-'.length) if !photo_url? photo_url = '/assets/shared/avatar_generic.png' teacherDetails = `
{name}
` if lesson_type == 'single-free' header = `

enter card info

Your card wil not be charged.
See explanation to the right.
` bookingInfo = `

You are booking a single free {lesson_length}-minute lesson.

` bookingDetail = `

To book this lesson, you will need to enter your credit card information. You will absolutely not be charged for this free lesson, and you have no further commitment to purchase anything. We have to collect a credit card to prevent abuse by some users who would otherwise set up multiple free accounts to get multiple free lessons.

jamclass policies

` else if lesson_type == 'test-drive' if @reuseStoredCard() header = `

purchase test drive

` else header = `

enter payment info for test drive

` bookingInfo = `

` bookingDetail = `

You are purchasing the TestDrive package of JamClass by JamKazam. This purchase entitles you to take 4 private online music lessons - 1 each from 4 different instructors in the JamClass instructor community.
jamclass policies

` else if lesson_type == 'paid' header = `

enter payment info for lesson

` if this.state.lesson.recurring if this.state.lesson.payment_style == 'single' bookingInfo = `

You are booking a {lesson_length} minute lesson for ${this.state.lesson.booked_price.toFixed(2)}

` bookingDetail = `

Your card will not be charged until the day of the lesson. You must cancel at least 24 hours before your lesson is scheduled, or you will be charged for the lesson in full.
jamclass policies

` else if this.state.lesson.payment_style == 'weekly' bookingInfo = `

You are booking a weekly recurring series of {lesson_length}-minute lessons, to be paid individually as each lesson is taken, until cancelled.

` bookingDetail = `

Your card will be charged on the day of each lesson. If you need to cancel a lesson, you must do so at least 24 hours before the lesson is scheduled, or you will be charged for the lesson in full.
jamclass policies

` else if this.state.lesson.payment_style == 'monthly' bookingInfo = `

You are booking a weekly recurring series of {lesson_length}-minute lessons, to be paid for monthly until cancelled.

` bookingDetail = `

Your card will be charged on the first day of each month. Canceling individual lessons does not earn a refund when buying monthly. To cancel, you must cancel at least 24 hours before the beginning of the month, or you will be charged for that month in full.
jamclass policies

` else bookingInfo = `

You are booking a {lesson_length} minute lesson for ${this.state.lesson.booked_price.toFixed(2)}

` bookingDetail = `

Your card will not be charged until the day of the lesson. You must cancel at least 24 hours before your lesson is scheduled, or you will be charged for the lesson in full.
jamclass policies

` else header = `

enter payment info

` bookingInfo = `

You are entering your credit card info so that later checkouts go quickly. You can skip this for now.

` bookingDetail = `

Your card will not be charged until the day of the lesson. You must cancel at least 24 hours before your lesson is scheduled, or you will be charged for the lesson in full.
jamclass policies

` submitClassNames = {'button-orange': true, disabled: disabled && @state.updating} updateCardClassNames = {'button-grey': 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.user?['has_stored_credit_card?'] if @state.userWantsUpdateCC updateCardAction = `NEVERMIND, USE MY STORED PAYMENT INFO` leftPurchaseActions = `
BACK {updateCardAction} PURCHASE
` else updateCardAction = `I'D LIKE TO UPDATE MY PAYMENT INFO` rightPurchaseActions = `
BACK {updateCardAction} PURCHASE
` else leftPurchaseActions = `
BACKSUBMIT CARD INFORMATION
` if @state.shouldShowName && @state.user?.name? username = @state.user?.name nameField = `
` `
{header}
{nameField}
{leftPurchaseActions}
{teacherDetails}
{bookingInfo} {bookingDetail} {rightPurchaseActions}

` })