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, "test-drive": false, teacher: null, package: null } beforeHide: (e) -> @resetErrors() beforeShow: (e) -> afterShow: (e) -> @resetState() @resetErrors() parsed = @parseId(e.id) parsed.updating = false if parsed['lesson-booking'] parsed.updating = true rest.getLessonBooking({id: parsed.lesson_booking_id}).done((response) => @lessonBookingLoaded(response)).fail((jqXHR) => @failedLessonBooking(jqXHR)) else if parsed['teacher-intent'] parsed.updating = true rest.getUserDetail({id: parsed.teacher_id}).done((response) => @teacherLoaded(response)).fail((jqXHR) => @failedTeacher(jqXHR)) else if parsed['test-drive'] logger.debug("test-drive lesson payment; no teacher/booking in context") else if parsed['package-choice'] logger.debug("TestDrive package selected " + parsed.package_id) rest.getTestDrivePackageChoice({id: parsed.package_id}).done((response) => @packageLoaded(response)).fail((jqXHR) => @failedPackage(jqXHR)) else logger.error("unknown state for lesson-payment") window.location.href = '/client#/jamclass' @setState(parsed) parseId: (id) -> result = {} # id can be: # 'test-drive' # or 'lesson-booking_id' # or 'teacher_id result['test-drive'] = false result['lesson-booking'] = false result['teacher-intent'] = false result['package-choice'] = false bits = id.split('_') if bits.length == 1 # should be id=test-drive result[id] = true else if bits.length == 2 type = bits[0] if type == 'lesson-booking' result[type] = true result.lesson_booking_id = bits[1] else if type == 'teacher' result['teacher-intent'] = true result.teacher_id = bits[1] else if type == 'package' result['package-choice'] = true result.package_id = bits[1] logger.debug("LessonPayment: parseId " + JSON.stringify(result)) result 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, teacher: null, "test-drive": false, "lesson-booking" : false, "teacher-intent": false, package: null, "package-choice": null}) lessonBookingLoaded: (response) -> @setState({updating: false}) logger.debug("lesson booking loaded", response) if response.card_presumed_ok context.JK.Banner.showNotice("Lesson Already Requested", "You have already requested this lesson from this teacher.") window.location.href = "/client#/jamclass" @setState({lesson: response, teacher: response.teacher}) failedLessonBooking: (jqXHR) -> @setState({updating: false}) @app.layout.notify({ title: 'unable to load lesson info', text: 'Something has gone wrong. Please try refreshing the page.' }) teacherLoaded: (response) -> @setState({updating: false}) logger.debug("teacher loaded", response) @setState({teacher: response}) failedTeacher: (jqXHR) -> @setState({updating: false}) @app.layout.notify({ title: 'unable to load teacher info', text: 'Something has gone wrong. Please try refreshing the page.' }) packageLoaded: (response) -> @setState({updating: false}) logger.debug("package loaded", response) @setState({package: response}) failedPackage: (jqXHR) -> @setState({updating: false}) @app.layout.notify({ title: 'unable to load package info', text: 'Something has gone wrong. Please try refreshing the page.' }) onBack: (e) -> e.preventDefault() window.location.href = '/client#/teachers/search' onSubmit: (e) -> @resetErrors() e.preventDefault() if !window.Stripe? logger.error("no 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 @reuseStoredCard() @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, } @setState({updating: true}) logger.debug("creating stripe token") 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}) else @attemptPurchase(response.id) isNormal: () -> @state.lesson?.lesson_type == 'paid' isTestDrive: () -> @state['test-drive'] == true || @state.lesson?.lesson_type == 'test-drive' || @state['teacher-intent'] || @state['package-choice'] == true attemptPurchase: (token) -> if this.state.billingInUS zip = @root.find('input.zip').val() data = { token: token, zip: zip, test_drive: @isTestDrive(), booking_id: @state.lesson?.id, test_drive_package_choice_id: @state.package?.id normal: @isNormal() } if @state.shouldShowName data.name = @root.find('#set-user-on-card').val() @setState({updating: true}) logger.debug("submitting purchase info: " + JSON.stringify(data)) rest.submitStripe(data).done((response) => @stripeSubmitted(response)).fail((jqXHR) => @stripeSubmitFailure(jqXHR)) stripeSubmitted: (response) -> @setState({updating: false}) logger.debug("stripe submitted: " + JSON.stringify(response)) #if @state.shouldShowName window.UserActions.refresh() # if the response has a lesson, take them there if response.test_drive? # ok, they bought a package if response.lesson_package_type? # always of form test-drive-# prefixLength = "test-drive-".length packageLength = response.lesson_package_type.package_type.length testDriveCount = response.lesson_package_type.package_type.substring(prefixLength, packageLength) logger.debug("testDriveCount: " + testDriveCount) testDriveCountInt = parseInt(testDriveCount); if context._.isNaN(testDriveCountInt) testDriveCountInt = 3 context.JK.GA.trackTestDrivePurchase(testDriveCountInt); if response.test_drive?.teacher_id teacher_id = response.test_drive.teacher_id if testDriveCount == '1' text = "You have purchased 1 TestDrive credit and have used it to request a JamClass with #{@state.package.teachers[0].user.name}. The teacher has received your request and should respond shortly." else if response.package? text = "Each teacher has received your request and should respond shortly." else text = "You have purchased #{testDriveCount} TestDrive credits and have used 1 credit to request a JamClass with #{@state.teacher.name}. The teacher has received your request and should respond shortly." location = "/client#/jamclass" else if @state.teacher?.id # the user bought the testdrive, and there is a teacher of interest in context (but no booking) if testDriveCount == '1' text = "You now have 1 TestDrive credit.

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

We've taken you to the lesson booking screen for the teacher you initially showed interest in." location = "/client#/jamclass/book-lesson/test-drive_" + teacher_id else # the user bought test drive, but 'cold' , i.e., no teacher in context if testDriveCount == '1' text = "You now have 1 TestDrive credit.

We've taken you to the Teacher Search screen, so you can search for teachers right for you." location = "/client#/teachers/search" else text = "You now have #{testDriveCount} TestDrive credits that you can take with #{testDriveCount} different teachers.

We've taken you to the Teacher Search screen, so you can search for teachers right for you." location = "/client#/teachers/search" context.JK.Banner.showNotice("TestDrive Purchased",text) window.location = location else context.JK.Banner.showNotice("Something Went Wrong", "Please email support@jamkazam.com and indicate that your attempt to buy a TestDrive failed") window.location = "/client#/jamclass/" else if response.lesson?.id? context.JK.Banner.showNotice("Lesson Requested","The teacher has been notified of your lesson request, and should respond soon.

We've taken you back to the JamClass home page, where you can check the status of this lesson, as well as any other past and future lessons.") url = "/client#/jamclass/lesson-booking/" + response.lesson.id url = "/client#/jamclass" window.location.href = url else window.location = "/client#/teachers/search" stripeSubmitFailure: (jqXHR) -> logger.debug("stripe submission failure", jqXHR.responseText) @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 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?'] bookedPrice: () -> booked_price = this.state.lesson.booked_price if booked_price? if typeof booked_price == "string" booked_price = new Number(booked_price) return booked_price.toFixed(2) else return '??' render: () -> disabled = @state.updating || @reuseStoredCard() if @state.updating photo_url = '/assets/shared/avatar_generic.png' name = 'Loading ...' teacherDetails = `
{name}
` else if @state.lesson? || @state['test-drive'] || @state.teacher? || @state['package-choice'] == true if @state.teacher? photo_url = @state.teacher.photo_url name = @state.teacher.name if !photo_url? photo_url = '/assets/shared/avatar_generic.png' teacherDetails = `
{name}
` else if @state.package? teachers = [] teachersHolder = [] count = 0 for teacher_choice in @state.package.teachers if count == 2 teachersHolder.push( `
{teachers}
`) teachers = [] teacher = teacher_choice.user photo_url = teacher.photo_url name = teacher.name if !photo_url? photo_url = '/assets/shared/avatar_generic.png' teachers.push( `
{teacher.first_name}
{teacher.last_name}
`) count++ teachersHolder.push( `
{teachers}
`) teacherDetails = `
{teachersHolder}
` if @state.lesson? lesson_length = @state.lesson.lesson_length lesson_type = @state.lesson.lesson_type else lesson_length = 30 lesson_type = 'test-drive' if @isTestDrive() if @reuseStoredCard() header = `

purchase test drive

` else header = `

enter payment info for test drive

` bookingInfo = `

` if this.state['package-choice'] if this.state.package? if @state.package.teachers.length == 1 explanation = `You are purchasing the TestDrive package of JamClass by JamKazam. This purchase entities you to take a private online music lesson from this instructor. The price of this TestDrive is $14.99. If you have scheduling conflicts with this instructors, we will help you choose another teacher as a replacement.` else if @state.package.teachers.length == 2 explanation = `You are purchasing the TestDrive package of JamClass by JamKazam. This purchase entities you to take 2 private online music lessons - 1 each from these 2 instructors. The price of this TestDrive is $29.99. If you have scheduling conflicts with any of these instructors, we will help you choose another teacher as a replacement.` else if @state.package.teachers.length == 4 explanation = `You are purchasing the TestDrive package of JamClass by JamKazam. This purchase entities you to take 4 private online music lessons - 1 each from these 4 instructors. The price of this TestDrive is $49.99. If you have scheduling conflicts with any of these instructors, we will help you choose another teacher as a replacement.` else alert("unknown package type") else if this.state.user.lesson_package_type_id == 'test-drive' explanation = `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. The price of this TestDrive package is $49.99.` else if this.state.user.lesson_package_type_id == 'test-drive-1' explanation =`You are purchasing the TestDrive package of JamClass by JamKazam. This purchase entitles you to take 1 private online music lesson from an instructor in the JamClass instructor community. The price of this TestDrive package is $14.99.` else if this.state.user.lesson_package_type_id == 'test-drive-2' explanation =`You are purchasing the TestDrive package of JamClass by JamKazam. This purchase entitles you to take 2 private online music lessons - 1 each from 2 different instructors in the JamClass instructor community. The price of this TestDrive package is $29.99.` else alert("You do not have a test drive package selected: " + this.state.user.lesson_package_type_id ) bookingDetail = `

{explanation}
jamclass policies

` else if @isNormal() if @reuseStoredCard() header = `

purchase lesson

` else 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.bookedPrice()}

` 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.bookedPrice()}

` 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 @reuseStoredCard() header = `

payment info already entered

` 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, 'purchase-btn': 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}

` })