Set up a subscription page with subscription info and the subscribe button, taking that logic out of the payments page.
This commit is contained in:
parent
84cdc5d150
commit
02953d0e6c
11 changed files with 133 additions and 51 deletions
app
core
locale
styles/account
templates
views/account
server/users
|
@ -24,6 +24,7 @@ module.exports = class CocoRouter extends Backbone.Router
|
||||||
'account/unsubscribe': go('account/UnsubscribeView')
|
'account/unsubscribe': go('account/UnsubscribeView')
|
||||||
'account/profile': go('user/JobProfileView') # legacy URL, sent in emails
|
'account/profile': go('user/JobProfileView') # legacy URL, sent in emails
|
||||||
'account/payments': go('account/PaymentsView')
|
'account/payments': go('account/PaymentsView')
|
||||||
|
'account/subscription': go('account/SubscriptionView')
|
||||||
|
|
||||||
'admin': go('admin/MainAdminView')
|
'admin': go('admin/MainAdminView')
|
||||||
'admin/candidates': go('admin/CandidatesView')
|
'admin/candidates': go('admin/CandidatesView')
|
||||||
|
|
|
@ -811,13 +811,21 @@
|
||||||
recently_played: "Recently Played"
|
recently_played: "Recently Played"
|
||||||
no_recent_games: "No games played during the past two weeks."
|
no_recent_games: "No games played during the past two weeks."
|
||||||
payments: "Payments"
|
payments: "Payments"
|
||||||
|
purchased: "Purchased"
|
||||||
|
subscription: "Subscription"
|
||||||
service_apple: "Apple"
|
service_apple: "Apple"
|
||||||
service_web: "Web"
|
service_web: "Web"
|
||||||
paid_on: "Paid On"
|
paid_on: "Paid On"
|
||||||
service: "Service"
|
service: "Service"
|
||||||
price: "Price"
|
price: "Price"
|
||||||
gems: "Gems"
|
gems: "Gems"
|
||||||
status_subscribed: "You're currently subscribed at $9.99 USD/mo. Thanks for your support!"
|
active: "Active"
|
||||||
|
subscribed: "Subscribed"
|
||||||
|
unsubscribed: "Unsubscribed"
|
||||||
|
active_until: "Active Until"
|
||||||
|
cost: "Cost"
|
||||||
|
next_payment: "Next Payment"
|
||||||
|
card: "Card"
|
||||||
status_unsubscribed_active: "You're not subscribed and won't be billed, but your account is still active for now."
|
status_unsubscribed_active: "You're not subscribed and won't be billed, but your account is still active for now."
|
||||||
status_unsubscribed: "Get access to new levels, heroes, items, and bonus gems with a CodeCombat subscription!"
|
status_unsubscribed: "Get access to new levels, heroes, items, and bonus gems with a CodeCombat subscription!"
|
||||||
|
|
||||||
|
@ -878,6 +886,7 @@
|
||||||
clas: "CLAs"
|
clas: "CLAs"
|
||||||
play_counts: "Play Counts"
|
play_counts: "Play Counts"
|
||||||
feedback: "Feedback"
|
feedback: "Feedback"
|
||||||
|
payment_info: "Payment Info"
|
||||||
|
|
||||||
delta:
|
delta:
|
||||||
added: "Added"
|
added: "Added"
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
@import "app/styles/mixins"
|
|
||||||
@import "app/styles/bootstrap/variables"
|
|
||||||
|
|
||||||
#payments-view
|
|
||||||
.start-subscription-button, .end-subscription-button
|
|
||||||
margin-bottom: 20px
|
|
||||||
float: left
|
|
||||||
|
|
||||||
.payment-status
|
|
||||||
float: left
|
|
||||||
margin: 15px
|
|
5
app/styles/account/subscription-view.sass
Normal file
5
app/styles/account/subscription-view.sass
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
#subscription-view
|
||||||
|
.start-subscription-button, .end-subscription-button
|
||||||
|
margin-bottom: 20px
|
||||||
|
float: left
|
||||||
|
width: 100%
|
|
@ -18,4 +18,6 @@ block content
|
||||||
li.list-group-item
|
li.list-group-item
|
||||||
a.btn.btn-lg.btn-primary(href="/account/settings", data-i18n="play.settings")
|
a.btn.btn-lg.btn-primary(href="/account/settings", data-i18n="play.settings")
|
||||||
li.list-group-item
|
li.list-group-item
|
||||||
a.btn.btn-lg.btn-primary(href="/account/payments", data-i18n="account.payments")
|
a.btn.btn-lg.btn-primary(href="/account/payments", data-i18n="account.payments")
|
||||||
|
li.list-group-item
|
||||||
|
a.btn.btn-lg.btn-primary(href="/account/subscription", data-i18n="account.subscription")
|
|
@ -10,19 +10,10 @@ block content
|
||||||
a(href="/account", data-i18n="nav.account")
|
a(href="/account", data-i18n="nav.account")
|
||||||
li.active(data-i18n="account.payments")
|
li.active(data-i18n="account.payments")
|
||||||
|
|
||||||
if subscribed
|
|
||||||
button.end-subscription-button.btn.btn-lg.btn-warning(data-i18n="subscribe.unsubscribe") Unsubscribe
|
|
||||||
.payment-status(data-i18n="account.status_subscribed")
|
|
||||||
else if !me.isAnonymous()
|
|
||||||
button.start-subscription-button.btn.btn-lg.btn-success(data-i18n="subscribe.subscribe_title") Subscribe
|
|
||||||
if active
|
|
||||||
.payment-status(data-i18n="account.status_unsubscribed_active")
|
|
||||||
else
|
|
||||||
.payment-status(data-i18n="account.status_unsubscribed")
|
|
||||||
|
|
||||||
if payments.models.length
|
if payments.models.length
|
||||||
table.table.table-striped
|
table.table.table-striped
|
||||||
tr
|
tr
|
||||||
|
th(data-i18n="account.purchased")
|
||||||
th(data-i18n="account.paid_on")
|
th(data-i18n="account.paid_on")
|
||||||
th(data-i18n="account.service")
|
th(data-i18n="account.service")
|
||||||
th(data-i18n="account.price")
|
th(data-i18n="account.price")
|
||||||
|
@ -30,7 +21,11 @@ block content
|
||||||
for payment in payments.models
|
for payment in payments.models
|
||||||
- var service = payment.get('service')
|
- var service = payment.get('service')
|
||||||
tr
|
tr
|
||||||
td= moment(payment.getCreationDate()).format('lll')
|
if payment.get('productID')
|
||||||
|
td(data-i18n='account.gems')
|
||||||
|
else
|
||||||
|
td(data-i18n='subscribe.stripe_description')
|
||||||
|
td= moment(payment.getCreationDate()).format('l')
|
||||||
if service === 'ios'
|
if service === 'ios'
|
||||||
td(data-i18n="account.service_apple")
|
td(data-i18n="account.service_apple")
|
||||||
td= payment.get('ios').localPrice
|
td= payment.get('ios').localPrice
|
||||||
|
|
49
app/templates/account/subscription-view.jade
Normal file
49
app/templates/account/subscription-view.jade
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
extends /templates/base
|
||||||
|
|
||||||
|
block content
|
||||||
|
|
||||||
|
ol.breadcrumb
|
||||||
|
li
|
||||||
|
a(href="/")
|
||||||
|
span.glyphicon.glyphicon-home
|
||||||
|
li
|
||||||
|
a(href="/account", data-i18n="nav.account")
|
||||||
|
li.active(data-i18n="account.subscription")
|
||||||
|
|
||||||
|
.panel.panel-default
|
||||||
|
.panel-heading
|
||||||
|
if subscribed
|
||||||
|
button.end-subscription-button.btn.btn-lg.btn-warning(data-i18n="subscribe.unsubscribe") Unsubscribe
|
||||||
|
else if !me.isAnonymous()
|
||||||
|
button.start-subscription-button.btn.btn-lg.btn-success(data-i18n="subscribe.subscribe_title") Subscribe
|
||||||
|
|
||||||
|
.panel-body
|
||||||
|
table.table.table-striped
|
||||||
|
tr
|
||||||
|
th Status
|
||||||
|
td
|
||||||
|
if subscribed
|
||||||
|
strong(data-i18n="account.subscribed")
|
||||||
|
else
|
||||||
|
if active
|
||||||
|
strong(data-i18n="account.active")
|
||||||
|
.text-muted(data-i18n="account.status_unsubscribed_active")
|
||||||
|
else
|
||||||
|
strong(data-i18n="account.unsubscribed")
|
||||||
|
.text-muted(data-i18n="account.status_unsubscribed")
|
||||||
|
if activeUntil
|
||||||
|
tr
|
||||||
|
th(data-i18n="account.active_until")
|
||||||
|
td= moment(activeUntil).format('l')
|
||||||
|
if nextPaymentDate
|
||||||
|
tr
|
||||||
|
th(data-i18n="account.next_paymthn")
|
||||||
|
td= moment(nextPaymentDate).format('l')
|
||||||
|
if cost
|
||||||
|
tr
|
||||||
|
th(data-i18n="account.cost")
|
||||||
|
td= cost
|
||||||
|
if card
|
||||||
|
tr
|
||||||
|
th(data-i18n="account.card")
|
||||||
|
td= card
|
|
@ -34,6 +34,8 @@ block header
|
||||||
a(href="/account/settings", data-i18n="play.settings")
|
a(href="/account/settings", data-i18n="play.settings")
|
||||||
li
|
li
|
||||||
a(href="/account/payments", data-i18n="account.payments")
|
a(href="/account/payments", data-i18n="account.payments")
|
||||||
|
li
|
||||||
|
a(href="/account/subscription", data-i18n="account.subscription")
|
||||||
li
|
li
|
||||||
a#logout-button(data-i18n="login.log_out")
|
a#logout-button(data-i18n="login.log_out")
|
||||||
|
|
||||||
|
|
|
@ -2,19 +2,11 @@ RootView = require 'views/core/RootView'
|
||||||
template = require 'templates/account/payments-view'
|
template = require 'templates/account/payments-view'
|
||||||
CocoCollection = require 'collections/CocoCollection'
|
CocoCollection = require 'collections/CocoCollection'
|
||||||
Payment = require 'models/Payment'
|
Payment = require 'models/Payment'
|
||||||
SubscribeModal = require 'views/play/modal/SubscribeModal'
|
|
||||||
|
|
||||||
module.exports = class PaymentsView extends RootView
|
module.exports = class PaymentsView extends RootView
|
||||||
id: "payments-view"
|
id: "payments-view"
|
||||||
template: template
|
template: template
|
||||||
|
|
||||||
events:
|
|
||||||
'click .start-subscription-button': 'onClickStartSubscription'
|
|
||||||
'click .end-subscription-button': 'onClickEndSubscription'
|
|
||||||
|
|
||||||
subscriptions:
|
|
||||||
'subscribe-modal:subscribed': 'onSubscribed'
|
|
||||||
|
|
||||||
constructor: (options) ->
|
constructor: (options) ->
|
||||||
super(options)
|
super(options)
|
||||||
@payments = new CocoCollection([], { url: '/db/payment', model: Payment, comparator:'_id' })
|
@payments = new CocoCollection([], { url: '/db/payment', model: Payment, comparator:'_id' })
|
||||||
|
@ -23,21 +15,4 @@ module.exports = class PaymentsView extends RootView
|
||||||
getRenderData: ->
|
getRenderData: ->
|
||||||
c = super()
|
c = super()
|
||||||
c.payments = @payments
|
c.payments = @payments
|
||||||
c.subscribed = me.get('stripe')?.planID
|
c
|
||||||
c.active = me.isPremium()
|
|
||||||
c
|
|
||||||
|
|
||||||
onClickStartSubscription: (e) ->
|
|
||||||
@openModalView new SubscribeModal()
|
|
||||||
window.tracker?.trackEvent 'Show subscription modal', category: 'Subscription', label: 'payments view'
|
|
||||||
window.tracker?.trackPageView "subscription/show-modal", ['Google Analytics']
|
|
||||||
|
|
||||||
onSubscribed: ->
|
|
||||||
document.location.reload()
|
|
||||||
|
|
||||||
onClickEndSubscription: (e) ->
|
|
||||||
stripe = _.clone(me.get('stripe'))
|
|
||||||
delete stripe.planID
|
|
||||||
me.set('stripe', stripe)
|
|
||||||
me.patch({headers: {'X-Change-Plan': 'true'}})
|
|
||||||
@listenToOnce me, 'sync', -> document.location.reload()
|
|
55
app/views/account/SubscriptionView.coffee
Normal file
55
app/views/account/SubscriptionView.coffee
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
RootView = require 'views/core/RootView'
|
||||||
|
template = require 'templates/account/subscription-view'
|
||||||
|
CocoCollection = require 'collections/CocoCollection'
|
||||||
|
SubscribeModal = require 'views/play/modal/SubscribeModal'
|
||||||
|
|
||||||
|
module.exports = class SubscriptionView extends RootView
|
||||||
|
id: "subscription-view"
|
||||||
|
template: template
|
||||||
|
|
||||||
|
events:
|
||||||
|
'click .start-subscription-button': 'onClickStartSubscription'
|
||||||
|
'click .end-subscription-button': 'onClickEndSubscription'
|
||||||
|
|
||||||
|
subscriptions:
|
||||||
|
'subscribe-modal:subscribed': 'onSubscribed'
|
||||||
|
|
||||||
|
constructor: (options) ->
|
||||||
|
super(options)
|
||||||
|
if me.get('stripe')
|
||||||
|
options = { url: "/db/user/#{me.id}/stripe" }
|
||||||
|
options.success = (@stripeInfo) =>
|
||||||
|
@supermodel.addRequestResource('payment_info', options).load()
|
||||||
|
|
||||||
|
getRenderData: ->
|
||||||
|
c = super()
|
||||||
|
if @stripeInfo
|
||||||
|
if subscription = @stripeInfo.subscriptions?.data?[0]
|
||||||
|
periodEnd = new Date((subscription.trial_end or subscription.current_period_end) * 1000)
|
||||||
|
if subscription.cancel_at_period_end
|
||||||
|
c.activeUntil = periodEnd
|
||||||
|
else
|
||||||
|
c.nextPaymentDate = periodEnd
|
||||||
|
c.cost = "$#{(subscription.plan.amount/100).toFixed(2)}"
|
||||||
|
if card = @stripeInfo.cards?.data?[0]
|
||||||
|
c.card = "#{card.brand}: x#{card.last4}"
|
||||||
|
|
||||||
|
c.stripeInfo = @stripeInfo
|
||||||
|
c.subscribed = me.get('stripe')?.planID
|
||||||
|
c.active = me.isPremium()
|
||||||
|
c
|
||||||
|
|
||||||
|
onClickStartSubscription: (e) ->
|
||||||
|
@openModalView new SubscribeModal()
|
||||||
|
window.tracker?.trackEvent 'Show subscription modal', category: 'Subscription', label: 'account subscription view'
|
||||||
|
window.tracker?.trackPageView "subscription/show-modal", ['Google Analytics']
|
||||||
|
|
||||||
|
onSubscribed: ->
|
||||||
|
document.location.reload()
|
||||||
|
|
||||||
|
onClickEndSubscription: (e) ->
|
||||||
|
stripe = _.clone(me.get('stripe'))
|
||||||
|
delete stripe.planID
|
||||||
|
me.set('stripe', stripe)
|
||||||
|
me.patch({headers: {'X-Change-Plan': 'true'}})
|
||||||
|
@listenToOnce me, 'sync', -> document.location.reload()
|
|
@ -238,7 +238,7 @@ UserHandler = class UserHandler extends Handler
|
||||||
@getDocumentForIdOrSlug handle, (err, user) =>
|
@getDocumentForIdOrSlug handle, (err, user) =>
|
||||||
return @sendNotFoundError(res) if not user
|
return @sendNotFoundError(res) if not user
|
||||||
return @sendForbiddenError(res) unless req.user and (req.user.isAdmin() or req.user.get('_id').equals(user.get('_id')))
|
return @sendForbiddenError(res) unless req.user and (req.user.isAdmin() or req.user.get('_id').equals(user.get('_id')))
|
||||||
return @sendNotFoundError(res) #if not customerID = user.get('stripe')?.customerID
|
return @sendNotFoundError(res) if not customerID = user.get('stripe')?.customerID
|
||||||
stripe.customers.retrieve customerID, (err, customer) =>
|
stripe.customers.retrieve customerID, (err, customer) =>
|
||||||
return @sendDatabaseError(res, err) if err
|
return @sendDatabaseError(res, err) if err
|
||||||
@sendSuccess(res, JSON.stringify(customer, null, '\t'))
|
@sendSuccess(res, JSON.stringify(customer, null, '\t'))
|
||||||
|
|
Reference in a new issue