mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-11-24 08:08:15 -05:00
Merge branch 'master' into china
This commit is contained in:
commit
d8c21771fc
27 changed files with 977 additions and 428 deletions
|
@ -55,7 +55,7 @@ module.exports = class LevelSetupManager extends CocoClass
|
|||
loadModals: ->
|
||||
# build modals and prevent them from disappearing.
|
||||
if @level.get('slug') is 'zero-sum'
|
||||
@session.set 'heroConfig', {"thangType":"52fd1524c7e6cf99160e7bc9","inventory":{"misc-0":"53e2396a53457600003e3f0f","programming-book":"546e266e9df4a17d0d449be5","minion":"54eb5dbc49fa2d5c905ddf56","feet":"53e214f153457600003e3eab","right-hand":"544d88478494308424f56505","left-hand":"546375a43839c6e02811d311","wrists":"54693797a2b1f53ce79443e9","gloves":"5469425ca2b1f53ce7944421","torso":"546d4a549df4a17d0d449a97","neck":"54693274a2b1f53ce79443c9","eyes":"546941fda2b1f53ce794441d","head":"546d4ca19df4a17d0d449abf"}}
|
||||
@session.set 'heroConfig', {"thangType":"52fd1524c7e6cf99160e7bc9","inventory":{"misc-0":"53e2396a53457600003e3f0f","programming-book":"546e266e9df4a17d0d449be5","minion":"54eb5dbc49fa2d5c905ddf56","feet":"53e214f153457600003e3eab","right-hand":"54eab7f52b7506e891ca7202","left-hand":"5463758f3839c6e02811d30f","wrists":"54693797a2b1f53ce79443e9","gloves":"5469425ca2b1f53ce7944421","torso":"546d4a549df4a17d0d449a97","neck":"54693274a2b1f53ce79443c9","eyes":"546941fda2b1f53ce794441d","head":"546d4ca19df4a17d0d449abf"}}
|
||||
@onInventoryModalPlayClicked()
|
||||
return
|
||||
@heroesModal = new PlayHeroesModal({supermodel: @supermodel, session: @session, confirmButtonI18N: 'play.next', level: @level, hadEverChosenHero: @options.hadEverChosenHero})
|
||||
|
|
|
@ -442,7 +442,7 @@
|
|||
managed_subs: "Managed Subscriptions"
|
||||
managed_subs_desc: "Add subscriptions for other players (students, children, etc.)"
|
||||
group_discounts: "Group discounts"
|
||||
group_discounts_1st: "1st subscription (includes yours)"
|
||||
group_discounts_1st: "1st subscription"
|
||||
group_discounts_full: "Full price"
|
||||
group_discounts_2nd: "Subscriptions 2-11"
|
||||
group_discounts_20: "20% off"
|
||||
|
@ -455,6 +455,8 @@
|
|||
no_users_subscribed: "No users subscribed, please double check your email addresses."
|
||||
current_recipients: "Current Recipients"
|
||||
unsubscribing: "Unsubscribing..."
|
||||
subscribe_prepaid: "Click Subscribe to use prepaid code"
|
||||
using_prepaid: "Using prepaid code for monthly subscription"
|
||||
|
||||
choose_hero:
|
||||
choose_hero: "Choose Your Hero"
|
||||
|
@ -575,48 +577,37 @@
|
|||
retrostyle_blurb: "RetroStyle Games"
|
||||
|
||||
teachers:
|
||||
title: "CodeCombat for Teachers"
|
||||
preparation_title: "Preparation"
|
||||
preparation_1: "CodeCombat is free to play for the core level progression and does not require students to sign up. We encourage teachers to"
|
||||
preparation_play_campaign: "play through the campaign"
|
||||
preparation_2: "to try it out, but the only thing you absolutely need to do to be ready is ensure students have access to a computer."
|
||||
preparation_3: "It is not necessary for teachers to be comfortable with computer science concepts for students to have fun learning with CodeCombat."
|
||||
violent_title: "Is it violent?"
|
||||
violent_1: "We get this from teachers a lot due to our name. Although CodeCombat does contain cartoon violence, there is nothing graphic in either the visuals or language."
|
||||
violent_2: "If you are comfortable having your students play Angry Birds, you will be comfortable with CodeCombat."
|
||||
for_girls_title: "Is it for girls?"
|
||||
for_girls_1: "There are three game modes in CodeCombat: building, puzzles, and combat. We have intentionally designed each to appeal to both boys and girls and think that the building and puzzle levels especially differentiate the game from violent triple A titles that repel female players."
|
||||
age_recommended_title: "What age is it recommended at?"
|
||||
age_recommended_1: "The recommended minimum age is 9, however children as young as 7 can play the first stages with the assistance of an adult."
|
||||
what_cover_title: "What do we cover?"
|
||||
what_cover_1: "There are 27 levels in the first stage of CodeCombat that teach and reinforce 6 specific computer science concepts:"
|
||||
what_cover_notation_1: "Formal notation"
|
||||
what_cover_notation_2: "- builds an understanding of the importance of syntax in programming."
|
||||
what_cover_methods_1: "Calling methods"
|
||||
what_cover_methods_2: "- familiarizes students with the syntax of object-oriented method calls."
|
||||
what_cover_parameters_1: "Parameters"
|
||||
what_cover_parameters_2: "- trains how to pass parameters to functions."
|
||||
what_cover_strings_1: "Strings"
|
||||
what_cover_strings_2: "- teaches students about string notation and passing strings as parameters."
|
||||
what_cover_loops_1: "Loops"
|
||||
what_cover_loops_2: "- develops the abstraction of designing short programs with loops."
|
||||
what_cover_variables_1: "Variables"
|
||||
what_cover_variables_2: "- adds the skill of referencing values that change over time."
|
||||
what_cover_2: "Students may continue past level 20, depending on their speed and interest, to learn two additional concepts in later levels:"
|
||||
what_cover_logic_1: "Conditional logic"
|
||||
what_cover_logic_2: "- when and how to use if/else to control in-game outcomes."
|
||||
what_cover_input_1: "Handling player input"
|
||||
what_cover_input_2: "- responding to input events to create a user interface."
|
||||
what_cover_array_1: "Arrays"
|
||||
what_cover_array_2: "- allows the player to create and loop through arrays."
|
||||
title: "CodeCombat: Info for Teachers"
|
||||
intro_1: "CodeCombat is an online game that teaches programming. Students write code in real programming languages."
|
||||
intro_2: "No experience required!"
|
||||
free_title: "How much does it cost?"
|
||||
free_1: "CodeCombat Basic is FREE! There are 70+ free levels which cover every concept."
|
||||
free_2: "A monthly subscription provides access to video tutorials and extra practice levels."
|
||||
teacher_subs_title: "Teachers get free subscriptions!"
|
||||
teacher_subs_1: "Please contact"
|
||||
teacher_subs_2: "to setup a free monthly subscription."
|
||||
sub_includes_title: "What is included in the subscription?"
|
||||
sub_includes_1: "In additional to the 70+ free levels, students with a monthly subscription get access to these additional features:"
|
||||
sub_includes_2: "40+ practice levels"
|
||||
sub_includes_3: "Video tutorials"
|
||||
sub_includes_4: "Premium email support"
|
||||
sub_includes_5: "7 new heroes with unique skills to master"
|
||||
sub_includes_6: "3500 bonus gems every month"
|
||||
who_for_title: "Who is CodeCombat for?"
|
||||
who_for_1: "We recommend CodeCombat for students aged 9 and up. No prior programming experience is needed."
|
||||
who_for_2: "We've designed CodeCombat to appeal to both boys and girls."
|
||||
material_title: "How much material is there?"
|
||||
material_1: "Approximately 8 hours of free content, and an additional 14 hours of subscriber content."
|
||||
concepts_title: "What concepts are covered?"
|
||||
how_much_title: "How much does a monthly subscription cost?"
|
||||
how_much_1: "A"
|
||||
how_much_2: "monthly subscription"
|
||||
how_much_3: "costs $9.99, and can be cancelled anytime."
|
||||
how_much_4: "Additionally, we provide discounts for larger groups:"
|
||||
group_discounts_1: "We also offer group discounts for bulk subscriptions."
|
||||
sys_requirements_title: "System Requirements"
|
||||
sys_requirements_1: "Because CodeCombat is a game, it is more intensive for computers to run smoothly than video or written tutorials. We have optimized it to run quickly on all modern browsers and on older machines so that everyone can play. That said, here are our suggestions for getting the most out of the CodeCombat experience:"
|
||||
sys_requirements_2: "Use newer versions of Chrome or Firefox."
|
||||
sys_requirements_3: "Although CodeCombat will work on browsers as old as Internet Explorer 9, the performance is not as good. Chrome is best."
|
||||
sys_requirements_4: "Use newer computers."
|
||||
sys_requirements_5: "Older computers, Chromebooks, and netbooks tend to have very few system resources, which makes for a less enjoyable experience. At least 2GB of RAM is required."
|
||||
sys_requirements_6: "Allow players to wear headphones/earbuds to hear the audio."
|
||||
sys_requirements_7: "We help players learn through voiceover and sound effects, which will make classrooms noisy and distracting."
|
||||
sys_requirements_1: "A modern web browser. Newer versions of Chrome, Firefox, or Safari. Internet Explorer 9 or later."
|
||||
sys_requirements_2: "CodeCombat is not supported on iPad yet."
|
||||
|
||||
versions:
|
||||
save_version_title: "Save New Version"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
module.exports = nativeDescription: "Esperanto", englishDescription: "Esperanto", translation:
|
||||
# home:
|
||||
# slogan: "Learn to Code by Playing a Game"
|
||||
home:
|
||||
slogan: "Lernu programi per ludo"
|
||||
# no_ie: "CodeCombat does not run in Internet Explorer 8 or older. Sorry!" # Warning that only shows up in IE8 and older
|
||||
# no_mobile: "CodeCombat wasn't designed for mobile devices and may not work!" # Warning that shows up on mobile devices
|
||||
# play: "Play" # The big play button that opens up the campaign view.
|
||||
|
@ -13,39 +13,39 @@ module.exports = nativeDescription: "Esperanto", englishDescription: "Esperanto"
|
|||
# for_developers: "For Developers" # Not currently shown on home page.
|
||||
# or_ipad: "Or download for iPad"
|
||||
|
||||
# nav:
|
||||
# play: "Levels" # The top nav bar entry where players choose which levels to play
|
||||
# community: "Community"
|
||||
# editor: "Editor"
|
||||
# blog: "Blog"
|
||||
# forum: "Forum"
|
||||
# account: "Account"
|
||||
# profile: "Profile"
|
||||
# stats: "Stats"
|
||||
# code: "Code"
|
||||
# admin: "Admin" # Only shows up when you are an admin
|
||||
# home: "Home"
|
||||
# contribute: "Contribute"
|
||||
# legal: "Legal"
|
||||
# about: "About"
|
||||
# contact: "Contact"
|
||||
# twitter_follow: "Follow"
|
||||
# teachers: "Teachers"
|
||||
nav:
|
||||
play: "Niveloj" # The top nav bar entry where players choose which levels to play
|
||||
community: "Komunumo"
|
||||
editor: "Redaktoro"
|
||||
blog: "Blogo"
|
||||
forum: "Forumo"
|
||||
account: "Konto"
|
||||
profile: "Profilo"
|
||||
stats: "Statistiko"
|
||||
code: "Kodo"
|
||||
admin: "Administrado" # Only shows up when you are an admin
|
||||
home: "Hejmo"
|
||||
contribute: "Kontribui"
|
||||
legal: "Leĝa informo"
|
||||
about: "Pri"
|
||||
contact: "Kontakti"
|
||||
twitter_follow: "Sekvu"
|
||||
teachers: "Instruistoj"
|
||||
|
||||
# modal:
|
||||
# close: "Close"
|
||||
# okay: "Okay"
|
||||
modal:
|
||||
close: "Fermi"
|
||||
okay: "Bone"
|
||||
|
||||
# not_found:
|
||||
# page_not_found: "Page not found"
|
||||
not_found:
|
||||
page_not_found: "Paĝo ne trovita"
|
||||
|
||||
diplomat_suggestion:
|
||||
# title: "Help translate CodeCombat!" # This shows up when a player switches to a non-English language using the language selector.
|
||||
# sub_heading: "We need your language skills."
|
||||
title: "Helpu traduki CodeCombat!" # This shows up when a player switches to a non-English language using the language selector.
|
||||
sub_heading: "Ni bezonas vian lingvokapabloj."
|
||||
pitch_body: "We develop CodeCombat in English, but we already have players all over the world. There's got to be at least one of them who wants to play in Esperanto! So if you can speak both, please consider signing up to be a Diplomat and help translate both the CodeCombat website and all the levels into Esperanto."
|
||||
missing_translations: "Until we can translate everything into Esperanto, you'll see English when Esperanto isn't available."
|
||||
# learn_more: "Learn more about being a Diplomat"
|
||||
# subscribe_as_diplomat: "Subscribe as a Diplomat"
|
||||
learn_more: "Eksciu pli pri esti Diplomato"
|
||||
subscribe_as_diplomat: "Aboni kiel Diplomato"
|
||||
|
||||
# play:
|
||||
# play_as: "Play As" # Ladder page
|
||||
|
|
14
app/schemas/models/prepaid.schema.coffee
Normal file
14
app/schemas/models/prepaid.schema.coffee
Normal file
|
@ -0,0 +1,14 @@
|
|||
c = require './../schemas'
|
||||
|
||||
PrepaidSchema = c.object({title: 'Prepaid', required: ['creator', 'redeemer', 'type']}, {
|
||||
creator: c.objectId(links: [ {rel: 'extra', href: '/db/user/{($)}'} ])
|
||||
redeemer: c.objectId(links: [ {rel: 'extra', href: '/db/user/{($)}'} ])
|
||||
code: c.shortString(title: "Unique code to redeem")
|
||||
type: { type: 'string' }
|
||||
status: { enum: ['active', 'used'], default: 'active' }
|
||||
properties: {type: 'object'}
|
||||
})
|
||||
|
||||
c.extendBasicProperties(PrepaidSchema, 'prepaid')
|
||||
|
||||
module.exports = PrepaidSchema
|
|
@ -288,6 +288,7 @@ _.extend UserSchema.properties,
|
|||
token: { type: 'string' }
|
||||
couponID: { type: 'string' }
|
||||
free: { type: ['boolean', 'string'], format: 'date-time', description: 'Type string is subscription end date' }
|
||||
prepaidCode: c.shortString description: 'Prepaid code to apply to sub purchase'
|
||||
|
||||
# Sponsored subscriptions
|
||||
subscribeEmails: c.array { description: 'Input for subscribing other users' }, c.shortString()
|
||||
|
|
|
@ -1,2 +1,5 @@
|
|||
#admin-view
|
||||
color: black
|
||||
color: black
|
||||
|
||||
#free-sub-input
|
||||
min-width: 50%
|
||||
|
|
|
@ -1,5 +1,14 @@
|
|||
#teachers-view
|
||||
|
||||
.main-content-area
|
||||
width: 650px
|
||||
box-shadow: 0px 0px 0px
|
||||
.main-content-area
|
||||
width: 650px
|
||||
box-shadow: 0px 0px 0px
|
||||
|
||||
table
|
||||
background-color: #F9F1DD
|
||||
|
||||
.discount-table
|
||||
width: 50%
|
||||
|
||||
.teachers-title
|
||||
color: green
|
||||
|
|
|
@ -19,9 +19,15 @@ block content
|
|||
.panel.panel-default
|
||||
.panel-heading
|
||||
h3(data-i18n="subscribe.personal_sub")
|
||||
if personalSub.prepaidCode && !personalSub.usingPrepaidCode
|
||||
div
|
||||
span(data-i18n="subscribe.subscribe_prepaid")
|
||||
span.spl.spr= personalSub.prepaidCode
|
||||
.panel-body
|
||||
if personalSub.state === 'loading'
|
||||
.alert.alert-info(data-i18n="subscribe.loading_info")
|
||||
else if personalSub.state === 'subscribing'
|
||||
.alert.alert-info(data-i18n="subscribe.subscribing")
|
||||
else if personalSub.sponsor
|
||||
div
|
||||
span(data-i18n="subscribe.managed_by")
|
||||
|
@ -30,11 +36,30 @@ block content
|
|||
div
|
||||
span(data-i18n="subscribe.will_be_cancelled")
|
||||
span.spl.spr= moment(personalSub.endDate).format('l')
|
||||
|
||||
else if personalSub.usingPrepaidCode
|
||||
div(data-i18n="subscribe.using_prepaid")
|
||||
|
||||
else if personalSub.self
|
||||
if personalSub.subscribed
|
||||
button.end-subscription-button.btn.btn-lg.btn-warning(data-i18n="subscribe.unsubscribe") Unsubscribe
|
||||
else
|
||||
if personalSub.state === 'declined'
|
||||
.alert.alert-danger.alert-dismissible
|
||||
span(data-i18n="buy_gems.declined")
|
||||
button.close(type="button" data-dismiss="alert")
|
||||
span(aria-hidden="true") ×
|
||||
br
|
||||
else if personalSub.state === 'unknown_error'
|
||||
.alert.alert-danger.alert-dismissible
|
||||
button.close(type="button" data-dismiss="alert")
|
||||
span(aria-hidden="true") ×
|
||||
p(data-i18n="loading_error.unknown")
|
||||
p= personalSub.stateMessage
|
||||
br
|
||||
|
||||
if !personalSub.subscribed || personalSub.prepaidCode
|
||||
button.start-subscription-button.btn.btn-lg.btn-success(data-i18n="subscribe.subscribe_title") Subscribe
|
||||
else
|
||||
button.end-subscription-button.btn.btn-lg.btn-warning(data-i18n="subscribe.unsubscribe") Unsubscribe
|
||||
|
||||
.unsubscribe-feedback.row.secret
|
||||
.col-lg-7
|
||||
h3
|
||||
|
@ -75,10 +100,10 @@ block content
|
|||
tr
|
||||
th(data-i18n="account.cost")
|
||||
td= personalSub.cost
|
||||
if personalSub.card
|
||||
tr
|
||||
th(data-i18n="account.card")
|
||||
td= personalSub.card
|
||||
if personalSub.card
|
||||
tr
|
||||
th(data-i18n="account.card")
|
||||
td= personalSub.card
|
||||
|
||||
else
|
||||
button.start-subscription-button.btn.btn-lg.btn-success(data-i18n="subscribe.subscribe_title") Subscribe
|
||||
|
@ -129,7 +154,7 @@ block content
|
|||
button.close(type="button" data-dismiss="alert")
|
||||
span(aria-hidden="true") ×
|
||||
p(data-i18n="loading_error.unknown")
|
||||
p= stateMessage
|
||||
p= recipientSubs.stateMessage
|
||||
else if recipientSubs.justSubscribed && recipientSubs.justSubscribed.length > 0
|
||||
br
|
||||
.alert.alert-success.alert-dismissible
|
||||
|
|
|
@ -47,6 +47,14 @@ block content
|
|||
li
|
||||
a(href="/admin/growth", data-i18n="admin.growth") Growth
|
||||
|
||||
if me.isAdmin()
|
||||
hr
|
||||
h3 Prepaids
|
||||
a.btn.btn-secondary#create-free-sub-btn Create Free Subscription Link
|
||||
span.spl.spr
|
||||
if freeSubLink
|
||||
input#free-sub-input(type="text", readonly, value="#{freeSubLink}")
|
||||
|
||||
hr
|
||||
|
||||
h3 Achievements
|
||||
|
|
|
@ -6,125 +6,105 @@ block content
|
|||
|
||||
.span5
|
||||
|
||||
h2(data-i18n="teachers.title") CodeCombat for Teachers
|
||||
h2(data-i18n="teachers.title")
|
||||
p(data-i18n="teachers.intro_1")
|
||||
p(data-i18n="teachers.intro_2")
|
||||
|
||||
h3(data-i18n="teachers.preparation_title") Preparation
|
||||
h3(data-i18n="teachers.free_title")
|
||||
p(data-i18n="teachers.free_1")
|
||||
p(data-i18n="teachers.free_2")
|
||||
|
||||
p
|
||||
span.spr(data-i18n="teachers.preparation_1")
|
||||
| CodeCombat is free to play for the core level progression
|
||||
| and does not require students to sign up. We encourage teachers to
|
||||
a(href="/play", data-i18n="teachers.preparation_play_campaign") play through the campaign
|
||||
span.spl(data-i18n="teachers.preparation_2")
|
||||
| to try it out, but the only thing you absolutely need to do
|
||||
| to be ready is ensure students have access to a computer.
|
||||
|
||||
p(data-i18n="teachers.preparation_3")
|
||||
| It is not necessary for teachers to be comfortable with computer
|
||||
| science concepts for students to have fun learning with CodeCombat.
|
||||
|
||||
h3(data-i18n="teachers.violent_title")
|
||||
| Is it violent?
|
||||
|
||||
p
|
||||
span.spr(data-i18n="teachers.violent_1")
|
||||
| We get this from teachers a lot due to our name. Although CodeCombat
|
||||
| does contain cartoon violence, there is nothing graphic in either the
|
||||
| visuals or language.
|
||||
span(data-i18n="teachers.violent_2")
|
||||
| If you are comfortable having your students play Angry Birds, you will
|
||||
| be comfortable with CodeCombat.
|
||||
|
||||
h3(data-i18n="teachers.for_girls_title")
|
||||
| Is it for girls?
|
||||
|
||||
p(data-i18n="teachers.for_girls_1")
|
||||
| There are three game modes in CodeCombat: building, puzzles, and combat.
|
||||
| We have intentionally designed each to appeal to both boys and girls and
|
||||
| think that the building and puzzle levels especially differentiate the game
|
||||
| from violent triple A titles that repel female players.
|
||||
|
||||
h3(data-i18n="teachers.age_recommended_title")
|
||||
| What age is it recommended at?
|
||||
|
||||
p(data-i18n="teachers.age_recommended_1")
|
||||
| The recommended minimum age is 9, however children as young as 7 can play the
|
||||
| first stages with the assistance of an adult.
|
||||
|
||||
h3(data-i18n="teachers.what_cover_title")
|
||||
| What do we cover?
|
||||
|
||||
p(data-i18n="teachers.what_cover_1")
|
||||
| There are 27 levels in the first stage of CodeCombat that teach and
|
||||
| reinforce 6 specific computer science concepts:
|
||||
|
||||
ol
|
||||
li
|
||||
strong.spr(data-i18n="teachers.what_cover_notation_1") Formal notation
|
||||
span(data-i18n="teachers.what_cover_notation_2")
|
||||
| - builds an understanding of the importance of syntax in programming.
|
||||
li
|
||||
strong.spr(data-i18n="teachers.what_cover_methods_1") Calling methods
|
||||
span(data-i18n="teachers.what_cover_methods_2")
|
||||
| - familiarizes students with the syntax of object-oriented method calls.
|
||||
li
|
||||
strong.spr(data-i18n="teachers.what_cover_parameters_1") Parameters
|
||||
span(data-i18n="teachers.what_cover_parameters_2")
|
||||
| - trains how to pass parameters to functions.
|
||||
li
|
||||
strong.spr(data-i18n="teachers.what_cover_strings_1") Strings
|
||||
span(data-i18n="teachers.what_cover_strings_2")
|
||||
| - teaches students about string notation and passing strings as parameters.
|
||||
li
|
||||
strong.spr(data-i18n="teachers.what_cover_loops_1") Loops
|
||||
span(data-i18n="teachers.what_cover_loops_2")
|
||||
| - develops the abstraction of designing short programs with loops.
|
||||
li
|
||||
strong.spr(data-i18n="teachers.what_cover_variables_1") Variables
|
||||
span(data-i18n="teachers.what_cover_variables_2")
|
||||
| - adds the skill of referencing values that change over time.
|
||||
|
||||
p(data-i18n="teachers.what_cover_2")
|
||||
| Students may continue past level 20, depending on their
|
||||
| speed and interest, to learn two additional concepts in later levels:
|
||||
ol
|
||||
li
|
||||
strong.spr(data-i18n="teachers.what_cover_logic_1") Conditional logic
|
||||
span(data-i18n="teachers.what_cover_logic_2")
|
||||
| - when and how to use if/else to control in-game outcomes.
|
||||
li
|
||||
strong.spr(data-i18n="teachers.what_cover_input_1") Handling player input
|
||||
span(data-i18n="teachers.what_cover_input_2")
|
||||
| - responding to input events to create a user interface.
|
||||
li
|
||||
strong.spr(data-i18n="teachers.what_cover_array_1") Arrays
|
||||
span(data-i18n="teachers.what_cover_array_2")
|
||||
| - allows the player to create and loop through arrays.
|
||||
|
||||
|
||||
h3(data-i18n="teachers.sys_requirements_title") System Requirements
|
||||
|
||||
p(data-i18n="teachers.sys_requirements_1")
|
||||
| Because CodeCombat is a game, it is more intensive for computers
|
||||
| to run smoothly than video or written tutorials. We have optimized
|
||||
| it to run quickly on all modern browsers and on older machines so
|
||||
| that everyone can play. That said, here are our suggestions for getting
|
||||
| the most out of the CodeCombat experience:
|
||||
h3.teachers-title(data-i18n="teachers.teacher_subs_title")
|
||||
p
|
||||
span(data-i18n="teachers.teacher_subs_1")
|
||||
span.spr.spl
|
||||
a(href='mailto:team@codecombat.com?subject=Free%20Teacher%20Subscription') team@codecombat.com
|
||||
span.spr.spl(data-i18n="teachers.teacher_subs_2")
|
||||
|
||||
h3(data-i18n="teachers.sub_includes_title")
|
||||
p(data-i18n="teachers.sub_includes_1")
|
||||
ul
|
||||
li
|
||||
strong.spr(data-i18n="teachers.sys_requirements_2") Use newer versions of Chrome or Firefox.
|
||||
span(data-i18n="teachers.sys_requirements_3")
|
||||
| Although CodeCombat will work on browsers as old as Internet Explorer 9, the
|
||||
| performance is not as good. Chrome is best.
|
||||
li
|
||||
strong.spr(data-i18n="teachers.sys_requirements_4") Use newer computers.
|
||||
span(data-i18n="teachers.sys_requirements_5")
|
||||
| Older computers, Chromebooks, and netbooks tend to have very few
|
||||
| system resources, which makes for a less enjoyable experience.
|
||||
| At least 2GB of RAM is required.
|
||||
li
|
||||
strong.spr(data-i18n="teachers.sys_requirements_6") Allow players to wear headphones/earbuds to hear the audio.
|
||||
span(data-i18n="teachers.sys_requirements_7")
|
||||
| We help players learn through voiceover and sound effects, which
|
||||
| will make classrooms noisy and distracting.
|
||||
li(data-i18n="teachers.sub_includes_2")
|
||||
li(data-i18n="teachers.sub_includes_3")
|
||||
li(data-i18n="teachers.sub_includes_4")
|
||||
li(data-i18n="teachers.sub_includes_5")
|
||||
li(data-i18n="teachers.sub_includes_6")
|
||||
|
||||
h3(data-i18n="teachers.who_for_title")
|
||||
p(data-i18n="teachers.who_for_1")
|
||||
p(data-i18n="teachers.who_for_2")
|
||||
|
||||
h3(data-i18n="teachers.material_title")
|
||||
p(data-i18n="teachers.material_1")
|
||||
|
||||
h3(data-i18n="teachers.concepts_title")
|
||||
|
||||
//- TODO: i18n for concepts?
|
||||
|
||||
table.table.table-condensed.concepts-table
|
||||
thead
|
||||
tr
|
||||
th
|
||||
a(href='/play/dungeon') Kithgard Dungeon
|
||||
th
|
||||
a(href='/play/forest') Backwoods Forest
|
||||
th
|
||||
a(href='/play/desert') Sarven Desert
|
||||
th
|
||||
a(href='/play/mountain') Cloudrip Mountain
|
||||
tbody
|
||||
tr
|
||||
td Syntax
|
||||
td If/Else
|
||||
td Arithmetic
|
||||
td Object Literals
|
||||
tr
|
||||
td Methods
|
||||
td Relational Operators
|
||||
td While-loops
|
||||
td Remote Method Invocation
|
||||
tr
|
||||
td Parameters
|
||||
td Object Properties
|
||||
td Break
|
||||
td For-Loops
|
||||
tr
|
||||
td Strings
|
||||
td Input Handling
|
||||
td Arrays
|
||||
td Functions
|
||||
tr
|
||||
td Loops
|
||||
td
|
||||
td String Comparison
|
||||
td Drawing
|
||||
tr
|
||||
td Variables
|
||||
td
|
||||
td Finding Min/Max
|
||||
td Modulo
|
||||
|
||||
h3(data-i18n="teachers.how_much_title")
|
||||
p
|
||||
span(data-i18n="teachers.how_much_1")
|
||||
span.spr.spl
|
||||
a(href='/account/subscription', data-i18n="teachers.how_much_2")
|
||||
span.spr.spl(data-i18n="teachers.how_much_3")
|
||||
p(data-i18n="teachers.how_much_4")
|
||||
h4
|
||||
a(href='/account/subscription', data-i18n="subscribe.group_discounts")
|
||||
p(data-i18n="subscribe.group_discounts_1")
|
||||
table.table.table-condensed.discount-table
|
||||
tr
|
||||
td(data-i18n="subscribe.group_discounts_1st")
|
||||
td(data-i18n="subscribe.group_discounts_full")
|
||||
tr
|
||||
td(data-i18n="subscribe.group_discounts_2nd")
|
||||
td(data-i18n="subscribe.group_discounts_20")
|
||||
tr
|
||||
td(data-i18n="subscribe.group_discounts_12th")
|
||||
td(data-i18n="subscribe.group_discounts_40")
|
||||
|
||||
h3(data-i18n="teachers.sys_requirements_title")
|
||||
p(data-i18n="teachers.sys_requirements_1")
|
||||
p(data-i18n="teachers.sys_requirements_2")
|
||||
|
|
|
@ -18,9 +18,14 @@ utils = require 'core/utils'
|
|||
# TODO: next payment amount incorrect if have an expiring personal sub
|
||||
# TODO: consider hiding managed subscription body UI while things are updating to avoid brief legacy data
|
||||
# TODO: Next payment info for personal sub displays most recent payment when resubscribing before trial end
|
||||
# TODO: PersonalSub and RecipientSubs have similar subscribe APIs
|
||||
# TODO: Better recovery from trying to reuse a prepaid
|
||||
# TODO: No way to unsubscribe from prepaid subscription
|
||||
# TODO: Refactor state machines driving the UI. They've become a hot mess.
|
||||
|
||||
# TODO: Get basic plan price dynamically
|
||||
basicPlanPrice = 999
|
||||
basicPlanID = 'basic'
|
||||
|
||||
module.exports = class SubscriptionView extends RootView
|
||||
id: "subscription-view"
|
||||
|
@ -41,7 +46,8 @@ module.exports = class SubscriptionView extends RootView
|
|||
|
||||
constructor: (options) ->
|
||||
super(options)
|
||||
@personalSub = new PersonalSub(@supermodel)
|
||||
prepaidCode = utils.getQueryVariable '_ppc'
|
||||
@personalSub = new PersonalSub(@supermodel, prepaidCode)
|
||||
@recipientSubs = new RecipientSubs(@supermodel)
|
||||
@personalSub.update => @render?()
|
||||
@recipientSubs.update => @render?()
|
||||
|
@ -55,7 +61,10 @@ module.exports = class SubscriptionView extends RootView
|
|||
# Personal Subscriptions
|
||||
|
||||
onClickStartSubscription: (e) ->
|
||||
@openModalView new SubscribeModal()
|
||||
if @personalSub.prepaidCode
|
||||
@personalSub.subscribe(=> @render?())
|
||||
else
|
||||
@openModalView new SubscribeModal()
|
||||
window.tracker?.trackEvent 'Show subscription modal', category: 'Subscription', label: 'account subscription view'
|
||||
|
||||
onSubscribed: ->
|
||||
|
@ -95,7 +104,45 @@ module.exports = class SubscriptionView extends RootView
|
|||
# Helper classes for managing subscription actions and updating UI state
|
||||
|
||||
class PersonalSub
|
||||
constructor: (@supermodel) ->
|
||||
constructor: (@supermodel, @prepaidCode) ->
|
||||
|
||||
subscribe: (render) ->
|
||||
return unless @prepaidCode
|
||||
|
||||
if @prepaidCode is me.get('stripe')?.prepaidCode
|
||||
delete @prepaidCode
|
||||
return render()
|
||||
|
||||
@state = 'subscribing'
|
||||
@stateMessage = ''
|
||||
render()
|
||||
|
||||
stripeInfo = _.clone(me.get('stripe') ? {})
|
||||
stripeInfo.planID = basicPlanID
|
||||
stripeInfo.prepaidCode = @prepaidCode
|
||||
me.set('stripe', stripeInfo)
|
||||
|
||||
me.once 'sync', =>
|
||||
application.tracker?.trackEvent 'Finished subscription purchase', revenue: 0
|
||||
delete @prepaidCode
|
||||
@update(render)
|
||||
me.once 'error', (user, response, options) =>
|
||||
console.error 'We got an error subscribing with Stripe from our server:', response
|
||||
stripeInfo = me.get('stripe') ? {}
|
||||
delete stripeInfo.planID
|
||||
delete stripeInfo.prepaidCode
|
||||
me.set('stripe', stripeInfo)
|
||||
xhr = options.xhr
|
||||
if xhr.status is 402
|
||||
@state = 'declined'
|
||||
@stateMessage = ''
|
||||
else
|
||||
if xhr.status is 403
|
||||
delete @prepaidCode
|
||||
@state = 'unknown_error'
|
||||
@stateMessage = "#{xhr.status}: #{xhr.responseText}"
|
||||
render()
|
||||
me.patch({headers: {'X-Change-Plan': 'true'}})
|
||||
|
||||
unsubscribe: (message) ->
|
||||
removeStripe = =>
|
||||
|
@ -133,6 +180,11 @@ class PersonalSub
|
|||
success: onSubSponsorSuccess
|
||||
}, 0).load()
|
||||
|
||||
else if stripeInfo.prepaidCode
|
||||
@usingPrepaidCode = true
|
||||
delete @state
|
||||
render()
|
||||
|
||||
else if stripeInfo.subscriptionID
|
||||
@self = true
|
||||
@active = me.isPremium()
|
||||
|
@ -146,7 +198,7 @@ class PersonalSub
|
|||
periodEnd = new Date((sub.trial_end or sub.current_period_end) * 1000)
|
||||
if sub.cancel_at_period_end
|
||||
@activeUntil = periodEnd
|
||||
else
|
||||
else if sub.discount?.coupon?.id isnt 'free'
|
||||
@nextPaymentDate = periodEnd
|
||||
@cost = "$#{(sub.plan.amount/100).toFixed(2)}"
|
||||
else
|
||||
|
@ -165,6 +217,7 @@ class PersonalSub
|
|||
@free = stripeInfo.free
|
||||
delete @state
|
||||
render()
|
||||
|
||||
else
|
||||
delete @state
|
||||
render()
|
||||
|
|
|
@ -14,6 +14,12 @@ module.exports = class MainAdminView extends RootView
|
|||
'click #user-search-button': 'searchForUser'
|
||||
'click #increment-button': 'incrementUserAttribute'
|
||||
'click #user-search-result': 'onClickUserSearchResult'
|
||||
'click #create-free-sub-btn': 'onClickFreeSubLink'
|
||||
|
||||
getRenderData: ->
|
||||
context = super()
|
||||
context.freeSubLink = @freeSubLink
|
||||
context
|
||||
|
||||
checkForFormSubmissionEnterPress: (e) ->
|
||||
if e.which is 13 and @$el.find('#espionage-name-or-email').val() isnt ''
|
||||
|
@ -61,7 +67,25 @@ module.exports = class MainAdminView extends RootView
|
|||
val = $('#increment-field').val()
|
||||
me.set(val, me.get(val) + 1)
|
||||
me.save()
|
||||
|
||||
|
||||
onClickUserSearchResult: (e) ->
|
||||
userID = $(e.target).closest('tr').data('user-id')
|
||||
@openModalView new AdministerUserModal({}, userID) if userID
|
||||
|
||||
onClickFreeSubLink: (e) =>
|
||||
delete @freeSubLink
|
||||
return unless me.isAdmin()
|
||||
options =
|
||||
url: '/db/prepaid/-/create'
|
||||
data: {type: 'subscription'}
|
||||
method: 'POST'
|
||||
options.success = (model, response, options) =>
|
||||
# TODO: Don't hardcode domain.
|
||||
if application.isProduction()
|
||||
@freeSubLink = "https://codecombat.com/account/subscription?_ppc=#{model.code}"
|
||||
else
|
||||
@freeSubLink = "http://localhost:3000/account/subscription?_ppc=#{model.code}"
|
||||
@render?()
|
||||
options.error = (model, response, options) =>
|
||||
console.error 'Failed to create prepaid', response
|
||||
@supermodel.addRequestResource('create_prepaid', options, 0).load()
|
||||
|
|
|
@ -154,6 +154,7 @@ module.exports = class SubscribeModal extends ModalView
|
|||
stripe = me.get('stripe') ? {}
|
||||
delete stripe.token
|
||||
delete stripe.planID
|
||||
# TODO: Need me.set('stripe', stripe) here?
|
||||
xhr = options.xhr
|
||||
if xhr.status is 402
|
||||
@state = 'declined'
|
||||
|
|
|
@ -82,6 +82,9 @@ module.exports = class ControlBarView extends CocoView
|
|||
campaign = @level.get 'campaign'
|
||||
@homeLink += '/' + campaign
|
||||
@homeViewArgs.push campaign
|
||||
else if @level.get('type', true) in ['campaign']
|
||||
@homeLink = c.homeLink = '/play-old'
|
||||
@homeViewClass = 'views/MainPlayView'
|
||||
else
|
||||
@homeLink = c.homeLink = '/'
|
||||
@homeViewClass = 'views/HomeView'
|
||||
|
|
|
@ -271,7 +271,7 @@ module.exports = class PlayLevelView extends RootView
|
|||
Backbone.Mediator.publish "ipad:language-chosen", language: e.session.get('codeLanguage') ? "python"
|
||||
# Just the level and session have been loaded by the level loader
|
||||
if e.level.get('slug') is 'zero-sum'
|
||||
e.session.set 'heroConfig', {"thangType":"52fd1524c7e6cf99160e7bc9","inventory":{"misc-0":"53e2396a53457600003e3f0f","programming-book":"546e266e9df4a17d0d449be5","minion":"54eb5dbc49fa2d5c905ddf56","feet":"53e214f153457600003e3eab","right-hand":"544d88478494308424f56505","left-hand":"546375a43839c6e02811d311","wrists":"54693797a2b1f53ce79443e9","gloves":"5469425ca2b1f53ce7944421","torso":"546d4a549df4a17d0d449a97","neck":"54693274a2b1f53ce79443c9","eyes":"546941fda2b1f53ce794441d","head":"546d4ca19df4a17d0d449abf"}}
|
||||
e.session.set 'heroConfig', {"thangType":"52fd1524c7e6cf99160e7bc9","inventory":{"misc-0":"53e2396a53457600003e3f0f","programming-book":"546e266e9df4a17d0d449be5","minion":"54eb5dbc49fa2d5c905ddf56","feet":"53e214f153457600003e3eab","right-hand":"54eab7f52b7506e891ca7202","left-hand":"5463758f3839c6e02811d30f","wrists":"54693797a2b1f53ce79443e9","gloves":"5469425ca2b1f53ce7944421","torso":"546d4a549df4a17d0d449a97","neck":"54693274a2b1f53ce79443c9","eyes":"546941fda2b1f53ce794441d","head":"546d4ca19df4a17d0d449abf"}}
|
||||
else if e.level.get('type', true) in ['hero', 'hero-ladder', 'hero-coop'] and not _.size e.session.get('heroConfig')?.inventory ? {}
|
||||
@setupManager?.destroy()
|
||||
@setupManager = new LevelSetupManager({supermodel: @supermodel, levelID: @levelID, parent: @, session: @session})
|
||||
|
|
|
@ -107,8 +107,9 @@ module.exports = class SpellPaletteEntryView extends CocoView
|
|||
Backbone.Mediator.publish 'tome:palette-pin-toggled', entry: @, pinned: @popoverPinned
|
||||
|
||||
onClick: (e) =>
|
||||
if @options.level.get('type', true) is 'hero'
|
||||
if true or @options.level.get('type', true) is 'hero'
|
||||
# Jiggle instead of pin for hero levels
|
||||
# Actually, do it all the time, because we recently busted the pin CSS. TODO: restore pinning
|
||||
jigglyPopover = $('.spell-palette-popover.popover')
|
||||
jigglyPopover.addClass 'jiggling'
|
||||
pauseJiggle = =>
|
||||
|
|
|
@ -23,6 +23,7 @@ module.exports.handlers =
|
|||
'earned_achievement': 'achievements/earned_achievement_handler'
|
||||
'poll': 'polls/poll_handler'
|
||||
'user_polls_record': 'polls/user_polls_record_handler'
|
||||
'prepaid': 'prepaids/prepaid_handler'
|
||||
|
||||
module.exports.routes =
|
||||
[
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
async = require 'async'
|
||||
Handler = require '../commons/Handler'
|
||||
discountHandler = require './discount_handler'
|
||||
Prepaid = require '../prepaids/Prepaid'
|
||||
User = require '../users/User'
|
||||
{findStripeSubscription} = require '../lib/utils'
|
||||
{getSponsoredSubsAmount} = require '../../app/core/utils'
|
||||
|
@ -25,52 +26,50 @@ class SubscriptionHandler extends Handler
|
|||
return done({res: 'You must be signed in to subscribe.', code: 403})
|
||||
|
||||
token = req.body.stripe.token
|
||||
prepaidCode = req.body.stripe.prepaidCode
|
||||
customerID = user.get('stripe')?.customerID
|
||||
if not (token or customerID)
|
||||
@logSubscriptionError(user, 'Missing stripe token or customer ID.')
|
||||
return done({res: 'Missing stripe token or customer ID.', code: 422})
|
||||
if not (token or customerID or prepaidCode)
|
||||
@logSubscriptionError(user, 'Missing Stripe token or customer ID or prepaid code')
|
||||
return done({res: 'Missing Stripe token or customer ID or prepaid code', code: 422})
|
||||
|
||||
# Create/retrieve Stripe customer
|
||||
if token
|
||||
if customerID
|
||||
# Get Stripe customer
|
||||
if customerID
|
||||
if token
|
||||
stripe.customers.update customerID, { card: token }, (err, customer) =>
|
||||
if err or not customer
|
||||
# should not happen outside of test and production polluting each other
|
||||
@logSubscriptionError(user, 'Cannot find customer: ' + customerID + '\n\n' + err)
|
||||
return done({res: 'Cannot find customer.', code: 404})
|
||||
@checkForExistingSubscription(req, user, customer, done)
|
||||
|
||||
@checkForCoupon(req, user, customer, done)
|
||||
else
|
||||
options =
|
||||
card: token
|
||||
email: user.get('email')
|
||||
metadata: { id: user._id + '', slug: user.get('slug') }
|
||||
stripe.customers.create options, (err, customer) =>
|
||||
stripe.customers.retrieve customerID, (err, customer) =>
|
||||
if err
|
||||
if err.type in ['StripeCardError', 'StripeInvalidRequestError']
|
||||
return done({res: 'Card error', code: 402})
|
||||
else
|
||||
@logSubscriptionError(user, 'Stripe customer creation error. ' + err)
|
||||
return done({res: 'Database error.', code: 500})
|
||||
|
||||
stripeInfo = _.cloneDeep(user.get('stripe') ? {})
|
||||
stripeInfo.customerID = customer.id
|
||||
user.set('stripe', stripeInfo)
|
||||
user.save (err) =>
|
||||
if err
|
||||
@logSubscriptionError(user, 'Stripe customer id save db error. ' + err)
|
||||
return done({res: 'Database error.', code: 500})
|
||||
@checkForExistingSubscription(req, user, customer, done)
|
||||
|
||||
@logSubscriptionError(user, 'Stripe customer retrieve error. ' + err)
|
||||
return done({res: 'Database error.', code: 500})
|
||||
@checkForCoupon(req, user, customer, done)
|
||||
else
|
||||
stripe.customers.retrieve(customerID, (err, customer) =>
|
||||
options =
|
||||
email: user.get('email')
|
||||
metadata: { id: user._id + '', slug: user.get('slug') }
|
||||
options.card = token if token?
|
||||
stripe.customers.create options, (err, customer) =>
|
||||
if err
|
||||
@logSubscriptionError(user, 'Stripe customer retrieve error. ' + err)
|
||||
return done({res: 'Database error.', code: 500})
|
||||
@checkForExistingSubscription(req, user, customer, done)
|
||||
)
|
||||
if err.type in ['StripeCardError', 'StripeInvalidRequestError']
|
||||
return done({res: 'Card error', code: 402})
|
||||
else
|
||||
@logSubscriptionError(user, 'Stripe customer creation error. ' + err)
|
||||
return done({res: 'Database error.', code: 500})
|
||||
|
||||
checkForExistingSubscription: (req, user, customer, done) ->
|
||||
stripeInfo = _.cloneDeep(user.get('stripe') ? {})
|
||||
stripeInfo.customerID = customer.id
|
||||
user.set('stripe', stripeInfo)
|
||||
user.save (err) =>
|
||||
if err
|
||||
@logSubscriptionError(user, 'Stripe customer id save db error. ' + err)
|
||||
return done({res: 'Database error.', code: 500})
|
||||
@checkForCoupon(req, user, customer, done)
|
||||
|
||||
checkForCoupon: (req, user, customer, done) ->
|
||||
# Check if user is subscribing someone else
|
||||
if req.body.stripe?.subscribeEmails?
|
||||
return @updateStripeRecipientSubscriptions req, user, customer, done
|
||||
|
@ -78,12 +77,31 @@ class SubscriptionHandler extends Handler
|
|||
if user.get('stripe')?.sponsorID
|
||||
return done({res: 'You already have a sponsored subscription.', code: 403})
|
||||
|
||||
couponID = user.get('stripe')?.couponID
|
||||
if req.body?.stripe?.prepaidCode
|
||||
Prepaid.findOne code: req.body.stripe.prepaidCode, (err, prepaid) =>
|
||||
if err
|
||||
@logSubscriptionError(user, 'Prepaid lookup error. ' + err)
|
||||
return done({res: 'Database error.', code: 500})
|
||||
return done({res: 'Prepaid not found', code: 404}) unless prepaid?
|
||||
return done({res: 'Prepaid not for subscription', code: 403}) unless prepaid.get('type') is 'subscription'
|
||||
return done({res: 'Prepaid has already been used', code: 403}) unless prepaid.get('status') is 'active'
|
||||
return done({res: 'Database error.', code: 500}) unless prepaid.get('properties')?.couponID
|
||||
couponID = prepaid.get('properties').couponID
|
||||
|
||||
# SALE LOGIC
|
||||
# overwrite couponID with another for everyone-sales
|
||||
#couponID = 'hoc_399' if not couponID
|
||||
# Update user
|
||||
stripeInfo = _.cloneDeep(user.get('stripe') ? {})
|
||||
stripeInfo.couponID = couponID
|
||||
stripeInfo.prepaidCode = req.body.stripe.prepaidCode
|
||||
user.set('stripe', stripeInfo)
|
||||
@checkForExistingSubscription(req, user, customer, couponID, done)
|
||||
else
|
||||
couponID = user.get('stripe')?.couponID
|
||||
# SALE LOGIC
|
||||
# overwrite couponID with another for everyone-sales
|
||||
#couponID = 'hoc_399' if not couponID
|
||||
@checkForExistingSubscription(req, user, customer, couponID, done)
|
||||
|
||||
checkForExistingSubscription: (req, user, customer, couponID, done) ->
|
||||
findStripeSubscription customer.id, subscriptionID: user.get('stripe')?.subscriptionID, (subscription) =>
|
||||
|
||||
if subscription
|
||||
|
@ -97,19 +115,25 @@ class SubscriptionHandler extends Handler
|
|||
if err
|
||||
@logSubscriptionError(user, 'Stripe cancel subscription error. ' + err)
|
||||
return done({res: 'Database error.', code: 500})
|
||||
|
||||
options = { plan: 'basic', metadata: {id: user.id}, trial_end: subscription.current_period_end }
|
||||
options.coupon = couponID if couponID
|
||||
stripe.customers.createSubscription customer.id, options, (err, subscription) =>
|
||||
if err
|
||||
@logSubscriptionError(user, 'Stripe customer plan setting error. ' + err)
|
||||
return done({res: 'Database error.', code: 500})
|
||||
|
||||
@updateUser(req, user, customer, subscription, false, done)
|
||||
|
||||
else if couponID
|
||||
# Update subscription with given couponID
|
||||
stripe.customers.updateSubscription customer.id, subscription.id, coupon: couponID, (err, subscription) =>
|
||||
if err
|
||||
@logSubscriptionError(user, 'Stripe update subscription coupon error. ' + err)
|
||||
return done({res: 'Database error.', code: 500})
|
||||
@updateUser(req, user, customer, subscription, false, done)
|
||||
|
||||
else
|
||||
# can skip creating the subscription
|
||||
return @updateUser(req, user, customer, subscription, false, done)
|
||||
# Skip creating the subscription
|
||||
@updateUser(req, user, customer, subscription, false, done)
|
||||
|
||||
else
|
||||
options = { plan: 'basic', metadata: {id: user.id}}
|
||||
|
@ -143,8 +167,25 @@ class SubscriptionHandler extends Handler
|
|||
if err
|
||||
@logSubscriptionError(user, 'Stripe user plan saving error. ' + err)
|
||||
return done({res: 'Database error.', code: 500})
|
||||
user?.saveActiveUser 'subscribe'
|
||||
return done()
|
||||
|
||||
if stripeInfo.prepaidCode?
|
||||
# Update prepaid to 'used'
|
||||
Prepaid.findOne code: stripeInfo.prepaidCode, (err, prepaid) =>
|
||||
if err
|
||||
@logSubscriptionError(user, 'Prepaid find error. ' + err)
|
||||
return done({res: 'Database error.', code: 500})
|
||||
unless prepaid?
|
||||
@logSubscriptionError(user, "Expected prepaid not found: #{stripeInfo.prepaidCode}")
|
||||
return done({res: 'Database error.', code: 500})
|
||||
prepaid.set('status', 'used')
|
||||
prepaid.set('redeemer', user.get('_id'))
|
||||
prepaid.save (err) =>
|
||||
if err
|
||||
@logSubscriptionError(user, 'Prepaid update error. ' + err)
|
||||
return done({res: 'Database error.', code: 500})
|
||||
done()
|
||||
else
|
||||
done()
|
||||
|
||||
updateStripeRecipientSubscriptions: (req, user, customer, done) ->
|
||||
return done({res: 'Database error.', code: 500}) unless req.body.stripe?.subscribeEmails?
|
||||
|
|
|
@ -25,6 +25,7 @@ UserPollsRecordHandler = class UserPollsRecordHandler extends Handler
|
|||
@createAndSaveNewUserPollsRecord userID, req, res
|
||||
|
||||
createAndSaveNewUserPollsRecord: (userID, req, res) =>
|
||||
return @sendForbiddenError(res) unless req.user
|
||||
initVals = user: userID, polls: {}, level: req.user.level()
|
||||
userPollsRecord = new UserPollsRecord initVals
|
||||
userPollsRecord.save (err) =>
|
||||
|
|
5
server/prepaids/Prepaid.coffee
Normal file
5
server/prepaids/Prepaid.coffee
Normal file
|
@ -0,0 +1,5 @@
|
|||
mongoose = require 'mongoose'
|
||||
|
||||
PrepaidSchema = new mongoose.Schema {}, {strict: false, minimize: false}
|
||||
|
||||
module.exports = Prepaid = mongoose.model('prepaid', PrepaidSchema)
|
45
server/prepaids/prepaid_handler.coffee
Normal file
45
server/prepaids/prepaid_handler.coffee
Normal file
|
@ -0,0 +1,45 @@
|
|||
Handler = require '../commons/Handler'
|
||||
Prepaid = require './Prepaid'
|
||||
|
||||
# TODO: Should this happen on a save() call instead of a prepaid/-/create post?
|
||||
# TODO: Probably a better way to create a unique 8 charactor string property using db voodoo
|
||||
|
||||
PrepaidHandler = class PrepaidHandler extends Handler
|
||||
modelClass: Prepaid
|
||||
jsonSchema: require '../../app/schemas/models/prepaid.schema'
|
||||
allowedMethods: ['POST']
|
||||
|
||||
hasAccess: (req) ->
|
||||
req.user?.isAdmin()
|
||||
|
||||
getByRelationship: (req, res, args...) ->
|
||||
relationship = args[1]
|
||||
return @createPrepaid(req, res) if relationship is 'create'
|
||||
super arguments...
|
||||
|
||||
createPrepaid: (req, res) ->
|
||||
return @sendForbiddenError(res) unless @hasAccess(req)
|
||||
return @sendForbiddenError(res) unless req.body.type is 'subscription'
|
||||
@generateNewCode (code) =>
|
||||
return @sendDatabaseError(res, 'Database error.') unless code
|
||||
prepaid = new Prepaid
|
||||
creator: req.user.id
|
||||
type: req.body.type
|
||||
status: 'active'
|
||||
code: code
|
||||
properties:
|
||||
couponID: 'free'
|
||||
prepaid.save (err) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
@sendSuccess(res, prepaid.toObject())
|
||||
|
||||
generateNewCode: (done) ->
|
||||
tryCode = ->
|
||||
code = _.sample("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 8).join('')
|
||||
Prepaid.findOne code: code, (err, prepaid) ->
|
||||
return if err
|
||||
return done(code) unless prepaid
|
||||
tryCode()
|
||||
tryCode()
|
||||
|
||||
module.exports = new PrepaidHandler()
|
|
@ -139,7 +139,7 @@ module.exports.getTwoGames = (req, res) ->
|
|||
ogresGameID = req.body.ogresGameID
|
||||
return if simulatorIsTooOld req, res
|
||||
#ladderGameIDs = ['greed', 'criss-cross', 'brawlwood', 'dungeon-arena', 'gold-rush', 'sky-span'] # Let's not give any extra simulations to old ladders.
|
||||
ladderGameIDs = ['dueling-grounds', 'cavern-survival', 'multiplayer-treasure-grove', 'harrowland'] #, 'zero-sum']
|
||||
ladderGameIDs = ['dueling-grounds', 'cavern-survival', 'multiplayer-treasure-grove', 'harrowland', 'zero-sum']
|
||||
levelID = _.sample ladderGameIDs
|
||||
unless ogresGameID and humansGameID
|
||||
async.map [{levelID: levelID, team: 'humans'}, {levelID: levelID, team: 'ogres'}], findRandomSession, (err, sessions) ->
|
||||
|
|
|
@ -108,6 +108,7 @@ module.exports.setup = (app) ->
|
|||
|
||||
stripeInfo = _.cloneDeep(user.get('stripe') ? {})
|
||||
delete stripeInfo.planID
|
||||
delete stripeInfo.prepaidCode
|
||||
delete stripeInfo.subscriptionID
|
||||
user.set('stripe', stripeInfo)
|
||||
user.save (err) =>
|
||||
|
|
|
@ -143,7 +143,7 @@ UserHandler = class UserHandler extends Handler
|
|||
)
|
||||
else
|
||||
wantsPlan = req.body.stripe.planID?
|
||||
hasPlan = user.get('stripe')?.planID?
|
||||
hasPlan = user.get('stripe')?.planID? and not req.body.stripe.prepaidCode?
|
||||
finishSubscription hasPlan, wantsPlan
|
||||
|
||||
# Discount setting
|
||||
|
|
|
@ -37,6 +37,7 @@ models_path = [
|
|||
'../../server/achievements/Achievement'
|
||||
'../../server/achievements/EarnedAchievement'
|
||||
'../../server/payments/Payment'
|
||||
'../../server/prepaids/Prepaid'
|
||||
]
|
||||
|
||||
for m in models_path
|
||||
|
@ -113,6 +114,11 @@ wrapUpGetUser = (email, user, done) ->
|
|||
GLOBAL.getURL = (path) ->
|
||||
return 'http://localhost:3001' + path
|
||||
|
||||
GLOBAL.createPrepaid = (type, done) ->
|
||||
options = uri: GLOBAL.getURL('/db/prepaid/-/create')
|
||||
options.json = type: type if type?
|
||||
request.post options, done
|
||||
|
||||
newUserCount = 0
|
||||
GLOBAL.createNewUser = (done) ->
|
||||
name = password = "user#{newUserCount++}"
|
||||
|
|
95
test/server/functional/prepaid.spec.coffee
Normal file
95
test/server/functional/prepaid.spec.coffee
Normal file
|
@ -0,0 +1,95 @@
|
|||
require '../common'
|
||||
|
||||
describe '/db/prepaid', ->
|
||||
prepaidURL = getURL('/db/prepaid')
|
||||
prepaidCreateURL = getURL('/db/prepaid/-/create')
|
||||
|
||||
verifyPrepaid = (user, prepaid, done) ->
|
||||
expect(prepaid.creator).toEqual(user.id)
|
||||
expect(prepaid.type).toEqual('subscription')
|
||||
expect(prepaid.status).toEqual('active')
|
||||
expect(prepaid.code).toMatch(/^\w{8}$/)
|
||||
expect(prepaid.properties?.couponID).toEqual('free')
|
||||
done()
|
||||
|
||||
it 'Clear database users and prepaids', (done) ->
|
||||
clearModels [User, Prepaid], (err) ->
|
||||
throw err if err
|
||||
done()
|
||||
|
||||
it 'Anonymous creates prepaid code', (done) ->
|
||||
createPrepaid 'subscription', (err, res, body) ->
|
||||
expect(err).toBeNull()
|
||||
expect(res.statusCode).toBe(401)
|
||||
done()
|
||||
|
||||
it 'Non-admin creates prepaid code', (done) ->
|
||||
loginNewUser (user1) ->
|
||||
expect(user1.isAdmin()).toEqual(false)
|
||||
createPrepaid 'subscription', (err, res, body) ->
|
||||
expect(err).toBeNull()
|
||||
expect(res.statusCode).toBe(403)
|
||||
done()
|
||||
|
||||
it 'Admin creates prepaid code with type subscription', (done) ->
|
||||
loginNewUser (user1) ->
|
||||
user1.set('permissions', ['admin'])
|
||||
user1.save (err, user1) ->
|
||||
expect(err).toBeNull()
|
||||
expect(user1.isAdmin()).toEqual(true)
|
||||
createPrepaid 'subscription', (err, res, body) ->
|
||||
expect(err).toBeNull()
|
||||
expect(res.statusCode).toBe(200)
|
||||
verifyPrepaid user1, body, done
|
||||
|
||||
it 'Admin creates prepaid code with invalid type', (done) ->
|
||||
loginNewUser (user1) ->
|
||||
user1.set('permissions', ['admin'])
|
||||
user1.save (err, user1) ->
|
||||
expect(err).toBeNull()
|
||||
expect(user1.isAdmin()).toEqual(true)
|
||||
createPrepaid 'bulldozer', (err, res, body) ->
|
||||
expect(err).toBeNull()
|
||||
expect(res.statusCode).toBe(403)
|
||||
done()
|
||||
|
||||
it 'Admin creates prepaid code with no type specified', (done) ->
|
||||
loginNewUser (user1) ->
|
||||
user1.set('permissions', ['admin'])
|
||||
user1.save (err, user1) ->
|
||||
expect(err).toBeNull()
|
||||
expect(user1.isAdmin()).toEqual(true)
|
||||
createPrepaid null, (err, res, body) ->
|
||||
expect(err).toBeNull()
|
||||
expect(res.statusCode).toBe(403)
|
||||
done()
|
||||
|
||||
it 'Non-admin requests /db/prepaid', (done) ->
|
||||
loginNewUser (user1) ->
|
||||
expect(user1.isAdmin()).toEqual(false)
|
||||
request.get {uri: prepaidURL}, (err, res, body) ->
|
||||
expect(err).toBeNull()
|
||||
expect(res.statusCode).toBe(403)
|
||||
done()
|
||||
|
||||
it 'Admin requests /db/prepaid', (done) ->
|
||||
loginNewUser (user1) ->
|
||||
user1.set('permissions', ['admin'])
|
||||
user1.save (err, user1) ->
|
||||
expect(err).toBeNull()
|
||||
expect(user1.isAdmin()).toEqual(true)
|
||||
createPrepaid 'subscription', (err, res, prepaid) ->
|
||||
expect(err).toBeNull()
|
||||
expect(res.statusCode).toBe(200)
|
||||
request.get {uri: prepaidURL}, (err, res, body) ->
|
||||
expect(err).toBeNull()
|
||||
expect(res.statusCode).toBe(200)
|
||||
prepaids = JSON.parse(body)
|
||||
found = false
|
||||
for p in prepaids
|
||||
if p._id is prepaid._id
|
||||
found = true
|
||||
verifyPrepaid user1, p, done
|
||||
break
|
||||
expect(found).toEqual(true)
|
||||
done() unless found
|
|
@ -217,7 +217,7 @@ describe '/db/user, editing stripe property', ->
|
|||
setTimeout(f, 500) # bit of a race condition here, response returns before stripe has been updated
|
||||
|
||||
|
||||
describe 'Sponsored subscriptions', ->
|
||||
describe 'Subscriptions', ->
|
||||
# TODO: Test recurring billing via webhooks
|
||||
# TODO: Test error rollbacks, Stripe is authority
|
||||
|
||||
|
@ -359,23 +359,37 @@ describe 'Sponsored subscriptions', ->
|
|||
expect(payment.get('gems')).toBeGreaterThan(subGems - 1)
|
||||
done()
|
||||
|
||||
subscribeUser = (user, token, done) ->
|
||||
subscribeUser = (user, token, prepaidCode, done) ->
|
||||
requestBody = user.toObject()
|
||||
requestBody.stripe =
|
||||
planID: 'basic'
|
||||
requestBody.stripe.token = token.id if token?
|
||||
requestBody.stripe.prepaidCode = prepaidCode if prepaidCode?
|
||||
request.put {uri: userURL, json: requestBody, headers: headers }, (err, res, body) ->
|
||||
expect(err).toBeNull()
|
||||
expect(res.statusCode).toBe(200)
|
||||
expect(body.stripe.customerID).toBeDefined()
|
||||
expect(body.stripe.planID).toBe('basic')
|
||||
expect(body.stripe.token).toBeUndefined()
|
||||
if prepaidCode?
|
||||
expect(body.stripe.prepaidCode).toEqual(prepaidCode)
|
||||
expect(body.stripe.couponID).toEqual('free')
|
||||
expect(body.purchased.gems).toBeGreaterThan(subGems - 1)
|
||||
User.findById user.id, (err, user) ->
|
||||
stripeInfo = user.get('stripe')
|
||||
expect(stripeInfo.customerID).toBeDefined()
|
||||
expect(stripeInfo.planID).toBe('basic')
|
||||
expect(stripeInfo.token).toBeUndefined()
|
||||
if prepaidCode?
|
||||
expect(stripeInfo.prepaidCode).toEqual(prepaidCode)
|
||||
expect(stripeInfo.couponID).toEqual('free')
|
||||
expect(user.get('purchased').gems).toBeGreaterThan(subGems - 1)
|
||||
done()
|
||||
|
||||
unsubscribeUser = (user, done) ->
|
||||
requestBody = user.toObject()
|
||||
delete requestBody.stripe.planID
|
||||
delete requestBody.stripe.prepaidCode
|
||||
request.put {uri: userURL, json: requestBody, headers: headers }, (err, res, body) ->
|
||||
expect(err).toBeNull()
|
||||
expect(res.statusCode).toBe(200)
|
||||
|
@ -383,7 +397,11 @@ describe 'Sponsored subscriptions', ->
|
|||
expect(user.get('stripe').customerID).toBeDefined()
|
||||
expect(user.get('stripe').planID).toBeUndefined()
|
||||
expect(user.get('stripe').token).toBeUndefined()
|
||||
done()
|
||||
stripe.customers.retrieveSubscription user.get('stripe').customerID, user.get('stripe').subscriptionID, (err, subscription) ->
|
||||
expect(err).toBeNull()
|
||||
expect(subscription).not.toBeNull()
|
||||
expect(subscription?.cancel_at_period_end).toEqual(true)
|
||||
done()
|
||||
|
||||
subscribeRecipients = (sponsor, recipients, token, done) ->
|
||||
# console.log 'subscribeRecipients', sponsor.id, (recipient.id for recipient in recipients), token?
|
||||
|
@ -444,7 +462,7 @@ describe 'Sponsored subscriptions', ->
|
|||
# Simulate subscription ending after cancellation
|
||||
return done() unless immediately
|
||||
|
||||
# Simulate subscription cancelling a trial end
|
||||
# Simulate subscription cancelling at period end
|
||||
stripe.customers.cancelSubscription customerID, subscriptionID, (err) ->
|
||||
expect(err).toBeNull()
|
||||
|
||||
|
@ -494,7 +512,177 @@ describe 'Sponsored subscriptions', ->
|
|||
throw err if err
|
||||
done()
|
||||
|
||||
describe 'Basic', ->
|
||||
describe 'Personal', ->
|
||||
it 'Subscribe user with new token', (done) ->
|
||||
stripe.tokens.create {
|
||||
card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
|
||||
}, (err, token) ->
|
||||
loginNewUser (user1) ->
|
||||
subscribeUser user1, token, null, done
|
||||
|
||||
it 'Admin subscribes self with valid prepaid', (done) ->
|
||||
loginNewUser (user1) ->
|
||||
user1.set('permissions', ['admin'])
|
||||
user1.save (err, user1) ->
|
||||
expect(err).toBeNull()
|
||||
expect(user1.isAdmin()).toEqual(true)
|
||||
createPrepaid 'subscription', (err, res, prepaid) ->
|
||||
expect(err).toBeNull()
|
||||
subscribeUser user1, null, prepaid.code, ->
|
||||
Prepaid.findById prepaid._id, (err, prepaid) ->
|
||||
expect(err).toBeNull()
|
||||
expect(prepaid.get('status')).toEqual('used')
|
||||
done()
|
||||
|
||||
it 'Admin subscribes self with invalid prepaid', (done) ->
|
||||
loginNewUser (user1) ->
|
||||
user1.set('permissions', ['admin'])
|
||||
user1.save (err, user1) ->
|
||||
expect(err).toBeNull()
|
||||
expect(user1.isAdmin()).toEqual(true)
|
||||
requestBody = user1.toObject()
|
||||
requestBody.stripe =
|
||||
planID: 'basic'
|
||||
requestBody.stripe.prepaidCode = 'MattMatt'
|
||||
request.put {uri: userURL, json: requestBody, headers: headers }, (err, res, body) ->
|
||||
expect(err).toBeNull()
|
||||
expect(res.statusCode).toBe(404)
|
||||
done()
|
||||
|
||||
it 'User2 subscribes with used prepaid', (done) ->
|
||||
loginNewUser (user1) ->
|
||||
user1.set('permissions', ['admin'])
|
||||
user1.save (err, user1) ->
|
||||
expect(err).toBeNull()
|
||||
expect(user1.isAdmin()).toEqual(true)
|
||||
createPrepaid 'subscription', (err, res, prepaid) ->
|
||||
expect(err).toBeNull()
|
||||
subscribeUser user1, null, prepaid.code, ->
|
||||
loginNewUser (user2) ->
|
||||
requestBody = user2.toObject()
|
||||
requestBody.stripe =
|
||||
planID: 'basic'
|
||||
requestBody.stripe.prepaidCode = prepaid.code
|
||||
request.put {uri: userURL, json: requestBody, headers: headers }, (err, res, body) ->
|
||||
expect(err).toBeNull()
|
||||
expect(res.statusCode).toBe(403)
|
||||
done()
|
||||
|
||||
it 'Subscribe normally, subscribe with valid prepaid', (done) ->
|
||||
stripe.tokens.create {
|
||||
card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
|
||||
}, (err, token) ->
|
||||
loginNewUser (user1) ->
|
||||
user1.set('permissions', ['admin'])
|
||||
user1.save (err, user1) ->
|
||||
expect(err).toBeNull()
|
||||
expect(user1.isAdmin()).toEqual(true)
|
||||
subscribeUser user1, token, null, ->
|
||||
User.findById user1.id, (err, user1) ->
|
||||
expect(err).toBeNull()
|
||||
createPrepaid 'subscription', (err, res, prepaid) ->
|
||||
expect(err).toBeNull()
|
||||
subscribeUser user1, null, prepaid.code, ->
|
||||
Prepaid.findById prepaid._id, (err, prepaid) ->
|
||||
expect(err).toBeNull()
|
||||
expect(prepaid.get('status')).toEqual('used')
|
||||
User.findById user1.id, (err, user1) ->
|
||||
expect(err).toBeNull()
|
||||
customerID = user1.get('stripe').customerID
|
||||
subscriptionID = user1.get('stripe').subscriptionID
|
||||
stripe.customers.retrieveSubscription customerID, subscriptionID, (err, subscription) ->
|
||||
expect(err).toBeNull()
|
||||
expect(subscription).not.toBeNull()
|
||||
expect(subscription.discount?.coupon?.id).toEqual('free')
|
||||
done()
|
||||
|
||||
it 'Subscribe with coupon, subscribe with valid prepaid', (done) ->
|
||||
stripe.tokens.create {
|
||||
card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
|
||||
}, (err, token) ->
|
||||
loginNewUser (user1) ->
|
||||
user1.set('permissions', ['admin'])
|
||||
user1.save (err, user1) ->
|
||||
requestBody = user1.toObject()
|
||||
requestBody.stripe =
|
||||
planID: 'basic'
|
||||
token: token.id
|
||||
couponID: '20pct'
|
||||
request.put {uri: userURL, json: requestBody, headers: headers }, (err, res, updatedUser) ->
|
||||
expect(err).toBeNull()
|
||||
expect(res.statusCode).toBe(200)
|
||||
createPrepaid 'subscription', (err, res, prepaid) ->
|
||||
subscribeUser user1, null, prepaid.code, ->
|
||||
Prepaid.findById prepaid._id, (err, prepaid) ->
|
||||
expect(err).toBeNull()
|
||||
expect(prepaid.get('status')).toEqual('used')
|
||||
User.findById user1.id, (err, user1) ->
|
||||
expect(err).toBeNull()
|
||||
customerID = user1.get('stripe').customerID
|
||||
subscriptionID = user1.get('stripe').subscriptionID
|
||||
stripe.customers.retrieveSubscription customerID, subscriptionID, (err, subscription) ->
|
||||
expect(err).toBeNull()
|
||||
expect(subscription).not.toBeNull()
|
||||
expect(subscription.discount?.coupon?.id).toEqual('free')
|
||||
done()
|
||||
|
||||
it 'Subscribe with prepaid, then cancel', (done) ->
|
||||
loginNewUser (user1) ->
|
||||
user1.set('permissions', ['admin'])
|
||||
user1.save (err, user1) ->
|
||||
expect(err).toBeNull()
|
||||
expect(user1.isAdmin()).toEqual(true)
|
||||
createPrepaid 'subscription', (err, res, prepaid) ->
|
||||
expect(err).toBeNull()
|
||||
subscribeUser user1, null, prepaid.code, ->
|
||||
Prepaid.findById prepaid._id, (err, prepaid) ->
|
||||
expect(err).toBeNull()
|
||||
expect(prepaid.get('status')).toEqual('used')
|
||||
User.findById user1.id, (err, user1) ->
|
||||
expect(err).toBeNull()
|
||||
unsubscribeUser user1, ->
|
||||
User.findById user1.id, (err, user1) ->
|
||||
expect(err).toBeNull()
|
||||
expect(user1.get('stripe').prepaidCode).toEqual(prepaid.get('code'))
|
||||
done()
|
||||
|
||||
it 'Subscribe with prepaid, then delete', (done) ->
|
||||
loginNewUser (user1) ->
|
||||
user1.set('permissions', ['admin'])
|
||||
user1.save (err, user1) ->
|
||||
expect(err).toBeNull()
|
||||
expect(user1.isAdmin()).toEqual(true)
|
||||
createPrepaid 'subscription', (err, res, prepaid) ->
|
||||
expect(err).toBeNull()
|
||||
subscribeUser user1, null, prepaid.code, ->
|
||||
Prepaid.findById prepaid._id, (err, prepaid) ->
|
||||
expect(err).toBeNull()
|
||||
expect(prepaid.get('status')).toEqual('used')
|
||||
User.findById user1.id, (err, user1) ->
|
||||
expect(err).toBeNull()
|
||||
unsubscribeUser user1, ->
|
||||
User.findById user1.id, (err, user1) ->
|
||||
expect(err).toBeNull()
|
||||
stripeInfo = user1.get('stripe')
|
||||
expect(stripeInfo.prepaidCode).toEqual(prepaid.get('code'))
|
||||
|
||||
# Delete subscription
|
||||
stripe.customers.retrieveSubscription stripeInfo.customerID, stripeInfo.subscriptionID, (err, subscription) ->
|
||||
expect(err).toBeNull()
|
||||
event = _.cloneDeep(customerSubscriptionDeletedSampleEvent)
|
||||
event.data.object = subscription
|
||||
request.post {uri: webhookURL, json: event}, (err, res, body) ->
|
||||
expect(err).toBeNull()
|
||||
User.findById user1.id, (err, user1) ->
|
||||
expect(err).toBeNull()
|
||||
stripeInfo = user1.get('stripe')
|
||||
expect(stripeInfo.planID).toBeUndefined()
|
||||
expect(stripeInfo.prepaidCode).toBeUndefined()
|
||||
expect(stripeInfo.subscriptionID).toBeUndefined()
|
||||
done()
|
||||
|
||||
|
||||
describe 'Sponsored', ->
|
||||
it 'Unsubscribed user1 subscribes user2', (done) ->
|
||||
stripe.tokens.create {
|
||||
card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
|
||||
|
@ -572,7 +760,7 @@ describe 'Sponsored subscriptions', ->
|
|||
}, (err, token) ->
|
||||
createNewUser (user2) ->
|
||||
loginNewUser (user1) ->
|
||||
subscribeUser user1, token, (updatedUser) ->
|
||||
subscribeUser user1, token, null, (updatedUser) ->
|
||||
User.findById user1.id, (err, user1) ->
|
||||
expect(err).toBeNull()
|
||||
subscribeRecipients user1, [user2], null, (updatedUser) ->
|
||||
|
@ -588,7 +776,7 @@ describe 'Sponsored subscriptions', ->
|
|||
}, (err, token) ->
|
||||
createNewUser (user2) ->
|
||||
loginNewUser (user1) ->
|
||||
subscribeUser user1, token, (updatedUser) ->
|
||||
subscribeUser user1, token, null, (updatedUser) ->
|
||||
User.findById user1.id, (err, user1) ->
|
||||
expect(err).toBeNull()
|
||||
subscribeRecipients user1, [user2], null, (updatedUser) ->
|
||||
|
@ -608,12 +796,12 @@ describe 'Sponsored subscriptions', ->
|
|||
}, (err, token) ->
|
||||
createNewUser (user2) ->
|
||||
loginNewUser (user1) ->
|
||||
subscribeUser user1, token, (updatedUser) ->
|
||||
subscribeUser user1, token, null, (updatedUser) ->
|
||||
User.findById user1.id, (err, user1) ->
|
||||
expect(err).toBeNull()
|
||||
subscribeRecipients user1, [user2], null, (updatedUser) ->
|
||||
User.findById user1.id, (err, user1) ->
|
||||
unsubscribeUser user1, (updatedUser) ->
|
||||
unsubscribeUser user1, ->
|
||||
verifySponsorship user1.id, user2.id, done
|
||||
|
||||
it 'Sponsored user2 tries to subscribe', (done) ->
|
||||
|
@ -636,6 +824,29 @@ describe 'Sponsored subscriptions', ->
|
|||
expect(res.statusCode).toBe(403)
|
||||
done()
|
||||
|
||||
it 'Sponsored user2 tries to subscribe with valid prepaid', (done) ->
|
||||
stripe.tokens.create {
|
||||
card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
|
||||
}, (err, token) ->
|
||||
createNewUser (user2) ->
|
||||
loginNewUser (user1) ->
|
||||
subscribeRecipients user1, [user2], token, (updatedUser) ->
|
||||
loginUser user2, (user2) ->
|
||||
user2.set('permissions', ['admin'])
|
||||
user2.save (err, user1) ->
|
||||
expect(err).toBeNull()
|
||||
expect(user2.isAdmin()).toEqual(true)
|
||||
createPrepaid 'subscription', (err, res, prepaid) ->
|
||||
expect(err).toBeNull()
|
||||
requestBody = user2.toObject()
|
||||
requestBody.stripe =
|
||||
planID: 'basic'
|
||||
prepaidCode: prepaid.code
|
||||
request.put {uri: userURL, json: requestBody, headers: headers }, (err, res, body) ->
|
||||
expect(err).toBeNull()
|
||||
expect(res.statusCode).toBe(403)
|
||||
done()
|
||||
|
||||
it 'Sponsored user2 tries to unsubscribe', (done) ->
|
||||
stripe.tokens.create {
|
||||
card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
|
||||
|
@ -771,7 +982,7 @@ describe 'Sponsored subscriptions', ->
|
|||
stripe.tokens.create {
|
||||
card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
|
||||
}, (err, token) ->
|
||||
subscribeUser user1, token, (updatedUser) ->
|
||||
subscribeUser user1, token, null, (updatedUser) ->
|
||||
User.findById user1.id, (err, user1) ->
|
||||
expect(err).toBeNull()
|
||||
expect(user1.get('stripe').subscriptionID).toBeDefined()
|
||||
|
@ -791,186 +1002,216 @@ describe 'Sponsored subscriptions', ->
|
|||
expect(sub.metadata?.id).toEqual(user1.id)
|
||||
done()
|
||||
|
||||
describe 'Bulk discounts', ->
|
||||
# Bulk discount algorithm (includes personal sub):
|
||||
# 1 100%
|
||||
# 2-11 80%
|
||||
# 12+ 60%
|
||||
it 'Subscribe with prepaid, then get sponsored', (done) ->
|
||||
loginNewUser (user1) ->
|
||||
user1.set('permissions', ['admin'])
|
||||
user1.save (err, user1) ->
|
||||
expect(err).toBeNull()
|
||||
expect(user1.isAdmin()).toEqual(true)
|
||||
createPrepaid 'subscription', (err, res, prepaid) ->
|
||||
expect(err).toBeNull()
|
||||
subscribeUser user1, null, prepaid.code, ->
|
||||
stripe.tokens.create {
|
||||
card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
|
||||
}, (err, token) ->
|
||||
loginNewUser (user2) ->
|
||||
requestBody = user2.toObject()
|
||||
requestBody.stripe =
|
||||
token: token.id
|
||||
subscribeEmails: [user1.get('emailLower')]
|
||||
request.put {uri: userURL, json: requestBody, headers: headers }, (err, res, body) ->
|
||||
expect(err).toBeNull()
|
||||
expect(res.statusCode).toBe(200)
|
||||
User.findById user1.id, (err, user) ->
|
||||
expect(err).toBeNull()
|
||||
stripeInfo = user.get('stripe')
|
||||
expect(stripeInfo.customerID).toBeDefined()
|
||||
expect(stripeInfo.planID).toBeDefined()
|
||||
expect(stripeInfo.subscriptionID).toBeDefined()
|
||||
expect(stripeInfo.sponsorID).toBeUndefined()
|
||||
done()
|
||||
|
||||
it 'Unsubscribed user1 subscribes two users', (done) ->
|
||||
stripe.tokens.create {
|
||||
card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
|
||||
}, (err, token) ->
|
||||
createNewUser (user3) ->
|
||||
createNewUser (user2) ->
|
||||
|
||||
describe 'Bulk discounts', ->
|
||||
# Bulk discount algorithm (includes personal sub):
|
||||
# 1 100%
|
||||
# 2-11 80%
|
||||
# 12+ 60%
|
||||
|
||||
it 'Unsubscribed user1 subscribes two users', (done) ->
|
||||
stripe.tokens.create {
|
||||
card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
|
||||
}, (err, token) ->
|
||||
createNewUser (user3) ->
|
||||
createNewUser (user2) ->
|
||||
loginNewUser (user1) ->
|
||||
subscribeRecipients user1, [user2, user3], token, (updatedUser) ->
|
||||
verifySponsorship user1.id, user2.id, ->
|
||||
verifySponsorship user1.id, user3.id, done
|
||||
|
||||
it 'Subscribed user1 subscribes 2 users, unsubscribes 2', (done) ->
|
||||
recipientCount = 2
|
||||
recipientsToVerify = [0, 1]
|
||||
recipients = new SubbedRecipients recipientCount, recipientsToVerify
|
||||
|
||||
# Create recipients
|
||||
recipients.createRecipients ->
|
||||
expect(recipients.length()).toEqual(recipientCount)
|
||||
|
||||
stripe.tokens.create {
|
||||
card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
|
||||
}, (err, token) ->
|
||||
|
||||
# Create sponsor user
|
||||
loginNewUser (user1) ->
|
||||
subscribeRecipients user1, [user2, user3], token, (updatedUser) ->
|
||||
verifySponsorship user1.id, user2.id, ->
|
||||
verifySponsorship user1.id, user3.id, done
|
||||
subscribeUser user1, token, null, (updatedUser) ->
|
||||
User.findById user1.id, (err, user1) ->
|
||||
expect(err).toBeNull()
|
||||
|
||||
it 'Subscribed user1 subscribes 2 users, unsubscribes 2', (done) ->
|
||||
recipientCount = 2
|
||||
recipientsToVerify = [0, 1]
|
||||
recipients = new SubbedRecipients recipientCount, recipientsToVerify
|
||||
# Subscribe recipients
|
||||
recipients.subRecipients user1, null, ->
|
||||
User.findById user1.id, (err, user1) ->
|
||||
|
||||
# Create recipients
|
||||
recipients.createRecipients ->
|
||||
expect(recipients.length()).toEqual(recipientCount)
|
||||
# Unsubscribe recipient0
|
||||
unsubscribeRecipient user1, recipients.get(0), true, ->
|
||||
User.findById user1.id, (err, user1) ->
|
||||
stripeInfo = user1.get('stripe')
|
||||
expect(stripeInfo.recipients.length).toEqual(1)
|
||||
verifyNotSponsoring user1.id, recipients.get(0).id, ->
|
||||
verifyNotRecipient recipients.get(0).id, ->
|
||||
stripe.customers.retrieveSubscription stripeInfo.customerID, stripeInfo.sponsorSubscriptionID, (err, subscription) ->
|
||||
expect(err).toBeNull()
|
||||
expect(subscription).not.toBeNull()
|
||||
expect(subscription.quantity).toEqual(getSubscribedQuantity(1))
|
||||
|
||||
stripe.tokens.create {
|
||||
card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
|
||||
}, (err, token) ->
|
||||
# Unsubscribe recipient1
|
||||
unsubscribeRecipient user1, recipients.get(1), true, ->
|
||||
User.findById user1.id, (err, user1) ->
|
||||
stripeInfo = user1.get('stripe')
|
||||
expect(stripeInfo.recipients.length).toEqual(0)
|
||||
verifyNotSponsoring user1.id, recipients.get(1).id, ->
|
||||
verifyNotRecipient recipients.get(1).id, ->
|
||||
stripe.customers.retrieveSubscription stripeInfo.customerID, stripeInfo.sponsorSubscriptionID, (err, subscription) ->
|
||||
expect(err).toBeNull()
|
||||
expect(subscription).not.toBeNull()
|
||||
expect(subscription.quantity).toEqual(0)
|
||||
done()
|
||||
|
||||
# Create sponsor user
|
||||
loginNewUser (user1) ->
|
||||
subscribeUser user1, token, (updatedUser) ->
|
||||
User.findById user1.id, (err, user1) ->
|
||||
expect(err).toBeNull()
|
||||
it 'Subscribed user1 subscribes 3 users, unsubscribes 2, themselves, then 1', (done) ->
|
||||
recipientCount = 3
|
||||
recipientsToVerify = [0, 1, 2]
|
||||
recipients = new SubbedRecipients recipientCount, recipientsToVerify
|
||||
|
||||
# Subscribe recipients
|
||||
recipients.subRecipients user1, null, ->
|
||||
User.findById user1.id, (err, user1) ->
|
||||
# Create recipients
|
||||
recipients.createRecipients ->
|
||||
expect(recipients.length()).toEqual(recipientCount)
|
||||
stripe.tokens.create {
|
||||
card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
|
||||
}, (err, token) ->
|
||||
|
||||
# Unsubscribe recipient0
|
||||
unsubscribeRecipient user1, recipients.get(0), true, ->
|
||||
User.findById user1.id, (err, user1) ->
|
||||
stripeInfo = user1.get('stripe')
|
||||
expect(stripeInfo.recipients.length).toEqual(1)
|
||||
verifyNotSponsoring user1.id, recipients.get(0).id, ->
|
||||
verifyNotRecipient recipients.get(0).id, ->
|
||||
stripe.customers.retrieveSubscription stripeInfo.customerID, stripeInfo.sponsorSubscriptionID, (err, subscription) ->
|
||||
expect(err).toBeNull()
|
||||
expect(subscription).not.toBeNull()
|
||||
expect(subscription.quantity).toEqual(getSubscribedQuantity(1))
|
||||
# Create sponsor user
|
||||
loginNewUser (user1) ->
|
||||
subscribeUser user1, token, null, (updatedUser) ->
|
||||
User.findById user1.id, (err, user1) ->
|
||||
expect(err).toBeNull()
|
||||
|
||||
# Unsubscribe recipient1
|
||||
unsubscribeRecipient user1, recipients.get(1), true, ->
|
||||
User.findById user1.id, (err, user1) ->
|
||||
stripeInfo = user1.get('stripe')
|
||||
expect(stripeInfo.recipients.length).toEqual(0)
|
||||
verifyNotSponsoring user1.id, recipients.get(1).id, ->
|
||||
verifyNotRecipient recipients.get(1).id, ->
|
||||
stripe.customers.retrieveSubscription stripeInfo.customerID, stripeInfo.sponsorSubscriptionID, (err, subscription) ->
|
||||
expect(err).toBeNull()
|
||||
expect(subscription).not.toBeNull()
|
||||
expect(subscription.quantity).toEqual(0)
|
||||
done()
|
||||
# Subscribe recipients
|
||||
recipients.subRecipients user1, null, ->
|
||||
User.findById user1.id, (err, user1) ->
|
||||
|
||||
it 'Subscribed user1 subscribes 3 users, unsubscribes 2, themselves, then 1', (done) ->
|
||||
recipientCount = 3
|
||||
recipientsToVerify = [0, 1, 2]
|
||||
recipients = new SubbedRecipients recipientCount, recipientsToVerify
|
||||
# Unsubscribe first recipient
|
||||
unsubscribeRecipient user1, recipients.get(0), true, ->
|
||||
User.findById user1.id, (err, user1) ->
|
||||
stripeInfo = user1.get('stripe')
|
||||
expect(stripeInfo.recipients.length).toEqual(recipientCount - 1)
|
||||
verifyNotSponsoring user1.id, recipients.get(0).id, ->
|
||||
verifyNotRecipient recipients.get(0).id, ->
|
||||
stripe.customers.retrieveSubscription stripeInfo.customerID, stripeInfo.sponsorSubscriptionID, (err, subscription) ->
|
||||
expect(err).toBeNull()
|
||||
expect(subscription).not.toBeNull()
|
||||
expect(subscription.quantity).toEqual(getSubscribedQuantity(recipientCount - 1))
|
||||
|
||||
# Create recipients
|
||||
recipients.createRecipients ->
|
||||
expect(recipients.length()).toEqual(recipientCount)
|
||||
stripe.tokens.create {
|
||||
card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
|
||||
}, (err, token) ->
|
||||
# Unsubscribe second recipient
|
||||
unsubscribeRecipient user1, recipients.get(1), true, ->
|
||||
User.findById user1.id, (err, user1) ->
|
||||
stripeInfo = user1.get('stripe')
|
||||
expect(stripeInfo.recipients.length).toEqual(recipientCount - 2)
|
||||
verifyNotSponsoring user1.id, recipients.get(1).id, ->
|
||||
verifyNotRecipient recipients.get(1).id, ->
|
||||
stripe.customers.retrieveSubscription stripeInfo.customerID, stripeInfo.sponsorSubscriptionID, (err, subscription) ->
|
||||
expect(err).toBeNull()
|
||||
expect(subscription).not.toBeNull()
|
||||
expect(subscription.quantity).toEqual(getSubscribedQuantity(recipientCount - 2))
|
||||
|
||||
# Create sponsor user
|
||||
loginNewUser (user1) ->
|
||||
subscribeUser user1, token, (updatedUser) ->
|
||||
User.findById user1.id, (err, user1) ->
|
||||
expect(err).toBeNull()
|
||||
# Unsubscribe self
|
||||
User.findById user1.id, (err, user1) ->
|
||||
unsubscribeUser user1, ->
|
||||
User.findById user1.id, (err, user1) ->
|
||||
stripeInfo = user1.get('stripe')
|
||||
expect(stripeInfo.planID).toBeUndefined()
|
||||
|
||||
# Subscribe recipients
|
||||
recipients.subRecipients user1, null, ->
|
||||
User.findById user1.id, (err, user1) ->
|
||||
# Unsubscribe third recipient
|
||||
verifySponsorship user1.id, recipients.get(2).id, ->
|
||||
unsubscribeRecipient user1, recipients.get(2), true, ->
|
||||
User.findById user1.id, (err, user1) ->
|
||||
stripeInfo = user1.get('stripe')
|
||||
expect(stripeInfo.recipients.length).toEqual(recipientCount - 3)
|
||||
verifyNotSponsoring user1.id, recipients.get(2).id, ->
|
||||
verifyNotRecipient recipients.get(2).id, ->
|
||||
stripe.customers.retrieveSubscription stripeInfo.customerID, stripeInfo.sponsorSubscriptionID, (err, subscription) ->
|
||||
expect(err).toBeNull()
|
||||
expect(subscription).not.toBeNull()
|
||||
expect(subscription.quantity).toEqual(getSubscribedQuantity(recipientCount - 3))
|
||||
done()
|
||||
|
||||
# Unsubscribe first recipient
|
||||
unsubscribeRecipient user1, recipients.get(0), true, ->
|
||||
User.findById user1.id, (err, user1) ->
|
||||
stripeInfo = user1.get('stripe')
|
||||
expect(stripeInfo.recipients.length).toEqual(recipientCount - 1)
|
||||
verifyNotSponsoring user1.id, recipients.get(0).id, ->
|
||||
verifyNotRecipient recipients.get(0).id, ->
|
||||
stripe.customers.retrieveSubscription stripeInfo.customerID, stripeInfo.sponsorSubscriptionID, (err, subscription) ->
|
||||
expect(err).toBeNull()
|
||||
expect(subscription).not.toBeNull()
|
||||
expect(subscription.quantity).toEqual(getSubscribedQuantity(recipientCount - 1))
|
||||
it 'Unsubscribed user1 subscribes 13 users, unsubcribes 2', (done) ->
|
||||
# TODO: verify interim invoices?
|
||||
recipientCount = 13
|
||||
recipientsToVerify = [0, 1, 10, 11, 12]
|
||||
recipients = new SubbedRecipients recipientCount, recipientsToVerify
|
||||
|
||||
# Unsubscribe second recipient
|
||||
unsubscribeRecipient user1, recipients.get(1), true, ->
|
||||
User.findById user1.id, (err, user1) ->
|
||||
stripeInfo = user1.get('stripe')
|
||||
expect(stripeInfo.recipients.length).toEqual(recipientCount - 2)
|
||||
verifyNotSponsoring user1.id, recipients.get(1).id, ->
|
||||
verifyNotRecipient recipients.get(1).id, ->
|
||||
stripe.customers.retrieveSubscription stripeInfo.customerID, stripeInfo.sponsorSubscriptionID, (err, subscription) ->
|
||||
expect(err).toBeNull()
|
||||
expect(subscription).not.toBeNull()
|
||||
expect(subscription.quantity).toEqual(getSubscribedQuantity(recipientCount - 2))
|
||||
# Create recipients
|
||||
recipients.createRecipients ->
|
||||
expect(recipients.length()).toEqual(recipientCount)
|
||||
|
||||
# Unsubscribe self
|
||||
User.findById user1.id, (err, user1) ->
|
||||
unsubscribeUser user1, ->
|
||||
User.findById user1.id, (err, user1) ->
|
||||
stripeInfo = user1.get('stripe')
|
||||
expect(stripeInfo.planID).toBeUndefined()
|
||||
stripe.tokens.create {
|
||||
card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
|
||||
}, (err, token) ->
|
||||
|
||||
# Unsubscribe third recipient
|
||||
verifySponsorship user1.id, recipients.get(2).id, ->
|
||||
unsubscribeRecipient user1, recipients.get(2), true, ->
|
||||
User.findById user1.id, (err, user1) ->
|
||||
stripeInfo = user1.get('stripe')
|
||||
expect(stripeInfo.recipients.length).toEqual(recipientCount - 3)
|
||||
verifyNotSponsoring user1.id, recipients.get(2).id, ->
|
||||
verifyNotRecipient recipients.get(2).id, ->
|
||||
stripe.customers.retrieveSubscription stripeInfo.customerID, stripeInfo.sponsorSubscriptionID, (err, subscription) ->
|
||||
expect(err).toBeNull()
|
||||
expect(subscription).not.toBeNull()
|
||||
expect(subscription.quantity).toEqual(getSubscribedQuantity(recipientCount - 3))
|
||||
done()
|
||||
# Create sponsor user
|
||||
loginNewUser (user1) ->
|
||||
|
||||
it 'Unsubscribed user1 subscribes 13 users, unsubcribes 2', (done) ->
|
||||
# TODO: verify interim invoices?
|
||||
recipientCount = 13
|
||||
recipientsToVerify = [0, 1, 10, 11, 12]
|
||||
recipients = new SubbedRecipients recipientCount, recipientsToVerify
|
||||
# Subscribe recipients
|
||||
recipients.subRecipients user1, token, ->
|
||||
User.findById user1.id, (err, user1) ->
|
||||
|
||||
# Create recipients
|
||||
recipients.createRecipients ->
|
||||
expect(recipients.length()).toEqual(recipientCount)
|
||||
# Unsubscribe first recipient
|
||||
unsubscribeRecipient user1, recipients.get(0), true, ->
|
||||
User.findById user1.id, (err, user1) ->
|
||||
stripeInfo = user1.get('stripe')
|
||||
expect(stripeInfo.recipients.length).toEqual(recipientCount - 1)
|
||||
verifyNotSponsoring user1.id, recipients.get(0).id, ->
|
||||
verifyNotRecipient recipients.get(0).id, ->
|
||||
stripe.customers.retrieveSubscription stripeInfo.customerID, stripeInfo.sponsorSubscriptionID, (err, subscription) ->
|
||||
expect(err).toBeNull()
|
||||
expect(subscription).not.toBeNull()
|
||||
expect(subscription.quantity).toEqual(getUnsubscribedQuantity(recipientCount - 1))
|
||||
|
||||
stripe.tokens.create {
|
||||
card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
|
||||
}, (err, token) ->
|
||||
|
||||
# Create sponsor user
|
||||
loginNewUser (user1) ->
|
||||
|
||||
# Subscribe recipients
|
||||
recipients.subRecipients user1, token, ->
|
||||
User.findById user1.id, (err, user1) ->
|
||||
|
||||
# Unsubscribe first recipient
|
||||
unsubscribeRecipient user1, recipients.get(0), true, ->
|
||||
User.findById user1.id, (err, user1) ->
|
||||
stripeInfo = user1.get('stripe')
|
||||
expect(stripeInfo.recipients.length).toEqual(recipientCount - 1)
|
||||
verifyNotSponsoring user1.id, recipients.get(0).id, ->
|
||||
verifyNotRecipient recipients.get(0).id, ->
|
||||
stripe.customers.retrieveSubscription stripeInfo.customerID, stripeInfo.sponsorSubscriptionID, (err, subscription) ->
|
||||
expect(err).toBeNull()
|
||||
expect(subscription).not.toBeNull()
|
||||
expect(subscription.quantity).toEqual(getUnsubscribedQuantity(recipientCount - 1))
|
||||
|
||||
# Unsubscribe last recipient
|
||||
unsubscribeRecipient user1, recipients.get(recipientCount - 1), true, ->
|
||||
User.findById user1.id, (err, user1) ->
|
||||
stripeInfo = user1.get('stripe')
|
||||
expect(stripeInfo.recipients.length).toEqual(recipientCount - 2)
|
||||
verifyNotSponsoring user1.id, recipients.get(recipientCount - 1).id, ->
|
||||
verifyNotRecipient recipients.get(recipientCount - 1).id, ->
|
||||
stripe.customers.retrieveSubscription stripeInfo.customerID, stripeInfo.sponsorSubscriptionID, (err, subscription) ->
|
||||
expect(err).toBeNull()
|
||||
expect(subscription).not.toBeNull()
|
||||
numSponsored = recipientCount - 2
|
||||
if numSponsored <= 1
|
||||
expect(subscription.quantity).toEqual(subPrice)
|
||||
else if numSponsored <= 11
|
||||
expect(subscription.quantity).toEqual(subPrice + (numSponsored - 1) * subPrice * 0.8)
|
||||
else
|
||||
expect(subscription.quantity).toEqual(subPrice + 10 * subPrice * 0.8 + (numSponsored - 11) * subPrice * 0.6)
|
||||
done()
|
||||
# Unsubscribe last recipient
|
||||
unsubscribeRecipient user1, recipients.get(recipientCount - 1), true, ->
|
||||
User.findById user1.id, (err, user1) ->
|
||||
stripeInfo = user1.get('stripe')
|
||||
expect(stripeInfo.recipients.length).toEqual(recipientCount - 2)
|
||||
verifyNotSponsoring user1.id, recipients.get(recipientCount - 1).id, ->
|
||||
verifyNotRecipient recipients.get(recipientCount - 1).id, ->
|
||||
stripe.customers.retrieveSubscription stripeInfo.customerID, stripeInfo.sponsorSubscriptionID, (err, subscription) ->
|
||||
expect(err).toBeNull()
|
||||
expect(subscription).not.toBeNull()
|
||||
numSponsored = recipientCount - 2
|
||||
if numSponsored <= 1
|
||||
expect(subscription.quantity).toEqual(subPrice)
|
||||
else if numSponsored <= 11
|
||||
expect(subscription.quantity).toEqual(subPrice + (numSponsored - 1) * subPrice * 0.8)
|
||||
else
|
||||
expect(subscription.quantity).toEqual(subPrice + 10 * subPrice * 0.8 + (numSponsored - 11) * subPrice * 0.6)
|
||||
done()
|
||||
|
|
Loading…
Reference in a new issue