mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2025-03-14 07:00:01 -04:00
Add subscription sale
Give a discount for purchasing a year New sale button on subscribe modal New subscription sale landing page
This commit is contained in:
parent
a7074b11ca
commit
9e222d0873
14 changed files with 385 additions and 19 deletions
|
@ -24,6 +24,7 @@ module.exports = class CocoRouter extends Backbone.Router
|
|||
'account/profile': go('EmployersView') # Show the not-recruiting-now screen
|
||||
'account/payments': go('account/PaymentsView')
|
||||
'account/subscription': go('account/SubscriptionView')
|
||||
'account/subscription/sale': go('account/SubscriptionSaleView')
|
||||
'account/invoices': go('account/InvoicesView')
|
||||
|
||||
'admin': go('admin/MainAdminView')
|
||||
|
|
|
@ -437,7 +437,18 @@
|
|||
payment_methods_title: "Accepted Payment Methods"
|
||||
payment_methods_blurb1: "We currently accept credit cards and Alipay."
|
||||
payment_methods_blurb2: "If you require an alternate form of payment, please contact"
|
||||
sale_already_subscribed: "You're already subscribed!"
|
||||
sale_blurb1: "Save 35%"
|
||||
sale_blurb2: "off regular subscription price!"
|
||||
sale_button: "Sale!"
|
||||
sale_button_title: "Save 35% when you purchase a 1 year subscription"
|
||||
sale_click_here: "Click Here"
|
||||
sale_continue: "Ready to continue adventuring?"
|
||||
sale_paid: "Payment received. Thanks!"
|
||||
sale_title: "Back to School Sale"
|
||||
sale_view_button: "Buy 1 year subscription for"
|
||||
stripe_description: "Monthly Subscription"
|
||||
stripe_description_year_sale: "1 Year Subscription (35% discount)"
|
||||
subscription_required_to_play: "You'll need a subscription to play this level."
|
||||
unlock_help_videos: "Subscribe to unlock all video tutorials."
|
||||
|
||||
|
@ -446,7 +457,7 @@
|
|||
managed_by: "Managed by"
|
||||
will_be_cancelled: "Will be cancelled on"
|
||||
currently_free: "You currently have a free subscription"
|
||||
currently_free_until: "You currently have a free subscription until"
|
||||
currently_free_until: "You currently have a subscription until" #{changed}
|
||||
was_free_until: "You had a free subscription until"
|
||||
managed_subs: "Managed Subscriptions"
|
||||
managed_subs_desc: "Add subscriptions for other players (students, children, etc.)"
|
||||
|
@ -1102,6 +1113,7 @@
|
|||
no_recent_games: "No games played during the past two weeks."
|
||||
payments: "Payments"
|
||||
purchased: "Purchased"
|
||||
sale: "Sale"
|
||||
subscription: "Subscription"
|
||||
invoices: "Invoices"
|
||||
service_apple: "Apple"
|
||||
|
|
|
@ -137,14 +137,17 @@ module.exports = class User extends CocoModel
|
|||
return 0 unless numVideos > 0
|
||||
return me.get('testGroupNumber') % numVideos
|
||||
|
||||
isPremium: ->
|
||||
return true if me.isInGodMode()
|
||||
return true if me.isAdmin()
|
||||
hasSubscription: ->
|
||||
return false unless stripe = @get('stripe')
|
||||
return true if stripe.sponsorID
|
||||
return true if stripe.subscriptionID
|
||||
return true if stripe.free is true
|
||||
return true if _.isString(stripe.free) and new Date() < new Date(stripe.free)
|
||||
|
||||
isPremium: ->
|
||||
return true if me.isInGodMode()
|
||||
return true if me.isAdmin()
|
||||
return true if me.hasSubscription()
|
||||
return false
|
||||
|
||||
tiersByLevel = [-1, 0, 0.05, 0.14, 0.18, 0.32, 0.41, 0.5, 0.64, 0.82, 0.91, 1.04, 1.22, 1.35, 1.48, 1.65, 1.78, 1.96, 2.1, 2.24, 2.38, 2.55, 2.69, 2.86, 3.03, 3.16, 3.29, 3.42, 3.58, 3.74, 3.89, 4.04, 4.19, 4.32, 4.47, 4.64, 4.79, 4.96,
|
||||
|
|
7
app/styles/account/subscription-sale-view.sass
Normal file
7
app/styles/account/subscription-sale-view.sass
Normal file
|
@ -0,0 +1,7 @@
|
|||
#subscription-sale-view
|
||||
.center
|
||||
text-align: center
|
||||
.sale-blurb
|
||||
font-size: 22px
|
||||
#pay-button
|
||||
font-size: 18px
|
|
@ -152,7 +152,7 @@
|
|||
.purchase-button
|
||||
position: absolute
|
||||
right: 24px
|
||||
width: 400px
|
||||
width: 300px
|
||||
height: 70px
|
||||
top: 430px
|
||||
font-size: 32px
|
||||
|
@ -190,6 +190,24 @@
|
|||
border-width: 14px 20px 20px 20px
|
||||
color: darken(white, 5%)
|
||||
|
||||
//- Sale button
|
||||
|
||||
.sale-button
|
||||
position: absolute
|
||||
left: 290px
|
||||
width: 115px
|
||||
height: 70px
|
||||
top: 430px
|
||||
font-size: 32px
|
||||
line-height: 42px
|
||||
border-style: solid
|
||||
border-image: url(/images/common/button-background-primary-active.png) 14 20 20 20 fill round
|
||||
border-width: 14px 20px 20px 20px
|
||||
color: darken(white, 5%)
|
||||
|
||||
span
|
||||
pointer-events: none
|
||||
|
||||
.email-parent-form
|
||||
.email_invalid
|
||||
color: red
|
||||
|
|
54
app/templates/account/subscription-sale-view.jade
Normal file
54
app/templates/account/subscription-sale-view.jade
Normal file
|
@ -0,0 +1,54 @@
|
|||
extends /templates/base
|
||||
|
||||
block content
|
||||
|
||||
ol.breadcrumb
|
||||
li
|
||||
a(href="/")
|
||||
span.glyphicon.glyphicon-home
|
||||
li
|
||||
a(href="/account", data-i18n="nav.account")
|
||||
li
|
||||
a(href="/account/subscription", data-i18n="account.subscription")
|
||||
li.active(data-i18n="account.sale") Sale
|
||||
|
||||
if me.get('anonymous')
|
||||
p(data-i18n="account_settings.not_logged_in")
|
||||
else if me.hasSubscription()
|
||||
h1(data-i18n="subscribe.sale_already_subscribed")
|
||||
span.spr(data-i18n="subscribe.sale_continue")
|
||||
a(href="/play", data-i18n="subscribe.sale_click_here")
|
||||
else
|
||||
if state === 'purchasing'
|
||||
.alert.alert-info(data-i18n="account_invoices.purchasing")
|
||||
else
|
||||
h1.center(data-i18n="subscribe.sale_title")
|
||||
br
|
||||
p.center.sale-blurb
|
||||
strong.spr(data-i18n="subscribe.sale_blurb1")
|
||||
span(data-i18n="subscribe.sale_blurb2")
|
||||
br
|
||||
if !state || state !== 'invoice_paid'
|
||||
p.center
|
||||
button.btn.btn-success#pay-button #{payButtonText}
|
||||
br
|
||||
else if state === 'invoice_paid'
|
||||
div.center
|
||||
span.spr(data-i18n="subscribe.sale_continue")
|
||||
a(href="/play", data-i18n="subscribe.sale_click_here")
|
||||
br
|
||||
#declined-alert.alert.alert-success.alert-dismissible
|
||||
button.close(type="button" data-dismiss="alert")
|
||||
span(aria-hidden="true") ×
|
||||
p(data-i18n="subscribe.sale_paid")
|
||||
if state === 'declined'
|
||||
#declined-alert.alert.alert-danger.alert-dismissible
|
||||
span(data-i18n="account_invoices.declined")
|
||||
button.close(type="button" data-dismiss="alert")
|
||||
span(aria-hidden="true") ×
|
||||
if state === 'unknown_error'
|
||||
#error-alert.alert.alert-danger.alert-dismissible
|
||||
button.close(type="button" data-dismiss="alert")
|
||||
span(aria-hidden="true") ×
|
||||
p(data-i18n="loading_error.unknown")
|
||||
p= stateMessage
|
|
@ -106,7 +106,6 @@ block content
|
|||
td= personalSub.card
|
||||
|
||||
else
|
||||
button.start-subscription-button.btn.btn-lg.btn-success(data-i18n="subscribe.subscribe_title") Subscribe
|
||||
if personalSub.free === true
|
||||
div(data-i18n="subscribe.currently_free")
|
||||
else if typeof personalSub.free === 'string'
|
||||
|
@ -117,6 +116,8 @@ block content
|
|||
else
|
||||
span(data-i18n="subscribe.was_free_until")
|
||||
span.spl.spr= moment(new Date(personalSub.free)).format('l')
|
||||
else
|
||||
button.start-subscription-button.btn.btn-lg.btn-success(data-i18n="subscribe.subscribe_title") Subscribe
|
||||
|
||||
//- Sponsored Subscriptions
|
||||
|
||||
|
|
|
@ -80,8 +80,9 @@
|
|||
#parents-info(data-i18n="subscribe.parents")
|
||||
#payment-methods-info(data-i18n="subscribe.payment_methods")
|
||||
|
||||
button.btn.btn-lg.btn-illustrated.purchase-button(data-i18n="subscribe.subscribe_title")
|
||||
button.btn.btn-lg.btn-illustrated.parent-button(data-i18n="subscribe.parent_button")
|
||||
button.btn.btn-lg.btn-illustrated.sale-button(title="#{saleButtonTitle}", data-i18n="subscribe.sale_button")
|
||||
button.btn.btn-lg.btn-illustrated.purchase-button(data-i18n="subscribe.subscribe_title")
|
||||
|
||||
if state === 'declined'
|
||||
#declined-alert.alert.alert-danger.alert-dismissible
|
||||
|
|
69
app/views/account/SubscriptionSaleView.coffee
Normal file
69
app/views/account/SubscriptionSaleView.coffee
Normal file
|
@ -0,0 +1,69 @@
|
|||
RootView = require 'views/core/RootView'
|
||||
template = require 'templates/account/subscription-sale-view'
|
||||
stripeHandler = require 'core/services/stripe'
|
||||
utils = require 'core/utils'
|
||||
|
||||
module.exports = class SubscriptionSaleView extends RootView
|
||||
id: "subscription-sale-view"
|
||||
template: template
|
||||
yearSaleAmount: 7900
|
||||
|
||||
events:
|
||||
'click #pay-button': 'onPayButton'
|
||||
|
||||
subscriptions:
|
||||
'stripe:received-token': 'onStripeReceivedToken'
|
||||
|
||||
constructor: (options) ->
|
||||
super(options)
|
||||
@description = $.i18n.t('subscribe.stripe_description_year_sale')
|
||||
displayAmount = (@yearSaleAmount / 100).toFixed(2)
|
||||
@payButtonText = "#{$.i18n.t('subscribe.sale_view_button')} $#{displayAmount}"
|
||||
|
||||
getRenderData: ->
|
||||
c = super()
|
||||
c.payButtonText = @payButtonText
|
||||
c.state = @state
|
||||
c.stateMessage = @stateMessage
|
||||
c
|
||||
|
||||
onPayButton: ->
|
||||
@state = undefined
|
||||
@stateMessage = undefined
|
||||
@render()
|
||||
|
||||
# Show Stripe handler
|
||||
application.tracker?.trackEvent 'Started sale landing page subscription purchase'
|
||||
@timestampForPurchase = new Date().getTime()
|
||||
stripeHandler.open
|
||||
amount: @yearSaleAmount
|
||||
description: @description
|
||||
bitcoin: true
|
||||
alipay: if me.get('chinaVersion') or (me.get('preferredLanguage') or 'en-US')[...2] is 'zh' then true else 'auto'
|
||||
|
||||
onStripeReceivedToken: (e) ->
|
||||
@state = 'purchasing'
|
||||
@render?()
|
||||
|
||||
# Call year sale API
|
||||
data =
|
||||
stripe:
|
||||
token: e.token.id
|
||||
timestamp: @timestampForPurchase
|
||||
jqxhr = $.post('/db/subscription/-/year_sale', data)
|
||||
jqxhr.done (data, textStatus, jqXHR) =>
|
||||
application.tracker?.trackEvent 'Finished sale landing page subscription purchase', value: @yearSaleAmount
|
||||
me.set 'stripe', data?.stripe if data?.stripe?
|
||||
@state = 'invoice_paid'
|
||||
@stateMessage = undefined
|
||||
@render?()
|
||||
jqxhr.fail (xhr, textStatus, errorThrown) =>
|
||||
console.error 'We got an error subscribing with Stripe from our server:', textStatus, errorThrown
|
||||
application.tracker?.trackEvent 'Failed to finish 1 year subscription purchase', status: textStatus
|
||||
if xhr.status is 402
|
||||
@state = 'declined'
|
||||
@stateMessage = arguments[2]
|
||||
else
|
||||
@state = 'unknown_error'
|
||||
@stateMessage = "#{xhr.status}: #{xhr.responseText}"
|
||||
@render?()
|
|
@ -12,6 +12,7 @@ module.exports = class SubscribeModal extends ModalView
|
|||
product:
|
||||
amount: 999
|
||||
planID: 'basic'
|
||||
yearAmount: 7900
|
||||
|
||||
subscriptions:
|
||||
'stripe:received-token': 'onStripeReceivedToken'
|
||||
|
@ -21,13 +22,16 @@ module.exports = class SubscribeModal extends ModalView
|
|||
'click .popover-content .parent-send': 'onClickParentSendButton'
|
||||
'click .email-parent-complete button': 'onClickParentEmailCompleteButton'
|
||||
'click .purchase-button': 'onClickPurchaseButton'
|
||||
'click .sale-button': 'onClickSaleButton'
|
||||
|
||||
constructor: (options) ->
|
||||
super(options)
|
||||
@state = 'standby'
|
||||
@saleButtonTitle = $.i18n.t('subscribe.sale_button_title')
|
||||
|
||||
getRenderData: ->
|
||||
c = super()
|
||||
c.saleButtonTitle = @saleButtonTitle
|
||||
c.state = @state
|
||||
c.stateMessage = @stateMessage
|
||||
c.price = @product.amount / 100
|
||||
|
@ -137,21 +141,61 @@ module.exports = class SubscribeModal extends ModalView
|
|||
#}
|
||||
|
||||
@purchasedAmount = options.amount
|
||||
stripeHandler.open(options)
|
||||
|
||||
onClickSaleButton: (e) ->
|
||||
@playSound 'menu-button-click'
|
||||
return @openModalView new AuthModal() if me.get('anonymous')
|
||||
application.tracker?.trackEvent 'Started 1 year subscription purchase'
|
||||
options =
|
||||
description: $.i18n.t('subscribe.stripe_description_year_sale')
|
||||
amount: @product.yearAmount
|
||||
alipay: if me.get('chinaVersion') or (me.get('preferredLanguage') or 'en-US')[...2] is 'zh' then true else 'auto'
|
||||
alipayReusable: true
|
||||
@purchasedAmount = options.amount
|
||||
stripeHandler.open(options)
|
||||
|
||||
onStripeReceivedToken: (e) ->
|
||||
@state = 'purchasing'
|
||||
@render()
|
||||
|
||||
stripe = _.clone(me.get('stripe') ? {})
|
||||
stripe.planID = @product.planID
|
||||
stripe.token = e.token.id
|
||||
me.set 'stripe', stripe
|
||||
|
||||
@listenToOnce me, 'sync', @onSubscriptionSuccess
|
||||
@listenToOnce me, 'error', @onSubscriptionError
|
||||
me.patch({headers: {'X-Change-Plan': 'true'}})
|
||||
if @purchasedAmount is @product.amount
|
||||
stripe = _.clone(me.get('stripe') ? {})
|
||||
stripe.planID = @product.planID
|
||||
stripe.token = e.token.id
|
||||
me.set 'stripe', stripe
|
||||
@listenToOnce me, 'sync', @onSubscriptionSuccess
|
||||
@listenToOnce me, 'error', @onSubscriptionError
|
||||
me.patch({headers: {'X-Change-Plan': 'true'}})
|
||||
else if @purchasedAmount is @product.yearAmount
|
||||
# Purchasing a year
|
||||
data =
|
||||
stripe:
|
||||
token: e.token.id
|
||||
timestamp: new Date().getTime()
|
||||
jqxhr = $.post('/db/subscription/-/year_sale', data)
|
||||
jqxhr.done (data, textStatus, jqXHR) =>
|
||||
application.tracker?.trackEvent 'Finished 1 year subscription purchase', value: @purchasedAmount
|
||||
me.set 'stripe', data?.stripe if data?.stripe?
|
||||
Backbone.Mediator.publish 'subscribe-modal:subscribed', {}
|
||||
@playSound 'victory'
|
||||
@hide()
|
||||
jqxhr.fail (xhr, textStatus, errorThrown) =>
|
||||
console.error 'We got an error subscribing with Stripe from our server:', textStatus, errorThrown
|
||||
application.tracker?.trackEvent 'Failed to finish 1 year subscription purchase', status: textStatus, value: @purchasedAmount
|
||||
stripe = me.get('stripe') ? {}
|
||||
delete stripe.token
|
||||
delete stripe.planID
|
||||
if xhr.status is 402
|
||||
@state = 'declined'
|
||||
else
|
||||
@state = 'unknown_error'
|
||||
@stateMessage = "#{xhr.status}: #{xhr.responseText}"
|
||||
@render()
|
||||
else
|
||||
console.error "Unexpected purchase amount received", @purchasedAmount, e
|
||||
@state = 'unknown_error'
|
||||
@stateMessage = "Uknown problem occurred while processing Stripe request"
|
||||
|
||||
onSubscriptionSuccess: ->
|
||||
application.tracker?.trackEvent 'Finished subscription purchase', value: @purchasedAmount
|
||||
|
@ -161,6 +205,7 @@ module.exports = class SubscribeModal extends ModalView
|
|||
|
||||
onSubscriptionError: (user, response, options) ->
|
||||
console.error 'We got an error subscribing with Stripe from our server:', response
|
||||
application.tracker?.trackEvent 'Failed to finish subscription purchase', status: options.xhr?.status, value: @purchasedAmount
|
||||
stripe = me.get('stripe') ? {}
|
||||
delete stripe.token
|
||||
delete stripe.planID
|
||||
|
|
79
server/lib/stripe_utils.coffee
Normal file
79
server/lib/stripe_utils.coffee
Normal file
|
@ -0,0 +1,79 @@
|
|||
log = require 'winston'
|
||||
Payment = require '../payments/Payment'
|
||||
PaymentHandler = require '../payments/payment_handler'
|
||||
|
||||
module.exports =
|
||||
logError: (user, msg) ->
|
||||
log.error "Stripe Utils Error: #{user.get('slug')} (#{user._id}): '#{msg}'"
|
||||
|
||||
createCharge: (user, amount, metadata, done) ->
|
||||
options =
|
||||
amount: amount
|
||||
currency: 'usd'
|
||||
customer: user.get('stripe')?.customerID
|
||||
metadata: metadata
|
||||
receipt_email: user.get('email')
|
||||
statement_descriptor: 'CODECOMBAT.COM'
|
||||
stripe.charges.create options, (err, charge) =>
|
||||
if err
|
||||
@logError(user, "Charge create error: #{JSON.stringify(err)}")
|
||||
return done(err)
|
||||
done(err, charge)
|
||||
|
||||
createPayment: (user, stripeCharge, done) ->
|
||||
payment = new Payment
|
||||
purchaser: user._id
|
||||
recipient: user._id
|
||||
created: new Date().toISOString()
|
||||
service: 'stripe'
|
||||
amount: parseInt(stripeCharge.amount)
|
||||
payment.set 'description', stripeCharge.metadata.description if stripeCharge.metadata.description
|
||||
payment.set 'stripe',
|
||||
customerID: stripeCharge.customer
|
||||
timestamp: parseInt(stripeCharge.metadata.timestamp)
|
||||
chargeID: stripeCharge.id
|
||||
validation = PaymentHandler.validateDocumentInput(payment.toObject())
|
||||
if validation.valid is false
|
||||
@logError(user, 'Invalid stripe payment object.')
|
||||
return done(validation.errors)
|
||||
payment.save (err) =>
|
||||
if err
|
||||
@logError(user, "Payment save error: #{JSON.stringify(err)}")
|
||||
return done(err)
|
||||
done(err, payment)
|
||||
|
||||
getCustomer: (user, token, done) ->
|
||||
# If necessary, creates new Stripe customer and saves to user
|
||||
customerID = user.get('stripe')?.customerID
|
||||
if customerID
|
||||
if token
|
||||
# old customer, new token. Save it.
|
||||
stripe.customers.update customerID, { card: token }, (err, customer) =>
|
||||
if err
|
||||
@logError(user, "Customer update error: #{JSON.stringify(err)}")
|
||||
return done(err)
|
||||
done(err, customer)
|
||||
else
|
||||
stripe.customers.retrieve customerID, (err, customer) =>
|
||||
if err
|
||||
@logError(user, "Customer retrieve error: #{JSON.stringify(err)}")
|
||||
return done(err)
|
||||
done(err, customer)
|
||||
else
|
||||
newCustomer = {
|
||||
card: token
|
||||
email: user.get('email')
|
||||
metadata: { id: user._id + '', slug: user.get('slug') }
|
||||
}
|
||||
stripe.customers.create newCustomer, (err, customer) =>
|
||||
if err
|
||||
@logError(user, "Customer creation error: #{JSON.stringify(err)}")
|
||||
return done(err)
|
||||
stripeInfo = _.cloneDeep(user.get('stripe') ? {})
|
||||
stripeInfo.customerID = customer.id
|
||||
user.set('stripe', stripeInfo)
|
||||
user.save (err) =>
|
||||
if err
|
||||
@logError(user, 'Stripe customer id save db error. '+err)
|
||||
return done(err)
|
||||
done(err, customer)
|
|
@ -12,6 +12,7 @@ Prepaid = require '../prepaids/Prepaid'
|
|||
User = require '../users/User'
|
||||
{findStripeSubscription} = require '../lib/utils'
|
||||
{getSponsoredSubsAmount} = require '../../app/core/utils'
|
||||
StripeUtils = require '../lib/stripe_utils'
|
||||
|
||||
recipientCouponID = 'free'
|
||||
|
||||
|
@ -21,6 +22,9 @@ subscriptions = {
|
|||
gems: 3500
|
||||
amount: 999 # For calculating incremental quantity before sub creation
|
||||
}
|
||||
year_sale: {
|
||||
amount: 7900
|
||||
}
|
||||
}
|
||||
|
||||
class SubscriptionHandler extends Handler
|
||||
|
@ -32,6 +36,7 @@ class SubscriptionHandler extends Handler
|
|||
return @getStripeInvoices(req, res) if args[1] is 'stripe_invoices'
|
||||
return @getStripeSubscriptions(req, res) if args[1] is 'stripe_subscriptions'
|
||||
return @getSubscribers(req, res) if args[1] is 'subscribers'
|
||||
return @purchaseYearSale(req, res) if args[1] is 'year_sale'
|
||||
super(arguments...)
|
||||
|
||||
getStripeEvents: (req, res) ->
|
||||
|
@ -111,6 +116,42 @@ class SubscriptionHandler extends Handler
|
|||
log.debug 'Analytics error:\n' + err
|
||||
@sendSuccess(res, userMap)
|
||||
|
||||
purchaseYearSale: (req, res) ->
|
||||
return @sendForbiddenError(res) unless req.user?
|
||||
return @sendForbiddenError(res) if req.user?.hasSubscription()
|
||||
|
||||
StripeUtils.getCustomer req.user, req.body.stripe?.token, (err, customer) =>
|
||||
if err
|
||||
@logSubscriptionError(req.user, "Purchase year sale get customer: #{JSON.stringify(err)}")
|
||||
return @sendDatabaseError(res, err)
|
||||
metadata =
|
||||
type: req.body.type
|
||||
userID: req.user._id + ''
|
||||
timestamp: parseInt(req.body.stripe?.timestamp)
|
||||
description: req.body.description
|
||||
|
||||
StripeUtils.createCharge req.user, subscriptions.year_sale.amount, metadata, (err, charge) =>
|
||||
if err
|
||||
@logSubscriptionError(req.user, "Purchase year sale create charge: #{JSON.stringify(err)}")
|
||||
return @sendDatabaseError(res, err)
|
||||
|
||||
StripeUtils.createPayment req.user, charge, (err, payment) =>
|
||||
if err
|
||||
@logSubscriptionError(req.user, "Purchase year sale create payment: #{JSON.stringify(err)}")
|
||||
return @sendDatabaseError(res, err)
|
||||
|
||||
# Add terminal subscription to User
|
||||
endDate = new Date()
|
||||
endDate.setUTCFullYear(endDate.getUTCFullYear() + 1)
|
||||
stripeInfo = _.cloneDeep(req.user.get('stripe') ? {})
|
||||
stripeInfo.free = endDate.toISOString().substring(0, 10)
|
||||
req.user.set('stripe', stripeInfo)
|
||||
req.user.save (err, user) =>
|
||||
if err
|
||||
@logSubscriptionError(req.user, "User save error: #{JSON.stringify(err)}")
|
||||
return @sendDatabaseError(res, err)
|
||||
@sendSuccess(res, user)
|
||||
|
||||
subscribeUser: (req, user, done) ->
|
||||
if (not req.user) or req.user.isAnonymous() or user.isAnonymous()
|
||||
return done({res: 'You must be signed in to subscribe.', code: 403})
|
||||
|
@ -234,7 +275,7 @@ class SubscriptionHandler extends Handler
|
|||
options.coupon = couponID if couponID
|
||||
stripe.customers.createSubscription customer.id, options, (err, subscription) =>
|
||||
if err
|
||||
@logSubscriptionError(user, 'Stripe customer plan setting error. ' + err)
|
||||
@logSubscriptionError(user, 'Stripe customer plan resetting error. ' + err)
|
||||
return done({res: 'Database error.', code: 500})
|
||||
@updateUser(req, user, customer, subscription, false, done)
|
||||
|
||||
|
|
|
@ -217,14 +217,17 @@ UserSchema.methods.register = (done) ->
|
|||
delighted.addDelightedUser @
|
||||
@saveActiveUser 'register'
|
||||
|
||||
UserSchema.methods.isPremium = ->
|
||||
return true if @isInGodMode()
|
||||
return true if @isAdmin()
|
||||
UserSchema.methods.hasSubscription = ->
|
||||
return false unless stripeObject = @get('stripe')
|
||||
return true if stripeObject.sponsorID
|
||||
return true if stripeObject.subscriptionID
|
||||
return true if stripeObject.free is true
|
||||
return true if _.isString(stripeObject.free) and new Date() < new Date(stripeObject.free)
|
||||
|
||||
UserSchema.methods.isPremium = ->
|
||||
return true if @isInGodMode()
|
||||
return true if @isAdmin()
|
||||
return true if @hasSubscription()
|
||||
return false
|
||||
|
||||
UserSchema.methods.level = ->
|
||||
|
|
|
@ -375,6 +375,8 @@ describe 'Subscriptions', ->
|
|||
expect(err).toBeNull()
|
||||
return done() if err
|
||||
expect(res.statusCode).toBe(200)
|
||||
expect(body.stripe).toBeDefined()
|
||||
return done() unless body.stripe
|
||||
expect(body.stripe.customerID).toBeDefined()
|
||||
expect(body.stripe.planID).toBe('basic')
|
||||
expect(body.stripe.token).toBeUndefined()
|
||||
|
@ -1366,3 +1368,33 @@ describe 'Subscriptions', ->
|
|||
else
|
||||
expect(subscription.quantity).toEqual(subPrice + 10 * subPrice * 0.8 + (numSponsored - 11) * subPrice * 0.6)
|
||||
done()
|
||||
|
||||
describe 'APIs', ->
|
||||
subscriptionURL = getURL('/db/subscription')
|
||||
|
||||
it 'year_sale', (done) ->
|
||||
stripe.tokens.create {
|
||||
card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
|
||||
}, (err, token) ->
|
||||
loginNewUser (user1) ->
|
||||
expect(user1.get('stripe')?.free).toBeUndefined()
|
||||
requestBody =
|
||||
stripe:
|
||||
token: token.id
|
||||
timestamp: new Date()
|
||||
request.put {uri: "#{subscriptionURL}/-/year_sale", json: requestBody, headers: headers }, (err, res) ->
|
||||
expect(err).toBeNull()
|
||||
expect(res.statusCode).toBe(200)
|
||||
User.findById user1.id, (err, user1) ->
|
||||
expect(err).toBeNull()
|
||||
stripeInfo = user1.get('stripe')
|
||||
expect(stripeInfo).toBeDefined()
|
||||
return done() unless stripeInfo
|
||||
endDate = new Date()
|
||||
endDate.setUTCFullYear(endDate.getUTCFullYear() + 1)
|
||||
expect(stripeInfo.free).toEqual(endDate.toISOString().substring(0, 10))
|
||||
expect(stripeInfo.customerID).toBeDefined()
|
||||
Payment.findOne 'stripe.customerID': stripeInfo.customerID, (err, payment) ->
|
||||
expect(err).toBeNull()
|
||||
expect(payment).toBeDefined()
|
||||
done()
|
||||
|
|
Loading…
Reference in a new issue