mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2025-04-26 14:03:28 -04:00
Merge branch 'master' into production
This commit is contained in:
commit
76ee449693
66 changed files with 364 additions and 85 deletions
app
locale
ar.coffeebg.coffeeca.coffeecs.coffeeda.coffeede-AT.coffeede-CH.coffeede-DE.coffeeel.coffeeen-AU.coffeeen-GB.coffeeen-US.coffeeen.coffeees-419.coffeees-ES.coffeefa.coffeefi.coffeefr.coffeegl.coffeehe.coffeehi.coffeehu.coffeeid.coffeeit.coffeeja.coffeeko.coffeelt.coffeems.coffeenb.coffeenl-BE.coffeenl-NL.coffeenn.coffeeno.coffeepl.coffeept-BR.coffeept-PT.coffeero.coffeeru.coffeesk.coffeesl.coffeesr.coffeesv.coffeeth.coffeetr.coffeeuk.coffeeur.coffeevi.coffeezh-HANS.coffeezh-HANT.coffeezh-WUU-HANS.coffeezh-WUU-HANT.coffee
schemas/models
styles/play/level/tome
templates/play/level
views
account
play
server
payments
users
test/server/functional
|
@ -296,6 +296,7 @@ module.exports = nativeDescription: "العربية", englishDescription: "Arabi
|
|||
# tip_hofstadters_law: "Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law."
|
||||
# tip_premature_optimization: "Premature optimization is the root of all evil. - Donald Knuth"
|
||||
# tip_brute_force: "When in doubt, use brute force. - Ken Thompson"
|
||||
# tip_extrapolation: "There are only two kinds of people: those that can extrapolate from incomplete data..."
|
||||
# customize_wizard: "Customize Wizard"
|
||||
|
||||
# game_menu:
|
||||
|
|
|
@ -296,6 +296,7 @@ module.exports = nativeDescription: "български език", englishDescri
|
|||
# tip_hofstadters_law: "Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law."
|
||||
# tip_premature_optimization: "Premature optimization is the root of all evil. - Donald Knuth"
|
||||
# tip_brute_force: "When in doubt, use brute force. - Ken Thompson"
|
||||
# tip_extrapolation: "There are only two kinds of people: those that can extrapolate from incomplete data..."
|
||||
# customize_wizard: "Customize Wizard"
|
||||
|
||||
# game_menu:
|
||||
|
|
|
@ -296,6 +296,7 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr
|
|||
# tip_hofstadters_law: "Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law."
|
||||
# tip_premature_optimization: "Premature optimization is the root of all evil. - Donald Knuth"
|
||||
# tip_brute_force: "When in doubt, use brute force. - Ken Thompson"
|
||||
# tip_extrapolation: "There are only two kinds of people: those that can extrapolate from incomplete data..."
|
||||
customize_wizard: "Personalitza el teu bruixot"
|
||||
|
||||
game_menu:
|
||||
|
|
|
@ -296,6 +296,7 @@ module.exports = nativeDescription: "čeština", englishDescription: "Czech", tr
|
|||
# tip_hofstadters_law: "Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law."
|
||||
# tip_premature_optimization: "Premature optimization is the root of all evil. - Donald Knuth"
|
||||
# tip_brute_force: "When in doubt, use brute force. - Ken Thompson"
|
||||
# tip_extrapolation: "There are only two kinds of people: those that can extrapolate from incomplete data..."
|
||||
customize_wizard: "Upravit Kouzelníka"
|
||||
|
||||
game_menu:
|
||||
|
|
|
@ -296,6 +296,7 @@ module.exports = nativeDescription: "dansk", englishDescription: "Danish", trans
|
|||
# tip_hofstadters_law: "Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law."
|
||||
# tip_premature_optimization: "Premature optimization is the root of all evil. - Donald Knuth"
|
||||
# tip_brute_force: "When in doubt, use brute force. - Ken Thompson"
|
||||
# tip_extrapolation: "There are only two kinds of people: those that can extrapolate from incomplete data..."
|
||||
customize_wizard: "Tilpas troldmand"
|
||||
|
||||
game_menu:
|
||||
|
|
|
@ -296,6 +296,7 @@ module.exports = nativeDescription: "Deutsch (Österreich)", englishDescription:
|
|||
# tip_hofstadters_law: "Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law."
|
||||
# tip_premature_optimization: "Premature optimization is the root of all evil. - Donald Knuth"
|
||||
# tip_brute_force: "When in doubt, use brute force. - Ken Thompson"
|
||||
# tip_extrapolation: "There are only two kinds of people: those that can extrapolate from incomplete data..."
|
||||
customize_wizard: "Bearbeite den Zauberer"
|
||||
|
||||
game_menu:
|
||||
|
|
|
@ -296,6 +296,7 @@ module.exports = nativeDescription: "Deutsch (Schweiz)", englishDescription: "Ge
|
|||
# tip_hofstadters_law: "Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law."
|
||||
# tip_premature_optimization: "Premature optimization is the root of all evil. - Donald Knuth"
|
||||
# tip_brute_force: "When in doubt, use brute force. - Ken Thompson"
|
||||
# tip_extrapolation: "There are only two kinds of people: those that can extrapolate from incomplete data..."
|
||||
customize_wizard: "Zauberer apasse"
|
||||
|
||||
game_menu:
|
||||
|
|
|
@ -296,6 +296,7 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
|
|||
tip_hofstadters_law: "Hofstadter's Gesetz: Es dauert immer länger als erwartet, auch wenn du Hofstadter's Gesetz anwendest."
|
||||
tip_premature_optimization: "Vorzeitige Optimierung ist die Wurzel allen Übels (oder mindestens des meisten) bei der Programmierung - Donald Knuth"
|
||||
tip_brute_force: "Verwende im Zweifelsfall rohe Gewalt. - Ken Thompson"
|
||||
# tip_extrapolation: "There are only two kinds of people: those that can extrapolate from incomplete data..."
|
||||
customize_wizard: "Bearbeite den Zauberer"
|
||||
|
||||
game_menu:
|
||||
|
|
|
@ -296,6 +296,7 @@ module.exports = nativeDescription: "Ελληνικά", englishDescription: "Gre
|
|||
# tip_hofstadters_law: "Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law."
|
||||
# tip_premature_optimization: "Premature optimization is the root of all evil. - Donald Knuth"
|
||||
# tip_brute_force: "When in doubt, use brute force. - Ken Thompson"
|
||||
# tip_extrapolation: "There are only two kinds of people: those that can extrapolate from incomplete data..."
|
||||
customize_wizard: "Προσαρμογή Μάγου"
|
||||
|
||||
game_menu:
|
||||
|
|
|
@ -296,6 +296,7 @@ module.exports = nativeDescription: "English (AU)", englishDescription: "English
|
|||
# tip_hofstadters_law: "Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law."
|
||||
# tip_premature_optimization: "Premature optimization is the root of all evil. - Donald Knuth"
|
||||
# tip_brute_force: "When in doubt, use brute force. - Ken Thompson"
|
||||
# tip_extrapolation: "There are only two kinds of people: those that can extrapolate from incomplete data..."
|
||||
# customize_wizard: "Customize Wizard"
|
||||
|
||||
# game_menu:
|
||||
|
|
|
@ -296,6 +296,7 @@ module.exports = nativeDescription: "English (UK)", englishDescription: "English
|
|||
# tip_hofstadters_law: "Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law."
|
||||
# tip_premature_optimization: "Premature optimization is the root of all evil. - Donald Knuth"
|
||||
# tip_brute_force: "When in doubt, use brute force. - Ken Thompson"
|
||||
# tip_extrapolation: "There are only two kinds of people: those that can extrapolate from incomplete data..."
|
||||
customize_wizard: "Customise Wizard"
|
||||
|
||||
# game_menu:
|
||||
|
|
|
@ -296,6 +296,7 @@ module.exports = nativeDescription: "English (US)", englishDescription: "English
|
|||
# tip_hofstadters_law: "Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law."
|
||||
# tip_premature_optimization: "Premature optimization is the root of all evil. - Donald Knuth"
|
||||
# tip_brute_force: "When in doubt, use brute force. - Ken Thompson"
|
||||
# tip_extrapolation: "There are only two kinds of people: those that can extrapolate from incomplete data..."
|
||||
# customize_wizard: "Customize Wizard"
|
||||
|
||||
# game_menu:
|
||||
|
|
|
@ -296,6 +296,7 @@
|
|||
tip_hofstadters_law: "Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law."
|
||||
tip_premature_optimization: "Premature optimization is the root of all evil. - Donald Knuth"
|
||||
tip_brute_force: "When in doubt, use brute force. - Ken Thompson"
|
||||
tip_extrapolation: "There are only two kinds of people: those that can extrapolate from incomplete data..."
|
||||
customize_wizard: "Customize Wizard"
|
||||
|
||||
game_menu:
|
||||
|
|
|
@ -296,6 +296,7 @@ module.exports = nativeDescription: "español (América Latina)", englishDescrip
|
|||
tip_hofstadters_law: "Ley de Hofstadter: Siempre toma más tiempo del que esperas, inclso cuando tienes en cuenta la ley de Hofstadter."
|
||||
tip_premature_optimization: "La optimización prematura es la raíz de la maldad. - Donald Knuth"
|
||||
tip_brute_force: "Cuando tengas duda, usa la fuerza bruta. - Ken Thompson"
|
||||
# tip_extrapolation: "There are only two kinds of people: those that can extrapolate from incomplete data..."
|
||||
customize_wizard: "Personalizar Hechicero"
|
||||
|
||||
game_menu:
|
||||
|
|
|
@ -296,6 +296,7 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis
|
|||
# tip_hofstadters_law: "Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law."
|
||||
tip_premature_optimization: "La optimizacion prematura es la raiz de todo mal. - Donald Knuth"
|
||||
tip_brute_force: "Cuando haya dudas, usa la fuerza bruta. - Ken Thompson"
|
||||
# tip_extrapolation: "There are only two kinds of people: those that can extrapolate from incomplete data..."
|
||||
customize_wizard: "Personalizar Mago"
|
||||
|
||||
game_menu:
|
||||
|
|
|
@ -296,6 +296,7 @@ module.exports = nativeDescription: "فارسی", englishDescription: "Persian",
|
|||
# tip_hofstadters_law: "Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law."
|
||||
# tip_premature_optimization: "Premature optimization is the root of all evil. - Donald Knuth"
|
||||
# tip_brute_force: "When in doubt, use brute force. - Ken Thompson"
|
||||
# tip_extrapolation: "There are only two kinds of people: those that can extrapolate from incomplete data..."
|
||||
# customize_wizard: "Customize Wizard"
|
||||
|
||||
# game_menu:
|
||||
|
|
|
@ -296,6 +296,7 @@ module.exports = nativeDescription: "suomi", englishDescription: "Finnish", tran
|
|||
# tip_hofstadters_law: "Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law."
|
||||
# tip_premature_optimization: "Premature optimization is the root of all evil. - Donald Knuth"
|
||||
# tip_brute_force: "When in doubt, use brute force. - Ken Thompson"
|
||||
# tip_extrapolation: "There are only two kinds of people: those that can extrapolate from incomplete data..."
|
||||
# customize_wizard: "Customize Wizard"
|
||||
|
||||
# game_menu:
|
||||
|
|
|
@ -296,6 +296,7 @@ module.exports = nativeDescription: "français", englishDescription: "French", t
|
|||
tip_hofstadters_law: "Loi de Hofstadter: Il faut toujours plus de temps que prévu, même si vous prenez en compte la loi de Hofstadter."
|
||||
tip_premature_optimization: "L'optimisation prématurée est la racine de tous les maux. - Donald Knuth"
|
||||
tip_brute_force: "En cas de doute, utiliser la force brute. - Ken Thompson"
|
||||
# tip_extrapolation: "There are only two kinds of people: those that can extrapolate from incomplete data..."
|
||||
customize_wizard: "Personnaliser le magicien"
|
||||
|
||||
game_menu:
|
||||
|
|
|
@ -296,6 +296,7 @@ module.exports = nativeDescription: "Galego", englishDescription: "Galician", tr
|
|||
# tip_hofstadters_law: "Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law."
|
||||
tip_premature_optimization: "A optimizacion prematura é a raíz de todo mal. - Donald Knuth"
|
||||
tip_brute_force: "Cando hai dúbidas, usa a forza bruta. - Ken Thompson"
|
||||
# tip_extrapolation: "There are only two kinds of people: those that can extrapolate from incomplete data..."
|
||||
customize_wizard: "Persoalizar Mago"
|
||||
|
||||
game_menu:
|
||||
|
|
|
@ -296,6 +296,7 @@ module.exports = nativeDescription: "עברית", englishDescription: "Hebrew",
|
|||
# tip_hofstadters_law: "Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law."
|
||||
# tip_premature_optimization: "Premature optimization is the root of all evil. - Donald Knuth"
|
||||
# tip_brute_force: "When in doubt, use brute force. - Ken Thompson"
|
||||
# tip_extrapolation: "There are only two kinds of people: those that can extrapolate from incomplete data..."
|
||||
# customize_wizard: "Customize Wizard"
|
||||
|
||||
# game_menu:
|
||||
|
|
|
@ -296,6 +296,7 @@ module.exports = nativeDescription: "मानक हिन्दी", englishDe
|
|||
# tip_hofstadters_law: "Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law."
|
||||
# tip_premature_optimization: "Premature optimization is the root of all evil. - Donald Knuth"
|
||||
# tip_brute_force: "When in doubt, use brute force. - Ken Thompson"
|
||||
# tip_extrapolation: "There are only two kinds of people: those that can extrapolate from incomplete data..."
|
||||
# customize_wizard: "Customize Wizard"
|
||||
|
||||
# game_menu:
|
||||
|
|
|
@ -296,6 +296,7 @@ module.exports = nativeDescription: "magyar", englishDescription: "Hungarian", t
|
|||
# tip_hofstadters_law: "Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law."
|
||||
# tip_premature_optimization: "Premature optimization is the root of all evil. - Donald Knuth"
|
||||
# tip_brute_force: "When in doubt, use brute force. - Ken Thompson"
|
||||
# tip_extrapolation: "There are only two kinds of people: those that can extrapolate from incomplete data..."
|
||||
customize_wizard: "Varázsló testreszabása"
|
||||
|
||||
game_menu:
|
||||
|
|
|
@ -296,6 +296,7 @@ module.exports = nativeDescription: "Bahasa Indonesia", englishDescription: "Ind
|
|||
# tip_hofstadters_law: "Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law."
|
||||
# tip_premature_optimization: "Premature optimization is the root of all evil. - Donald Knuth"
|
||||
# tip_brute_force: "When in doubt, use brute force. - Ken Thompson"
|
||||
# tip_extrapolation: "There are only two kinds of people: those that can extrapolate from incomplete data..."
|
||||
# customize_wizard: "Customize Wizard"
|
||||
|
||||
# game_menu:
|
||||
|
|
|
@ -296,6 +296,7 @@ module.exports = nativeDescription: "Italiano", englishDescription: "Italian", t
|
|||
# tip_hofstadters_law: "Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law."
|
||||
# tip_premature_optimization: "Premature optimization is the root of all evil. - Donald Knuth"
|
||||
# tip_brute_force: "When in doubt, use brute force. - Ken Thompson"
|
||||
# tip_extrapolation: "There are only two kinds of people: those that can extrapolate from incomplete data..."
|
||||
customize_wizard: "Personalizza il mago"
|
||||
|
||||
game_menu:
|
||||
|
|
|
@ -296,6 +296,7 @@ module.exports = nativeDescription: "日本語", englishDescription: "Japanese",
|
|||
# tip_hofstadters_law: "Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law."
|
||||
# tip_premature_optimization: "Premature optimization is the root of all evil. - Donald Knuth"
|
||||
# tip_brute_force: "When in doubt, use brute force. - Ken Thompson"
|
||||
# tip_extrapolation: "There are only two kinds of people: those that can extrapolate from incomplete data..."
|
||||
customize_wizard: "魔法使いの設定"
|
||||
|
||||
game_menu:
|
||||
|
|
|
@ -296,6 +296,7 @@ module.exports = nativeDescription: "한국어", englishDescription: "Korean", t
|
|||
# tip_hofstadters_law: "Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law."
|
||||
# tip_premature_optimization: "Premature optimization is the root of all evil. - Donald Knuth"
|
||||
# tip_brute_force: "When in doubt, use brute force. - Ken Thompson"
|
||||
# tip_extrapolation: "There are only two kinds of people: those that can extrapolate from incomplete data..."
|
||||
customize_wizard: "사용자 정의 마법사"
|
||||
|
||||
game_menu:
|
||||
|
|
|
@ -296,6 +296,7 @@ module.exports = nativeDescription: "lietuvių kalba", englishDescription: "Lith
|
|||
# tip_hofstadters_law: "Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law."
|
||||
# tip_premature_optimization: "Premature optimization is the root of all evil. - Donald Knuth"
|
||||
# tip_brute_force: "When in doubt, use brute force. - Ken Thompson"
|
||||
# tip_extrapolation: "There are only two kinds of people: those that can extrapolate from incomplete data..."
|
||||
# customize_wizard: "Customize Wizard"
|
||||
|
||||
# game_menu:
|
||||
|
|
|
@ -296,6 +296,7 @@ module.exports = nativeDescription: "Bahasa Melayu", englishDescription: "Bahasa
|
|||
# tip_hofstadters_law: "Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law."
|
||||
# tip_premature_optimization: "Premature optimization is the root of all evil. - Donald Knuth"
|
||||
# tip_brute_force: "When in doubt, use brute force. - Ken Thompson"
|
||||
# tip_extrapolation: "There are only two kinds of people: those that can extrapolate from incomplete data..."
|
||||
# customize_wizard: "Customize Wizard"
|
||||
|
||||
# game_menu:
|
||||
|
|
|
@ -296,6 +296,7 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
|
|||
tip_hofstadters_law: "Hofstadters Lov: Ting tar alltid lenger tid enn du tror, selv når du tar Hofstadters Lov med i beregningen."
|
||||
# tip_premature_optimization: "Premature optimization is the root of all evil. - Donald Knuth"
|
||||
# tip_brute_force: "When in doubt, use brute force. - Ken Thompson"
|
||||
# tip_extrapolation: "There are only two kinds of people: those that can extrapolate from incomplete data..."
|
||||
customize_wizard: "Tilpass Trollmann"
|
||||
|
||||
game_menu:
|
||||
|
|
|
@ -296,6 +296,7 @@ module.exports = nativeDescription: "Nederlands (België)", englishDescription:
|
|||
# tip_hofstadters_law: "Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law."
|
||||
# tip_premature_optimization: "Premature optimization is the root of all evil. - Donald Knuth"
|
||||
# tip_brute_force: "When in doubt, use brute force. - Ken Thompson"
|
||||
# tip_extrapolation: "There are only two kinds of people: those that can extrapolate from incomplete data..."
|
||||
customize_wizard: "Pas Tovenaar aan"
|
||||
|
||||
game_menu:
|
||||
|
|
|
@ -296,6 +296,7 @@ module.exports = nativeDescription: "Nederlands (Nederland)", englishDescription
|
|||
# tip_hofstadters_law: "Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law."
|
||||
# tip_premature_optimization: "Premature optimization is the root of all evil. - Donald Knuth"
|
||||
# tip_brute_force: "When in doubt, use brute force. - Ken Thompson"
|
||||
# tip_extrapolation: "There are only two kinds of people: those that can extrapolate from incomplete data..."
|
||||
customize_wizard: "Pas Tovenaar aan"
|
||||
|
||||
game_menu:
|
||||
|
|
|
@ -296,6 +296,7 @@ module.exports = nativeDescription: "Norwegian Nynorsk", englishDescription: "No
|
|||
# tip_hofstadters_law: "Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law."
|
||||
# tip_premature_optimization: "Premature optimization is the root of all evil. - Donald Knuth"
|
||||
# tip_brute_force: "When in doubt, use brute force. - Ken Thompson"
|
||||
# tip_extrapolation: "There are only two kinds of people: those that can extrapolate from incomplete data..."
|
||||
# customize_wizard: "Customize Wizard"
|
||||
|
||||
# game_menu:
|
||||
|
|
|
@ -296,6 +296,7 @@ module.exports = nativeDescription: "Norsk", englishDescription: "Norwegian", tr
|
|||
# tip_hofstadters_law: "Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law."
|
||||
# tip_premature_optimization: "Premature optimization is the root of all evil. - Donald Knuth"
|
||||
# tip_brute_force: "When in doubt, use brute force. - Ken Thompson"
|
||||
# tip_extrapolation: "There are only two kinds of people: those that can extrapolate from incomplete data..."
|
||||
customize_wizard: "Tilpass trollmann"
|
||||
|
||||
game_menu:
|
||||
|
|
|
@ -296,6 +296,7 @@ module.exports = nativeDescription: "język polski", englishDescription: "Polish
|
|||
# tip_hofstadters_law: "Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law."
|
||||
# tip_premature_optimization: "Premature optimization is the root of all evil. - Donald Knuth"
|
||||
# tip_brute_force: "When in doubt, use brute force. - Ken Thompson"
|
||||
# tip_extrapolation: "There are only two kinds of people: those that can extrapolate from incomplete data..."
|
||||
customize_wizard: "Spersonalizuj czarodzieja"
|
||||
|
||||
game_menu:
|
||||
|
|
|
@ -296,6 +296,7 @@ module.exports = nativeDescription: "português do Brasil", englishDescription:
|
|||
tip_hofstadters_law: "Lei de Hofstadter: Sempre demora mais do que você espera, mesmo quando você leva em consideração a Lei de Hofstadter."
|
||||
tip_premature_optimization: "Uma otimização permatura é a raíz de todos os males. - Donald Knuth"
|
||||
tip_brute_force: "Na dúvida, utilize força bruta. - Ken Thompson"
|
||||
# tip_extrapolation: "There are only two kinds of people: those that can extrapolate from incomplete data..."
|
||||
customize_wizard: "Personalize o feiticeiro"
|
||||
|
||||
game_menu:
|
||||
|
|
|
@ -296,6 +296,7 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
|
|||
tip_hofstadters_law: "Lei de Hofstadter: Tudo demora sempre mais do que pensas, mesmo quando levas em conta a Lei de Hofstadter."
|
||||
tip_premature_optimization: "Uma otimização permatura é a raíz de todo o mal. - Donald Knuth"
|
||||
tip_brute_force: "Quando em dúvida, usa a força bruta. - Ken Thompson"
|
||||
# tip_extrapolation: "There are only two kinds of people: those that can extrapolate from incomplete data..."
|
||||
customize_wizard: "Personalizar Feiticeiro"
|
||||
|
||||
game_menu:
|
||||
|
|
|
@ -296,6 +296,7 @@ module.exports = nativeDescription: "limba română", englishDescription: "Roman
|
|||
# tip_hofstadters_law: "Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law."
|
||||
# tip_premature_optimization: "Premature optimization is the root of all evil. - Donald Knuth"
|
||||
# tip_brute_force: "When in doubt, use brute force. - Ken Thompson"
|
||||
# tip_extrapolation: "There are only two kinds of people: those that can extrapolate from incomplete data..."
|
||||
customize_wizard: "Personalizează Wizard-ul"
|
||||
|
||||
game_menu:
|
||||
|
|
|
@ -296,6 +296,7 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
|
|||
tip_hofstadters_law: "Закон Хофштадтера: Любое дело всегда длится дольше, чем ожидается, даже если учесть закон Хофштадтера."
|
||||
tip_premature_optimization: "Поспешная оптимизация - корень всех зол. - Donald Knuth"
|
||||
tip_brute_force: "Когда сомневаешься используй грубую силу. - Кен Томпсон"
|
||||
# tip_extrapolation: "There are only two kinds of people: those that can extrapolate from incomplete data..."
|
||||
customize_wizard: "Настройки волшебника"
|
||||
|
||||
game_menu:
|
||||
|
@ -376,13 +377,13 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
|
|||
blocks: "Блокирует" # As in "this shield blocks this much damage"
|
||||
skills: "Умения"
|
||||
available_for_purchase: "Доступно для покупки"
|
||||
level_to_unlock: "Разблокируется на уровне:" # ToDo: check in interface
|
||||
level_to_unlock: "Разблокируется на уровне:"
|
||||
restricted_to_certain_heroes: "Только определенные герои могут играть этот уровень."
|
||||
|
||||
skill_docs:
|
||||
# writable: "writable" # Hover over "attack" in Your Skills while playing a level to see most of this
|
||||
# read_only: "read-only"
|
||||
action_name: "имя" # ToDo: check in interface
|
||||
action_name: "имя"
|
||||
action_cooldown: "Применяется"
|
||||
action_specific_cooldown: "Восстановление"
|
||||
action_damage: "Повреждения"
|
||||
|
|
|
@ -296,6 +296,7 @@ module.exports = nativeDescription: "slovenčina", englishDescription: "Slovak",
|
|||
# tip_hofstadters_law: "Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law."
|
||||
# tip_premature_optimization: "Premature optimization is the root of all evil. - Donald Knuth"
|
||||
# tip_brute_force: "When in doubt, use brute force. - Ken Thompson"
|
||||
# tip_extrapolation: "There are only two kinds of people: those that can extrapolate from incomplete data..."
|
||||
# customize_wizard: "Customize Wizard"
|
||||
|
||||
# game_menu:
|
||||
|
|
|
@ -296,6 +296,7 @@ module.exports = nativeDescription: "slovenščina", englishDescription: "Sloven
|
|||
# tip_hofstadters_law: "Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law."
|
||||
# tip_premature_optimization: "Premature optimization is the root of all evil. - Donald Knuth"
|
||||
# tip_brute_force: "When in doubt, use brute force. - Ken Thompson"
|
||||
# tip_extrapolation: "There are only two kinds of people: those that can extrapolate from incomplete data..."
|
||||
# customize_wizard: "Customize Wizard"
|
||||
|
||||
# game_menu:
|
||||
|
|
|
@ -296,6 +296,7 @@ module.exports = nativeDescription: "српски", englishDescription: "Serbian
|
|||
# tip_hofstadters_law: "Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law."
|
||||
# tip_premature_optimization: "Premature optimization is the root of all evil. - Donald Knuth"
|
||||
# tip_brute_force: "When in doubt, use brute force. - Ken Thompson"
|
||||
# tip_extrapolation: "There are only two kinds of people: those that can extrapolate from incomplete data..."
|
||||
customize_wizard: "Прилагоди Чаробњака"
|
||||
|
||||
game_menu:
|
||||
|
|
|
@ -296,6 +296,7 @@ module.exports = nativeDescription: "Svenska", englishDescription: "Swedish", tr
|
|||
# tip_hofstadters_law: "Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law."
|
||||
# tip_premature_optimization: "Premature optimization is the root of all evil. - Donald Knuth"
|
||||
# tip_brute_force: "When in doubt, use brute force. - Ken Thompson"
|
||||
# tip_extrapolation: "There are only two kinds of people: those that can extrapolate from incomplete data..."
|
||||
customize_wizard: "Skräddarsy trollkarl"
|
||||
|
||||
game_menu:
|
||||
|
|
|
@ -296,6 +296,7 @@ module.exports = nativeDescription: "ไทย", englishDescription: "Thai", tra
|
|||
# tip_hofstadters_law: "Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law."
|
||||
# tip_premature_optimization: "Premature optimization is the root of all evil. - Donald Knuth"
|
||||
# tip_brute_force: "When in doubt, use brute force. - Ken Thompson"
|
||||
# tip_extrapolation: "There are only two kinds of people: those that can extrapolate from incomplete data..."
|
||||
# customize_wizard: "Customize Wizard"
|
||||
|
||||
game_menu:
|
||||
|
|
|
@ -296,6 +296,7 @@ module.exports = nativeDescription: "Türkçe", englishDescription: "Turkish", t
|
|||
# tip_hofstadters_law: "Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law."
|
||||
# tip_premature_optimization: "Premature optimization is the root of all evil. - Donald Knuth"
|
||||
# tip_brute_force: "When in doubt, use brute force. - Ken Thompson"
|
||||
# tip_extrapolation: "There are only two kinds of people: those that can extrapolate from incomplete data..."
|
||||
customize_wizard: "Sihirbazı Düzenle"
|
||||
|
||||
game_menu:
|
||||
|
|
|
@ -296,6 +296,7 @@ module.exports = nativeDescription: "українська мова", englishDesc
|
|||
# tip_hofstadters_law: "Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law."
|
||||
# tip_premature_optimization: "Premature optimization is the root of all evil. - Donald Knuth"
|
||||
# tip_brute_force: "When in doubt, use brute force. - Ken Thompson"
|
||||
# tip_extrapolation: "There are only two kinds of people: those that can extrapolate from incomplete data..."
|
||||
customize_wizard: "Налаштування персонажа"
|
||||
|
||||
game_menu:
|
||||
|
|
|
@ -296,6 +296,7 @@ module.exports = nativeDescription: "اُردُو", englishDescription: "Urdu",
|
|||
# tip_hofstadters_law: "Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law."
|
||||
# tip_premature_optimization: "Premature optimization is the root of all evil. - Donald Knuth"
|
||||
# tip_brute_force: "When in doubt, use brute force. - Ken Thompson"
|
||||
# tip_extrapolation: "There are only two kinds of people: those that can extrapolate from incomplete data..."
|
||||
# customize_wizard: "Customize Wizard"
|
||||
|
||||
# game_menu:
|
||||
|
|
|
@ -296,6 +296,7 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
|
|||
# tip_hofstadters_law: "Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law."
|
||||
# tip_premature_optimization: "Premature optimization is the root of all evil. - Donald Knuth"
|
||||
# tip_brute_force: "When in doubt, use brute force. - Ken Thompson"
|
||||
# tip_extrapolation: "There are only two kinds of people: those that can extrapolate from incomplete data..."
|
||||
customize_wizard: "Tùy chỉnh Wizard"
|
||||
|
||||
game_menu:
|
||||
|
|
|
@ -296,6 +296,7 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
|
|||
tip_hofstadters_law: "侯世达定律:做事所花费的时间总是比你预期的要长,即使你的预期中考虑了侯世达定律。"
|
||||
tip_premature_optimization: "过早的优化是万恶之源。 - 高德纳"
|
||||
tip_brute_force: "拿不准时就用穷举法。 - Ken Thompson"
|
||||
# tip_extrapolation: "There are only two kinds of people: those that can extrapolate from incomplete data..."
|
||||
customize_wizard: "自定义向导"
|
||||
|
||||
game_menu:
|
||||
|
|
|
@ -296,6 +296,7 @@ module.exports = nativeDescription: "繁体中文", englishDescription: "Chinese
|
|||
# tip_hofstadters_law: "Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law."
|
||||
# tip_premature_optimization: "Premature optimization is the root of all evil. - Donald Knuth"
|
||||
# tip_brute_force: "When in doubt, use brute force. - Ken Thompson"
|
||||
# tip_extrapolation: "There are only two kinds of people: those that can extrapolate from incomplete data..."
|
||||
customize_wizard: "自定義巫師"
|
||||
|
||||
game_menu:
|
||||
|
|
|
@ -296,6 +296,7 @@ module.exports = nativeDescription: "吴语", englishDescription: "Wuu (Simplifi
|
|||
# tip_hofstadters_law: "Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law."
|
||||
# tip_premature_optimization: "Premature optimization is the root of all evil. - Donald Knuth"
|
||||
# tip_brute_force: "When in doubt, use brute force. - Ken Thompson"
|
||||
# tip_extrapolation: "There are only two kinds of people: those that can extrapolate from incomplete data..."
|
||||
# customize_wizard: "Customize Wizard"
|
||||
|
||||
# game_menu:
|
||||
|
|
|
@ -296,6 +296,7 @@ module.exports = nativeDescription: "吳語", englishDescription: "Wuu (Traditio
|
|||
# tip_hofstadters_law: "Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law."
|
||||
# tip_premature_optimization: "Premature optimization is the root of all evil. - Donald Knuth"
|
||||
# tip_brute_force: "When in doubt, use brute force. - Ken Thompson"
|
||||
# tip_extrapolation: "There are only two kinds of people: those that can extrapolate from incomplete data..."
|
||||
customize_wizard: "自設定獻路人"
|
||||
|
||||
game_menu:
|
||||
|
|
|
@ -278,6 +278,9 @@ _.extend UserSchema.properties,
|
|||
planID: { enum: ['basic'] }
|
||||
subscriptionID: { type: 'string' }
|
||||
token: { type: 'string' }
|
||||
couponID: { type: 'string' }
|
||||
discountID: { type: 'string' }
|
||||
free: { type: ['boolean', 'string'], format: 'date-time' }
|
||||
}
|
||||
|
||||
c.extendBasicProperties UserSchema, 'user'
|
||||
|
|
|
@ -107,6 +107,9 @@
|
|||
.executed
|
||||
background-color: rgba(110, 110, 110, 0.12)
|
||||
|
||||
.locked-code
|
||||
border: 1px dashed rgba(53, 45, 34, 0.5)
|
||||
|
||||
+keyframes(pulseRedBackground)
|
||||
from
|
||||
background-color: rgba(255, 45, 27, 0.4)
|
||||
|
|
|
@ -52,6 +52,7 @@
|
|||
strong.tip.rare(data-i18n='play_level.tip_hofstadters_law') Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law.
|
||||
strong.tip.rare(data-i18n='play_level.tip_premature_optimization') Premature optimization is the root of all evil - Donald Knuth
|
||||
strong.tip.rare(data-i18n='play_level.tip_brute_force') When in doubt, use brute force. - Ken Thompson
|
||||
strong.tip.rare(data-i18n='play_level.tip_extrapolation') There are only two kinds of people: those that can extrapolate from incomplete data...
|
||||
strong.tip.rare
|
||||
span(data-i18n='play_level.tip_harry') Yer a Wizard,
|
||||
span= me.get('name', true)
|
||||
|
|
|
@ -30,6 +30,7 @@ module.exports = class PaymentsView extends RootView
|
|||
onClickStartSubscription: (e) ->
|
||||
@openModalView new SubscribeModal()
|
||||
window.tracker?.trackEvent 'Show subscription modal', category: 'Subscription', label: 'payments view'
|
||||
window.tracker?.trackPageView "subscription/show-modal", ['Google Analytics']
|
||||
|
||||
onSubscribed: ->
|
||||
document.location.reload()
|
||||
|
|
|
@ -124,6 +124,7 @@ module.exports = class WorldMapView extends RootView
|
|||
@openModalView? new modal() unless window.currentModal
|
||||
if modal is SubscribeModal
|
||||
window.tracker?.trackEvent 'Show subscription modal', category: 'Subscription', label: 'world map loadded'
|
||||
window.tracker?.trackPageView "subscription/show-modal", ['Google Analytics']
|
||||
, 2000
|
||||
|
||||
onSubscribed: ->
|
||||
|
@ -214,6 +215,7 @@ module.exports = class WorldMapView extends RootView
|
|||
@openModalView new modal()
|
||||
if modal is SubscribeModal
|
||||
window.tracker?.trackEvent 'Show subscription modal', category: 'Subscription', label: 'map level clicked'
|
||||
window.tracker?.trackPageView "subscription/show-modal", ['Google Analytics']
|
||||
else if $(e.target).attr('disabled')
|
||||
Backbone.Mediator.publish 'router:navigate', route: '/contribute/adventurer'
|
||||
return
|
||||
|
@ -222,11 +224,13 @@ module.exports = class WorldMapView extends RootView
|
|||
else
|
||||
@startLevel levelElement
|
||||
window.tracker?.trackEvent 'Clicked Level', category: 'World Map', levelID: levelID, ['Google Analytics']
|
||||
window.tracker?.trackPageView "world-map/clicked-level/#{levelID}", ['Google Analytics']
|
||||
|
||||
onClickStartLevel: (e) ->
|
||||
levelElement = $(e.target).parents('.level-info-container')
|
||||
@startLevel levelElement
|
||||
window.tracker?.trackEvent 'Clicked Start Level', category: 'World Map', levelID: levelElement.data('level-id'), ['Google Analytics']
|
||||
window.tracker?.trackPageView "world-map/clicked-start-level/#{levelElement.data('level-id')}", ['Google Analytics']
|
||||
|
||||
startLevel: (levelElement) ->
|
||||
@setupManager?.destroy()
|
||||
|
|
|
@ -114,6 +114,7 @@ module.exports = class LevelLoadingView extends CocoView
|
|||
@openModalView new modal()
|
||||
if modal is SubscribeModal
|
||||
window.tracker?.trackEvent 'Show subscription modal', category: 'Subscription', label: 'level loading'
|
||||
window.tracker?.trackPageView "subscription/show-modal", ['Google Analytics']
|
||||
|
||||
onSubscribed: ->
|
||||
document.location.reload()
|
||||
|
|
|
@ -273,6 +273,15 @@ module.exports = class SpellView extends CocoView
|
|||
return true for range in @readOnlyRanges when leftRange.intersects(range)
|
||||
false
|
||||
|
||||
intersectsRight = =>
|
||||
rightRange = @ace.getSelectionRange().clone()
|
||||
if rightRange.end.column < @aceDoc.getLine(rightRange.end.row).length
|
||||
rightRange.setEnd rightRange.end.row, rightRange.end.column + 1
|
||||
else if rightRange.start.row < @aceDoc.getLength() - 1
|
||||
rightRange.setEnd rightRange.end.row + 1, 0
|
||||
return true for range in @readOnlyRanges when rightRange.intersects(range)
|
||||
false
|
||||
|
||||
preventReadonly = (next) ->
|
||||
return true if intersects()
|
||||
next?()
|
||||
|
@ -284,27 +293,16 @@ module.exports = class SpellView extends CocoView
|
|||
wrapper => orig.apply obj, args
|
||||
obj[method]
|
||||
|
||||
finishRange = (row, startRow, startColumn) =>
|
||||
range = new Range startRow, startColumn, row, @aceSession.getLine(row).length - 1
|
||||
range.start = @aceDoc.createAnchor range.start
|
||||
range.end = @aceDoc.createAnchor range.end
|
||||
range.end.$insertRight = true
|
||||
@readOnlyRanges.push range
|
||||
if @lockedCodeMarkerID?
|
||||
@aceSession.removeMarker @lockedCodeMarkerID
|
||||
@lockedCodeMarkerID = null
|
||||
|
||||
# Create a read-only range for each chunk of text not separated by an empty line
|
||||
@readOnlyRanges = []
|
||||
startRow = startColumn = null
|
||||
for row in [0...@aceSession.getLength()]
|
||||
unless /^\s*$/.test @aceSession.getLine(row)
|
||||
unless startRow? and startColumn?
|
||||
startRow = row
|
||||
startColumn = 0
|
||||
else
|
||||
if startRow? and startColumn?
|
||||
finishRange row - 1, startRow, startColumn
|
||||
startRow = startColumn = null
|
||||
if startRow? and startColumn?
|
||||
finishRange @aceSession.getLength() - 1, startRow, startColumn
|
||||
lines = @aceDoc.getAllLines()
|
||||
lastRow = row for line, row in lines when not /^\s*$/.test(line)
|
||||
if lastRow?
|
||||
@readOnlyRanges.push new Range 0, 0, lastRow, lines[lastRow].length - 1
|
||||
@lockedCodeMarkerID = @aceSession.addMarker @readOnlyRanges[0], 'locked-code', 'fullLine'
|
||||
|
||||
# Override write operations that intersect with default code
|
||||
interceptCommand @ace, 'onPaste', preventReadonly
|
||||
|
@ -319,6 +317,9 @@ module.exports = class SpellView extends CocoView
|
|||
if e.command.name in ['Backspace', 'throttle-backspaces'] and intersectsLeft()
|
||||
@zatanna?.off?()
|
||||
return false
|
||||
if e.command.name is 'del' and intersectsRight()
|
||||
@zatanna?.off?()
|
||||
return false
|
||||
if e.command.name in ['enter-skip-delimiters', 'Enter', 'Return']
|
||||
if intersects()
|
||||
e.editor.navigateDown 1
|
||||
|
|
|
@ -45,10 +45,12 @@ module.exports = class SubscribeModal extends ModalView
|
|||
container: @$el
|
||||
).on 'shown.bs.popover', =>
|
||||
application.tracker?.trackEvent 'Subscription parent hover', {}
|
||||
application.tracker?.trackPageView "subscription/parent-hover", ['Google Analytics']
|
||||
|
||||
onClickPurchaseButton: (e) ->
|
||||
@playSound 'menu-button-click'
|
||||
application.tracker?.trackEvent 'Started subscription purchase', {}
|
||||
application.tracker?.trackPageView "subscription/start-purchase", ['Google Analytics']
|
||||
stripeHandler.open({
|
||||
description: $.i18n.t('subscribe.stripe_description')
|
||||
amount: @product.amount
|
||||
|
@ -69,6 +71,7 @@ module.exports = class SubscribeModal extends ModalView
|
|||
|
||||
onSubscriptionSuccess: ->
|
||||
application.tracker?.trackEvent 'Finished subscription purchase', {}
|
||||
application.tracker?.trackPageView "subscription/finish-purchase", ['Google Analytics']
|
||||
Backbone.Mediator.publish 'subscribe-modal:subscribed', {}
|
||||
@playSound 'victory'
|
||||
@hide()
|
||||
|
|
48
server/payments/discount_handler.coffee
Normal file
48
server/payments/discount_handler.coffee
Normal file
|
@ -0,0 +1,48 @@
|
|||
# Not paired with a document in the DB, just handles coordinating between
|
||||
# the stripe property in the user with what's being stored in Stripe.
|
||||
|
||||
Handler = require '../commons/Handler'
|
||||
config = require '../../server_config'
|
||||
stripe = require('stripe')(config.stripe.secretKey)
|
||||
|
||||
class DiscountHandler extends Handler
|
||||
logDiscountError: (req, msg) ->
|
||||
console.warn "Discount Error: #{req.user.get('slug')} (#{req.user._id}): '#{msg}'"
|
||||
|
||||
discountUser: (req, user, done) ->
|
||||
if (not user) or user.isAnonymous()
|
||||
return done({res: 'User must not be anonymous.', code: 403})
|
||||
|
||||
couponID = req.body.stripe.couponID
|
||||
if not couponID
|
||||
@logDiscountError(req, 'Missing couponID.')
|
||||
return done({res: 'Missing couponID.', code: 422})
|
||||
|
||||
stripe.coupons.retrieve couponID, (err, coupon) =>
|
||||
if (err)
|
||||
return done({res: 'No coupon with id '+couponID, code: 404})
|
||||
|
||||
if customerID = user.get('stripe')?.customerID
|
||||
options = { coupon: coupon.id }
|
||||
stripe.customers.update customerID, options, (err, customer) =>
|
||||
if err
|
||||
@logDiscountError(req, 'Error applying coupon to customer'+customerID)
|
||||
return done({res: 'Error applying coupon to customer.', code: 500})
|
||||
done()
|
||||
|
||||
else
|
||||
# couponID will be set on the user by the handler
|
||||
done()
|
||||
|
||||
removeDiscountFromCustomer: (req, user, done) ->
|
||||
customerID = user.get('stripe').customerID
|
||||
return done() unless customerID
|
||||
|
||||
stripe.customers.deleteDiscount customerID, (err, customer) =>
|
||||
if err
|
||||
console.log 'err?', err
|
||||
@logDiscountError(req, 'Error removing coupon from customer ' + customerID)
|
||||
return done({res: 'Error applying coupon to customer.', code: 500})
|
||||
done()
|
||||
|
||||
module.exports = new DiscountHandler()
|
|
@ -166,29 +166,34 @@ PaymentHandler = class PaymentHandler extends Handler
|
|||
handleStripePaymentPost: (req, res, timestamp, productID, token) ->
|
||||
|
||||
# First, make sure we save the payment info as a Customer object, if we haven't already.
|
||||
if not req.user.get('stripe')?.customerID
|
||||
stripe.customers.create({
|
||||
card: token
|
||||
email: req.user.get('email')
|
||||
metadata: {
|
||||
id: req.user._id + ''
|
||||
slug: req.user.get('slug')
|
||||
}
|
||||
}).then(((customer) =>
|
||||
stripeInfo = _.cloneDeep(req.user.get('stripe') ? {})
|
||||
stripeInfo.customerID = customer.id
|
||||
req.user.set('stripe', stripeInfo)
|
||||
req.user.save((err) =>
|
||||
if err
|
||||
@logPaymentError(req, 'Stripe customer id save db error. '+err)
|
||||
return @sendDatabaseError(res, err)
|
||||
if token
|
||||
customerID = req.user.get('stripe')?.customerID
|
||||
|
||||
if customerID
|
||||
# old customer, new token. Save it.
|
||||
stripe.customers.update customerID, { card: token }, (err, customer) =>
|
||||
@beginStripePayment(req, res, timestamp, productID)
|
||||
)
|
||||
),
|
||||
(err) =>
|
||||
@logPaymentError(req, 'Stripe customer creation error. '+err)
|
||||
return @sendDatabaseError(res, err)
|
||||
)
|
||||
|
||||
else
|
||||
newCustomer = {
|
||||
card: token
|
||||
email: req.user.get('email')
|
||||
metadata: { id: req.user._id + '', slug: req.user.get('slug') }
|
||||
}
|
||||
|
||||
stripe.customers.create newCustomer, (err, customer) =>
|
||||
if err
|
||||
@logPaymentError(req, 'Stripe customer creation error. '+err)
|
||||
return @sendDatabaseError(res, err)
|
||||
|
||||
stripeInfo = _.cloneDeep(req.user.get('stripe') ? {})
|
||||
stripeInfo.customerID = customer.id
|
||||
req.user.set('stripe', stripeInfo)
|
||||
req.user.save (err) =>
|
||||
if err
|
||||
@logPaymentError(req, 'Stripe customer id save db error. '+err)
|
||||
return @sendDatabaseError(res, err)
|
||||
@beginStripePayment(req, res, timestamp, productID)
|
||||
|
||||
else
|
||||
@beginStripePayment(req, res, timestamp, productID)
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
Handler = require '../commons/Handler'
|
||||
config = require '../../server_config'
|
||||
stripe = require('stripe')(config.stripe.secretKey)
|
||||
discountHandler = require './discount_handler'
|
||||
|
||||
subscriptions = {
|
||||
basic: {
|
||||
|
@ -19,61 +20,52 @@ class SubscriptionHandler extends Handler
|
|||
if (not req.user) or req.user.isAnonymous()
|
||||
return done({res: 'You must be signed in to subscribe.', code: 403})
|
||||
|
||||
stripeToken = req.body.stripe?.token
|
||||
extantCustomerID = user.get('stripe')?.customerID
|
||||
if not (stripeToken or extantCustomerID)
|
||||
token = req.body.stripe?.token
|
||||
customerID = user.get('stripe')?.customerID
|
||||
if not (token or customerID)
|
||||
@logSubscriptionError(req, 'Missing stripe token or customer ID.')
|
||||
return done({res: 'Missing stripe token or customer ID.', code: 422})
|
||||
|
||||
if stripeToken
|
||||
stripe.customers.create({
|
||||
card: stripeToken
|
||||
email: req.user.get('email')
|
||||
metadata: {
|
||||
id: req.user._id + ''
|
||||
slug: req.user.get('slug')
|
||||
if token
|
||||
if customerID
|
||||
stripe.customers.update customerID, { card: token }, (err, customer) =>
|
||||
@checkForExistingSubscription(req, user, customer, done)
|
||||
|
||||
else
|
||||
newCustomer = {
|
||||
card: token
|
||||
email: req.user.get('email')
|
||||
metadata: { id: req.user._id + '', slug: req.user.get('slug') }
|
||||
}
|
||||
}).then(((customer) =>
|
||||
|
||||
stripe.customers.create newCustomer, (err, customer) =>
|
||||
if err
|
||||
if err.type in ['StripeCardError', 'StripeInvalidRequestError']
|
||||
return done({res: 'Card error', code: 402})
|
||||
else
|
||||
@logSubscriptionError(req, 'Stripe customer creation error. '+err)
|
||||
return done({res: 'Database error.', code: 500})
|
||||
|
||||
stripeInfo = _.cloneDeep(req.user.get('stripe') ? {})
|
||||
stripeInfo.customerID = customer.id
|
||||
req.user.set('stripe', stripeInfo)
|
||||
req.user.save((err) =>
|
||||
req.user.save (err) =>
|
||||
if err
|
||||
@logSubscriptionError(req, 'Stripe customer id save db error. '+err)
|
||||
return done({res: 'Database error.', code: 500})
|
||||
@checkForExistingSubscription(req, user, customer, done)
|
||||
)
|
||||
),
|
||||
(err) =>
|
||||
if err.type in ['StripeCardError', 'StripeInvalidRequestError']
|
||||
done({res: 'Card error', code: 402})
|
||||
else
|
||||
@logSubscriptionError(req, 'Stripe customer creation error. '+err)
|
||||
return done({res: 'Database error.', code: 500})
|
||||
)
|
||||
|
||||
else
|
||||
stripe.customers.retrieve(extantCustomerID, (err, customer) =>
|
||||
stripe.customers.retrieve(customerID, (err, customer) =>
|
||||
if err
|
||||
@logSubscriptionError(req, 'Stripe customer creation error. '+err)
|
||||
return done({res: 'Database error.', code: 500})
|
||||
else if not customer
|
||||
# TODO: what actually happens when you try to retrieve a customer and it DNE?
|
||||
@logSubscriptionError(req, 'Stripe customer id is missing! '+err)
|
||||
stripeInfo = _.cloneDeep(req.user.get('stripe') ? {})
|
||||
delete stripeInfo.customerID
|
||||
req.user.set('stripe', stripeInfo)
|
||||
req.user.save (err) =>
|
||||
if err
|
||||
@logSubscriptionError(req, 'Stripe customer id delete db error. '+err)
|
||||
return done({res: 'Database error.', code: 500})
|
||||
@subscribeUser(req, done)
|
||||
else
|
||||
@checkForExistingSubscription(req, user, customer, done)
|
||||
@checkForExistingSubscription(req, user, customer, done)
|
||||
)
|
||||
|
||||
|
||||
checkForExistingSubscription: (req, user, customer, done) ->
|
||||
couponID = user.get('stripe')?.couponID
|
||||
if subscription = customer.subscriptions?.data?[0]
|
||||
|
||||
if subscription.cancel_at_period_end
|
||||
|
@ -87,30 +79,34 @@ class SubscriptionHandler extends Handler
|
|||
return done({res: 'Database error.', code: 500})
|
||||
|
||||
options = { plan: 'basic', trial_end: subscription.current_period_end }
|
||||
options.coupon = couponID if couponID
|
||||
stripe.customers.update req.user.get('stripe').customerID, options, (err, customer) =>
|
||||
if err
|
||||
@logSubscriptionError(req, 'Stripe customer plan setting error. '+err)
|
||||
return done({res: 'Database error.', code: 500})
|
||||
|
||||
@updateUser(req, user, customer.subscriptions.data[0], false, done)
|
||||
@updateUser(req, user, customer, false, done)
|
||||
|
||||
else
|
||||
# can skip creating the subscription
|
||||
return @updateUser(req, user, customer.subscriptions.data[0], false, done)
|
||||
return @updateUser(req, user, customer, false, done)
|
||||
|
||||
else
|
||||
stripe.customers.update req.user.get('stripe').customerID, { plan: 'basic' }, (err, customer) =>
|
||||
options = { plan: 'basic' }
|
||||
options.coupon = couponID if couponID
|
||||
stripe.customers.update req.user.get('stripe').customerID, options, (err, customer) =>
|
||||
if err
|
||||
@logSubscriptionError(req, 'Stripe customer plan setting error. '+err)
|
||||
return done({res: 'Database error.', code: 500})
|
||||
|
||||
@updateUser(req, user, customer.subscriptions.data[0], true, done)
|
||||
@updateUser(req, user, customer, true, done)
|
||||
|
||||
updateUser: (req, user, subscription, increment, done) ->
|
||||
updateUser: (req, user, customer, increment, done) ->
|
||||
subscription = customer.subscriptions.data[0]
|
||||
stripeInfo = _.cloneDeep(user.get('stripe') ? {})
|
||||
stripeInfo.planID = 'basic'
|
||||
stripeInfo.subscriptionID = subscription.id
|
||||
stripeInfo.customerID = subscription.customer
|
||||
stripeInfo.customerID = customer.id
|
||||
req.body.stripe = stripeInfo # to make sure things work for admins, who are mad with power
|
||||
user.set('stripe', stripeInfo)
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ moment = require 'moment'
|
|||
LevelSession = require '../levels/sessions/LevelSession'
|
||||
LevelSessionHandler = require '../levels/sessions/level_session_handler'
|
||||
SubscriptionHandler = require '../payments/subscription_handler'
|
||||
DiscountHandler = require '../payments/discount_handler'
|
||||
EarnedAchievement = require '../achievements/EarnedAchievement'
|
||||
UserRemark = require './remarks/UserRemark'
|
||||
{isID} = require '../lib/utils'
|
||||
|
@ -123,6 +124,25 @@ UserHandler = class UserHandler extends Handler
|
|||
return callback(err) if err
|
||||
return callback(null, req, user)
|
||||
)
|
||||
|
||||
# Discount setting
|
||||
(req, user, callback) ->
|
||||
return callback(null, req, user) unless req.user?.isAdmin()
|
||||
hasCoupon = user.get('stripe')?.couponID
|
||||
wantsCoupon = req.body.stripe?.couponID
|
||||
|
||||
return callback(null, req, user) if hasCoupon is wantsCoupon
|
||||
if wantsCoupon and (hasCoupon isnt wantsCoupon)
|
||||
DiscountHandler.discountUser(req, user, (err) ->
|
||||
return callback(err) if err
|
||||
return callback(null, req, user)
|
||||
)
|
||||
else if hasCoupon and not wantsCoupon
|
||||
DiscountHandler.removeDiscountFromCustomer(req, user, (err) ->
|
||||
return callback(err) if err
|
||||
return callback(null, req, user)
|
||||
)
|
||||
|
||||
]
|
||||
|
||||
getById: (req, res, id) ->
|
||||
|
|
|
@ -56,7 +56,6 @@ setupExpressMiddleware = (app) ->
|
|||
express.logger.format('prod', productionLogging)
|
||||
app.use(express.logger('prod'))
|
||||
app.use express.compress filter: (req, res) ->
|
||||
return false if req.headers.host is 'codecombat.com' # CloudFlare will gzip it for us on codecombat.com # But now it's disabled.
|
||||
compressible res.getHeader('Content-Type')
|
||||
else
|
||||
express.logger.format('dev', developmentLogging)
|
||||
|
|
114
test/server/functional/discount_handler.spec.coffee
Normal file
114
test/server/functional/discount_handler.spec.coffee
Normal file
|
@ -0,0 +1,114 @@
|
|||
|
||||
config = require '../../../server_config'
|
||||
require '../common'
|
||||
|
||||
# sample data that comes in through the webhook when you subscribe
|
||||
|
||||
|
||||
describe '/db/user, editing stripe.couponID property', ->
|
||||
|
||||
stripe = require('stripe')(config.stripe.secretKey)
|
||||
userURL = getURL('/db/user')
|
||||
webhookURL = getURL('/stripe/webhook')
|
||||
|
||||
it 'clears the db first', (done) ->
|
||||
clearModels [User, Payment], (err) ->
|
||||
throw err if err
|
||||
done()
|
||||
|
||||
#- shared data between tests
|
||||
joeData = null
|
||||
firstSubscriptionID = null
|
||||
|
||||
it 'does not work for non-admins', (done) ->
|
||||
loginJoe (joe) ->
|
||||
joeData = joe.toObject()
|
||||
expect(joeData.stripe).toBeUndefined()
|
||||
joeData.stripe = { couponID: '20pct' }
|
||||
request.put {uri: userURL, json: joeData }, (err, res, body) ->
|
||||
expect(res.statusCode).toBe(200) # fails silently
|
||||
expect(res.body.stripe).toBeUndefined() # but still fails
|
||||
done()
|
||||
|
||||
it 'does not work with invalid coupons', (done) ->
|
||||
loginAdmin (admin) ->
|
||||
joeData.stripe = { couponID: 'DNE' }
|
||||
request.put {uri: userURL, json: joeData }, (err, res, body) ->
|
||||
expect(res.statusCode).toBe(404)
|
||||
done()
|
||||
|
||||
it 'sets the couponID on a user without an existing stripe object', (done) ->
|
||||
joeData.stripe = { couponID: '20pct' }
|
||||
request.put {uri: userURL, json: joeData }, (err, res, body) ->
|
||||
joeData = body
|
||||
expect(res.statusCode).toBe(200)
|
||||
expect(body.stripe.couponID).toBe('20pct')
|
||||
done()
|
||||
|
||||
it 'just updates the couponID when it changes and there is no existing subscription', (done) ->
|
||||
joeData.stripe.couponID = '500off'
|
||||
request.put {uri: userURL, json: joeData }, (err, res, body) ->
|
||||
expect(res.statusCode).toBe(200)
|
||||
expect(body.stripe.couponID).toBe('500off')
|
||||
done()
|
||||
|
||||
it 'removes the couponID from the user when the admin makes it so', (done) ->
|
||||
delete joeData.stripe.couponID
|
||||
request.put {uri: userURL, json: joeData }, (err, res, body) ->
|
||||
joeData = body
|
||||
expect(res.statusCode).toBe(200)
|
||||
expect(body.stripe).toBeUndefined()
|
||||
done()
|
||||
|
||||
it 'puts the coupon back', (done) ->
|
||||
joeData.stripe = {couponID: '500off'}
|
||||
request.put {uri: userURL, json: joeData }, (err, res, body) ->
|
||||
expect(res.statusCode).toBe(200)
|
||||
expect(body.stripe.couponID).toBe('500off')
|
||||
done()
|
||||
|
||||
it 'applies a discount to the newly created customer when a plan is set', (done) ->
|
||||
stripe.tokens.create {
|
||||
card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
|
||||
}, (err, token) ->
|
||||
stripeTokenID = token.id
|
||||
loginJoe (joe) ->
|
||||
joeData.stripe.token = stripeTokenID
|
||||
joeData.stripe.planID = 'basic'
|
||||
request.put {uri: userURL, json: joeData }, (err, res, body) ->
|
||||
joeData = body
|
||||
expect(res.statusCode).toBe(200)
|
||||
stripe.customers.retrieve joeData.stripe.customerID, (err, customer) ->
|
||||
expect(customer.discount).toBeDefined()
|
||||
expect(customer.discount.coupon.id).toBe('500off')
|
||||
done()
|
||||
|
||||
|
||||
it 'updates the discount on the customer when an admin changes the couponID', (done) ->
|
||||
loginAdmin (admin) ->
|
||||
joeData.stripe.couponID = '20pct'
|
||||
request.put {uri: userURL, json: joeData }, (err, res, body) ->
|
||||
expect(res.statusCode).toBe(200)
|
||||
expect(body.stripe.couponID).toBe('20pct')
|
||||
stripe.customers.retrieve joeData.stripe.customerID, (err, customer) ->
|
||||
expect(customer.discount.coupon.id).toBe('20pct')
|
||||
done()
|
||||
|
||||
it 'removes discounts from the customer when an admin removes the couponID', (done) ->
|
||||
delete joeData.stripe.couponID
|
||||
request.put {uri: userURL, json: joeData }, (err, res, body) ->
|
||||
expect(res.statusCode).toBe(200)
|
||||
expect(body.stripe.couponID).toBeUndefined()
|
||||
stripe.customers.retrieve joeData.stripe.customerID, (err, customer) ->
|
||||
expect(customer.discount).toBe(null)
|
||||
done()
|
||||
|
||||
it 'adds a discount to the customer when an admin adds the couponID', (done) ->
|
||||
joeData.stripe.couponID = '20pct'
|
||||
request.put {uri: userURL, json: joeData }, (err, res, body) ->
|
||||
expect(res.statusCode).toBe(200)
|
||||
expect(body.stripe.couponID).toBe('20pct')
|
||||
stripe.customers.retrieve joeData.stripe.customerID, (err, customer) ->
|
||||
expect(customer.discount.coupon.id).toBe('20pct')
|
||||
done()
|
||||
|
|
@ -82,6 +82,7 @@ describe '/db/payment', ->
|
|||
joeID = null
|
||||
timestamp = new Date().getTime()
|
||||
stripeTokenID = null
|
||||
joeData = null
|
||||
|
||||
it 'clears the db first', (done) ->
|
||||
clearModels [User, Payment], (err) ->
|
||||
|
@ -131,6 +132,34 @@ describe '/db/payment', ->
|
|||
done()
|
||||
)
|
||||
)
|
||||
|
||||
it 'allows a new charge on the existing customer', (done) ->
|
||||
data = { productID: 'gems_5', stripe: { timestamp: new Date().getTime() } }
|
||||
request.post {uri: paymentURL, json: data }, (err, res, body) ->
|
||||
expect(res.statusCode).toBe 201
|
||||
Payment.count {}, (err, count) ->
|
||||
expect(count).toBe(2)
|
||||
User.findById joeID, (err, user) ->
|
||||
joeData = user.toObject()
|
||||
expect(user.get('purchased').gems).toBe(10000)
|
||||
done()
|
||||
|
||||
it "updates the customer's card when you submit a new token", (done) ->
|
||||
stripe.customers.retrieve joeData.stripe.customerID, (err, customer) ->
|
||||
originalCustomerID = customer.id
|
||||
originalCardID = customer.cards.data[0].id
|
||||
stripe.tokens.create {
|
||||
card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
|
||||
}, (err, token) ->
|
||||
data = { productID: 'gems_5', stripe: { timestamp: new Date().getTime(), token: token.id } }
|
||||
request.post {uri: paymentURL, json: data }, (err, res, body) ->
|
||||
expect(res.statusCode).toBe(201)
|
||||
User.findById joeID, (err, user) ->
|
||||
joeData = user.toObject()
|
||||
expect(joeData.stripe.customerID).toBe(originalCustomerID)
|
||||
stripe.customers.retrieve joeData.stripe.customerID, (err, customer) ->
|
||||
expect(customer.cards.data[0].id).not.toBe(originalCardID)
|
||||
done()
|
||||
|
||||
it 'clears the db', (done) ->
|
||||
clearModels [User, Payment], (err) ->
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue