diff --git a/test/api/unit/libs/payments/payments.test.js b/test/api/unit/libs/payments/payments.test.js index 82fb3dabad..dbed33741f 100644 --- a/test/api/unit/libs/payments/payments.test.js +++ b/test/api/unit/libs/payments/payments.test.js @@ -209,7 +209,7 @@ describe('payments/index', () => { await api.createSubscription(data); let msg = '\`Hello recipient, sender has sent you 3 months of subscription!\`'; - expect(user.sendMessage).to.be.calledOnce; + expect(user.sendMessage).to.be.calledTwice; expect(user.sendMessage).to.be.calledWith(recipient, { receiverMsg: msg, senderMsg: msg, save: false }); }); @@ -247,6 +247,77 @@ describe('payments/index', () => { }, }); }); + + context('Winter 2018-19 Gift-1-Get-1 Promotion', async () => { + it('creates a gift subscription for purchaser and recipient if none exist', async () => { + await api.createSubscription(data); + + expect(user.items.pets['Jackalope-RoyalPurple']).to.eql(5); + expect(user.purchased.plan.customerId).to.eql('Gift'); + expect(user.purchased.plan.dateTerminated).to.exist; + expect(user.purchased.plan.dateUpdated).to.exist; + expect(user.purchased.plan.dateCreated).to.exist; + + expect(recipient.items.pets['Jackalope-RoyalPurple']).to.eql(5); + expect(recipient.purchased.plan.customerId).to.eql('Gift'); + expect(recipient.purchased.plan.dateTerminated).to.exist; + expect(recipient.purchased.plan.dateUpdated).to.exist; + expect(recipient.purchased.plan.dateCreated).to.exist; + }); + + it('adds extraMonths to existing subscription for purchaser and creates a gift subscription for recipient without sub', async () => { + user.purchased.plan = plan; + + expect(user.purchased.plan.extraMonths).to.eql(0); + + await api.createSubscription(data); + + expect(user.purchased.plan.extraMonths).to.eql(3); + + expect(recipient.items.pets['Jackalope-RoyalPurple']).to.eql(5); + expect(recipient.purchased.plan.customerId).to.eql('Gift'); + expect(recipient.purchased.plan.dateTerminated).to.exist; + expect(recipient.purchased.plan.dateUpdated).to.exist; + expect(recipient.purchased.plan.dateCreated).to.exist; + }); + + it('adds extraMonths to existing subscription for recipient and creates a gift subscription for purchaser without sub', async () => { + recipient.purchased.plan = plan; + + expect(recipient.purchased.plan.extraMonths).to.eql(0); + + await api.createSubscription(data); + + expect(recipient.purchased.plan.extraMonths).to.eql(3); + + expect(user.items.pets['Jackalope-RoyalPurple']).to.eql(5); + expect(user.purchased.plan.customerId).to.eql('Gift'); + expect(user.purchased.plan.dateTerminated).to.exist; + expect(user.purchased.plan.dateUpdated).to.exist; + expect(user.purchased.plan.dateCreated).to.exist; + }); + + it('adds extraMonths to existing subscriptions for purchaser and recipient', async () => { + user.purchased.plan = plan; + recipient.purchased.plan = plan; + + expect(user.purchased.plan.extraMonths).to.eql(0); + expect(recipient.purchased.plan.extraMonths).to.eql(0); + + await api.createSubscription(data); + + expect(user.purchased.plan.extraMonths).to.eql(3); + expect(recipient.purchased.plan.extraMonths).to.eql(3); + }); + + it('sends a private message about the promotion', async () => { + await api.createSubscription(data); + let msg = '\`Hello sender, you received 3 months of subscription as part of our holiday gift-giving promotion!\`'; + + expect(user.sendMessage).to.be.calledTwice; + expect(user.sendMessage).to.be.calledWith(user, { senderMsg: msg }); + }); + }); }); context('Purchasing a subscription for self', () => { diff --git a/website/client/app.vue b/website/client/app.vue index a53f71c705..5652cf21e0 100644 --- a/website/client/app.vue +++ b/website/client/app.vue @@ -16,18 +16,24 @@ div router-view(v-if="!isUserLoggedIn || isStaticPage") template(v-else) template(v-if="isUserLoaded") - div.resting-banner(v-show="showRestingBanner", ref="restingBanner") + .resting-banner(v-show="showRestingBanner", ref="restingBanner") span.content span.label.d-inline.d-sm-none {{ $t('innCheckOutBannerShort') }} span.label.d-none.d-sm-inline {{ $t('innCheckOutBanner') }} span.separator | span.resume(@click="resumeDamage()") {{ $t('resumeDamage') }} - div.closepadding(@click="hideBanner()") + .closepadding(@click="hideBanner()") + span.svg-icon.inline.icon-10(aria-hidden="true", v-html="icons.close") + .g1g1-banner.d-flex.justify-content-center.align-items-center(v-if="!giftingHidden") + .svg-icon.svg-gifts.left-gift(v-html="icons.gifts") + router-link(:to="{name: 'subscription'}") {{ $t('g1g1Announcement') }} + .svg-icon.svg-gifts.right-gift(v-html="icons.gifts") + .closepadding(@click="hideGiftingBanner()") span.svg-icon.inline.icon-10(aria-hidden="true", v-html="icons.close") notifications-display - app-menu(:class='{"restingInn": showRestingBanner}' :style="{ marginTop: bannerHeight + 'px' }") + app-menu .container-fluid - app-header(:class='{"restingInn": showRestingBanner}') + app-header buyModal( :item="selectedItemToBuy || {}", :withPin="true", @@ -50,6 +56,13 @@ div - - + + diff --git a/website/client/assets/css/sprites/spritesmith-largeSprites-0.css b/website/client/assets/css/sprites/spritesmith-largeSprites-0.css index 7106bc8ecd..d6a218b52d 100644 --- a/website/client/assets/css/sprites/spritesmith-largeSprites-0.css +++ b/website/client/assets/css/sprites/spritesmith-largeSprites-0.css @@ -1,6 +1,6 @@ .achievement-costumeContest6x { background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png'); - background-position: -1136px -438px; + background-position: -740px -420px; width: 144px; height: 156px; } @@ -34,6 +34,12 @@ width: 417px; height: 147px; } +.promo_g1g1 { + background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png'); + background-position: -1136px -867px; + width: 237px; + height: 150px; +} .promo_ios { background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png'); background-position: 0px -361px; @@ -42,22 +48,34 @@ } .promo_mystery_201811 { background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png'); - background-position: -1136px -142px; + background-position: -1136px -571px; width: 282px; height: 147px; } .promo_piyo { background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png'); - background-position: -1136px -290px; + background-position: -1136px -719px; width: 279px; height: 147px; } +.promo_studying { + background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png'); + background-position: -1136px -142px; + width: 220px; + height: 232px; +} .promo_take_this { background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png'); - background-position: -1281px -438px; + background-position: -1357px -142px; width: 96px; height: 69px; } +.promo_todos { + background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png'); + background-position: -1136px -375px; + width: 240px; + height: 195px; +} .promo_turkey_day_2018 { background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png'); background-position: 0px -871px; @@ -90,7 +108,7 @@ } .scene_veteran_pets { background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png'); - background-position: -1136px -595px; + background-position: -740px -577px; width: 242px; height: 62px; } diff --git a/website/client/assets/images/sprites/spritesmith-largeSprites-0.png b/website/client/assets/images/sprites/spritesmith-largeSprites-0.png index 7622716ed8..04a0c42c99 100644 Binary files a/website/client/assets/images/sprites/spritesmith-largeSprites-0.png and b/website/client/assets/images/sprites/spritesmith-largeSprites-0.png differ diff --git a/website/client/assets/svg/close.svg b/website/client/assets/svg/close.svg index 5070218c06..19115eaea2 100644 --- a/website/client/assets/svg/close.svg +++ b/website/client/assets/svg/close.svg @@ -1,5 +1,5 @@ - + diff --git a/website/client/assets/svg/gifts.svg b/website/client/assets/svg/gifts.svg new file mode 100644 index 0000000000..31aad16b88 --- /dev/null +++ b/website/client/assets/svg/gifts.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/website/client/components/payments/buyGemsModal.vue b/website/client/components/payments/buyGemsModal.vue index 47d80dc5f1..523aa4bf42 100644 --- a/website/client/components/payments/buyGemsModal.vue +++ b/website/client/components/payments/buyGemsModal.vue @@ -2,180 +2,172 @@ mixin featureBullet (text) .row .col-md-2.offset-1 - .bubble.mx-auto - .svg-icon.check(v-html='icons.check') + .d-flex.bubble.justify-content-center.align-items-center + .svg-icon.check.mx-auto(v-html='icons.check') .col-md-8.align-self-center p=text div(v-if='user') - b-modal(:hide-footer='true', :hide-header='true', :id='"buy-gems"', size='lg') - .container-fluid.purple-gradient - .gemfall + b-modal#buy-gems(:hide-footer='true', :id='"buy-gems"', size='lg') + .header-wrap(slot='modal-header') + .image-gemfall .row - h2.text-invert.mx-auto {{ $t('support') }} + h2.header-invert.mx-auto {{ $t('support') }} .row .logo.svg-icon.mx-auto(v-html="icons.logo") - .container-fluid - .row - .col-6.offset-3.nav - .nav-item(@click='selectedPage = "subscribe"', :class="{active: selectedPage === 'subscribe'}") {{ $t('subscribe') }} - .nav-item(@click='selectedPage = "gems"', :class="{active: selectedPage === 'gems'}") {{ $t('buyGems') }} - div(v-show='selectedPage === "gems"') - div(v-if='hasSubscription') - .row.text-center - h2.mx-auto.text-leadin {{ $t('subscriptionAlreadySubscribedLeadIn') }} - .row.text-center - .col-6.offset-3 - p {{ $t("gemsPurchaseNote") }} + .d-flex.nav.justify-content-center + .nav-item.text-center(@click='selectedPage = "subscribe"', :class="{active: selectedPage === 'subscribe'}") {{ $t('subscribe') }} + .nav-item.text-center(@click='selectedPage = "gems"', :class="{active: selectedPage === 'gems'}") {{ $t('buyGems') }} + div(v-show='selectedPage === "gems"') + div(v-if='hasSubscription') .row.text-center - h2.mx-auto.text-leadin {{ $t('gemBenefitLeadin') }} + h2.mx-auto.text-leadin {{ $t('subscriptionAlreadySubscribedLeadIn') }} + .row.text-center + .col-6.offset-3 + p {{ $t("gemsPurchaseNote") }} + .row.text-center + h2.mx-auto.text-leadin {{ $t('gemBenefitLeadin') }} + .row + .col + +featureBullet("{{ $t('gemBenefit1') }}") + +featureBullet("{{ $t('gemBenefit2') }}") + .col + +featureBullet("{{ $t('gemBenefit3') }}") + +featureBullet("{{ $t('gemBenefit4') }}") + .card-deck.gem-deck + .card.text-center.col-3(:class="{active: gemAmount === 20 }") + .card-img-top + .mx-auto(v-html='icons.twentyOneGems', style='"height: 55px; width: 47.5px; margin-top: 1.85em;"') + .card-body + .gem-count 20 + .gem-text {{ $t('gems') }} + .divider + button.btn.btn-primary(@click='gemAmount === 20 ? gemAmount = 0 : gemAmount = 20') {{gemAmount === 20 ? $t('selected') : '$5.00'}} + .row.text-center + h2.mx-auto.text-payment {{ $t('choosePaymentMethod') }} + .card-deck + .card.text-center.payment-method(@click='showStripe({})') + .card-body + .mx-auto(v-html='icons.creditCard', style='"height: 56px; width: 159px; margin-top: 1em;"') + .card.text-center.payment-method + a.card-body.paypal(@click="openPaypal(paypalCheckoutLink, 'gems')") + img(src='~assets/images/paypal.png') + .card.text-center.payment-method(@click="amazonPaymentsInit({type: 'single'})") + .card-body.amazon + img(src='~assets/images/amazon-payments.png') + .row.text-center + .svg-icon.mx-auto(v-html='icons.heart', style='"height: 24px; width: 24px;"') + .row.text-center.text-outtro + .col-6.offset-3 {{ $t('buyGemsSupportsDevs') }} + + div(v-show='selectedPage === "subscribe"') + .g1g1-promo.d-flex.justify-content-center.align-items-center + .svg-icon.svg-gifts.left-gift(v-html="icons.gifts") + .text-center + strong.gift-text {{ $t('g1g1Announcement') }} + .gift-text {{ $t('g1g1Details') }} + .svg-icon.svg-gifts.right-gift(v-html="icons.gifts") + div(v-if='hasSubscription') + .row.text-center + h2.mx-auto.text-leadin {{ $t('subscriptionAlreadySubscribedLeadIn') }} + .row.text-center + .col-10.offset-1 + p(v-html='$t("subscriptionAlreadySubscribed1")') + div(v-if='!hasSubscription') + .row.text-center + h2.mx-auto.text-leadin {{ $t('subscriptionBenefitLeadin') }} .row .col - +featureBullet("{{ $t('gemBenefit1') }}") - +featureBullet("{{ $t('gemBenefit2') }}") + +featureBullet("{{ $t('subscriptionBenefit1') }}") + +featureBullet("{{ $t('subscriptionBenefit2') }}") + +featureBullet("{{ $t('subscriptionBenefit3') }}") .col - +featureBullet("{{ $t('gemBenefit3') }}") - +featureBullet("{{ $t('gemBenefit4') }}") - .card-deck.gem-deck - //.card.text-center(:class="{active: gemAmount === 4}") - .card-img-top - .mx-auto(v-html='icons.fourGems', style='"height: 53px; width: 49.5px; margin-top: 2em;"') - .card-body - .gem-count 4 - .gem-text {{ $t('gems') }} - .divider - button.btn.btn-primary(@click='gemAmount = 4') {{gemAmount === 4 ? $t('selected') : '$1.00'}} - .card.text-center.col-3(:class="{active: gemAmount === 20 }") - .card-img-top - .mx-auto(v-html='icons.twentyOneGems', style='"height: 55px; width: 47.5px; margin-top: 1.85em;"') - .card-body - .gem-count 20 - .gem-text {{ $t('gems') }} - .divider - button.btn.btn-primary(@click='gemAmount === 20 ? gemAmount = 0 : gemAmount = 20') {{gemAmount === 20 ? $t('selected') : '$5.00'}} - //.card.text-center(:class="{active: gemAmount === 42}") - .card-img-top - .mx-auto(v-html='icons.fortyTwoGems', style='"height: 49.5px; width: 51px; margin-top: 1.9em;"') - .card-body - .gem-count 42 - .gem-text {{ $t('gems') }} - .divider - button.btn.btn-primary(@click='gemAmount = 42') {{gemAmount === 42 ? $t('selected') : '$10.00'}} - //.card.text-center(:class="{active: gemAmount === 84}") - .card-img-top - .mx-auto(v-html='icons.eightyFourGems', style='"height: 65px; width: 67px; margin-top: 1em;"') - .card-body - .gem-count 84 - .gem-text {{ $t('gems') }} - .divider - button.btn.btn-primary(@click='gemAmount = 84') {{gemAmount === 84 ? $t('selected') : '$20.00'}} - .row.text-center - h2.mx-auto.text-payment {{ $t('choosePaymentMethod') }} + +featureBullet("{{ $t('subscriptionBenefit4') }}") + +featureBullet("{{ $t('subscriptionBenefit5') }}") + +featureBullet("{{ $t('subscriptionBenefit6') }}") .card-deck - .card.text-center.payment-method(@click='showStripe({})') + .card.text-center(:class='{active: subscriptionPlan === "basic_earned"}') .card-body + .subscription-price + span.superscript $ + span 5 + span.superscript.muted .00 + .small(v-once) {{ $t('everyMonth') }} + .divider + p.benefits(v-markdown='$t("earnGemsMonthly", {cap:25})') + .spacer + button.btn.btn-primary(@click='subscriptionPlan = "basic_earned"') {{ subscriptionPlan === "basic_earned" ? $t('selected') : $t('select') }} + .card.text-center(:class='{active: subscriptionPlan === "basic_3mo"}') + .card-body + .subscription-price + span.superscript $ + span 15 + span.superscript.muted .00 + .small(v-once) {{ $t('everyXMonths', {interval: 3}) }} + .divider + p.benefits(v-markdown='$t("earnGemsMonthly", {cap:30})') + p.benefits(v-markdown='$t("receiveMysticHourglass")') + button.btn.btn-primary(@click='subscriptionPlan = "basic_3mo"') {{ subscriptionPlan === "basic_3mo" ? $t('selected') : $t('select') }} + .card.text-center(:class='{active: subscriptionPlan === "basic_6mo"}') + .card-body + .subscription-price + span.superscript $ + span 30 + span.superscript.muted .00 + .small(v-once) {{ $t('everyXMonths', {interval: 6}) }} + .divider + p.benefits(v-markdown='$t("earnGemsMonthly", {cap:35})') + p.benefits(v-markdown='$t("receiveMysticHourglasses", {amount:2})') + button.btn.btn-primary(@click='subscriptionPlan = "basic_6mo"') {{ subscriptionPlan === "basic_6mo" ? $t('selected') : $t('select') }} + .card.text-center(:class='{active: subscriptionPlan === "basic_12mo"}') + .card-body + .subscription-price + span.superscript $ + span 48 + span.superscript.muted .00 + .small(v-once) {{ $t('everyYear') }} + .divider + p.benefits(v-markdown='$t("earnGemsMonthly", {cap:45})') + p.benefits(v-markdown='$t("receiveMysticHourglasses", {amount:4})') + button.btn.btn-primary(@click='subscriptionPlan = "basic_12mo"') {{ subscriptionPlan === "basic_12mo" ? $t('selected') : $t('select') }} + .row.text-center(v-if='subscriptionPlan') + h2.mx-auto.text-payment(v-once) {{ $t('choosePaymentMethod') }} + .row.text-center + a.mx-auto(v-once) {{ $t('haveCouponCode') }} + .card-deck(v-if='subscriptionPlan') + .card.text-center.payment-method + .card-body(@click='showStripe({subscription: subscriptionPlan})') .mx-auto(v-html='icons.creditCard', style='"height: 56px; width: 159px; margin-top: 1em;"') .card.text-center.payment-method - a.card-body.paypal(@click="openPaypal(paypalCheckoutLink, 'gems')") + a.card-body.paypal(@click="openPaypal(paypalSubscriptionLink, 'subscription')") img(src='~assets/images/paypal.png') - .card.text-center.payment-method(@click="amazonPaymentsInit({type: 'single'})") - .card-body.amazon + .card.text-center.payment-method + .card-body.amazon(@click="amazonPaymentsInit({type: 'subscription', subscription: subscriptionPlan})") img(src='~assets/images/amazon-payments.png') .row.text-center .svg-icon.mx-auto(v-html='icons.heart', style='"height: 24px; width: 24px;"') .row.text-center.text-outtro - .col-6.offset-3 {{ $t('buyGemsSupportsDevs') }} - - div(v-show='selectedPage === "subscribe"') - div(v-if='hasSubscription') - .row.text-center - h2.mx-auto.text-leadin {{ $t('subscriptionAlreadySubscribedLeadIn') }} - .row.text-center - .col - p(v-html='$t("subscriptionAlreadySubscribed1")') - div(v-if='!hasSubscription') - .row.text-center - h2.mx-auto.text-leadin {{ $t('subscriptionBenefitLeadin') }} - .row - .col - +featureBullet("{{ $t('subscriptionBenefit1') }}") - +featureBullet("{{ $t('subscriptionBenefit2') }}") - +featureBullet("{{ $t('subscriptionBenefit3') }}") - .col - +featureBullet("{{ $t('subscriptionBenefit4') }}") - +featureBullet("{{ $t('subscriptionBenefit5') }}") - +featureBullet("{{ $t('subscriptionBenefit6') }}") - .card-deck - .card.text-center(:class='{active: subscriptionPlan === "basic_earned"}') - .card-body - .subscription-price - span.superscript $ - span 5 - span.superscript.muted .00 - .small(v-once) {{ $t('everyMonth') }} - .divider - p.benefits(v-markdown='$t("earnGemsMonthly", {cap:25})') - .spacer - button.btn.btn-primary(@click='subscriptionPlan = "basic_earned"') {{ subscriptionPlan === "basic_earned" ? $t('selected') : $t('select') }} - .card.text-center(:class='{active: subscriptionPlan === "basic_3mo"}') - .card-body - .subscription-price - span.superscript $ - span 15 - span.superscript.muted .00 - .small(v-once) {{ $t('everyXMonths', {interval: 3}) }} - .divider - p.benefits(v-markdown='$t("earnGemsMonthly", {cap:30})') - p.benefits(v-markdown='$t("receiveMysticHourglass")') - button.btn.btn-primary(@click='subscriptionPlan = "basic_3mo"') {{ subscriptionPlan === "basic_3mo" ? $t('selected') : $t('select') }} - .card.text-center(:class='{active: subscriptionPlan === "basic_6mo"}') - .card-body - .subscription-price - span.superscript $ - span 30 - span.superscript.muted .00 - .small(v-once) {{ $t('everyXMonths', {interval: 6}) }} - .divider - p.benefits(v-markdown='$t("earnGemsMonthly", {cap:35})') - p.benefits(v-markdown='$t("receiveMysticHourglasses", {amount:2})') - button.btn.btn-primary(@click='subscriptionPlan = "basic_6mo"') {{ subscriptionPlan === "basic_6mo" ? $t('selected') : $t('select') }} - .card.text-center(:class='{active: subscriptionPlan === "basic_12mo"}') - .card-body - .subscription-price - span.superscript $ - span 48 - span.superscript.muted .00 - .small(v-once) {{ $t('everyYear') }} - .divider - p.benefits(v-markdown='$t("earnGemsMonthly", {cap:45})') - p.benefits(v-markdown='$t("receiveMysticHourglasses", {amount:4})') - button.btn.btn-primary(@click='subscriptionPlan = "basic_12mo"') {{ subscriptionPlan === "basic_12mo" ? $t('selected') : $t('select') }} - .row.text-center(v-if='subscriptionPlan') - h2.mx-auto.text-payment(v-once) {{ $t('choosePaymentMethod') }} - .row.text-center - a.mx-auto(v-once) {{ $t('haveCouponCode') }} - .card-deck(v-if='subscriptionPlan') - .card.text-center.payment-method - .card-body(@click='showStripe({subscription: subscriptionPlan})') - .mx-auto(v-html='icons.creditCard', style='"height: 56px; width: 159px; margin-top: 1em;"') - .card.text-center.payment-method - a.card-body.paypal(@click="openPaypal(paypalSubscriptionLink, 'subscription')") - img(src='~assets/images/paypal.png') - .card.text-center.payment-method - .card-body.amazon(@click="amazonPaymentsInit({type: 'subscription', subscription: subscriptionPlan})") - img(src='~assets/images/amazon-payments.png') - .row.text-center - .svg-icon.mx-auto(v-html='icons.heart', style='"height: 24px; width: 24px;"') - .row.text-center.text-outtro - .col-6.offset-3 {{ $t('subscribeSupportsDevs') }} + .col-6.offset-3 {{ $t('subscribeSupportsDevs') }}